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:
+13
-37
@@ -3084,45 +3084,19 @@ void App::renderStatusBar()
|
||||
int blocksLeft = chainTip - state_.sync.blocks;
|
||||
if (blocksLeft < 0) blocksLeft = 0;
|
||||
|
||||
// Calculate sync speed (blocks/sec) using a smoothed rolling average
|
||||
static int s_prev_blocks = 0;
|
||||
static double s_prev_time = 0.0;
|
||||
static double s_blocks_per_sec = 0.0;
|
||||
double now = ImGui::GetTime();
|
||||
if (s_prev_time == 0.0) {
|
||||
// First sample — seed baseline, don't compute rate yet
|
||||
s_prev_blocks = state_.sync.blocks;
|
||||
s_prev_time = now;
|
||||
} else if (state_.sync.blocks > s_prev_blocks) {
|
||||
double dt = now - s_prev_time;
|
||||
if (dt > 0.5) {
|
||||
double raw = (state_.sync.blocks - s_prev_blocks) / dt;
|
||||
// Exponential smoothing (alpha ~0.3 per update)
|
||||
s_blocks_per_sec = s_blocks_per_sec > 0.0
|
||||
? s_blocks_per_sec * 0.7 + raw * 0.3
|
||||
: raw;
|
||||
s_prev_blocks = state_.sync.blocks;
|
||||
s_prev_time = now;
|
||||
}
|
||||
} else if (now - s_prev_time > 10.0) {
|
||||
// No new blocks for 10s — decay the displayed rate
|
||||
s_blocks_per_sec *= 0.5;
|
||||
s_prev_time = now;
|
||||
}
|
||||
|
||||
if (s_blocks_per_sec > 0.1) {
|
||||
int eta_sec = (int)(blocksLeft / s_blocks_per_sec);
|
||||
// App-owned smoothed sync-rate estimator: self-resets after a rescan/restart height drop (and on
|
||||
// connect/disconnect via the pending flag), so the ETA never goes stale/absurd ("412h"); days-aware.
|
||||
if (sync_eta_reset_pending_.exchange(false)) sync_eta_.reset();
|
||||
sync_eta_.update(state_.sync.blocks, ImGui::GetTime());
|
||||
const ImVec4 syncCol = ImGui::ColorConvertU32ToFloat4(ui::material::Warning());
|
||||
const long long etaSec = sync_eta_.etaSeconds(blocksLeft);
|
||||
if (etaSec >= 0) {
|
||||
char eta[32];
|
||||
if (eta_sec >= 3600)
|
||||
snprintf(eta, sizeof(eta), "%dh %dm", eta_sec / 3600, (eta_sec % 3600) / 60);
|
||||
else if (eta_sec >= 60)
|
||||
snprintf(eta, sizeof(eta), "%dm %ds", eta_sec / 60, eta_sec % 60);
|
||||
else
|
||||
snprintf(eta, sizeof(eta), "%ds", eta_sec);
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), TR("sb_syncing_eta"),
|
||||
state_.sync.verification_progress * 100.0, blocksLeft, s_blocks_per_sec, eta);
|
||||
util::formatEta(etaSec, eta, sizeof(eta));
|
||||
ImGui::TextColored(syncCol, TR("sb_syncing_eta"),
|
||||
state_.sync.verification_progress * 100.0, blocksLeft, sync_eta_.rate(), eta);
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), TR("sb_syncing_basic"),
|
||||
ImGui::TextColored(syncCol, TR("sb_syncing_basic"),
|
||||
state_.sync.verification_progress * 100.0, blocksLeft);
|
||||
}
|
||||
} else if (state_.connected && !isLiteBuild()) {
|
||||
@@ -6337,6 +6311,7 @@ void App::rescanBlockchain()
|
||||
// actually observe the restarted daemon rescanning — so the first poll (which may still reach the
|
||||
// pre-restart daemon and see rescanning=false) can't be misread as instant completion.
|
||||
state_.sync.rescanning = true;
|
||||
sync_eta_reset_pending_.store(true, std::memory_order_relaxed); // rescan drops height → reset ETA
|
||||
rescan_confirmed_active_ = false;
|
||||
user_initiated_rescan_ = true; // user-triggered rescan → its completion should toast
|
||||
state_.sync.rescan_progress = 0.0f;
|
||||
@@ -8136,6 +8111,7 @@ std::string App::buildDiagnosticsReport()
|
||||
|
||||
void App::restartDaemon()
|
||||
{
|
||||
sync_eta_reset_pending_.store(true, std::memory_order_relaxed);
|
||||
if (!supportsFullNodeLifecycleActions()) {
|
||||
ui::Notifications::instance().warning(TR("appx_fullnode_lifecycle_unavailable_lite"));
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user