fix: Tier-2 quick mediums — view-only guard, memo counter, updater/close guards
- Send: block Review from a view-only source (no spending key) with a tooltip, instead of failing at submit; label the memo counter as bytes and warn in colour once the field hits its cap (Sapling memos are byte-limited). - Shield: show "No shielded (z) address yet — create one on the Receive tab first" when the destination combo is empty, instead of an empty dropdown. - Updater dialogs (xmrig/daemon): suppress the window X during an active download/verify/extract so closing can't orphan/block on the worker — the in-dialog Cancel is the way to abort. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,12 +62,14 @@ public:
|
||||
}
|
||||
using namespace material;
|
||||
const float dp = Layout::dpiScale();
|
||||
const auto p = s_updater->getProgress();
|
||||
// No window X during an active download/verify/extract — closing would orphan/block on the
|
||||
// worker; the in-dialog Cancel is the intended way to abort.
|
||||
const bool active = (p.state == St::Downloading || p.state == St::Verifying || p.state == St::Extracting);
|
||||
OverlayDialogSpec ov;
|
||||
ov.title = TR("daemon_update_title"); ov.p_open = &s_open;
|
||||
ov.title = TR("daemon_update_title"); ov.p_open = active ? nullptr : &s_open;
|
||||
ov.style = OverlayStyle::BlurFloat; ov.cardWidth = 480.0f; ov.cardBottomViewportRatio = 0.94f;
|
||||
if (BeginOverlayDialog(ov)) {
|
||||
const auto p = s_updater->getProgress();
|
||||
using St = util::DaemonUpdater::State;
|
||||
switch (p.state) {
|
||||
case St::Checking: renderChecking(dp, p); break;
|
||||
case St::UpToDate:
|
||||
|
||||
@@ -889,12 +889,19 @@ static void RenderActionButtons(App* app, float width, float vScale,
|
||||
auto& S = schema::UI();
|
||||
const auto& state = app->getWalletState();
|
||||
double total = s_amount + s_fee;
|
||||
// Block spending from a view-only source (imported viewing key, no spending key) — it would only
|
||||
// fail at submit. Defaults to spendable if the address isn't in the loaded list yet.
|
||||
bool sourceSpendable = true;
|
||||
for (const auto& a : state.addresses) {
|
||||
if (a.address == s_from_address) { sourceSpendable = a.has_spending_key; break; }
|
||||
}
|
||||
bool can_send = app->isConnected() &&
|
||||
!state.sync.syncing &&
|
||||
is_valid_address &&
|
||||
s_amount > 0 &&
|
||||
s_from_address[0] != '\0' &&
|
||||
total <= available &&
|
||||
sourceSpendable &&
|
||||
!s_sending;
|
||||
|
||||
float btnGap = Layout::spacingMd();
|
||||
@@ -934,6 +941,8 @@ static void RenderActionButtons(App* app, float width, float vScale,
|
||||
material::Tooltip("%s", TR("send_tooltip_enter_amount"));
|
||||
else if (total > available)
|
||||
material::Tooltip("%s", TR("send_tooltip_exceeds_balance"));
|
||||
else if (!sourceSpendable)
|
||||
material::Tooltip("%s", "View-only address — no spending key, cannot send");
|
||||
else if (s_sending)
|
||||
material::Tooltip("%s", TR("send_tooltip_in_progress"));
|
||||
}
|
||||
@@ -1439,9 +1448,13 @@ void RenderSendTab(App* app)
|
||||
ImVec2(colW, memoInputH));
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
// The Sapling memo limit is in BYTES (multi-byte UTF-8 fills it faster than visible chars);
|
||||
// warn in colour once the field is at its cap so it's clear input has stopped being accepted.
|
||||
size_t memo_len = strlen(s_memo);
|
||||
snprintf(buf, sizeof(buf), "%zu / %d", memo_len, (int)S.drawElement("business", "memo-max-length").size);
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);
|
||||
size_t memoMax = (size_t)S.drawElement("business", "memo-max-length").size;
|
||||
bool memoAtCap = memo_len + 1 >= memoMax;
|
||||
snprintf(buf, sizeof(buf), "%zu / %zu bytes", memo_len, memoMax);
|
||||
Type().textColored(TypeStyle::Caption, memoAtCap ? Warning() : OnSurfaceDisabled(), buf);
|
||||
}
|
||||
|
||||
// Divider before action buttons
|
||||
|
||||
@@ -129,7 +129,11 @@ void ShieldDialog::render(App* app)
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
if (state.z_addresses.empty()) {
|
||||
material::Type().textColored(material::TypeStyle::Caption, material::Warning(),
|
||||
"No shielded (z) address yet — create one on the Receive tab first.");
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Fee
|
||||
|
||||
@@ -51,12 +51,14 @@ public:
|
||||
}
|
||||
using namespace material;
|
||||
const float dp = Layout::dpiScale();
|
||||
const auto p = s_updater->getProgress();
|
||||
// No window X during an active download/verify/extract — closing would orphan/block on the
|
||||
// worker; the in-dialog Cancel is the intended way to abort.
|
||||
const bool active = (p.state == St::Downloading || p.state == St::Verifying || p.state == St::Extracting);
|
||||
OverlayDialogSpec ov;
|
||||
ov.title = TR("xmrig_update_title"); ov.p_open = &s_open;
|
||||
ov.title = TR("xmrig_update_title"); ov.p_open = active ? nullptr : &s_open;
|
||||
ov.style = OverlayStyle::BlurFloat; ov.cardWidth = 480.0f; ov.cardBottomViewportRatio = 0.94f;
|
||||
if (BeginOverlayDialog(ov)) {
|
||||
const auto p = s_updater->getProgress();
|
||||
using St = util::XmrigUpdater::State;
|
||||
switch (p.state) {
|
||||
case St::Checking: renderChecking(dp, p); break;
|
||||
case St::UpToDate:
|
||||
|
||||
Reference in New Issue
Block a user