feat: blockchain rescan via daemon restart + status bar progress
- Fix z_importwallet to use full path instead of filename only - Add rescanBlockchain() method that restarts daemon with -rescan flag - Track rescan progress via daemon output parsing and getrescaninfo RPC - Display rescan progress in status bar with animated indicator when starting - Improve dark theme card contrast: lighter surface-variant, tinted borders, stronger rim-light
25
build.sh
@@ -350,18 +350,31 @@ APPRUN
|
||||
|
||||
local ARCH
|
||||
ARCH=$(uname -m)
|
||||
local IMG_NAME="DragonX_Wallet-${VERSION}-${ARCH}.AppImage"
|
||||
local IMG_NAME="ObsidianDragon-${ARCH}.AppImage"
|
||||
cd "$bd"
|
||||
ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "$IMG_NAME" 2>/dev/null && {
|
||||
cp "$IMG_NAME" "$out/"
|
||||
info "AppImage: $out/$IMG_NAME ($(du -h "$IMG_NAME" | cut -f1))"
|
||||
# Rename to match Windows convention: ObsidianDragon.AppImage
|
||||
mv "$out/$IMG_NAME" "$out/ObsidianDragon.AppImage"
|
||||
info "AppImage: $out/ObsidianDragon.AppImage ($(du -h "$out/ObsidianDragon.AppImage" | cut -f1))"
|
||||
} || warn "AppImage creation failed (appimagetool issue) — raw binary still in release/linux/"
|
||||
|
||||
# Clean up: keep only AppImage + raw binary in release/linux/
|
||||
if ls "$out"/*.AppImage 1>/dev/null 2>&1; then
|
||||
# AppImage succeeded — remove everything except AppImage and the binary
|
||||
find "$out" -maxdepth 1 -type f ! -name '*.AppImage' ! -name 'ObsidianDragon' -delete
|
||||
# Clean up: keep only AppImage + zip in release/linux/
|
||||
if [[ -f "$out/ObsidianDragon.AppImage" ]]; then
|
||||
# AppImage succeeded — remove everything except AppImage
|
||||
find "$out" -maxdepth 1 -type f ! -name 'ObsidianDragon.AppImage' -delete
|
||||
rm -rf "$out/res" 2>/dev/null
|
||||
|
||||
# Create zip matching Windows naming convention
|
||||
local DIST="DragonX-Wallet-Linux-x64"
|
||||
local dist_dir="$out/$DIST"
|
||||
mkdir -p "$dist_dir"
|
||||
cp "$out/ObsidianDragon.AppImage" "$dist_dir/"
|
||||
if command -v zip &>/dev/null; then
|
||||
(cd "$out" && zip -r "$DIST.zip" "$DIST")
|
||||
info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
|
||||
fi
|
||||
rm -rf "$dist_dir"
|
||||
fi
|
||||
|
||||
info "Linux release artifacts: $out/"
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 556 KiB After Width: | Height: | Size: 370 KiB |
@@ -2,7 +2,7 @@
|
||||
name = "Color Pop Dark"
|
||||
author = "The Hush Developers"
|
||||
dark = true
|
||||
elevation = { --elevation-0 = "#121218", --elevation-1 = "#1C1C24", --elevation-2 = "#26262E", --elevation-3 = "#303038", --elevation-4 = "#3A3A44" }
|
||||
elevation = { --elevation-0 = "#121218", --elevation-1 = "#1C1C24", --elevation-2 = "#282836", --elevation-3 = "#303038", --elevation-4 = "#3A3A44" }
|
||||
images = { background_image = "backgrounds/texture/pop-dark_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
||||
|
||||
[theme.palette]
|
||||
@@ -14,7 +14,7 @@ images = { background_image = "backgrounds/texture/pop-dark_bg.png", logo = "log
|
||||
--secondary-light = "#FF9CDC"
|
||||
--background = "#0E0E14"
|
||||
--surface = "#161620"
|
||||
--surface-variant = "#20202C"
|
||||
--surface-variant = "#282836"
|
||||
--on-primary = "#FFFFFF"
|
||||
--on-secondary = "#FFFFFF"
|
||||
--on-background = "#E8E6F0"
|
||||
@@ -35,7 +35,7 @@ images = { background_image = "backgrounds/texture/pop-dark_bg.png", logo = "log
|
||||
--surface-active = "rgba(200,190,240,0.10)"
|
||||
--glass-button = "rgba(124,108,255,0.08)"
|
||||
--glass-button-hover = "rgba(124,108,255,0.16)"
|
||||
--card-border = "rgba(200,190,240,0.10)"
|
||||
--card-border = "rgba(200,190,240,0.24)"
|
||||
--text-shadow = "rgba(0,0,0,0.45)"
|
||||
--input-overlay-text = "rgba(232,230,240,0.25)"
|
||||
--slider-text = "rgba(232,230,240,0.82)"
|
||||
@@ -48,13 +48,13 @@ images = { background_image = "backgrounds/texture/pop-dark_bg.png", logo = "log
|
||||
--tooltip-bg = "rgba(14,14,22,0.94)"
|
||||
--tooltip-border = "rgba(124,108,255,0.18)"
|
||||
--glass-fill = "rgba(200,190,240,0.06)"
|
||||
--glass-border = "rgba(200,190,240,0.10)"
|
||||
--glass-border = "rgba(124,108,255,0.28)"
|
||||
--glass-noise-tint = "rgba(124,108,255,0.03)"
|
||||
--tactile-top = "rgba(200,190,240,0.07)"
|
||||
--tactile-bottom = "rgba(200,190,240,0.0)"
|
||||
--hover-overlay = "rgba(124,108,255,0.05)"
|
||||
--active-overlay = "rgba(124,108,255,0.10)"
|
||||
--rim-light = "rgba(124,108,255,0.10)"
|
||||
--rim-light = "rgba(124,108,255,0.16)"
|
||||
--status-divider = "rgba(200,190,240,0.06)"
|
||||
--sidebar-hover = "rgba(124,108,255,0.10)"
|
||||
--sidebar-icon = "rgba(232,230,240,0.45)"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "Dark"
|
||||
author = "The Hush Developers"
|
||||
dark = true
|
||||
elevation = { --elevation-0 = "#161618", --elevation-1 = "#222224", --elevation-2 = "#2C2C2E", --elevation-3 = "#363638", --elevation-4 = "#404044" }
|
||||
elevation = { --elevation-0 = "#161618", --elevation-1 = "#222224", --elevation-2 = "#2E2E32", --elevation-3 = "#363638", --elevation-4 = "#404044" }
|
||||
images = { background_image = "backgrounds/texture/dark_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
||||
|
||||
[theme.palette]
|
||||
@@ -14,7 +14,7 @@ images = { background_image = "backgrounds/texture/dark_bg.png", logo = "logos/l
|
||||
--secondary-light = "#9DC5BE"
|
||||
--background = "#141416"
|
||||
--surface = "#1A1A1C"
|
||||
--surface-variant = "#262628"
|
||||
--surface-variant = "#2E2E32"
|
||||
--on-primary = "#000000"
|
||||
--on-secondary = "#000000"
|
||||
--on-background = "#D0D0D4"
|
||||
@@ -35,7 +35,7 @@ images = { background_image = "backgrounds/texture/dark_bg.png", logo = "logos/l
|
||||
--surface-active = "rgba(220,220,225,0.08)"
|
||||
--glass-button = "rgba(220,220,225,0.05)"
|
||||
--glass-button-hover = "rgba(220,220,225,0.10)"
|
||||
--card-border = "rgba(220,220,225,0.12)"
|
||||
--card-border = "rgba(220,220,225,0.24)"
|
||||
--text-shadow = "rgba(0,0,0,0.40)"
|
||||
--input-overlay-text = "rgba(208,208,212,0.28)"
|
||||
--slider-text = "rgba(208,208,212,0.85)"
|
||||
@@ -48,13 +48,13 @@ images = { background_image = "backgrounds/texture/dark_bg.png", logo = "logos/l
|
||||
--tooltip-bg = "rgba(18,18,22,0.92)"
|
||||
--tooltip-border = "rgba(220,220,225,0.10)"
|
||||
--glass-fill = "rgba(220,220,225,0.07)"
|
||||
--glass-border = "rgba(220,220,225,0.12)"
|
||||
--glass-border = "rgba(154,175,200,0.28)"
|
||||
--glass-noise-tint = "rgba(220,220,225,0.03)"
|
||||
--tactile-top = "rgba(220,220,225,0.06)"
|
||||
--tactile-bottom = "rgba(220,220,225,0.0)"
|
||||
--hover-overlay = "rgba(220,220,225,0.04)"
|
||||
--active-overlay = "rgba(220,220,225,0.08)"
|
||||
--rim-light = "rgba(220,220,225,0.08)"
|
||||
--rim-light = "rgba(220,220,225,0.14)"
|
||||
--status-divider = "rgba(220,220,225,0.06)"
|
||||
--sidebar-hover = "rgba(220,220,225,0.08)"
|
||||
--sidebar-icon = "rgba(220,220,225,0.40)"
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
name = "Obsidian"
|
||||
author = "The Hush Developers"
|
||||
dark = true
|
||||
elevation = { --elevation-0 = "#0E0B14", --elevation-1 = "#17121E", --elevation-2 = "#1C1625", --elevation-3 = "#211A2C", --elevation-4 = "#261E33" }
|
||||
elevation = { --elevation-0 = "#0E0B14", --elevation-1 = "#17121E", --elevation-2 = "#252030", --elevation-3 = "#2D2838", --elevation-4 = "#353040" }
|
||||
images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
||||
|
||||
[theme.palette]
|
||||
--primary = "#AB47BC"
|
||||
--primary-variant = "#8E24AA"
|
||||
--primary-light = "#CE93D8"
|
||||
--secondary = "#B388FF"
|
||||
--secondary-variant = "#7C4DFF"
|
||||
--secondary-light = "#D1C4E9"
|
||||
--primary = "#9A6BA8"
|
||||
--primary-variant = "#7A4888"
|
||||
--primary-light = "#C5A8CC"
|
||||
--secondary = "#A898D8"
|
||||
--secondary-variant = "#8074C8"
|
||||
--secondary-light = "#CCC4D9"
|
||||
--background = "#0A0810"
|
||||
--surface = "#110E18"
|
||||
--surface-variant = "#1C1625"
|
||||
--surface-variant = "#252030"
|
||||
--on-primary = "#FFFFFF"
|
||||
--on-secondary = "#000000"
|
||||
--on-background = "#E8E0F0"
|
||||
@@ -35,7 +35,7 @@ images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "log
|
||||
--surface-active = "rgba(200,180,255,0.10)"
|
||||
--glass-button = "rgba(200,180,255,0.06)"
|
||||
--glass-button-hover = "rgba(200,180,255,0.12)"
|
||||
--card-border = "rgba(200,180,255,0.14)"
|
||||
--card-border = "rgba(200,180,255,0.26)"
|
||||
--text-shadow = "rgba(0,0,0,0.50)"
|
||||
--input-overlay-text = "rgba(232,224,240,0.30)"
|
||||
--slider-text = "rgba(232,224,240,0.85)"
|
||||
@@ -48,13 +48,13 @@ images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "log
|
||||
--tooltip-bg = "rgba(14,11,20,0.92)"
|
||||
--tooltip-border = "rgba(200,180,255,0.12)"
|
||||
--glass-fill = "rgba(200,180,255,0.08)"
|
||||
--glass-border = "rgba(200,180,255,0.14)"
|
||||
--glass-border = "rgba(154,107,168,0.30)"
|
||||
--glass-noise-tint = "rgba(200,180,255,0.03)"
|
||||
--tactile-top = "rgba(200,180,255,0.06)"
|
||||
--tactile-bottom = "rgba(200,180,255,0.0)"
|
||||
--hover-overlay = "rgba(200,180,255,0.05)"
|
||||
--active-overlay = "rgba(200,180,255,0.10)"
|
||||
--rim-light = "rgba(200,180,255,0.08)"
|
||||
--rim-light = "rgba(200,180,255,0.14)"
|
||||
--status-divider = "rgba(200,180,255,0.08)"
|
||||
--sidebar-hover = "rgba(200,180,255,0.10)"
|
||||
--sidebar-icon = "rgba(200,180,255,0.42)"
|
||||
@@ -65,19 +65,19 @@ images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "log
|
||||
--window-control-hover = "rgba(200,180,255,0.12)"
|
||||
--window-close-hover = "rgba(232,17,35,0.78)"
|
||||
--spinner-track = "rgba(200,180,255,0.10)"
|
||||
--spinner-active = "rgba(179,136,255,0.85)"
|
||||
--spinner-active = "rgba(168,152,216,0.85)"
|
||||
--shutdown-panel-bg = "rgba(10,8,16,0.90)"
|
||||
--shutdown-panel-border = "rgba(200,180,255,0.07)"
|
||||
--ram-bar-app = "#AB47BC"
|
||||
--ram-bar-app = "#9A6BA8"
|
||||
--ram-bar-system = "rgba(255,255,255,0.18)"
|
||||
--accent-total = "#CE93D8"
|
||||
--accent-shielded = "#80CBC4"
|
||||
--accent-transparent = "#FFAB91"
|
||||
--accent-action = "#AB47BC"
|
||||
--accent-market = "#80CBC4"
|
||||
--accent-portfolio = "#B388FF"
|
||||
--toast-info-accent = "#AB47BC"
|
||||
--toast-info-text = "#CE93D8"
|
||||
--accent-total = "#C5A8CC"
|
||||
--accent-shielded = "#8AB8B4"
|
||||
--accent-transparent = "#D8B0A0"
|
||||
--accent-action = "#9A6BA8"
|
||||
--accent-market = "#8AB8B4"
|
||||
--accent-portfolio = "#A898D8"
|
||||
--toast-info-accent = "#9A6BA8"
|
||||
--toast-info-text = "#C5A8CC"
|
||||
--toast-success-accent = "rgba(50,180,80,1.0)"
|
||||
--toast-success-text = "rgba(180,255,180,1.0)"
|
||||
--toast-warning-accent = "rgba(204,166,50,1.0)"
|
||||
@@ -86,10 +86,10 @@ images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "log
|
||||
--toast-error-text = "rgba(255,153,153,1.0)"
|
||||
--snackbar-bg = "rgba(40,35,55,0.95)"
|
||||
--snackbar-text = "rgba(232,224,240,0.87)"
|
||||
--snackbar-action = "rgba(179,136,255,1.0)"
|
||||
--snackbar-action-hover = "rgba(206,147,216,1.0)"
|
||||
--snackbar-action = "rgba(168,152,216,1.0)"
|
||||
--snackbar-action-hover = "rgba(197,168,204,1.0)"
|
||||
--switch-track-off = "rgba(200,180,255,0.12)"
|
||||
--switch-track-on = "rgba(171,71,188,0.50)"
|
||||
--switch-track-on = "rgba(154,107,168,0.50)"
|
||||
--switch-thumb-off = "#B0A0C0"
|
||||
--switch-thumb-on = "#E8E0F0"
|
||||
--control-shadow = "rgba(0,0,0,0.24)"
|
||||
@@ -144,18 +144,18 @@ gradient-border-enabled = { size = 1.0 }
|
||||
gradient-border-speed = { size = 0.12 }
|
||||
gradient-border-thickness = { size = 1.5 }
|
||||
gradient-border-alpha = { size = 0.55 }
|
||||
gradient-border-color-a = { color = "#CE93D8" }
|
||||
gradient-border-color-b = { color = "#3F51B5" }
|
||||
gradient-border-color-a = { color = "#C5A8CC" }
|
||||
gradient-border-color-b = { color = "#6878A8" }
|
||||
|
||||
ember-rise-enabled = { size = 0.0 }
|
||||
|
||||
# Shader-like viewport overlay — deep indigo crystal atmosphere
|
||||
viewport-wash-enabled = { size = 1.0 }
|
||||
viewport-wash-alpha = { size = 0.05 }
|
||||
viewport-wash-tl = { color = "#4A148C" }
|
||||
viewport-wash-tr = { color = "#1A237E" }
|
||||
viewport-wash-bl = { color = "#311B92" }
|
||||
viewport-wash-br = { color = "#6A1B9A" }
|
||||
viewport-wash-tl = { color = "#3A2860" }
|
||||
viewport-wash-tr = { color = "#2A3058" }
|
||||
viewport-wash-bl = { color = "#302860" }
|
||||
viewport-wash-br = { color = "#4A3068" }
|
||||
viewport-wash-rotate = { size = 0.015 }
|
||||
viewport-wash-pulse = { size = 0.0 }
|
||||
viewport-wash-pulse-depth = { size = 0.0 }
|
||||
|
||||
@@ -20,7 +20,7 @@ spacing-tokens = { xs = 2.0, sm = 4.0, md = 8.0, lg = 12.0, xl = 16.0, xxl = 24.
|
||||
name = "DragonX"
|
||||
author = "DanS"
|
||||
dark = true
|
||||
elevation = { --elevation-0 = "#120A08", --elevation-1 = "#1A0F0C", --elevation-2 = "#201410", --elevation-3 = "#261914", --elevation-4 = "#2C1E18" }
|
||||
elevation = { --elevation-0 = "#120A08", --elevation-1 = "#1A0F0C", --elevation-2 = "#281C16", --elevation-3 = "#30241C", --elevation-4 = "#382C22" }
|
||||
images = { background_image = "backgrounds/texture/drgx_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
|
||||
|
||||
[theme.palette]
|
||||
@@ -32,7 +32,7 @@ images = { background_image = "backgrounds/texture/drgx_bg.png", logo = "logos/l
|
||||
--secondary-light = "#FF9E40"
|
||||
--background = "#0C0606"
|
||||
--surface = "#120A08"
|
||||
--surface-variant = "#201410"
|
||||
--surface-variant = "#281C16"
|
||||
--on-primary = "#FFFFFF"
|
||||
--on-secondary = "#000000"
|
||||
--on-background = "#F0E0D8"
|
||||
@@ -66,13 +66,13 @@ images = { background_image = "backgrounds/texture/drgx_bg.png", logo = "logos/l
|
||||
--tooltip-bg = "rgba(12,8,6,0.92)"
|
||||
--tooltip-border = "rgba(255,180,140,0.12)"
|
||||
--glass-fill = "rgba(255,180,140,0.08)"
|
||||
--glass-border = "rgba(255,180,140,0.13)"
|
||||
--glass-border = "rgba(211,47,47,0.26)"
|
||||
--glass-noise-tint = "rgba(255,180,140,0.03)"
|
||||
--tactile-top = "rgba(255,180,140,0.06)"
|
||||
--tactile-bottom = "rgba(255,180,140,0.0)"
|
||||
--hover-overlay = "rgba(255,180,140,0.05)"
|
||||
--active-overlay = "rgba(255,180,140,0.10)"
|
||||
--rim-light = "rgba(255,180,140,0.08)"
|
||||
--rim-light = "rgba(255,180,140,0.14)"
|
||||
--status-divider = "rgba(255,180,140,0.08)"
|
||||
--sidebar-hover = "rgba(255,180,140,0.10)"
|
||||
--sidebar-icon = "rgba(255,180,140,0.36)"
|
||||
|
||||
665
src/app.cpp
@@ -281,6 +281,38 @@ void App::update()
|
||||
fast_refresh_timer_ = 0.0f;
|
||||
if (state_.connected && !state_.isLocked()) {
|
||||
refreshMiningInfo();
|
||||
|
||||
// Poll getrescaninfo for rescan progress (if rescan flag is set)
|
||||
if (state_.sync.rescanning && fast_worker_) {
|
||||
fast_worker_->post([this]() -> rpc::RPCWorker::MainCb {
|
||||
try {
|
||||
auto info = rpc_->call("getrescaninfo");
|
||||
bool rescanning = info.value("rescanning", false);
|
||||
float progress = 0.0f;
|
||||
if (info.contains("rescan_progress")) {
|
||||
std::string progStr = info["rescan_progress"].get<std::string>();
|
||||
try { progress = std::stof(progStr) * 100.0f; } catch (...) {}
|
||||
}
|
||||
return [this, rescanning, progress]() {
|
||||
if (rescanning) {
|
||||
state_.sync.rescanning = true;
|
||||
if (progress > 0.0f) {
|
||||
state_.sync.rescan_progress = progress / 100.0f;
|
||||
}
|
||||
} else if (state_.sync.rescanning) {
|
||||
// Rescan just finished
|
||||
ui::Notifications::instance().success("Blockchain rescan complete");
|
||||
state_.sync.rescanning = false;
|
||||
state_.sync.rescan_progress = 1.0f;
|
||||
state_.sync.rescan_status.clear();
|
||||
}
|
||||
};
|
||||
} catch (...) {
|
||||
// RPC not available yet or failed
|
||||
return [](){};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Poll xmrig stats every ~2 seconds (use a simple toggle)
|
||||
@@ -315,6 +347,100 @@ void App::update()
|
||||
} else if (xmrig_manager_ && !xmrig_manager_->isRunning()) {
|
||||
state_.pool_mining.xmrig_running = false;
|
||||
}
|
||||
|
||||
// Check daemon output for rescan progress
|
||||
if (embedded_daemon_ && embedded_daemon_->isRunning()) {
|
||||
std::string newOutput = embedded_daemon_->getOutputSince(daemon_output_offset_);
|
||||
if (!newOutput.empty()) {
|
||||
// Look for rescan progress patterns in new output
|
||||
// Hush patterns: "Still rescanning. At block X. Progress=Y" or "Rescanning..." with percentage
|
||||
bool foundRescan = false;
|
||||
float rescanPct = 0.0f;
|
||||
|
||||
// Search line by line for rescan info
|
||||
size_t pos = 0;
|
||||
while (pos < newOutput.size()) {
|
||||
size_t eol = newOutput.find('\n', pos);
|
||||
if (eol == std::string::npos) eol = newOutput.size();
|
||||
std::string line = newOutput.substr(pos, eol - pos);
|
||||
pos = eol + 1;
|
||||
|
||||
// Check for "Rescanning from height" (rescan starting)
|
||||
if (line.find("Rescanning from height") != std::string::npos ||
|
||||
line.find("Rescanning last") != std::string::npos) {
|
||||
foundRescan = true;
|
||||
state_.sync.rescan_status = line;
|
||||
}
|
||||
|
||||
// Check for "Still rescanning" with progress
|
||||
auto stillIdx = line.find("Still rescanning");
|
||||
if (stillIdx != std::string::npos) {
|
||||
foundRescan = true;
|
||||
// Try to extract progress (Progress=0.XXXX)
|
||||
auto progIdx = line.find("Progress=");
|
||||
if (progIdx != std::string::npos) {
|
||||
size_t numStart = progIdx + 9; // strlen("Progress=")
|
||||
size_t numEnd = numStart;
|
||||
while (numEnd < line.size() && (std::isdigit(line[numEnd]) || line[numEnd] == '.')) {
|
||||
numEnd++;
|
||||
}
|
||||
if (numEnd > numStart) {
|
||||
try {
|
||||
rescanPct = std::stof(line.substr(numStart, numEnd - numStart)) * 100.0f;
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
state_.sync.rescan_status = line;
|
||||
}
|
||||
|
||||
// Check for "Rescanning..." with percentage (ShowProgress output)
|
||||
auto rescIdx = line.find("Rescanning...");
|
||||
if (rescIdx != std::string::npos) {
|
||||
foundRescan = true;
|
||||
// Try to extract percentage
|
||||
auto pctIdx = line.find('%');
|
||||
if (pctIdx != std::string::npos && pctIdx > 0) {
|
||||
// Walk backwards to find the number
|
||||
size_t numEnd = pctIdx;
|
||||
size_t numStart = numEnd;
|
||||
while (numStart > 0 && (std::isdigit(line[numStart - 1]) || line[numStart - 1] == '.')) {
|
||||
numStart--;
|
||||
}
|
||||
if (numStart < numEnd) {
|
||||
try {
|
||||
rescanPct = std::stof(line.substr(numStart, numEnd - numStart));
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
state_.sync.rescan_status = line;
|
||||
}
|
||||
|
||||
// Check for "Done rescanning" (rescan complete)
|
||||
if (line.find("Done rescanning") != std::string::npos ||
|
||||
line.find("Rescan complete") != std::string::npos) {
|
||||
if (state_.sync.rescanning) {
|
||||
ui::Notifications::instance().success("Blockchain rescan complete");
|
||||
}
|
||||
state_.sync.rescanning = false;
|
||||
state_.sync.rescan_progress = 1.0f;
|
||||
state_.sync.rescan_status.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (foundRescan) {
|
||||
state_.sync.rescanning = true;
|
||||
if (rescanPct > 0.0f) {
|
||||
state_.sync.rescan_progress = rescanPct / 100.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!embedded_daemon_ || !embedded_daemon_->isRunning()) {
|
||||
// Clear rescan state if daemon is not running (but preserve during restart)
|
||||
if (state_.sync.rescanning && state_.sync.rescan_progress >= 0.99f) {
|
||||
state_.sync.rescanning = false;
|
||||
state_.sync.rescan_status.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Regular refresh every 5 seconds
|
||||
@@ -965,12 +1091,8 @@ void App::render()
|
||||
renderBackupDialog();
|
||||
}
|
||||
|
||||
// Encrypt wallet / change passphrase dialogs (from Settings)
|
||||
renderEncryptWalletDialog();
|
||||
renderDecryptWalletDialog();
|
||||
|
||||
// PIN setup / change / remove dialogs (from Settings)
|
||||
renderPinDialogs();
|
||||
// Security overlay dialogs (encrypt, decrypt, PIN) are rendered AFTER ImGui::End()
|
||||
// to ensure they appear on top of all other content
|
||||
|
||||
// Send confirm popup
|
||||
ui::RenderSendConfirmPopup(this);
|
||||
@@ -1020,6 +1142,11 @@ void App::render()
|
||||
ImGui::ShowDemoWindow(&show_demo_window_);
|
||||
}
|
||||
|
||||
// Security overlay dialogs (must render LAST to be on top of everything)
|
||||
renderEncryptWalletDialog();
|
||||
renderDecryptWalletDialog();
|
||||
renderPinDialogs();
|
||||
|
||||
// Render notifications (toast messages)
|
||||
ui::Notifications::instance().render();
|
||||
}
|
||||
@@ -1111,7 +1238,19 @@ void App::renderStatusBar()
|
||||
ImGui::SameLine(0, sbSectionGap);
|
||||
ImGui::TextDisabled("|");
|
||||
ImGui::SameLine(0, sbSeparatorGap);
|
||||
if (state_.sync.syncing) {
|
||||
if (state_.sync.rescanning) {
|
||||
// Show rescan progress (takes priority over sync)
|
||||
// Use animated dots if progress is unknown (0%)
|
||||
if (state_.sync.rescan_progress > 0.01f) {
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "Rescanning %.0f%%",
|
||||
state_.sync.rescan_progress * 100.0f);
|
||||
} else {
|
||||
// Animated "Rescanning..." with pulsing dots
|
||||
int dots = (int)(ImGui::GetTime() * 2.0f) % 4;
|
||||
const char* dotStr = (dots == 0) ? "." : (dots == 1) ? ".." : (dots == 2) ? "..." : "";
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "Rescanning%s", dotStr);
|
||||
}
|
||||
} else if (state_.sync.syncing) {
|
||||
int blocksLeft = state_.sync.headers - state_.sync.blocks;
|
||||
if (blocksLeft < 0) blocksLeft = 0;
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "Syncing %.1f%% (%d left)",
|
||||
@@ -1226,40 +1365,33 @@ void App::renderAboutDialog()
|
||||
auto it = dlg.extraFloats.find(key);
|
||||
return it != dlg.extraFloats.end() ? it->second : fb;
|
||||
};
|
||||
ImGui::OpenPopup("About ObsidianDragon");
|
||||
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowSize(ImVec2(dlgF("width", 400.0f), dlg.height > 0 ? dlg.height : 250.0f));
|
||||
|
||||
const auto& acrylicTheme = ui::GetCurrentAcrylicTheme();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(ui::Layout::spacingXl(), ui::Layout::spacingLg()));
|
||||
if (ui::effects::ImGuiAcrylic::BeginAcrylicPopupModal("About ObsidianDragon", &show_about_, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
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;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ui::effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
|
||||
if (!ui::material::BeginOverlayDialog("About ObsidianDragon", &show_about_, dlgF("width", 400.0f), 0.94f)) {
|
||||
return;
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
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()
|
||||
@@ -1269,62 +1401,55 @@ void App::renderImportKeyDialog()
|
||||
auto it = dlg.extraFloats.find(key);
|
||||
return it != dlg.extraFloats.end() ? it->second : fb;
|
||||
};
|
||||
ImGui::OpenPopup("Import Private Key");
|
||||
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowSize(ImVec2(dlgF("width", 500.0f), dlg.height > 0 ? dlg.height : 200.0f));
|
||||
|
||||
const auto& acrylicTheme = ui::GetCurrentAcrylicTheme();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(ui::Layout::spacingXl(), ui::Layout::spacingLg()));
|
||||
if (ui::effects::ImGuiAcrylic::BeginAcrylicPopupModal("Import Private Key", &show_import_key_, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, "Import Private Key");
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ImGui::TextWrapped("Enter a private key to import. The wallet will rescan the blockchain for transactions.");
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Private Key:");
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##importkey", import_key_input_, sizeof(import_key_input_));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (!import_status_.empty()) {
|
||||
if (import_success_) {
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", import_status_.c_str());
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "%s", import_status_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
int btnFont = (int)dlgF("button-font", 1);
|
||||
float btnW = dlgF("button-width", 120.0f);
|
||||
if (ui::material::StyledButton("Import", ImVec2(btnW, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
std::string key(import_key_input_);
|
||||
if (!key.empty()) {
|
||||
importPrivateKey(key, [this](bool success, const std::string& msg) {
|
||||
import_success_ = success;
|
||||
import_status_ = msg;
|
||||
if (success) {
|
||||
memset(import_key_input_, 0, sizeof(import_key_input_));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Close", ImVec2(btnW, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
show_import_key_ = false;
|
||||
import_status_.clear();
|
||||
memset(import_key_input_, 0, sizeof(import_key_input_));
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ui::effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
if (!ui::material::BeginOverlayDialog("Import Private Key", &show_import_key_, dlgF("width", 500.0f), 0.94f)) {
|
||||
return;
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, "Import Private Key");
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ImGui::TextWrapped("Enter a private key to import. The wallet will rescan the blockchain for transactions.");
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Private Key:");
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##importkey", import_key_input_, sizeof(import_key_input_));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (!import_status_.empty()) {
|
||||
if (import_success_) {
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", import_status_.c_str());
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "%s", import_status_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
int btnFont = (int)dlgF("button-font", 1);
|
||||
float btnW = dlgF("button-width", 120.0f);
|
||||
if (ui::material::StyledButton("Import", ImVec2(btnW, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
std::string key(import_key_input_);
|
||||
if (!key.empty()) {
|
||||
importPrivateKey(key, [this](bool success, const std::string& msg) {
|
||||
import_success_ = success;
|
||||
import_status_ = msg;
|
||||
if (success) {
|
||||
memset(import_key_input_, 0, sizeof(import_key_input_));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Close", ImVec2(btnW, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
show_import_key_ = false;
|
||||
import_status_.clear();
|
||||
memset(import_key_input_, 0, sizeof(import_key_input_));
|
||||
}
|
||||
|
||||
ui::material::EndOverlayDialog();
|
||||
}
|
||||
|
||||
void App::renderExportKeyDialog()
|
||||
@@ -1334,78 +1459,71 @@ void App::renderExportKeyDialog()
|
||||
auto it = dlg.extraFloats.find(key);
|
||||
return it != dlg.extraFloats.end() ? it->second : fb;
|
||||
};
|
||||
ImGui::OpenPopup("Export Private Key");
|
||||
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowSize(ImVec2(dlgF("width", 600.0f), dlg.height > 0 ? dlg.height : 300.0f));
|
||||
|
||||
const auto& acrylicTheme = ui::GetCurrentAcrylicTheme();
|
||||
int btnFont = (int)dlgF("button-font", 1);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(ui::Layout::spacingXl(), ui::Layout::spacingLg()));
|
||||
if (ui::effects::ImGuiAcrylic::BeginAcrylicPopupModal("Export Private Key", &show_export_key_, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, "Export Private Key");
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ImGui::TextColored(ImVec4(0.9f, 0.4f, 0.4f, 1.0f),
|
||||
"WARNING: Anyone with this key can spend your coins!");
|
||||
ImGui::Spacing();
|
||||
|
||||
// Address selector
|
||||
ImGui::Text("Select Address:");
|
||||
std::vector<std::string> all_addrs;
|
||||
for (const auto& a : state_.t_addresses) all_addrs.push_back(a.address);
|
||||
for (const auto& a : state_.z_addresses) all_addrs.push_back(a.address);
|
||||
|
||||
int addrFrontLen = (int)dlgF("addr-front-len", 20);
|
||||
int addrBackLen = (int)dlgF("addr-back-len", 8);
|
||||
if (ImGui::BeginCombo("##exportaddr", export_address_.empty() ? "Select address..." : export_address_.c_str())) {
|
||||
for (const auto& addr : all_addrs) {
|
||||
bool selected = (export_address_ == addr);
|
||||
std::string display = addr.substr(0, addrFrontLen) + "..." + addr.substr(addr.length() - addrBackLen);
|
||||
if (ImGui::Selectable(display.c_str(), selected)) {
|
||||
export_address_ = addr;
|
||||
export_result_.clear();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Export", ImVec2(0, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
if (!export_address_.empty()) {
|
||||
exportPrivateKey(export_address_, [this](const std::string& key) {
|
||||
export_result_ = key;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (!export_result_.empty()) {
|
||||
ImGui::Text("Private Key:");
|
||||
ImGui::InputTextMultiline("##exportresult", (char*)export_result_.c_str(),
|
||||
export_result_.size() + 1, ImVec2(-1, dlgF("key-display-height", 60.0f)), ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
if (ui::material::StyledButton("Copy to Clipboard", ImVec2(0, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
ImGui::SetClipboardText(export_result_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
int closeBtnFont = (int)dlgF("close-button-font", -1);
|
||||
if (closeBtnFont < 0) closeBtnFont = btnFont;
|
||||
if (ui::material::StyledButton("Close", ImVec2(dlgF("close-button-width", 120.0f), 0), ui::material::resolveButtonFont(closeBtnFont))) {
|
||||
show_export_key_ = false;
|
||||
export_result_.clear();
|
||||
export_address_.clear();
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ui::effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
|
||||
if (!ui::material::BeginOverlayDialog("Export Private Key", &show_export_key_, dlgF("width", 600.0f), 0.94f)) {
|
||||
return;
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, "Export Private Key");
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ImGui::TextColored(ImVec4(0.9f, 0.4f, 0.4f, 1.0f),
|
||||
"WARNING: Anyone with this key can spend your coins!");
|
||||
ImGui::Spacing();
|
||||
|
||||
// Address selector
|
||||
ImGui::Text("Select Address:");
|
||||
std::vector<std::string> all_addrs;
|
||||
for (const auto& a : state_.t_addresses) all_addrs.push_back(a.address);
|
||||
for (const auto& a : state_.z_addresses) all_addrs.push_back(a.address);
|
||||
|
||||
int addrFrontLen = (int)dlgF("addr-front-len", 20);
|
||||
int addrBackLen = (int)dlgF("addr-back-len", 8);
|
||||
if (ImGui::BeginCombo("##exportaddr", export_address_.empty() ? "Select address..." : export_address_.c_str())) {
|
||||
for (const auto& addr : all_addrs) {
|
||||
bool selected = (export_address_ == addr);
|
||||
std::string display = addr.substr(0, addrFrontLen) + "..." + addr.substr(addr.length() - addrBackLen);
|
||||
if (ImGui::Selectable(display.c_str(), selected)) {
|
||||
export_address_ = addr;
|
||||
export_result_.clear();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Export", ImVec2(0, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
if (!export_address_.empty()) {
|
||||
exportPrivateKey(export_address_, [this](const std::string& key) {
|
||||
export_result_ = key;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (!export_result_.empty()) {
|
||||
ImGui::Text("Private Key:");
|
||||
ImGui::InputTextMultiline("##exportresult", (char*)export_result_.c_str(),
|
||||
export_result_.size() + 1, ImVec2(-1, dlgF("key-display-height", 60.0f)), ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
if (ui::material::StyledButton("Copy to Clipboard", ImVec2(0, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
ImGui::SetClipboardText(export_result_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
int closeBtnFont = (int)dlgF("close-button-font", -1);
|
||||
if (closeBtnFont < 0) closeBtnFont = btnFont;
|
||||
if (ui::material::StyledButton("Close", ImVec2(dlgF("close-button-width", 120.0f), 0), ui::material::resolveButtonFont(closeBtnFont))) {
|
||||
show_export_key_ = false;
|
||||
export_result_.clear();
|
||||
export_address_.clear();
|
||||
}
|
||||
|
||||
ui::material::EndOverlayDialog();
|
||||
}
|
||||
|
||||
void App::renderBackupDialog()
|
||||
@@ -1415,59 +1533,52 @@ void App::renderBackupDialog()
|
||||
auto it = dlg.extraFloats.find(key);
|
||||
return it != dlg.extraFloats.end() ? it->second : fb;
|
||||
};
|
||||
ImGui::OpenPopup("Backup Wallet");
|
||||
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowSize(ImVec2(dlgF("width", 500.0f), dlg.height > 0 ? dlg.height : 200.0f));
|
||||
|
||||
const auto& acrylicTheme = ui::GetCurrentAcrylicTheme();
|
||||
int btnFont = (int)dlgF("button-font", 1);
|
||||
float btnW = dlgF("button-width", 120.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(ui::Layout::spacingXl(), ui::Layout::spacingLg()));
|
||||
if (ui::effects::ImGuiAcrylic::BeginAcrylicPopupModal("Backup Wallet", &show_backup_, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, "Backup Wallet");
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ImGui::TextWrapped("Export all private keys to a file. Keep this file secure!");
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Backup File Path:");
|
||||
static char backup_path[512] = "dragonx-backup.txt";
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##backuppath", backup_path, sizeof(backup_path));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (!backup_status_.empty()) {
|
||||
if (backup_success_) {
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", backup_status_.c_str());
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "%s", backup_status_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
if (ui::material::StyledButton("Save Backup", ImVec2(btnW, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
std::string path(backup_path);
|
||||
if (!path.empty()) {
|
||||
backupWallet(path, [this](bool success, const std::string& msg) {
|
||||
backup_success_ = success;
|
||||
backup_status_ = msg;
|
||||
});
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Close", ImVec2(btnW, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
show_backup_ = false;
|
||||
backup_status_.clear();
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ui::effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
|
||||
if (!ui::material::BeginOverlayDialog("Backup Wallet", &show_backup_, dlgF("width", 500.0f), 0.94f)) {
|
||||
return;
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, "Backup Wallet");
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ImGui::TextWrapped("Export all private keys to a file. Keep this file secure!");
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Backup File Path:");
|
||||
static char backup_path[512] = "dragonx-backup.txt";
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##backuppath", backup_path, sizeof(backup_path));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (!backup_status_.empty()) {
|
||||
if (backup_success_) {
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", backup_status_.c_str());
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "%s", backup_status_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
if (ui::material::StyledButton("Save Backup", ImVec2(btnW, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
std::string path(backup_path);
|
||||
if (!path.empty()) {
|
||||
backupWallet(path, [this](bool success, const std::string& msg) {
|
||||
backup_success_ = success;
|
||||
backup_status_ = msg;
|
||||
});
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Close", ImVec2(btnW, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
show_backup_ = false;
|
||||
backup_status_.clear();
|
||||
}
|
||||
|
||||
ui::material::EndOverlayDialog();
|
||||
}
|
||||
|
||||
void App::renderAntivirusHelpDialog()
|
||||
@@ -1475,57 +1586,46 @@ void App::renderAntivirusHelpDialog()
|
||||
#ifdef _WIN32
|
||||
if (!pending_antivirus_dialog_) return;
|
||||
|
||||
ImGui::OpenPopup("Windows Defender Blocked Miner");
|
||||
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowSize(ImVec2(560.0f, 360.0f));
|
||||
|
||||
const auto& acrylicTheme = ui::GetCurrentAcrylicTheme();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(ui::Layout::spacingXl(), ui::Layout::spacingLg()));
|
||||
|
||||
if (ui::effects::ImGuiAcrylic::BeginAcrylicPopupModal("Windows Defender Blocked Miner", &pending_antivirus_dialog_,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, "Windows Defender Blocked xmrig");
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
|
||||
ImGui::TextWrapped(
|
||||
"Mining software is often flagged as potentially unwanted. "
|
||||
"Follow these steps to enable pool mining:");
|
||||
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, "Step 1: Add Exclusion");
|
||||
ImGui::BulletText("Open Windows Security > Virus & threat protection");
|
||||
ImGui::BulletText("Click Manage settings > Exclusions > Add or remove");
|
||||
ImGui::BulletText("Add folder: %%APPDATA%%\\ObsidianDragon\\");
|
||||
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, "Step 2: Restore from Quarantine (if needed)");
|
||||
ImGui::BulletText("Windows Security > Protection history");
|
||||
ImGui::BulletText("Find xmrig.exe and click Restore");
|
||||
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, "Step 3: Restart wallet and try again");
|
||||
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingMd()));
|
||||
ImGui::Separator();
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
|
||||
float btnW = 160.0f;
|
||||
if (ui::material::StyledButton("Open Windows Security", ImVec2(btnW, 0))) {
|
||||
// Open Windows Security app to the exclusions page
|
||||
ShellExecuteA(NULL, "open", "windowsdefender://threat", NULL, NULL, SW_SHOWNORMAL);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Close", ImVec2(100.0f, 0))) {
|
||||
pending_antivirus_dialog_ = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ui::effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
if (!ui::material::BeginOverlayDialog("Windows Defender Blocked Miner", &pending_antivirus_dialog_, 560.0f, 0.94f)) {
|
||||
return;
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, "Windows Defender Blocked xmrig");
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
|
||||
ImGui::TextWrapped(
|
||||
"Mining software is often flagged as potentially unwanted. "
|
||||
"Follow these steps to enable pool mining:");
|
||||
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, "Step 1: Add Exclusion");
|
||||
ImGui::BulletText("Open Windows Security > Virus & threat protection");
|
||||
ImGui::BulletText("Click Manage settings > Exclusions > Add or remove");
|
||||
ImGui::BulletText("Add folder: %%APPDATA%%\\ObsidianDragon\\");
|
||||
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, "Step 2: Restore from Quarantine (if needed)");
|
||||
ImGui::BulletText("Windows Security > Protection history");
|
||||
ImGui::BulletText("Find xmrig.exe and click Restore");
|
||||
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
ui::material::Type().text(ui::material::TypeStyle::Subtitle2, "Step 3: Restart wallet and try again");
|
||||
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingMd()));
|
||||
ImGui::Separator();
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
|
||||
float btnW = 160.0f;
|
||||
if (ui::material::StyledButton("Open Windows Security", ImVec2(btnW, 0))) {
|
||||
// Open Windows Security app to the exclusions page
|
||||
ShellExecuteA(NULL, "open", "windowsdefender://threat", NULL, NULL, SW_SHOWNORMAL);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Close", ImVec2(100.0f, 0))) {
|
||||
pending_antivirus_dialog_ = false;
|
||||
}
|
||||
|
||||
ui::material::EndOverlayDialog();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1723,6 +1823,43 @@ bool App::isEmbeddedDaemonRunning() const
|
||||
return embedded_daemon_ && embedded_daemon_->isRunning();
|
||||
}
|
||||
|
||||
void App::rescanBlockchain()
|
||||
{
|
||||
if (!isUsingEmbeddedDaemon() || !embedded_daemon_) {
|
||||
ui::Notifications::instance().warning(
|
||||
"Rescan requires embedded daemon. Restart your daemon with -rescan manually.");
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_LOGF("[App] Starting blockchain rescan - stopping daemon first\n");
|
||||
ui::Notifications::instance().info("Restarting daemon with -rescan flag...");
|
||||
|
||||
// Initialize rescan state for status bar display
|
||||
state_.sync.rescanning = true;
|
||||
state_.sync.rescan_progress = 0.0f;
|
||||
state_.sync.rescan_status = "Starting rescan...";
|
||||
|
||||
// Set rescan flag BEFORE stopping so it's ready when we restart
|
||||
embedded_daemon_->setRescanOnNextStart(true);
|
||||
DEBUG_LOGF("[App] Rescan flag set, rescanOnNextStart=%d\n", embedded_daemon_->rescanOnNextStart() ? 1 : 0);
|
||||
|
||||
// Stop daemon, then restart
|
||||
std::thread([this]() {
|
||||
DEBUG_LOGF("[App] Stopping daemon for rescan...\n");
|
||||
stopEmbeddedDaemon();
|
||||
|
||||
// Wait for daemon to fully stop
|
||||
DEBUG_LOGF("[App] Waiting for daemon to fully stop...\n");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
|
||||
// Reset output offset so we parse fresh output for rescan progress
|
||||
daemon_output_offset_ = 0;
|
||||
|
||||
DEBUG_LOGF("[App] Starting daemon with rescan flag=%d\n", embedded_daemon_->rescanOnNextStart() ? 1 : 0);
|
||||
startEmbeddedDaemon();
|
||||
}).detach();
|
||||
}
|
||||
|
||||
double App::getDaemonMemoryUsageMB() const
|
||||
{
|
||||
// If we have an embedded daemon with a tracked process handle, use it
|
||||
|
||||
@@ -241,6 +241,7 @@ public:
|
||||
bool isEmbeddedDaemonRunning() const;
|
||||
bool isUsingEmbeddedDaemon() const { return use_embedded_daemon_; }
|
||||
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use; }
|
||||
void rescanBlockchain(); // restart daemon with -rescan flag
|
||||
|
||||
// Get daemon memory usage in MB (uses embedded daemon handle if available,
|
||||
// falls back to platform-level process scan for external daemons)
|
||||
@@ -304,6 +305,7 @@ public:
|
||||
void showDecryptDialog() {
|
||||
show_decrypt_dialog_ = true;
|
||||
decrypt_phase_ = 0; // passphrase entry
|
||||
decrypt_step_ = 0;
|
||||
decrypt_status_.clear();
|
||||
decrypt_in_progress_ = false;
|
||||
memset(decrypt_pass_buf_, 0, sizeof(decrypt_pass_buf_));
|
||||
@@ -368,6 +370,7 @@ private:
|
||||
bool use_embedded_daemon_ = true;
|
||||
std::string daemon_status_;
|
||||
mutable std::string daemon_mem_diag_; // diagnostic info for daemon memory detection
|
||||
size_t daemon_output_offset_ = 0; // for incremental output parsing (rescan detection)
|
||||
|
||||
// Export/Import state
|
||||
std::string export_result_;
|
||||
@@ -505,6 +508,7 @@ private:
|
||||
// Decrypt wallet dialog state
|
||||
bool show_decrypt_dialog_ = false;
|
||||
int decrypt_phase_ = 0; // 0=passphrase, 1=working, 2=done, 3=error
|
||||
int decrypt_step_ = 0; // 0=unlock, 1=export, 2=stop, 3=rename, 4=restart, 5=import
|
||||
char decrypt_pass_buf_[256] = {};
|
||||
std::string decrypt_status_;
|
||||
bool decrypt_in_progress_ = false;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include "ui/material/typography.h"
|
||||
#include "ui/material/draw_helpers.h"
|
||||
#include "ui/schema/ui_schema.h"
|
||||
#include "ui/theme.h"
|
||||
#include "ui/effects/imgui_acrylic.h"
|
||||
#include "util/platform.h"
|
||||
#include "util/secure_vault.h"
|
||||
#include "util/perf_log.h"
|
||||
@@ -694,21 +696,18 @@ void App::renderLockScreen() {
|
||||
|
||||
void App::renderEncryptWalletDialog() {
|
||||
if (!show_encrypt_dialog_ && !show_change_passphrase_) return;
|
||||
using namespace ui::material;
|
||||
|
||||
// Encrypt wallet dialog — multi-phase: passphrase → encrypting → PIN setup
|
||||
if (show_encrypt_dialog_) {
|
||||
const char* dlgTitle = (encrypt_dialog_phase_ == EncryptDialogPhase::PinSetup)
|
||||
? "Quick-Unlock PIN##EncDlg" : "Encrypt Wallet##EncDlg";
|
||||
? "Quick-Unlock PIN" : "Encrypt Wallet";
|
||||
|
||||
// Prevent closing via X button while encrypting
|
||||
bool canClose = (encrypt_dialog_phase_ != EncryptDialogPhase::Encrypting);
|
||||
bool* pOpen = canClose ? &show_encrypt_dialog_ : nullptr;
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(460, 0), ImGuiCond_FirstUseEver);
|
||||
ImGuiWindowFlags dlgFlags = ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoDocking |
|
||||
ImGuiWindowFlags_AlwaysAutoResize;
|
||||
if (ImGui::Begin(dlgTitle, pOpen, dlgFlags)) {
|
||||
if (BeginOverlayDialog(dlgTitle, pOpen, 460.0f, 0.94f)) {
|
||||
|
||||
// ---- Phase 1: Passphrase entry ----
|
||||
if (encrypt_dialog_phase_ == EncryptDialogPhase::PassphraseEntry) {
|
||||
@@ -911,8 +910,8 @@ void App::renderEncryptWalletDialog() {
|
||||
show_encrypt_dialog_ = false;
|
||||
}
|
||||
}
|
||||
EndOverlayDialog();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
// Clean up saved passphrase if dialog was closed via X button
|
||||
if (!show_encrypt_dialog_ && !enc_dlg_saved_passphrase_.empty()) {
|
||||
@@ -924,9 +923,8 @@ void App::renderEncryptWalletDialog() {
|
||||
|
||||
// Change passphrase dialog
|
||||
if (show_change_passphrase_) {
|
||||
ImGui::SetNextWindowSize(ImVec2(440, 320), ImGuiCond_FirstUseEver);
|
||||
if (ImGui::Begin("Change Passphrase##ChgDlg", &show_change_passphrase_,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking)) {
|
||||
if (BeginOverlayDialog("Change Passphrase", &show_change_passphrase_, 440.0f, 0.94f)) {
|
||||
|
||||
ImGui::Text("Current Passphrase:");
|
||||
ImGui::PushItemWidth(-1);
|
||||
ImGui::InputText("##chg_old", change_old_pass_buf_, sizeof(change_old_pass_buf_),
|
||||
@@ -959,8 +957,8 @@ void App::renderEncryptWalletDialog() {
|
||||
std::string(change_new_pass_buf_));
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
EndOverlayDialog();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -978,16 +976,10 @@ void App::renderDecryptWalletDialog() {
|
||||
if (!show_decrypt_dialog_) return;
|
||||
using namespace ui::material;
|
||||
|
||||
const char* title = "Remove Wallet Encryption##DecryptDlg";
|
||||
bool canClose = (decrypt_phase_ != 1); // don't close while working
|
||||
bool* pOpen = canClose ? &show_decrypt_dialog_ : nullptr;
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(480, 0), ImGuiCond_FirstUseEver);
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoDocking |
|
||||
ImGuiWindowFlags_AlwaysAutoResize;
|
||||
|
||||
if (ImGui::Begin(title, pOpen, flags)) {
|
||||
if (BeginOverlayDialog("Remove Wallet Encryption", pOpen, 480.0f, 0.94f)) {
|
||||
|
||||
// ---- Phase 0: Passphrase entry ----
|
||||
if (decrypt_phase_ == 0) {
|
||||
@@ -1026,6 +1018,7 @@ void App::renderDecryptWalletDialog() {
|
||||
std::string passphrase(decrypt_pass_buf_);
|
||||
memset(decrypt_pass_buf_, 0, sizeof(decrypt_pass_buf_));
|
||||
decrypt_phase_ = 1;
|
||||
decrypt_step_ = 0;
|
||||
decrypt_in_progress_ = true;
|
||||
decrypt_status_ = "Unlocking wallet...";
|
||||
|
||||
@@ -1044,129 +1037,163 @@ void App::renderDecryptWalletDialog() {
|
||||
};
|
||||
}
|
||||
|
||||
// Step 2: Export wallet to temp file
|
||||
std::string exportFile = "obsidian_decrypt_export_" +
|
||||
std::to_string(std::time(nullptr));
|
||||
// Update step on main thread
|
||||
return [this]() {
|
||||
decrypt_step_ = 1;
|
||||
decrypt_status_ = "Exporting wallet keys...";
|
||||
|
||||
// Continue with step 2
|
||||
worker_->post([this]() -> rpc::RPCWorker::MainCb {
|
||||
std::string dataDir = util::Platform::getDragonXDataDir();
|
||||
std::string exportFile = "obsidiandecryptexport" +
|
||||
std::to_string(std::time(nullptr));
|
||||
std::string exportPath = dataDir + exportFile;
|
||||
|
||||
// Update status on main thread
|
||||
// (we can't easily do mid-flow updates from worker,
|
||||
// so we just proceed — the UI shows "working")
|
||||
|
||||
try {
|
||||
rpc_->call("z_exportwallet", {exportFile});
|
||||
} catch (const std::exception& e) {
|
||||
std::string err = e.what();
|
||||
return [this, err]() {
|
||||
decrypt_in_progress_ = false;
|
||||
decrypt_status_ = "Export failed: " + err;
|
||||
decrypt_phase_ = 3;
|
||||
};
|
||||
}
|
||||
|
||||
// Step 3: Stop daemon
|
||||
try {
|
||||
rpc_->call("stop");
|
||||
} catch (...) {
|
||||
// stop often throws because connection drops
|
||||
}
|
||||
|
||||
// Wait for daemon to fully stop
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
|
||||
// Step 4: Rename encrypted wallet.dat → wallet.dat.encrypted.bak
|
||||
std::string dataDir = util::Platform::getDragonXDataDir();
|
||||
std::string walletPath = dataDir + "wallet.dat";
|
||||
std::string backupPath = dataDir + "wallet.dat.encrypted.bak";
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(walletPath, ec)) {
|
||||
// Remove old backup if exists
|
||||
std::filesystem::remove(backupPath, ec);
|
||||
std::filesystem::rename(walletPath, backupPath, ec);
|
||||
if (ec) {
|
||||
std::string err = ec.message();
|
||||
return [this, err]() {
|
||||
decrypt_in_progress_ = false;
|
||||
decrypt_status_ = "Failed to rename wallet.dat: " + err;
|
||||
decrypt_phase_ = 3;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Restart daemon (creates fresh unencrypted wallet)
|
||||
return [this, exportFile]() {
|
||||
decrypt_status_ = "Restarting daemon...";
|
||||
|
||||
auto restartAndImport = [this, exportFile]() {
|
||||
// Give daemon time to stop fully
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
if (isUsingEmbeddedDaemon()) {
|
||||
stopEmbeddedDaemon();
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
startEmbeddedDaemon();
|
||||
}
|
||||
|
||||
// Wait for daemon to become available
|
||||
int maxWait = 60; // seconds
|
||||
bool daemonUp = false;
|
||||
for (int i = 0; i < maxWait; i++) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
try {
|
||||
rpc_->call("getinfo");
|
||||
daemonUp = true;
|
||||
break;
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
if (!daemonUp) {
|
||||
// Schedule error on main thread — can't directly update
|
||||
// but we'll let the import attempt fail
|
||||
}
|
||||
|
||||
// Step 6: Import wallet (includes rescan)
|
||||
try {
|
||||
rpc_->call("z_importwallet", {exportFile});
|
||||
rpc_->call("z_exportwallet", {exportFile});
|
||||
} catch (const std::exception& e) {
|
||||
std::string err = e.what();
|
||||
// Post result back to main thread via worker
|
||||
if (worker_) {
|
||||
worker_->post([this, err]() -> rpc::RPCWorker::MainCb {
|
||||
return [this, err]() {
|
||||
decrypt_in_progress_ = false;
|
||||
decrypt_status_ = "Import failed: " + err +
|
||||
"\nYour encrypted wallet backup is at wallet.dat.encrypted.bak";
|
||||
decrypt_phase_ = 3;
|
||||
};
|
||||
});
|
||||
}
|
||||
return;
|
||||
return [this, err]() {
|
||||
decrypt_in_progress_ = false;
|
||||
decrypt_status_ = "Export failed: " + err;
|
||||
decrypt_phase_ = 3;
|
||||
};
|
||||
}
|
||||
|
||||
// Success — post to main thread
|
||||
if (worker_) {
|
||||
worker_->post([this]() -> rpc::RPCWorker::MainCb {
|
||||
return [this]() {
|
||||
decrypt_in_progress_ = false;
|
||||
decrypt_status_ = "Wallet decrypted successfully!";
|
||||
decrypt_phase_ = 2;
|
||||
return [this, exportPath]() {
|
||||
decrypt_step_ = 2;
|
||||
decrypt_status_ = "Stopping daemon...";
|
||||
|
||||
// Continue with step 3
|
||||
worker_->post([this, exportPath]() -> rpc::RPCWorker::MainCb {
|
||||
try {
|
||||
rpc_->call("stop");
|
||||
} catch (...) {
|
||||
// stop often throws because connection drops
|
||||
}
|
||||
|
||||
// Clean up PIN vault since encryption is gone
|
||||
if (vault_ && vault_->hasVault()) {
|
||||
vault_->removeVault();
|
||||
}
|
||||
if (settings_ && settings_->getPinEnabled()) {
|
||||
settings_->setPinEnabled(false);
|
||||
settings_->save();
|
||||
}
|
||||
// Wait for daemon to fully stop
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
|
||||
refreshWalletEncryptionState();
|
||||
DEBUG_LOGF("[App] Wallet decrypted successfully\n");
|
||||
return [this, exportPath]() {
|
||||
decrypt_step_ = 3;
|
||||
decrypt_status_ = "Backing up encrypted wallet...";
|
||||
|
||||
// Continue with step 4 (rename)
|
||||
worker_->post([this, exportPath]() -> rpc::RPCWorker::MainCb {
|
||||
std::string dataDir = util::Platform::getDragonXDataDir();
|
||||
std::string walletPath = dataDir + "wallet.dat";
|
||||
std::string backupPath = dataDir + "wallet.dat.encrypted.bak";
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(walletPath, ec)) {
|
||||
std::filesystem::remove(backupPath, ec);
|
||||
std::filesystem::rename(walletPath, backupPath, ec);
|
||||
if (ec) {
|
||||
std::string err = ec.message();
|
||||
return [this, err]() {
|
||||
decrypt_in_progress_ = false;
|
||||
decrypt_status_ = "Failed to rename wallet.dat: " + err;
|
||||
decrypt_phase_ = 3;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return [this, exportPath]() {
|
||||
decrypt_step_ = 4;
|
||||
decrypt_status_ = "Restarting daemon...";
|
||||
|
||||
auto restartAndImport = [this, exportPath]() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
if (isUsingEmbeddedDaemon()) {
|
||||
stopEmbeddedDaemon();
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
startEmbeddedDaemon();
|
||||
}
|
||||
|
||||
// Wait for daemon to become available
|
||||
int maxWait = 60;
|
||||
bool daemonUp = false;
|
||||
for (int i = 0; i < maxWait; i++) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
try {
|
||||
rpc_->call("getinfo");
|
||||
daemonUp = true;
|
||||
break;
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
if (!daemonUp) {
|
||||
if (worker_) {
|
||||
worker_->post([this]() -> rpc::RPCWorker::MainCb {
|
||||
return [this]() {
|
||||
decrypt_in_progress_ = false;
|
||||
decrypt_status_ = "Daemon failed to restart";
|
||||
decrypt_phase_ = 3;
|
||||
};
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Update step on main thread
|
||||
if (worker_) {
|
||||
worker_->post([this]() -> rpc::RPCWorker::MainCb {
|
||||
return [this]() {
|
||||
decrypt_step_ = 5;
|
||||
decrypt_status_ = "Importing keys (this may take a while)...";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Step 6: Import wallet (use full path)
|
||||
try {
|
||||
rpc_->call("z_importwallet", {exportPath});
|
||||
} catch (const std::exception& e) {
|
||||
std::string err = e.what();
|
||||
if (worker_) {
|
||||
worker_->post([this, err]() -> rpc::RPCWorker::MainCb {
|
||||
return [this, err]() {
|
||||
decrypt_in_progress_ = false;
|
||||
decrypt_status_ = "Import failed: " + err +
|
||||
"\nYour encrypted wallet backup is at wallet.dat.encrypted.bak";
|
||||
decrypt_phase_ = 3;
|
||||
};
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Success
|
||||
if (worker_) {
|
||||
worker_->post([this]() -> rpc::RPCWorker::MainCb {
|
||||
return [this]() {
|
||||
decrypt_in_progress_ = false;
|
||||
decrypt_status_ = "Wallet decrypted successfully!";
|
||||
decrypt_phase_ = 2;
|
||||
|
||||
if (vault_ && vault_->hasVault()) {
|
||||
vault_->removeVault();
|
||||
}
|
||||
if (settings_ && settings_->getPinEnabled()) {
|
||||
settings_->setPinEnabled(false);
|
||||
settings_->save();
|
||||
}
|
||||
|
||||
refreshWalletEncryptionState();
|
||||
DEBUG_LOGF("[App] Wallet decrypted successfully\n");
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
std::thread(restartAndImport).detach();
|
||||
};
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
std::thread(restartAndImport).detach();
|
||||
};
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -1181,7 +1208,45 @@ void App::renderDecryptWalletDialog() {
|
||||
|
||||
// ---- Phase 1: Working ----
|
||||
} else if (decrypt_phase_ == 1) {
|
||||
ImGui::Text("%s", decrypt_status_.empty() ? "Working..." : decrypt_status_.c_str());
|
||||
// Step checklist
|
||||
const char* stepLabels[] = {
|
||||
"Unlocking wallet",
|
||||
"Exporting wallet keys",
|
||||
"Stopping daemon",
|
||||
"Backing up encrypted wallet",
|
||||
"Restarting daemon",
|
||||
"Importing keys (rescan)"
|
||||
};
|
||||
const int numSteps = 6;
|
||||
|
||||
ImGui::Spacing();
|
||||
for (int i = 0; i < numSteps; i++) {
|
||||
ImGui::PushFont(Type().iconMed());
|
||||
if (i < decrypt_step_) {
|
||||
// Completed
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.9f, 0.4f, 1.0f), ICON_MD_CHECK_CIRCLE);
|
||||
} else if (i == decrypt_step_) {
|
||||
// In progress - animate
|
||||
float alpha = 0.5f + 0.5f * sinf((float)ImGui::GetTime() * 4.0f);
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.0f, alpha), ICON_MD_PENDING);
|
||||
} else {
|
||||
// Not started
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 0.5f), ICON_MD_RADIO_BUTTON_UNCHECKED);
|
||||
}
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
|
||||
if (i == decrypt_step_) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.6f, 1.0f), "%s...", stepLabels[i]);
|
||||
} else if (i < decrypt_step_) {
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.8f, 0.6f, 1.0f), "%s", stepLabels[i]);
|
||||
} else {
|
||||
ImGui::TextDisabled("%s", stepLabels[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Indeterminate progress bar
|
||||
@@ -1242,6 +1307,7 @@ void App::renderDecryptWalletDialog() {
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button("Try Again", ImVec2(btnW, 40))) {
|
||||
decrypt_phase_ = 0;
|
||||
decrypt_step_ = 0;
|
||||
decrypt_status_.clear();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
@@ -1249,8 +1315,8 @@ void App::renderDecryptWalletDialog() {
|
||||
show_decrypt_dialog_ = false;
|
||||
}
|
||||
}
|
||||
EndOverlayDialog();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
@@ -1258,11 +1324,12 @@ void App::renderDecryptWalletDialog() {
|
||||
// ===========================================================================
|
||||
|
||||
void App::renderPinDialogs() {
|
||||
using namespace ui::material;
|
||||
|
||||
// ---- Set PIN dialog ----
|
||||
if (show_pin_setup_) {
|
||||
ImGui::SetNextWindowSize(ImVec2(420, 340), ImGuiCond_FirstUseEver);
|
||||
if (ImGui::Begin("Set PIN##PinSetupDlg", &show_pin_setup_,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking)) {
|
||||
if (BeginOverlayDialog("Set PIN", &show_pin_setup_, 420.0f, 0.94f)) {
|
||||
|
||||
ImGui::TextWrapped(
|
||||
"Set a 4-8 digit PIN for quick wallet unlock. "
|
||||
"Your wallet passphrase will be encrypted with this PIN "
|
||||
@@ -1352,15 +1419,14 @@ void App::renderPinDialogs() {
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
EndOverlayDialog();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// ---- Change PIN dialog ----
|
||||
if (show_pin_change_) {
|
||||
ImGui::SetNextWindowSize(ImVec2(420, 300), ImGuiCond_FirstUseEver);
|
||||
if (ImGui::Begin("Change PIN##PinChangeDlg", &show_pin_change_,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking)) {
|
||||
if (BeginOverlayDialog("Change PIN", &show_pin_change_, 420.0f, 0.94f)) {
|
||||
|
||||
ImGui::TextWrapped("Change your unlock PIN. You need your current PIN and a new PIN.");
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
@@ -1426,15 +1492,14 @@ void App::renderPinDialogs() {
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
EndOverlayDialog();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// ---- Remove PIN dialog ----
|
||||
if (show_pin_remove_) {
|
||||
ImGui::SetNextWindowSize(ImVec2(400, 220), ImGuiCond_FirstUseEver);
|
||||
if (ImGui::Begin("Remove PIN##PinRemoveDlg", &show_pin_remove_,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking)) {
|
||||
if (BeginOverlayDialog("Remove PIN", &show_pin_remove_, 400.0f, 0.94f)) {
|
||||
|
||||
ImGui::TextWrapped(
|
||||
"Enter your current PIN to confirm removal. "
|
||||
"You will need to use your full passphrase to unlock.");
|
||||
@@ -1490,8 +1555,8 @@ void App::renderPinDialogs() {
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
EndOverlayDialog();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -318,6 +318,12 @@ bool EmbeddedDaemon::start(const std::string& binary_path)
|
||||
for (const auto& cat : debug_categories_) {
|
||||
args.push_back("-debug=" + cat);
|
||||
}
|
||||
|
||||
// Add -rescan flag if requested (one-shot)
|
||||
if (rescan_on_next_start_.exchange(false)) {
|
||||
DEBUG_LOGF("[INFO] Adding -rescan flag for blockchain rescan\n");
|
||||
args.push_back("-rescan");
|
||||
}
|
||||
|
||||
if (!startProcess(daemon_path, args)) {
|
||||
DEBUG_LOGF("[ERROR] Failed to start dragonxd process: %s\\n", last_error_.c_str());
|
||||
|
||||
@@ -152,6 +152,12 @@ public:
|
||||
void setDebugCategories(const std::set<std::string>& cats) { debug_categories_ = cats; }
|
||||
const std::set<std::string>& getDebugCategories() const { return debug_categories_; }
|
||||
|
||||
/**
|
||||
* @brief Request a blockchain rescan on the next daemon start
|
||||
*/
|
||||
void setRescanOnNextStart(bool v) { rescan_on_next_start_ = v; }
|
||||
bool rescanOnNextStart() const { return rescan_on_next_start_.load(); }
|
||||
|
||||
/** Get number of consecutive daemon crashes (resets on successful start or manual reset) */
|
||||
int getCrashCount() const { return crash_count_.load(); }
|
||||
/** Reset crash counter (call on successful connection or manual restart) */
|
||||
@@ -189,6 +195,7 @@ private:
|
||||
std::atomic<bool> should_stop_{false};
|
||||
std::set<std::string> debug_categories_;
|
||||
std::atomic<int> crash_count_{0}; // consecutive crash counter
|
||||
std::atomic<bool> rescan_on_next_start_{false}; // -rescan flag for next start
|
||||
};
|
||||
|
||||
} // namespace daemon
|
||||
|
||||
@@ -113,6 +113,11 @@ struct SyncInfo {
|
||||
bool syncing = false;
|
||||
std::string best_blockhash;
|
||||
|
||||
// Rescan state (detected from daemon output)
|
||||
bool rescanning = false;
|
||||
float rescan_progress = 0.0f; // 0.0 - 1.0
|
||||
std::string rescan_status; // e.g. "Rescanning... 25%"
|
||||
|
||||
bool isSynced() const { return !syncing && blocks > 0 && blocks >= headers - 2; }
|
||||
};
|
||||
|
||||
|
||||
@@ -158,6 +158,10 @@ ConnectionConfig Connection::autoDetectConfig()
|
||||
std::string conf_path = getDefaultConfPath();
|
||||
|
||||
if (fs::exists(conf_path)) {
|
||||
// Ensure exportdir is present in existing config
|
||||
ensureExportDir(conf_path);
|
||||
// Ensure encryption flags are present
|
||||
ensureEncryptionEnabled(conf_path);
|
||||
config = parseConfFile(conf_path);
|
||||
config.hush_dir = data_dir;
|
||||
} else {
|
||||
@@ -203,6 +207,9 @@ bool Connection::createDefaultConfig(const std::string& path)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the data directory for exportdir
|
||||
std::string dataDir = getDefaultDataDir();
|
||||
|
||||
file << "# DragonX configuration file\n";
|
||||
file << "# Auto-generated by DragonX Wallet\n";
|
||||
file << "\n";
|
||||
@@ -211,6 +218,9 @@ bool Connection::createDefaultConfig(const std::string& path)
|
||||
file << "rpcport=" << DRAGONX_DEFAULT_RPC_PORT << "\n";
|
||||
file << "server=1\n";
|
||||
file << "txindex=1\n";
|
||||
file << "exportdir=" << dataDir << "\n";
|
||||
file << "experimentalfeatures=1\n";
|
||||
file << "developerencryptwallet=1\n";
|
||||
file << "addnode=195.201.20.230\n";
|
||||
file << "addnode=195.201.137.219\n";
|
||||
|
||||
@@ -220,5 +230,94 @@ bool Connection::createDefaultConfig(const std::string& path)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Connection::ensureExportDir(const std::string& confPath)
|
||||
{
|
||||
// Read existing config and check if exportdir is present
|
||||
std::ifstream inFile(confPath);
|
||||
if (!inFile.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string contents;
|
||||
std::string line;
|
||||
bool hasExportDir = false;
|
||||
|
||||
while (std::getline(inFile, line)) {
|
||||
contents += line + "\n";
|
||||
// Check for exportdir (case insensitive check for key)
|
||||
if (line.find("exportdir=") == 0 || line.find("exportdir =") == 0) {
|
||||
hasExportDir = true;
|
||||
}
|
||||
}
|
||||
inFile.close();
|
||||
|
||||
if (hasExportDir) {
|
||||
return true; // Already has exportdir
|
||||
}
|
||||
|
||||
// Append exportdir to config
|
||||
std::ofstream outFile(confPath, std::ios::app);
|
||||
if (!outFile.is_open()) {
|
||||
DEBUG_LOGF("Failed to open config for appending exportdir: %s\n", confPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string dataDir = getDefaultDataDir();
|
||||
outFile << "\n# Export directory for wallet backups (required for z_exportwallet)\n";
|
||||
outFile << "exportdir=" << dataDir << "\n";
|
||||
outFile.close();
|
||||
|
||||
DEBUG_LOGF("Added exportdir to config: %s\n", confPath.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Connection::ensureEncryptionEnabled(const std::string& confPath)
|
||||
{
|
||||
// Read existing config and check if encryption flags are present
|
||||
std::ifstream inFile(confPath);
|
||||
if (!inFile.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string contents;
|
||||
std::string line;
|
||||
bool hasExperimental = false;
|
||||
bool hasEncrypt = false;
|
||||
|
||||
while (std::getline(inFile, line)) {
|
||||
contents += line + "\n";
|
||||
if (line.find("experimentalfeatures=") == 0 || line.find("experimentalfeatures =") == 0) {
|
||||
hasExperimental = true;
|
||||
}
|
||||
if (line.find("developerencryptwallet=") == 0 || line.find("developerencryptwallet =") == 0) {
|
||||
hasEncrypt = true;
|
||||
}
|
||||
}
|
||||
inFile.close();
|
||||
|
||||
if (hasExperimental && hasEncrypt) {
|
||||
return true; // Already has both flags
|
||||
}
|
||||
|
||||
// Append missing flags to config
|
||||
std::ofstream outFile(confPath, std::ios::app);
|
||||
if (!outFile.is_open()) {
|
||||
DEBUG_LOGF("Failed to open config for appending encryption flags: %s\n", confPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
outFile << "\n# Enable wallet encryption support\n";
|
||||
if (!hasExperimental) {
|
||||
outFile << "experimentalfeatures=1\n";
|
||||
}
|
||||
if (!hasEncrypt) {
|
||||
outFile << "developerencryptwallet=1\n";
|
||||
}
|
||||
outFile.close();
|
||||
|
||||
DEBUG_LOGF("Added encryption flags to config: %s\n", confPath.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
} // namespace dragonx
|
||||
|
||||
@@ -73,6 +73,20 @@ public:
|
||||
*/
|
||||
static bool createDefaultConfig(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief Ensure exportdir is set in DRAGONX.conf
|
||||
* @param confPath Path to the conf file
|
||||
* @return true if exportdir exists or was added
|
||||
*/
|
||||
static bool ensureExportDir(const std::string& confPath);
|
||||
|
||||
/**
|
||||
* @brief Ensure wallet encryption flags are set in DRAGONX.conf
|
||||
* @param confPath Path to the conf file
|
||||
* @return true if flags exist or were added
|
||||
*/
|
||||
static bool ensureEncryptionEnabled(const std::string& confPath);
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "../theme.h"
|
||||
#include "../../util/noise_texture.h"
|
||||
#include "../../embedded/IconsMaterialDesign.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
#include <algorithm>
|
||||
@@ -774,6 +775,171 @@ inline void ApplySmoothScroll(float speed = 12.0f)
|
||||
ImGui::SetScrollY(s.current);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Dialog Title Bar with Close Button
|
||||
// ============================================================================
|
||||
// Draws a custom title bar for modal popups with a title and close button.
|
||||
// Returns true if the close button was clicked.
|
||||
|
||||
inline bool DrawDialogTitleBar(const char* title, bool* p_open, ImU32 accent_col = 0)
|
||||
{
|
||||
bool closeClicked = false;
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
ImVec2 winPos = ImGui::GetWindowPos();
|
||||
float winWidth = ImGui::GetWindowWidth();
|
||||
float barHeight = 36.0f;
|
||||
|
||||
// Get accent color from theme if not provided
|
||||
if (!accent_col) {
|
||||
accent_col = schema::UI().resolveColor("var(--primary)", IM_COL32(76, 175, 80, 255));
|
||||
}
|
||||
|
||||
// Draw title bar background with subtle gradient
|
||||
ImVec2 barMin = winPos;
|
||||
ImVec2 barMax(winPos.x + winWidth, winPos.y + barHeight);
|
||||
|
||||
// Slightly darker top edge
|
||||
ImU32 barTop = IM_COL32(0, 0, 0, 40);
|
||||
ImU32 barBot = IM_COL32(0, 0, 0, 20);
|
||||
dl->AddRectFilledMultiColor(barMin, barMax, barTop, barTop, barBot, barBot);
|
||||
|
||||
// Accent line at bottom of title bar
|
||||
dl->AddLine(ImVec2(barMin.x, barMax.y), ImVec2(barMax.x, barMax.y),
|
||||
ScaleAlpha(accent_col, 0.6f), 1.0f);
|
||||
|
||||
// Title text
|
||||
ImFont* titleFont = Type().subtitle1();
|
||||
ImGui::PushFont(titleFont);
|
||||
ImVec2 titleSize = ImGui::CalcTextSize(title);
|
||||
float titleX = barMin.x + 16.0f;
|
||||
float titleY = barMin.y + (barHeight - titleSize.y) * 0.5f;
|
||||
DrawTextShadow(dl, ImVec2(titleX, titleY), OnSurface(), title);
|
||||
ImGui::PopFont();
|
||||
|
||||
// Close button (X) on right side
|
||||
if (p_open) {
|
||||
float btnSize = 24.0f;
|
||||
float btnX = barMax.x - btnSize - 12.0f;
|
||||
float btnY = barMin.y + (barHeight - btnSize) * 0.5f;
|
||||
ImVec2 btnMin(btnX, btnY);
|
||||
ImVec2 btnMax(btnX + btnSize, btnY + btnSize);
|
||||
|
||||
bool hovered = ImGui::IsMouseHoveringRect(btnMin, btnMax);
|
||||
bool held = false;
|
||||
if (hovered && ImGui::IsMouseClicked(0)) {
|
||||
held = true;
|
||||
}
|
||||
|
||||
// Button background on hover
|
||||
if (hovered) {
|
||||
dl->AddRectFilled(btnMin, btnMax, IM_COL32(255, 255, 255, held ? 40 : 25), 4.0f);
|
||||
}
|
||||
|
||||
// Draw X icon
|
||||
ImGui::PushFont(Type().iconSmall());
|
||||
ImVec2 iconSize = ImGui::CalcTextSize(ICON_MD_CLOSE);
|
||||
float iconX = btnX + (btnSize - iconSize.x) * 0.5f;
|
||||
float iconY = btnY + (btnSize - iconSize.y) * 0.5f;
|
||||
dl->AddText(ImVec2(iconX, iconY),
|
||||
hovered ? IM_COL32(255, 255, 255, 255) : OnSurfaceMedium(),
|
||||
ICON_MD_CLOSE);
|
||||
ImGui::PopFont();
|
||||
|
||||
// Handle click
|
||||
if (hovered && ImGui::IsMouseReleased(0)) {
|
||||
*p_open = false;
|
||||
closeClicked = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve space for title bar so content starts below it
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + barHeight + 8.0f);
|
||||
|
||||
return closeClicked;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Fullscreen Overlay Dialog
|
||||
// ============================================================================
|
||||
// Creates a fullscreen semi-transparent overlay with a centered card dialog.
|
||||
// Similar to the shutdown screen pattern but for interactive dialogs.
|
||||
|
||||
inline bool BeginOverlayDialog(const char* title, bool* p_open, float cardWidth = 460.0f, float scrimOpacity = 0.92f)
|
||||
{
|
||||
ImGuiViewport* vp = ImGui::GetMainViewport();
|
||||
ImVec2 vp_pos = vp->Pos;
|
||||
ImVec2 vp_size = vp->Size;
|
||||
|
||||
// Fullscreen scrim overlay
|
||||
ImGui::SetNextWindowPos(vp_pos);
|
||||
ImGui::SetNextWindowSize(vp_size);
|
||||
ImGui::SetNextWindowFocus();
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.04f, 0.04f, 0.06f, scrimOpacity));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
|
||||
bool opened = ImGui::Begin("##OverlayScrim", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoNav |
|
||||
ImGuiWindowFlags_NoSavedSettings);
|
||||
|
||||
if (!opened) {
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(3);
|
||||
ImGui::PopStyleColor();
|
||||
return false;
|
||||
}
|
||||
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
|
||||
// Calculate card position (centered)
|
||||
float cardX = vp_pos.x + (vp_size.x - cardWidth) * 0.5f;
|
||||
float cardY = vp_pos.y + vp_size.y * 0.15f;
|
||||
|
||||
// Draw glass card background
|
||||
ImVec2 cardMin(cardX, cardY);
|
||||
ImVec2 cardMax(cardX + cardWidth, vp_pos.y + vp_size.y * 0.85f);
|
||||
|
||||
// Card background with glass effect
|
||||
GlassPanelSpec cardGlass;
|
||||
cardGlass.rounding = 16.0f;
|
||||
cardGlass.fillAlpha = 35;
|
||||
cardGlass.borderAlpha = 50;
|
||||
cardGlass.borderWidth = 1.0f;
|
||||
DrawGlassPanel(dl, cardMin, cardMax, cardGlass);
|
||||
|
||||
// Set up child region for card content
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardX, cardY));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 16.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(28, 24));
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0)); // Transparent - glass already drawn
|
||||
|
||||
bool childVisible = ImGui::BeginChild("##OverlayDialogContent",
|
||||
ImVec2(cardWidth, 0), // 0 height = auto-size
|
||||
ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysUseWindowPadding,
|
||||
ImGuiWindowFlags_NoScrollbar);
|
||||
|
||||
// Draw title bar with close button
|
||||
if (childVisible && title) {
|
||||
DrawDialogTitleBar(title, p_open);
|
||||
}
|
||||
|
||||
return childVisible;
|
||||
}
|
||||
|
||||
inline void EndOverlayDialog()
|
||||
{
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleColor(); // ChildBg
|
||||
ImGui::PopStyleVar(2); // ChildRounding, WindowPadding (for child)
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(3); // WindowRounding, WindowBorderSize, WindowPadding (for scrim)
|
||||
ImGui::PopStyleColor(); // WindowBg scrim
|
||||
}
|
||||
|
||||
} // namespace material
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
|
||||
@@ -108,6 +108,7 @@ static bool sp_stop_external_daemon = false;
|
||||
static std::set<std::string> sp_debug_categories;
|
||||
static bool sp_debug_cats_dirty = false; // true when changed but daemon not yet restarted
|
||||
static bool sp_debug_expanded = false; // collapsible card state
|
||||
static bool sp_confirm_clear_ztx = false; // confirmation dialog for clearing z-tx history
|
||||
|
||||
// (APPEARANCE card now uses ChannelsSplit like all other cards)
|
||||
|
||||
@@ -969,46 +970,41 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Indent(pad);
|
||||
|
||||
float contentW = availWidth - pad * 2;
|
||||
bool wideBtns = availWidth >= S.drawElement("components.settings-page", "compact-breakpoint").size;
|
||||
|
||||
// Content-aware button sizing: uniform per-row width based on widest label
|
||||
float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(130.0f);
|
||||
float btnSpacing = Layout::spacingMd();
|
||||
float btnPad = S.drawElement("components.settings-page", "wallet-btn-padding").sizeOr(24.0f);
|
||||
auto rowBtnW = [&](std::initializer_list<const char*> labels) -> float {
|
||||
float maxTextW = 0;
|
||||
for (auto* l : labels) maxTextW = std::max(maxTextW, ImGui::CalcTextSize(l).x);
|
||||
return std::max(minBtnW, maxTextW + btnPad * 2);
|
||||
};
|
||||
|
||||
// Calculate button width that fits available space
|
||||
// 6 buttons total: on wide screens 3+3, on narrow screens 2+2+2
|
||||
int btnsPerRow = (contentW >= 600.0f) ? 3 : 2;
|
||||
float bw = (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow;
|
||||
// Clamp to reasonable size
|
||||
float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(100.0f);
|
||||
bw = std::max(minBtnW, bw);
|
||||
|
||||
// Row 1 — Tools & Actions
|
||||
{
|
||||
float bw = rowBtnW({"Address Book...", "Validate Address...", "Request Payment...", "Shield Mining...", "Merge to Address...", "Clear Z-Tx History"});
|
||||
if (TactileButton("Address Book...", ImVec2(bw, 0), S.resolveFont("button")))
|
||||
AddressBookDialog::show();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Manage saved addresses for quick sending");
|
||||
ImGui::SameLine(0, Layout::spacingMd());
|
||||
ImGui::SameLine(0, btnSpacing);
|
||||
if (TactileButton("Validate Address...", ImVec2(bw, 0), S.resolveFont("button")))
|
||||
ValidateAddressDialog::show();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Check if a DragonX address is valid");
|
||||
ImGui::SameLine(0, Layout::spacingMd());
|
||||
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
|
||||
if (TactileButton("Request Payment...", ImVec2(bw, 0), S.resolveFont("button")))
|
||||
RequestPaymentDialog::show();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Generate a payment request with QR code");
|
||||
if (wideBtns) ImGui::SameLine(0, Layout::spacingMd()); else ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
if (btnsPerRow >= 3) { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } else { ImGui::SameLine(0, btnSpacing); }
|
||||
if (TactileButton("Shield Mining...", ImVec2(bw, 0), S.resolveFont("button")))
|
||||
ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase);
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Move transparent mining rewards to a shielded address");
|
||||
ImGui::SameLine(0, Layout::spacingMd());
|
||||
ImGui::SameLine(0, btnSpacing);
|
||||
if (TactileButton("Merge to Address...", ImVec2(bw, 0), S.resolveFont("button")))
|
||||
ShieldDialog::show(ShieldDialog::Mode::MergeToAddress);
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Consolidate multiple UTXOs into one address");
|
||||
ImGui::SameLine(0, Layout::spacingMd());
|
||||
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
|
||||
if (TactileButton("Clear Z-Tx History", ImVec2(bw, 0), S.resolveFont("button"))) {
|
||||
std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json";
|
||||
if (util::Platform::deleteFile(ztx_file))
|
||||
Notifications::instance().success("Z-transaction history cleared");
|
||||
else
|
||||
Notifications::instance().info("No history file found");
|
||||
sp_confirm_clear_ztx = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Delete locally cached z-transaction history");
|
||||
}
|
||||
@@ -1348,24 +1344,7 @@ void RenderSettingsPage(App* app) {
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("Verify the RPC connection to the daemon");
|
||||
ImGui::SameLine(0, Layout::spacingMd());
|
||||
if (TactileButton("Rescan Blockchain", ImVec2(nodeBtnW, 0), btnFont)) {
|
||||
if (app->rpc() && app->rpc()->isConnected() && app->worker()) {
|
||||
Notifications::instance().info("Starting blockchain rescan...");
|
||||
app->worker()->post([rpc = app->rpc()]() -> rpc::RPCWorker::MainCb {
|
||||
try {
|
||||
rpc->call("rescanblockchain", {0});
|
||||
return []() {
|
||||
Notifications::instance().success("Blockchain rescan started");
|
||||
};
|
||||
} catch (const std::exception& e) {
|
||||
std::string err = e.what();
|
||||
return [err]() {
|
||||
Notifications::instance().error("Failed to start rescan: " + err);
|
||||
};
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Notifications::instance().warning("Not connected to daemon");
|
||||
}
|
||||
app->rescanBlockchain();
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("Rescan the blockchain for missing transactions");
|
||||
ImGui::EndDisabled();
|
||||
@@ -1839,6 +1818,48 @@ void RenderSettingsPage(App* app) {
|
||||
|
||||
ImGui::EndChild(); // ##SettingsPageScroll
|
||||
|
||||
// Confirmation dialog for clearing z-tx history
|
||||
if (sp_confirm_clear_ztx) {
|
||||
if (BeginOverlayDialog("Confirm Clear Z-Tx History", &sp_confirm_clear_ztx, 480.0f, 0.94f)) {
|
||||
ImGui::PushFont(Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Warning");
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped(
|
||||
"Clearing z-transaction history may cause your shielded balance to show as 0 "
|
||||
"until a wallet rescan is performed.");
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped(
|
||||
"If this happens, you will need to re-import your z-address private keys with "
|
||||
"rescan enabled to recover your balance.");
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button("Cancel", ImVec2(btnW, 40))) {
|
||||
sp_confirm_clear_ztx = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.3f, 0.3f, 1.0f));
|
||||
if (ImGui::Button("Clear Anyway", ImVec2(btnW, 40))) {
|
||||
std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json";
|
||||
if (util::Platform::deleteFile(ztx_file)) {
|
||||
Notifications::instance().success("Z-transaction history cleared");
|
||||
} else {
|
||||
Notifications::instance().info("No history file found");
|
||||
}
|
||||
sp_confirm_clear_ztx = false;
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#include "about_dialog.h"
|
||||
#include "../../app.h"
|
||||
#include "../../config/version.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/type.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
@@ -25,16 +23,7 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
auto versionLbl = S.label("dialogs.about", "version-label");
|
||||
auto editionLbl = S.label("dialogs.about", "edition-label");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
|
||||
// Use acrylic modal popup from current theme
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("About ObsidianDragon");
|
||||
if (!effects::ImGuiAcrylic::BeginAcrylicPopupModal("About ObsidianDragon", p_open,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
if (!material::BeginOverlayDialog("About ObsidianDragon", p_open, win.width, 0.94f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -173,7 +162,7 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
}
|
||||
|
||||
ImGui::PopFont(); // Body2
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#include "../../app.h"
|
||||
#include "../../data/address_book.h"
|
||||
#include "../notifications.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "imgui.h"
|
||||
@@ -67,14 +65,7 @@ void AddressBookDialog::render(App* app)
|
||||
auto notesInput = S.input("dialogs.address-book", "notes-input");
|
||||
auto actionBtn = S.button("dialogs.address-book", "action-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Address Book");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Address Book", &s_open,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog("Address Book", &s_open, win.width, 0.94f)) {
|
||||
auto& book = getAddressBook();
|
||||
|
||||
// Toolbar
|
||||
@@ -192,15 +183,16 @@ void AddressBookDialog::render(App* app)
|
||||
|
||||
// Status line
|
||||
ImGui::TextDisabled("%zu addresses saved", book.size());
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
|
||||
// Add dialog
|
||||
if (s_show_add_dialog) {
|
||||
ImGui::OpenPopup("Add Address");
|
||||
}
|
||||
|
||||
// Re-use center from above (already defined at start of render)
|
||||
// Re-use center for sub-dialogs
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
|
||||
if (ImGui::BeginPopupModal("Add Address", &s_show_add_dialog, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <string>
|
||||
@@ -62,14 +61,7 @@ void BackupWalletDialog::render(App* app)
|
||||
auto backupBtn = S.button("dialogs.backup-wallet", "backup-button");
|
||||
auto closeBtn = S.button("dialogs.backup-wallet", "close-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowFocus();
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Backup Wallet");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Backup Wallet", &s_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog("Backup Wallet", &s_open, win.width, 0.94f)) {
|
||||
ImGui::TextWrapped(
|
||||
"Create a backup of your wallet.dat file. This file contains all your "
|
||||
"private keys and transaction history. Store the backup in a secure location."
|
||||
@@ -191,8 +183,8 @@ void BackupWalletDialog::render(App* app)
|
||||
ImGui::BulletText("Store backups on external drives or cloud storage");
|
||||
ImGui::BulletText("Create multiple backups in different locations");
|
||||
ImGui::BulletText("Test restoring from backup periodically");
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
#include "../notifications.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
@@ -101,14 +99,7 @@ void BlockInfoDialog::render(App* app)
|
||||
auto hashBackLbl = S.label("dialogs.block-info", "hash-back-label");
|
||||
auto closeBtn = S.button("dialogs.block-info", "close-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowFocus();
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Block Information");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Block Information", &s_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog("Block Information", &s_open, win.width, 0.94f)) {
|
||||
auto* rpc = app->rpc();
|
||||
const auto& state = app->getWalletState();
|
||||
|
||||
@@ -307,8 +298,8 @@ void BlockInfoDialog::render(App* app)
|
||||
if (material::StyledButton("Close", ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -347,12 +347,8 @@ void ConsoleTab::render(daemon::EmbeddedDaemon* daemon, rpc::RPCClient* rpc, rpc
|
||||
void ConsoleTab::renderCommandsPopupModal()
|
||||
{
|
||||
if (!show_commands_popup_) {
|
||||
renderCommandsPopup();
|
||||
return;
|
||||
}
|
||||
// Called at top-level window scope so the modal blocks all input.
|
||||
ImGui::OpenPopup("RPC Command Reference");
|
||||
show_commands_popup_ = false;
|
||||
renderCommandsPopup();
|
||||
}
|
||||
|
||||
@@ -1287,18 +1283,8 @@ void ConsoleTab::renderCommandsPopup()
|
||||
{
|
||||
using namespace material;
|
||||
|
||||
// Center the modal
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
float popW = std::min(schema::UI().drawElement("tabs.console", "popup-max-width").size, ImGui::GetMainViewport()->Size.x * schema::UI().drawElement("tabs.console", "popup-width-ratio").size);
|
||||
float popH = std::min(schema::UI().drawElement("tabs.console", "popup-max-height").size, ImGui::GetMainViewport()->Size.y * schema::UI().drawElement("tabs.console", "popup-height-ratio").size);
|
||||
ImGui::SetNextWindowSize(ImVec2(popW, popH), ImGuiCond_Appearing);
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Layout::spacingXl(), Layout::spacingLg()));
|
||||
if (!effects::ImGuiAcrylic::BeginAcrylicPopupModal("RPC Command Reference", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
ImGui::PopStyleVar();
|
||||
if (!material::BeginOverlayDialog("RPC Command Reference", &show_commands_popup_, popW, 0.94f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1591,11 +1577,10 @@ void ConsoleTab::renderCommandsPopup()
|
||||
// Close button
|
||||
if (ImGui::Button("Close", ImVec2(-1, 0))) {
|
||||
cmdFilter[0] = '\0';
|
||||
ImGui::CloseCurrentPopup();
|
||||
show_commands_popup_ = false;
|
||||
}
|
||||
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
ImGui::PopStyleVar();
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
|
||||
void ConsoleTab::executeCommand(const std::string& cmd, rpc::RPCClient* rpc, rpc::RPCWorker* worker)
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../material/type.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "../../embedded/IconsMaterialDesign.h"
|
||||
#include "imgui.h"
|
||||
|
||||
@@ -70,14 +69,7 @@ void ExportAllKeysDialog::render(App* app)
|
||||
auto exportBtn = S.button("dialogs.export-all-keys", "export-button");
|
||||
auto closeBtn = S.button("dialogs.export-all-keys", "close-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowFocus();
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Export All Private Keys");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Export All Private Keys", &s_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog("Export All Private Keys", &s_open, win.width, 0.94f)) {
|
||||
// Warning
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
|
||||
ImGui::PushFont(material::Type().iconSmall());
|
||||
@@ -250,8 +242,8 @@ void ExportAllKeysDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", s_status.c_str());
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <string>
|
||||
@@ -73,14 +72,7 @@ void ExportTransactionsDialog::render(App* app)
|
||||
auto exportBtn = S.button("dialogs.export-transactions", "export-button");
|
||||
auto closeBtn = S.button("dialogs.export-transactions", "close-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowFocus();
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Export Transactions to CSV");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Export Transactions to CSV", &s_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog("Export Transactions to CSV", &s_open, win.width, 0.94f)) {
|
||||
const auto& state = app->getWalletState();
|
||||
|
||||
ImGui::Text("Export %zu transactions to CSV file.", state.transactions.size());
|
||||
@@ -165,8 +157,8 @@ void ExportTransactionsDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", s_status.c_str());
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../material/type.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "../../embedded/IconsMaterialDesign.h"
|
||||
#include "imgui.h"
|
||||
|
||||
@@ -115,14 +113,7 @@ void ImportKeyDialog::render(App* app)
|
||||
auto importBtn = S.button("dialogs.import-key", "import-button");
|
||||
auto closeBtn = S.button("dialogs.import-key", "close-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowFocus();
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Import Private Key");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Import Private Key", &s_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog("Import Private Key", &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());
|
||||
@@ -283,8 +274,8 @@ void ImportKeyDialog::render(App* app)
|
||||
ImGui::TextDisabled("Supported key formats:");
|
||||
ImGui::BulletText("Z-address spending keys (secret-extended-key-...)");
|
||||
ImGui::BulletText("T-address WIF private keys");
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
#include "../../rpc/rpc_client.h"
|
||||
#include "../../rpc/rpc_worker.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "imgui.h"
|
||||
@@ -58,15 +56,7 @@ void KeyExportDialog::render(App* app)
|
||||
auto copyBtn = S.button("dialogs.key-export", "copy-button");
|
||||
auto closeBtn = S.button("dialogs.key-export", "close-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImGui::OpenPopup(title);
|
||||
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal(title, &s_open,
|
||||
ImGuiWindowFlags_NoResize, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog(title, &s_open, win.width, 0.94f)) {
|
||||
ImGui::Spacing();
|
||||
|
||||
// Warning section with colored background
|
||||
@@ -239,7 +229,7 @@ void KeyExportDialog::render(App* app)
|
||||
s_show_key = false;
|
||||
}
|
||||
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "imgui.h"
|
||||
|
||||
namespace dragonx {
|
||||
@@ -64,14 +63,7 @@ void QRPopupDialog::render(App* app)
|
||||
auto addrInput = S.input("dialogs.qr-popup", "address-input");
|
||||
auto actionBtn = S.button("dialogs.qr-popup", "action-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowFocus();
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("QR Code");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("QR Code", &s_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog("QR Code", &s_open, win.width, 0.94f)) {
|
||||
|
||||
// Label if present
|
||||
if (!s_label.empty()) {
|
||||
@@ -137,8 +129,8 @@ void QRPopupDialog::render(App* app)
|
||||
if (material::StyledButton("Close", ImVec2(button_width, 0), S.resolveFont(actionBtn.font))) {
|
||||
close();
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
|
||||
// Handle window close button
|
||||
if (!s_open && s_qr_texture != 0) {
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../widgets/qr_code.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <string>
|
||||
@@ -130,14 +128,7 @@ void RequestPaymentDialog::render(App* app)
|
||||
auto qr = S.drawElement("dialogs.request-payment", "qr-code");
|
||||
auto actionBtn = S.button("dialogs.request-payment", "action-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowFocus();
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Request Payment");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Request Payment", &s_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog("Request Payment", &s_open, win.width, 0.94f)) {
|
||||
const auto& state = app->getWalletState();
|
||||
|
||||
ImGui::TextWrapped(
|
||||
@@ -284,8 +275,8 @@ void RequestPaymentDialog::render(App* app)
|
||||
if (material::StyledButton("Close", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
|
||||
// Cleanup on close
|
||||
if (!s_open && s_qr_texture != 0) {
|
||||
|
||||
@@ -668,20 +668,13 @@ void RenderSendConfirmPopup(App* app) {
|
||||
|
||||
double total = s_amount + s_fee;
|
||||
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
float popupAvailW = ImGui::GetMainViewport()->Size.x * S.drawElement("tabs.send", "confirm-popup-width-ratio").size;
|
||||
float popupW = std::min(schema::UI().drawElement("tabs.send", "confirm-popup-max-width").size, popupAvailW);
|
||||
float popVs = Layout::vScale();
|
||||
ImGui::SetNextWindowSize(ImVec2(popupW, 0), ImGuiCond_Always);
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Layout::spacingXl(), Layout::spacingLg()));
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal(TR("confirm_send"), nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog(TR("confirm_send"), nullptr, popupW, 0.94f)) {
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Escape) && !s_sending) {
|
||||
s_show_confirm = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
float popW = ImGui::GetContentRegionAvail().x;
|
||||
@@ -825,21 +818,15 @@ void RenderSendConfirmPopup(App* app) {
|
||||
}
|
||||
);
|
||||
s_show_confirm = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton("Cancel", ImVec2(S.button("tabs.send", "cancel-button").width, std::max(schema::UI().drawElement("tabs.send", "confirm-btn-min-height").size, schema::UI().drawElement("tabs.send", "confirm-btn-base-height").size * popVs)), S.resolveFont(S.button("tabs.send", "cancel-button").font))) {
|
||||
s_show_confirm = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
} else {
|
||||
// BeginPopupModal returned false — popup was closed externally
|
||||
s_show_confirm = false;
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -140,15 +140,8 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
auto walletBtn = S.button("dialogs.settings", "wallet-button");
|
||||
auto saveBtn = S.button("dialogs.settings", "save-button");
|
||||
auto cancelBtn = S.button("dialogs.settings", "cancel-button");
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
|
||||
// Use acrylic modal popup
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Settings");
|
||||
if (!effects::ImGuiAcrylic::BeginAcrylicPopupModal("Settings", p_open, ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
if (!material::BeginOverlayDialog("Settings", p_open, win.width, 0.94f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -476,16 +469,53 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
static bool s_confirm_clear_ztx = false;
|
||||
if (material::StyledButton("Clear Saved Z-Transaction History", ImVec2(walletBtn.width, 0), S.resolveFont(walletBtn.font))) {
|
||||
// Clear z-transaction history file
|
||||
std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json";
|
||||
if (util::Platform::deleteFile(ztx_file)) {
|
||||
Notifications::instance().success("Z-transaction history cleared");
|
||||
} else {
|
||||
Notifications::instance().info("No history file found");
|
||||
}
|
||||
s_confirm_clear_ztx = true;
|
||||
}
|
||||
ImGui::TextDisabled(" Delete locally stored shielded transaction data");
|
||||
|
||||
// Confirmation dialog
|
||||
if (s_confirm_clear_ztx) {
|
||||
if (material::BeginOverlayDialog("Confirm Clear Z-Tx History", &s_confirm_clear_ztx, 480.0f, 0.94f)) {
|
||||
ImGui::PushFont(material::Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Warning");
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped(
|
||||
"Clearing z-transaction history may cause your shielded balance to show as 0 "
|
||||
"until a wallet rescan is performed.");
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped(
|
||||
"If this happens, you will need to re-import your z-address private keys with "
|
||||
"rescan enabled to recover your balance.");
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button("Cancel", ImVec2(btnW, 40))) {
|
||||
s_confirm_clear_ztx = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.3f, 0.3f, 1.0f));
|
||||
if (ImGui::Button("Clear Anyway", ImVec2(btnW, 40))) {
|
||||
std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json";
|
||||
if (util::Platform::deleteFile(ztx_file)) {
|
||||
Notifications::instance().success("Z-transaction history cleared");
|
||||
} else {
|
||||
Notifications::instance().info("No history file found");
|
||||
}
|
||||
s_confirm_clear_ztx = false;
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
@@ -560,7 +590,7 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
*p_open = false;
|
||||
}
|
||||
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
#include "../notifications.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <vector>
|
||||
@@ -81,14 +79,7 @@ void ShieldDialog::render(App* app)
|
||||
? "Shield Coinbase Rewards"
|
||||
: "Merge to Address";
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowFocus();
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup(title);
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal(title, &s_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog(title, &s_open, win.width, 0.94f)) {
|
||||
const auto& state = app->getWalletState();
|
||||
|
||||
// Description
|
||||
@@ -304,8 +295,8 @@ void ShieldDialog::render(App* app)
|
||||
}
|
||||
}
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "../../config/settings.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "imgui.h"
|
||||
@@ -45,14 +44,7 @@ void TransactionDetailsDialog::render(App* app)
|
||||
auto memoInput = S.input("dialogs.transaction-details", "memo-input");
|
||||
auto bottomBtn = S.button("dialogs.transaction-details", "bottom-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Transaction Details");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Transaction Details", &s_open,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog("Transaction Details", &s_open, win.width, 0.94f)) {
|
||||
const auto& tx = s_transaction;
|
||||
|
||||
// Type indicator with color
|
||||
@@ -200,8 +192,8 @@ void TransactionDetailsDialog::render(App* app)
|
||||
if (material::StyledButton("Close", ImVec2(button_width, 0), S.resolveFont(bottomBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "imgui.h"
|
||||
|
||||
namespace dragonx {
|
||||
@@ -53,14 +52,7 @@ void ValidateAddressDialog::render(App* app)
|
||||
auto lbl = S.label("dialogs.validate-address", "label");
|
||||
auto closeBtn = S.button("dialogs.validate-address", "close-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowFocus();
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Validate Address");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Validate Address", &s_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
if (material::BeginOverlayDialog("Validate Address", &s_open, win.width, 0.94f)) {
|
||||
ImGui::TextWrapped("Enter a DragonX address to check if it's valid and whether it belongs to this wallet.");
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -216,8 +208,8 @@ void ValidateAddressDialog::render(App* app)
|
||||
if (material::StyledButton("Close", ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||