feat(keys): improve the key-export modal — auto-clear copy, inline QR, cleaner actions

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 20:59:06 -05:00
parent 4ee830c5dd
commit 2182c060e6
3 changed files with 61 additions and 6 deletions

View File

@@ -9,6 +9,9 @@
#include "../../util/i18n.h" #include "../../util/i18n.h"
#include "../schema/ui_schema.h" #include "../schema/ui_schema.h"
#include "../material/draw_helpers.h" #include "../material/draw_helpers.h"
#include "../material/type.h"
#include "../material/colors.h"
#include "../widgets/qr_code.h"
#include "imgui.h" #include "imgui.h"
namespace dragonx { namespace dragonx {
@@ -18,16 +21,27 @@ namespace ui {
bool KeyExportDialog::s_open = false; bool KeyExportDialog::s_open = false;
bool KeyExportDialog::s_fetching = false; bool KeyExportDialog::s_fetching = false;
bool KeyExportDialog::s_show_key = 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; KeyExportDialog::KeyType KeyExportDialog::s_key_type = KeyExportDialog::KeyType::Private;
std::string KeyExportDialog::s_address; std::string KeyExportDialog::s_address;
std::string KeyExportDialog::s_key; std::string KeyExportDialog::s_key;
std::string KeyExportDialog::s_error; 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) void KeyExportDialog::show(const std::string& address, KeyType type)
{ {
s_open = true; s_open = true;
s_fetching = false; s_fetching = false;
s_show_key = false; s_show_key = false;
releaseQr();
s_key_type = type; s_key_type = type;
s_address = address; s_address = address;
s_key.clear(); s_key.clear();
@@ -198,18 +212,43 @@ void KeyExportDialog::render(App* app)
ImVec2(-1, keyDisplay.height > 0 ? keyDisplay.height : 80), ImGuiInputTextFlags_ReadOnly); ImVec2(-1, keyDisplay.height > 0 ? keyDisplay.height : 80), ImGuiInputTextFlags_ReadOnly);
} }
// Show/Hide and Copy buttons // Action row: Show/Hide · Copy · QR
ImGui::Spacing(); ImGui::Spacing();
if (material::StyledButton(s_show_key ? TR("hide") : TR("show"), ImVec2(toggleBtn.width, 0), S.resolveFont(toggleBtn.font))) { if (material::StyledButton(s_show_key ? TR("hide") : TR("show"), ImVec2(toggleBtn.width, 0), S.resolveFont(toggleBtn.font))) {
s_show_key = !s_show_key; s_show_key = !s_show_key;
if (!s_show_key) s_show_qr = false; // hiding the key also hides its QR
} }
ImGui::SameLine(); ImGui::SameLine();
if (material::StyledButton(TR("copy_to_clipboard"), ImVec2(copyBtn.width, 0), S.resolveFont(copyBtn.font))) { if (material::StyledButton(TR("copy_to_clipboard"), ImVec2(copyBtn.width, 0), S.resolveFont(copyBtn.font))) {
ImGui::SetClipboardText(s_key.c_str()); // Auto-clearing clipboard: the key (as sensitive as the seed) is wiped after ~45s.
// Could add a notification here 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 // Clear sensitive data
s_key.clear(); s_key.clear();
s_show_key = false; s_show_key = false;
releaseQr();
} }
material::EndOverlayDialog(); 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 } // namespace ui

View File

@@ -4,6 +4,7 @@
#pragma once #pragma once
#include <cstdint>
#include <string> #include <string>
namespace dragonx { namespace dragonx {
@@ -47,10 +48,15 @@ private:
static bool s_open; static bool s_open;
static bool s_fetching; static bool s_fetching;
static bool s_show_key; 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 KeyType s_key_type;
static std::string s_address; static std::string s_address;
static std::string s_key; static std::string s_key;
static std::string s_error; static std::string s_error;
static void releaseQr(); // free the QR texture + reset its state
}; };
} // namespace ui } // namespace ui

View File

@@ -673,6 +673,8 @@ void I18n::loadBuiltinEnglish()
strings_["address_details"] = "Address Details"; strings_["address_details"] = "Address Details";
strings_["view_on_explorer"] = "View on Explorer"; strings_["view_on_explorer"] = "View on Explorer";
strings_["qr_code"] = "QR Code"; strings_["qr_code"] = "QR Code";
strings_["show_qr"] = "Show QR";
strings_["hide_qr"] = "Hide QR";
strings_["request_payment"] = "Request Payment"; strings_["request_payment"] = "Request Payment";
// Transactions Tab // Transactions Tab