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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user