From d82821ba06024aaba995f1242eb348704ca484e2 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 14 Jul 2026 13:55:01 -0500 Subject: [PATCH] feat(settings): redesign the migrate-to-seed modal onto reference design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate App::renderSeedMigrationDialog onto the settings-modal reference design. This is the most fund-critical dialog in the app (create isolated seed wallet -> z_mergetoaddress sweep -> confirm -> adopt/rescan), already fully i18n'd and dp-scaled, so the change is purely the presentation shell: - struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth 580, idSuffix "migrate"}) replacing the legacy positional overload - all 26 StyledButton -> TactileButton (identical signature; height-0 auto-sizing) - remove all 13 footer Separator dividers - ShowSeed seed-phrase warning -> material::DialogWarningHeader (red text -> amber ⚠ header, matching the export-key reference dialog) The fund pyramid is byte-for-byte unchanged: the busy/dismiss veto, the wipeSeed (sodium_memzero) + close lambdas and the post-EndOverlayDialog seed scrub, all seed_migration_step_ transitions, begin CreateSeedWallet/SweepToSeedWallet/ AdoptSeedWallet, refreshSeedMigrationBalance, pollSweepStatus, the snprintf balance/confs/remaining formatting, both confirmation checkboxes and their gates, RenderSeedWordGrid, copySecretToClipboard, writeFileAtomically, and the Discard remove_all + settings cleanup. Verified: build + ctest + hygiene clean; Spacing() count unchanged (28->28, proving no collateral deletion); StyledButtons elsewhere in app.cpp untouched (the swap was range-bounded to this function); a 3-lens adversarial review (fund-path-untouched / secret-hygiene / widget-visual) returned zero findings, with the fund-path lens proving the logic diffs byte-for-byte identical to HEAD. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app.cpp | 83 ++++++++++++++++++++++++++--------------------------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index ca50401..ead6d9c 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -3524,7 +3524,11 @@ void App::renderSeedMigrationDialog() || seed_migration_step_ == SeedMigrationStep::Sweeping || seed_migration_step_ == SeedMigrationStep::Adopting; - if (!ui::material::BeginOverlayDialog(TR("mig_title"), &show_seed_migration_, 580.0f, 0.94f)) { + ui::material::OverlayDialogSpec migOv; + migOv.title = TR("mig_title"); migOv.p_open = &show_seed_migration_; + migOv.style = ui::material::OverlayStyle::BlurFloat; + migOv.cardWidth = 580.0f; migOv.idSuffix = "migrate"; + if (!ui::material::BeginOverlayDialog(migOv)) { if (!busy) wipeSeed(); return; } @@ -3544,14 +3548,14 @@ void App::renderSeedMigrationDialog() // probe found so we never offer a pointless (already-seeded) or impossible (old daemon) run. if (!isConnected()) { ImGui::TextWrapped("%s", TR("mig_not_connected")); - ImGui::Spacing(); ImGui::Separator(); - if (ui::material::StyledButton(TR("close"), ImVec2(120 * dp, 0))) close(); + ImGui::Spacing(); + if (ui::material::TactileButton(TR("close"), ImVec2(120 * dp, 0))) close(); break; } if (state_.isLocked()) { ImGui::TextWrapped("%s", TR("mig_unlock_to_migrate")); - ImGui::Spacing(); ImGui::Separator(); - if (ui::material::StyledButton(TR("close"), ImVec2(120 * dp, 0))) close(); + ImGui::Spacing(); + if (ui::material::TactileButton(TR("close"), ImVec2(120 * dp, 0))) close(); break; } if (!seed_migration_precheck_started_ && !capture_mode_) beginSeedMigrationPrecheck(); @@ -3564,37 +3568,37 @@ void App::renderSeedMigrationDialog() ImGui::PushStyleColor(ImGuiCol_Text, ui::material::SuccessVec4()); ImGui::TextWrapped("%s", TR("mig_already_mnemonic")); ImGui::PopStyleColor(); - ImGui::Spacing(); ImGui::Separator(); - if (ui::material::StyledButton(TR("mig_backup_instead"), ImVec2(200 * dp, 0))) { + ImGui::Spacing(); + if (ui::material::TactileButton(TR("mig_backup_instead"), ImVec2(200 * dp, 0))) { close(); showSeedBackupDialog(); } ImGui::SameLine(); - if (ui::material::StyledButton(TR("close"), ImVec2(120 * dp, 0))) close(); + if (ui::material::TactileButton(TR("close"), ImVec2(120 * dp, 0))) close(); break; case SeedMigrationPrecheck::DaemonTooOld: ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextWrapped("%s", TR("mig_daemon_too_old")); ImGui::PopStyleColor(); - ImGui::Spacing(); ImGui::Separator(); - if (ui::material::StyledButton(TR("close"), ImVec2(120 * dp, 0))) close(); + ImGui::Spacing(); + if (ui::material::TactileButton(TR("close"), ImVec2(120 * dp, 0))) close(); break; case SeedMigrationPrecheck::CheckFailed: ImGui::TextWrapped("%s", TR("mig_check_failed")); - ImGui::Spacing(); ImGui::Separator(); - if (ui::material::StyledButton(TR("mig_recheck"), ImVec2(120 * dp, 0))) + ImGui::Spacing(); + if (ui::material::TactileButton(TR("mig_recheck"), ImVec2(120 * dp, 0))) seed_migration_precheck_started_ = false; // re-probe next frame ImGui::SameLine(); - if (ui::material::StyledButton(TR("close"), ImVec2(120 * dp, 0))) close(); + if (ui::material::TactileButton(TR("close"), ImVec2(120 * dp, 0))) close(); break; case SeedMigrationPrecheck::Legacy: default: ImGui::TextWrapped("%s", TR("mig_intro")); - ImGui::Spacing(); ImGui::Separator(); - if (ui::material::StyledButton(TR("mig_create"), ImVec2(180 * dp, 0))) + ImGui::Spacing(); + if (ui::material::TactileButton(TR("mig_create"), ImVec2(180 * dp, 0))) beginCreateSeedWallet(); ImGui::SameLine(); - if (ui::material::StyledButton(TR("cancel"), ImVec2(120 * dp, 0))) + if (ui::material::TactileButton(TR("cancel"), ImVec2(120 * dp, 0))) close(); break; } @@ -3608,19 +3612,17 @@ void App::renderSeedMigrationDialog() break; case SeedMigrationStep::ShowSeed: { - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); - ImGui::TextWrapped("%s", TR("mig_seed_warning")); - ImGui::PopStyleColor(); + ui::material::DialogWarningHeader(TR("mig_seed_warning")); ImGui::Spacing(); ui::RenderSeedWordGrid(ui::SplitSeedWords(seed_migration_seed_)); ImGui::Spacing(); ImGui::TextColored(kMedium, "%s", TR("mig_receive_addr")); ImGui::TextWrapped("%s", seed_migration_dest_.c_str()); ImGui::Spacing(); - if (ui::material::StyledButton(TR("mig_copy_seed"), ImVec2(120 * dp, 0))) + if (ui::material::TactileButton(TR("mig_copy_seed"), ImVec2(120 * dp, 0))) copySecretToClipboard(seed_migration_seed_); ImGui::SameLine(); - if (ui::material::StyledButton(TR("seed_backup_save"), ImVec2(130 * dp, 0))) { + if (ui::material::TactileButton(TR("seed_backup_save"), ImVec2(130 * dp, 0))) { std::string path = util::Platform::getConfigDir() + "/dragonx-seed-wallet-backup.txt"; std::string content = seed_migration_seed_ + "\nAddress: " + seed_migration_dest_ + "\n"; const bool ok = util::Platform::writeFileAtomically(path, content, /*restrict=*/true); @@ -3636,9 +3638,8 @@ void App::renderSeedMigrationDialog() ImGui::PushStyleColor(ImGuiCol_Text, ui::material::OnSurfaceMedium()); ImGui::TextWrapped("%s", TR("mig_step1_done")); ImGui::PopStyleColor(); - ImGui::Separator(); if (!seed_migration_backed_up_) ImGui::BeginDisabled(); - if (ui::material::StyledButton(TR("mig_continue_sweep"), ImVec2(170 * dp, 0))) { + if (ui::material::TactileButton(TR("mig_continue_sweep"), ImVec2(170 * dp, 0))) { wipeSeed(); // the sweep/adopt steps only use the address, never the seed itself seed_migration_step_ = SeedMigrationStep::Sweep; seed_migration_balance_loaded_ = false; @@ -3647,7 +3648,7 @@ void App::renderSeedMigrationDialog() } if (!seed_migration_backed_up_) ImGui::EndDisabled(); ImGui::SameLine(); - if (ui::material::StyledButton(TR("mig_discard"), ImVec2(160 * dp, 0))) { + if (ui::material::TactileButton(TR("mig_discard"), ImVec2(160 * dp, 0))) { // Throw away the isolated new wallet + clear the pending state. if (!seed_migration_temp_dir_.empty()) { std::error_code ec; @@ -3671,8 +3672,8 @@ void App::renderSeedMigrationDialog() // value, so gate on the loaded flag to avoid flashing "no funds" while it's still fetching). if (!seed_migration_balance_loaded_) { ImGui::TextColored(kMedium, "%s%s", TR("mig_checking_balance"), ui::material::LoadingDots()); - ImGui::Spacing(); ImGui::Separator(); - if (ui::material::StyledButton(TR("mig_later"), ImVec2(100 * dp, 0))) close(); + ImGui::Spacing(); + if (ui::material::TactileButton(TR("mig_later"), ImVec2(100 * dp, 0))) close(); break; } if (seed_migration_balance_ <= DRAGONX_DEFAULT_FEE) { @@ -3685,16 +3686,16 @@ void App::renderSeedMigrationDialog() // Adopt swaps wallet.dat — gate it behind an explicit acknowledgement even though no // funds are at risk (the legacy wallet is still moved aside to a backup on adopt). ImGui::Checkbox(TR("mig_nofunds_confirm"), &seed_migration_nofunds_confirmed_); - ImGui::Spacing(); ImGui::Separator(); + ImGui::Spacing(); if (!seed_migration_nofunds_confirmed_) ImGui::BeginDisabled(); - if (ui::material::StyledButton(TR("mig_adopt_now"), ImVec2(190 * dp, 0))) + if (ui::material::TactileButton(TR("mig_adopt_now"), ImVec2(190 * dp, 0))) beginAdoptSeedWallet(); if (!seed_migration_nofunds_confirmed_) ImGui::EndDisabled(); ImGui::SameLine(); - if (ui::material::StyledButton(TR("refresh"), ImVec2(110 * dp, 0))) + if (ui::material::TactileButton(TR("refresh"), ImVec2(110 * dp, 0))) refreshSeedMigrationBalance(); ImGui::SameLine(); - if (ui::material::StyledButton(TR("mig_later"), ImVec2(100 * dp, 0))) + if (ui::material::TactileButton(TR("mig_later"), ImVec2(100 * dp, 0))) close(); break; } @@ -3710,19 +3711,18 @@ void App::renderSeedMigrationDialog() ImGui::PopStyleColor(); } ImGui::Spacing(); - ImGui::Separator(); { const bool canSweep = !state_.isLocked() && seed_migration_balance_ > 0.0; if (!canSweep) ImGui::BeginDisabled(); - if (ui::material::StyledButton(TR("mig_sweep_all"), ImVec2(170 * dp, 0))) + if (ui::material::TactileButton(TR("mig_sweep_all"), ImVec2(170 * dp, 0))) beginSweepToSeedWallet(); if (!canSweep) ImGui::EndDisabled(); } ImGui::SameLine(); - if (ui::material::StyledButton(TR("refresh"), ImVec2(110 * dp, 0))) + if (ui::material::TactileButton(TR("refresh"), ImVec2(110 * dp, 0))) refreshSeedMigrationBalance(); ImGui::SameLine(); - if (ui::material::StyledButton(TR("mig_later"), ImVec2(100 * dp, 0))) + if (ui::material::TactileButton(TR("mig_later"), ImVec2(100 * dp, 0))) close(); // pending state persists; resumes here next time break; } @@ -3749,24 +3749,23 @@ void App::renderSeedMigrationDialog() ImGui::TextColored(kMedium, "%s", rbuf); } ImGui::Spacing(); - ImGui::Separator(); if (confirmed && legacyEmpty) { - if (ui::material::StyledButton(TR("mig_adopt"), ImVec2(180 * dp, 0))) + if (ui::material::TactileButton(TR("mig_adopt"), ImVec2(180 * dp, 0))) beginAdoptSeedWallet(); } else if (confirmed && !legacyEmpty) { ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextWrapped("%s", TR("mig_remainder")); ImGui::PopStyleColor(); - if (ui::material::StyledButton(TR("mig_sweep_remaining"), ImVec2(180 * dp, 0))) + if (ui::material::TactileButton(TR("mig_sweep_remaining"), ImVec2(180 * dp, 0))) beginSweepToSeedWallet(); } else { ImGui::TextColored(kMedium, "%s", TR("mig_waiting_mine")); } ImGui::SameLine(); - if (ui::material::StyledButton(TR("refresh"), ImVec2(110 * dp, 0))) + if (ui::material::TactileButton(TR("refresh"), ImVec2(110 * dp, 0))) pollSweepStatus(); ImGui::SameLine(); - if (ui::material::StyledButton(TR("mig_later"), ImVec2(100 * dp, 0))) + if (ui::material::TactileButton(TR("mig_later"), ImVec2(100 * dp, 0))) close(); // pending state + txid persist; resumes here next time break; } @@ -3794,8 +3793,7 @@ void App::renderSeedMigrationDialog() ImGui::PopStyleColor(); } ImGui::Spacing(); - ImGui::Separator(); - if (ui::material::StyledButton(TR("mig_done_btn"), ImVec2(120 * dp, 0))) + if (ui::material::TactileButton(TR("mig_done_btn"), ImVec2(120 * dp, 0))) close(); break; @@ -3804,8 +3802,7 @@ void App::renderSeedMigrationDialog() ImGui::TextWrapped("%s", seed_migration_status_.c_str()); ImGui::PopStyleColor(); ImGui::Spacing(); - ImGui::Separator(); - if (ui::material::StyledButton(TR("close"), ImVec2(120 * dp, 0))) + if (ui::material::TactileButton(TR("close"), ImVec2(120 * dp, 0))) close(); break; }