Refactor app services and stabilize refresh/UI flows

- Add refresh scheduler and network refresh service boundaries for typed
  refresh results, ordered RPC collectors, applicators, and price parsing.
- Add daemon lifecycle and wallet security workflow helpers while preserving
  App-owned command RPC, decrypt, cancellation, and UI handoff behavior.
- Split balance, console, mining, amount formatting, and async task logic into
  focused modules with expanded Phase 4 test coverage.
- Fix market price loading by triggering price refresh immediately, avoiding
  queue-pressure drops, tracking loading/error state, and adding translations.
- Polish send, explorer, peers, settings, theme/schema, and related tab UI.
- Replace checked-in generated language headers with build-generated resources.
- Document the cleanup audit, UI static-state guidance, and architecture updates.
This commit is contained in:
dan_s
2026-04-29 12:47:57 -05:00
parent 9e1b1397ad
commit d684db446e
95 changed files with 8776 additions and 37563 deletions

View File

@@ -10,6 +10,7 @@
#include "rpc/rpc_worker.h"
#include "rpc/connection.h"
#include "config/settings.h"
#include "daemon/daemon_controller.h"
#include "daemon/embedded_daemon.h"
#include "ui/notifications.h"
#include "ui/material/color_theme.h"
@@ -39,13 +40,43 @@ namespace dragonx {
using json = nlohmann::json;
namespace {
struct WizardLowSpecSnapshot {
bool valid = false;
float blur = 0.0f;
float uiOp = 0.0f;
bool fx = false;
bool scanline = false;
};
struct WizardUiState {
float blur_amount = 1.5f;
bool theme_effects = true;
float ui_opacity = 1.0f;
bool low_spec = false;
bool scanline = true;
std::string balance_layout = "classic";
int language_index = 0;
bool appearance_init = false;
WizardLowSpecSnapshot low_spec_snapshot;
float card0_max_h = 0.0f;
float card1_max_h = 0.0f;
double external_last_check = -10.0;
bool daemon_prestarted = false;
};
WizardUiState s_wizardUi;
} // namespace
void App::restartWizard()
{
DEBUG_LOGF("[App] Restarting setup wizard — stopping daemon...\n");
// Reset crash counter for fresh wizard attempt
if (embedded_daemon_) {
embedded_daemon_->resetCrashCount();
if (daemon_controller_) {
daemon_controller_->resetCrashCount();
}
// Disconnect RPC
@@ -56,10 +87,11 @@ void App::restartWizard()
// Stop the embedded daemon in a background thread to avoid
// blocking the UI for up to 32 seconds (RPC stop + process wait).
if (embedded_daemon_ && isEmbeddedDaemonRunning()) {
std::thread([this]() {
if (daemon_controller_ && isEmbeddedDaemonRunning()) {
async_tasks_.submit("wizard-restart-stop-daemon", [this](const util::AsyncTaskManager::Token& token) {
if (token.cancelled()) return;
stopEmbeddedDaemon();
}).detach();
});
}
// Enter wizard — the wizard completion handler already calls
@@ -73,6 +105,7 @@ void App::restartWizard()
// ===========================================================================
void App::renderFirstRunWizard() {
auto& wizardUi = s_wizardUi;
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
@@ -243,15 +276,14 @@ void App::renderFirstRunWizard() {
(textCol & 0x00FFFFFF) | IM_COL32(0,0,0,40), 1.0f * dp);
cy += 14.0f * dp;
// Statics for appearance settings
static float wiz_blur_amount = 1.5f;
static bool wiz_theme_effects = true;
static float wiz_ui_opacity = 1.0f;
static bool wiz_low_spec = false;
static bool wiz_scanline = true;
static std::string wiz_balance_layout = "classic";
static int wiz_language_index = 0;
static bool wiz_appearance_init = false;
float& wiz_blur_amount = wizardUi.blur_amount;
bool& wiz_theme_effects = wizardUi.theme_effects;
float& wiz_ui_opacity = wizardUi.ui_opacity;
bool& wiz_low_spec = wizardUi.low_spec;
bool& wiz_scanline = wizardUi.scanline;
std::string& wiz_balance_layout = wizardUi.balance_layout;
int& wiz_language_index = wizardUi.language_index;
bool& wiz_appearance_init = wizardUi.appearance_init;
if (!wiz_appearance_init) {
wiz_blur_amount = settings_->getBlurMultiplier();
wiz_theme_effects = settings_->getThemeEffectsEnabled();
@@ -398,7 +430,7 @@ void App::renderFirstRunWizard() {
// --- Low-spec mode checkbox ---
// Snapshot for restoring settings when low-spec is turned off
static struct { bool valid; float blur; float uiOp; bool fx; bool scanline; } wiz_lsSnap = {};
WizardLowSpecSnapshot& wiz_lsSnap = wizardUi.low_spec_snapshot;
ImGui::SetCursorScreenPos(ImVec2(cx, cy));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
@@ -596,7 +628,7 @@ void App::renderFirstRunWizard() {
cy += cardPad;
// Lock card height to the tallest content ever seen
static float card0MaxH = 0.0f;
float& card0MaxH = wizardUi.card0_max_h;
card0MaxH = std::max(card0MaxH, cy - card0Top);
card0Bot = card0Top + card0MaxH;
@@ -774,14 +806,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
if (ImGui::Button("Retry##bs", ImVec2(btnW2, btnH2))) {
// Stop embedded daemon before bootstrap to avoid chain data corruption
if (isEmbeddedDaemonRunning()) {
DEBUG_LOGF("[Wizard] Stopping embedded daemon before bootstrap retry...\n");
if (rpc_ && rpc_->isConnected()) {
try { rpc_->call("stop"); } catch (...) {}
rpc_->disconnect();
}
onDisconnected("Bootstrap retry");
}
stopDaemonForBootstrap();
bootstrap_ = std::make_unique<util::Bootstrap>();
std::string dataDir = util::Platform::getDragonXDataDir();
bootstrap_->start(dataDir);
@@ -808,17 +833,23 @@ void App::renderFirstRunWizard() {
if (isFocused) {
static std::atomic<bool> s_extCached{false};
static std::atomic<bool> s_checkInFlight{false};
static double s_extLastCheck = -10.0;
double& s_extLastCheck = wizardUi.external_last_check;
double now = ImGui::GetTime();
if (now - s_extLastCheck >= 2.0 && !s_checkInFlight.load()) {
s_extLastCheck = now;
bool embeddedRunning = isEmbeddedDaemonRunning();
s_checkInFlight.store(true);
std::thread([embeddedRunning]() {
async_tasks_.submit("wizard-external-daemon-check", [embeddedRunning](const util::AsyncTaskManager::Token& token) {
if (token.cancelled()) {
s_checkInFlight.store(false);
return;
}
bool inUse = daemon::EmbeddedDaemon::isRpcPortInUse();
s_extCached.store(inUse && !embeddedRunning);
if (!token.cancelled()) {
s_extCached.store(inUse && !embeddedRunning);
}
s_checkInFlight.store(false);
}).detach();
});
}
externalRunning = s_extCached.load();
}
@@ -859,19 +890,19 @@ void App::renderFirstRunWizard() {
if (ImGui::Button("Stop Daemon##wiz", ImVec2(stopW, btnH2))) {
wizard_stopping_external_ = true;
wizard_stop_status_ = "Sending stop command...";
if (wizard_stop_thread_.joinable()) wizard_stop_thread_.join();
wizard_stop_thread_ = std::thread([this]() {
async_tasks_.submit("wizard-stop-external-daemon", [this](const util::AsyncTaskManager::Token& token) {
auto config = rpc::Connection::autoDetectConfig();
if (!config.rpcuser.empty() && !config.rpcpassword.empty()) {
auto tmp_rpc = std::make_unique<rpc::RPCClient>();
if (tmp_rpc->connect(config.host, config.port,
config.rpcuser, config.rpcpassword)) {
try { tmp_rpc->call("stop"); } catch (...) {}
config.rpcuser, config.rpcpassword,
config.use_tls)) {
sendStopCommandSafely(*tmp_rpc, "Wizard external daemon stop");
tmp_rpc->disconnect();
}
}
wizard_stop_status_ = "Waiting for daemon to shut down...";
for (int i = 0; i < 60; i++) {
for (int i = 0; i < 60 && !token.cancelled(); i++) {
std::this_thread::sleep_for(std::chrono::seconds(1));
if (!daemon::EmbeddedDaemon::isRpcPortInUse()) {
wizard_stop_status_ = "Daemon stopped.";
@@ -879,6 +910,7 @@ void App::renderFirstRunWizard() {
return;
}
}
if (token.cancelled()) return;
wizard_stop_status_ = "Daemon did not stop — try manually.";
wizard_stopping_external_ = false;
});
@@ -955,14 +987,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
if (ImGui::Button("Download##bs", ImVec2(dlBtnW, btnH2))) {
// Stop embedded daemon before bootstrap to avoid chain data corruption
if (isEmbeddedDaemonRunning()) {
DEBUG_LOGF("[Wizard] Stopping embedded daemon before bootstrap...\n");
if (rpc_ && rpc_->isConnected()) {
try { rpc_->call("stop"); } catch (...) {}
rpc_->disconnect();
}
onDisconnected("Bootstrap");
}
stopDaemonForBootstrap();
bootstrap_ = std::make_unique<util::Bootstrap>();
std::string dataDir = util::Platform::getDragonXDataDir();
bootstrap_->start(dataDir);
@@ -978,14 +1003,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnSurface()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
if (ImGui::Button("Mirror##bs_mirror", ImVec2(mirrorW, btnH2))) {
if (isEmbeddedDaemonRunning()) {
DEBUG_LOGF("[Wizard] Stopping embedded daemon before bootstrap (mirror)...\n");
if (rpc_ && rpc_->isConnected()) {
try { rpc_->call("stop"); } catch (...) {}
rpc_->disconnect();
}
onDisconnected("Bootstrap");
}
stopDaemonForBootstrap();
bootstrap_ = std::make_unique<util::Bootstrap>();
std::string dataDir = util::Platform::getDragonXDataDir();
std::string mirrorUrl = std::string(util::Bootstrap::kMirrorUrl) + "/" + util::Bootstrap::kZipName;
@@ -1012,7 +1030,7 @@ void App::renderFirstRunWizard() {
cy += cardPad;
// Lock card height to the tallest content ever seen (but not when collapsed)
static float card1MaxH = 0.0f;
float& card1MaxH = wizardUi.card1_max_h;
if (isCollapsed) {
card1Bot = card1Top + (cy - card1Top);
} else {
@@ -1037,7 +1055,7 @@ void App::renderFirstRunWizard() {
// Pre-start daemon when encrypt card becomes focused so it's ready
// by the time the user finishes typing their passphrase
if (isFocused) {
static bool wiz_daemon_prestarted = false;
bool& wiz_daemon_prestarted = wizardUi.daemon_prestarted;
if (!wiz_daemon_prestarted) {
wiz_daemon_prestarted = true;
if (!state_.connected && isUsingEmbeddedDaemon() && !isEmbeddedDaemonRunning()) {
@@ -1281,10 +1299,9 @@ void App::renderFirstRunWizard() {
ImGui::BeginDisabled(!canEncrypt);
if (ImGui::Button("Encrypt & Continue##wiz", ImVec2(encBtnW, btnH2))) {
// Save passphrase + optional PIN for background processing
deferred_encrypt_passphrase_ = std::string(encrypt_pass_buf_);
if (pinEntered && pinOk)
deferred_encrypt_pin_ = pinStr;
deferred_encrypt_pending_ = true;
wallet_security_.beginDeferredEncryption(
std::string(encrypt_pass_buf_),
(pinEntered && pinOk) ? pinStr : std::string());
// Clear sensitive buffers
memset(encrypt_pass_buf_, 0, sizeof(encrypt_pass_buf_));