feat(settings): migrate Export All Keys modal to the reference design

Phase 1. DialogWarningHeader care header (was a hand-rolled red icon+text);
centered DialogActionFooter (Export + Close) replacing the StyledButtons + two
separators; on full success the saved path shows in a copy field with a green
check, partial/error render inline; export progress uses LoadingDots. Full i18n:
the previously-hardcoded notification + status strings now use TR (8 new
export_keys_* keys in en + 8 langs). Added hide() + a modal-export-all-keys sweep
surface. Also fixes a latent Begin/EndOverlayDialog imbalance on the 0-address
early return. No change to the export RPC logic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-13 21:43:53 -05:00
parent 22638b094c
commit 822891d4ef
13 changed files with 150 additions and 81 deletions

Binary file not shown.

View File

@@ -373,10 +373,18 @@
"export_keys_danger": "ACHTUNG: Dies exportiert ALLE privaten Schlüssel aus Ihrer Wallet! Jeder mit Zugriff auf diese Datei kann Ihre Gelder stehlen. Sicher aufbewahren und nach Gebrauch löschen.", "export_keys_danger": "ACHTUNG: Dies exportiert ALLE privaten Schlüssel aus Ihrer Wallet! Jeder mit Zugriff auf diese Datei kann Ihre Gelder stehlen. Sicher aufbewahren und nach Gebrauch löschen.",
"export_keys_include_t": "T-Adressen einschließen (transparent)", "export_keys_include_t": "T-Adressen einschließen (transparent)",
"export_keys_include_z": "Z-Adressen einschließen (abgeschirmt)", "export_keys_include_z": "Z-Adressen einschließen (abgeschirmt)",
"export_keys_none_addrs": "Keine Adressen zum Exportieren",
"export_keys_none_result": "Keine Schlüssel exportiert (0 von %d) — entsperren Sie die Wallet und versuchen Sie es erneut.",
"export_keys_none_toast": "Es konnten keine Schlüssel exportiert werden — ist die Wallet entsperrt?",
"export_keys_not_connected": "Nicht mit dem Daemon verbunden",
"export_keys_options": "Export-Optionen:", "export_keys_options": "Export-Optionen:",
"export_keys_partial": "%d von %d Schlüsseln exportiert — unvollständig (einige hatten keinen Ausgabeschlüssel oder die Wallet ist gesperrt).",
"export_keys_partial_toast": "Teilexport: %d von %d Schlüsseln",
"export_keys_progress": "Exportiere %d/%d...", "export_keys_progress": "Exportiere %d/%d...",
"export_keys_select_type": "Wählen Sie mindestens einen Adresstyp",
"export_keys_success": "Schlüssel erfolgreich exportiert", "export_keys_success": "Schlüssel erfolgreich exportiert",
"export_keys_title": "Alle privaten Schlüssel exportieren", "export_keys_title": "Alle privaten Schlüssel exportieren",
"export_keys_write_fail": "Schlüsseldatei konnte nicht geschrieben werden.",
"export_private_key": "Privaten Schlüssel exportieren", "export_private_key": "Privaten Schlüssel exportieren",
"export_tx_count": "%zu Transaktionen als CSV exportieren.", "export_tx_count": "%zu Transaktionen als CSV exportieren.",
"export_tx_file_fail": "CSV-Datei konnte nicht erstellt werden", "export_tx_file_fail": "CSV-Datei konnte nicht erstellt werden",

View File

@@ -373,10 +373,18 @@
"export_keys_danger": "PELIGRO: ¡Esto exportará TODAS las claves privadas de tu cartera! Cualquiera con acceso a este archivo puede robar tus fondos. Guárdalo de forma segura y elimínalo después de usar.", "export_keys_danger": "PELIGRO: ¡Esto exportará TODAS las claves privadas de tu cartera! Cualquiera con acceso a este archivo puede robar tus fondos. Guárdalo de forma segura y elimínalo después de usar.",
"export_keys_include_t": "Incluir direcciones T (transparentes)", "export_keys_include_t": "Incluir direcciones T (transparentes)",
"export_keys_include_z": "Incluir direcciones Z (protegidas)", "export_keys_include_z": "Incluir direcciones Z (protegidas)",
"export_keys_none_addrs": "No hay direcciones para exportar",
"export_keys_none_result": "No se exportaron claves (0 de %d): desbloquea el monedero e inténtalo de nuevo.",
"export_keys_none_toast": "No se pudieron exportar claves: ¿está desbloqueado el monedero?",
"export_keys_not_connected": "Sin conexión con el daemon",
"export_keys_options": "Opciones de exportación:", "export_keys_options": "Opciones de exportación:",
"export_keys_partial": "Se exportaron %d de %d claves: incompleto (algunas no tenían clave de gasto o el monedero está bloqueado).",
"export_keys_partial_toast": "Exportación parcial: %d de %d claves",
"export_keys_progress": "Exportando %d/%d...", "export_keys_progress": "Exportando %d/%d...",
"export_keys_select_type": "Selecciona al menos un tipo de dirección",
"export_keys_success": "Claves exportadas exitosamente", "export_keys_success": "Claves exportadas exitosamente",
"export_keys_title": "Exportar Todas las Claves Privadas", "export_keys_title": "Exportar Todas las Claves Privadas",
"export_keys_write_fail": "No se pudo escribir el archivo de claves.",
"export_private_key": "Exportar Clave Privada", "export_private_key": "Exportar Clave Privada",
"export_tx_count": "Exportar %zu transacciones a archivo CSV.", "export_tx_count": "Exportar %zu transacciones a archivo CSV.",
"export_tx_file_fail": "Error al crear archivo CSV", "export_tx_file_fail": "Error al crear archivo CSV",

View File

@@ -373,10 +373,18 @@
"export_keys_danger": "DANGER : Ceci exportera TOUTES les clés privées de votre portefeuille ! Toute personne ayant accès à ce fichier peut voler vos fonds. Conservez-le en sécurité et supprimez-le après utilisation.", "export_keys_danger": "DANGER : Ceci exportera TOUTES les clés privées de votre portefeuille ! Toute personne ayant accès à ce fichier peut voler vos fonds. Conservez-le en sécurité et supprimez-le après utilisation.",
"export_keys_include_t": "Inclure les adresses T (transparentes)", "export_keys_include_t": "Inclure les adresses T (transparentes)",
"export_keys_include_z": "Inclure les adresses Z (blindées)", "export_keys_include_z": "Inclure les adresses Z (blindées)",
"export_keys_none_addrs": "Aucune adresse à exporter",
"export_keys_none_result": "Aucune clé exportée (0 sur %d) — déverrouillez le portefeuille et réessayez.",
"export_keys_none_toast": "Aucune clé n'a pu être exportée — le portefeuille est-il déverrouillé ?",
"export_keys_not_connected": "Non connecté au daemon",
"export_keys_options": "Options d'exportation :", "export_keys_options": "Options d'exportation :",
"export_keys_partial": "%d clés sur %d exportées — incomplet (certaines sans clé de dépense, ou le portefeuille est verrouillé).",
"export_keys_partial_toast": "Export partiel : %d clés sur %d",
"export_keys_progress": "Exportation %d/%d...", "export_keys_progress": "Exportation %d/%d...",
"export_keys_select_type": "Sélectionnez au moins un type d'adresse",
"export_keys_success": "Clés exportées avec succès", "export_keys_success": "Clés exportées avec succès",
"export_keys_title": "Exporter toutes les clés privées", "export_keys_title": "Exporter toutes les clés privées",
"export_keys_write_fail": "Échec de l'écriture du fichier de clés.",
"export_private_key": "Exporter la clé privée", "export_private_key": "Exporter la clé privée",
"export_tx_count": "Exporter %zu transactions en fichier CSV.", "export_tx_count": "Exporter %zu transactions en fichier CSV.",
"export_tx_file_fail": "Impossible de créer le fichier CSV", "export_tx_file_fail": "Impossible de créer le fichier CSV",

View File

@@ -373,10 +373,18 @@
"export_keys_danger": "危険:ウォレットからすべての秘密鍵がエクスポートされます!このファイルにアクセスできる人は誰でもあなたの資金を盗めます。安全に保管し、使用後は削除してください。", "export_keys_danger": "危険:ウォレットからすべての秘密鍵がエクスポートされます!このファイルにアクセスできる人は誰でもあなたの資金を盗めます。安全に保管し、使用後は削除してください。",
"export_keys_include_t": "Tアドレスを含める透明", "export_keys_include_t": "Tアドレスを含める透明",
"export_keys_include_z": "Zアドレスを含めるシールド", "export_keys_include_z": "Zアドレスを含めるシールド",
"export_keys_none_addrs": "エクスポートするアドレスがありません",
"export_keys_none_result": "鍵をエクスポートできませんでした(%d 件中 0 件)。ウォレットのロックを解除して再試行してください。",
"export_keys_none_toast": "鍵をエクスポートできませんでした。ウォレットはロック解除されていますか?",
"export_keys_not_connected": "デーモンに接続されていません",
"export_keys_options": "エクスポートオプション:", "export_keys_options": "エクスポートオプション:",
"export_keys_partial": "%d 件中 %d 件の鍵をエクスポート — 不完全(支払い鍵がない、またはウォレットがロックされています)。",
"export_keys_partial_toast": "部分エクスポート: %d 件中 %d 件",
"export_keys_progress": "エクスポート中 %d/%d...", "export_keys_progress": "エクスポート中 %d/%d...",
"export_keys_select_type": "アドレスの種類を少なくとも1つ選択してください",
"export_keys_success": "鍵のエクスポートに成功しました", "export_keys_success": "鍵のエクスポートに成功しました",
"export_keys_title": "すべての秘密鍵をエクスポート", "export_keys_title": "すべての秘密鍵をエクスポート",
"export_keys_write_fail": "鍵ファイルの書き込みに失敗しました。",
"export_private_key": "秘密鍵をエクスポート", "export_private_key": "秘密鍵をエクスポート",
"export_tx_count": "%zu件の取引をCSVファイルにエクスポート。", "export_tx_count": "%zu件の取引をCSVファイルにエクスポート。",
"export_tx_file_fail": "CSVファイルの作成に失敗しました", "export_tx_file_fail": "CSVファイルの作成に失敗しました",

View File

@@ -373,10 +373,18 @@
"export_keys_danger": "위험: 지갑의 모든 개인 키가 내보내집니다! 이 파일에 접근할 수 있는 사람은 누구나 자금을 훔칠 수 있습니다. 안전하게 보관하고 사용 후 삭제하세요.", "export_keys_danger": "위험: 지갑의 모든 개인 키가 내보내집니다! 이 파일에 접근할 수 있는 사람은 누구나 자금을 훔칠 수 있습니다. 안전하게 보관하고 사용 후 삭제하세요.",
"export_keys_include_t": "T 주소 포함 (투명)", "export_keys_include_t": "T 주소 포함 (투명)",
"export_keys_include_z": "Z 주소 포함 (차폐)", "export_keys_include_z": "Z 주소 포함 (차폐)",
"export_keys_none_addrs": "내보낼 주소가 없습니다",
"export_keys_none_result": "키를 내보내지 못했습니다(%d개 중 0개). 지갑 잠금을 해제하고 다시 시도하세요.",
"export_keys_none_toast": "키를 내보낼 수 없습니다. 지갑이 잠금 해제되어 있나요?",
"export_keys_not_connected": "데몬에 연결되어 있지 않습니다",
"export_keys_options": "내보내기 옵션:", "export_keys_options": "내보내기 옵션:",
"export_keys_partial": "%d개 중 %d개 키 내보냄 — 불완전(일부는 지출 키가 없거나 지갑이 잠겨 있음).",
"export_keys_partial_toast": "부분 내보내기: %d개 중 %d개 키",
"export_keys_progress": "내보내는 중 %d/%d...", "export_keys_progress": "내보내는 중 %d/%d...",
"export_keys_select_type": "주소 유형을 하나 이상 선택하세요",
"export_keys_success": "키 내보내기 성공", "export_keys_success": "키 내보내기 성공",
"export_keys_title": "모든 개인 키 내보내기", "export_keys_title": "모든 개인 키 내보내기",
"export_keys_write_fail": "키 파일을 쓰지 못했습니다.",
"export_private_key": "개인 키 내보내기", "export_private_key": "개인 키 내보내기",
"export_tx_count": "%zu건의 거래를 CSV 파일로 내보냈습니다.", "export_tx_count": "%zu건의 거래를 CSV 파일로 내보냈습니다.",
"export_tx_file_fail": "CSV 파일 생성 실패", "export_tx_file_fail": "CSV 파일 생성 실패",

View File

@@ -373,10 +373,18 @@
"export_keys_danger": "PERIGO: Isto exportará TODAS as chaves privadas da sua carteira! Qualquer pessoa com acesso a este arquivo pode roubar seus fundos. Guarde com segurança e exclua após o uso.", "export_keys_danger": "PERIGO: Isto exportará TODAS as chaves privadas da sua carteira! Qualquer pessoa com acesso a este arquivo pode roubar seus fundos. Guarde com segurança e exclua após o uso.",
"export_keys_include_t": "Incluir endereços T (transparentes)", "export_keys_include_t": "Incluir endereços T (transparentes)",
"export_keys_include_z": "Incluir endereços Z (blindados)", "export_keys_include_z": "Incluir endereços Z (blindados)",
"export_keys_none_addrs": "Nenhum endereço para exportar",
"export_keys_none_result": "Nenhuma chave exportada (0 de %d) — desbloqueie a carteira e tente novamente.",
"export_keys_none_toast": "Não foi possível exportar chaves — a carteira está desbloqueada?",
"export_keys_not_connected": "Não conectado ao daemon",
"export_keys_options": "Opções de exportação:", "export_keys_options": "Opções de exportação:",
"export_keys_partial": "Exportadas %d de %d chaves — incompleto (algumas sem chave de gasto, ou a carteira está bloqueada).",
"export_keys_partial_toast": "Exportação parcial: %d de %d chaves",
"export_keys_progress": "Exportando %d/%d...", "export_keys_progress": "Exportando %d/%d...",
"export_keys_select_type": "Selecione ao menos um tipo de endereço",
"export_keys_success": "Chaves exportadas com sucesso", "export_keys_success": "Chaves exportadas com sucesso",
"export_keys_title": "Exportar Todas as Chaves Privadas", "export_keys_title": "Exportar Todas as Chaves Privadas",
"export_keys_write_fail": "Falha ao gravar o arquivo de chaves.",
"export_private_key": "Exportar Chave Privada", "export_private_key": "Exportar Chave Privada",
"export_tx_count": "Exportar %zu transações para arquivo CSV.", "export_tx_count": "Exportar %zu transações para arquivo CSV.",
"export_tx_file_fail": "Falha ao criar arquivo CSV", "export_tx_file_fail": "Falha ao criar arquivo CSV",

View File

@@ -373,10 +373,18 @@
"export_keys_danger": "ОПАСНОСТЬ: Будут экспортированы ВСЕ приватные ключи из вашего кошелька! Любой, кто получит доступ к этому файлу, сможет украсть ваши средства. Храните его в безопасности и удалите после использования.", "export_keys_danger": "ОПАСНОСТЬ: Будут экспортированы ВСЕ приватные ключи из вашего кошелька! Любой, кто получит доступ к этому файлу, сможет украсть ваши средства. Храните его в безопасности и удалите после использования.",
"export_keys_include_t": "Включить T-адреса (прозрачные)", "export_keys_include_t": "Включить T-адреса (прозрачные)",
"export_keys_include_z": "Включить Z-адреса (экранированные)", "export_keys_include_z": "Включить Z-адреса (экранированные)",
"export_keys_none_addrs": "Нет адресов для экспорта",
"export_keys_none_result": "Ключи не экспортированы (0 из %d) — разблокируйте кошелёк и повторите попытку.",
"export_keys_none_toast": "Не удалось экспортировать ключи — кошелёк разблокирован?",
"export_keys_not_connected": "Нет подключения к демону",
"export_keys_options": "Параметры экспорта:", "export_keys_options": "Параметры экспорта:",
"export_keys_partial": "Экспортировано %d из %d ключей — неполно (у некоторых нет ключа расходования или кошелёк заблокирован).",
"export_keys_partial_toast": "Частичный экспорт: %d из %d ключей",
"export_keys_progress": "Экспорт %d/%d...", "export_keys_progress": "Экспорт %d/%d...",
"export_keys_select_type": "Выберите хотя бы один тип адреса",
"export_keys_success": "Ключи успешно экспортированы", "export_keys_success": "Ключи успешно экспортированы",
"export_keys_title": "Экспорт всех приватных ключей", "export_keys_title": "Экспорт всех приватных ключей",
"export_keys_write_fail": "Не удалось записать файл ключей.",
"export_private_key": "Экспорт приватного ключа", "export_private_key": "Экспорт приватного ключа",
"export_tx_count": "Экспортировать %zu транзакций в файл CSV.", "export_tx_count": "Экспортировать %zu транзакций в файл CSV.",
"export_tx_file_fail": "Не удалось создать файл CSV", "export_tx_file_fail": "Не удалось создать файл CSV",

View File

@@ -373,10 +373,18 @@
"export_keys_danger": "危险:这将导出您钱包中的所有私钥!任何获得此文件的人都可以窃取您的资金。请安全保管并在使用后删除。", "export_keys_danger": "危险:这将导出您钱包中的所有私钥!任何获得此文件的人都可以窃取您的资金。请安全保管并在使用后删除。",
"export_keys_include_t": "包含 T 地址(透明)", "export_keys_include_t": "包含 T 地址(透明)",
"export_keys_include_z": "包含 Z 地址(屏蔽)", "export_keys_include_z": "包含 Z 地址(屏蔽)",
"export_keys_none_addrs": "没有可导出的地址",
"export_keys_none_result": "未导出任何密钥(%d 个中 0 个)——请解锁钱包后重试。",
"export_keys_none_toast": "无法导出密钥——钱包已解锁吗?",
"export_keys_not_connected": "未连接到守护进程",
"export_keys_options": "导出选项:", "export_keys_options": "导出选项:",
"export_keys_partial": "已导出 %d/%d 个密钥——不完整(部分没有花费密钥,或钱包已锁定)。",
"export_keys_partial_toast": "部分导出:%d/%d 个密钥",
"export_keys_progress": "正在导出 %d/%d...", "export_keys_progress": "正在导出 %d/%d...",
"export_keys_select_type": "请至少选择一种地址类型",
"export_keys_success": "密钥导出成功", "export_keys_success": "密钥导出成功",
"export_keys_title": "导出所有私钥", "export_keys_title": "导出所有私钥",
"export_keys_write_fail": "写入密钥文件失败。",
"export_private_key": "导出私钥", "export_private_key": "导出私钥",
"export_tx_count": "导出 %zu 笔交易到 CSV 文件。", "export_tx_count": "导出 %zu 笔交易到 CSV 文件。",
"export_tx_file_fail": "无法创建 CSV 文件", "export_tx_file_fail": "无法创建 CSV 文件",

View File

@@ -20,6 +20,7 @@
#include "ui/windows/send_tab.h" #include "ui/windows/send_tab.h"
#include "ui/windows/wallets_dialog.h" #include "ui/windows/wallets_dialog.h"
#include "ui/windows/export_transactions_dialog.h" #include "ui/windows/export_transactions_dialog.h"
#include "ui/windows/export_all_keys_dialog.h"
#include "ui/windows/daemon_download_dialog.h" #include "ui/windows/daemon_download_dialog.h"
#include "ui/windows/xmrig_download_dialog.h" #include "ui/windows/xmrig_download_dialog.h"
#include "ui/pages/settings_page.h" #include "ui/pages/settings_page.h"
@@ -269,6 +270,8 @@ void App::buildSweepCatalog()
[](App& a) { a.show_export_key_ = true; }, [](App& a) { a.show_export_key_ = false; }); [](App& a) { a.show_export_key_ = true; }, [](App& a) { a.show_export_key_ = false; });
add("modal-export-transactions", ui::NavPage::Settings, add("modal-export-transactions", ui::NavPage::Settings,
[](App&) { ui::ExportTransactionsDialog::show(); }, [](App&) { ui::ExportTransactionsDialog::hide(); }); [](App&) { ui::ExportTransactionsDialog::show(); }, [](App&) { ui::ExportTransactionsDialog::hide(); });
add("modal-export-all-keys", ui::NavPage::Settings,
[](App&) { ui::ExportAllKeysDialog::show(); }, [](App&) { ui::ExportAllKeysDialog::hide(); });
add("modal-backup", ui::NavPage::Overview, add("modal-backup", ui::NavPage::Overview,
[](App& a) { a.show_backup_ = true; }, [](App& a) { a.show_backup_ = false; a.backup_status_.clear(); }); [](App& a) { a.show_backup_ = true; }, [](App& a) { a.show_backup_ = false; a.backup_status_.clear(); });
add("modal-about", ui::NavPage::Overview, add("modal-about", ui::NavPage::Overview,

View File

@@ -12,6 +12,7 @@
#include "../schema/ui_schema.h" #include "../schema/ui_schema.h"
#include "../material/draw_helpers.h" #include "../material/draw_helpers.h"
#include "../material/type.h" #include "../material/type.h"
#include "../widgets/copy_field.h"
#include "../theme.h" #include "../theme.h"
#include "../../embedded/IconsMaterialDesign.h" #include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h" #include "imgui.h"
@@ -29,7 +30,8 @@ using json = nlohmann::json;
// Static state // Static state
static bool s_open = false; static bool s_open = false;
static bool s_exporting = false; static bool s_exporting = false;
static std::string s_status; static std::string s_status; // error / partial message (non-empty => red text)
static std::string s_saved_path; // full path on full success (=> green check + copy field)
static std::string s_exported_keys; static std::string s_exported_keys;
static int s_total_addresses = 0; static int s_total_addresses = 0;
static int s_exported_count = 0; static int s_exported_count = 0;
@@ -42,6 +44,7 @@ void ExportAllKeysDialog::show()
s_open = true; s_open = true;
s_exporting = false; s_exporting = false;
s_status.clear(); s_status.clear();
s_saved_path.clear();
s_exported_keys.clear(); s_exported_keys.clear();
s_total_addresses = 0; s_total_addresses = 0;
s_exported_count = 0; s_exported_count = 0;
@@ -60,74 +63,77 @@ bool ExportAllKeysDialog::isOpen()
return s_open; return s_open;
} }
void ExportAllKeysDialog::hide()
{
s_open = false;
s_status.clear();
s_saved_path.clear();
}
void ExportAllKeysDialog::render(App* app) void ExportAllKeysDialog::render(App* app)
{ {
if (!s_open) return; if (!s_open) return;
namespace m = material;
auto& S = schema::UI(); m::OverlayDialogSpec ov;
auto win = S.window("dialogs.export-all-keys"); ov.title = TR("export_keys_title");
auto exportBtn = S.button("dialogs.export-all-keys", "export-button"); ov.p_open = &s_open;
auto closeBtn = S.button("dialogs.export-all-keys", "close-button"); ov.style = m::OverlayStyle::BlurFloat;
ov.cardWidth = 600.0f;
ov.idSuffix = "exportallkeys";
if (m::BeginOverlayDialog(ov)) {
m::DialogWarningHeader(TR("export_keys_danger"));
ImGui::Spacing();
material::OverlayDialogSpec ov; ImGui::BeginDisabled(s_exporting);
ov.title = TR("export_keys_title"); ov.p_open = &s_open; ImGui::TextUnformatted(TR("export_keys_options"));
ov.style = material::OverlayStyle::BlurFloat; // floating content on the blur, plain heading
ov.cardWidth = win.width; ov.cardBottomViewportRatio = 0.94f; // keep authored width
if (material::BeginOverlayDialog(ov)) {
// Warning
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
ImGui::PushFont(material::Type().iconSmall());
ImGui::Text(ICON_MD_WARNING);
ImGui::PopFont();
ImGui::SameLine(0, 4.0f);
ImGui::TextWrapped("%s", TR("export_keys_danger"));
ImGui::PopStyleColor();
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (s_exporting) {
ImGui::BeginDisabled();
}
// Options
ImGui::Text("%s", TR("export_keys_options"));
ImGui::Checkbox(TR("export_keys_include_z"), &s_include_z); ImGui::Checkbox(TR("export_keys_include_z"), &s_include_z);
ImGui::Checkbox(TR("export_keys_include_t"), &s_include_t); ImGui::Checkbox(TR("export_keys_include_t"), &s_include_t);
ImGui::Spacing(); ImGui::Spacing();
m::LabeledInput(TR("output_filename"), "##Filename", s_filename, sizeof(s_filename));
// Filename
material::LabeledInput(TR("output_filename"), "##Filename", s_filename, sizeof(s_filename));
ImGui::Spacing(); ImGui::Spacing();
ImGui::TextDisabled("%s", TR("file_save_location")); ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::OnSurfaceDisabled()), "%s", TR("file_save_location"));
ImGui::EndDisabled();
// Progress / result.
if (s_exporting) { if (s_exporting) {
ImGui::EndDisabled(); ImGui::Spacing();
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::Primary()), TR("export_keys_progress"),
s_exported_count, s_total_addresses);
ImGui::SameLine(0, 0);
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::Primary()), "%s", m::LoadingDots());
} else if (!s_saved_path.empty()) {
ImGui::Spacing();
ImGui::PushFont(m::Type().iconSmall());
ImGui::TextColored(m::SuccessVec4(), ICON_MD_CHECK_CIRCLE);
ImGui::PopFont();
ImGui::SameLine(0, 4.0f * Layout::dpiScale());
ImGui::TextColored(m::SuccessVec4(), "%s", TR("export_keys_success"));
widgets::AddressCopyField("##exportedkeyspath", s_saved_path);
} else if (!s_status.empty()) {
ImGui::Spacing();
ImGui::PushTextWrapPos(0.0f);
ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "%s", s_status.c_str());
ImGui::PopTextWrapPos();
} }
ImGui::Spacing(); ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing(); bool doExport = false, doClose = false;
m::DialogActionFooter(TR("export_keys_btn"), !s_exporting, TR("close"), doExport, doClose);
// Export button if (doClose) s_open = false;
if (s_exporting) { if (doExport) {
ImGui::BeginDisabled();
}
if (material::StyledButton(TR("export_keys_btn"), ImVec2(exportBtn.width, 0), S.resolveFont(exportBtn.font))) {
if (!s_include_z && !s_include_t) { if (!s_include_z && !s_include_t) {
Notifications::instance().warning("Select at least one address type"); Notifications::instance().warning(TR("export_keys_select_type"));
} else if (!app->rpc() || !app->rpc()->isConnected()) { } else if (!app->rpc() || !app->rpc()->isConnected()) {
Notifications::instance().error("Not connected to daemon"); Notifications::instance().error(TR("export_keys_not_connected"));
} else { } else {
s_exporting = true; s_exporting = true;
s_exported_keys.clear(); s_exported_keys.clear();
s_exported_count = 0; s_exported_count = 0;
s_status = "Exporting keys..."; s_status.clear();
s_saved_path.clear();
const auto& state = app->getWalletState(); const auto& state = app->getWalletState();
// Count total addresses to export // Count total addresses to export
@@ -137,7 +143,8 @@ void ExportAllKeysDialog::render(App* app)
if (s_total_addresses == 0) { if (s_total_addresses == 0) {
s_exporting = false; s_exporting = false;
s_status = "No addresses to export"; s_status = TR("export_keys_none_addrs");
m::EndOverlayDialog();
return; return;
} }
@@ -216,21 +223,21 @@ void ExportAllKeysDialog::render(App* app)
return [exported, total, filepath, writeOk]() { return [exported, total, filepath, writeOk]() {
s_exported_count = exported; s_exported_count = exported;
s_exporting = false; s_exporting = false;
char buf[320];
if (exported == 0) { if (exported == 0) {
s_status = "No keys exported (0 of " + std::to_string(total) + snprintf(buf, sizeof(buf), TR("export_keys_none_result"), total);
") — unlock the wallet (if encrypted) and try again."; s_status = buf;
Notifications::instance().error("No keys could be exported — is the wallet unlocked?"); Notifications::instance().error(TR("export_keys_none_toast"));
} else if (!writeOk) { } else if (!writeOk) {
s_status = "Failed to write file"; s_status = TR("export_keys_write_fail");
Notifications::instance().error("Failed to save key file"); Notifications::instance().error(TR("export_keys_write_fail"));
} else if (exported < total) { } else if (exported < total) {
s_status = "Exported " + std::to_string(exported) + " of " + snprintf(buf, sizeof(buf), TR("export_keys_partial"), exported, total);
std::to_string(total) + " keys to: " + filepath + s_status = buf;
" (INCOMPLETE — some addresses had no spending key or the wallet is locked)"; snprintf(buf, sizeof(buf), TR("export_keys_partial_toast"), exported, total);
Notifications::instance().warning("Partial export: " + std::to_string(exported) + Notifications::instance().warning(buf);
" of " + std::to_string(total) + " keys");
} else { } else {
s_status = "Exported to: " + filepath; s_saved_path = filepath;
Notifications::instance().success(TR("export_keys_success"), 5.0f); Notifications::instance().success(TR("export_keys_success"), 5.0f);
} }
}; };
@@ -238,24 +245,8 @@ void ExportAllKeysDialog::render(App* app)
} }
} }
} }
if (s_exporting) { m::EndOverlayDialog();
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::TextDisabled(TR("export_keys_progress"), s_exported_count, s_total_addresses);
}
ImGui::SameLine();
if (material::StyledButton(TR("close"), ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
s_open = false;
}
// Status
if (!s_status.empty()) {
ImGui::Spacing();
ImGui::TextWrapped("%s", s_status.c_str());
}
material::EndOverlayDialog();
} }
} }

View File

@@ -27,6 +27,9 @@ public:
// Check if dialog is open // Check if dialog is open
static bool isOpen(); static bool isOpen();
// Close the dialog (used by the UI sweep teardown)
static void hide();
}; };
} // namespace ui } // namespace ui

View File

@@ -1327,6 +1327,14 @@ void I18n::loadBuiltinEnglish()
strings_["export_keys_include_z"] = "Include Z-addresses (shielded)"; strings_["export_keys_include_z"] = "Include Z-addresses (shielded)";
strings_["export_keys_options"] = "Export options:"; strings_["export_keys_options"] = "Export options:";
strings_["export_keys_success"] = "Keys exported successfully"; strings_["export_keys_success"] = "Keys exported successfully";
strings_["export_keys_select_type"] = "Select at least one address type";
strings_["export_keys_not_connected"] = "Not connected to the daemon";
strings_["export_keys_none_addrs"] = "No addresses to export";
strings_["export_keys_none_result"] = "No keys exported (0 of %d) \xE2\x80\x94 unlock the wallet and try again.";
strings_["export_keys_none_toast"] = "No keys could be exported \xE2\x80\x94 is the wallet unlocked?";
strings_["export_keys_write_fail"] = "Failed to write the key file.";
strings_["export_keys_partial"] = "Exported %d of %d keys \xE2\x80\x94 incomplete (some had no spending key, or the wallet is locked).";
strings_["export_keys_partial_toast"] = "Partial export: %d of %d keys";
strings_["export_keys_title"] = "Export All Private Keys"; strings_["export_keys_title"] = "Export All Private Keys";
// --- Export Transactions Dialog --- // --- Export Transactions Dialog ---