diff --git a/core/src/main/java/haveno/core/app/AppStartupState.java b/core/src/main/java/haveno/core/app/AppStartupState.java index 89fe61576a..5a00a9087f 100644 --- a/core/src/main/java/haveno/core/app/AppStartupState.java +++ b/core/src/main/java/haveno/core/app/AppStartupState.java @@ -69,7 +69,7 @@ public class AppStartupState { }); xmrWalletService.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> { - if (xmrWalletService.isDownloadComplete()) + if (xmrWalletService.wasWalletSynced()) isWalletSynced.set(true); }); diff --git a/core/src/main/java/haveno/core/support/SupportManager.java b/core/src/main/java/haveno/core/support/SupportManager.java index 940650d969..7f8260f3ec 100644 --- a/core/src/main/java/haveno/core/support/SupportManager.java +++ b/core/src/main/java/haveno/core/support/SupportManager.java @@ -387,7 +387,7 @@ public abstract class SupportManager { p2PService.isBootstrapped() && xmrConnectionService.isDownloadComplete() && xmrConnectionService.hasSufficientPeersForBroadcast() && - xmrWalletService.isDownloadComplete(); + xmrWalletService.wasWalletSynced(); } diff --git a/core/src/main/java/haveno/core/xmr/setup/DownloadListener.java b/core/src/main/java/haveno/core/xmr/setup/DownloadListener.java index 8e70245ca7..325328a6e6 100644 --- a/core/src/main/java/haveno/core/xmr/setup/DownloadListener.java +++ b/core/src/main/java/haveno/core/xmr/setup/DownloadListener.java @@ -10,6 +10,7 @@ import java.util.Date; public class DownloadListener { private final DoubleProperty percentage = new SimpleDoubleProperty(-1); + // TODO: remove redundant execute? public void progress(double percentage, long blocksLeft, Date date) { UserThread.execute(() -> { UserThread.await(() -> this.percentage.set(percentage)); // TODO: these awaits are jenky diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletBase.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletBase.java index fcf6b24544..91b1e471fd 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletBase.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletBase.java @@ -19,6 +19,7 @@ import haveno.core.api.XmrConnectionService; import haveno.core.trade.HavenoUtils; import haveno.core.xmr.setup.DownloadListener; import javafx.beans.property.LongProperty; +import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.SimpleLongProperty; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -50,13 +51,14 @@ public abstract class XmrWalletBase { protected boolean wasWalletSynced; protected final Map> txCache = new HashMap>(); protected boolean isClosingWallet; + protected boolean isSyncingWithoutProgress; protected boolean isSyncingWithProgress; protected Long syncStartHeight; protected TaskLooper syncProgressLooper; protected CountDownLatch syncProgressLatch; protected Exception syncProgressError; protected Timer syncProgressTimeout; - protected final DownloadListener downloadListener = new DownloadListener(); + protected final DownloadListener walletSyncListener = new DownloadListener(); protected final LongProperty walletHeight = new SimpleLongProperty(0); @Getter protected boolean isShutDownStarted; @@ -82,9 +84,12 @@ public abstract class XmrWalletBase { ExecutorService executor = Executors.newSingleThreadExecutor(); Callable task = () -> { - MoneroSyncResult result = wallet.sync(); - saveWalletIfElapsedTime(); + if (isSyncing()) log.warn("Syncing without progress while already syncing. That should never happen."); + isSyncingWithoutProgress = true; walletHeight.set(wallet.getHeight()); + MoneroSyncResult result = wallet.sync(); + walletHeight.set(wallet.getHeight()); + wasWalletSynced = true; return result; }; @@ -101,6 +106,8 @@ public abstract class XmrWalletBase { Thread.currentThread().interrupt(); // restore interrupt status throw new RuntimeException("Sync was interrupted", e); } finally { + isSyncingWithoutProgress = false; + saveWalletIfElapsedTime(); executor.shutdownNow(); } } @@ -116,13 +123,13 @@ public abstract class XmrWalletBase { try { // set initial state - if (isSyncingWithProgress) log.warn("Syncing with progress while already syncing with progress. That should never happen"); + if (isSyncing()) log.warn("Syncing with progress while already syncing. That should never happen."); resetSyncProgressTimeout(); isSyncingWithProgress = true; + walletSyncListener.progress(0, -1, null); // reset progress syncProgressError = null; long targetHeightAtStart = xmrConnectionService.getTargetHeight(); - syncStartHeight = walletHeight.get(); - updateSyncProgress(syncStartHeight, targetHeightAtStart); + updateSyncProgress(walletHeight.get(), targetHeightAtStart); // test connection changing on startup before wallet synced if (testReconnectOnStartup) { @@ -144,7 +151,7 @@ public abstract class XmrWalletBase { updateSyncProgress(height, appliedTargetHeight); } }); - setWalletSyncedWithProgress(); + onDoneSyncWithProgress(); return; } @@ -189,11 +196,8 @@ public abstract class XmrWalletBase { syncProgressLooper.stop(); // set synced or throw error - if (syncProgressError == null) { - setWalletSyncedWithProgress(); - } else { - throw new RuntimeException(syncProgressError); - } + if (syncProgressError == null) onDoneSyncWithProgress(); + else throw new RuntimeException(syncProgressError); } catch (Exception e) { throw e; } finally { @@ -203,6 +207,10 @@ public abstract class XmrWalletBase { } } + public boolean wasWalletSynced() { + return wasWalletSynced; + } + public boolean requestSwitchToNextBestConnection(MoneroRpcConnection sourceConnection) { if (xmrConnectionService.requestSwitchToNextBestConnection(sourceConnection)) { onConnectionChanged(xmrConnectionService.getConnection()); // change connection on same thread @@ -224,6 +232,14 @@ public abstract class XmrWalletBase { ThreadUtils.submitToPool(() -> saveWalletIfElapsedTime()); } + public boolean isSyncing() { + return isSyncingWithProgress || isSyncingWithoutProgress; + } + + public ReadOnlyDoubleProperty downloadPercentageProperty() { + return walletSyncListener.percentageProperty(); + } + public static boolean isSyncWithProgressTimeout(Throwable e) { return e.getMessage() != null && e.getMessage().contains(SYNC_TIMEOUT_MSG); } @@ -246,17 +262,15 @@ public abstract class XmrWalletBase { // set wallet height walletHeight.set(height); - // new wallet reports height 1 before synced - if (height == 1) { - downloadListener.progress(0, targetHeight - height, null); - return; - } + // new wallet reports height 0 or 1 before synced + if (height <= 1) return; // set progress long blocksLeft = targetHeight - height; if (syncStartHeight == null) syncStartHeight = height; double percent = Math.min(1.0, targetHeight == syncStartHeight ? 1.0 : ((double) height - syncStartHeight) / (double) (targetHeight - syncStartHeight)); - downloadListener.progress(percent, blocksLeft, null); + if (percent >= 1.0) wasWalletSynced = true; // set synced state before announcing progress + walletSyncListener.progress(percent, blocksLeft, null); } private synchronized void resetSyncProgressTimeout() { @@ -268,7 +282,7 @@ public abstract class XmrWalletBase { }, SYNC_TIMEOUT_SECONDS, TimeUnit.SECONDS); } - private void setWalletSyncedWithProgress() { + private void onDoneSyncWithProgress() { // stop syncing and save wallet if elapsed time if (wallet != null) { // can become null if interrupted by force close @@ -277,8 +291,5 @@ public abstract class XmrWalletBase { saveWalletIfElapsedTime(); } } - - // update state - wasWalletSynced = true; } } diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java index 3217a5188e..8d00fa6e93 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -63,7 +63,6 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.stream.Collectors; import java.util.stream.Stream; import javafx.beans.property.LongProperty; -import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.value.ChangeListener; import monero.common.MoneroError; import monero.common.MoneroRpcConnection; @@ -266,18 +265,6 @@ public class XmrWalletService extends XmrWalletBase { return accountService.getPassword() != null; } - public ReadOnlyDoubleProperty downloadPercentageProperty() { - return downloadListener.percentageProperty(); - } - - private void doneDownload() { - downloadListener.doneDownload(); - } - - public boolean isDownloadComplete() { - return downloadPercentageProperty().get() == 1d; - } - public LongProperty walletHeightProperty() { return walletHeight; } @@ -1494,7 +1481,7 @@ public class XmrWalletService extends XmrWalletBase { resetIfWalletChanged(); // signal that main wallet is synced - doneDownload(); + walletSyncListener.doneDownload(); // notify setup that main wallet is initialized // TODO: app fully initializes after this is set to true, even though wallet might not be initialized if unconnected. wallet will be created when connection detected diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 9c470a33dc..b52f3b649d 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -660,6 +660,7 @@ portfolio.pending.unconfirmedTooLong=Deposit transactions on trade {0} are still confirmed in Haveno, try restarting Haveno.\n\n\ If the problem persists, contact Haveno support [HYPERLINK:https://matrix.to/#/#haveno:monero.social]. +portfolio.pending.syncing=Syncing trade wallet — {0}% ({1} blocks remaining) portfolio.pending.step1.waitForConf=Wait for blockchain confirmations portfolio.pending.step2_buyer.additionalConf=Deposits have reached 10 confirmations.\nFor extra security, we recommend waiting {0} confirmations before sending payment.\nProceed early at your own risk. portfolio.pending.step2_buyer.startPayment=Start payment diff --git a/core/src/main/resources/i18n/displayStrings_cs.properties b/core/src/main/resources/i18n/displayStrings_cs.properties index 7ce51e0a98..66727ff9aa 100644 --- a/core/src/main/resources/i18n/displayStrings_cs.properties +++ b/core/src/main/resources/i18n/displayStrings_cs.properties @@ -660,6 +660,7 @@ portfolio.pending.unconfirmedTooLong=Vkladové transakce obchodu {0} jsou stále potvrzené v Haveno, zkuste Haveno restartovat.\n\n\ Pokud problém přetrvává, kontaktujte podporu Haveno [HYPERLINK:https://matrix.to/#/#haveno:monero.social]. +portfolio.pending.syncing=Synchronizuji obchodní peněženku — {0}% ({1} bloků zbývá) portfolio.pending.step1.waitForConf=Počkejte na potvrzení na blockchainu portfolio.pending.step2_buyer.additionalConf=Vklady dosáhly 10 potvrzení.\nPro vyšší bezpečnost doporučujeme počkat na {0} potvrzení před odesláním platby.\nPokračujte dříve na vlastní riziko. portfolio.pending.step2_buyer.startPayment=Zahajte platbu diff --git a/core/src/main/resources/i18n/displayStrings_de.properties b/core/src/main/resources/i18n/displayStrings_de.properties index 59f5e64684..1031956088 100644 --- a/core/src/main/resources/i18n/displayStrings_de.properties +++ b/core/src/main/resources/i18n/displayStrings_de.properties @@ -577,6 +577,7 @@ portfolio.closedTrades.deviation.help=Prozentuale Preisabweichung vom Markt portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\nPlease do NOT send the traditional or crypto payment.\n\nOpen a support ticket to get assistance from a Mediator.\n\nError message: {0} +portfolio.pending.syncing=Handelwallet wird synchronisiert — {0}% ({1} Blöcke verbleibend) portfolio.pending.step1.waitForConf=Auf Blockchain-Bestätigung warten portfolio.pending.step2_buyer.additionalConf=Einzahlungen haben 10 Bestätigungen erreicht.\nFür zusätzliche Sicherheit empfehlen wir, {0} Bestätigungen abzuwarten, bevor Sie die Zahlung senden.\nEin früheres Vorgehen erfolgt auf eigenes Risiko. portfolio.pending.step2_buyer.startPayment=Zahlung beginnen diff --git a/core/src/main/resources/i18n/displayStrings_es.properties b/core/src/main/resources/i18n/displayStrings_es.properties index 1566b36b2f..97a8cb9cbf 100644 --- a/core/src/main/resources/i18n/displayStrings_es.properties +++ b/core/src/main/resources/i18n/displayStrings_es.properties @@ -577,6 +577,7 @@ portfolio.closedTrades.deviation.help=Desviación porcentual de precio de mercad portfolio.pending.invalidTx=Hay un problema con una transacción inválida o no encontrada.\n\nPor faovr NO envíe el pago de traditional o cryptos.\n\nAbra un ticket de soporte para obtener asistencia de un mediador.\n\nMensaje de error: {0} +portfolio.pending.syncing=Sincronizando la billetera de comercio — {0}% ({1} bloques restantes) portfolio.pending.step1.waitForConf=Esperar a la confirmación en la cadena de bloques portfolio.pending.step2_buyer.additionalConf=Los depósitos han alcanzado 10 confirmaciones.\nPara mayor seguridad, recomendamos esperar {0} confirmaciones antes de enviar el pago.\nProceda antes bajo su propio riesgo. portfolio.pending.step2_buyer.startPayment=Comenzar pago diff --git a/core/src/main/resources/i18n/displayStrings_fa.properties b/core/src/main/resources/i18n/displayStrings_fa.properties index 5914064710..9f93bcf539 100644 --- a/core/src/main/resources/i18n/displayStrings_fa.properties +++ b/core/src/main/resources/i18n/displayStrings_fa.properties @@ -576,6 +576,7 @@ portfolio.closedTrades.deviation.help=Percentage price deviation from market portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\nPlease do NOT send the traditional or crypto payment.\n\nOpen a support ticket to get assistance from a Mediator.\n\nError message: {0} +portfolio.pending.syncing=همگام‌سازی کیف‌پول معامله — {0}% ({1} بلاک باقی‌مانده) portfolio.pending.step1.waitForConf=برای تأییدیه بلاک چین منتظر باشید portfolio.pending.step2_buyer.additionalConf=واریزها به ۱۰ تأیید رسیده‌اند.\nبرای امنیت بیشتر، توصیه می‌کنیم قبل از ارسال پرداخت، {0} تأیید صبر کنید.\nاقدام زودهنگام با مسئولیت خودتان است. portfolio.pending.step2_buyer.startPayment=آغاز پرداخت diff --git a/core/src/main/resources/i18n/displayStrings_fr.properties b/core/src/main/resources/i18n/displayStrings_fr.properties index 1dbfb153ed..ae3bd243ae 100644 --- a/core/src/main/resources/i18n/displayStrings_fr.properties +++ b/core/src/main/resources/i18n/displayStrings_fr.properties @@ -577,6 +577,7 @@ portfolio.closedTrades.deviation.help=Pourcentage de déviation du prix par rapp portfolio.pending.invalidTx=Il y'a un problème avec une transaction manquante ou invalide.\n\nVeuillez NE PAS envoyer le payement Traditional ou crypto.\n\nOuvrez un ticket de support pour avoir l'aide d'un médiateur.\n\nMessage d'erreur: {0} +portfolio.pending.syncing=Synchronisation du portefeuille de trading — {0}% ({1} blocs restants) portfolio.pending.step1.waitForConf=Attendre la confirmation de la blockchain portfolio.pending.step2_buyer.additionalConf=Les dépôts ont atteint 10 confirmations.\nPour plus de sécurité, nous recommandons d’attendre {0} confirmations avant d’envoyer le paiement.\nProcédez plus tôt à vos propres risques. portfolio.pending.step2_buyer.startPayment=Initier le paiement diff --git a/core/src/main/resources/i18n/displayStrings_it.properties b/core/src/main/resources/i18n/displayStrings_it.properties index 81479f29a8..7504045341 100644 --- a/core/src/main/resources/i18n/displayStrings_it.properties +++ b/core/src/main/resources/i18n/displayStrings_it.properties @@ -576,6 +576,7 @@ portfolio.closedTrades.deviation.help=Percentage price deviation from market portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\nPlease do NOT send the traditional or crypto payment.\n\nOpen a support ticket to get assistance from a Mediator.\n\nError message: {0} +portfolio.pending.syncing=Sincronizzazione del portafoglio trade — {0}% ({1} blocchi rimanenti) portfolio.pending.step1.waitForConf=Attendi la conferma della blockchain portfolio.pending.step2_buyer.additionalConf=I depositi hanno raggiunto 10 conferme.\nPer maggiore sicurezza, consigliamo di attendere {0} conferme prima di inviare il pagamento.\nProcedi in anticipo a tuo rischio. portfolio.pending.step2_buyer.startPayment=Inizia il pagamento diff --git a/core/src/main/resources/i18n/displayStrings_ja.properties b/core/src/main/resources/i18n/displayStrings_ja.properties index 556bed4f49..e564e642cc 100644 --- a/core/src/main/resources/i18n/displayStrings_ja.properties +++ b/core/src/main/resources/i18n/displayStrings_ja.properties @@ -577,6 +577,7 @@ portfolio.closedTrades.deviation.help=市場からの割合価格偏差 portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\nPlease do NOT send the traditional or crypto payment.\n\nOpen a support ticket to get assistance from a Mediator.\n\nError message: {0} +portfolio.pending.syncing=取引ウォレットを同期中 — {0}%(残り {1} ブロック) portfolio.pending.step1.waitForConf=ブロックチェーンの承認をお待ち下さい portfolio.pending.step2_buyer.additionalConf=入金は10承認に達しました。\n追加の安全のため、支払いを送信する前に{0}承認を待つことをお勧めします。\n早めに進める場合は自己責任となります。 portfolio.pending.step2_buyer.startPayment=支払い開始 diff --git a/core/src/main/resources/i18n/displayStrings_pt-br.properties b/core/src/main/resources/i18n/displayStrings_pt-br.properties index 2ea36f411b..7912f8331d 100644 --- a/core/src/main/resources/i18n/displayStrings_pt-br.properties +++ b/core/src/main/resources/i18n/displayStrings_pt-br.properties @@ -579,6 +579,7 @@ portfolio.closedTrades.deviation.help=Percentage price deviation from market portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\nPlease do NOT send the traditional or crypto payment.\n\nOpen a support ticket to get assistance from a Mediator.\n\nError message: {0} +portfolio.pending.syncing=Sincronizando a carteira de negociação — {0}% ({1} blocos restantes) portfolio.pending.step1.waitForConf=Aguardar confirmação da blockchain portfolio.pending.step2_buyer.additionalConf=Depósitos alcançaram 10 confirmações.\nPara maior segurança, recomendamos aguardar {0} confirmações antes de enviar o pagamento.\nProssiga antecipadamente por sua própria conta e risco. portfolio.pending.step2_buyer.startPayment=Iniciar pagamento diff --git a/core/src/main/resources/i18n/displayStrings_pt.properties b/core/src/main/resources/i18n/displayStrings_pt.properties index f817c16b38..7515d3b80e 100644 --- a/core/src/main/resources/i18n/displayStrings_pt.properties +++ b/core/src/main/resources/i18n/displayStrings_pt.properties @@ -576,6 +576,7 @@ portfolio.closedTrades.deviation.help=Percentage price deviation from market portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\nPlease do NOT send the traditional or crypto payment.\n\nOpen a support ticket to get assistance from a Mediator.\n\nError message: {0} +portfolio.pending.syncing=Sincronizando a carteira de negociação — {0}% ({1} blocos restantes) portfolio.pending.step1.waitForConf=Esperando confirmação da blockchain portfolio.pending.step2_buyer.additionalConf=Os depósitos alcançaram 10 confirmações.\nPara maior segurança, recomendamos aguardar {0} confirmações antes de enviar o pagamento.\nProceda antecipadamente por sua própria conta e risco. portfolio.pending.step2_buyer.startPayment=Iniciar pagamento diff --git a/core/src/main/resources/i18n/displayStrings_ru.properties b/core/src/main/resources/i18n/displayStrings_ru.properties index 38075f7c14..7e1c54b7e0 100644 --- a/core/src/main/resources/i18n/displayStrings_ru.properties +++ b/core/src/main/resources/i18n/displayStrings_ru.properties @@ -576,6 +576,7 @@ portfolio.closedTrades.deviation.help=Percentage price deviation from market portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\nPlease do NOT send the traditional or crypto payment.\n\nOpen a support ticket to get assistance from a Mediator.\n\nError message: {0} +portfolio.pending.syncing=Синхронизация торгового кошелька — {0}% (осталось блоков: {1}) portfolio.pending.step1.waitForConf=Ожидание подтверждения в блокчейне portfolio.pending.step2_buyer.additionalConf=Депозиты достигли 10 подтверждений.\nДля дополнительной безопасности мы рекомендуем дождаться {0} подтверждений перед отправкой платежа.\nРанее действия осуществляются на ваш страх и риск. portfolio.pending.step2_buyer.startPayment=Сделать платеж diff --git a/core/src/main/resources/i18n/displayStrings_th.properties b/core/src/main/resources/i18n/displayStrings_th.properties index 921a39da4a..5f0c96edfe 100644 --- a/core/src/main/resources/i18n/displayStrings_th.properties +++ b/core/src/main/resources/i18n/displayStrings_th.properties @@ -576,6 +576,7 @@ portfolio.closedTrades.deviation.help=Percentage price deviation from market portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\nPlease do NOT send the traditional or crypto payment.\n\nOpen a support ticket to get assistance from a Mediator.\n\nError message: {0} +portfolio.pending.syncing=กำลังซิงค์กระเป๋าเงินสำหรับการซื้อขาย — {0}% (เหลืออีก {1} บล็อก) portfolio.pending.step1.waitForConf=รอการยืนยันของบล็อกเชน portfolio.pending.step2_buyer.additionalConf=ยอดฝากถึง 10 การยืนยันแล้ว\nเพื่อความปลอดภัยเพิ่มเติม เราแนะนำให้รอ {0} การยืนยันก่อนทำการชำระเงิน\nดำเนินการล่วงหน้าตามความเสี่ยงของคุณเอง portfolio.pending.step2_buyer.startPayment=เริ่มการชำระเงิน diff --git a/core/src/main/resources/i18n/displayStrings_tr.properties b/core/src/main/resources/i18n/displayStrings_tr.properties index 09a0fe4d5e..7857e60ca7 100644 --- a/core/src/main/resources/i18n/displayStrings_tr.properties +++ b/core/src/main/resources/i18n/displayStrings_tr.properties @@ -623,6 +623,7 @@ portfolio.pending.unconfirmedTooLong=İşlem {0} üzerindeki güvence işlemleri onaylanmış olarak gösterilmiyorlarsa, Haveno'yu yeniden başlatmayı deneyin.\n\n\ Sorun devam ederse, Haveno desteğiyle iletişime geçin [HYPERLINK:https://matrix.to/#/#haveno:monero.social]. +portfolio.pending.syncing=Ticaret cüzdanı senkronize ediliyor — %{0} ({1} blok kaldı) portfolio.pending.step1.waitForConf=Blok zinciri onaylarını bekleyin portfolio.pending.step2_buyer.additionalConf=Mevduatlar 10 onayı ulaştı.\nEkstra güvenlik için, ödeme göndermeden önce {0} onayı beklemenizi öneririz.\nErken ilerlemek kendi riskinizdedir. portfolio.pending.step2_buyer.startPayment=Ödemeyi başlat diff --git a/core/src/main/resources/i18n/displayStrings_vi.properties b/core/src/main/resources/i18n/displayStrings_vi.properties index a20dff1749..901fbeff72 100644 --- a/core/src/main/resources/i18n/displayStrings_vi.properties +++ b/core/src/main/resources/i18n/displayStrings_vi.properties @@ -576,6 +576,7 @@ portfolio.closedTrades.deviation.help=Percentage price deviation from market portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\nPlease do NOT send the traditional or crypto payment.\n\nOpen a support ticket to get assistance from a Mediator.\n\nError message: {0} +portfolio.pending.syncing=Đang đồng bộ ví giao dịch — {0}% (còn lại {1} khối) portfolio.pending.step1.waitForConf=Đợi xác nhận blockchain portfolio.pending.step2_buyer.additionalConf=Tiền gửi đã đạt 10 xác nhận.\nĐể tăng cường bảo mật, chúng tôi khuyên bạn chờ {0} xác nhận trước khi gửi thanh toán.\nTiến hành sớm là rủi ro của bạn. portfolio.pending.step2_buyer.startPayment=Bắt đầu thanh toán diff --git a/core/src/main/resources/i18n/displayStrings_zh-hans.properties b/core/src/main/resources/i18n/displayStrings_zh-hans.properties index 9075d1b1d5..4560546453 100644 --- a/core/src/main/resources/i18n/displayStrings_zh-hans.properties +++ b/core/src/main/resources/i18n/displayStrings_zh-hans.properties @@ -577,6 +577,7 @@ portfolio.closedTrades.deviation.help=与市场价格偏差百分比 portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\nPlease do NOT send the traditional or crypto payment.\n\nOpen a support ticket to get assistance from a Mediator.\n\nError message: {0} +portfolio.pending.syncing=正在同步交易钱包 — {0}%(剩余 {1} 个区块) portfolio.pending.step1.waitForConf=等待区块链确认 portfolio.pending.step2_buyer.additionalConf=存款已达到 10 个确认。\n为了额外安全,我们建议在发送付款前等待 {0} 个确认。\n提前操作风险自负。 portfolio.pending.step2_buyer.startPayment=开始付款 diff --git a/core/src/main/resources/i18n/displayStrings_zh-hant.properties b/core/src/main/resources/i18n/displayStrings_zh-hant.properties index 14a2a21c31..33308fe47e 100644 --- a/core/src/main/resources/i18n/displayStrings_zh-hant.properties +++ b/core/src/main/resources/i18n/displayStrings_zh-hant.properties @@ -577,6 +577,7 @@ portfolio.closedTrades.deviation.help=Percentage price deviation from market portfolio.pending.invalidTx=There is an issue with a missing or invalid transaction.\n\nPlease do NOT send the traditional or crypto payment.\n\nOpen a support ticket to get assistance from a Mediator.\n\nError message: {0} +portfolio.pending.syncing=正在同步交易錢包 — {0}%(剩餘 {1} 個區塊) portfolio.pending.step1.waitForConf=等待區塊鏈確認 portfolio.pending.step2_buyer.additionalConf=存款已達 10 次確認。\n為了額外安全,我們建議在發送付款前等待 {0} 次確認。\n提前操作風險自負。 portfolio.pending.step2_buyer.startPayment=開始付款 diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java index 57f3bd09f8..dc02b2c332 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java @@ -77,7 +77,7 @@ public abstract class TradeStepView extends AnchorPane { protected final Preferences preferences; protected final GridPane gridPane; - private Subscription tradePeriodStateSubscription, tradeStateSubscription, disputeStateSubscription, mediationResultStateSubscription; + private Subscription tradePeriodStateSubscription, tradeStateSubscription, disputeStateSubscription, mediationResultStateSubscription, syncProgressSubscription; protected int gridRow = 0; private TextField timeLeftTextField; private ProgressBar timeLeftProgressBar; @@ -93,6 +93,9 @@ public abstract class TradeStepView extends AnchorPane { private BootstrapListener bootstrapListener; private TradeSubView.ChatCallback chatCallback; private ChangeListener pendingTradesInitializedListener; + protected Label statusLabel; + protected String syncStatus; + protected String tradeStatus; /////////////////////////////////////////////////////////////////////////////////////////// @@ -258,9 +261,21 @@ public abstract class TradeStepView extends AnchorPane { if (newValue) addTradeStateSubscription(); }); + syncProgressSubscription = EasyBind.subscribe(trade.downloadPercentageProperty(), newValue -> { + if (newValue != null) onSyncProgress((double) newValue); + }); + UserThread.execute(() -> model.p2PService.removeP2PServiceListener(bootstrapListener)); } + protected void onSyncProgress(double percent) { + if (percent < 0.0 || percent >= 1.0) setSyncStatus(""); + else { + long blocksRemaining = HavenoUtils.xmrConnectionService.getTargetHeight() - trade.getHeight(); + setSyncStatus(Res.get("portfolio.pending.syncing", ((int) Math.round(percent * 100)), blocksRemaining)); + } + } + private void addTradeStateSubscription() { tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> { if (newValue != null) { @@ -301,6 +316,9 @@ public abstract class TradeStepView extends AnchorPane { if (mediationResultStateSubscription != null) mediationResultStateSubscription.unsubscribe(); + if (syncProgressSubscription != null) + syncProgressSubscription.unsubscribe(); + if (tradePeriodStateSubscription != null) tradePeriodStateSubscription.unsubscribe(); @@ -402,6 +420,9 @@ public abstract class TradeStepView extends AnchorPane { infoLabel = addMultilineLabel(gridPane, gridRow, "", Layout.COMPACT_FIRST_ROW_AND_COMPACT_GROUP_DISTANCE); GridPane.setColumnSpan(infoLabel, 2); + + statusLabel = new Label(); + gridPane.add(statusLabel, 0, ++gridRow, 2, 1); } protected String getInfoText() { @@ -869,4 +890,24 @@ public abstract class TradeStepView extends AnchorPane { public void setChatCallback(TradeSubView.ChatCallback chatCallback) { this.chatCallback = chatCallback; } + + protected void setSyncStatus(String text) { + syncStatus = text; + if (syncStatus == null || syncStatus.isEmpty()) { + setStatus(tradeStatus); + } else { + setStatus(syncStatus); + } + } + + protected void setTradeStatus(String text) { + tradeStatus = text; + if (syncStatus == null || syncStatus.isEmpty()) { + setStatus(tradeStatus); + } + } + + private void setStatus(String text) { + if (statusLabel != null) statusLabel.setText(text == null ? "" : text); + } } diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 3adc2862d9..b1e2f4a9ee 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -128,7 +128,6 @@ import static haveno.desktop.util.FormBuilder.addTopLabelTextFieldWithCopyIcon; public class BuyerStep2View extends TradeStepView { private Button confirmButton; - private Label statusLabel; private BusyAnimation busyAnimation; private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; @@ -159,40 +158,40 @@ public class BuyerStep2View extends TradeStepView { if (trade.isDepositsUnlocked() && !trade.isPaymentSent()) { busyAnimation.stop(); - statusLabel.setText(""); + setTradeStatus(""); showPopup(); } else if (state.ordinal() <= Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG.ordinal()) { switch (state) { case BUYER_CONFIRMED_PAYMENT_SENT: busyAnimation.play(); - statusLabel.setText(Res.get("shared.preparingConfirmation")); + setTradeStatus(Res.get("shared.preparingConfirmation")); break; case BUYER_SENT_PAYMENT_SENT_MSG: busyAnimation.play(); - statusLabel.setText(Res.get("shared.sendingConfirmation")); + setTradeStatus(Res.get("shared.sendingConfirmation")); timeoutTimer = UserThread.runAfter(() -> { busyAnimation.stop(); - statusLabel.setText(Res.get("shared.sendingConfirmationAgain")); + setTradeStatus(Res.get("shared.sendingConfirmationAgain")); }, 30); break; case BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG: busyAnimation.stop(); - statusLabel.setText(Res.get("shared.messageStoredInMailbox")); + setTradeStatus(Res.get("shared.messageStoredInMailbox")); break; case BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG: case SELLER_RECEIVED_PAYMENT_SENT_MSG: busyAnimation.stop(); - statusLabel.setText(Res.get("shared.messageArrived")); + setTradeStatus(Res.get("shared.messageArrived")); break; case BUYER_SEND_FAILED_PAYMENT_SENT_MSG: // We get a popup and the trade closed, so we dont need to show anything here busyAnimation.stop(); - statusLabel.setText(""); + setTradeStatus(""); break; default: log.warn("Unexpected case: State={}, tradeId={} ", state.name(), trade.getId()); busyAnimation.stop(); - statusLabel.setText(Res.get("shared.sendingConfirmationAgain")); + setTradeStatus(Res.get("shared.sendingConfirmationAgain")); break; } } @@ -676,7 +675,7 @@ public class BuyerStep2View extends TradeStepView { private void confirmPaymentSent() { busyAnimation.play(); - statusLabel.setText(Res.get("shared.preparingConfirmation")); + setTradeStatus(Res.get("shared.preparingConfirmation")); confirmButton.setDisable(true); model.dataModel.onPaymentSent(() -> { @@ -684,7 +683,7 @@ public class BuyerStep2View extends TradeStepView { busyAnimation.stop(); new Popup().warning(Res.get("popup.warning.sendMsgFailed") + "\n\n" + errorMessage).show(); confirmButton.setDisable(!confirmPaymentSentPermitted()); - UserThread.execute(() -> statusLabel.setText("Error confirming payment sent.")); + UserThread.execute(() -> setTradeStatus("Error confirming payment sent.")); }); } diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java index 81605cf33f..36df783b30 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java @@ -18,6 +18,7 @@ package haveno.desktop.main.portfolio.pendingtrades.steps.buyer; import de.jensd.fx.fontawesome.AwesomeIcon; +import haveno.common.util.Tuple4; import haveno.core.locale.Res; import haveno.core.network.MessageState; import haveno.desktop.components.TextFieldWithIcon; @@ -27,10 +28,11 @@ import haveno.desktop.util.Layout; import javafx.beans.value.ChangeListener; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; import static haveno.desktop.util.FormBuilder.addMultilineLabel; import static haveno.desktop.util.FormBuilder.addTitledGroupBg; -import static haveno.desktop.util.FormBuilder.addTopLabelTextFieldWithIcon; +import static haveno.desktop.util.FormBuilder.addTopLabelTextFieldWithIconLabel; public class BuyerStep3View extends TradeStepView { private final ChangeListener messageStateChangeListener; @@ -74,8 +76,11 @@ public class BuyerStep3View extends TradeStepView { addTitledGroupBg(gridPane, ++gridRow, 2, getInfoBlockTitle(), Layout.GROUP_DISTANCE); infoLabel = addMultilineLabel(gridPane, gridRow, "", Layout.FIRST_ROW_AND_GROUP_DISTANCE); GridPane.setColumnSpan(infoLabel, 2); - textFieldWithIcon = addTopLabelTextFieldWithIcon(gridPane, ++gridRow, - Res.get("portfolio.pending.step3_buyer.wait.msgStateInfo.label"), 0).second; + Tuple4 tuple = addTopLabelTextFieldWithIconLabel(gridPane, ++gridRow, + Res.get("portfolio.pending.step3_buyer.wait.msgStateInfo.label"), 0); + GridPane.setColumnSpan(tuple.first, 2); + textFieldWithIcon = tuple.third; + statusLabel = tuple.fourth; } @Override diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 20e12a3f56..f6e4315d66 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -77,7 +77,6 @@ import static haveno.desktop.util.Layout.FLOATING_LABEL_DISTANCE; public class SellerStep3View extends TradeStepView { private Button confirmButton; - private Label statusLabel; private BusyAnimation busyAnimation; private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; @@ -110,44 +109,44 @@ public class SellerStep3View extends TradeStepView { if (trade.isPaymentSent() && !trade.isPaymentReceived()) { busyAnimation.stop(); - statusLabel.setText(""); + setTradeStatus(""); showPopup(); } else if (trade.isPaymentReceived()) { if (trade.isCompleted()) { if (!trade.isPayoutPublished()) log.warn("Payout is expected to be published for {} {} state {}", trade.getClass().getSimpleName(), trade.getId(), trade.getState()); busyAnimation.stop(); - statusLabel.setText(""); + setTradeStatus(""); } else switch (state) { case SELLER_CONFIRMED_PAYMENT_RECEIPT: busyAnimation.play(); - statusLabel.setText(Res.get("shared.preparingConfirmation")); + setTradeStatus(Res.get("shared.preparingConfirmation")); break; case SELLER_SENT_PAYMENT_RECEIVED_MSG: busyAnimation.play(); - statusLabel.setText(Res.get("shared.sendingConfirmation")); + setTradeStatus(Res.get("shared.sendingConfirmation")); timeoutTimer = UserThread.runAfter(() -> { busyAnimation.stop(); - statusLabel.setText(Res.get("shared.sendingConfirmationAgain")); + setTradeStatus(Res.get("shared.sendingConfirmationAgain")); }, 30); break; case SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG: busyAnimation.stop(); - statusLabel.setText(Res.get("shared.messageStoredInMailbox")); + setTradeStatus(Res.get("shared.messageStoredInMailbox")); break; case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG: case BUYER_RECEIVED_PAYMENT_RECEIVED_MSG: busyAnimation.stop(); - statusLabel.setText(Res.get("shared.messageArrived")); + setTradeStatus(Res.get("shared.messageArrived")); break; case SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG: // We get a popup and the trade closed, so we dont need to show anything here busyAnimation.stop(); - statusLabel.setText(""); + setTradeStatus(""); break; default: log.warn("Unexpected case: State={}, tradeId={} " + state.name(), trade.getId()); busyAnimation.stop(); - statusLabel.setText(Res.get("shared.sendingConfirmationAgain")); + setTradeStatus(Res.get("shared.sendingConfirmationAgain")); break; } } @@ -446,7 +445,7 @@ public class SellerStep3View extends TradeStepView { private void confirmPaymentReceived() { log.info("User pressed the [Confirm payment receipt] button for Trade {}", trade.getShortId()); busyAnimation.play(); - statusLabel.setText(Res.get("shared.preparingConfirmation")); + setTradeStatus(Res.get("shared.preparingConfirmation")); confirmButton.setDisable(true); model.dataModel.onPaymentReceived(() -> { @@ -454,7 +453,7 @@ public class SellerStep3View extends TradeStepView { busyAnimation.stop(); new Popup().warning(Res.get("popup.warning.sendMsgFailed") + "\n\n" + errorMessage).show(); confirmButton.setDisable(!confirmPaymentReceivedPermitted()); - UserThread.execute(() -> statusLabel.setText("Error confirming payment received.")); + UserThread.execute(() -> setTradeStatus("Error confirming payment received.")); }); } diff --git a/desktop/src/main/java/haveno/desktop/util/FormBuilder.java b/desktop/src/main/java/haveno/desktop/util/FormBuilder.java index dbee100f06..8a06d83b2c 100644 --- a/desktop/src/main/java/haveno/desktop/util/FormBuilder.java +++ b/desktop/src/main/java/haveno/desktop/util/FormBuilder.java @@ -540,6 +540,42 @@ public class FormBuilder { return new Tuple2<>(addTopLabelWithVBox(gridPane, rowIndex, columnIndex, title, textFieldWithIcon, top).first, textFieldWithIcon); } + /////////////////////////////////////////////////////////////////////////////////////////// + // Label + TextFieldWithIcon + Label + /////////////////////////////////////////////////////////////////////////////////////////// + + public static Tuple4 addTopLabelTextFieldWithIconLabel(GridPane gridPane, + int rowIndex, + String title, + double top) { + return addTopLabelTextFieldWithIconLabel(gridPane, rowIndex, 0, title, top); + } + + public static Tuple4 addTopLabelTextFieldWithIconLabel(GridPane gridPane, + int rowIndex, + int columnIndex, + String title, + double top) { + HBox hBox = new HBox(); + hBox.setSpacing(10); + + TextFieldWithIcon textFieldWithIcon = new TextFieldWithIcon(); + textFieldWithIcon.setFocusTraversable(false); + + Label label = new AutoTooltipLabel(); + hBox.setAlignment(Pos.CENTER_LEFT); + + hBox.getChildren().addAll(textFieldWithIcon, label); + GridPane.setRowIndex(hBox, rowIndex); + GridPane.setColumnIndex(hBox, columnIndex); + GridPane.setMargin(hBox, new Insets(top, 0, 0, 0)); + gridPane.getChildren().add(hBox); + + Tuple2 topLabelWithVBox = addTopLabelWithVBox(gridPane, rowIndex, columnIndex, title, hBox, top); + + return new Tuple4<>(topLabelWithVBox.second, topLabelWithVBox.first, textFieldWithIcon, label); + } + /////////////////////////////////////////////////////////////////////////////////////////// // HyperlinkWithIcon