diff --git a/src/app_sweep.cpp b/src/app_sweep.cpp index 742ae7c..55f3558 100644 --- a/src/app_sweep.cpp +++ b/src/app_sweep.cpp @@ -19,6 +19,7 @@ #include "ui/sidebar.h" #include "ui/windows/send_tab.h" #include "ui/windows/wallets_dialog.h" +#include "ui/windows/export_transactions_dialog.h" #include "ui/windows/daemon_download_dialog.h" #include "ui/windows/xmrig_download_dialog.h" #include "ui/pages/settings_page.h" @@ -266,6 +267,8 @@ void App::buildSweepCatalog() [](App& a) { a.import_view_mode_ = true; a.show_import_key_ = true; }, [](App& a) { a.show_import_key_ = false; a.import_view_mode_ = false; }); add("modal-export-key", ui::NavPage::Overview, [](App& a) { a.show_export_key_ = true; }, [](App& a) { a.show_export_key_ = false; }); + add("modal-export-transactions", ui::NavPage::Settings, + [](App&) { ui::ExportTransactionsDialog::show(); }, [](App&) { ui::ExportTransactionsDialog::hide(); }); add("modal-backup", ui::NavPage::Overview, [](App& a) { a.show_backup_ = true; }, [](App& a) { a.show_backup_ = false; a.backup_status_.clear(); }); add("modal-about", ui::NavPage::Overview, diff --git a/src/ui/windows/export_transactions_dialog.cpp b/src/ui/windows/export_transactions_dialog.cpp index 26f2ff2..1c51555 100644 --- a/src/ui/windows/export_transactions_dialog.cpp +++ b/src/ui/windows/export_transactions_dialog.cpp @@ -9,7 +9,10 @@ #include "../notifications.h" #include "../schema/ui_schema.h" #include "../material/draw_helpers.h" +#include "../material/type.h" +#include "../widgets/copy_field.h" #include "../theme.h" +#include "../../embedded/IconsMaterialDesign.h" #include "imgui.h" #include @@ -24,7 +27,8 @@ namespace ui { // Static state static bool s_open = false; static char s_filename[256] = ""; -static std::string s_status; +static std::string s_saved_path; // full path on success (shown as a copy field); empty otherwise +static std::string s_error; // error message on failure; empty otherwise // Re-entrancy guard: true while a CSV write is in flight (disables the // Export button so a second synchronous write can't be kicked off). static bool s_exporting = false; @@ -52,8 +56,9 @@ static std::string escapeCSV(const std::string& field) void ExportTransactionsDialog::show() { s_open = true; - s_status.clear(); - + s_saved_path.clear(); + s_error.clear(); + // Generate default filename with timestamp std::time_t now = std::time(nullptr); char timebuf[32]; @@ -66,104 +71,96 @@ bool ExportTransactionsDialog::isOpen() return s_open; } +void ExportTransactionsDialog::hide() +{ + s_open = false; + s_saved_path.clear(); + s_error.clear(); +} + void ExportTransactionsDialog::render(App* app) { if (!s_open) return; + namespace m = material; - auto& S = schema::UI(); - auto win = S.window("dialogs.export-transactions"); - auto exportBtn = S.button("dialogs.export-transactions", "export-button"); - auto closeBtn = S.button("dialogs.export-transactions", "close-button"); + m::OverlayDialogSpec ov; + ov.title = TR("export_tx_title"); + ov.p_open = &s_open; + ov.style = m::OverlayStyle::BlurFloat; + ov.cardWidth = 520.0f; + ov.idSuffix = "exporttx"; + if (!m::BeginOverlayDialog(ov)) return; - if (material::BeginOverlayDialog(TR("export_tx_title"), &s_open, win.width, 0.94f)) { - const auto& state = app->getWalletState(); - - ImGui::Text(TR("export_tx_count"), state.transactions.size()); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - // Filename - material::LabeledInput(TR("output_filename"), "##Filename", s_filename, sizeof(s_filename)); - - ImGui::Spacing(); - ImGui::TextDisabled("%s", TR("file_save_location")); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - // Export button (disabled while a write is already in flight) - ImGui::BeginDisabled(s_exporting); - if (material::StyledButton(TR("export"), ImVec2(exportBtn.width, 0), S.resolveFont(exportBtn.font))) { - if (state.transactions.empty()) { - Notifications::instance().warning(TR("export_tx_none")); - } else { - s_exporting = true; - std::string configDir = util::Platform::getConfigDir(); - std::string filepath = configDir + "/" + s_filename; + const auto& state = app->getWalletState(); - std::ofstream file(filepath); - if (!file.is_open()) { - s_status = "Failed to create file"; - Notifications::instance().error(TR("export_tx_file_fail")); - } else { - // Write CSV header - file << "Date,Type,Amount,Address,TXID,Confirmations,Memo\n"; - - // Write transactions - for (const auto& tx : state.transactions) { - // Date - std::time_t t = static_cast(tx.timestamp); - char datebuf[32]; - std::strftime(datebuf, sizeof(datebuf), "%Y-%m-%d %H:%M:%S", std::localtime(&t)); - file << datebuf << ","; - - // Type - file << escapeCSV(tx.type) << ","; - - // Amount - std::ostringstream amt; - amt << std::fixed << std::setprecision(8) << tx.amount; - file << amt.str() << ","; - - // Address - file << escapeCSV(tx.address) << ","; - - // TXID - file << escapeCSV(tx.txid) << ","; - - // Confirmations - file << tx.confirmations << ","; - - // Memo - file << escapeCSV(tx.memo) << "\n"; - } - - file.close(); - - s_status = "Exported " + std::to_string(state.transactions.size()) + - " transactions to: " + filepath; - Notifications::instance().success(TR("export_tx_success"), 5.0f); - } - s_exporting = false; - } - } - ImGui::EndDisabled(); + ImGui::Text(TR("export_tx_count"), state.transactions.size()); + ImGui::Spacing(); - ImGui::SameLine(); - if (material::StyledButton("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(); + // Filename + save-location hint. + m::LabeledInput(TR("output_filename"), "##Filename", s_filename, sizeof(s_filename)); + ImGui::Spacing(); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::OnSurfaceDisabled()), "%s", TR("file_save_location")); + + // Result — success shows the saved path as a copy field; failure shows a red line. + 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_tx_success")); + widgets::AddressCopyField("##exportedpath", s_saved_path); + } else if (!s_error.empty()) { + ImGui::Spacing(); + ImGui::PushTextWrapPos(0.0f); + ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "%s", s_error.c_str()); + ImGui::PopTextWrapPos(); } + + ImGui::Spacing(); + + bool doExport = false, doClose = false; + m::DialogActionFooter(TR("export"), !s_exporting, TR("close"), doExport, doClose); + if (doClose) s_open = false; + if (doExport) { + s_saved_path.clear(); + s_error.clear(); + if (state.transactions.empty()) { + Notifications::instance().warning(TR("export_tx_none")); + } else { + s_exporting = true; + std::string configDir = util::Platform::getConfigDir(); + std::string filepath = configDir + "/" + s_filename; + + std::ofstream file(filepath); + if (!file.is_open()) { + s_error = TR("export_tx_file_fail"); + Notifications::instance().error(TR("export_tx_file_fail")); + } else { + file << "Date,Type,Amount,Address,TXID,Confirmations,Memo\n"; + for (const auto& tx : state.transactions) { + std::time_t t = static_cast(tx.timestamp); + char datebuf[32]; + std::strftime(datebuf, sizeof(datebuf), "%Y-%m-%d %H:%M:%S", std::localtime(&t)); + file << datebuf << ","; + file << escapeCSV(tx.type) << ","; + std::ostringstream amt; + amt << std::fixed << std::setprecision(8) << tx.amount; + file << amt.str() << ","; + file << escapeCSV(tx.address) << ","; + file << escapeCSV(tx.txid) << ","; + file << tx.confirmations << ","; + file << escapeCSV(tx.memo) << "\n"; + } + file.close(); + s_saved_path = filepath; + Notifications::instance().success(TR("export_tx_success"), 5.0f); + } + s_exporting = false; + } + } + + m::EndOverlayDialog(); } } // namespace ui diff --git a/src/ui/windows/export_transactions_dialog.h b/src/ui/windows/export_transactions_dialog.h index 0660851..b158424 100644 --- a/src/ui/windows/export_transactions_dialog.h +++ b/src/ui/windows/export_transactions_dialog.h @@ -25,6 +25,9 @@ public: // Check if dialog is open static bool isOpen(); + + // Close the dialog (used by the UI sweep teardown) + static void hide(); }; } // namespace ui