DRAGONX_DEBUG was defined unconditionally, causing D3D11CreateDevice() to request the debug layer via D3D11_CREATE_DEVICE_DEBUG. This layer is only available on machines with the Windows SDK or Graphics Tools installed, so the call fails with DXGI_ERROR_SDK_COMPONENT_MISSING on regular user machines — causing the app to silently exit.
562 lines
17 KiB
C++
562 lines
17 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include "../material/material.h"
|
|
#include "../../config/version.h"
|
|
#include "../../embedded/IconsMaterialDesign.h"
|
|
#include "imgui.h"
|
|
#include <string>
|
|
#include <vector>
|
|
#include <functional>
|
|
#include <cstring>
|
|
|
|
namespace dragonx {
|
|
namespace ui {
|
|
namespace screens {
|
|
|
|
using namespace material;
|
|
|
|
// ============================================================================
|
|
// Settings Screen
|
|
// ============================================================================
|
|
// Application settings with Material list items
|
|
|
|
/**
|
|
* @brief Setting category
|
|
*/
|
|
enum class SettingsCategory {
|
|
Wallet,
|
|
Display,
|
|
Network,
|
|
Privacy,
|
|
Advanced
|
|
};
|
|
|
|
/**
|
|
* @brief Settings data
|
|
*/
|
|
struct SettingsData {
|
|
// Wallet
|
|
bool autoShield = true;
|
|
double defaultFee = DRAGONX_DEFAULT_FEE;
|
|
|
|
// Display
|
|
std::string theme = "dark";
|
|
std::string language = "en";
|
|
std::string fiatCurrency = "USD";
|
|
bool showBalanceInFiat = true;
|
|
bool hideEmptyAddresses = true;
|
|
|
|
// Network
|
|
std::string proxyAddress;
|
|
int proxyPort = 9050;
|
|
bool useTor = false;
|
|
int maxConnections = 125;
|
|
|
|
// Privacy
|
|
bool rememberAddresses = true;
|
|
bool autoLock = true;
|
|
int autoLockTimeout = 5; // minutes
|
|
|
|
// Advanced
|
|
int confirmations = 10;
|
|
std::string dataDir;
|
|
bool debugMode = false;
|
|
};
|
|
|
|
/**
|
|
* @brief Settings screen with Material list
|
|
*/
|
|
class SettingsScreen {
|
|
public:
|
|
void setSettings(const SettingsData& settings) { m_settings = settings; }
|
|
const SettingsData& getSettings() const { return m_settings; }
|
|
|
|
void setOnSettingsChanged(std::function<void(const SettingsData&)> callback) {
|
|
m_onSettingsChanged = callback;
|
|
}
|
|
|
|
void setOnBackupWallet(std::function<void()> callback) {
|
|
m_onBackupWallet = callback;
|
|
}
|
|
|
|
void setOnExportKeys(std::function<void()> callback) {
|
|
m_onExportKeys = callback;
|
|
}
|
|
|
|
void setOnRescan(std::function<void()> callback) {
|
|
m_onRescan = callback;
|
|
}
|
|
|
|
void render();
|
|
|
|
private:
|
|
void renderWalletSettings();
|
|
void renderDisplaySettings();
|
|
void renderNetworkSettings();
|
|
void renderPrivacySettings();
|
|
void renderAdvancedSettings();
|
|
void renderAboutSection();
|
|
|
|
void notifySettingsChanged();
|
|
|
|
SettingsData m_settings;
|
|
std::function<void(const SettingsData&)> m_onSettingsChanged;
|
|
std::function<void()> m_onBackupWallet;
|
|
std::function<void()> m_onExportKeys;
|
|
std::function<void()> m_onRescan;
|
|
|
|
SettingsCategory m_selectedCategory = SettingsCategory::Wallet;
|
|
};
|
|
|
|
// ============================================================================
|
|
// Implementation
|
|
// ============================================================================
|
|
|
|
inline void SettingsScreen::render() {
|
|
// Title
|
|
Typography::instance().text(TypeStyle::H5, "Settings");
|
|
ImGui::Dummy(ImVec2(0, spacing::dp(2)));
|
|
|
|
// Category tabs
|
|
TabSpec tabSpec;
|
|
tabSpec.variant = TabVariant::Scrollable;
|
|
|
|
if (BeginTabs("settings_tabs", tabSpec)) {
|
|
if (Tab(ICON_MD_ACCOUNT_BALANCE_WALLET " Wallet", m_selectedCategory == SettingsCategory::Wallet)) {
|
|
m_selectedCategory = SettingsCategory::Wallet;
|
|
}
|
|
if (Tab(ICON_MD_PALETTE " Display", m_selectedCategory == SettingsCategory::Display)) {
|
|
m_selectedCategory = SettingsCategory::Display;
|
|
}
|
|
if (Tab(ICON_MD_PUBLIC " Network", m_selectedCategory == SettingsCategory::Network)) {
|
|
m_selectedCategory = SettingsCategory::Network;
|
|
}
|
|
if (Tab(ICON_MD_LOCK " Privacy", m_selectedCategory == SettingsCategory::Privacy)) {
|
|
m_selectedCategory = SettingsCategory::Privacy;
|
|
}
|
|
if (Tab(ICON_MD_SETTINGS " Advanced", m_selectedCategory == SettingsCategory::Advanced)) {
|
|
m_selectedCategory = SettingsCategory::Advanced;
|
|
}
|
|
EndTabs();
|
|
}
|
|
|
|
ImGui::Dummy(ImVec2(0, spacing::dp(2)));
|
|
|
|
// Settings content
|
|
switch (m_selectedCategory) {
|
|
case SettingsCategory::Wallet:
|
|
renderWalletSettings();
|
|
break;
|
|
case SettingsCategory::Display:
|
|
renderDisplaySettings();
|
|
break;
|
|
case SettingsCategory::Network:
|
|
renderNetworkSettings();
|
|
break;
|
|
case SettingsCategory::Privacy:
|
|
renderPrivacySettings();
|
|
break;
|
|
case SettingsCategory::Advanced:
|
|
renderAdvancedSettings();
|
|
break;
|
|
}
|
|
|
|
ImGui::Dummy(ImVec2(0, spacing::dp(3)));
|
|
|
|
// About section (always visible)
|
|
renderAboutSection();
|
|
}
|
|
|
|
inline void SettingsScreen::renderWalletSettings() {
|
|
CardSpec cardSpec;
|
|
cardSpec.elevation = 1;
|
|
cardSpec.padding = 0;
|
|
|
|
if (BeginCard(cardSpec)) {
|
|
BeginList("wallet_settings", false);
|
|
|
|
// Auto-shield toggle
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Auto-Shield Transparent Funds";
|
|
itemSpec.secondaryText = "Automatically move transparent balance to shielded";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ImGui::PushID("auto_shield");
|
|
ListItem(itemSpec);
|
|
|
|
// Toggle positioned at end
|
|
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
|
|
if (Switch("##toggle", m_settings.autoShield)) {
|
|
notifySettingsChanged();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Default fee
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Default Transaction Fee";
|
|
|
|
char feeStr[32];
|
|
snprintf(feeStr, sizeof(feeStr), "%.8f DRGX", m_settings.defaultFee);
|
|
itemSpec.secondaryText = feeStr;
|
|
itemSpec.trailingIcon = ">";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ListItem(itemSpec);
|
|
}
|
|
|
|
// Backup wallet
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.leadingIcon = ICON_MD_SAVE;
|
|
itemSpec.primaryText = "Backup Wallet";
|
|
itemSpec.secondaryText = "Export wallet backup file";
|
|
itemSpec.trailingIcon = ">";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
if (ListItem(itemSpec)) {
|
|
if (m_onBackupWallet) m_onBackupWallet();
|
|
}
|
|
}
|
|
|
|
// Export keys
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.leadingIcon = ICON_MD_KEY;
|
|
itemSpec.primaryText = "Export Private Keys";
|
|
itemSpec.secondaryText = "Export viewing and spending keys";
|
|
itemSpec.trailingIcon = ">";
|
|
|
|
if (ListItem(itemSpec)) {
|
|
if (m_onExportKeys) m_onExportKeys();
|
|
}
|
|
}
|
|
|
|
EndList();
|
|
EndCard();
|
|
}
|
|
}
|
|
|
|
inline void SettingsScreen::renderDisplaySettings() {
|
|
CardSpec cardSpec;
|
|
cardSpec.elevation = 1;
|
|
cardSpec.padding = 0;
|
|
|
|
if (BeginCard(cardSpec)) {
|
|
BeginList("display_settings", false);
|
|
|
|
// Theme selection
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Theme";
|
|
itemSpec.secondaryText = m_settings.theme == "dark" ? "Dark" : "Light";
|
|
itemSpec.trailingIcon = ">";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ListItem(itemSpec);
|
|
}
|
|
|
|
// Language
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Language";
|
|
itemSpec.secondaryText = "English"; // Would map from code
|
|
itemSpec.trailingIcon = ">";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ListItem(itemSpec);
|
|
}
|
|
|
|
// Fiat currency
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Fiat Currency";
|
|
itemSpec.secondaryText = m_settings.fiatCurrency.c_str();
|
|
itemSpec.trailingIcon = ">";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ListItem(itemSpec);
|
|
}
|
|
|
|
// Show balance in fiat toggle
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Show Balance in Fiat";
|
|
itemSpec.secondaryText = "Display fiat equivalent on home screen";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ImGui::PushID("show_fiat");
|
|
ListItem(itemSpec);
|
|
|
|
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
|
|
if (Switch("##toggle", m_settings.showBalanceInFiat)) {
|
|
notifySettingsChanged();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Hide empty addresses
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Hide Empty Addresses";
|
|
itemSpec.secondaryText = "Don't show addresses with zero balance";
|
|
|
|
ImGui::PushID("hide_empty");
|
|
ListItem(itemSpec);
|
|
|
|
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
|
|
if (Switch("##toggle", m_settings.hideEmptyAddresses)) {
|
|
notifySettingsChanged();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
EndList();
|
|
EndCard();
|
|
}
|
|
}
|
|
|
|
inline void SettingsScreen::renderNetworkSettings() {
|
|
CardSpec cardSpec;
|
|
cardSpec.elevation = 1;
|
|
cardSpec.padding = 0;
|
|
|
|
if (BeginCard(cardSpec)) {
|
|
BeginList("network_settings", false);
|
|
|
|
// Use Tor
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.leadingIcon = "🧅";
|
|
itemSpec.primaryText = "Use Tor Network";
|
|
itemSpec.secondaryText = "Route connections through Tor for privacy";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ImGui::PushID("use_tor");
|
|
ListItem(itemSpec);
|
|
|
|
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
|
|
if (Switch("##toggle", m_settings.useTor)) {
|
|
notifySettingsChanged();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Proxy settings (shown if Tor enabled)
|
|
if (m_settings.useTor) {
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Proxy Address";
|
|
|
|
char proxyStr[128];
|
|
snprintf(proxyStr, sizeof(proxyStr), "%s:%d",
|
|
m_settings.proxyAddress.empty() ? "127.0.0.1" : m_settings.proxyAddress.c_str(),
|
|
m_settings.proxyPort);
|
|
itemSpec.secondaryText = proxyStr;
|
|
itemSpec.trailingIcon = ">";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ListItem(itemSpec);
|
|
}
|
|
|
|
// Max connections
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Maximum Connections";
|
|
|
|
char connStr[32];
|
|
snprintf(connStr, sizeof(connStr), "%d peers", m_settings.maxConnections);
|
|
itemSpec.secondaryText = connStr;
|
|
itemSpec.trailingIcon = ">";
|
|
|
|
ListItem(itemSpec);
|
|
}
|
|
|
|
EndList();
|
|
EndCard();
|
|
}
|
|
}
|
|
|
|
inline void SettingsScreen::renderPrivacySettings() {
|
|
CardSpec cardSpec;
|
|
cardSpec.elevation = 1;
|
|
cardSpec.padding = 0;
|
|
|
|
if (BeginCard(cardSpec)) {
|
|
BeginList("privacy_settings", false);
|
|
|
|
// Remember addresses
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Remember Addresses";
|
|
itemSpec.secondaryText = "Save recently used addresses";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ImGui::PushID("remember_addr");
|
|
ListItem(itemSpec);
|
|
|
|
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
|
|
if (Switch("##toggle", m_settings.rememberAddresses)) {
|
|
notifySettingsChanged();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Auto-lock
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Auto-Lock Wallet";
|
|
itemSpec.secondaryText = "Lock wallet after inactivity";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ImGui::PushID("auto_lock");
|
|
ListItem(itemSpec);
|
|
|
|
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
|
|
if (Switch("##toggle", m_settings.autoLock)) {
|
|
notifySettingsChanged();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Lock timeout (if auto-lock enabled)
|
|
if (m_settings.autoLock) {
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Lock Timeout";
|
|
|
|
char timeoutStr[32];
|
|
snprintf(timeoutStr, sizeof(timeoutStr), "%d minutes", m_settings.autoLockTimeout);
|
|
itemSpec.secondaryText = timeoutStr;
|
|
itemSpec.trailingIcon = ">";
|
|
|
|
ListItem(itemSpec);
|
|
}
|
|
|
|
EndList();
|
|
EndCard();
|
|
}
|
|
}
|
|
|
|
inline void SettingsScreen::renderAdvancedSettings() {
|
|
CardSpec cardSpec;
|
|
cardSpec.elevation = 1;
|
|
cardSpec.padding = 0;
|
|
|
|
if (BeginCard(cardSpec)) {
|
|
BeginList("advanced_settings", false);
|
|
|
|
// Required confirmations
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.primaryText = "Required Confirmations";
|
|
|
|
char confStr[32];
|
|
snprintf(confStr, sizeof(confStr), "%d blocks", m_settings.confirmations);
|
|
itemSpec.secondaryText = confStr;
|
|
itemSpec.trailingIcon = ">";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ListItem(itemSpec);
|
|
}
|
|
|
|
// Data directory
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.leadingIcon = "📁";
|
|
itemSpec.primaryText = "Data Directory";
|
|
itemSpec.secondaryText = m_settings.dataDir.empty() ?
|
|
"(default)" : m_settings.dataDir.c_str();
|
|
itemSpec.trailingIcon = ">";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
ListItem(itemSpec);
|
|
}
|
|
|
|
// Rescan blockchain
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.leadingIcon = ICON_MD_SYNC;
|
|
itemSpec.primaryText = "Rescan Blockchain";
|
|
itemSpec.secondaryText = "Rebuild transaction history from blockchain";
|
|
itemSpec.trailingIcon = ">";
|
|
itemSpec.dividerBelow = true;
|
|
|
|
if (ListItem(itemSpec)) {
|
|
if (m_onRescan) m_onRescan();
|
|
}
|
|
}
|
|
|
|
// Debug mode
|
|
{
|
|
ListItemSpec itemSpec;
|
|
itemSpec.leadingIcon = "🐛";
|
|
itemSpec.primaryText = "Debug Mode";
|
|
itemSpec.secondaryText = "Enable verbose logging";
|
|
|
|
ImGui::PushID("debug_mode");
|
|
ListItem(itemSpec);
|
|
|
|
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 60);
|
|
if (Switch("##toggle", m_settings.debugMode)) {
|
|
notifySettingsChanged();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
EndList();
|
|
EndCard();
|
|
}
|
|
}
|
|
|
|
inline void SettingsScreen::renderAboutSection() {
|
|
CardSpec cardSpec;
|
|
cardSpec.elevation = 0;
|
|
cardSpec.padding = spacing::dp(3);
|
|
cardSpec.outlined = true;
|
|
|
|
if (BeginCard(cardSpec)) {
|
|
float availWidth = ImGui::GetContentRegionAvail().x;
|
|
|
|
// App name and version centered
|
|
const char* appName = "ObsidianDragon";
|
|
ImVec2 nameSize = ImGui::CalcTextSize(appName);
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - nameSize.x) * 0.5f);
|
|
Typography::instance().text(TypeStyle::H6, appName);
|
|
|
|
char version[64];
|
|
snprintf(version, sizeof(version), "Version %s-imgui", DRAGONX_VERSION);
|
|
ImVec2 versionSize = ImGui::CalcTextSize(version);
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - versionSize.x) * 0.5f);
|
|
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), version);
|
|
|
|
ImGui::Dummy(ImVec2(0, spacing::dp(1)));
|
|
|
|
const char* copyright = "© 2024-2026 The Hush Developers";
|
|
ImVec2 copySize = ImGui::CalcTextSize(copyright);
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - copySize.x) * 0.5f);
|
|
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), copyright);
|
|
|
|
const char* license = "Released under GPLv3";
|
|
ImVec2 licenseSize = ImGui::CalcTextSize(license);
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - licenseSize.x) * 0.5f);
|
|
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), license);
|
|
|
|
EndCard();
|
|
}
|
|
}
|
|
|
|
inline void SettingsScreen::notifySettingsChanged() {
|
|
if (m_onSettingsChanged) {
|
|
m_onSettingsChanged(m_settings);
|
|
}
|
|
}
|
|
|
|
} // namespace screens
|
|
} // namespace ui
|
|
} // namespace dragonx
|