feat(send): migrate the Shield/Merge modal to the reference design

Presentation-only migration of ShieldDialog::render (Shield coinbase / Merge funds)
— a fund-moving modal:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth win.width,
  idSuffix shielddialog}) replacing the legacy positional overload
- drop the description divider and the form->footer divider (keep the post-submit
  operation-id section divider)
- 3 StyledButton -> TactileButton (submit / cancel / check-status)
- TR() the hardcoded "No shielded (z) address yet …" note -> new shield_no_zaddr_hint
  key, 8 langs (em-dash preserved); the "DRGX" ticker stays hardcoded per convention

The fund path is byte-for-byte unchanged: the can_submit guard, fee/utxo clamps, the
"ANY_TADDR" setup, the z_shieldcoinbase / z_mergetoaddress worker->post calls + their
opid/status/Notifications/trackOperation callbacks, and the BeginDisabled/tooltip logic.

Verified: build + ctest + hygiene clean; a 2-lens adversarial review
(fund-path-untouched / presentation-i18n) returned zero findings, proving the fund
path diffs byte-for-byte identical to HEAD via isolation diffs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 18:03:54 -05:00
parent dae677acd8
commit ca65aa8bf7
10 changed files with 20 additions and 11 deletions

View File

@@ -79,18 +79,20 @@ void ShieldDialog::render(App* app)
? TR("shield_title")
: TR("merge_title");
if (material::BeginOverlayDialog(title, &s_open, win.width, 0.94f)) {
material::OverlayDialogSpec ov;
ov.title = title; ov.p_open = &s_open;
ov.style = material::OverlayStyle::BlurFloat;
ov.cardWidth = win.width; ov.idSuffix = "shielddialog";
if (material::BeginOverlayDialog(ov)) {
const auto& state = app->getWalletState();
// Description
if (s_mode == Mode::ShieldCoinbase) {
ImGui::TextWrapped("%s", TR("shield_description"));
} else {
ImGui::TextWrapped("%s", TR("merge_description"));
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// From address (for shield coinbase)
@@ -131,7 +133,7 @@ void ShieldDialog::render(App* app)
}
if (state.z_addresses.empty()) {
material::Type().textColored(material::TypeStyle::Caption, material::Warning(),
"No shielded (z) address yet — create one on the Receive tab first.");
TR("shield_no_zaddr_hint"));
}
ImGui::Spacing();
@@ -156,8 +158,6 @@ void ShieldDialog::render(App* app)
if (s_utxo_limit < 1) s_utxo_limit = 1;
if (s_utxo_limit > 100) s_utxo_limit = 100;
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Status message
@@ -179,7 +179,7 @@ void ShieldDialog::render(App* app)
if (!can_submit) ImGui::BeginDisabled();
const char* btn_label = (s_mode == Mode::ShieldCoinbase) ? TR("shield_funds") : TR("merge_funds");
if (material::StyledButton(btn_label, ImVec2(shieldBtn.width, 0), S.resolveFont(shieldBtn.font))) {
if (material::TactileButton(btn_label, ImVec2(shieldBtn.width, 0), S.resolveFont(shieldBtn.font))) {
s_operation_pending = true;
s_status_message = TR("shield_submitting");
@@ -259,7 +259,7 @@ void ShieldDialog::render(App* app)
ImGui::SameLine();
if (material::StyledButton(TR("cancel"), ImVec2(cancelBtn.width, 0), S.resolveFont(cancelBtn.font))) {
if (material::TactileButton(TR("cancel"), ImVec2(cancelBtn.width, 0), S.resolveFont(cancelBtn.font))) {
s_open = false;
}
@@ -271,7 +271,7 @@ void ShieldDialog::render(App* app)
ImGui::Text(TR("shield_operation_id"), s_operation_id.c_str());
if (material::StyledButton(TR("shield_check_status"), ImVec2(0,0), S.resolveFont(shieldBtn.font))) {
if (material::TactileButton(TR("shield_check_status"), ImVec2(0,0), S.resolveFont(shieldBtn.font))) {
std::string opid = s_operation_id;
if (app->worker()) {
app->worker()->post([rpc = app->rpc(), opid]() -> rpc::RPCWorker::MainCb {