Files
ObsidianDragon/src/ui/windows/export_transactions_dialog.cpp
T
DanSandClaude Opus 4.8 a99f71a0cd fix(ux): land quick-win UX audit fixes across settings, exports, toasts, nav
Batch of safe, isolated fixes from the 10-surface UX audit:

- settings: Auto-shield / Save-z-transactions toggles now persist on change
  (they discarded CB()'s return and relied on a Save button that only renders
  on the About tab, so the choice was silently lost on relaunch).
- exports: show the resolved Platform::getConfigDir() instead of a hardcoded
  ~/.config path (wrong on Windows/macOS) in both the all-keys and CSV
  dialogs; all-keys also reveals the file path on a partial export.
- notifications: error/warning toasts last 9s/7s (were 4s/3.5s) so failures
  are readable.
- qr popup / tx-details: copy-to-clipboard now confirms with a toast.
- about: Escape-to-close + readable heading contrast via OnSurfaceMedium().
- sidebar: suppress single-key nav (J/K/arrows/[) while an overlay dialog or
  ImGui popup is open, so it no longer switches the tab behind a modal.
- i18n: strip the stale hardcoded path out of file_save_location in i18n.cpp
  and all eight res/lang/*.json locales (additive, one key each).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-17 16:27:58 -05:00

135 lines
4.4 KiB
C++

// 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 "../../data/transaction_csv.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 <string>
#include <ctime>
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
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::tm nowTm{};
#ifdef _WIN32
const bool nowOk = localtime_s(&nowTm, &now) == 0;
#else
const bool nowOk = localtime_r(&now, &nowTm) != nullptr;
#endif
timebuf[0] = '\0';
if (nowOk) std::strftime(timebuf, sizeof(timebuf), "%Y%m%d_%H%M%S", &nowTm);
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();
// Show the real, resolved config directory (the old hardcoded ~/.config path was wrong on Windows/macOS).
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::OnSurfaceDisabled()), "%s %s",
TR("file_save_location"), util::Platform::getConfigDir().c_str());
// 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"), true, 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 {
std::string configDir = util::Platform::getConfigDir();
std::string filepath = configDir + "/" + s_filename;
// Build in memory, then write atomically (temp + fsync + rename): a disk-full /
// removable-media failure is reported instead of leaving a truncated CSV behind a success toast.
const std::string csv = data::formatTransactionsCsv(state.transactions);
if (!util::Platform::writeFileAtomically(filepath, csv)) {
s_error = TR("export_tx_file_fail");
Notifications::instance().error(TR("export_tx_file_fail"));
} else {
s_saved_path = filepath;
Notifications::instance().success(TR("export_tx_success"), 5.0f);
}
}
}
m::EndOverlayDialog();
}
} // namespace ui
} // namespace dragonx