From 994ddea6cd295290fa6cf926517220879afeea4c Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 15 Jul 2026 22:11:53 -0500 Subject: [PATCH] feat(wallet): richer detail in the wallet-switch progress modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The switch modal now shows more than a bare phase line: - The title carries the target wallet ("Switching wallet — savings") and a "from " caption for context (names prettified: wallet.dat → "Default wallet", wallet-.dat → "", in-place links → "External wallet"). - During the Reconnecting phase — the ~30-60s where the node loads the block index, verifies, and rescans — it surfaces the node's LIVE init stage (state_.warmup_status/description via the existing translateWarmup mapping: "Loading blockchain data…", "Verifying blockchain…", "Scanning for transactions…") instead of a static "Reconnecting…". - An elapsed timer (m:ss) so the wait visibly progresses. i18n: from/elapsed/default-wallet/external-wallet labels (EN + 8 languages, additive; no new CJK glyphs). No switch-flow logic change. Co-Authored-By: Claude Opus 4.8 (1M context) --- res/lang/de.json | 4 +++ res/lang/es.json | 4 +++ res/lang/fr.json | 4 +++ res/lang/ja.json | 4 +++ res/lang/ko.json | 4 +++ res/lang/pt.json | 4 +++ res/lang/ru.json | 4 +++ res/lang/zh.json | 4 +++ src/app.cpp | 65 ++++++++++++++++++++++++++++++++++++--------- src/app.h | 1 + src/app_network.cpp | 1 + src/util/i18n.cpp | 4 +++ 12 files changed, 90 insertions(+), 13 deletions(-) diff --git a/res/lang/de.json b/res/lang/de.json index 9f3ad46..7858a20 100644 --- a/res/lang/de.json +++ b/res/lang/de.json @@ -1295,7 +1295,11 @@ "sweep_toggle": "In meine Wallet fegen (Schlüssel nicht behalten)", "sweep_tx": "Transaktion:", "switch_progress_background": "Im Hintergrund fortsetzen", + "switch_progress_default_wallet": "Standard-Wallet", + "switch_progress_elapsed": "Vergangen", + "switch_progress_external_wallet": "Externe Wallet", "switch_progress_failed_title": "Wallet-Wechsel fehlgeschlagen", + "switch_progress_from_label": "von", "switch_progress_hint": "Ein sauberes Herunterfahren kann bis zu einer Minute dauern.", "switch_progress_reconnecting": "Neu verbinden", "switch_progress_starting": "Knoten wird mit der neuen Wallet gestartet", diff --git a/res/lang/es.json b/res/lang/es.json index 34fcaea..46d924a 100644 --- a/res/lang/es.json +++ b/res/lang/es.json @@ -1295,7 +1295,11 @@ "sweep_toggle": "Barrer a mi monedero (no conservar la clave)", "sweep_tx": "Transacción:", "switch_progress_background": "Continuar en segundo plano", + "switch_progress_default_wallet": "Cartera predeterminada", + "switch_progress_elapsed": "Transcurrido", + "switch_progress_external_wallet": "Cartera externa", "switch_progress_failed_title": "Error al cambiar de cartera", + "switch_progress_from_label": "desde", "switch_progress_hint": "Un apagado ordenado puede tardar hasta un minuto.", "switch_progress_reconnecting": "Reconectando", "switch_progress_starting": "Iniciando el nodo con la nueva cartera", diff --git a/res/lang/fr.json b/res/lang/fr.json index 006567f..865c409 100644 --- a/res/lang/fr.json +++ b/res/lang/fr.json @@ -1295,7 +1295,11 @@ "sweep_toggle": "Balayer vers mon portefeuille (ne pas conserver la clé)", "sweep_tx": "Transaction :", "switch_progress_background": "Continuer en arrière-plan", + "switch_progress_default_wallet": "Portefeuille par défaut", + "switch_progress_elapsed": "Écoulé", + "switch_progress_external_wallet": "Portefeuille externe", "switch_progress_failed_title": "Échec du changement de portefeuille", + "switch_progress_from_label": "depuis", "switch_progress_hint": "Un arrêt propre peut prendre jusqu'à une minute.", "switch_progress_reconnecting": "Reconnexion", "switch_progress_starting": "Démarrage du nœud sur le nouveau portefeuille", diff --git a/res/lang/ja.json b/res/lang/ja.json index 2653376..131a97e 100644 --- a/res/lang/ja.json +++ b/res/lang/ja.json @@ -1295,7 +1295,11 @@ "sweep_toggle": "ウォレットに集約(鍵は保持しない)", "sweep_tx": "取引:", "switch_progress_background": "バックグラウンドで続行", + "switch_progress_default_wallet": "既定のウォレット", + "switch_progress_elapsed": "経過", + "switch_progress_external_wallet": "外部ウォレット", "switch_progress_failed_title": "ウォレットの切り替えに失敗しました", + "switch_progress_from_label": "元:", "switch_progress_hint": "正常なシャットダウンには最大1分かかることがあります。", "switch_progress_reconnecting": "再接続しています", "switch_progress_starting": "新しいウォレットでノードを起動しています", diff --git a/res/lang/ko.json b/res/lang/ko.json index 664e209..9d1dadb 100644 --- a/res/lang/ko.json +++ b/res/lang/ko.json @@ -1295,7 +1295,11 @@ "sweep_toggle": "내 지갑으로 쓸어담기 (키 보관 안 함)", "sweep_tx": "거래:", "switch_progress_background": "백그라운드에서 계속", + "switch_progress_default_wallet": "기본 지갑", + "switch_progress_elapsed": "경과", + "switch_progress_external_wallet": "외부 지갑", "switch_progress_failed_title": "지갑 전환 실패", + "switch_progress_from_label": "이전:", "switch_progress_hint": "정상 종료에는 최대 1분이 걸릴 수 있습니다.", "switch_progress_reconnecting": "다시 연결하는 중", "switch_progress_starting": "새 지갑으로 노드를 시작하는 중", diff --git a/res/lang/pt.json b/res/lang/pt.json index f611394..c0b1986 100644 --- a/res/lang/pt.json +++ b/res/lang/pt.json @@ -1295,7 +1295,11 @@ "sweep_toggle": "Varrer para minha carteira (não manter a chave)", "sweep_tx": "Transação:", "switch_progress_background": "Continuar em segundo plano", + "switch_progress_default_wallet": "Carteira padrão", + "switch_progress_elapsed": "Decorrido", + "switch_progress_external_wallet": "Carteira externa", "switch_progress_failed_title": "Falha ao trocar de carteira", + "switch_progress_from_label": "de", "switch_progress_hint": "Um desligamento normal pode levar até um minuto.", "switch_progress_reconnecting": "Reconectando", "switch_progress_starting": "Iniciando o nó com a nova carteira", diff --git a/res/lang/ru.json b/res/lang/ru.json index 052884a..e6ec3f2 100644 --- a/res/lang/ru.json +++ b/res/lang/ru.json @@ -1295,7 +1295,11 @@ "sweep_toggle": "Перевести в мой кошелёк (не сохранять ключ)", "sweep_tx": "Транзакция:", "switch_progress_background": "Продолжить в фоне", + "switch_progress_default_wallet": "Кошелёк по умолчанию", + "switch_progress_elapsed": "Прошло", + "switch_progress_external_wallet": "Внешний кошелёк", "switch_progress_failed_title": "Не удалось переключить кошелёк", + "switch_progress_from_label": "из", "switch_progress_hint": "Корректное завершение работы может занять до минуты.", "switch_progress_reconnecting": "Переподключение", "switch_progress_starting": "Запуск узла с новым кошельком", diff --git a/res/lang/zh.json b/res/lang/zh.json index 435dc01..cbfa78a 100644 --- a/res/lang/zh.json +++ b/res/lang/zh.json @@ -1295,7 +1295,11 @@ "sweep_toggle": "归集到我的钱包(不保留密钥)", "sweep_tx": "交易:", "switch_progress_background": "在后台继续", + "switch_progress_default_wallet": "默认钱包", + "switch_progress_elapsed": "已用时", + "switch_progress_external_wallet": "外部钱包", "switch_progress_failed_title": "切换钱包失败", + "switch_progress_from_label": "来自", "switch_progress_hint": "正常关闭最多可能需要一分钟。", "switch_progress_reconnecting": "正在重新连接", "switch_progress_starting": "正在以新钱包启动节点", diff --git a/src/app.cpp b/src/app.cpp index ad84834..0ff27fb 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -3870,10 +3870,24 @@ void App::renderSwitchStopDaemonDialog() const auto phase = static_cast(wallet_switch_phase_.load()); const bool failed = progress && phase == WalletSwitchPhase::Failed; + // Prettify a wallet filename for display (wallet.dat → "Default wallet", wallet-savings.dat → "savings"). + auto pretty = [this](const std::string& f) -> std::string { + if (f.empty() || f == "wallet.dat") return TR("switch_progress_default_wallet"); + std::string s = f; + if (s.size() > 4 && s.substr(s.size() - 4) == ".dat") s.resize(s.size() - 4); + if (s.rfind("wallet-ip-", 0) == 0) return TR("switch_progress_external_wallet"); + if (s.rfind("wallet-", 0) == 0) s = s.substr(7); + return s; + }; + const std::string toName = pretty(settings_ ? settings_->getActiveWalletFile() : std::string()); + + // Progress title carries the target wallet ("Switching wallet — savings"); keep the string alive. + std::string titleStr = confirm ? std::string(TR("switch_stopnode_title")) + : failed ? std::string(TR("switch_progress_failed_title")) + : std::string(TR("switch_progress_title")) + " — " + toName; + ui::material::OverlayDialogSpec ov; - ov.title = confirm ? TR("switch_stopnode_title") - : failed ? TR("switch_progress_failed_title") - : TR("switch_progress_title"); + ov.title = titleStr.c_str(); // A confirm can be dismissed via X/backdrop (= cancel); a running switch cannot (stay until it's up). ov.p_open = confirm ? &show_switch_stop_daemon_confirm_ : nullptr; ov.style = ui::material::OverlayStyle::BlurFloat; @@ -3911,18 +3925,43 @@ void App::renderSwitchStopDaemonDialog() wallet_switch_error_.clear(); } } else { - // Live progress: an animated phase line, kept redrawing by isWalletSwitchInProgress(). - const char* status = - phase == WalletSwitchPhase::Starting ? TR("switch_progress_starting") - : phase == WalletSwitchPhase::Reconnecting ? TR("switch_progress_reconnecting") - : TR("switch_progress_stopping"); - ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm())); - ui::material::Type().text(ui::material::TypeStyle::Subtitle1, - (std::string(status) + ui::material::LoadingDots()).c_str()); + // Live progress, kept redrawing by isWalletSwitchInProgress(). + // Source context (the target is already in the title). + ImGui::PushFont(ui::material::Type().caption()); + ImGui::TextDisabled("%s %s", TR("switch_progress_from_label"), pretty(wallet_switch_prev_file_).c_str()); + ImGui::PopFont(); + ImGui::Dummy(ImVec2(0, ui::Layout::spacingMd())); + + // Primary status + detail. During Reconnecting the node is loading the block index / verifying / + // rescanning — surface its live init stage (state_.warmup_status/description) instead of a bare + // "Reconnecting…", so the ~30-60s wait shows real progress. + std::string primary, detail; if (phase == WalletSwitchPhase::Stopping) { - ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm())); - ImGui::TextWrapped("%s", TR("switch_progress_hint")); + primary = TR("switch_progress_stopping"); + detail = TR("switch_progress_hint"); + } else if (phase == WalletSwitchPhase::Starting) { + primary = TR("switch_progress_starting"); + } else { // Reconnecting + if (!state_.warmup_status.empty()) { primary = state_.warmup_status; detail = state_.warmup_description; } + else primary = TR("switch_progress_reconnecting"); } + ui::material::Type().text(ui::material::TypeStyle::Subtitle1, + (primary + ui::material::LoadingDots()).c_str()); + if (!detail.empty()) { + ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm())); + ImGui::PushFont(ui::material::Type().body2()); + ImGui::TextWrapped("%s", detail.c_str()); + ImGui::PopFont(); + } + + // Elapsed timer — captured on the first progress frame (reset per switch in switchToWallet). + if (wallet_switch_started_time_ <= 0.0) wallet_switch_started_time_ = ImGui::GetTime(); + const int el = static_cast(ImGui::GetTime() - wallet_switch_started_time_); + ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm())); + ImGui::PushFont(ui::material::Type().caption()); + ImGui::TextDisabled("%s %d:%02d", TR("switch_progress_elapsed"), el / 60, el % 60); + ImGui::PopFont(); + ImGui::Dummy(ImVec2(0, ui::Layout::spacingMd())); // Escape hatch — the switch keeps running in the background (a toast reports the result). if (ui::material::TactileButton(TR("switch_progress_background"), ImVec2(220.0f * dp, 0))) diff --git a/src/app.h b/src/app.h index 304ac32..7e7a788 100644 --- a/src/app.h +++ b/src/app.h @@ -779,6 +779,7 @@ private: std::atomic wallet_switch_phase_{0}; std::atomic wallet_switch_dialog_open_{false}; std::string wallet_switch_error_; + double wallet_switch_started_time_ = 0.0; // ImGui::GetTime() captured on first progress frame (elapsed) // Set by the deleteBlockchainData worker (item count); the main loop surfaces a completion toast // and resets it to -1. Atomic because the worker thread writes it and the UI thread reads/clears it. diff --git a/src/app_network.cpp b/src/app_network.cpp index dcaafb2..b97b86e 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -1113,6 +1113,7 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed // Open the switch progress modal — it stays up through stop → wait-for-exit → start → reconnect and // auto-closes when the new node connects (onConnected). The worker advances the phase from here. wallet_switch_error_.clear(); + wallet_switch_started_time_ = 0.0; // re-captured on the first progress frame → elapsed starts fresh wallet_switch_phase_.store(static_cast(WalletSwitchPhase::Stopping)); wallet_switch_dialog_open_.store(true); diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index d933129..9672ac5 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1053,6 +1053,10 @@ void I18n::loadBuiltinEnglish() strings_["switch_progress_reconnecting"] = "Reconnecting"; strings_["switch_progress_hint"] = "A graceful shutdown can take up to a minute."; strings_["switch_progress_background"] = "Continue in background"; + strings_["switch_progress_from_label"] = "from"; + strings_["switch_progress_elapsed"] = "Elapsed"; + strings_["switch_progress_default_wallet"] = "Default wallet"; + strings_["switch_progress_external_wallet"] = "External wallet"; // Receive Tab strings_["receiving_addresses"] = "Your Receiving Addresses";