feat(wallet): richer detail in the wallet-switch progress modal

The switch modal now shows more than a bare phase line:
- The title carries the target wallet ("Switching wallet — savings") and a
  "from <previous>" caption for context (names prettified: wallet.dat → "Default
  wallet", wallet-<name>.dat → "<name>", 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) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 22:11:53 -05:00
parent 41e4e73e63
commit 994ddea6cd
12 changed files with 90 additions and 13 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "新しいウォレットでノードを起動しています",

View File

@@ -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": "새 지갑으로 노드를 시작하는 중",

View File

@@ -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",

View File

@@ -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": "Запуск узла с новым кошельком",

View File

@@ -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": "正在以新钱包启动节点",

View File

@@ -3870,10 +3870,24 @@ void App::renderSwitchStopDaemonDialog()
const auto phase = static_cast<WalletSwitchPhase>(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<int>(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)))

View File

@@ -779,6 +779,7 @@ private:
std::atomic<int> wallet_switch_phase_{0};
std::atomic<bool> 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.

View File

@@ -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<int>(WalletSwitchPhase::Stopping));
wallet_switch_dialog_open_.store(true);

View File

@@ -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";