From 2182c060e657e1df6a87f5b8d6cd599c975d5239 Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 10 Jun 2026 20:59:06 -0500 Subject: [PATCH] =?UTF-8?q?feat(keys):=20improve=20the=20key-export=20moda?= =?UTF-8?q?l=20=E2=80=94=20auto-clear=20copy,=20inline=20QR,=20cleaner=20a?= =?UTF-8?q?ctions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Auto-clear: the Copy button now routes through App::copySecretToClipboard, so a copied private/viewing key is wiped from the clipboard after ~45s (same protection as the seed) with a "auto-clears" notice — instead of the raw SetClipboardText that left it indefinitely. - QR: once the key is revealed, a Show/Hide QR toggle renders the key's QR inline (via the same GenerateQRTexture/RenderQRCode widget the Receive tab uses) for scanning into another wallet. The QR texture is cached, regenerated on key change, and freed on hide/close/dismiss; hiding the key also hides its QR. - Actions row tightened to Show/Hide · Copy · QR, and the key + QR texture are now cleared on any dismissal (Close button, scrim click, Esc), not just the Close button. Co-Authored-By: Claude Opus 4.8 --- src/ui/windows/key_export_dialog.cpp | 59 +++++++++++++++++++++++++--- src/ui/windows/key_export_dialog.h | 6 +++ src/util/i18n.cpp | 2 + 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/ui/windows/key_export_dialog.cpp b/src/ui/windows/key_export_dialog.cpp index 339c469..af98765 100644 --- a/src/ui/windows/key_export_dialog.cpp +++ b/src/ui/windows/key_export_dialog.cpp @@ -9,6 +9,9 @@ #include "../../util/i18n.h" #include "../schema/ui_schema.h" #include "../material/draw_helpers.h" +#include "../material/type.h" +#include "../material/colors.h" +#include "../widgets/qr_code.h" #include "imgui.h" namespace dragonx { @@ -18,16 +21,27 @@ namespace ui { bool KeyExportDialog::s_open = false; bool KeyExportDialog::s_fetching = false; bool KeyExportDialog::s_show_key = false; +bool KeyExportDialog::s_show_qr = false; +std::uintptr_t KeyExportDialog::s_qr_tex = 0; +std::string KeyExportDialog::s_qr_cached; KeyExportDialog::KeyType KeyExportDialog::s_key_type = KeyExportDialog::KeyType::Private; std::string KeyExportDialog::s_address; std::string KeyExportDialog::s_key; std::string KeyExportDialog::s_error; +void KeyExportDialog::releaseQr() +{ + if (s_qr_tex) { FreeQRTexture(s_qr_tex); s_qr_tex = 0; } + s_qr_cached.clear(); + s_show_qr = false; +} + void KeyExportDialog::show(const std::string& address, KeyType type) { s_open = true; s_fetching = false; s_show_key = false; + releaseQr(); s_key_type = type; s_address = address; s_key.clear(); @@ -198,18 +212,43 @@ void KeyExportDialog::render(App* app) ImVec2(-1, keyDisplay.height > 0 ? keyDisplay.height : 80), ImGuiInputTextFlags_ReadOnly); } - // Show/Hide and Copy buttons + // Action row: Show/Hide · Copy · QR ImGui::Spacing(); - + if (material::StyledButton(s_show_key ? TR("hide") : TR("show"), ImVec2(toggleBtn.width, 0), S.resolveFont(toggleBtn.font))) { s_show_key = !s_show_key; + if (!s_show_key) s_show_qr = false; // hiding the key also hides its QR } - + ImGui::SameLine(); - + if (material::StyledButton(TR("copy_to_clipboard"), ImVec2(copyBtn.width, 0), S.resolveFont(copyBtn.font))) { - ImGui::SetClipboardText(s_key.c_str()); - // Could add a notification here + // Auto-clearing clipboard: the key (as sensitive as the seed) is wiped after ~45s. + app->copySecretToClipboard(s_key); + } + + // QR (only once revealed) — for scanning the key into another wallet. + if (s_show_key) { + ImGui::SameLine(); + if (material::StyledButton(s_show_qr ? TR("hide_qr") : TR("show_qr"), + ImVec2(copyBtn.width, 0), S.resolveFont(copyBtn.font))) { + s_show_qr = !s_show_qr; + } + } + + if (s_show_qr && s_show_key && !s_key.empty()) { + if (s_qr_cached != s_key) { // (re)generate the QR texture when the key changes + if (s_qr_tex) { FreeQRTexture(s_qr_tex); s_qr_tex = 0; } + int qw = 0, qh = 0; + s_qr_tex = GenerateQRTexture(s_key.c_str(), &qw, &qh); + s_qr_cached = s_key; + } + if (s_qr_tex) { + ImGui::Spacing(); + const float qrSize = 180.0f; + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - qrSize) * 0.5f); + RenderQRCode(s_qr_tex, qrSize); + } } } @@ -227,10 +266,18 @@ void KeyExportDialog::render(App* app) // Clear sensitive data s_key.clear(); s_show_key = false; + releaseQr(); } material::EndOverlayDialog(); } + + // Dialog dismissed any other way (scrim click / Esc): drop the key + its QR texture. + if (!s_open) { + if (!s_key.empty()) s_key.clear(); + s_show_key = false; + releaseQr(); + } } } // namespace ui diff --git a/src/ui/windows/key_export_dialog.h b/src/ui/windows/key_export_dialog.h index 8c148f0..0f48748 100644 --- a/src/ui/windows/key_export_dialog.h +++ b/src/ui/windows/key_export_dialog.h @@ -4,6 +4,7 @@ #pragma once +#include #include namespace dragonx { @@ -47,10 +48,15 @@ private: static bool s_open; static bool s_fetching; static bool s_show_key; + static bool s_show_qr; // inline QR of the revealed key (for scanning into another wallet) + static std::uintptr_t s_qr_tex; // cached QR texture handle (0 = none) + static std::string s_qr_cached; // key the cached QR was generated for static KeyType s_key_type; static std::string s_address; static std::string s_key; static std::string s_error; + + static void releaseQr(); // free the QR texture + reset its state }; } // namespace ui diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 7585887..9b81353 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -673,6 +673,8 @@ void I18n::loadBuiltinEnglish() strings_["address_details"] = "Address Details"; strings_["view_on_explorer"] = "View on Explorer"; strings_["qr_code"] = "QR Code"; + strings_["show_qr"] = "Show QR"; + strings_["hide_qr"] = "Hide QR"; strings_["request_payment"] = "Request Payment"; // Transactions Tab