feat(overview): migrate the Export-Key modal to the reference design
Presentation-only migration of KeyExportDialog::render (reveals a private or
viewing key) — a secret-handling modal:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth cardW,
idSuffix keyexport}) replacing the legacy positional overload
- replace the bespoke red-bordered "WARNING" box with material::DialogWarningHeader
(user-approved) so the key-export warning matches the import-key reference; passes
the correct per-key-type body key (private vs viewing)
- remove the two dividers (after the warning, above the Close footer)
- 4 StyledButton -> TactileButton (retry / reveal / show-hide / close)
The secret path is byte-for-byte unchanged: the z_exportkey/dumpprivkey/
z_exportviewingkey (and lite exportPrivateKeys) fetch + their secureWipeLiteSecret
calls, the "*"-masking when hidden, the Show/Hide toggle, copySecretToClipboard
(auto-clearing), the QR generate/cache/releaseQr, and the secret-clear on BOTH exit
paths (Close button + the post-EndOverlayDialog dismiss teardown).
Verified: build + ctest + hygiene clean; a 2-lens adversarial review
(secret-hygiene / presentation-fund) returned zero findings, proving every
secret-handling path diffs byte-for-byte identical to HEAD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,7 +66,6 @@ void KeyExportDialog::render(App* app)
|
||||
TR("export_private_key") : TR("export_viewing_key");
|
||||
|
||||
auto& S = schema::UI();
|
||||
auto warningBox = S.drawElement("dialogs.key-export", "warning-box");
|
||||
auto addrInput = S.input("dialogs.key-export", "address-input");
|
||||
auto revealBtn = S.button("dialogs.key-export", "reveal-button");
|
||||
auto keyDisplay = S.drawElement("dialogs.key-export", "key-display");
|
||||
@@ -79,54 +78,14 @@ void KeyExportDialog::render(App* app)
|
||||
// Modal scales to 85% of the window width. BeginOverlayDialog multiplies the width by
|
||||
// Layout::dpiScale(); divide it out here so the final card is exactly 85% at any font scale.
|
||||
const float cardW = (0.85f * ImGui::GetMainViewport()->Size.x) / Layout::dpiScale();
|
||||
if (material::BeginOverlayDialog(title, &s_open, cardW, 0.94f)) {
|
||||
ImGui::Spacing();
|
||||
material::OverlayDialogSpec ov;
|
||||
ov.title = title; ov.p_open = &s_open;
|
||||
ov.style = material::OverlayStyle::BlurFloat;
|
||||
ov.cardWidth = cardW; ov.idSuffix = "keyexport";
|
||||
if (material::BeginOverlayDialog(ov)) {
|
||||
material::DialogWarningHeader((s_key_type == KeyType::Private)
|
||||
? TR("key_export_private_warning") : TR("key_export_viewing_warning"));
|
||||
|
||||
// Warning section with colored background
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.6f, 0.2f, 0.2f, 0.3f));
|
||||
// Auto-size the warning box to its (wrapping, font-scaled) text so it never clips it.
|
||||
(void)warningBox;
|
||||
ImGui::BeginChild("WarningBox", ImVec2(-1, 0),
|
||||
ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders);
|
||||
|
||||
// Heading — warning icon (drawn with the icon font, not the text font, so it isn't tofu) +
|
||||
// larger h6 text, centered, for emphasis.
|
||||
{
|
||||
ImFont* iconF = material::Type().iconMed();
|
||||
ImFont* textF = material::Type().h6();
|
||||
const char* icon = ICON_MD_WARNING;
|
||||
const char* txt = TR("warning_upper");
|
||||
ImGui::PushFont(iconF); const float iw = ImGui::CalcTextSize(icon).x; ImGui::PopFont();
|
||||
ImGui::PushFont(textF); const float tw = ImGui::CalcTextSize(txt).x; ImGui::PopFont();
|
||||
const float sp = ImGui::GetStyle().ItemSpacing.x;
|
||||
const float wa = ImGui::GetContentRegionAvail().x;
|
||||
const float total = iw + sp + tw;
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (wa > total ? (wa - total) * 0.5f : 0.0f));
|
||||
const ImVec4 red(1.0f, 0.42f, 0.42f, 1.0f);
|
||||
ImGui::PushFont(iconF); ImGui::TextColored(red, "%s", icon); ImGui::PopFont();
|
||||
ImGui::SameLine(0, sp);
|
||||
ImGui::PushFont(textF); ImGui::TextColored(red, "%s", txt); ImGui::PopFont();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
|
||||
// Body — centered wrapped text.
|
||||
{
|
||||
const char* body = (s_key_type == KeyType::Private)
|
||||
? TR("key_export_private_warning") : TR("key_export_viewing_warning");
|
||||
ImDrawList* wdl = ImGui::GetWindowDrawList();
|
||||
ImFont* wf = ImGui::GetFont();
|
||||
const float wfs = ImGui::GetFontSize();
|
||||
const float wa = ImGui::GetContentRegionAvail().x;
|
||||
const ImVec2 wp = ImGui::GetCursorScreenPos();
|
||||
const float bh = widgets::DrawCenteredWrapped(wdl, wf, wfs, wp, wa, material::OnSurface(), body);
|
||||
ImGui::Dummy(ImVec2(wa, bh));
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Address — click-to-copy field (chunked, width-clamped, centered).
|
||||
@@ -145,7 +104,7 @@ void KeyExportDialog::render(App* app)
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), TR("error_format"), s_error.c_str());
|
||||
// Not a dead-end: clearing the error falls back through to the Reveal button next frame.
|
||||
ImGui::Spacing();
|
||||
if (material::StyledButton(TR("retry"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
|
||||
if (material::TactileButton(TR("retry"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
|
||||
s_error.clear();
|
||||
}
|
||||
} else if (s_key.empty() && s_address.length() < 26) {
|
||||
@@ -154,7 +113,7 @@ void KeyExportDialog::render(App* app)
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s", TR("invalid_address"));
|
||||
} else if (s_key.empty()) {
|
||||
// Show button to fetch key
|
||||
if (material::StyledButton(TR("key_export_reveal"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
|
||||
if (material::TactileButton(TR("key_export_reveal"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
|
||||
s_fetching = true;
|
||||
|
||||
// Check if z-address or t-address
|
||||
@@ -292,7 +251,7 @@ void KeyExportDialog::render(App* app)
|
||||
app->copySecretToClipboard(s_key); // auto-clearing clipboard
|
||||
}
|
||||
ImGui::Spacing();
|
||||
if (material::StyledButton(s_show_key ? TR("hide") : TR("show"), ImVec2(0, 0),
|
||||
if (material::TactileButton(s_show_key ? TR("hide") : TR("show"), ImVec2(0, 0),
|
||||
material::Type().subtitle1())) {
|
||||
s_show_key = !s_show_key;
|
||||
}
|
||||
@@ -328,8 +287,6 @@ void KeyExportDialog::render(App* app)
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Close button
|
||||
@@ -337,7 +294,7 @@ void KeyExportDialog::render(App* app)
|
||||
float avail_width = ImGui::GetContentRegionAvail().x;
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (avail_width - button_width) / 2.0f);
|
||||
|
||||
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||
if (material::TactileButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||
s_open = false;
|
||||
// Clear sensitive data
|
||||
s_key.clear();
|
||||
|
||||
Reference in New Issue
Block a user