diff --git a/src/ui/windows/daemon_download_dialog.h b/src/ui/windows/daemon_download_dialog.h index 457a7b9..44b3387 100644 --- a/src/ui/windows/daemon_download_dialog.h +++ b/src/ui/windows/daemon_download_dialog.h @@ -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: diff --git a/src/ui/windows/send_tab.cpp b/src/ui/windows/send_tab.cpp index 9cdb322..7e96ce7 100644 --- a/src/ui/windows/send_tab.cpp +++ b/src/ui/windows/send_tab.cpp @@ -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 diff --git a/src/ui/windows/shield_dialog.cpp b/src/ui/windows/shield_dialog.cpp index 6a867d7..8eaba7b 100644 --- a/src/ui/windows/shield_dialog.cpp +++ b/src/ui/windows/shield_dialog.cpp @@ -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 diff --git a/src/ui/windows/xmrig_download_dialog.h b/src/ui/windows/xmrig_download_dialog.h index c31581b..533073a 100644 --- a/src/ui/windows/xmrig_download_dialog.h +++ b/src/ui/windows/xmrig_download_dialog.h @@ -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: