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:
+44
-45
@@ -300,14 +300,14 @@ void App::restartDaemonAfterEncryption(const char* taskName, bool announceRestar
|
||||
});
|
||||
} else {
|
||||
ui::Notifications::instance().warning(
|
||||
"Please restart your daemon for encryption to take effect.");
|
||||
TR("sec_restart_daemon_for_encryption"));
|
||||
}
|
||||
}
|
||||
|
||||
void App::encryptWalletWithPassphrase(const std::string& passphrase) {
|
||||
if (!rpc_ || !rpc_->isConnected()) return;
|
||||
encrypt_in_progress_ = true;
|
||||
encrypt_status_ = "Encrypting wallet...";
|
||||
encrypt_status_ = TR("sec_encrypting_wallet");
|
||||
|
||||
if (worker_) {
|
||||
worker_->post([this, passphrase]() mutable -> rpc::RPCWorker::MainCb {
|
||||
@@ -317,7 +317,7 @@ void App::encryptWalletWithPassphrase(const std::string& passphrase) {
|
||||
if (result.encrypted) {
|
||||
return [this]() {
|
||||
encrypt_in_progress_ = false;
|
||||
encrypt_status_ = "Wallet encrypted. Restarting daemon...";
|
||||
encrypt_status_ = TR("sec_wallet_encrypted_restarting_daemon");
|
||||
DEBUG_LOGF("[App] Wallet encrypted — restarting daemon\n");
|
||||
|
||||
// Immediately update local encryption state so the
|
||||
@@ -336,7 +336,7 @@ void App::encryptWalletWithPassphrase(const std::string& passphrase) {
|
||||
}
|
||||
|
||||
ui::Notifications::instance().info(
|
||||
"Wallet encrypted successfully", 5.0f);
|
||||
TR("sec_wallet_encrypted_successfully"), 5.0f);
|
||||
|
||||
// The daemon shuts itself down after encryptwallet.
|
||||
// Update connection_status_ so the loading overlay
|
||||
@@ -348,11 +348,11 @@ void App::encryptWalletWithPassphrase(const std::string& passphrase) {
|
||||
std::string err = result.error;
|
||||
return [this, err]() {
|
||||
encrypt_in_progress_ = false;
|
||||
encrypt_status_ = "Encryption failed: " + err;
|
||||
encrypt_status_ = std::string(TR("sec_encryption_failed_prefix")) + err;
|
||||
DEBUG_LOGF("[App] encryptwallet failed: %s\n", err.c_str());
|
||||
|
||||
ui::Notifications::instance().error(
|
||||
"Encryption failed: " + err);
|
||||
std::string(TR("sec_encryption_failed_prefix")) + err);
|
||||
|
||||
// Return to passphrase entry on failure
|
||||
if (show_encrypt_dialog_ &&
|
||||
@@ -393,7 +393,7 @@ void App::processDeferredEncryption() {
|
||||
std::string pin = std::move(deferredEncryption.pin);
|
||||
|
||||
encrypt_in_progress_ = true;
|
||||
encrypt_status_ = "Encrypting wallet...";
|
||||
encrypt_status_ = TR("sec_encrypting_wallet");
|
||||
|
||||
if (worker_) {
|
||||
worker_->post([this, request = services::WalletSecurityController::DeferredEncryptionSnapshot{std::move(passphrase), std::move(pin)}]() mutable -> rpc::RPCWorker::MainCb {
|
||||
@@ -413,13 +413,13 @@ void App::processDeferredEncryption() {
|
||||
if (result.pinStored) {
|
||||
settings_->setPinEnabled(true);
|
||||
settings_->save();
|
||||
ui::Notifications::instance().info("Wallet encrypted & PIN set", 5.0f);
|
||||
ui::Notifications::instance().info(TR("sec_wallet_encrypted_and_pin_set"), 5.0f);
|
||||
} else {
|
||||
ui::Notifications::instance().warning(
|
||||
"Wallet encrypted but PIN vault failed");
|
||||
TR("sec_wallet_encrypted_but_pin_vault_failed"));
|
||||
}
|
||||
} else {
|
||||
ui::Notifications::instance().info("Wallet encrypted successfully", 5.0f);
|
||||
ui::Notifications::instance().info(TR("sec_wallet_encrypted_successfully"), 5.0f);
|
||||
}
|
||||
|
||||
wallet_security_.clearDeferredEncryption();
|
||||
@@ -432,9 +432,9 @@ void App::processDeferredEncryption() {
|
||||
std::string err = result.error;
|
||||
return [this, err]() {
|
||||
encrypt_in_progress_ = false;
|
||||
encrypt_status_ = "Encryption failed: " + err;
|
||||
encrypt_status_ = std::string(TR("sec_encryption_failed_prefix")) + err;
|
||||
DEBUG_LOGF("[App] Deferred encryptwallet failed: %s\n", err.c_str());
|
||||
ui::Notifications::instance().error("Encryption failed: " + err);
|
||||
ui::Notifications::instance().error(std::string(TR("sec_encryption_failed_prefix")) + err);
|
||||
wallet_security_.clearDeferredEncryption();
|
||||
};
|
||||
}
|
||||
@@ -531,7 +531,7 @@ void App::lockWallet() {
|
||||
if (!lock_failure_warned_) {
|
||||
lock_failure_warned_ = true;
|
||||
ui::Notifications::instance().warning(
|
||||
"Couldn't lock the wallet — it is still unlocked. Check the daemon connection.", 12.0f);
|
||||
TR("sec_couldnt_lock_wallet"), 12.0f);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -541,7 +541,7 @@ void App::lockWallet() {
|
||||
void App::changePassphrase(const std::string& oldPass, const std::string& newPass) {
|
||||
if (!rpc_ || !rpc_->isConnected() || !worker_) return;
|
||||
encrypt_in_progress_ = true;
|
||||
encrypt_status_ = "Changing passphrase...";
|
||||
encrypt_status_ = TR("sec_changing_passphrase");
|
||||
|
||||
auto* w = (fast_worker_ && fast_worker_->isRunning()) ? fast_worker_.get() : worker_.get();
|
||||
auto* r = (fast_rpc_ && fast_rpc_->isConnected()) ? fast_rpc_.get() : rpc_.get();
|
||||
@@ -574,9 +574,9 @@ void App::changePassphrase(const std::string& oldPass, const std::string& newPas
|
||||
memset(change_confirm_buf_, 0, sizeof(change_confirm_buf_));
|
||||
unlockTransactionHistoryCacheWithPassphrase(newPass);
|
||||
storeTransactionHistoryCacheIfAvailable();
|
||||
ui::Notifications::instance().info("Passphrase changed successfully");
|
||||
ui::Notifications::instance().info(TR("sec_passphrase_changed_successfully"));
|
||||
} else {
|
||||
encrypt_status_ = "Failed: " + err_msg;
|
||||
encrypt_status_ = std::string(TR("sec_failed_prefix")) + err_msg;
|
||||
}
|
||||
util::SecureVault::secureZero(newPass.data(), newPass.size());
|
||||
};
|
||||
@@ -637,8 +637,7 @@ void App::refreshWalletEncryptionState() {
|
||||
!encryption_incomplete_warned_) {
|
||||
encryption_incomplete_warned_ = true;
|
||||
ui::Notifications::instance().warning(
|
||||
"Wallet encryption did not complete — your wallet is NOT encrypted. "
|
||||
"Open Settings to finish encrypting it.", 30.0f);
|
||||
TR("sec_encryption_did_not_complete"), 30.0f);
|
||||
}
|
||||
if (state_.transactions.empty()) {
|
||||
loadTransactionHistoryCacheIfAvailable();
|
||||
@@ -951,7 +950,7 @@ void App::renderLockScreen() {
|
||||
ImU32 textCol = ui::material::OnSurface();
|
||||
|
||||
{
|
||||
const char* title = "Wallet Locked";
|
||||
const char* title = TR("sec_wallet_locked_title");
|
||||
ImVec2 ts = titleFont->CalcTextSizeA(titleFont->LegacySize, FLT_MAX, 0, title);
|
||||
dl->AddText(titleFont, titleFont->LegacySize,
|
||||
ImVec2(cardX + (cardW - ts.x) * 0.5f, cy), textCol, title);
|
||||
@@ -964,7 +963,7 @@ void App::renderLockScreen() {
|
||||
if (lock_lockout_timer_ < 0) lock_lockout_timer_ = 0;
|
||||
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof(msg), "Too many attempts. Wait %.0f seconds...", lock_lockout_timer_);
|
||||
snprintf(msg, sizeof(msg), TR("sec_too_many_attempts_wait"), lock_lockout_timer_);
|
||||
ImVec2 ms = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, msg);
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(cardX + (cardW - ms.x) * 0.5f, cy), ui::material::Warning(), msg);
|
||||
@@ -977,10 +976,10 @@ void App::renderLockScreen() {
|
||||
// Mode toggle (PIN / Passphrase) — only show if PIN vault exists
|
||||
if (hasPinVault) {
|
||||
const char* modeIcon = lock_use_pin_ ? ICON_MD_DIALPAD : ICON_MD_PASSWORD;
|
||||
const char* modeText = lock_use_pin_ ? " PIN" : " Passphrase";
|
||||
const char* modeText = lock_use_pin_ ? " PIN" : TR("sec_mode_passphrase");
|
||||
const char* switchLabel = lock_use_pin_
|
||||
? "Use passphrase instead"
|
||||
: "Use PIN instead";
|
||||
? TR("sec_use_passphrase_instead")
|
||||
: TR("sec_use_pin_instead");
|
||||
|
||||
// Current mode indicator — icon with icon font, text with caption font
|
||||
ImFont* iconFont = ui::material::Type().iconSmall();
|
||||
@@ -1082,7 +1081,7 @@ void App::renderLockScreen() {
|
||||
if (lock_unlock_in_progress_) {
|
||||
// Animated spinner dots
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "Unlocking%s", ui::material::LoadingDots());
|
||||
snprintf(msg, sizeof(msg), TR("sec_unlocking_fmt"), ui::material::LoadingDots());
|
||||
ImVec2 ms = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, msg);
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(cardX + (cardW - ms.x) * 0.5f, cy),
|
||||
@@ -1106,7 +1105,7 @@ void App::renderLockScreen() {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||
ImGui::BeginDisabled(!canSubmit);
|
||||
bool btnClicked = ui::material::TactileButton("Unlock", ImVec2(unlockW, unlockH));
|
||||
bool btnClicked = ui::material::TactileButton(TR("sec_unlock_button"), ImVec2(unlockW, unlockH));
|
||||
ImGui::EndDisabled();
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor(3);
|
||||
@@ -1160,7 +1159,7 @@ void App::renderLockScreen() {
|
||||
r->call("walletpassphrase", {passphrase, timeout});
|
||||
rpcOk = true;
|
||||
} else {
|
||||
rpcErr = "Not connected to daemon";
|
||||
rpcErr = TR("sec_not_connected_to_daemon");
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
rpcErr = e.what();
|
||||
@@ -1176,7 +1175,7 @@ void App::renderLockScreen() {
|
||||
// so route through applyUnlockFailure so the lockout curve applies
|
||||
// (this path previously bumped the counter but skipped the lockout math).
|
||||
return [this, rpcErr, passphrase = std::move(passphrase)]() mutable {
|
||||
applyUnlockFailure("Unlock failed: " + rpcErr);
|
||||
applyUnlockFailure(std::string(TR("sec_unlock_failed_prefix")) + rpcErr);
|
||||
util::SecureVault::secureZero(passphrase.data(), passphrase.size());
|
||||
};
|
||||
}
|
||||
@@ -1579,7 +1578,7 @@ void App::renderDecryptWalletDialog() {
|
||||
if (!passphrase.empty()) sodium_memzero(&passphrase[0], passphrase.size());
|
||||
if (!unlock.ok) {
|
||||
return [this]() {
|
||||
wallet_security_workflow_.failEntry("Incorrect passphrase");
|
||||
wallet_security_workflow_.failEntry(TR("sec_incorrect_passphrase_decrypt"));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1695,7 +1694,7 @@ void App::renderDecryptWalletDialog() {
|
||||
});
|
||||
|
||||
ui::Notifications::instance().info(
|
||||
"Importing keys & rescanning blockchain — wallet is usable while this runs",
|
||||
TR("sec_importing_keys_rescanning"),
|
||||
8.0f);
|
||||
};
|
||||
});
|
||||
@@ -1714,7 +1713,7 @@ void App::renderDecryptWalletDialog() {
|
||||
wallet_security_workflow_.finishImport();
|
||||
ui::Notifications::instance().error(
|
||||
err +
|
||||
"\nEncrypted backup: wallet.dat.encrypted.bak",
|
||||
TR("sec_encrypted_backup_suffix"),
|
||||
12.0f);
|
||||
};
|
||||
});
|
||||
@@ -1740,7 +1739,7 @@ void App::renderDecryptWalletDialog() {
|
||||
refreshPeerInfo();
|
||||
|
||||
ui::Notifications::instance().success(
|
||||
"Wallet decrypted successfully! All keys imported.",
|
||||
TR("sec_wallet_decrypted_all_keys_imported"),
|
||||
8.0f);
|
||||
DEBUG_LOGF("[App] Wallet decrypted successfully\n");
|
||||
};
|
||||
@@ -1862,7 +1861,7 @@ void App::renderDecryptWalletDialog() {
|
||||
int tMins = (int)(totalElapsed / 60);
|
||||
int tSecs = (int)(totalElapsed % 60);
|
||||
ImGui::Spacing();
|
||||
ImGui::TextDisabled("Total elapsed: %dm %02ds", tMins, tSecs);
|
||||
ImGui::TextDisabled(TR("sec_total_elapsed_fmt"), tMins, tSecs);
|
||||
}
|
||||
|
||||
// ---- Phase 2: Success ----
|
||||
@@ -1965,7 +1964,7 @@ void App::renderPinDialogs() {
|
||||
ImGui::BeginDisabled(!valid || pin_in_progress_);
|
||||
if (ui::material::TactileButton(TR("settings_set_pin"), ImVec2(btnW, 40))) {
|
||||
pin_in_progress_ = true;
|
||||
pin_status_ = "Verifying passphrase...";
|
||||
pin_status_ = TR("sec_verifying_passphrase");
|
||||
|
||||
// Verify passphrase + store vault on worker thread to avoid
|
||||
// blocking the UI with Argon2id key derivation.
|
||||
@@ -1985,7 +1984,7 @@ void App::renderPinDialogs() {
|
||||
if (!passphrase.empty()) util::SecureVault::secureZero(&passphrase[0], passphrase.size());
|
||||
if (!pin.empty()) util::SecureVault::secureZero(&pin[0], pin.size());
|
||||
return [this]() {
|
||||
pin_status_ = "Incorrect passphrase";
|
||||
pin_status_ = TR("sec_incorrect_passphrase_pin_setup");
|
||||
pin_in_progress_ = false;
|
||||
};
|
||||
}
|
||||
@@ -2009,15 +2008,15 @@ void App::renderPinDialogs() {
|
||||
pin_status_.clear();
|
||||
pin_in_progress_ = false;
|
||||
show_pin_setup_ = false;
|
||||
ui::Notifications::instance().info("PIN set successfully");
|
||||
ui::Notifications::instance().info(TR("sec_pin_set_successfully"));
|
||||
} else {
|
||||
pin_status_ = "Failed to create vault";
|
||||
pin_status_ = TR("sec_failed_to_create_vault");
|
||||
pin_in_progress_ = false;
|
||||
}
|
||||
};
|
||||
});
|
||||
} else {
|
||||
pin_status_ = "Not connected to daemon";
|
||||
pin_status_ = TR("sec_not_connected_to_daemon_pin");
|
||||
pin_in_progress_ = false;
|
||||
}
|
||||
}
|
||||
@@ -2082,7 +2081,7 @@ void App::renderPinDialogs() {
|
||||
ImGui::BeginDisabled(!valid || pin_in_progress_);
|
||||
if (ui::material::TactileButton(TR("settings_change_pin"), ImVec2(-1, 40))) {
|
||||
pin_in_progress_ = true;
|
||||
pin_status_ = "Changing PIN...";
|
||||
pin_status_ = TR("sec_changing_pin");
|
||||
std::string oldPin(pin_old_buf_);
|
||||
std::string newPinCopy = newPin;
|
||||
memset(pin_old_buf_, 0, sizeof(pin_old_buf_));
|
||||
@@ -2098,15 +2097,15 @@ void App::renderPinDialogs() {
|
||||
pin_status_.clear();
|
||||
pin_in_progress_ = false;
|
||||
show_pin_change_ = false;
|
||||
ui::Notifications::instance().info("PIN changed successfully");
|
||||
ui::Notifications::instance().info(TR("sec_pin_changed_successfully"));
|
||||
} else {
|
||||
pin_status_ = "Incorrect current PIN";
|
||||
pin_status_ = TR("sec_incorrect_current_pin");
|
||||
pin_in_progress_ = false;
|
||||
}
|
||||
};
|
||||
});
|
||||
} else {
|
||||
pin_status_ = "Internal error";
|
||||
pin_status_ = TR("sec_internal_error_change_pin");
|
||||
pin_in_progress_ = false;
|
||||
}
|
||||
}
|
||||
@@ -2147,7 +2146,7 @@ void App::renderPinDialogs() {
|
||||
ImGui::BeginDisabled(!valid || pin_in_progress_);
|
||||
if (ui::material::TactileButton(TR("settings_remove_pin"), ImVec2(-1, 40))) {
|
||||
pin_in_progress_ = true;
|
||||
pin_status_ = "Verifying PIN...";
|
||||
pin_status_ = TR("sec_verifying_pin");
|
||||
std::string oldPin(pin_old_buf_);
|
||||
memset(pin_old_buf_, 0, sizeof(pin_old_buf_));
|
||||
|
||||
@@ -2167,15 +2166,15 @@ void App::renderPinDialogs() {
|
||||
pin_status_.clear();
|
||||
pin_in_progress_ = false;
|
||||
show_pin_remove_ = false;
|
||||
ui::Notifications::instance().info("PIN removed");
|
||||
ui::Notifications::instance().info(TR("sec_pin_removed"));
|
||||
} else {
|
||||
pin_status_ = "Incorrect PIN";
|
||||
pin_status_ = TR("sec_incorrect_pin_remove");
|
||||
pin_in_progress_ = false;
|
||||
}
|
||||
};
|
||||
});
|
||||
} else {
|
||||
pin_status_ = "Internal error";
|
||||
pin_status_ = TR("sec_internal_error_remove_pin");
|
||||
pin_in_progress_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user