From ca65aa8bf71f6b524d9b59266c18394803944fcf Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 14 Jul 2026 18:03:54 -0500 Subject: [PATCH] feat(send): migrate the Shield/Merge modal to the reference design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- res/lang/de.json | 1 + res/lang/es.json | 1 + res/lang/fr.json | 1 + res/lang/ja.json | 1 + res/lang/ko.json | 1 + res/lang/pt.json | 1 + res/lang/ru.json | 1 + res/lang/zh.json | 1 + src/ui/windows/shield_dialog.cpp | 22 +++++++++++----------- src/util/i18n.cpp | 1 + 10 files changed, 20 insertions(+), 11 deletions(-) diff --git a/res/lang/de.json b/res/lang/de.json index 50a9890..9878903 100644 --- a/res/lang/de.json +++ b/res/lang/de.json @@ -1223,6 +1223,7 @@ "shield_in_progress": "Vorgang läuft...", "shield_max_utxos": "Max. UTXOs pro Vorgang", "shield_merge_done": "Abschirmung/Zusammenführung abgeschlossen!", + "shield_no_zaddr_hint": "Noch keine geschützte (z-)Adresse — erstellen Sie zuerst eine auf der Registerkarte Empfangen.", "shield_op_failed": "Vorgang fehlgeschlagen: ", "shield_op_submitted": "Vorgang übermittelt: ", "shield_operation_id": "Vorgangs-ID: %s", diff --git a/res/lang/es.json b/res/lang/es.json index 686a2cc..fd33837 100644 --- a/res/lang/es.json +++ b/res/lang/es.json @@ -1223,6 +1223,7 @@ "shield_in_progress": "Operación en progreso...", "shield_max_utxos": "UTXOs máximos por operación", "shield_merge_done": "¡Protección/fusión completada!", + "shield_no_zaddr_hint": "Aún no hay dirección blindada (z) — crea una primero en la pestaña Recibir.", "shield_op_failed": "Operación fallida: ", "shield_op_submitted": "Operación enviada: ", "shield_operation_id": "ID de operación: %s", diff --git a/res/lang/fr.json b/res/lang/fr.json index c5bc73b..2915389 100644 --- a/res/lang/fr.json +++ b/res/lang/fr.json @@ -1223,6 +1223,7 @@ "shield_in_progress": "Opération en cours...", "shield_max_utxos": "UTXOs max par opération", "shield_merge_done": "Blindage/fusion terminé !", + "shield_no_zaddr_hint": "Aucune adresse blindée (z) pour l'instant — créez-en une d'abord dans l'onglet Recevoir.", "shield_op_failed": "Échec de l'opération : ", "shield_op_submitted": "Opération soumise : ", "shield_operation_id": "ID d'opération : %s", diff --git a/res/lang/ja.json b/res/lang/ja.json index f92fbff..287d52e 100644 --- a/res/lang/ja.json +++ b/res/lang/ja.json @@ -1223,6 +1223,7 @@ "shield_in_progress": "操作進行中...", "shield_max_utxos": "1回の操作あたりの最大UTXO数", "shield_merge_done": "シールド/統合が完了しました!", + "shield_no_zaddr_hint": "シールドアドレス(z)がまだありません — まず「受取」タブで作成してください。", "shield_op_failed": "操作に失敗しました: ", "shield_op_submitted": "操作を送信しました: ", "shield_operation_id": "オペレーション ID:%s", diff --git a/res/lang/ko.json b/res/lang/ko.json index 6444b27..f61ee2f 100644 --- a/res/lang/ko.json +++ b/res/lang/ko.json @@ -1223,6 +1223,7 @@ "shield_in_progress": "작업 진행 중...", "shield_max_utxos": "작업당 최대 UTXO 수", "shield_merge_done": "차폐/통합이 완료되었습니다!", + "shield_no_zaddr_hint": "아직 보호된(z) 주소가 없습니다 — 먼저 받기 탭에서 만드세요.", "shield_op_failed": "작업 실패: ", "shield_op_submitted": "작업이 제출되었습니다: ", "shield_operation_id": "작업 ID: %s", diff --git a/res/lang/pt.json b/res/lang/pt.json index 2212f50..4c2b6ef 100644 --- a/res/lang/pt.json +++ b/res/lang/pt.json @@ -1223,6 +1223,7 @@ "shield_in_progress": "Operação em andamento...", "shield_max_utxos": "Máx. UTXOs por operação", "shield_merge_done": "Blindagem/fusão concluída!", + "shield_no_zaddr_hint": "Ainda não há endereço blindado (z) — crie um primeiro na aba Receber.", "shield_op_failed": "Falha na operação: ", "shield_op_submitted": "Operação enviada: ", "shield_operation_id": "ID da operação: %s", diff --git a/res/lang/ru.json b/res/lang/ru.json index 666194b..225e723 100644 --- a/res/lang/ru.json +++ b/res/lang/ru.json @@ -1223,6 +1223,7 @@ "shield_in_progress": "Операция выполняется...", "shield_max_utxos": "Макс. UTXO за операцию", "shield_merge_done": "Экранирование/объединение завершено!", + "shield_no_zaddr_hint": "Пока нет защищённого (z-)адреса — сначала создайте его на вкладке «Получить».", "shield_op_failed": "Ошибка операции: ", "shield_op_submitted": "Операция отправлена: ", "shield_operation_id": "ID операции: %s", diff --git a/res/lang/zh.json b/res/lang/zh.json index 7196e03..8c69376 100644 --- a/res/lang/zh.json +++ b/res/lang/zh.json @@ -1223,6 +1223,7 @@ "shield_in_progress": "操作进行中...", "shield_max_utxos": "每次操作最大 UTXO 数", "shield_merge_done": "屏蔽/合并完成!", + "shield_no_zaddr_hint": "还没有屏蔽(z)地址 — 请先在“接收”标签页创建一个。", "shield_op_failed": "操作失败:", "shield_op_submitted": "操作已提交:", "shield_operation_id": "操作 ID:%s", diff --git a/src/ui/windows/shield_dialog.cpp b/src/ui/windows/shield_dialog.cpp index 98f6000..495d0e4 100644 --- a/src/ui/windows/shield_dialog.cpp +++ b/src/ui/windows/shield_dialog.cpp @@ -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 { diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index acc7d10..9c79a4b 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1858,6 +1858,7 @@ void I18n::loadBuiltinEnglish() strings_["shield_max_utxos"] = "Max UTXOs per operation"; strings_["shield_merge_done"] = "Shield/merge completed!"; strings_["shield_select_z"] = "Select z-address..."; + strings_["shield_no_zaddr_hint"] = "No shielded (z) address yet — create one on the Receive tab first."; strings_["shield_started"] = "Shield operation started"; strings_["shield_title"] = "Shield Coinbase Rewards"; strings_["shield_to_address"] = "To Address (Shielded):";