Compare commits
2
Commits
8b46ead72a
...
8d8e523a26
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d8e523a26 | ||
|
|
8b07f776e5 |
@@ -48,6 +48,36 @@ for f in "${files[@]}"; do
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Content guards (UX-hygiene threads) ──────────────────────────────────────
|
||||
# Low-false-positive regression checks for debt the UX audit cleared, so it can't
|
||||
# silently regrow. Each is deliberately narrow; widen only with an allowlist.
|
||||
for f in "${files[@]}"; do
|
||||
[[ "$f" == *.cpp ]] || continue
|
||||
[[ -f "$f" ]] || continue
|
||||
# app_sweep.cpp is the dev screenshot-sweep harness: its fixture strings are never shipped UI.
|
||||
[[ "$(basename "$f")" == "app_sweep.cpp" ]] && continue
|
||||
|
||||
# (1) Tactile button sized with a raw multi-digit pixel literal that ignores HiDPI.
|
||||
# Real sizes multiply by ui::Layout::dpiScale() / dp / hScale, or come from schema.
|
||||
if hits=$(grep -nE 'Tactile(Small)?Button\([^;]*ImVec2\([1-9][0-9]+(\.[0-9]+f?)?,' "$f" \
|
||||
| grep -vE 'dpiScale|\* *dp\b|\* *hdpi|hScale|schema|Scale\('); then
|
||||
while IFS= read -r line; do
|
||||
echo "✗ unscaled button size (multiply the px by ui::Layout::dpiScale()): $f:$line" >&2
|
||||
fail=1
|
||||
done <<< "$hits"
|
||||
fi
|
||||
|
||||
# (2) User-visible migrate/sweep status assigned a bare English literal instead of TR(...).
|
||||
# "__SENTINEL__" comparisons and empty strings are fine.
|
||||
if hits=$(grep -nE '(sweep_status_|seed_migration_status_)[[:space:]]*=[[:space:]]*"[A-Za-z]' "$f" \
|
||||
| grep -vE 'TR\(|"__'); then
|
||||
while IFS= read -r line; do
|
||||
echo "✗ untranslated status string (route through TR(...)): $f:$line" >&2
|
||||
fail=1
|
||||
done <<< "$hits"
|
||||
fi
|
||||
done
|
||||
|
||||
if (( fail )); then
|
||||
echo "" >&2
|
||||
echo "Source hygiene check failed. See docs in scripts/check-source-hygiene.sh." >&2
|
||||
|
||||
+7
-5
@@ -3590,7 +3590,7 @@ void App::renderLiteFirstRunPrompt()
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("appx_copy"), ImVec2(80 * dp, 0))) copySecretToClipboard(seed);
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(skipConfirm ? TR("appx_skip_anyway") : TR("appx_skip"), ImVec2(120, 0))) {
|
||||
if (ui::material::TactileButton(skipConfirm ? TR("appx_skip_anyway") : TR("appx_skip"), ImVec2(120.0f * ui::Layout::dpiScale(), 0))) {
|
||||
if (!skipConfirm) {
|
||||
skipConfirm = true; // require a second, deliberate click
|
||||
} else {
|
||||
@@ -3653,9 +3653,9 @@ void App::renderLiteFirstRunPrompt()
|
||||
}
|
||||
if (!verified) ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("appx_back"), ImVec2(80, 0))) { skipConfirm = false; step = 1; }
|
||||
if (ui::material::TactileButton(TR("appx_back"), ImVec2(80.0f * ui::Layout::dpiScale(), 0))) { skipConfirm = false; step = 1; }
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(skipConfirm ? TR("appx_skip_anyway") : TR("appx_skip"), ImVec2(120, 0))) {
|
||||
if (ui::material::TactileButton(skipConfirm ? TR("appx_skip_anyway") : TR("appx_skip"), ImVec2(120.0f * ui::Layout::dpiScale(), 0))) {
|
||||
if (!skipConfirm) {
|
||||
skipConfirm = true; // require a second, deliberate click
|
||||
} else {
|
||||
@@ -3752,7 +3752,7 @@ void App::renderLiteFirstRunPrompt()
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("appx_back"), ImVec2(80, 0))) {
|
||||
if (ui::material::TactileButton(TR("appx_back"), ImVec2(80.0f * ui::Layout::dpiScale(), 0))) {
|
||||
sodium_memzero(restoreSeed, sizeof(restoreSeed));
|
||||
restoreErr.clear();
|
||||
step = 0;
|
||||
@@ -4324,8 +4324,10 @@ void App::renderExportKeyDialog()
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::OnSurfaceMedium()), "%s", TR("key_export_private_key"));
|
||||
ImGui::InputTextMultiline("##exportresult", export_result_, sizeof(export_result_),
|
||||
ImVec2(-1, 60.0f * dp), ImGuiInputTextFlags_ReadOnly);
|
||||
if (m::TactileButton(TR("copy")))
|
||||
if (m::TactileButton(TR("copy"))) {
|
||||
ImGui::SetClipboardText(export_result_);
|
||||
ui::Notifications::instance().info(TR("copied_to_clipboard"));
|
||||
}
|
||||
} else if (export_error_) {
|
||||
ImGui::Spacing();
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
|
||||
+16
-16
@@ -5635,7 +5635,7 @@ void App::sweepPrivateKey(const std::string& rawKey, int startHeight, int destMo
|
||||
const std::string& destExisting)
|
||||
{
|
||||
if (!state_.connected || !rpc_ || !worker_) {
|
||||
sweep_status_ = "Not connected to the daemon.";
|
||||
sweep_status_ = TR("err_not_connected_daemon");
|
||||
sweep_step_ = SweepStep::Error;
|
||||
return;
|
||||
}
|
||||
@@ -5647,7 +5647,7 @@ void App::sweepPrivateKey(const std::string& rawKey, int startHeight, int destMo
|
||||
// Sweeping requires a SPENDING key (transparent WIF or shielded z-spending key) — a viewing key
|
||||
// can't sign, and this must never accidentally route to z_importviewingkey.
|
||||
if (!services::WalletSecurityController::isRecognizedPrivateKey(key)) {
|
||||
sweep_status_ = "Enter a spending key (a viewing key can't move funds).";
|
||||
sweep_status_ = TR("sweep_err_need_spending_key");
|
||||
sweep_step_ = SweepStep::Error;
|
||||
return;
|
||||
}
|
||||
@@ -5665,7 +5665,7 @@ void App::sweepPrivateKey(const std::string& rawKey, int startHeight, int destMo
|
||||
}
|
||||
|
||||
sweep_step_ = SweepStep::Running;
|
||||
sweep_status_ = "Importing key & rescanning…";
|
||||
sweep_status_ = TR("sweep_status_importing");
|
||||
sweep_txid_.clear();
|
||||
sweep_dest_shown_.clear();
|
||||
|
||||
@@ -5759,7 +5759,7 @@ void App::sweepPrivateKey(const std::string& rawKey, int startHeight, int destMo
|
||||
} catch (const std::exception& e) {
|
||||
err = e.what();
|
||||
} catch (...) {
|
||||
err = "Sweep failed (unknown error)";
|
||||
err = TR("sweep_err_unknown");
|
||||
}
|
||||
// Scrub the worker's copy of the spending key now that the request has been sent (all paths).
|
||||
if (!key.empty()) sodium_memzero(&key[0], key.size());
|
||||
@@ -5767,16 +5767,16 @@ void App::sweepPrivateKey(const std::string& rawKey, int startHeight, int destMo
|
||||
invalidateAddressValidationCache();
|
||||
refreshAddresses();
|
||||
if (!err.empty()) {
|
||||
if (err == "__NOFUNDS__") sweep_status_ = "This key holds no funds to sweep.";
|
||||
else if (err == "__UNCONFIRMED__") sweep_status_ = "This key's funds are still unconfirmed — try again shortly.";
|
||||
else if (err == "__ALREADY__") sweep_status_ = "This key is already in your wallet — use Send to move its funds.";
|
||||
else if (err == "__DUST__") sweep_status_ = "This key's balance is too small to cover the network fee.";
|
||||
if (err == "__NOFUNDS__") sweep_status_ = TR("sweep_err_nofunds");
|
||||
else if (err == "__UNCONFIRMED__") sweep_status_ = TR("sweep_err_unconfirmed");
|
||||
else if (err == "__ALREADY__") sweep_status_ = TR("sweep_err_already");
|
||||
else if (err == "__DUST__") sweep_status_ = TR("sweep_err_dust");
|
||||
else sweep_status_ = err;
|
||||
sweep_step_ = SweepStep::Error;
|
||||
return;
|
||||
}
|
||||
sweep_dest_shown_ = dest;
|
||||
sweep_status_ = "Sweeping funds to your address…";
|
||||
sweep_status_ = TR("sweep_status_sweeping");
|
||||
// Send all funds from the swept address to the destination through the tested z_sendmany
|
||||
// wrapper (fixed-decimal amount + async-op tracking). z_sendmany needs every input in one
|
||||
// transaction to cover the full balance, so it fails loudly rather than partial-sweeping.
|
||||
@@ -6283,7 +6283,7 @@ void App::showSeedMigrationDialog()
|
||||
pending_send_callbacks_[sweepOpid] = makeSweepCompletionCallback(/*resumed=*/true);
|
||||
trackOperation(sweepOpid);
|
||||
seed_migration_step_ = SeedMigrationStep::Sweeping;
|
||||
seed_migration_status_ = "Checking on the previous sweep…";
|
||||
seed_migration_status_ = TR("migrate_status_checking_sweep");
|
||||
break;
|
||||
case MigrationResume::SweepGate:
|
||||
// No txid, and either no opid or not connected to re-track it (a persisted opid is left in
|
||||
@@ -6361,15 +6361,15 @@ void App::beginSweepToSeedWallet()
|
||||
return;
|
||||
}
|
||||
if (!rpc_ || !worker_ || !state_.connected) {
|
||||
seed_migration_status_ = "Not connected to the daemon.";
|
||||
seed_migration_status_ = TR("err_not_connected_daemon");
|
||||
seed_migration_step_ = SeedMigrationStep::Error; return;
|
||||
}
|
||||
if (seed_migration_dest_.empty()) {
|
||||
seed_migration_status_ = "No destination address for the sweep.";
|
||||
seed_migration_status_ = TR("migrate_err_no_dest");
|
||||
seed_migration_step_ = SeedMigrationStep::Error; return;
|
||||
}
|
||||
seed_migration_step_ = SeedMigrationStep::Sweeping;
|
||||
seed_migration_status_ = "Building the sweep transaction…";
|
||||
seed_migration_status_ = TR("migrate_status_building");
|
||||
// Mark the sweep as submitted BEFORE broadcasting (persisted synchronously). If the app or daemon
|
||||
// then dies after the tx reaches the mempool but before its opid/txid is persisted, a resumed
|
||||
// migration still knows a sweep is in flight and won't mistake the resulting ~0 legacy balance for
|
||||
@@ -6409,7 +6409,7 @@ void App::beginSweepToSeedWallet()
|
||||
}
|
||||
pending_send_callbacks_[opid] = makeSweepCompletionCallback(/*resumed=*/false);
|
||||
trackOperation(opid);
|
||||
seed_migration_status_ = "Waiting for the sweep transaction to be accepted…";
|
||||
seed_migration_status_ = TR("migrate_status_waiting_accept");
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -6568,7 +6568,7 @@ void App::beginAdoptSeedWallet()
|
||||
{
|
||||
if (refuseIfNodeBusy()) return; // stays on its current step; the user retries once the node is free
|
||||
seed_migration_step_ = SeedMigrationStep::Adopting;
|
||||
seed_migration_status_ = "Restarting with your new wallet — this rescan can take a while…";
|
||||
seed_migration_status_ = TR("migrate_status_restarting");
|
||||
{ std::lock_guard<std::mutex> lk(seed_migration_mutex_); seed_migration_adopt_done_ = false; }
|
||||
// Gate the main-loop reconnect so tryConnect can't start the daemon while we swap wallet.dat.
|
||||
daemon_restarting_ = true;
|
||||
@@ -7410,7 +7410,7 @@ bool App::runtimeRescan(int startHeight)
|
||||
rescan_confirmed_active_ = true;
|
||||
user_initiated_rescan_ = true; // user clicked Rescan → completion should toast
|
||||
state_.sync.rescan_progress = 0.0f;
|
||||
state_.sync.rescan_status = "Rescanning from block " + std::to_string(startHeight) + "...";
|
||||
{ char rb[64]; snprintf(rb, sizeof(rb), TR("rescan_status_from_block"), startHeight); state_.sync.rescan_status = rb; }
|
||||
markTransactionsDirty();
|
||||
last_tx_block_height_ = -1;
|
||||
invalidateShieldedHistoryScanProgress(true);
|
||||
|
||||
@@ -2331,8 +2331,10 @@ void RenderSettingsPage(App* app) {
|
||||
cs.hoverBg = material::WithAlpha(OnSurface(), 30);
|
||||
cs.tooltip = TR("copy");
|
||||
const float ih = ImGui::GetFrameHeight();
|
||||
if (material::IconButton("##copydir", ICON_MD_CONTENT_COPY, Type().iconSmall(), ImVec2(ih, ih), cs))
|
||||
if (material::IconButton("##copydir", ICON_MD_CONTENT_COPY, Type().iconSmall(), ImVec2(ih, ih), cs)) {
|
||||
ImGui::SetClipboardText(dirPath.c_str());
|
||||
Notifications::instance().info(TR("copied_to_clipboard"));
|
||||
}
|
||||
}
|
||||
|
||||
// Row 2: Wallet size — label left, value RIGHT-aligned.
|
||||
|
||||
@@ -1738,7 +1738,10 @@ void RenderChatTab(App* app)
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginPopup("##msgmenu")) {
|
||||
if (ImGui::MenuItem(TR("copy"))) ImGui::SetClipboardText(m.body.c_str());
|
||||
if (ImGui::MenuItem(TR("copy"))) {
|
||||
ImGui::SetClipboardText(m.body.c_str());
|
||||
Notifications::instance().success(TR("copied"));
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
} else if (s_msgmenu_index == static_cast<int>(mi)) {
|
||||
s_msgmenu_index = -1; // popup closed
|
||||
|
||||
@@ -776,9 +776,11 @@ void RenderPeersTab(App* app)
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(TR("copy_address"))) {
|
||||
ImGui::SetClipboardText(peer.addr.c_str());
|
||||
ui::Notifications::instance().info(TR("copied_to_clipboard"));
|
||||
}
|
||||
if (ImGui::MenuItem(TR("peers_copy_ip"))) {
|
||||
ImGui::SetClipboardText(ExtractIP(peer.addr).c_str());
|
||||
ui::Notifications::instance().info(TR("copied_to_clipboard"));
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(TR("peers_ban_24h"))) {
|
||||
@@ -892,6 +894,7 @@ void RenderPeersTab(App* app)
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicContextItem(nullptr, 0, acrylicTheme2.menu)) {
|
||||
if (ImGui::MenuItem(TR("copy_address"))) {
|
||||
ImGui::SetClipboardText(banned.address.c_str());
|
||||
ui::Notifications::instance().info(TR("copied_to_clipboard"));
|
||||
}
|
||||
if (ImGui::MenuItem(TR("peers_unban"))) {
|
||||
app->unbanPeer(banned.address);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "transactions_tab.h"
|
||||
#include "../notifications.h"
|
||||
#include "transaction_details_dialog.h"
|
||||
#include "export_transactions_dialog.h"
|
||||
#include "transactions_tab_helpers.h"
|
||||
@@ -905,9 +906,11 @@ void RenderTransactionsTab(App* app)
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicContextItem("TxContext", 0, acrylicTheme.menu)) {
|
||||
if (ImGui::MenuItem(TR("copy_address")) && !tx.address.empty()) {
|
||||
ImGui::SetClipboardText(tx.address.c_str());
|
||||
Notifications::instance().info(TR("copied_to_clipboard"));
|
||||
}
|
||||
if (ImGui::MenuItem(TR("copy_txid"))) {
|
||||
ImGui::SetClipboardText(tx.txid.c_str());
|
||||
Notifications::instance().info(TR("copied_to_clipboard"));
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(TR("view_on_explorer"))) {
|
||||
@@ -950,7 +953,10 @@ void RenderTransactionsTab(App* app)
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("transaction_id"));
|
||||
ImGui::TextWrapped("%s", tx.txid.c_str());
|
||||
if (ImGui::IsItemHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (ImGui::IsItemClicked()) ImGui::SetClipboardText(tx.txid.c_str());
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText(tx.txid.c_str());
|
||||
Notifications::instance().info(TR("copied_to_clipboard"));
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Memo
|
||||
@@ -972,10 +978,12 @@ void RenderTransactionsTab(App* app)
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, schema::UI().drawElement("tabs.transactions", "detail-btn-rounding").size);
|
||||
if (TactileSmallButton(TrId("copy_txid", "detail").c_str(), S.resolveFont("button"))) {
|
||||
ImGui::SetClipboardText(tx.txid.c_str());
|
||||
Notifications::instance().info(TR("copied_to_clipboard"));
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (!tx.address.empty() && TactileSmallButton(TrId("copy_address", "detail").c_str(), S.resolveFont("button"))) {
|
||||
ImGui::SetClipboardText(tx.address.c_str());
|
||||
Notifications::instance().info(TR("copied_to_clipboard"));
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileSmallButton(TrId("explorer", "detail").c_str(), S.resolveFont("button"))) {
|
||||
|
||||
@@ -482,6 +482,21 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["mig_receive_addr"] = "New wallet receive address:";
|
||||
strings_["mig_copy_seed"] = "Copy seed";
|
||||
strings_["mig_backed_up"] = "I've written down my seed phrase";
|
||||
strings_["err_not_connected_daemon"] = "Not connected to the daemon.";
|
||||
strings_["sweep_err_need_spending_key"] = "Enter a spending key (a viewing key can't move funds).";
|
||||
strings_["sweep_status_importing"] = "Importing key & rescanning…";
|
||||
strings_["sweep_err_unknown"] = "Sweep failed (unknown error)";
|
||||
strings_["sweep_err_nofunds"] = "This key holds no funds to sweep.";
|
||||
strings_["sweep_err_unconfirmed"] = "This key's funds are still unconfirmed — try again shortly.";
|
||||
strings_["sweep_err_already"] = "This key is already in your wallet — use Send to move its funds.";
|
||||
strings_["sweep_err_dust"] = "This key's balance is too small to cover the network fee.";
|
||||
strings_["sweep_status_sweeping"] = "Sweeping funds to your address…";
|
||||
strings_["migrate_status_checking_sweep"] = "Checking on the previous sweep…";
|
||||
strings_["migrate_err_no_dest"] = "No destination address for the sweep.";
|
||||
strings_["migrate_status_building"] = "Building the sweep transaction…";
|
||||
strings_["migrate_status_waiting_accept"] = "Waiting for the sweep transaction to be accepted…";
|
||||
strings_["migrate_status_restarting"] = "Restarting with your new wallet — this rescan can take a while…";
|
||||
strings_["rescan_status_from_block"] = "Rescanning from block %d...";
|
||||
strings_["mig_understood"] = "I understand that once I continue, my funds will be moved into this new wallet and can only be recovered with this seed phrase";
|
||||
strings_["mig_ack_required"] = "Confirm both statements above to continue";
|
||||
strings_["mig_step1_done"] = "Step 1 of 2 complete — your funds have NOT moved yet. Sweeping them into this new wallet is the next step.";
|
||||
|
||||
Reference in New Issue
Block a user