feat(settings): redesign the migrate-to-seed modal onto reference design

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) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 13:55:01 -05:00
parent 8290e96ec4
commit d82821ba06

View File

@@ -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;
}