diff --git a/src/ui/windows/bootstrap_download_dialog.h b/src/ui/windows/bootstrap_download_dialog.h index d31f4aa..abccb96 100644 --- a/src/ui/windows/bootstrap_download_dialog.h +++ b/src/ui/windows/bootstrap_download_dialog.h @@ -45,6 +45,7 @@ public: s_state = State::Confirm; s_bootstrap.reset(); s_errorMsg.clear(); + s_lastUrl.clear(); s_wasDaemonRunning = false; } @@ -314,9 +315,15 @@ private: float btnW = 120.0f * dp; if (TactileButton(TR("retry"), ImVec2(btnW, 0))) { - startDownload(""); + startDownload(s_lastUrl); // keep the user's source/mirror choice (was startDownload("")) } ImGui::SameLine(); + { + const std::string mirrorUrl = std::string(util::Bootstrap::kMirrorUrl) + "/" + util::Bootstrap::kZipName; + if (s_lastUrl != mirrorUrl && TactileButton(TR("bootstrap_try_mirror"), ImVec2(130.0f * dp, 0))) + startDownload(mirrorUrl); + ImGui::SameLine(); + } if (TactileButton(TR("close"), ImVec2(90.0f * dp, 0))) { s_app->setBootstrapDownloading(false); s_open = false; @@ -336,6 +343,7 @@ private: s_bootstrap->start(dataDir); else s_bootstrap->start(dataDir, url); + s_lastUrl = url; // remember what we started (set only after the guards passed) s_state = State::Downloading; s_errorMsg.clear(); s_cancelling = false; @@ -347,6 +355,7 @@ private: static inline std::unique_ptr s_bootstrap; static inline bool s_wasDaemonRunning = false; static inline bool s_cancelling = false; // cancel requested; disables the Cancel button until the worker stops + static inline std::string s_lastUrl; // last source started ("" = default); so Retry keeps the mirror choice static inline std::string s_errorMsg; }; diff --git a/src/ui/windows/validate_address_dialog.cpp b/src/ui/windows/validate_address_dialog.cpp index 4c8daa0..07b6d91 100644 --- a/src/ui/windows/validate_address_dialog.cpp +++ b/src/ui/windows/validate_address_dialog.cpp @@ -91,7 +91,8 @@ void ValidateAddressDialog::render(App* app) ImGui::Spacing(); // Validate button - bool can_validate = strlen(s_address_input) > 0 && !s_validating && app->isConnected(); + // Classification + checksum are LOCAL, so allow validating offline (the is-mine check needs the node). + bool can_validate = strlen(s_address_input) > 0 && !s_validating; if (!can_validate) { ImGui::BeginDisabled(); @@ -123,8 +124,9 @@ void ValidateAddressDialog::render(App* app) s_address_type.clear(); s_validated = true; s_validating = false; - } else if (app->isLiteBuild()) { - // Lite has no node to ask: the checksum verdict is the answer, ownership is unknown (hidden). + } else if (app->isLiteBuild() || !app->isConnected()) { + // No node to ask (lite, or full-node offline): the checksum verdict is the answer; ownership + // is unknown. The result block shows why. s_is_valid = true; s_is_mine = false; s_ownership_known = false; @@ -209,15 +211,17 @@ void ValidateAddressDialog::render(App* app) ImGui::SameLine(lbl.position); ImGui::Text("%s", s_address_type.c_str()); - // Is mine? (not shown when no node was asked - lite) - if (s_ownership_known) { - ImGui::Text("%s", TR("validate_ownership")); - ImGui::SameLine(lbl.position); - if (s_is_mine) { - ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("validate_is_mine")); - } else { - ImGui::TextDisabled("%s", TR("validate_not_mine")); - } + // Is mine? When no node was asked (lite / offline) say why ownership is unknown rather + // than silently omitting it. + ImGui::Text("%s", TR("validate_ownership")); + ImGui::SameLine(lbl.position); + if (!s_ownership_known) { + ImGui::TextDisabled("%s", TR(app->isLiteBuild() ? "validate_ownership_unknown_lite" + : "validate_ownership_unknown_offline")); + } else if (s_is_mine) { + ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("validate_is_mine")); + } else { + ImGui::TextDisabled("%s", TR("validate_not_mine")); } if (s_is_mining) { // valid, but not for payments ImGui::Spacing(); @@ -227,8 +231,6 @@ void ValidateAddressDialog::render(App* app) } } } - } else if (!app->isConnected()) { - ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("not_connected")); } else { // Pre-interaction placeholder: keeps the results area from reading as a large // dead gap between the Validate/Paste row and the Close button until a result exists. diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 3325fae..b3a165a 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -688,6 +688,7 @@ void I18n::loadBuiltinEnglish() strings_["wiz_bootstrap_desc"] = "Download a blockchain bootstrap to dramatically speed up initial sync.\n\nYour existing wallet.dat will NOT be modified or replaced."; strings_["wiz_skip"] = "Skip"; strings_["wiz_bootstrap_failed"] = "Bootstrap failed"; + strings_["bootstrap_try_mirror"] = "Try mirror"; strings_["wiz_download_failed"] = "Download Failed"; strings_["wiz_ext_daemon_running"] = "External daemon running"; strings_["wiz_ext_daemon_warning"] = "It must be stopped before downloading a bootstrap, otherwise chain data could be corrupted."; @@ -2723,6 +2724,8 @@ void I18n::loadBuiltinEnglish() strings_["validate_invalid"] = "INVALID"; strings_["validate_is_mine"] = "This wallet owns this address"; strings_["validate_not_mine"] = "Not owned by this wallet"; + strings_["validate_ownership_unknown_offline"] = "Unknown — node offline"; + strings_["validate_ownership_unknown_lite"] = "Unknown — not checkable in the lite wallet"; strings_["validate_ownership"] = "Ownership:"; strings_["validate_results"] = "Results:"; strings_["validate_results_placeholder"] = "Results will appear here";