feat(ui): in-app FAQ, DPI-scaling audit fixes, mining/stratum polish + localize strings
Bundles the session's UI work (the touched files carry several of these changes together, so they are committed as one coherent UI batch): - FAQ: new RenderFaqDialog + data-driven faq_content, opened from a status-bar "?" (and the Windows title bar), styled like the Wallets modal with search, Wallet/Daemon tabs, and smooth scroll. - DPI/font-scale audit: multiply hand-drawn absolute geometry by Layout::dpiScale() across ~30 files so nothing renders native-size at HiDPI / font_scale 1.5 (verified with a full sweep at 1.5x). - Mining: chart now fills the horizontal space; thread stepper +/- buttons match the input-box height; move the stratum-host toggle into Node & Security (v1.3.0+). - Settings: fix the auto-shield status text overlapping the grid. - Sidebar: drop the peer-count badge on the Network button. - i18n: wrap 193 hardcoded literals with TR() (keys/translations added in the preceding i18n commit), so the security/PIN/lock flow, seed-backup wizard, and witness-rebuild dialog localize. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -580,11 +580,11 @@ enum SettingsTab { TAB_APPEARANCE = 0, TAB_WALLET, TAB_BACKUP, TAB_NODE, TAB_EXP
|
||||
static void renderSettingsTabBar(float availWidth) {
|
||||
using namespace material;
|
||||
struct T { int id; const char* label; const char* idstr; };
|
||||
static const T tabs[] = {
|
||||
{TAB_APPEARANCE, "Appearance", "##stabA"}, {TAB_WALLET, "Wallet", "##stabW"},
|
||||
{TAB_BACKUP, "Backup & Data", "##stabB"}, {TAB_NODE, "Node & Security", "##stabN"},
|
||||
{TAB_EXPLORER, "Explorer", "##stabE"}, {TAB_CHAT, "Chat", "##stabC"},
|
||||
{TAB_ABOUT, "About", "##stabT"},
|
||||
const T tabs[] = {
|
||||
{TAB_APPEARANCE, TR("grpa_tab_appearance"), "##stabA"}, {TAB_WALLET, TR("grpa_tab_wallet"), "##stabW"},
|
||||
{TAB_BACKUP, TR("grpa_tab_backup_data"), "##stabB"}, {TAB_NODE, TR("grpa_tab_node_security"), "##stabN"},
|
||||
{TAB_EXPLORER, TR("grpa_tab_explorer"), "##stabE"}, {TAB_CHAT, TR("grpa_tab_chat"), "##stabC"},
|
||||
{TAB_ABOUT, TR("grpa_tab_about"), "##stabT"},
|
||||
};
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
ImFont* f = Type().body2();
|
||||
@@ -811,7 +811,7 @@ void RenderSettingsPage(App* app) {
|
||||
if (!skin.valid) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
|
||||
ImGui::BeginDisabled(true);
|
||||
std::string lbl = skin.name + " (invalid)";
|
||||
std::string lbl = skin.name + TR("grpa_invalid_suffix");
|
||||
ImGui::Selectable(lbl.c_str(), false);
|
||||
ImGui::EndDisabled();
|
||||
ImGui::PopStyleColor();
|
||||
@@ -1196,14 +1196,25 @@ void RenderSettingsPage(App* app) {
|
||||
// O1: when a v1.3.0+ node auto-shields coinbase itself, show its real state — the checkbox
|
||||
// above only governs the wallet's own fallback shielder (which defers to the node). Nothing
|
||||
// renders on pre-1.3.0 daemons (never probed), so their behaviour is unchanged.
|
||||
if (app && app->daemonAutoShieldProbed()) {
|
||||
// This status must NOT flow with ImGui's cursor: the checkbox grid is absolutely positioned
|
||||
// (SetCursorScreenPos at rowY), so a normal-flow Text would land under the auto-shield checkbox
|
||||
// and the next grid row (Use Tor / Keep daemon) would draw on top of it. Instead, draw it on its
|
||||
// own full-width row at the grid's current rowY, wrapped to the card, then advance rowY past it.
|
||||
if (app && app->daemonAutoShieldProbed() &&
|
||||
(app->daemonAutoShieldActive() || !app->daemonAutoShieldDisabledReason().empty())) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(cx, rowY));
|
||||
ImGui::PushTextWrapPos((cx + cw) - ImGui::GetWindowPos().x); // wrap to the card content width
|
||||
if (app->daemonAutoShieldActive()) {
|
||||
ImGui::TextColored(ImVec4(0.40f, 0.78f, 0.40f, 1.0f), " %s", TR("autoshield_by_node"));
|
||||
if (!app->daemonAutoShieldAddress().empty())
|
||||
ImGui::TextDisabled(" %s", app->daemonAutoShieldAddress().c_str());
|
||||
} else if (!app->daemonAutoShieldDisabledReason().empty()) {
|
||||
} else {
|
||||
ImGui::TextDisabled(" %s", app->daemonAutoShieldDisabledReason().c_str());
|
||||
}
|
||||
ImGui::PopTextWrapPos();
|
||||
last = ImGui::GetCursorScreenPos().y; // cursor is now below the status text
|
||||
rowY = last + gp; // next grid row starts below it
|
||||
c = 0;
|
||||
}
|
||||
CB(TrId("use_tor", "tor"), &s_settingsState.use_tor);
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_tor"));
|
||||
@@ -1214,26 +1225,7 @@ void RenderSettingsPage(App* app) {
|
||||
if (CB(TrId("stop_external", "stop_ext"), &s_settingsState.stop_external_daemon))
|
||||
saveSettingsPageState(app->settings());
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_stop_external"));
|
||||
|
||||
// O2: host a RandomX stratum pool from this node. Only offered on daemons that implement
|
||||
// it (v1.3.0+, version encoded major*1e6+minor*1e4+rev*100+build), so we never show a
|
||||
// toggle that does nothing. Takes effect on the next daemon start/restart. Blank allow-IP
|
||||
// = loopback only (safe); a subnet opens it to that LAN.
|
||||
if (app->state().daemon_version >= 1030000) {
|
||||
if (CB(TrId("stratum_host", "strat_host"), &s_settingsState.stratum_host))
|
||||
saveSettingsPageState(app->settings());
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_stratum_host"));
|
||||
if (s_settingsState.stratum_host) {
|
||||
ImGui::TextDisabled(" %s", TR("stratum_host_hint"));
|
||||
ImGui::SetNextItemWidth(220.0f * Layout::dpiScale());
|
||||
if (ImGui::InputTextWithHint("##stratumallowip", TR("stratum_allowip_hint"),
|
||||
s_settingsState.stratum_allowip, sizeof(s_settingsState.stratum_allowip)))
|
||||
saveSettingsPageState(app->settings());
|
||||
if (s_settingsState.stratum_allowip[0] != '\0')
|
||||
ImGui::TextColored(ImVec4(0.95f, 0.75f, 0.25f, 1.0f), " %s",
|
||||
TR("stratum_expose_warn"));
|
||||
}
|
||||
}
|
||||
// (Stratum pool hosting lives in the Node & Security tab — it's a node feature.)
|
||||
}
|
||||
if (CB(TrId("verbose_logging", "verbose"), &s_settingsState.verbose_logging)) {
|
||||
dragonx::util::Logger::instance().setVerbose(s_settingsState.verbose_logging);
|
||||
@@ -1697,7 +1689,7 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted(TR("lite_birthday_label"));
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
|
||||
ImGui::SetNextItemWidth(std::min(160.0f * dp, liteInputW));
|
||||
ImGui::InputInt("##LiteRestoreBirthday", &s_settingsState.lite_restore_birthday);
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_restore_birthday"));
|
||||
if (s_settingsState.lite_restore_birthday < 0) s_settingsState.lite_restore_birthday = 0;
|
||||
@@ -1709,7 +1701,7 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted(TR("lite_account_label"));
|
||||
ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW);
|
||||
ImGui::SetNextItemWidth(std::min(160.0f, liteInputW));
|
||||
ImGui::SetNextItemWidth(std::min(160.0f * dp, liteInputW));
|
||||
ImGui::InputInt("##LiteRestoreAccount", &s_settingsState.lite_restore_account);
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_lite_restore_account"));
|
||||
if (s_settingsState.lite_restore_account < 0) s_settingsState.lite_restore_account = 0;
|
||||
@@ -1889,7 +1881,7 @@ void RenderSettingsPage(App* app) {
|
||||
while (!liteKey.empty() && (liteKey.front()==' '||liteKey.front()=='\t'||liteKey.front()=='\n'||liteKey.front()=='\r')) liteKey.erase(liteKey.begin());
|
||||
while (!liteKey.empty() && (liteKey.back()==' '||liteKey.back()=='\t'||liteKey.back()=='\n'||liteKey.back()=='\r')) liteKey.pop_back();
|
||||
if (liteKey.empty()) {
|
||||
s_settingsState.lite_backup_status = "Enter a private key to import.";
|
||||
s_settingsState.lite_backup_status = TR("grpa_enter_private_key_to_import");
|
||||
} else {
|
||||
const auto r = app->liteWallet()->importKey(liteKey);
|
||||
sodium_memzero(s_settingsState.lite_import_key, sizeof(s_settingsState.lite_import_key));
|
||||
@@ -2650,6 +2642,10 @@ void RenderSettingsPage(App* app) {
|
||||
if (material::ActionButton("##aboutbug", TR("report_bug"), ICON_MD_BUG_REPORT, AT::Secondary))
|
||||
util::Platform::openUrl("https://git.dragonx.is/dragonx/ObsidianDragon/issues");
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_report_bug"));
|
||||
lf.next(material::ActionButtonWidth(TR("faq"), ICON_MD_QUESTION_MARK));
|
||||
if (material::ActionButton("##aboutfaq", TR("faq"), ICON_MD_QUESTION_MARK, AT::Secondary))
|
||||
app->showFaqDialog();
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("faq_open_tooltip"));
|
||||
}
|
||||
const float rightBottom = ImGui::GetCursorScreenPos().y;
|
||||
|
||||
@@ -2676,6 +2672,33 @@ void RenderSettingsPage(App* app) {
|
||||
if (s_settingsState.current_tab == TAB_NODE)
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
|
||||
// ====================================================================
|
||||
// MINING POOL HOSTING — run this node's built-in RandomX stratum server so other miners can point
|
||||
// at this machine. It's a node feature, so it belongs here (not the Wallet tab). Full-node only, and
|
||||
// only on a daemon new enough to implement -stratum (v1.3.0+, version encoded
|
||||
// major*1e6+minor*1e4+rev*100+build). Takes effect on the next daemon start/restart; a blank allow-IP
|
||||
// keeps it loopback-only (safe), a subnet opens it to that LAN.
|
||||
// ====================================================================
|
||||
if (app->supportsFullNodeLifecycleActions() && s_settingsState.current_tab == TAB_NODE &&
|
||||
app->state().daemon_version >= 1030000) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("stratum_host_section"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
material::GlassCardScope card(dl, availWidth, pad, bottomPad, glassSpec);
|
||||
if (ImGui::Checkbox(TR("stratum_host"), &s_settingsState.stratum_host))
|
||||
saveSettingsPageState(app->settings());
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_stratum_host"));
|
||||
if (s_settingsState.stratum_host) {
|
||||
ImGui::TextDisabled(" %s", TR("stratum_host_hint"));
|
||||
ImGui::SetNextItemWidth(220.0f * Layout::dpiScale());
|
||||
if (ImGui::InputTextWithHint("##stratumallowip", TR("stratum_allowip_hint"),
|
||||
s_settingsState.stratum_allowip, sizeof(s_settingsState.stratum_allowip)))
|
||||
saveSettingsPageState(app->settings());
|
||||
if (s_settingsState.stratum_allowip[0] != '\0')
|
||||
ImGui::TextColored(ImVec4(0.95f, 0.75f, 0.25f, 1.0f), " %s",
|
||||
TR("stratum_expose_warn"));
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// DEBUG OPTIONS — collapsible card (full-node only: holds the screenshot sweep + the dragonxd
|
||||
// daemon debug= categories written to DRAGONX.conf; lite has no daemon). Shown on the Node tab.
|
||||
@@ -2738,7 +2761,7 @@ void RenderSettingsPage(App* app) {
|
||||
if (chat::hushChatFeatureEnabledAtBuild()) {
|
||||
// Populate the Chat tab with demo conversations so the sweep captures its real UI.
|
||||
ImGui::SameLine();
|
||||
if (TactileButton("Seed demo chat", ImVec2(0, 0), S.resolveFont("button")))
|
||||
if (TactileButton(TR("grpa_seed_demo_chat"), ImVec2(0, 0), S.resolveFont("button")))
|
||||
app->seedChatDemoData();
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_seed_demo_chat"));
|
||||
}
|
||||
@@ -2765,29 +2788,29 @@ void RenderSettingsPage(App* app) {
|
||||
"paymentdisclosure", "pow", "proxy", "prune", "rand",
|
||||
"reindex", "rpc", "selectcoins", "tor", "zmq", "zrpc"
|
||||
};
|
||||
static const char* debugTips[] = {
|
||||
"Peer address tracking and management",
|
||||
"Alert system messages",
|
||||
"Benchmark timings for operations",
|
||||
"Coin database read/write operations",
|
||||
"Berkeley DB operations",
|
||||
"Fee estimation algorithm",
|
||||
"HTTP RPC server activity",
|
||||
"Libevent networking library",
|
||||
"Lock contention debugging",
|
||||
"Transaction memory pool activity",
|
||||
"Network connections and messages",
|
||||
"Payment disclosure protocol",
|
||||
"Proof-of-work mining activity",
|
||||
"SOCKS5 proxy connections",
|
||||
"Block pruning operations",
|
||||
"Random number generation",
|
||||
"Blockchain reindexing progress",
|
||||
"RPC command processing",
|
||||
"Coin selection for transactions",
|
||||
"Tor integration and circuit info",
|
||||
"ZeroMQ notification system",
|
||||
"Shielded (z-addr) RPC operations"
|
||||
const char* debugTips[] = {
|
||||
TR("grpa_dbg_addrman"),
|
||||
TR("grpa_dbg_alert"),
|
||||
TR("grpa_dbg_bench"),
|
||||
TR("grpa_dbg_coindb"),
|
||||
TR("grpa_dbg_db"),
|
||||
TR("grpa_dbg_estimatefee"),
|
||||
TR("grpa_dbg_http"),
|
||||
TR("grpa_dbg_libevent"),
|
||||
TR("grpa_dbg_lock"),
|
||||
TR("grpa_dbg_mempool"),
|
||||
TR("grpa_dbg_net"),
|
||||
TR("grpa_dbg_paymentdisclosure"),
|
||||
TR("grpa_dbg_pow"),
|
||||
TR("grpa_dbg_proxy"),
|
||||
TR("grpa_dbg_prune"),
|
||||
TR("grpa_dbg_rand"),
|
||||
TR("grpa_dbg_reindex"),
|
||||
TR("grpa_dbg_rpc"),
|
||||
TR("grpa_dbg_selectcoins"),
|
||||
TR("grpa_dbg_tor"),
|
||||
TR("grpa_dbg_zmq"),
|
||||
TR("grpa_dbg_zrpc")
|
||||
};
|
||||
constexpr int numCats = sizeof(debugCats) / sizeof(debugCats[0]);
|
||||
|
||||
@@ -3037,7 +3060,7 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::TextWrapped("%s", TR("rescan_bootstrapped_msg"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("%s", TR("rescan_from_height"));
|
||||
ImGui::SetNextItemWidth(160.0f);
|
||||
ImGui::SetNextItemWidth(160.0f * dp);
|
||||
ImGui::InputInt("##rescanHeight", &s_settingsState.rescan_start_height);
|
||||
if (s_settingsState.rescan_start_height < 0) s_settingsState.rescan_start_height = 0;
|
||||
} else {
|
||||
@@ -3051,12 +3074,12 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (material::TactileButton(TrId("cancel", "rescan_cancel").c_str(), ImVec2(btnW, 40))) {
|
||||
if (material::TactileButton(TrId("cancel", "rescan_cancel").c_str(), ImVec2(btnW, 40 * dp))) {
|
||||
s_settingsState.confirm_rescan = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(detecting);
|
||||
if (material::TactileButton(TrId("rescan", "rescan_confirm").c_str(), ImVec2(btnW, 40))) {
|
||||
if (material::TactileButton(TrId("rescan", "rescan_confirm").c_str(), ImVec2(btnW, 40 * dp))) {
|
||||
if (bootstrapped) {
|
||||
app->runtimeRescan(s_settingsState.rescan_start_height);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user