feat(ux): robust sync ETA, persist Explorer settings, gate export-all out of lite

- Sync ETA: a new pure util::SyncRateEstimator (App-owned) replaces the
  function-local statics that went stale after a rescan/restart (producing the
  "412h" ETA). It self-resets on a height drop and on connect/disconnect/rescan/
  restart (via an atomic flag, since onConnected can run off-thread), formats
  with days, and uses the Warning() token.
- Settings: the Explorer-tab URL fields + custom-fees/fetch-prices checkboxes
  now persist on edit — they were the only controls still relying on the
  About-only Save button that a user on this tab never sees.
- Export-all-keys: hidden in the lite build (which has its own key export),
  instead of dead-ending on "Not connected to the daemon".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 13:02:46 -05:00
co-authored by Claude Opus 4.8
parent 8fe20f6385
commit f3f0b32a48
5 changed files with 99 additions and 42 deletions
+15 -5
View File
@@ -1548,9 +1548,13 @@ void RenderSettingsPage(App* app) {
if (cardBtn(cx, cy, cw, "##exp_key", TR("settings_export_key"), ICON_MD_LOGOUT)) app->showExportKeyDialog();
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_export_key"));
last = cy + bh; cy += bh + bgp;
if (cardBtn(cx, cy, cw, "##exp_all", TR("settings_export_all"), ICON_MD_ARCHIVE)) ExportAllKeysDialog::show();
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_export_all"));
last = cy + bh; cy += bh + bgp;
// Export-all only works over the full-node RPC; hide it in lite (which has its own key export
// in the lite wallet section) rather than dead-ending on "Not connected to the daemon".
if (fullNode) {
if (cardBtn(cx, cy, cw, "##exp_all", TR("settings_export_all"), ICON_MD_ARCHIVE)) ExportAllKeysDialog::show();
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_export_all"));
last = cy + bh; cy += bh + bgp;
}
if (cardBtn(cx, cy, cw, "##exp_csv", TR("settings_export_csv"), ICON_MD_DESCRIPTION)) ExportTransactionsDialog::show();
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_export_csv"));
last = cy + bh;
@@ -2727,7 +2731,10 @@ void RenderSettingsPage(App* app) {
ImGui::TextUnformatted(TR("transaction_url"));
ImGui::SameLine(urlRowX + urlLblW);
ImGui::SetNextItemWidth(urlInputW);
// These Explorer-tab controls persist on edit — they were the only ones still relying on the
// About-only Save button, which a user on this tab never sees.
ImGui::InputText("##TxExplorer", s_settingsState.tx_explorer, sizeof(s_settingsState.tx_explorer));
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_tx_url"));
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
@@ -2737,6 +2744,7 @@ void RenderSettingsPage(App* app) {
ImGui::SameLine(urlRowX + urlLblW);
ImGui::SetNextItemWidth(urlInputW);
ImGui::InputText("##AddrExplorer", s_settingsState.addr_explorer, sizeof(s_settingsState.addr_explorer));
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_addr_url"));
ImGui::PopFont();
@@ -2754,10 +2762,12 @@ void RenderSettingsPage(App* app) {
// Checkboxes + Block Explorer button (button wraps to its own row when it won't fit).
const float expRowRight = ImGui::GetCursorScreenPos().x + contentW;
ImGui::Checkbox(TrId("custom_fees", "custom_fees").c_str(), &s_settingsState.allow_custom_fees);
if (ImGui::Checkbox(TrId("custom_fees", "custom_fees").c_str(), &s_settingsState.allow_custom_fees))
saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_custom_fees"));
ImGui::SameLine(0, Layout::spacingLg());
ImGui::Checkbox(TrId("fetch_prices", "fetch_prices").c_str(), &s_settingsState.fetch_prices);
if (ImGui::Checkbox(TrId("fetch_prices", "fetch_prices").c_str(), &s_settingsState.fetch_prices))
saveSettingsPageState(app->settings());
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_fetch_prices"));
const float expBtnW = ImGui::CalcTextSize(TR("block_explorer")).x
+ ImGui::GetStyle().FramePadding.x * 2.0f + Layout::spacingLg();