chore(ui): remove dead dialog duplicates (Phase 2 cleanup)
Grep-verified dead, zero live callers (the live paths all go through
BeginOverlayDialog and already got the blur backdrop):
- App::renderAboutDialog (app.cpp/app.h) — dispatch uses ui::RenderAboutDialog.
- src/ui/windows/import_key_dialog.{h,cpp} (ImportKeyDialog class) — live path is
App::renderImportKeyDialog.
- src/ui/windows/backup_wallet_dialog.{h,cpp} (BackupWalletDialog class) — live
path is App::renderBackupDialog.
Drop the two dead files from CMakeLists. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -474,10 +474,8 @@ set(APP_SOURCES
|
||||
src/ui/windows/shield_dialog.cpp
|
||||
src/ui/windows/request_payment_dialog.cpp
|
||||
src/ui/windows/block_info_dialog.cpp
|
||||
src/ui/windows/import_key_dialog.cpp
|
||||
src/ui/windows/export_all_keys_dialog.cpp
|
||||
src/ui/windows/export_transactions_dialog.cpp
|
||||
src/ui/windows/backup_wallet_dialog.cpp
|
||||
src/ui/widgets/qr_code.cpp
|
||||
src/rpc/rpc_client.cpp
|
||||
src/rpc/rpc_worker.cpp
|
||||
@@ -600,10 +598,8 @@ set(APP_HEADERS
|
||||
src/ui/windows/shield_dialog.h
|
||||
src/ui/windows/request_payment_dialog.h
|
||||
src/ui/windows/block_info_dialog.h
|
||||
src/ui/windows/import_key_dialog.h
|
||||
src/ui/windows/export_all_keys_dialog.h
|
||||
src/ui/windows/export_transactions_dialog.h
|
||||
src/ui/windows/backup_wallet_dialog.h
|
||||
src/ui/widgets/qr_code.h
|
||||
src/rpc/rpc_client.h
|
||||
src/rpc/rpc_worker.h
|
||||
|
||||
35
src/app.cpp
35
src/app.cpp
@@ -2579,41 +2579,6 @@ void App::renderLiteUnlockPrompt()
|
||||
}
|
||||
}
|
||||
|
||||
void App::renderAboutDialog()
|
||||
{
|
||||
auto dlg = ui::schema::UI().drawElement("inline-dialogs", "about");
|
||||
auto dlgF = [&](const char* key, float fb) -> float {
|
||||
auto it = dlg.extraFloats.find(key);
|
||||
return it != dlg.extraFloats.end() ? it->second : fb;
|
||||
};
|
||||
|
||||
if (!ui::material::BeginOverlayDialog("About ObsidianDragon", &show_about_, dlgF("width", 400.0f), 0.94f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, DRAGONX_APP_NAME);
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
|
||||
ImGui::Text("Version: %s", DRAGONX_VERSION);
|
||||
ImGui::Text("ImGui: %s", IMGUI_VERSION);
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::TextWrapped("A shielded cryptocurrency wallet for DragonX (DRGX), "
|
||||
"built with Dear ImGui for a lightweight, portable experience.");
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Copyright 2024-2026 The Hush Developers");
|
||||
ImGui::Text("Released under the GPLv3 License");
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
if (ui::material::StyledButton("Close", ImVec2(dlgF("close-button-width", 120.0f), 0), ui::material::resolveButtonFont((int)dlgF("button-font", 1)))) {
|
||||
show_about_ = false;
|
||||
}
|
||||
|
||||
ui::material::EndOverlayDialog();
|
||||
}
|
||||
|
||||
void App::renderImportKeyDialog()
|
||||
{
|
||||
|
||||
@@ -839,7 +839,6 @@ private:
|
||||
|
||||
// Private methods - rendering
|
||||
void renderStatusBar();
|
||||
void renderAboutDialog();
|
||||
void renderLiteFirstRunPrompt(); // lite-only welcome modal when no wallet exists yet
|
||||
void renderLiteUnlockPrompt(); // lite-only send-time unlock modal
|
||||
void renderImportKeyDialog();
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "backup_wallet_dialog.h"
|
||||
#include "../../app.h"
|
||||
#include "../../rpc/rpc_client.h"
|
||||
#include "../../rpc/rpc_worker.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../../util/platform.h"
|
||||
#include "../notifications.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../theme.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <string>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using json = nlohmann::json;
|
||||
|
||||
// Static state
|
||||
static bool s_open = false;
|
||||
static char s_destination[512] = "";
|
||||
static std::string s_status;
|
||||
static bool s_backing_up = false;
|
||||
|
||||
void BackupWalletDialog::show()
|
||||
{
|
||||
s_open = true;
|
||||
s_status.clear();
|
||||
s_backing_up = false;
|
||||
|
||||
// Generate default destination 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));
|
||||
|
||||
// Default to home directory
|
||||
std::string home = util::Platform::getHomeDir();
|
||||
snprintf(s_destination, sizeof(s_destination), "%s/wallet_backup_%s.dat", home.c_str(), timebuf);
|
||||
}
|
||||
|
||||
bool BackupWalletDialog::isOpen()
|
||||
{
|
||||
return s_open;
|
||||
}
|
||||
|
||||
void BackupWalletDialog::render(App* app)
|
||||
{
|
||||
if (!s_open) return;
|
||||
|
||||
auto& S = schema::UI();
|
||||
auto win = S.window("dialogs.backup-wallet");
|
||||
auto backupBtn = S.button("dialogs.backup-wallet", "backup-button");
|
||||
auto closeBtn = S.button("dialogs.backup-wallet", "close-button");
|
||||
|
||||
if (material::BeginOverlayDialog(TR("backup_title"), &s_open, win.width, 0.94f)) {
|
||||
ImGui::TextWrapped("%s", TR("backup_description"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (s_backing_up) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
// Destination path
|
||||
ImGui::Text("%s", TR("backup_destination"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##Destination", s_destination, sizeof(s_destination));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Show wallet.dat location
|
||||
std::string walletPath = util::Platform::getDataDir() + "/wallet.dat";
|
||||
ImGui::TextDisabled(TR("backup_source"), walletPath.c_str());
|
||||
|
||||
// Check if source exists
|
||||
bool sourceExists = fs::exists(walletPath);
|
||||
if (!sourceExists) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
|
||||
ImGui::Text("%s", TR("backup_wallet_not_found"));
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
if (s_backing_up) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Backup button - use RPC backupwallet
|
||||
if (s_backing_up) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
if (material::StyledButton(TR("backup_create"), ImVec2(backupBtn.width, 0), S.resolveFont(backupBtn.font))) {
|
||||
if (strlen(s_destination) == 0) {
|
||||
Notifications::instance().warning("Please enter a destination path");
|
||||
} else if (!app->rpc() || !app->rpc()->isConnected()) {
|
||||
Notifications::instance().error("Not connected to daemon");
|
||||
} else {
|
||||
s_backing_up = true;
|
||||
s_status = "Creating backup...";
|
||||
|
||||
// Run backup on worker thread to avoid freezing UI
|
||||
std::string dest(s_destination);
|
||||
if (app->worker()) {
|
||||
app->worker()->post([rpc = app->rpc(), dest]() -> rpc::RPCWorker::MainCb {
|
||||
bool success = false;
|
||||
std::string statusMsg;
|
||||
try {
|
||||
rpc::RPCClient::TraceScope trace("Settings / Backup wallet");
|
||||
rpc->call("backupwallet", json::array({dest}));
|
||||
// Check if file was created
|
||||
if (fs::exists(dest)) {
|
||||
auto size = fs::file_size(dest);
|
||||
char sizebuf[32];
|
||||
if (size > 1024 * 1024) {
|
||||
snprintf(sizebuf, sizeof(sizebuf), "%.2f MB", size / (1024.0 * 1024.0));
|
||||
} else if (size > 1024) {
|
||||
snprintf(sizebuf, sizeof(sizebuf), "%.2f KB", size / 1024.0);
|
||||
} else {
|
||||
snprintf(sizebuf, sizeof(sizebuf), "%zu bytes", size);
|
||||
}
|
||||
statusMsg = std::string("Backup created successfully (") + sizebuf + ")";
|
||||
success = true;
|
||||
} else {
|
||||
statusMsg = "Backup may have failed - file not found";
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
statusMsg = std::string("Backup failed: ") + e.what();
|
||||
}
|
||||
return [success, statusMsg]() {
|
||||
s_status = statusMsg;
|
||||
s_backing_up = false;
|
||||
if (success) {
|
||||
Notifications::instance().success(TR("backup_created"), 5.0f);
|
||||
} else {
|
||||
Notifications::instance().warning(statusMsg);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s_backing_up) {
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("%s", TR("backup_backing_up"));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Tips
|
||||
ImGui::TextDisabled("%s", TR("backup_tips"));
|
||||
ImGui::BulletText("%s", TR("backup_tip_external"));
|
||||
ImGui::BulletText("%s", TR("backup_tip_multiple"));
|
||||
ImGui::BulletText("%s", TR("backup_tip_test"));
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
@@ -1,31 +0,0 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace dragonx {
|
||||
|
||||
class App;
|
||||
|
||||
namespace ui {
|
||||
|
||||
/**
|
||||
* @brief Dialog for backing up wallet.dat
|
||||
*/
|
||||
class BackupWalletDialog {
|
||||
public:
|
||||
// Show the dialog
|
||||
static void show();
|
||||
|
||||
// Render the dialog (call each frame)
|
||||
static void render(App* app);
|
||||
|
||||
// Check if dialog is open
|
||||
static bool isOpen();
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
@@ -1,412 +0,0 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "import_key_dialog.h"
|
||||
#include "../../app.h"
|
||||
#include "../../rpc/rpc_client.h"
|
||||
#include "../../rpc/rpc_worker.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../notifications.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../material/type.h"
|
||||
#include "../../embedded/IconsMaterialDesign.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// Static state
|
||||
static bool s_open = false;
|
||||
static char s_key_input[4096] = "";
|
||||
static bool s_rescan = true;
|
||||
static int s_rescan_height = 0; // 0 = full rescan
|
||||
static bool s_importing = false;
|
||||
static std::string s_status;
|
||||
static int s_total_keys = 0;
|
||||
static int s_imported_keys = 0;
|
||||
static int s_failed_keys = 0;
|
||||
static bool s_paste_previewing = false;
|
||||
static std::string s_preview_text;
|
||||
|
||||
// Helper to detect key type
|
||||
static std::string detectKeyType(const std::string& key)
|
||||
{
|
||||
if (key.empty()) return "unknown";
|
||||
|
||||
// Z-address spending keys start with "secret-extended-key-" or "SK" prefix patterns
|
||||
if (key.substr(0, 20) == "secret-extended-key-") {
|
||||
return "z-spending";
|
||||
}
|
||||
|
||||
// Legacy z-addr keys (SK prefix)
|
||||
if (key.length() >= 2 && key[0] == 'S' && key[1] == 'K') {
|
||||
return "z-spending";
|
||||
}
|
||||
|
||||
// T-address private keys (WIF format) - start with 5, K, or L for Bitcoin-derived
|
||||
// DragonX/HUSH uses different prefixes
|
||||
if (key.length() >= 51 && key.length() <= 52) {
|
||||
char first = key[0];
|
||||
if (first == '5' || first == 'K' || first == 'L' || first == 'U') {
|
||||
return "t-privkey";
|
||||
}
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
// Helper to split input into individual keys
|
||||
static std::vector<std::string> splitKeys(const std::string& input)
|
||||
{
|
||||
std::vector<std::string> keys;
|
||||
std::istringstream stream(input);
|
||||
std::string line;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
// Trim whitespace
|
||||
size_t start = line.find_first_not_of(" \t\r\n");
|
||||
size_t end = line.find_last_not_of(" \t\r\n");
|
||||
|
||||
if (start != std::string::npos && end != std::string::npos) {
|
||||
std::string key = line.substr(start, end - start + 1);
|
||||
if (!key.empty() && key[0] != '#') { // Skip comments
|
||||
keys.push_back(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
void ImportKeyDialog::show()
|
||||
{
|
||||
s_open = true;
|
||||
s_key_input[0] = '\0';
|
||||
s_rescan = true;
|
||||
s_rescan_height = 0;
|
||||
s_importing = false;
|
||||
s_status.clear();
|
||||
s_total_keys = 0;
|
||||
s_imported_keys = 0;
|
||||
s_failed_keys = 0;
|
||||
s_paste_previewing = false;
|
||||
s_preview_text.clear();
|
||||
}
|
||||
|
||||
bool ImportKeyDialog::isOpen()
|
||||
{
|
||||
return s_open;
|
||||
}
|
||||
|
||||
void ImportKeyDialog::render(App* app)
|
||||
{
|
||||
if (!s_open) return;
|
||||
|
||||
auto& S = schema::UI();
|
||||
auto win = S.window("dialogs.import-key");
|
||||
auto keyInput = S.input("dialogs.import-key", "key-input");
|
||||
auto rescanInput = S.input("dialogs.import-key", "rescan-height-input");
|
||||
auto importBtn = S.button("dialogs.import-key", "import-button");
|
||||
auto closeBtn = S.button("dialogs.import-key", "close-button");
|
||||
|
||||
if (material::BeginOverlayDialog(TR("import_key_title"), &s_open, win.width, 0.94f)) {
|
||||
// Warning
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.0f, 1.0f));
|
||||
ImGui::PushFont(material::Type().iconSmall());
|
||||
ImGui::Text(ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 4.0f);
|
||||
ImGui::TextWrapped("%s", TR("import_key_warning"));
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Key input
|
||||
ImGui::Text("%s", TR("import_key_label"));
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
material::Tooltip("%s", TR("import_key_tooltip"));
|
||||
}
|
||||
|
||||
// Validation indicator inline with title — check preview text during hover,
|
||||
// otherwise check the actual input
|
||||
{
|
||||
const char* checkSrc = s_key_input;
|
||||
if (s_paste_previewing && !s_preview_text.empty())
|
||||
checkSrc = s_preview_text.c_str();
|
||||
if (checkSrc[0] != '\0') {
|
||||
auto previewKeys = splitKeys(checkSrc);
|
||||
int pz = 0, pt = 0, pu = 0;
|
||||
for (const auto& k : previewKeys) {
|
||||
std::string kt = detectKeyType(k);
|
||||
if (kt == "z-spending") pz++;
|
||||
else if (kt == "t-privkey") pt++;
|
||||
else pu++;
|
||||
}
|
||||
if (pz > 0 || pt > 0) {
|
||||
ImGui::SameLine();
|
||||
char vbuf[128];
|
||||
if (pz > 0 && pt > 0)
|
||||
snprintf(vbuf, sizeof(vbuf), "%d shielded, %d transparent", pz, pt);
|
||||
else if (pz > 0)
|
||||
snprintf(vbuf, sizeof(vbuf), "%d shielded key(s)", pz);
|
||||
else
|
||||
snprintf(vbuf, sizeof(vbuf), "%d transparent key(s)", pt);
|
||||
material::Type().textColored(material::TypeStyle::Caption, material::Success(), vbuf);
|
||||
if (pu > 0) {
|
||||
ImGui::SameLine();
|
||||
snprintf(vbuf, sizeof(vbuf), "(%d unrecognized)", pu);
|
||||
material::Type().textColored(material::TypeStyle::Caption, material::Error(), vbuf);
|
||||
}
|
||||
} else if (pu > 0 && !s_paste_previewing) {
|
||||
ImGui::SameLine();
|
||||
material::Type().textColored(material::TypeStyle::Caption, material::Error(), "Unrecognized key format");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s_importing) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputTextMultiline("##KeyInput", s_key_input, sizeof(s_key_input),
|
||||
ImVec2(-1, keyInput.height > 0 ? keyInput.height : 150), ImGuiInputTextFlags_AllowTabInput);
|
||||
ImVec2 inputMin = ImGui::GetItemRectMin();
|
||||
ImVec2 inputMax = ImGui::GetItemRectMax();
|
||||
|
||||
// Detect paste button hover before drawing it
|
||||
ImVec2 pasteBtnPos = ImGui::GetCursorScreenPos();
|
||||
// Estimate button size for hover detection
|
||||
ImFont* btnFont = S.resolveFont(importBtn.font);
|
||||
ImVec2 pasteBtnSize = btnFont
|
||||
? ImVec2(btnFont->CalcTextSizeA(btnFont->LegacySize, FLT_MAX, 0, TR("paste_from_clipboard")).x
|
||||
+ ImGui::GetStyle().FramePadding.x * 2,
|
||||
ImGui::GetFrameHeight())
|
||||
: ImVec2(150, ImGui::GetFrameHeight());
|
||||
bool paste_hovered = material::IsRectHovered(pasteBtnPos,
|
||||
ImVec2(pasteBtnPos.x + pasteBtnSize.x, pasteBtnPos.y + pasteBtnSize.y));
|
||||
|
||||
// Handle preview state
|
||||
if (paste_hovered && !s_paste_previewing) {
|
||||
const char* clip = ImGui::GetClipboardText();
|
||||
if (clip && clip[0] != '\0') {
|
||||
std::string trimmed(clip);
|
||||
while (!trimmed.empty() && (trimmed.front() == ' ' || trimmed.front() == '\t' ||
|
||||
trimmed.front() == '\n' || trimmed.front() == '\r'))
|
||||
trimmed.erase(trimmed.begin());
|
||||
while (!trimmed.empty() && (trimmed.back() == ' ' || trimmed.back() == '\t' ||
|
||||
trimmed.back() == '\n' || trimmed.back() == '\r'))
|
||||
trimmed.pop_back();
|
||||
if (!trimmed.empty() && s_key_input[0] == '\0') {
|
||||
s_preview_text = trimmed;
|
||||
s_paste_previewing = true;
|
||||
}
|
||||
}
|
||||
} else if (!paste_hovered && s_paste_previewing) {
|
||||
s_paste_previewing = false;
|
||||
s_preview_text.clear();
|
||||
}
|
||||
|
||||
// Draw transparent preview text overlay on the input field
|
||||
if (s_paste_previewing && !s_preview_text.empty()) {
|
||||
ImVec2 textPos(inputMin.x + ImGui::GetStyle().FramePadding.x,
|
||||
inputMin.y + ImGui::GetStyle().FramePadding.y);
|
||||
ImVec4 previewCol = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
||||
previewCol.w = S.drawElement("dialogs.import-key", "paste-preview-alpha").sizeOr(0.3f);
|
||||
size_t maxChars = (size_t)S.drawElement("dialogs.import-key", "paste-preview-max-chars").sizeOr(200.0f);
|
||||
// Clip to input rect
|
||||
ImGui::GetWindowDrawList()->PushClipRect(inputMin, inputMax, true);
|
||||
ImGui::GetWindowDrawList()->AddText(textPos, ImGui::ColorConvertFloat4ToU32(previewCol),
|
||||
s_preview_text.c_str(), s_preview_text.c_str() + std::min(s_preview_text.size(), maxChars));
|
||||
ImGui::GetWindowDrawList()->PopClipRect();
|
||||
}
|
||||
|
||||
// Paste button
|
||||
if (material::StyledButton(TR("paste_from_clipboard"), ImVec2(0,0), S.resolveFont(importBtn.font))) {
|
||||
if (s_paste_previewing) {
|
||||
snprintf(s_key_input, sizeof(s_key_input), "%s", s_preview_text.c_str());
|
||||
s_paste_previewing = false;
|
||||
s_preview_text.clear();
|
||||
} else {
|
||||
const char* clipboard = ImGui::GetClipboardText();
|
||||
if (clipboard) {
|
||||
std::string trimmed(clipboard);
|
||||
while (!trimmed.empty() && (trimmed.front() == ' ' || trimmed.front() == '\t' ||
|
||||
trimmed.front() == '\n' || trimmed.front() == '\r'))
|
||||
trimmed.erase(trimmed.begin());
|
||||
while (!trimmed.empty() && (trimmed.back() == ' ' || trimmed.back() == '\t' ||
|
||||
trimmed.back() == '\n' || trimmed.back() == '\r'))
|
||||
trimmed.pop_back();
|
||||
snprintf(s_key_input, sizeof(s_key_input), "%s", trimmed.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton(TR("clear"), ImVec2(0,0), S.resolveFont(importBtn.font))) {
|
||||
s_key_input[0] = '\0';
|
||||
s_paste_previewing = false;
|
||||
s_preview_text.clear();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Rescan options
|
||||
ImGui::Checkbox(TR("import_key_rescan"), &s_rescan);
|
||||
|
||||
if (s_rescan) {
|
||||
ImGui::Indent();
|
||||
ImGui::Text("%s", TR("import_key_start_height"));
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(rescanInput.width);
|
||||
ImGui::InputInt("##RescanHeight", &s_rescan_height);
|
||||
if (s_rescan_height < 0) s_rescan_height = 0;
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("%s", TR("import_key_full_rescan"));
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
if (s_importing) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Import button
|
||||
if (s_importing) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
if (material::StyledButton(TR("import_key_btn"), ImVec2(importBtn.width, 0), S.resolveFont(importBtn.font))) {
|
||||
auto keys = splitKeys(s_key_input);
|
||||
|
||||
if (keys.empty()) {
|
||||
Notifications::instance().warning(TR("import_key_no_valid"));
|
||||
} else if (!app->rpc() || !app->rpc()->isConnected()) {
|
||||
Notifications::instance().error(TR("not_connected"));
|
||||
} else {
|
||||
s_importing = true;
|
||||
s_total_keys = static_cast<int>(keys.size());
|
||||
s_imported_keys = 0;
|
||||
s_failed_keys = 0;
|
||||
s_status = "Importing...";
|
||||
|
||||
// Import keys on worker thread to avoid freezing UI
|
||||
bool rescan = s_rescan;
|
||||
int rescanHeight = s_rescan_height;
|
||||
if (app->worker()) {
|
||||
app->worker()->post([app, rpc = app->rpc(), keys, rescan, rescanHeight]() -> rpc::RPCWorker::MainCb {
|
||||
int imported = 0;
|
||||
int failed = 0;
|
||||
|
||||
for (const auto& key : keys) {
|
||||
std::string keyType = detectKeyType(key);
|
||||
|
||||
try {
|
||||
rpc::RPCClient::TraceScope trace("Settings / Import private keys");
|
||||
if (keyType == "z-spending") {
|
||||
// z_importkey "key" "yes"|"no" startheight
|
||||
if (rescan && rescanHeight > 0) {
|
||||
rpc->call("z_importkey", {key, "yes", rescanHeight});
|
||||
} else {
|
||||
rpc->call("z_importkey", {key, rescan ? "yes" : "no"});
|
||||
}
|
||||
imported++;
|
||||
} else if (keyType == "t-privkey") {
|
||||
// importprivkey does not accept a startheight;
|
||||
// import without rescan, then rescanblockchain
|
||||
// from the requested height afterward.
|
||||
if (rescan && rescanHeight > 0) {
|
||||
rpc->call("importprivkey", {key, "", false});
|
||||
} else {
|
||||
rpc->call("importprivkey", {key, "", rescan});
|
||||
}
|
||||
imported++;
|
||||
} else {
|
||||
failed++;
|
||||
}
|
||||
} catch (...) {
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
// If t-keys were imported without rescan (because a
|
||||
// specific start height was requested), trigger a
|
||||
// single rescanblockchain from that height now.
|
||||
if (rescan && rescanHeight > 0 && imported > 0) {
|
||||
try {
|
||||
rpc::RPCClient::TraceScope trace("Settings / Import private keys");
|
||||
rpc->call("rescanblockchain", {rescanHeight});
|
||||
} catch (...) {
|
||||
// rescan failure is non-fatal; user can retry
|
||||
}
|
||||
}
|
||||
|
||||
return [app, imported, failed]() {
|
||||
s_imported_keys = imported;
|
||||
s_failed_keys = failed;
|
||||
s_importing = false;
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "Import complete: %d success, %d failed",
|
||||
imported, failed);
|
||||
s_status = buf;
|
||||
if (imported > 0) {
|
||||
app->invalidateAddressValidationCache();
|
||||
app->refreshNow();
|
||||
Notifications::instance().success(TR("import_key_success"), 5.0f);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s_importing) {
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled(TR("import_key_progress"), s_imported_keys + s_failed_keys, s_total_keys);
|
||||
}
|
||||
|
||||
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();
|
||||
bool success = s_failed_keys == 0 && !s_importing;
|
||||
ImGui::TextColored(
|
||||
success ? ImVec4(0.2f, 0.8f, 0.2f, 1.0f) : ImVec4(1.0f, 0.8f, 0.0f, 1.0f),
|
||||
"%s", s_status.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Help text
|
||||
ImGui::TextDisabled("%s", TR("import_key_formats"));
|
||||
ImGui::BulletText("%s", TR("import_key_z_format"));
|
||||
ImGui::BulletText("%s", TR("import_key_t_format"));
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
@@ -1,36 +0,0 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace dragonx {
|
||||
|
||||
class App;
|
||||
|
||||
namespace ui {
|
||||
|
||||
/**
|
||||
* @brief Dialog for importing private keys
|
||||
*
|
||||
* Supports importing:
|
||||
* - Z-address private keys (z_importkey)
|
||||
* - T-address private keys (importprivkey)
|
||||
* - Batch import (multiple keys, one per line)
|
||||
*/
|
||||
class ImportKeyDialog {
|
||||
public:
|
||||
// Show the dialog
|
||||
static void show();
|
||||
|
||||
// Render the dialog (call each frame)
|
||||
static void render(App* app);
|
||||
|
||||
// Check if dialog is open
|
||||
static bool isOpen();
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user