feat(wallet): live progress modal for wallet switching
The "Stop the running node?" confirm modal now stays open through the entire switch — stop → wait-for-exit → start → reconnect — showing a live phase, and auto-closes the moment the new node connects. This turns the up-to-a-minute graceful-shutdown wait from an apparent freeze into visible progress. - WalletSwitchPhase (Stopping/Starting/Reconnecting/Failed) + atomic phase and dialog-open flags; the worker advances the phase, onConnected closes the modal, and a failed switch shows the accurate reason with a Close button. - Owned switches (no confirm) also show the progress modal directly. - "Continue in background" escape hatch so a long rescan / a hung startup never traps the user (the switch keeps running; a toast reports the result). - isWalletSwitchInProgress() keeps the frame loop redrawing so the phase text and spinner animate while otherwise idle. - i18n (EN + 8 languages, additive) + a modal-switch-progress sweep surface. State machine adversarially verified 6/6 (thread-safety, no stuck modal, confirm→progress transition, owned/unowned, no phase leak, redraw scoping). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
75
src/app.cpp
75
src/app.cpp
@@ -3863,35 +3863,70 @@ void App::renderAntivirusHelpDialog()
|
||||
|
||||
void App::renderSwitchStopDaemonDialog()
|
||||
{
|
||||
if (!show_switch_stop_daemon_confirm_) return;
|
||||
const bool confirm = show_switch_stop_daemon_confirm_;
|
||||
const bool progress = wallet_switch_dialog_open_.load();
|
||||
if (!confirm && !progress) return;
|
||||
|
||||
const auto phase = static_cast<WalletSwitchPhase>(wallet_switch_phase_.load());
|
||||
const bool failed = progress && phase == WalletSwitchPhase::Failed;
|
||||
|
||||
ui::material::OverlayDialogSpec ov;
|
||||
ov.title = TR("switch_stopnode_title");
|
||||
ov.p_open = &show_switch_stop_daemon_confirm_; // the X / backdrop click acts as Cancel
|
||||
ov.title = confirm ? TR("switch_stopnode_title")
|
||||
: failed ? TR("switch_progress_failed_title")
|
||||
: TR("switch_progress_title");
|
||||
// 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;
|
||||
ov.cardWidth = 520.0f; ov.idSuffix = "switchstopnode";
|
||||
if (!ui::material::BeginOverlayDialog(ov)) {
|
||||
// Closed via X/backdrop — treat as cancel: drop the pending target.
|
||||
if (!show_switch_stop_daemon_confirm_) pending_switch_wallet_file_.clear();
|
||||
if (confirm && !show_switch_stop_daemon_confirm_) pending_switch_wallet_file_.clear(); // X = cancel
|
||||
return;
|
||||
}
|
||||
const float dp = ui::Layout::dpiScale();
|
||||
|
||||
ui::material::DialogWarningHeader(TR("switch_stopnode_warn"));
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ImGui::TextWrapped("%s", TR("switch_stopnode_body"));
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingMd()));
|
||||
|
||||
if (ui::material::TactileButton(TR("switch_stopnode_confirm"), ImVec2(200.0f * dp, 0))) {
|
||||
const std::string w = pending_switch_wallet_file_;
|
||||
pending_switch_wallet_file_.clear();
|
||||
show_switch_stop_daemon_confirm_ = false;
|
||||
switchToWallet(w, /*stopDaemonConfirmed=*/true); // now proceeds to stop + wait + restart
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("cancel"), ImVec2(110.0f * dp, 0))) {
|
||||
pending_switch_wallet_file_.clear();
|
||||
show_switch_stop_daemon_confirm_ = false;
|
||||
if (confirm) {
|
||||
ui::material::DialogWarningHeader(TR("switch_stopnode_warn"));
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ImGui::TextWrapped("%s", TR("switch_stopnode_body"));
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingMd()));
|
||||
if (ui::material::TactileButton(TR("switch_stopnode_confirm"), ImVec2(200.0f * dp, 0))) {
|
||||
const std::string w = pending_switch_wallet_file_;
|
||||
pending_switch_wallet_file_.clear();
|
||||
show_switch_stop_daemon_confirm_ = false;
|
||||
switchToWallet(w, /*stopDaemonConfirmed=*/true); // opens the live-progress phase below
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("cancel"), ImVec2(110.0f * dp, 0))) {
|
||||
pending_switch_wallet_file_.clear();
|
||||
show_switch_stop_daemon_confirm_ = false;
|
||||
}
|
||||
} else if (failed) {
|
||||
ui::material::DialogWarningHeader(TR("switch_progress_failed_title"));
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ImGui::TextWrapped("%s", wallet_switch_error_.c_str());
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingMd()));
|
||||
if (ui::material::TactileButton(TR("close"), ImVec2(110.0f * dp, 0))) {
|
||||
wallet_switch_dialog_open_.store(false);
|
||||
wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::None));
|
||||
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());
|
||||
if (phase == WalletSwitchPhase::Stopping) {
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ImGui::TextWrapped("%s", TR("switch_progress_hint"));
|
||||
}
|
||||
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)))
|
||||
wallet_switch_dialog_open_.store(false);
|
||||
}
|
||||
|
||||
ui::material::EndOverlayDialog();
|
||||
|
||||
Reference in New Issue
Block a user