12 Commits

Author SHA1 Message Date
1a41dec8d8 test(sweep): add capture surfaces for the Wave-2 fund/secret modals
Register sweep surfaces for the redesigned Wave-2 modals so they get captured for
visual review: modal-shield + modal-merge (ShieldDialog's two modes),
modal-transfer (a z->t transfer so the converted deshielding DialogWarningHeader
renders), and modal-key-export (KeyExportDialog — named distinctly from the
settings modal-export-key). Each setup opens the dialog through its public show()
without clicking any button, so no async RPC fires; teardown closes it.

Adds a static hide() to ShieldDialog and KeyExportDialog for clean sweep teardown
(AddressTransferDialog already had close()). KeyExportDialog::hide() also clears the
revealed key + frees its QR, mirroring the dialog's own close/dismiss secret-wipe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 18:37:22 -05:00
88e10f3e06 feat(overview): migrate the Export-Key modal to the reference design
Presentation-only migration of KeyExportDialog::render (reveals a private or
viewing key) — a secret-handling modal:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth cardW,
  idSuffix keyexport}) replacing the legacy positional overload
- replace the bespoke red-bordered "WARNING" box with material::DialogWarningHeader
  (user-approved) so the key-export warning matches the import-key reference; passes
  the correct per-key-type body key (private vs viewing)
- remove the two dividers (after the warning, above the Close footer)
- 4 StyledButton -> TactileButton (retry / reveal / show-hide / close)

The secret path is byte-for-byte unchanged: the z_exportkey/dumpprivkey/
z_exportviewingkey (and lite exportPrivateKeys) fetch + their secureWipeLiteSecret
calls, the "*"-masking when hidden, the Show/Hide toggle, copySecretToClipboard
(auto-clearing), the QR generate/cache/releaseQr, and the secret-clear on BOTH exit
paths (Close button + the post-EndOverlayDialog dismiss teardown).

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 18:23:14 -05:00
d29283a9b7 feat(overview): migrate the Transfer-funds modal to the reference design
Presentation-only migration of AddressTransferDialog::render (drag-one-address-
onto-another transfer) — a fund-moving modal:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth 620,
  idSuffix addrtransfer}) replacing the legacy positional overload
- the top-of-dialog deshielding warning -> material::DialogWarningHeader; the green
  shielding notice stays colored text (it's a positive notice, not a warning)
- remove the footer divider and drop its "+1.0f" footerH reserve term (keep the
  form->result-preview divider)
- reuse the existing shield_operation_id key for the "Operation ID: %s" label (no
  new i18n key)

The fund path is byte-for-byte unchanged: the amountValid guard, totalDeduct/
newFromBal/newToBal math, maxSendableAmount, the Max button, s_app->sendTransaction
+ its ok/fail callback, and the BeginDisabled(!amountValid || s_sending) gate.

Verified: build + ctest + hygiene clean; a 2-lens adversarial review returned zero
findings, proving the fund path diffs byte-for-byte identical to HEAD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 18:17:13 -05:00
ca65aa8bf7 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>
2026-07-14 18:03:54 -05:00
dae677acd8 feat(send): migrate the Confirm-Send modal to the reference design
Presentation-only migration of RenderSendConfirmPopup (the confirm-before-broadcast
popup) — a fund-moving modal:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth popupW,
  idSuffix confirmsend}) replacing the legacy positional overload; p_open stays
  nullptr (the popup keeps its own Escape + Cancel handling)
- TR() the one hardcoded status string in the synchronous live-revalidation guard
  ("Cannot send now …") -> new send_cannot_send_now key, 8 langs (em-dash preserved)

The fund path is byte-for-byte unchanged: the live re-validation guard, the
app->sendTransaction call + its success/failure callback, the lite-locked
requestLiteUnlock branch, the s_sending/s_show_confirm state, the Escape handler,
and the FROM/TO cards + fee-tier/summary rendering.

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:58:11 -05:00
4ef09d5546 fix(ui): drop Validate-address results divider; remove ungapturable daemon-prompt surface
Two sweep-driven fixes:
- Validate Address: remove the input->results Separator. When no result is shown
  yet it sat directly above the Close button and read as a footer divider,
  inconsistent with the reference design (the description divider was already gone).
- Drop the modal-daemon-prompt sweep surface: renderDaemonUpdatePrompt is gated
  behind !capture_mode_, so it never renders during a sweep (the capture showed the
  Settings page behind it). Its migration stays verified via build + the shared
  BlurFloat/TactileButton pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:40:59 -05:00
21be39c725 test(sweep): add capture surfaces for the Wave-1 standalone modals
Register sweep surfaces so the redesigned Wave-1 modals get captured for visual
review: modal-qr-popup, modal-request-payment, modal-validate-address,
modal-address-label, modal-daemon-prompt, and modal-antivirus (the last renders
only on Windows, where its #ifdef body compiles). Each is driven through the
dialog's public show()/flag; teardown closes it. Adds a static hide() to
RequestPaymentDialog / ValidateAddressDialog / AddressLabelDialog for clean sweep
teardown (they had no public close). The contact add/edit and explorer block-detail
modals are tab-embedded and not yet surfaced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:15:24 -05:00
abbdf3f4f7 feat(mining): migrate the Windows Defender antivirus-help dialog to reference design
Bring renderAntivirusHelpDialog onto the reference design and remove its hardcoded
English (the last Wave-1 modal):
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth 560,
  idSuffix antivirus}) replacing the legacy positional overload
- the hardcoded "Windows Defender Blocked Miner" title and all 10 step/intro/bullet
  strings -> TR() (11 new av_* keys + reuse `close`; 8-lang translations, Windows
  env vars / xmrig.exe kept literal, menu names localized)
- 2 StyledButton -> TactileButton, drop the footer divider, dp-scale the button widths
- CJK subset rebuilt (glyphs 1741->1751; kept — new glyphs are used)

NOTE: this dialog is inside #ifdef _WIN32, so the Linux build compiles it out and
cannot verify it — needs a Windows build to compile-check. The edits mirror the
already-verified daemon-update-prompt / migrate-to-seed struct-overlay pattern and
leave the ShellExecuteA call untouched. i18n.cpp + the daemon prompt (cross-platform)
build + ctest + hygiene clean on Linux.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:38:07 -05:00
28288d7197 feat(ui): migrate Explorer detail/loading + daemon-update prompt to reference design
Presentation-only reference-design migration:
- Explorer loading modal -> struct-form overlay (idSuffix explorerloading); block-
  detail modal loses its header divider; the search button + the prev/next nav
  become TactileButton; TR() the hardcoded "Previous block"/"Next block" tooltips,
  "TxID:", and the "shielded" output fallback (3 new explorer_* keys + reuse the
  existing `shielded` key), all 8 langs. Block-fetch RPC path untouched.
- Daemon-update startup prompt -> struct-form overlay (idSuffix daemonupdprompt),
  2 StyledButton -> TactileButton, drop the footer divider. The reinstall/
  prompted-size persistence logic is unchanged.

Build + ctest + hygiene clean; "DRGX" ticker left hardcoded per convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:33:52 -05:00
304f65c3b4 feat(ui): migrate Validate-address, Address-label & Contacts modals to reference design
Presentation-only reference-design migration of three more modals:
- Validate Address: struct-form overlay (idSuffix validateaddr), 3 StyledButton ->
  TactileButton, drop the description divider, and TR() the hardcoded "Error: %s"
  (reuse the existing error_format key). The validateaddress RPC/worker path is untouched.
- Address label: struct-form overlay (idSuffix addrlabel); drop the footer divider and
  zero its layout-reserve term (separatorH) so the fixed-height icon-picker card math
  stays consistent. Buttons were already TactileButton.
- Contacts add/edit: struct-form overlay (keeps its idSuffix + BeginOverlayDialogFooter);
  StyledButton -> TactileButton across the file (3 modal + 4 contact-list buttons) for a
  consistent Contacts tab. The address-book add/update/notify logic is untouched.

Build + hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:24:28 -05:00
04a5078133 feat(receive): migrate QR + Request-payment modals to the reference design
Bring the two Receive-tab modals onto the settings-modal reference language
(presentation-only):
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth, idSuffix})
  replacing the legacy positional overload (idSuffix qrpopup / requestpay)
- StyledButton -> TactileButton (2 + 3 sites)
- drop Request-payment's description divider

No logic touched: the QR texture lifecycle, payment-URI builder, address combo,
and clipboard/notification calls are unchanged. The "DRGX" ticker stays hardcoded
per the existing codebase convention (7 sites, no currency TR key).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:20:02 -05:00
b6b4fdacb5 fix(material): light-theme modal backdrop reads light, not dark
BlurFloat overlays draw a full-window veil over the live blur so the card-less
floating modal content reads as foreground. The light-theme branch used a strong
neutral DARK veil (IM_COL32(16,18,24,200)) on the theory that "light content pops
on a dark veil" — but on a light theme the modal chrome is dark-on-light, so it
sank INTO the veil: the heading, description, Sort row, and the create/scan/
reveal/close buttons all washed out to low-contrast dark-on-dark, and a "light"
theme read dark whenever a modal opened.

UI controls already carry a dropshadow on light themes for figure/ground, so the
backdrop no longer needs to supply contrast by going dark. Switch the light-theme
veil to a theme-tinted LIGHT frost (WithAlpha(Background(), 205)); the dark-theme
branch is untouched. Fixes every BlurFloat modal at once.

Verified via full sweep: marble / light read as clean light frost with all chrome
legible, dune gets a theme-appropriate warm frost, and obsidian (dark) is
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:09:16 -05:00
27 changed files with 379 additions and 148 deletions

Binary file not shown.

View File

@@ -55,6 +55,17 @@
"amount_label": "Betrag:",
"appearance": "ERSCHEINUNGSBILD",
"auto_shield": "Mining automatisch abschirmen",
"av_intro": "Mining-Software wird oft als potenziell unerwünscht eingestuft. Führen Sie diese Schritte aus, um das Pool-Mining zu aktivieren:",
"av_open_security": "Windows-Sicherheit öffnen",
"av_step1": "Schritt 1: Ausnahme hinzufügen",
"av_step1_b1": "Öffnen Sie Windows-Sicherheit > Viren- & Bedrohungsschutz",
"av_step1_b2": "Klicken Sie auf Einstellungen verwalten > Ausschlüsse > Hinzufügen oder entfernen",
"av_step1_b3": "Ordner hinzufügen: %APPDATA%\\ObsidianDragon\\",
"av_step2": "Schritt 2: Aus Quarantäne wiederherstellen (falls nötig)",
"av_step2_b1": "Windows-Sicherheit > Schutzverlauf",
"av_step2_b2": "Suchen Sie xmrig.exe und klicken Sie auf Wiederherstellen",
"av_step3": "Schritt 3: Wallet neu starten und erneut versuchen",
"av_title": "Windows Defender hat den Miner blockiert",
"available": "Verfügbar",
"backup_backing_up": "Sicherung läuft...",
"backup_create": "Sicherung erstellen",
@@ -386,13 +397,16 @@
"explorer_mempool": "Mempool",
"explorer_mempool_size": "Größe",
"explorer_mempool_txs": "Transaktionen",
"explorer_next_block": "Nächster Block",
"explorer_no_results": "Keine passenden zwischengespeicherten Blöcke",
"explorer_not_connected": "Nicht mit dem Daemon verbunden — Block- oder Transaktions-Hash kann nicht nachgeschlagen werden",
"explorer_prev_block": "Vorheriger Block",
"explorer_recent_blocks": "Letzte Blöcke",
"explorer_search": "Suchen",
"explorer_section": "EXPLORER",
"explorer_tx_outputs": "Ausgaben",
"explorer_tx_size": "Größe",
"explorer_txid": "TxID:",
"export": "Exportieren",
"export_csv": "CSV exportieren",
"export_keys_btn": "Schlüssel exportieren",
@@ -1070,6 +1084,7 @@
"send_amount": "Betrag",
"send_amount_details": "BETRAGSDETAILS",
"send_amount_upper": "BETRAG",
"send_cannot_send_now": "Senden derzeit nicht möglich — prüfen Sie Verbindung, Synchronisierung und verfügbares Guthaben.",
"send_clear_fields": "Alle Formularfelder leeren?",
"send_contacts_button": "Aus Kontakten auswählen",
"send_copy_error": "Fehler kopieren",
@@ -1208,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",

View File

@@ -55,6 +55,17 @@
"amount_label": "Cantidad:",
"appearance": "APARIENCIA",
"auto_shield": "Auto-proteger minería",
"av_intro": "El software de minería suele marcarse como potencialmente no deseado. Sigue estos pasos para habilitar la minería en pool:",
"av_open_security": "Abrir Seguridad de Windows",
"av_step1": "Paso 1: Agregar exclusión",
"av_step1_b1": "Abre Seguridad de Windows > Protección antivirus y contra amenazas",
"av_step1_b2": "Haz clic en Administrar configuración > Exclusiones > Agregar o quitar",
"av_step1_b3": "Agrega la carpeta: %APPDATA%\\ObsidianDragon\\",
"av_step2": "Paso 2: Restaurar desde cuarentena (si es necesario)",
"av_step2_b1": "Seguridad de Windows > Historial de protección",
"av_step2_b2": "Busca xmrig.exe y haz clic en Restaurar",
"av_step3": "Paso 3: Reinicia el monedero e inténtalo de nuevo",
"av_title": "Windows Defender bloqueó el minero",
"available": "Disponible",
"backup_backing_up": "Respaldando...",
"backup_create": "Crear Respaldo",
@@ -386,13 +397,16 @@
"explorer_mempool": "Mempool",
"explorer_mempool_size": "Tamaño",
"explorer_mempool_txs": "Transacciones",
"explorer_next_block": "Bloque siguiente",
"explorer_no_results": "No hay bloques en caché que coincidan",
"explorer_not_connected": "Sin conexión al daemon: no se puede buscar un bloque ni un hash de transacción",
"explorer_prev_block": "Bloque anterior",
"explorer_recent_blocks": "Bloques Recientes",
"explorer_search": "Buscar",
"explorer_section": "EXPLORADOR",
"explorer_tx_outputs": "Salidas",
"explorer_tx_size": "Tamaño",
"explorer_txid": "TxID:",
"export": "Exportar",
"export_csv": "Exportar CSV",
"export_keys_btn": "Exportar Claves",
@@ -1070,6 +1084,7 @@
"send_amount": "Cantidad",
"send_amount_details": "DETALLES DE CANTIDAD",
"send_amount_upper": "CANTIDAD",
"send_cannot_send_now": "No se puede enviar ahora — revisa la conexión, la sincronización y el saldo disponible.",
"send_clear_fields": "¿Limpiar todos los campos del formulario?",
"send_contacts_button": "Elegir de contactos",
"send_copy_error": "Copiar Error",
@@ -1208,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",

View File

@@ -55,6 +55,17 @@
"amount_label": "Montant :",
"appearance": "APPARENCE",
"auto_shield": "Auto-blindage du minage",
"av_intro": "Les logiciels de minage sont souvent signalés comme potentiellement indésirables. Suivez ces étapes pour activer le minage en pool :",
"av_open_security": "Ouvrir Sécurité Windows",
"av_step1": "Étape 1 : Ajouter une exclusion",
"av_step1_b1": "Ouvrez Sécurité Windows > Protection contre les virus et menaces",
"av_step1_b2": "Cliquez sur Gérer les paramètres > Exclusions > Ajouter ou supprimer",
"av_step1_b3": "Ajoutez le dossier : %APPDATA%\\ObsidianDragon\\",
"av_step2": "Étape 2 : Restaurer depuis la quarantaine (si nécessaire)",
"av_step2_b1": "Sécurité Windows > Historique de protection",
"av_step2_b2": "Trouvez xmrig.exe et cliquez sur Restaurer",
"av_step3": "Étape 3 : Redémarrez le portefeuille et réessayez",
"av_title": "Windows Defender a bloqué le mineur",
"available": "Disponible",
"backup_backing_up": "Sauvegarde en cours...",
"backup_create": "Créer une sauvegarde",
@@ -386,13 +397,16 @@
"explorer_mempool": "Mempool",
"explorer_mempool_size": "Taille",
"explorer_mempool_txs": "Transactions",
"explorer_next_block": "Bloc suivant",
"explorer_no_results": "Aucun bloc en cache correspondant",
"explorer_not_connected": "Non connecté au daemon — impossible de rechercher un hash de bloc ou de transaction",
"explorer_prev_block": "Bloc précédent",
"explorer_recent_blocks": "Blocs récents",
"explorer_search": "Rechercher",
"explorer_section": "EXPLORATEUR",
"explorer_tx_outputs": "Sorties",
"explorer_tx_size": "Taille",
"explorer_txid": "TxID :",
"export": "Exporter",
"export_csv": "Exporter en CSV",
"export_keys_btn": "Exporter les clés",
@@ -1070,6 +1084,7 @@
"send_amount": "Montant",
"send_amount_details": "DÉTAILS DU MONTANT",
"send_amount_upper": "MONTANT",
"send_cannot_send_now": "Envoi impossible pour le moment — vérifiez la connexion, la synchronisation et le solde disponible.",
"send_clear_fields": "Effacer tous les champs du formulaire ?",
"send_contacts_button": "Choisir parmi les contacts",
"send_copy_error": "Copier l'erreur",
@@ -1208,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",

View File

@@ -55,6 +55,17 @@
"amount_label": "金額:",
"appearance": "外観",
"auto_shield": "マイニング自動シールド",
"av_intro": "マイニングソフトウェアは、望ましくない可能性があるものとしてフラグが立てられることがよくあります。プールマイニングを有効にするには、次の手順に従ってください。",
"av_open_security": "Windows セキュリティを開く",
"av_step1": "ステップ1: 除外の追加",
"av_step1_b1": "Windows セキュリティ > ウイルスと脅威の防止 を開く",
"av_step1_b2": "設定の管理 > 除外 > 追加または削除 をクリック",
"av_step1_b3": "フォルダーを追加: %APPDATA%\\ObsidianDragon\\",
"av_step2": "ステップ2: 検疫から復元(必要な場合)",
"av_step2_b1": "Windows セキュリティ > 保護の履歴",
"av_step2_b2": "xmrig.exe を見つけて「復元」をクリック",
"av_step3": "ステップ3: ウォレットを再起動して再試行",
"av_title": "Windows Defender がマイナーをブロックしました",
"available": "利用可能",
"backup_backing_up": "バックアップ中...",
"backup_create": "バックアップを作成",
@@ -386,13 +397,16 @@
"explorer_mempool": "メモリプール",
"explorer_mempool_size": "サイズ",
"explorer_mempool_txs": "トランザクション",
"explorer_next_block": "次のブロック",
"explorer_no_results": "一致するキャッシュ済みブロックがありません",
"explorer_not_connected": "デーモンに未接続 — ブロックまたはトランザクションハッシュを検索できません",
"explorer_prev_block": "前のブロック",
"explorer_recent_blocks": "最近のブロック",
"explorer_search": "検索",
"explorer_section": "エクスプローラー",
"explorer_tx_outputs": "出力",
"explorer_tx_size": "サイズ",
"explorer_txid": "TxID",
"export": "エクスポート",
"export_csv": "CSVエクスポート",
"export_keys_btn": "鍵をエクスポート",
@@ -1070,6 +1084,7 @@
"send_amount": "金額",
"send_amount_details": "金額の詳細",
"send_amount_upper": "金額",
"send_cannot_send_now": "現在送信できません — 接続、同期、利用可能な残高を確認してください。",
"send_clear_fields": "すべてのフォームフィールドをクリアしますか?",
"send_contacts_button": "連絡先から選択",
"send_copy_error": "エラーをコピー",
@@ -1208,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",

View File

@@ -55,6 +55,17 @@
"amount_label": "금액:",
"appearance": "외관",
"auto_shield": "채굴 자동 차폐",
"av_intro": "채굴 소프트웨어는 종종 잠재적으로 원치 않는 항목으로 표시됩니다. 풀 채굴을 활성화하려면 다음 단계를 따르세요:",
"av_open_security": "Windows 보안 열기",
"av_step1": "1단계: 제외 추가",
"av_step1_b1": "Windows 보안 > 바이러스 및 위협 방지 열기",
"av_step1_b2": "설정 관리 > 제외 > 추가 또는 제거 클릭",
"av_step1_b3": "폴더 추가: %APPDATA%\\ObsidianDragon\\",
"av_step2": "2단계: 격리에서 복원(필요한 경우)",
"av_step2_b1": "Windows 보안 > 보호 기록",
"av_step2_b2": "xmrig.exe를 찾아 복원 클릭",
"av_step3": "3단계: 지갑을 다시 시작하고 다시 시도",
"av_title": "Windows Defender가 채굴기를 차단했습니다",
"available": "사용 가능",
"backup_backing_up": "백업 중...",
"backup_create": "백업 생성",
@@ -386,13 +397,16 @@
"explorer_mempool": "멤풀",
"explorer_mempool_size": "크기",
"explorer_mempool_txs": "트랜잭션",
"explorer_next_block": "다음 블록",
"explorer_no_results": "일치하는 캐시된 블록이 없습니다",
"explorer_not_connected": "데몬에 연결되지 않음 — 블록 또는 트랜잭션 해시를 조회할 수 없습니다",
"explorer_prev_block": "이전 블록",
"explorer_recent_blocks": "최근 블록",
"explorer_search": "검색",
"explorer_section": "탐색기",
"explorer_tx_outputs": "출력",
"explorer_tx_size": "크기",
"explorer_txid": "TxID:",
"export": "내보내기",
"export_csv": "CSV 내보내기",
"export_keys_btn": "키 내보내기",
@@ -1070,6 +1084,7 @@
"send_amount": "금액",
"send_amount_details": "금액 상세",
"send_amount_upper": "금액",
"send_cannot_send_now": "지금 보낼 수 없습니다 — 연결, 동기화, 사용 가능한 잔액을 확인하세요.",
"send_clear_fields": "모든 양식 필드를 지우시겠습니까?",
"send_contacts_button": "연락처에서 선택",
"send_copy_error": "오류 복사",
@@ -1208,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",

View File

@@ -55,6 +55,17 @@
"amount_label": "Valor:",
"appearance": "APARÊNCIA",
"auto_shield": "Auto-blindar mineração",
"av_intro": "Softwares de mineração costumam ser sinalizados como potencialmente indesejados. Siga estes passos para habilitar a mineração em pool:",
"av_open_security": "Abrir Segurança do Windows",
"av_step1": "Passo 1: Adicionar exclusão",
"av_step1_b1": "Abra Segurança do Windows > Proteção contra vírus e ameaças",
"av_step1_b2": "Clique em Gerenciar configurações > Exclusões > Adicionar ou remover",
"av_step1_b3": "Adicione a pasta: %APPDATA%\\ObsidianDragon\\",
"av_step2": "Passo 2: Restaurar da quarentena (se necessário)",
"av_step2_b1": "Segurança do Windows > Histórico de proteção",
"av_step2_b2": "Encontre xmrig.exe e clique em Restaurar",
"av_step3": "Passo 3: Reinicie a carteira e tente novamente",
"av_title": "Windows Defender bloqueou o minerador",
"available": "Disponível",
"backup_backing_up": "Fazendo backup...",
"backup_create": "Criar Backup",
@@ -386,13 +397,16 @@
"explorer_mempool": "Mempool",
"explorer_mempool_size": "Tamanho",
"explorer_mempool_txs": "Transações",
"explorer_next_block": "Próximo bloco",
"explorer_no_results": "Nenhum bloco em cache correspondente",
"explorer_not_connected": "Não conectado ao daemon — não é possível procurar um bloco ou hash de transação",
"explorer_prev_block": "Bloco anterior",
"explorer_recent_blocks": "Blocos Recentes",
"explorer_search": "Pesquisar",
"explorer_section": "EXPLORADOR",
"explorer_tx_outputs": "Saídas",
"explorer_tx_size": "Tamanho",
"explorer_txid": "TxID:",
"export": "Exportar",
"export_csv": "Exportar CSV",
"export_keys_btn": "Exportar Chaves",
@@ -1070,6 +1084,7 @@
"send_amount": "Valor",
"send_amount_details": "DETALHES DO VALOR",
"send_amount_upper": "VALOR",
"send_cannot_send_now": "Não é possível enviar agora — verifique a conexão, a sincronização e o saldo disponível.",
"send_clear_fields": "Limpar todos os campos do formulário?",
"send_contacts_button": "Escolher dos contatos",
"send_copy_error": "Copiar Erro",
@@ -1208,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",

View File

@@ -55,6 +55,17 @@
"amount_label": "Сумма:",
"appearance": "ВНЕШНИЙ ВИД",
"auto_shield": "Авто-экранирование майнинга",
"av_intro": "Программы для майнинга часто помечаются как потенциально нежелательные. Выполните эти шаги, чтобы включить пул-майнинг:",
"av_open_security": "Открыть Безопасность Windows",
"av_step1": "Шаг 1: Добавить исключение",
"av_step1_b1": "Откройте Безопасность Windows > Защита от вирусов и угроз",
"av_step1_b2": "Нажмите Управление настройками > Исключения > Добавить или удалить",
"av_step1_b3": "Добавьте папку: %APPDATA%\\ObsidianDragon\\",
"av_step2": "Шаг 2: Восстановить из карантина (при необходимости)",
"av_step2_b1": "Безопасность Windows > Журнал защиты",
"av_step2_b2": "Найдите xmrig.exe и нажмите Восстановить",
"av_step3": "Шаг 3: Перезапустите кошелёк и попробуйте снова",
"av_title": "Windows Defender заблокировал майнер",
"available": "Доступно",
"backup_backing_up": "Создание резервной копии...",
"backup_create": "Создать резервную копию",
@@ -386,13 +397,16 @@
"explorer_mempool": "Мемпул",
"explorer_mempool_size": "Размер",
"explorer_mempool_txs": "Транзакции",
"explorer_next_block": "Следующий блок",
"explorer_no_results": "Нет подходящих кэшированных блоков",
"explorer_not_connected": "Нет подключения к демону — невозможно найти хеш блока или транзакции",
"explorer_prev_block": "Предыдущий блок",
"explorer_recent_blocks": "Последние блоки",
"explorer_search": "Поиск",
"explorer_section": "ОБОЗРЕВАТЕЛЬ",
"explorer_tx_outputs": "Выходы",
"explorer_tx_size": "Размер",
"explorer_txid": "TxID:",
"export": "Экспорт",
"export_csv": "Экспорт в CSV",
"export_keys_btn": "Экспорт ключей",
@@ -1070,6 +1084,7 @@
"send_amount": "Сумма",
"send_amount_details": "ДЕТАЛИ СУММЫ",
"send_amount_upper": "СУММА",
"send_cannot_send_now": "Сейчас отправить нельзя — проверьте соединение, синхронизацию и доступный баланс.",
"send_clear_fields": "Очистить все поля формы?",
"send_contacts_button": "Выбрать из контактов",
"send_copy_error": "Копировать ошибку",
@@ -1208,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",

View File

@@ -55,6 +55,17 @@
"amount_label": "金额:",
"appearance": "外观",
"auto_shield": "自动屏蔽挖矿",
"av_intro": "挖矿软件经常被标记为潜在有害程序。请按照以下步骤启用矿池挖矿:",
"av_open_security": "打开 Windows 安全中心",
"av_step1": "第 1 步:添加排除项",
"av_step1_b1": "打开 Windows 安全中心 > 病毒和威胁防护",
"av_step1_b2": "单击 管理设置 > 排除项 > 添加或删除",
"av_step1_b3": "添加文件夹:%APPDATA%\\ObsidianDragon\\",
"av_step2": "第 2 步:从隔离区还原(如果需要)",
"av_step2_b1": "Windows 安全中心 > 保护历史记录",
"av_step2_b2": "找到 xmrig.exe 并单击“还原”",
"av_step3": "第 3 步:重启钱包并重试",
"av_title": "Windows Defender 已阻止矿工程序",
"available": "可用",
"backup_backing_up": "正在备份...",
"backup_create": "创建备份",
@@ -386,13 +397,16 @@
"explorer_mempool": "内存池",
"explorer_mempool_size": "大小",
"explorer_mempool_txs": "交易",
"explorer_next_block": "下一个区块",
"explorer_no_results": "没有匹配的缓存区块",
"explorer_not_connected": "未连接到守护进程——无法查询区块或交易哈希",
"explorer_prev_block": "上一个区块",
"explorer_recent_blocks": "最近区块",
"explorer_search": "搜索",
"explorer_section": "浏览器",
"explorer_tx_outputs": "输出",
"explorer_tx_size": "大小",
"explorer_txid": "TxID",
"export": "导出",
"export_csv": "导出 CSV",
"export_keys_btn": "导出密钥",
@@ -1070,6 +1084,7 @@
"send_amount": "金额",
"send_amount_details": "金额详情",
"send_amount_upper": "金额",
"send_cannot_send_now": "现在无法发送 — 请检查连接、同步和可用余额。",
"send_clear_fields": "清除所有表单字段?",
"send_contacts_button": "从联系人中选择",
"send_copy_error": "复制错误",
@@ -1208,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",

View File

@@ -3368,7 +3368,11 @@ void App::maybeOfferDaemonUpdate()
void App::renderDaemonUpdatePrompt()
{
if (!ui::material::BeginOverlayDialog(TR("daemon_update_title"), &show_daemon_update_prompt_, 520.0f, 0.94f))
ui::material::OverlayDialogSpec ov;
ov.title = TR("daemon_update_title"); ov.p_open = &show_daemon_update_prompt_;
ov.style = ui::material::OverlayStyle::BlurFloat;
ov.cardWidth = 520.0f; ov.idSuffix = "daemonupdprompt";
if (!ui::material::BeginOverlayDialog(ov))
return;
const float dp = ui::Layout::dpiScale();
ImGui::TextWrapped("%s", TR("daemon_update_body"));
@@ -3377,13 +3381,12 @@ void App::renderDaemonUpdatePrompt()
ImGui::TextWrapped("%s", TR("daemon_update_safe"));
ImGui::PopStyleColor();
ImGui::Spacing();
ImGui::Separator();
if (ui::material::StyledButton(TR("daemon_update_now"), ImVec2(170 * dp, 0))) {
if (ui::material::TactileButton(TR("daemon_update_now"), ImVec2(170 * dp, 0))) {
show_daemon_update_prompt_ = false;
reinstallBundledDaemon(); // stops the node (managed or external), overwrites, restarts
}
ImGui::SameLine();
if (ui::material::StyledButton(TR("daemon_update_keep"), ImVec2(150 * dp, 0))) {
if (ui::material::TactileButton(TR("daemon_update_keep"), ImVec2(150 * dp, 0))) {
show_daemon_update_prompt_ = false;
}
ui::material::EndOverlayDialog();
@@ -3817,42 +3820,43 @@ void App::renderAntivirusHelpDialog()
#ifdef _WIN32
if (!pending_antivirus_dialog_) return;
if (!ui::material::BeginOverlayDialog("Windows Defender Blocked Miner", &pending_antivirus_dialog_, 560.0f, 0.94f)) {
ui::material::OverlayDialogSpec ov;
ov.title = TR("av_title"); ov.p_open = &pending_antivirus_dialog_;
ov.style = ui::material::OverlayStyle::BlurFloat;
ov.cardWidth = 560.0f; ov.idSuffix = "antivirus";
if (!ui::material::BeginOverlayDialog(ov)) {
return;
}
ImGui::TextWrapped(
"Mining software is often flagged as potentially unwanted. "
"Follow these steps to enable pool mining:");
const float dp = ui::Layout::dpiScale();
ImGui::TextWrapped("%s", TR("av_intro"));
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, "Step 1: Add Exclusion");
ImGui::BulletText("Open Windows Security > Virus & threat protection");
ImGui::BulletText("Click Manage settings > Exclusions > Add or remove");
ImGui::BulletText("Add folder: %%APPDATA%%\\ObsidianDragon\\");
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, TR("av_step1"));
ImGui::BulletText("%s", TR("av_step1_b1"));
ImGui::BulletText("%s", TR("av_step1_b2"));
ImGui::BulletText("%s", TR("av_step1_b3"));
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, "Step 2: Restore from Quarantine (if needed)");
ImGui::BulletText("Windows Security > Protection history");
ImGui::BulletText("Find xmrig.exe and click Restore");
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, TR("av_step2"));
ImGui::BulletText("%s", TR("av_step2_b1"));
ImGui::BulletText("%s", TR("av_step2_b2"));
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, "Step 3: Restart wallet and try again");
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, TR("av_step3"));
ImGui::Dummy(ImVec2(0, ui::Layout::spacingMd()));
ImGui::Separator();
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
float btnW = 160.0f;
if (ui::material::StyledButton("Open Windows Security", ImVec2(btnW, 0))) {
float btnW = 160.0f * dp;
if (ui::material::TactileButton(TR("av_open_security"), ImVec2(btnW, 0))) {
// Open Windows Security app to the exclusions page
ShellExecuteA(NULL, "open", "windowsdefender://threat", NULL, NULL, SW_SHOWNORMAL);
}
ImGui::SameLine();
if (ui::material::StyledButton("Close", ImVec2(100.0f, 0))) {
if (ui::material::TactileButton(TR("close"), ImVec2(100.0f * dp, 0))) {
pending_antivirus_dialog_ = false;
}
ui::material::EndOverlayDialog();
#endif
}

View File

@@ -24,6 +24,13 @@
#include "ui/windows/bootstrap_download_dialog.h"
#include "ui/windows/daemon_download_dialog.h"
#include "ui/windows/xmrig_download_dialog.h"
#include "ui/windows/qr_popup_dialog.h"
#include "ui/windows/request_payment_dialog.h"
#include "ui/windows/validate_address_dialog.h"
#include "ui/windows/address_label_dialog.h"
#include "ui/windows/shield_dialog.h"
#include "ui/windows/address_transfer_dialog.h"
#include "ui/windows/key_export_dialog.h"
#include "ui/pages/settings_page.h"
#include "util/platform.h"
#include "wallet/wallet_capabilities.h"
@@ -325,6 +332,47 @@ void App::buildSweepCatalog()
a.show_pin_remove_ = false; a.pin_status_.clear();
memset(a.pin_old_buf_, 0, sizeof(a.pin_old_buf_));
});
// Wave-1 standalone dialogs (rendered globally from App::render, so any page works).
add("modal-qr-popup", ui::NavPage::Receive,
[](App&) { ui::QRPopupDialog::show(kDemoZAddr, "Savings"); },
[](App&) { ui::QRPopupDialog::close(); });
add("modal-request-payment", ui::NavPage::Receive,
[](App&) { ui::RequestPaymentDialog::show(kDemoZAddr); },
[](App&) { ui::RequestPaymentDialog::hide(); });
add("modal-validate-address", ui::NavPage::Receive,
[](App&) { ui::ValidateAddressDialog::show(); },
[](App&) { ui::ValidateAddressDialog::hide(); });
add("modal-address-label", ui::NavPage::Overview,
[](App& a) { ui::AddressLabelDialog::show(&a, kDemoZAddr, true); },
[](App&) { ui::AddressLabelDialog::hide(); });
// (No modal-daemon-prompt surface: renderDaemonUpdatePrompt is gated behind !capture_mode_,
// so it can't render during a sweep. Its migration is verified by build + the shared overlay pattern.)
add("modal-antivirus", ui::NavPage::Mining,
[](App& a) { a.pending_antivirus_dialog_ = true; },
[](App& a) { a.pending_antivirus_dialog_ = false; });
// Wave-2 fund/secret dialogs (setup never fires the async RPC — no button is clicked).
add("modal-shield", ui::NavPage::Send,
[](App&) { ui::ShieldDialog::showShieldCoinbase(); },
[](App&) { ui::ShieldDialog::hide(); });
add("modal-merge", ui::NavPage::Send,
[](App&) { ui::ShieldDialog::showMerge(); },
[](App&) { ui::ShieldDialog::hide(); });
// z->t transfer so the (converted) deshielding DialogWarningHeader renders.
add("modal-transfer", ui::NavPage::Overview,
[](App& a) {
ui::AddressTransferInfo info;
info.fromAddr = kDemoZAddr;
info.toAddr = "t1DemoTransparentReceiveAddress00000";
info.fromBalance = 12.5; info.toBalance = 3.0;
info.fromIsZ = true; info.toIsZ = false;
ui::AddressTransferDialog::show(&a, info);
},
[](App&) { ui::AddressTransferDialog::close(); });
// Distinct from the settings "modal-export-key" (App::renderExportKeyDialog) — this is the
// per-address KeyExportDialog opened from Overview address rows.
add("modal-key-export", ui::NavPage::Overview,
[](App&) { ui::KeyExportDialog::show(kDemoZAddr, ui::KeyExportDialog::KeyType::Private); },
[](App&) { ui::KeyExportDialog::hide(); });
add("modal-about", ui::NavPage::Overview,
[](App& a) { a.show_about_ = true; }, [](App& a) { a.show_about_ = false; });
add("modal-settings", ui::NavPage::Settings,

View File

@@ -746,13 +746,13 @@ inline void DrawFullWindowBlurBackdrop(ImDrawList* dl, const ImVec2& pMin, const
params.absoluteBlurRadius = true; // fixed modal blur — independent of the user's acrylic slider
params.fallbackColor.w = 1.0f; // full-strength blur so the live content reads
effects::ImGuiAcrylic::DrawAcrylicRect(dl, pMin, pMax, params, 0.0f);
// Dim over the blur so the floating (card-less) modal reads as foreground. On DARK themes the
// theme-tinted frost darkens the backdrop and the light content pops. On LIGHT themes that same
// tint whitens the backdrop and leaves card-less content with no figure/ground separation — so
// light themes get a neutral dark veil instead, which also makes the modal's fields/buttons stand
// out from the (now slightly greyed) surround. Both a touch stronger than before (was alpha 70).
// Veil over the blur so the floating (card-less) modal reads as foreground. On DARK themes a
// theme-tinted frost darkens the backdrop and the light content pops. On LIGHT themes the modal's
// own controls carry a dropshadow for figure/ground, so the backdrop stays LIGHT — a theme-tinted
// light frost — rather than a dark veil (which made a "light" theme read dark and sank the
// dark-on-light chrome into it).
if (IsLightTheme())
dl->AddRectFilled(pMin, pMax, IM_COL32(16, 18, 24, 200)); // neutral dark veil anchors on white
dl->AddRectFilled(pMin, pMax, WithAlpha(Background(), 205)); // light frost — dropshadows give shape
else
dl->AddRectFilled(pMin, pMax, WithAlpha(Background(), 120)); // theme-tinted darken, stronger
}

View File

@@ -61,7 +61,12 @@ public:
constexpr float cardTopViewportRatio = 0.15f;
constexpr float cardBottomViewportRatio = 0.80f;
if (BeginOverlayDialog(TR("set_label"), &s_open, 660.0f, 0.92f, cardBottomViewportRatio)) {
OverlayDialogSpec ov;
ov.title = TR("set_label"); ov.p_open = &s_open;
ov.style = OverlayStyle::BlurFloat;
ov.cardWidth = 660.0f; ov.idSuffix = "addrlabel";
ov.cardBottomViewportRatio = cardBottomViewportRatio;
if (BeginOverlayDialog(ov)) {
float dp = Layout::dpiScale();
// Address preview
@@ -108,7 +113,7 @@ public:
const bool showClearIcon = (s_selectedIcon >= 0);
const float clearRowH = showClearIcon ? ImGui::GetTextLineHeight() : 0.0f;
const float buttonRowH = ImGui::GetFrameHeight();
const float separatorH = 1.0f;
const float separatorH = 0.0f; // footer divider removed (reference design)
const float preButtonReserve =
(showClearIcon ? (style.ItemSpacing.y + clearRowH) : 0.0f) +
style.ItemSpacing.y * 3.0f + separatorH;
@@ -196,7 +201,6 @@ public:
ImGui::Spacing();
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (ImGui::GetCursorPosY() < buttonY) {
@@ -247,6 +251,9 @@ public:
static bool isOpen() { return s_open; }
// Close the dialog (used by the UI sweep teardown).
static void hide() { s_open = false; }
private:
static inline bool s_open = false;
static inline App* s_app = nullptr;

View File

@@ -59,7 +59,11 @@ public:
using namespace material;
if (BeginOverlayDialog(TR("transfer_funds"), &s_open, 620.0f, 0.94f)) {
OverlayDialogSpec ov;
ov.title = TR("transfer_funds"); ov.p_open = &s_open;
ov.style = OverlayStyle::BlurFloat;
ov.cardWidth = 620.0f; ov.idSuffix = "addrtransfer";
if (BeginOverlayDialog(ov)) {
float dp = Layout::dpiScale();
ImDrawList* dl = ImGui::GetWindowDrawList();
@@ -72,9 +76,7 @@ public:
// Shielding / deshielding warning
if (s_info.fromIsZ && !s_info.toIsZ) {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Warning()));
ImGui::TextWrapped("%s", TR("deshielding_warning"));
ImGui::PopStyleColor();
DialogWarningHeader(TR("deshielding_warning"));
ImGui::Spacing();
} else if (!s_info.fromIsZ && s_info.toIsZ) {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Success()));
@@ -188,7 +190,7 @@ public:
buttonFont->CalcTextSizeA(buttonFontSize, 1000.0f, 0.0f, sendingLabel).x);
float confirmW = std::max(confirmMinW, confirmTextW + buttonPadW);
float totalW = cancelW + confirmW + Layout::spacingMd();
float footerH = ImGui::GetFrameHeight() + ImGui::GetStyle().ItemSpacing.y * 3.0f + 1.0f;
float footerH = ImGui::GetFrameHeight() + ImGui::GetStyle().ItemSpacing.y * 3.0f; // footer divider removed
ImGuiViewport* vp = ImGui::GetMainViewport();
float cardBottomY = vp->Pos.y + vp->Size.y * 0.85f;
float footerTopY = cardBottomY - 24.0f * dp - footerH;
@@ -199,7 +201,6 @@ public:
ImGui::Spacing();
}
ImGui::Separator();
ImGui::Spacing();
float rowStartX = ImGui::GetCursorPosX();
@@ -300,7 +301,7 @@ private:
ImGui::Spacing();
{
char buf[128];
snprintf(buf, sizeof(buf), "Operation ID: %s",
snprintf(buf, sizeof(buf), TR("shield_operation_id"),
s_resultMsg.size() > 40
? (s_resultMsg.substr(0, 20) + "..." + s_resultMsg.substr(s_resultMsg.size() - 10)).c_str()
: s_resultMsg.c_str());

View File

@@ -129,8 +129,12 @@ void RenderContactsTab(App* app)
float actionGap = actionBtn.gap > 0 ? actionBtn.gap : Layout::kDialogActionGap();
float notesH = notesInput.height > 0 ? notesInput.height : 60.0f;
if (material::BeginOverlayDialog(title, open, dialogW, 0.94f,
Layout::kDialogCompactBottomRatio(), id)) {
material::OverlayDialogSpec ov;
ov.title = title; ov.p_open = open;
ov.style = material::OverlayStyle::BlurFloat;
ov.cardWidth = dialogW; ov.idSuffix = id;
ov.cardBottomViewportRatio = Layout::kDialogCompactBottomRatio();
if (material::BeginOverlayDialog(ov)) {
// Focus the first field the frame the dialog opens so it's keyboard-ready.
if (s_focus_edit_field) {
ImGui::SetKeyboardFocusHere();
@@ -145,7 +149,7 @@ void RenderContactsTab(App* app)
s_edit_address, sizeof(s_edit_address), formW);
if (!isEdit) {
ImGui::SameLine();
if (material::StyledButton(TR("paste"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
if (material::TactileButton(TR("paste"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
const char* clipboard = ImGui::GetClipboardText();
if (clipboard) copyEditField(s_edit_address, sizeof(s_edit_address), clipboard);
}
@@ -167,7 +171,7 @@ void RenderContactsTab(App* app)
if (!canSubmit) ImGui::BeginDisabled();
const char* primaryLabel = isEdit ? TR("save") : TR("add");
if (material::StyledButton(primaryLabel, ImVec2(actionW, 0), S.resolveFont(actionBtn.font))) {
if (material::TactileButton(primaryLabel, ImVec2(actionW, 0), S.resolveFont(actionBtn.font))) {
// Trim the label/address (a pasted address often carries a trailing newline); keep notes as-is.
auto trimAB = [](std::string s) {
while (!s.empty() && (s.front()==' '||s.front()=='\t'||s.front()=='\n'||s.front()=='\r')) s.erase(s.begin());
@@ -198,7 +202,7 @@ void RenderContactsTab(App* app)
if (!canSubmit) ImGui::EndDisabled();
ImGui::SameLine(0, actionGap);
if (material::StyledButton(TR("cancel"), ImVec2(actionW, 0), S.resolveFont(actionBtn.font))) {
if (material::TactileButton(TR("cancel"), ImVec2(actionW, 0), S.resolveFont(actionBtn.font))) {
*open = false;
}
@@ -212,7 +216,7 @@ void RenderContactsTab(App* app)
ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar);
// Toolbar
if (material::StyledButton(TR("address_book_add_new"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
if (material::TactileButton(TR("address_book_add_new"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
s_show_add_dialog = true;
s_focus_edit_field = true;
clearEditFields();
@@ -222,7 +226,7 @@ void RenderContactsTab(App* app)
if (!has_selection) ImGui::BeginDisabled();
if (material::StyledButton(TR("edit"), ImVec2(0,0), S.resolveFont(actionBtn.font)))
if (material::TactileButton(TR("edit"), ImVec2(0,0), S.resolveFont(actionBtn.font)))
openEdit();
ImGui::SameLine();
@@ -230,12 +234,12 @@ void RenderContactsTab(App* app)
// Delete — relabel to a visible confirm prompt while armed (not just a toast).
bool armed = has_selection && s_confirm_delete_idx == s_selected_index;
const char* delLabel = armed ? TR("address_book_confirm_delete") : TR("delete");
if (material::StyledButton(delLabel, ImVec2(0,0), S.resolveFont(actionBtn.font)))
if (material::TactileButton(delLabel, ImVec2(0,0), S.resolveFont(actionBtn.font)))
doDelete();
ImGui::SameLine();
if (material::StyledButton(TR("copy_address"), ImVec2(0,0), S.resolveFont(actionBtn.font)))
if (material::TactileButton(TR("copy_address"), ImVec2(0,0), S.resolveFont(actionBtn.font)))
doCopy();
if (!has_selection) ImGui::EndDisabled();

View File

@@ -483,7 +483,7 @@ static void renderSearchBar(App* app, float availWidth) {
// Search button — same height as input
ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, cursorY));
bool clicked = material::StyledButton(TR("explorer_search"),
bool clicked = material::TactileButton(TR("explorer_search"),
ImVec2(btnW, barH));
std::string query(s_search_buf);
@@ -1012,7 +1012,11 @@ static void renderBlockDetailModal(App* app) {
if (s_detail_loading && !s_show_detail_modal) {
// Show a loading modal while fetching
if (BeginOverlayDialog(TR("loading"), &s_detail_loading, 300.0f, 0.85f)) {
OverlayDialogSpec lov;
lov.title = TR("loading"); lov.p_open = &s_detail_loading;
lov.style = OverlayStyle::BlurFloat;
lov.cardWidth = 300.0f; lov.idSuffix = "explorerloading";
if (BeginOverlayDialog(lov)) {
ImGui::Spacing();
char loadBuf[64];
snprintf(loadBuf, sizeof(loadBuf), "%s%s", TR("loading"), material::LoadingDots());
@@ -1055,7 +1059,7 @@ static void renderBlockDetailModal(App* app) {
if (app->rpc() && app->rpc()->isConnected())
fetchBlockDetail(app, s_detail_height - 1);
}
if (ImGui::IsItemHovered()) material::Tooltip("Previous block");
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("explorer_prev_block"));
ImGui::PopID();
ImGui::PopFont();
ImGui::SameLine();
@@ -1069,7 +1073,7 @@ static void renderBlockDetailModal(App* app) {
if (app->rpc() && app->rpc()->isConnected())
fetchBlockDetail(app, s_detail_height + 1);
}
if (ImGui::IsItemHovered()) material::Tooltip("Next block");
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("explorer_next_block"));
ImGui::PopID();
ImGui::PopFont();
}
@@ -1077,7 +1081,6 @@ static void renderBlockDetailModal(App* app) {
ImGui::EndDisabled();
}
ImGui::Separator();
ImGui::Spacing();
// Show loading state inside the modal when navigating between blocks
@@ -1253,7 +1256,7 @@ static void renderBlockDetailModal(App* app) {
// Full TxID with copy
ImGui::PushFont(capFont);
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "TxID:");
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "%s", TR("explorer_txid"));
ImGui::PopFont();
ImGui::PushFont(body2);
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(linkCol));
@@ -1282,7 +1285,7 @@ static void renderBlockDetailModal(App* app) {
for (const auto& vout : s_tx_detail["vout"]) {
double val = vout.value("value", 0.0);
int n = vout.value("n", 0);
std::string addr = "shielded";
std::string addr = TR("shielded");
if (vout.contains("scriptPubKey") && vout["scriptPubKey"].contains("addresses") &&
vout["scriptPubKey"]["addresses"].is_array() && !vout["scriptPubKey"]["addresses"].empty()) {
addr = vout["scriptPubKey"]["addresses"][0].get<std::string>();

View File

@@ -58,6 +58,16 @@ bool KeyExportDialog::isOpen()
return s_open;
}
void KeyExportDialog::hide()
{
s_open = false;
s_fetching = false;
s_key.clear();
s_show_key = false;
s_error.clear();
releaseQr();
}
void KeyExportDialog::render(App* app)
{
if (!s_open) return;
@@ -66,7 +76,6 @@ void KeyExportDialog::render(App* app)
TR("export_private_key") : TR("export_viewing_key");
auto& S = schema::UI();
auto warningBox = S.drawElement("dialogs.key-export", "warning-box");
auto addrInput = S.input("dialogs.key-export", "address-input");
auto revealBtn = S.button("dialogs.key-export", "reveal-button");
auto keyDisplay = S.drawElement("dialogs.key-export", "key-display");
@@ -79,54 +88,14 @@ void KeyExportDialog::render(App* app)
// Modal scales to 85% of the window width. BeginOverlayDialog multiplies the width by
// Layout::dpiScale(); divide it out here so the final card is exactly 85% at any font scale.
const float cardW = (0.85f * ImGui::GetMainViewport()->Size.x) / Layout::dpiScale();
if (material::BeginOverlayDialog(title, &s_open, cardW, 0.94f)) {
ImGui::Spacing();
material::OverlayDialogSpec ov;
ov.title = title; ov.p_open = &s_open;
ov.style = material::OverlayStyle::BlurFloat;
ov.cardWidth = cardW; ov.idSuffix = "keyexport";
if (material::BeginOverlayDialog(ov)) {
material::DialogWarningHeader((s_key_type == KeyType::Private)
? TR("key_export_private_warning") : TR("key_export_viewing_warning"));
// Warning section with colored background
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.6f, 0.2f, 0.2f, 0.3f));
// Auto-size the warning box to its (wrapping, font-scaled) text so it never clips it.
(void)warningBox;
ImGui::BeginChild("WarningBox", ImVec2(-1, 0),
ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders);
// Heading — warning icon (drawn with the icon font, not the text font, so it isn't tofu) +
// larger h6 text, centered, for emphasis.
{
ImFont* iconF = material::Type().iconMed();
ImFont* textF = material::Type().h6();
const char* icon = ICON_MD_WARNING;
const char* txt = TR("warning_upper");
ImGui::PushFont(iconF); const float iw = ImGui::CalcTextSize(icon).x; ImGui::PopFont();
ImGui::PushFont(textF); const float tw = ImGui::CalcTextSize(txt).x; ImGui::PopFont();
const float sp = ImGui::GetStyle().ItemSpacing.x;
const float wa = ImGui::GetContentRegionAvail().x;
const float total = iw + sp + tw;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (wa > total ? (wa - total) * 0.5f : 0.0f));
const ImVec4 red(1.0f, 0.42f, 0.42f, 1.0f);
ImGui::PushFont(iconF); ImGui::TextColored(red, "%s", icon); ImGui::PopFont();
ImGui::SameLine(0, sp);
ImGui::PushFont(textF); ImGui::TextColored(red, "%s", txt); ImGui::PopFont();
}
ImGui::Spacing();
// Body — centered wrapped text.
{
const char* body = (s_key_type == KeyType::Private)
? TR("key_export_private_warning") : TR("key_export_viewing_warning");
ImDrawList* wdl = ImGui::GetWindowDrawList();
ImFont* wf = ImGui::GetFont();
const float wfs = ImGui::GetFontSize();
const float wa = ImGui::GetContentRegionAvail().x;
const ImVec2 wp = ImGui::GetCursorScreenPos();
const float bh = widgets::DrawCenteredWrapped(wdl, wf, wfs, wp, wa, material::OnSurface(), body);
ImGui::Dummy(ImVec2(wa, bh));
}
ImGui::EndChild();
ImGui::PopStyleColor();
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Address — click-to-copy field (chunked, width-clamped, centered).
@@ -145,7 +114,7 @@ void KeyExportDialog::render(App* app)
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), TR("error_format"), s_error.c_str());
// Not a dead-end: clearing the error falls back through to the Reveal button next frame.
ImGui::Spacing();
if (material::StyledButton(TR("retry"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
if (material::TactileButton(TR("retry"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
s_error.clear();
}
} else if (s_key.empty() && s_address.length() < 26) {
@@ -154,7 +123,7 @@ void KeyExportDialog::render(App* app)
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s", TR("invalid_address"));
} else if (s_key.empty()) {
// Show button to fetch key
if (material::StyledButton(TR("key_export_reveal"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
if (material::TactileButton(TR("key_export_reveal"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
s_fetching = true;
// Check if z-address or t-address
@@ -292,7 +261,7 @@ void KeyExportDialog::render(App* app)
app->copySecretToClipboard(s_key); // auto-clearing clipboard
}
ImGui::Spacing();
if (material::StyledButton(s_show_key ? TR("hide") : TR("show"), ImVec2(0, 0),
if (material::TactileButton(s_show_key ? TR("hide") : TR("show"), ImVec2(0, 0),
material::Type().subtitle1())) {
s_show_key = !s_show_key;
}
@@ -328,8 +297,6 @@ void KeyExportDialog::render(App* app)
}
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Close button
@@ -337,7 +304,7 @@ void KeyExportDialog::render(App* app)
float avail_width = ImGui::GetContentRegionAvail().x;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (avail_width - button_width) / 2.0f);
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
if (material::TactileButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
s_open = false;
// Clear sensitive data
s_key.clear();

View File

@@ -44,6 +44,11 @@ public:
*/
static bool isOpen();
/**
* @brief Close the dialog and clear the revealed key (used by the UI sweep teardown)
*/
static void hide();
private:
static bool s_open;
static bool s_fetching;

View File

@@ -67,7 +67,11 @@ void QRPopupDialog::render(App* app)
// Match the key-export modal: 85% of the window width (divide out the dpiScale that
// BeginOverlayDialog re-applies, so the final card is exactly 85% at any font scale).
const float cardW = (0.85f * ImGui::GetMainViewport()->Size.x) / Layout::dpiScale();
if (material::BeginOverlayDialog(TR("qr_title"), &s_open, cardW, 0.94f)) {
material::OverlayDialogSpec ov;
ov.title = TR("qr_title"); ov.p_open = &s_open;
ov.style = material::OverlayStyle::BlurFloat;
ov.cardWidth = cardW; ov.idSuffix = "qrpopup";
if (material::BeginOverlayDialog(ov)) {
// Label if present
if (!s_label.empty()) {
@@ -120,11 +124,11 @@ void QRPopupDialog::render(App* app)
const float total_width = w_copy + w_close + ImGui::GetStyle().ItemSpacing.x;
ImGui::SetCursorPosX((window_width - total_width) / 2.0f);
if (material::StyledButton(TR("copy_address"), ImVec2(w_copy, 0), btnFont)) {
if (material::TactileButton(TR("copy_address"), ImVec2(w_copy, 0), btnFont)) {
ImGui::SetClipboardText(s_address.c_str());
}
ImGui::SameLine();
if (material::StyledButton(TR("close"), ImVec2(w_close, 0), btnFont)) {
if (material::TactileButton(TR("close"), ImVec2(w_close, 0), btnFont)) {
close();
}
material::EndOverlayDialog();

View File

@@ -59,6 +59,15 @@ void RequestPaymentDialog::show(const std::string& address)
}
}
void RequestPaymentDialog::hide()
{
s_open = false;
if (s_qr_texture != 0) {
FreeQRTexture(s_qr_texture);
s_qr_texture = 0;
}
}
void RequestPaymentDialog::render(App* app)
{
if (!s_open) return;
@@ -76,13 +85,15 @@ void RequestPaymentDialog::render(App* app)
auto qr = S.drawElement("dialogs.request-payment", "qr-code");
auto actionBtn = S.button("dialogs.request-payment", "action-button");
if (material::BeginOverlayDialog(TR("request_title"), &s_open, win.width, 0.94f)) {
material::OverlayDialogSpec ov;
ov.title = TR("request_title"); ov.p_open = &s_open;
ov.style = material::OverlayStyle::BlurFloat;
ov.cardWidth = win.width; ov.idSuffix = "requestpay";
if (material::BeginOverlayDialog(ov)) {
const auto& state = app->getWalletState();
ImGui::TextWrapped("%s", TR("request_description"));
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Address selection
@@ -196,14 +207,14 @@ void RequestPaymentDialog::render(App* app)
ImGui::Spacing();
// Copy button
if (material::StyledButton(TR("request_copy_uri"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
if (material::TactileButton(TR("request_copy_uri"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
ImGui::SetClipboardText(s_payment_uri.c_str());
Notifications::instance().success(TR("request_uri_copied"));
}
ImGui::SameLine();
if (material::StyledButton(TR("copy_address"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
if (material::TactileButton(TR("copy_address"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
ImGui::SetClipboardText(s_address);
Notifications::instance().success(TR("address_copied"));
}
@@ -212,7 +223,7 @@ void RequestPaymentDialog::render(App* app)
ImGui::Spacing();
// Close button
if (material::StyledButton(TR("close"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
if (material::TactileButton(TR("close"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
s_open = false;
}
material::EndOverlayDialog();

View File

@@ -26,6 +26,11 @@ public:
* @brief Render the dialog (call each frame)
*/
static void render(App* app);
/**
* @brief Close the dialog + free its QR texture (used by the UI sweep teardown)
*/
static void hide();
};
} // namespace ui

View File

@@ -696,7 +696,11 @@ void RenderSendConfirmPopup(App* app) {
float popupAvailW = ImGui::GetMainViewport()->Size.x * S.drawElement("tabs.send", "confirm-popup-width-ratio").size;
float popupW = std::min(schema::UI().drawElement("tabs.send", "confirm-popup-max-width").size, popupAvailW);
float popVs = Layout::vScale();
if (material::BeginOverlayDialog(TR("confirm_send"), nullptr, popupW, 0.94f)) {
material::OverlayDialogSpec ov;
ov.title = TR("confirm_send"); ov.p_open = nullptr;
ov.style = material::OverlayStyle::BlurFloat;
ov.cardWidth = popupW; ov.idSuffix = "confirmsend";
if (material::BeginOverlayDialog(ov)) {
if (ImGui::IsKeyPressed(ImGuiKey_Escape) && !s_sending) {
s_show_confirm = false;
@@ -826,7 +830,7 @@ void RenderSendConfirmPopup(App* app) {
double liveAvail = GetAvailableBalance(app);
if (!app->isConnected() || liveState.sync.syncing || s_amount <= 0 ||
(s_amount + s_fee) > liveAvail) {
s_tx_status = "Cannot send now — check connection, sync, and available balance.";
s_tx_status = TR("send_cannot_send_now");
s_status_success = false;
s_status_timestamp = ImGui::GetTime();
}

View File

@@ -61,6 +61,14 @@ void ShieldDialog::showMerge()
show(Mode::MergeToAddress);
}
void ShieldDialog::hide()
{
s_open = false;
s_operation_pending = false;
s_status_message.clear();
s_operation_id.clear();
}
void ShieldDialog::render(App* app)
{
if (!s_open) return;
@@ -79,18 +87,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 +141,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 +166,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 +187,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 +267,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 +279,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 {

View File

@@ -41,6 +41,11 @@ public:
* @brief Render the dialog (call each frame)
*/
static void render(App* app);
/**
* @brief Close the dialog (used by the UI sweep teardown)
*/
static void hide();
};
} // namespace ui

View File

@@ -42,6 +42,11 @@ bool ValidateAddressDialog::isOpen()
return s_open;
}
void ValidateAddressDialog::hide()
{
s_open = false;
}
void ValidateAddressDialog::render(App* app)
{
if (!s_open) return;
@@ -53,11 +58,13 @@ void ValidateAddressDialog::render(App* app)
auto lbl = S.label("dialogs.validate-address", "label");
auto closeBtn = S.button("dialogs.validate-address", "close-button");
if (material::BeginOverlayDialog(TR("validate_title"), &s_open, win.width, 0.94f)) {
material::OverlayDialogSpec ov;
ov.title = TR("validate_title"); ov.p_open = &s_open;
ov.style = material::OverlayStyle::BlurFloat;
ov.cardWidth = win.width; ov.idSuffix = "validateaddr";
if (material::BeginOverlayDialog(ov)) {
ImGui::TextWrapped("%s", TR("validate_description"));
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Address input
@@ -74,7 +81,7 @@ void ValidateAddressDialog::render(App* app)
ImGui::BeginDisabled();
}
if (material::StyledButton(TR("validate_btn"), ImVec2(valBtn.width, 0), S.resolveFont(valBtn.font)) || (enter_pressed && can_validate)) {
if (material::TactileButton(TR("validate_btn"), ImVec2(valBtn.width, 0), S.resolveFont(valBtn.font)) || (enter_pressed && can_validate)) {
s_validating = true;
s_validated = false;
s_error_message.clear();
@@ -150,7 +157,7 @@ void ValidateAddressDialog::render(App* app)
ImGui::SameLine();
if (material::StyledButton(TR("paste"), ImVec2(pasteBtn.width, 0), S.resolveFont(pasteBtn.font))) {
if (material::TactileButton(TR("paste"), ImVec2(pasteBtn.width, 0), S.resolveFont(pasteBtn.font))) {
const char* clipboard = ImGui::GetClipboardText();
if (clipboard) {
strncpy(s_address_input, clipboard, sizeof(s_address_input) - 1);
@@ -164,8 +171,6 @@ void ValidateAddressDialog::render(App* app)
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "%s", TR("validating"));
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Results
@@ -174,7 +179,7 @@ void ValidateAddressDialog::render(App* app)
ImGui::Spacing();
if (!s_error_message.empty()) {
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Error: %s", s_error_message.c_str());
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), TR("error_format"), s_error_message.c_str());
} else {
// Valid/Invalid indicator
ImGui::Text("%s", TR("validate_status"));
@@ -210,7 +215,7 @@ void ValidateAddressDialog::render(App* app)
// Close button at bottom
float button_width = closeBtn.width;
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - button_width) / 2.0f);
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
if (material::TactileButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
s_open = false;
}
material::EndOverlayDialog();

View File

@@ -33,6 +33,11 @@ public:
*/
static bool isOpen();
/**
* @brief Close the dialog (used by the UI sweep teardown)
*/
static void hide();
private:
static bool s_open;
static bool s_validated;

View File

@@ -1837,6 +1837,7 @@ void I18n::loadBuiltinEnglish()
strings_["send_total"] = "Total";
strings_["send_tx_failed"] = "Transaction failed";
strings_["send_tx_sent"] = "Transaction sent!";
strings_["send_cannot_send_now"] = "Cannot send now — check connection, sync, and available balance.";
strings_["send_tx_success"] = "Transaction sent successfully!";
strings_["send_status_unconfirmed"] = "Transaction status could not be confirmed";
strings_["send_err_needs_rescan"] = "Your wallet's shielded note data is out of date with the blockchain (this happens after a bootstrap or reindex). Run a full rescan via Settings -> Rescan Blockchain and let it finish completely, then try sending again.";
@@ -1857,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):";
@@ -1937,6 +1939,21 @@ void I18n::loadBuiltinEnglish()
strings_["explorer_block_merkle"] = "Merkle Root";
strings_["explorer_tx_outputs"] = "Outputs";
strings_["explorer_tx_size"] = "Size";
strings_["explorer_txid"] = "TxID:";
strings_["explorer_prev_block"] = "Previous block";
strings_["explorer_next_block"] = "Next block";
// --- Antivirus (Windows Defender) help dialog (Windows-only) ---
strings_["av_title"] = "Windows Defender Blocked Miner";
strings_["av_intro"] = "Mining software is often flagged as potentially unwanted. Follow these steps to enable pool mining:";
strings_["av_step1"] = "Step 1: Add Exclusion";
strings_["av_step1_b1"] = "Open Windows Security > Virus & threat protection";
strings_["av_step1_b2"] = "Click Manage settings > Exclusions > Add or remove";
strings_["av_step1_b3"] = "Add folder: %APPDATA%\\ObsidianDragon\\";
strings_["av_step2"] = "Step 2: Restore from Quarantine (if needed)";
strings_["av_step2_b1"] = "Windows Security > Protection history";
strings_["av_step2_b2"] = "Find xmrig.exe and click Restore";
strings_["av_step3"] = "Step 3: Restart wallet and try again";
strings_["av_open_security"] = "Open Windows Security";
strings_["explorer_invalid_query"] = "Enter a block height or 64-character hash";
strings_["explorer_invalid_response"] = "Invalid response from daemon";
strings_["explorer_hash_not_found"] = "No block or transaction found for this hash";