feat(settings): migrate Seed phrase backup modal to the reference design
Phase 2 (second security modal), presentation-only + the review's one nit. Struct-form BlurFloat overlay (was the legacy positional overload); the phrase state's plain red warning -> material::DialogWarningHeader; StyledButtons -> TactileButtons with the Copy/Save/Close row and shared Close centered via PlaceOverlayDialogActions (the existing helper) and their separators dropped; success status now wraps. Already fully i18n'd. UNCHANGED (verified by the adversarial review): the closeAndWipe sodium_memzero of the 24-word phrase on every close path (Begin-false early return, outside-click/Esc teardown, phrase Close, shared Close), the async exportSeedPhrase fetch + its show_seed_backup_ callback guard, the atomic restricted-perms file-write + sodium_memzero of its content buffer, and copySecretToClipboard / RenderSeedWordGrid. No seed leak; Begin/EndOverlayDialog balanced on every return path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
32
src/app.cpp
32
src/app.cpp
@@ -3430,7 +3430,13 @@ void App::renderSeedBackupDialog()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ui::material::BeginOverlayDialog(TR("seed_backup_title"), &show_seed_backup_, 540.0f, 0.94f)) {
|
ui::material::OverlayDialogSpec ov;
|
||||||
|
ov.title = TR("seed_backup_title");
|
||||||
|
ov.p_open = &show_seed_backup_;
|
||||||
|
ov.style = ui::material::OverlayStyle::BlurFloat;
|
||||||
|
ov.cardWidth = 560.0f;
|
||||||
|
ov.idSuffix = "seedbackup";
|
||||||
|
if (!ui::material::BeginOverlayDialog(ov)) {
|
||||||
if (seed_backup_fetch_started_) closeAndWipe();
|
if (seed_backup_fetch_started_) closeAndWipe();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3453,24 +3459,26 @@ void App::renderSeedBackupDialog()
|
|||||||
} else if (seed_backup_no_mnemonic_) {
|
} else if (seed_backup_no_mnemonic_) {
|
||||||
ImGui::TextWrapped("%s", TR("seed_backup_none"));
|
ImGui::TextWrapped("%s", TR("seed_backup_none"));
|
||||||
} else if (!seed_backup_phrase_.empty()) {
|
} else if (!seed_backup_phrase_.empty()) {
|
||||||
ImGui::TextWrapped("%s", TR("seed_backup_intro"));
|
ui::material::DialogWarningHeader(TR("seed_backup_warning"));
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
|
ImGui::TextWrapped("%s", TR("seed_backup_intro"));
|
||||||
ImGui::TextWrapped("%s", TR("seed_backup_warning"));
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ui::RenderSeedWordGrid(ui::SplitSeedWords(seed_backup_phrase_));
|
ui::RenderSeedWordGrid(ui::SplitSeedWords(seed_backup_phrase_));
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
if (!seed_backup_status_.empty()) {
|
if (!seed_backup_status_.empty()) {
|
||||||
|
ImGui::PushTextWrapPos(0.0f);
|
||||||
ImGui::TextColored(ui::material::SuccessVec4(), "%s", seed_backup_status_.c_str());
|
ImGui::TextColored(ui::material::SuccessVec4(), "%s", seed_backup_status_.c_str());
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
// Footer: Copy | Save | Close — centered as a group (no divider).
|
||||||
if (ui::material::StyledButton(TR("seed_backup_copy"), ImVec2(120 * dp, 0))) {
|
const float cW = 120 * dp, sW = 150 * dp;
|
||||||
|
ui::material::PlaceOverlayDialogActions(cW + sW + cW + ImGui::GetStyle().ItemSpacing.x * 2.0f);
|
||||||
|
if (ui::material::TactileButton(TR("seed_backup_copy"), ImVec2(cW, 0))) {
|
||||||
copySecretToClipboard(seed_backup_phrase_);
|
copySecretToClipboard(seed_backup_phrase_);
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ui::material::StyledButton(TR("seed_backup_save"), ImVec2(150 * dp, 0))) {
|
if (ui::material::TactileButton(TR("seed_backup_save"), ImVec2(sW, 0))) {
|
||||||
std::string path = util::Platform::getConfigDir() + "/dragonx-seed-backup.txt";
|
std::string path = util::Platform::getConfigDir() + "/dragonx-seed-backup.txt";
|
||||||
std::string content = seed_backup_phrase_ + "\n";
|
std::string content = seed_backup_phrase_ + "\n";
|
||||||
const bool ok = util::Platform::writeFileAtomically(path, content,
|
const bool ok = util::Platform::writeFileAtomically(path, content,
|
||||||
@@ -3480,7 +3488,7 @@ void App::renderSeedBackupDialog()
|
|||||||
: std::string(TR("seed_backup_save_failed"))) + path;
|
: std::string(TR("seed_backup_save_failed"))) + path;
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ui::material::StyledButton(TR("seed_backup_close"), ImVec2(120 * dp, 0))) {
|
if (ui::material::TactileButton(TR("seed_backup_close"), ImVec2(cW, 0))) {
|
||||||
closeAndWipe();
|
closeAndWipe();
|
||||||
ui::material::EndOverlayDialog();
|
ui::material::EndOverlayDialog();
|
||||||
return;
|
return;
|
||||||
@@ -3492,8 +3500,10 @@ void App::renderSeedBackupDialog()
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::Separator();
|
// Centered Close (no divider).
|
||||||
if (ui::material::StyledButton(TR("seed_backup_close"), ImVec2(120 * dp, 0))) {
|
const float cbW = 120 * dp;
|
||||||
|
ui::material::PlaceOverlayDialogActions(cbW);
|
||||||
|
if (ui::material::TactileButton(TR("seed_backup_close"), ImVec2(cbW, 0))) {
|
||||||
closeAndWipe();
|
closeAndWipe();
|
||||||
}
|
}
|
||||||
ui::material::EndOverlayDialog();
|
ui::material::EndOverlayDialog();
|
||||||
|
|||||||
Reference in New Issue
Block a user