diff --git a/build.sh b/build.sh index ffd71be..eb14558 100755 --- a/build.sh +++ b/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/" diff --git a/docs/screenshots/1.webp b/docs/screenshots/1.webp index c16c9be..3d53093 100644 Binary files a/docs/screenshots/1.webp and b/docs/screenshots/1.webp differ diff --git a/docs/screenshots/2.webp b/docs/screenshots/2.webp index a087950..5f7a8d5 100644 Binary files a/docs/screenshots/2.webp and b/docs/screenshots/2.webp differ diff --git a/docs/screenshots/3.webp b/docs/screenshots/3.webp index 6924037..57e5c15 100644 Binary files a/docs/screenshots/3.webp and b/docs/screenshots/3.webp differ diff --git a/docs/screenshots/4.webp b/docs/screenshots/4.webp index e3d8195..c7d3ee3 100644 Binary files a/docs/screenshots/4.webp and b/docs/screenshots/4.webp differ diff --git a/docs/screenshots/5.webp b/docs/screenshots/5.webp index 5575d15..9c7025b 100644 Binary files a/docs/screenshots/5.webp and b/docs/screenshots/5.webp differ diff --git a/docs/screenshots/6.webp b/docs/screenshots/6.webp index 22a12a6..07fc1a8 100644 Binary files a/docs/screenshots/6.webp and b/docs/screenshots/6.webp differ diff --git a/docs/screenshots/7.webp b/docs/screenshots/7.webp index f079557..84f252b 100644 Binary files a/docs/screenshots/7.webp and b/docs/screenshots/7.webp differ diff --git a/docs/screenshots/8.webp b/docs/screenshots/8.webp index af21d28..b336755 100644 Binary files a/docs/screenshots/8.webp and b/docs/screenshots/8.webp differ diff --git a/docs/screenshots/9.webp b/docs/screenshots/9.webp index 9e18c6c..403ed85 100644 Binary files a/docs/screenshots/9.webp and b/docs/screenshots/9.webp differ diff --git a/res/img/backgrounds/gradient/gradient_obsidian_bg.png b/res/img/backgrounds/gradient/gradient_obsidian_bg.png index 0610ee1..173888a 100644 Binary files a/res/img/backgrounds/gradient/gradient_obsidian_bg.png and b/res/img/backgrounds/gradient/gradient_obsidian_bg.png differ diff --git a/res/img/backgrounds/texture/obsidian_bg.png b/res/img/backgrounds/texture/obsidian_bg.png index 7270b25..7d962b7 100644 Binary files a/res/img/backgrounds/texture/obsidian_bg.png and b/res/img/backgrounds/texture/obsidian_bg.png differ diff --git a/res/themes/color-pop-dark.toml b/res/themes/color-pop-dark.toml index c100911..333c63c 100644 --- a/res/themes/color-pop-dark.toml +++ b/res/themes/color-pop-dark.toml @@ -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)" diff --git a/res/themes/dark.toml b/res/themes/dark.toml index 6b00125..33cf8df 100644 --- a/res/themes/dark.toml +++ b/res/themes/dark.toml @@ -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)" diff --git a/res/themes/obsidian.toml b/res/themes/obsidian.toml index 3c2fed0..007c8a0 100644 --- a/res/themes/obsidian.toml +++ b/res/themes/obsidian.toml @@ -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 } diff --git a/res/themes/ui.toml b/res/themes/ui.toml index be061e1..e66cd4e 100644 --- a/res/themes/ui.toml +++ b/res/themes/ui.toml @@ -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)" diff --git a/src/app.cpp b/src/app.cpp index 96b20f4..6ef5987 100644 --- a/src/app.cpp +++ b/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(); + 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 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 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 diff --git a/src/app.h b/src/app.h index 2ae2577..4538ccc 100644 --- a/src/app.h +++ b/src/app.h @@ -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; diff --git a/src/app_security.cpp b/src/app_security.cpp index f88dd7a..e1bebb1 100644 --- a/src/app_security.cpp +++ b/src/app_security.cpp @@ -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(); } } diff --git a/src/daemon/embedded_daemon.cpp b/src/daemon/embedded_daemon.cpp index 3206203..38fa2b8 100644 --- a/src/daemon/embedded_daemon.cpp +++ b/src/daemon/embedded_daemon.cpp @@ -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()); diff --git a/src/daemon/embedded_daemon.h b/src/daemon/embedded_daemon.h index d76c365..c993ba4 100644 --- a/src/daemon/embedded_daemon.h +++ b/src/daemon/embedded_daemon.h @@ -152,6 +152,12 @@ public: void setDebugCategories(const std::set& cats) { debug_categories_ = cats; } const std::set& 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 should_stop_{false}; std::set debug_categories_; std::atomic crash_count_{0}; // consecutive crash counter + std::atomic rescan_on_next_start_{false}; // -rescan flag for next start }; } // namespace daemon diff --git a/src/data/wallet_state.h b/src/data/wallet_state.h index 01769b3..9ce25da 100644 --- a/src/data/wallet_state.h +++ b/src/data/wallet_state.h @@ -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; } }; diff --git a/src/rpc/connection.cpp b/src/rpc/connection.cpp index 2cc4681..6f877d4 100644 --- a/src/rpc/connection.cpp +++ b/src/rpc/connection.cpp @@ -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 diff --git a/src/rpc/connection.h b/src/rpc/connection.h index 2bde8ff..5d1eb41 100644 --- a/src/rpc/connection.h +++ b/src/rpc/connection.h @@ -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: }; diff --git a/src/ui/material/draw_helpers.h b/src/ui/material/draw_helpers.h index cb61413..2512423 100644 --- a/src/ui/material/draw_helpers.h +++ b/src/ui/material/draw_helpers.h @@ -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 @@ -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 diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index f020cc6..8fe3f8a 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -108,6 +108,7 @@ static bool sp_stop_external_daemon = false; static std::set 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 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 diff --git a/src/ui/windows/about_dialog.cpp b/src/ui/windows/about_dialog.cpp index 4919391..61b6e04 100644 --- a/src/ui/windows/about_dialog.cpp +++ b/src/ui/windows/about_dialog.cpp @@ -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 diff --git a/src/ui/windows/address_book_dialog.cpp b/src/ui/windows/address_book_dialog.cpp index 48cd7d1..dbc4405 100644 --- a/src/ui/windows/address_book_dialog.cpp +++ b/src/ui/windows/address_book_dialog.cpp @@ -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)) { diff --git a/src/ui/windows/backup_wallet_dialog.cpp b/src/ui/windows/backup_wallet_dialog.cpp index add4fc2..463cb2a 100644 --- a/src/ui/windows/backup_wallet_dialog.cpp +++ b/src/ui/windows/backup_wallet_dialog.cpp @@ -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 @@ -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 diff --git a/src/ui/windows/block_info_dialog.cpp b/src/ui/windows/block_info_dialog.cpp index 11b55e7..4a0ceae 100644 --- a/src/ui/windows/block_info_dialog.cpp +++ b/src/ui/windows/block_info_dialog.cpp @@ -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 @@ -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 diff --git a/src/ui/windows/console_tab.cpp b/src/ui/windows/console_tab.cpp index 4ce692c..5034705 100644 --- a/src/ui/windows/console_tab.cpp +++ b/src/ui/windows/console_tab.cpp @@ -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) diff --git a/src/ui/windows/export_all_keys_dialog.cpp b/src/ui/windows/export_all_keys_dialog.cpp index c5b1d10..180c5c8 100644 --- a/src/ui/windows/export_all_keys_dialog.cpp +++ b/src/ui/windows/export_all_keys_dialog.cpp @@ -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 diff --git a/src/ui/windows/export_transactions_dialog.cpp b/src/ui/windows/export_transactions_dialog.cpp index 8c69777..301b929 100644 --- a/src/ui/windows/export_transactions_dialog.cpp +++ b/src/ui/windows/export_transactions_dialog.cpp @@ -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 @@ -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 diff --git a/src/ui/windows/import_key_dialog.cpp b/src/ui/windows/import_key_dialog.cpp index c6499c0..f65fabc 100644 --- a/src/ui/windows/import_key_dialog.cpp +++ b/src/ui/windows/import_key_dialog.cpp @@ -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 diff --git a/src/ui/windows/key_export_dialog.cpp b/src/ui/windows/key_export_dialog.cpp index f2006b4..166bae9 100644 --- a/src/ui/windows/key_export_dialog.cpp +++ b/src/ui/windows/key_export_dialog.cpp @@ -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(); } } diff --git a/src/ui/windows/qr_popup_dialog.cpp b/src/ui/windows/qr_popup_dialog.cpp index 0871939..e3c51d0 100644 --- a/src/ui/windows/qr_popup_dialog.cpp +++ b/src/ui/windows/qr_popup_dialog.cpp @@ -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) { diff --git a/src/ui/windows/request_payment_dialog.cpp b/src/ui/windows/request_payment_dialog.cpp index f6a3c2a..f7f7d78 100644 --- a/src/ui/windows/request_payment_dialog.cpp +++ b/src/ui/windows/request_payment_dialog.cpp @@ -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 @@ -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) { diff --git a/src/ui/windows/send_tab.cpp b/src/ui/windows/send_tab.cpp index e77d680..4f65e0e 100644 --- a/src/ui/windows/send_tab.cpp +++ b/src/ui/windows/send_tab.cpp @@ -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(); } // ============================================================================ diff --git a/src/ui/windows/settings_window.cpp b/src/ui/windows/settings_window.cpp index 88f0202..87003fa 100644 --- a/src/ui/windows/settings_window.cpp +++ b/src/ui/windows/settings_window.cpp @@ -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 diff --git a/src/ui/windows/shield_dialog.cpp b/src/ui/windows/shield_dialog.cpp index 11a3141..fbd1d89 100644 --- a/src/ui/windows/shield_dialog.cpp +++ b/src/ui/windows/shield_dialog.cpp @@ -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 @@ -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 diff --git a/src/ui/windows/transaction_details_dialog.cpp b/src/ui/windows/transaction_details_dialog.cpp index 1194048..c500b7c 100644 --- a/src/ui/windows/transaction_details_dialog.cpp +++ b/src/ui/windows/transaction_details_dialog.cpp @@ -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 diff --git a/src/ui/windows/validate_address_dialog.cpp b/src/ui/windows/validate_address_dialog.cpp index 03d72c1..ecd97ac 100644 --- a/src/ui/windows/validate_address_dialog.cpp +++ b/src/ui/windows/validate_address_dialog.cpp @@ -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