feat(lite): add "Redownload blocks" (rescan from lite server) to Settings

Add a maintenance option for the lite wallet to re-download and re-scan every
block from the lite server — useful when balances or history look wrong.

- LiteWalletController::startRescan() runs the backend `rescan` command (which
  clears the wallet's synced block cache and re-syncs from its birthday) on a
  detached thread, reusing the existing sync progress/refresh machinery: it
  resets syncDone_ so refreshModel() shows progress again and refreshes data on
  completion. No-op if no wallet is open or a scan is already running.
- scanInProgress() exposes the initial-sync-or-rescan state.
- Settings (lite, open wallet) gains a "Redownload blocks" button behind a
  confirmation modal, disabled while a scan is running. i18n strings added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 17:05:27 -05:00
parent f0867084f3
commit 5547ab1cac
4 changed files with 98 additions and 0 deletions

View File

@@ -147,6 +147,7 @@ struct SettingsPageState {
bool confirm_delete_blockchain = false;
bool confirm_rescan = false;
bool confirm_restart_daemon = false;
bool confirm_lite_redownload = false;
effects::ScrollFadeShader fade_shader;
};
@@ -1819,6 +1820,24 @@ void RenderSettingsPage(App* app) {
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
s_settingsState.lite_encryption_status.c_str());
}
// ---- Maintenance: re-download blocks from the lite server ----
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
Type().text(TypeStyle::Body2, TR("lite_maintenance"));
const bool scanning = app->liteWallet()->scanInProgress();
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
ImGui::BeginDisabled(scanning);
if (TactileButton(TR("lite_redownload_blocks"), ImVec2(0, 0), S.resolveFont("button"))) {
s_settingsState.confirm_lite_redownload = true;
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
ImGui::SetTooltip("%s", TR("tt_lite_redownload"));
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
scanning ? TR("lite_redownload_running") : TR("lite_redownload_desc"));
} else if (!s_settingsState.lite_export_secret.empty()) {
// Wallet closed while a backup/secret was revealed — don't leave it in memory.
wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
@@ -2640,6 +2659,36 @@ void RenderSettingsPage(App* app) {
}
}
// Confirm: lite wallet re-download blocks (rescan from the lite server — long but safe)
if (s_settingsState.confirm_lite_redownload) {
if (BeginOverlayDialog(TR("confirm_lite_redownload_title"), &s_settingsState.confirm_lite_redownload, 500.0f, 0.94f)) {
ImGui::PushFont(Type().iconLarge());
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), ICON_MD_WARNING);
ImGui::PopFont();
ImGui::SameLine();
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("warning"));
ImGui::Spacing();
ImGui::TextWrapped("%s", TR("confirm_lite_redownload_msg"));
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("confirm_lite_redownload_safe"));
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
if (ImGui::Button(TrId("cancel", "lite_redl_cancel").c_str(), ImVec2(btnW, 40))) {
s_settingsState.confirm_lite_redownload = false;
}
ImGui::SameLine();
if (ImGui::Button(TrId("lite_redownload_blocks", "lite_redl_confirm").c_str(), ImVec2(btnW, 40))) {
if (auto* lite = app->liteWallet()) lite->startRescan();
s_settingsState.confirm_lite_redownload = false;
}
EndOverlayDialog();
}
}
}
} // namespace ui