ObsidianDragon - DragonX ImGui Wallet
Full-node GUI wallet for DragonX cryptocurrency. Built with Dear ImGui, SDL3, and OpenGL3/DX11. Features: - Send/receive shielded and transparent transactions - Autoshield with merged transaction display - Built-in CPU mining (xmrig) - Peer management and network monitoring - Wallet encryption with PIN lock - QR code generation for receive addresses - Transaction history with pagination - Console for direct RPC commands - Cross-platform (Linux, Windows)
This commit is contained in:
560
src/ui/screens/settings_screen.h
Normal file
560
src/ui/screens/settings_screen.h
Normal file
@@ -0,0 +1,560 @@
|
||||
// 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);
|
||||
|
||||
const char* version = "Version 1.0.0-imgui";
|
||||
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
|
||||
Reference in New Issue
Block a user