// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "export_transactions_dialog.h" #include "../../app.h" #include "../../util/i18n.h" #include "../../util/platform.h" #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 #include #include #include #include namespace dragonx { namespace ui { // Static state static bool s_open = false; static char s_filename[256] = ""; 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; // Helper to escape CSV field static std::string escapeCSV(const std::string& field) { if (field.find(',') != std::string::npos || field.find('"') != std::string::npos || field.find('\n') != std::string::npos) { // Escape quotes and wrap in quotes std::string escaped; escaped.reserve(field.size() + 4); escaped += '"'; for (char c : field) { if (c == '"') escaped += "\"\""; else escaped += c; } escaped += '"'; return escaped; } return field; } void ExportTransactionsDialog::show() { s_open = true; s_saved_path.clear(); s_error.clear(); // Generate default filename with timestamp std::time_t now = std::time(nullptr); char timebuf[32]; std::strftime(timebuf, sizeof(timebuf), "%Y%m%d_%H%M%S", std::localtime(&now)); snprintf(s_filename, sizeof(s_filename), "dragonx_transactions_%s.csv", timebuf); } 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; 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; const auto& state = app->getWalletState(); ImGui::Text(TR("export_tx_count"), state.transactions.size()); ImGui::Spacing(); // 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 } // namespace dragonx