- Replace all hardcoded English strings with TR() translation keys across every tab, dialog, and component (~20 UI files) - Expand all 8 language files (de, es, fr, ja, ko, pt, ru, zh) with complete translations (~37k lines added) - Improve i18n loader with exe-relative path fallback and English base fallback for missing keys - Add pool-side hashrate polling via pool stats API in xmrig_manager - Introduce Layout::beginFrame() per-frame caching and refresh balance layout config only on schema generation change - Offload daemon output parsing to worker thread - Add CJK subset fallback font for Chinese/Japanese/Korean glyphs
280 lines
9.2 KiB
C++
280 lines
9.2 KiB
C++
// 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;
|
|
|
|
// 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;
|
|
}
|
|
|
|
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()) {
|
|
ImGui::SetTooltip("%s", TR("import_key_tooltip"));
|
|
}
|
|
|
|
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);
|
|
|
|
// Paste button
|
|
if (material::StyledButton(TR("paste_from_clipboard"), ImVec2(0,0), S.resolveFont(importBtn.font))) {
|
|
const char* clipboard = ImGui::GetClipboardText();
|
|
if (clipboard) {
|
|
strncpy(s_key_input, clipboard, sizeof(s_key_input) - 1);
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
if (material::StyledButton(TR("clear"), ImVec2(0,0), S.resolveFont(importBtn.font))) {
|
|
s_key_input[0] = '\0';
|
|
}
|
|
|
|
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;
|
|
if (app->worker()) {
|
|
app->worker()->post([rpc = app->rpc(), keys, rescan]() -> rpc::RPCWorker::MainCb {
|
|
int imported = 0;
|
|
int failed = 0;
|
|
|
|
for (const auto& key : keys) {
|
|
std::string keyType = detectKeyType(key);
|
|
|
|
try {
|
|
if (keyType == "z-spending") {
|
|
rpc->call("z_importkey", {key, rescan ? "yes" : "no"});
|
|
imported++;
|
|
} else if (keyType == "t-privkey") {
|
|
rpc->call("importprivkey", {key, "", rescan});
|
|
imported++;
|
|
} else {
|
|
failed++;
|
|
}
|
|
} catch (...) {
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
return [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) {
|
|
Notifications::instance().success(TR("import_key_success"));
|
|
}
|
|
};
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|