From 9fb534da89c0b0df48f6ffce9ceef65e1caae8ec Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Sat, 25 Nov 2023 02:20:51 +0200 Subject: [PATCH] egui 0.24 update --- Cargo.toml | 13 +- core/Cargo.toml | 1 + core/src/app.rs | 48 ++- core/src/core.rs | 53 ++- core/src/events.rs | 13 +- core/src/imports.rs | 6 +- core/src/modules/account_create.rs | 52 +++ core/src/modules/account_manager.rs | 565 ++++++++++++++++++++-------- core/src/modules/block_dag.rs | 13 +- core/src/modules/mod.rs | 5 + core/src/modules/overview.rs | 78 ++-- core/src/modules/wallet_open.rs | 53 ++- core/src/notifications.rs | 5 + core/src/primitives/account.rs | 35 +- core/src/runtime/mod.rs | 10 +- core/src/runtime/system.rs | 29 +- core/src/utils/mod.rs | 16 +- 17 files changed, 697 insertions(+), 298 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 372d20e..d8feecc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,17 +26,17 @@ kaspa-ng-macros = { version = "0.1.0", path = "macros/" } # |___ |__] |__| | # ___________________ -egui = "0.23.0" -egui_plot = "0.23.0" -egui_extras = { version = "0.23.0", features = ["svg","image"] } -eframe = { version = "0.23.0", default-features = false, features = [ +egui = "0.24.0" +egui_plot = "0.24.0" +egui_extras = { version = "0.24.0", features = ["svg","image"] } +eframe = { version = "0.24.0", default-features = false, features = [ "accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies. "default_fonts", # Embed the default egui fonts. "glow", # Use the glow rendering backend. Alternative: "wgpu". "persistence", # Enable restoring app state when restarting the app. ] } -egui-phosphor = { version = "0.3.0", features = ["thin","light","regular","bold"] } -egui-notify = "0.10.0" +egui-phosphor = { version = "0.3.1", features = ["thin","light","regular","bold"] } +egui-notify = "0.11.0" # egui-toast = "0.9.0" # egui = { path = "../egui/crates/egui" } @@ -140,6 +140,7 @@ pad = "0.1.6" qrcode = "0.12.0" rand = "0.8" ritehash = "0.2.0" +rlimit = "0.10.1" separator = "0.4.1" serde = { version = "1", features = ["derive"] } serde_json = "1.0.107" diff --git a/core/Cargo.toml b/core/Cargo.toml index a793bd4..0acdf1a 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -88,6 +88,7 @@ kaspa-rpc-service.workspace = true kaspa-wrpc-server.workspace = true kaspad.workspace = true num_cpus.workspace = true +rlimit.workspace = true sysinfo.workspace = true tokio.workspace = true diff --git a/core/src/app.rs b/core/src/app.rs index 16f2762..bb47ffd 100644 --- a/core/src/app.rs +++ b/core/src/app.rs @@ -1,5 +1,6 @@ use crate::result::Result; use cfg_if::cfg_if; +use egui::ViewportBuilder; use kaspa_ng_core::runtime; use kaspa_ng_core::settings::Settings; use kaspa_wallet_core::api::WalletApi; @@ -22,7 +23,7 @@ pub const RUSTC_LLVM_VERSION: &str = env!("VERGEN_RUSTC_LLVM_VERSION"); pub const RUSTC_SEMVER: &str = env!("VERGEN_RUSTC_SEMVER"); pub const CARGO_TARGET_TRIPLE: &str = env!("VERGEN_CARGO_TARGET_TRIPLE"); // pub const CARGO_PROFILE: &str = env!("VERGEN_CARGO_PROFILE"); -pub const CODENAME: &str = "This is the way"; +pub const CODENAME: &str = "DNA"; cfg_if! { if #[cfg(not(target_arch = "wasm32"))] { @@ -32,7 +33,6 @@ cfg_if! { use kaspa_utils::fd_budget; use kaspa_core::signals::Signals; use clap::ArgAction; - use eframe::IconData; use crate::utils::*; use std::fs; @@ -122,6 +122,29 @@ cfg_if! { runtime::panic::init_panic_handler(); + // TODO - import from kaspad_lib + const DESIRED_DAEMON_SOFT_FD_LIMIT: u64 = 16 * 1024; + const MINIMUM_DAEMON_SOFT_FD_LIMIT: u64 = 4 * 1024; + match try_set_fd_limit(DESIRED_DAEMON_SOFT_FD_LIMIT) { + Ok(limit) => { + if limit < MINIMUM_DAEMON_SOFT_FD_LIMIT { + println!(); + println!("| Current OS file descriptor limit (soft FD limit) is set to {limit}"); + println!("| The kaspad node requires a setting of at least {DESIRED_DAEMON_SOFT_FD_LIMIT} to operate properly."); + println!("| Please increase the limits using the following command:"); + println!("| ulimit -n {DESIRED_DAEMON_SOFT_FD_LIMIT}"); + println!(); + } + } + Err(err) => { + println!(); + println!("| Unable to initialize the necessary OS file descriptor limit (soft FD limit) to: {}", err); + println!("| The kaspad node requires a setting of at least {DESIRED_DAEMON_SOFT_FD_LIMIT} to operate properly."); + println!(); + } + } + + match parse_args() { Args::Cli => { use kaspa_cli_lib::*; @@ -150,6 +173,8 @@ cfg_if! { // Log to stderr (if you run with `RUST_LOG=debug`). env_logger::init(); + set_log_level(LevelFilter::Info); + let mut settings = if reset_settings { println!("Resetting kaspa-ng settings on user request..."); Settings::default().store_sync()?.clone() @@ -182,10 +207,12 @@ cfg_if! { let runtime: Arc>> = Arc::new(Mutex::new(None)); let delegate = runtime.clone(); let native_options = eframe::NativeOptions { - icon_data : IconData::try_from_png_bytes(KASPA_NG_ICON_256X256).ok(), persist_window : true, - initial_window_size : Some(egui::Vec2 { x : 1000.0, y : 600.0 }), - // min_window_size : Some(egui::Vec2 { x : 1000.0, y : 600.0 }), + viewport: ViewportBuilder::default() + .with_resizable(true) + .with_title(i18n("Kaspa NG")) + .with_inner_size([1000.0,600.0]) + .with_icon(eframe::icon_data::from_png_bytes(KASPA_NG_ICON_256X256).unwrap()), ..Default::default() }; eframe::run_native( @@ -320,3 +347,14 @@ cfg_if! { } } } + +#[cfg(not(target_arch = "wasm32"))] +pub fn try_set_fd_limit(limit: u64) -> std::io::Result { + cfg_if::cfg_if! { + if #[cfg(target_os = "windows")] { + Ok(rlimit::setmaxstdio(limit as u32)?.map(|v| v as u64)) + } else if #[cfg(unix)] { + rlimit::increase_nofile_limit(limit) + } + } +} diff --git a/core/src/core.rs b/core/src/core.rs index 87d247f..d91cea8 100644 --- a/core/src/core.rs +++ b/core/src/core.rs @@ -93,6 +93,7 @@ pub struct Core { discard_hint: bool, exception: Option, + pub wallet_descriptor: Option, pub wallet_list: Vec, pub account_collection: Option, pub selected_account: Option, @@ -301,6 +302,7 @@ impl Core { default_style, large_style, + wallet_descriptor: None, wallet_list: Vec::new(), account_collection: None, selected_account: None, @@ -413,10 +415,6 @@ impl Core { } impl eframe::App for Core { - #[cfg(not(target_arch = "wasm32"))] - fn on_close_event(&mut self) -> bool { - true - } #[cfg(not(target_arch = "wasm32"))] fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) { @@ -528,7 +526,7 @@ impl Core { ui.menu_button("File", |ui| { #[cfg(not(target_arch = "wasm32"))] if ui.button("Quit").clicked() { - _frame.close(); + ui.ctx().send_viewport_cmd(ViewportCommand::Close) } ui.separator(); ui.label(" ~ Debug Modules ~"); @@ -698,8 +696,6 @@ impl Core { }); } - self.module.status_bar(ui); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if icons() .sliders @@ -811,6 +807,9 @@ impl Core { ui.label("Starting..."); } } + + self.module.status_bar(ui); + // if self.settings.node.node_kind != KaspadNodeKind::Disable { // ui.label("Not Connected"); @@ -858,6 +857,8 @@ impl Core { ui.separator(); ui.label(format!("DAA {}", current_daa_score.separated_string())); } + + self.module.status_bar(ui); } Status::Syncing { sync_status, peers } => { ui.vertical(|ui| { @@ -882,6 +883,8 @@ impl Core { status.render_text_state(ui); } } + + self.module.status_bar(ui); }); if let Some(status) = sync_status.as_ref() { @@ -908,14 +911,15 @@ impl Core { self.metrics = Some(snapshot); } Events::Exit => { - // println!("bye!"); cfg_if! { if #[cfg(not(target_arch = "wasm32"))] { - _frame.close(); + _ctx.send_viewport_cmd(ViewportCommand::Close); } } } - Events::Error(_error) => {} + Events::Error(error) => { + runtime().notify(UserNotification::error(error.as_str())); + } Events::WalletList { wallet_list } => { // println!("getting wallet list!, {:?}", wallet_list); self.wallet_list = (*wallet_list).clone(); @@ -935,7 +939,9 @@ impl Core { // // .map(|account| (account.id(), account)).collect::>() // // ); // } - Events::Notify { notification } => { + Events::Notify { + user_notification: notification, + } => { notification.render(&mut self.toasts); } Events::Close { .. } => {} @@ -1005,13 +1011,16 @@ impl Core { self.discard_hint = false; } CoreWallet::WalletOpen { + wallet_descriptor, account_descriptors, } | CoreWallet::WalletReload { + wallet_descriptor, account_descriptors, } => { self.state.is_open = true; + self.wallet_descriptor = wallet_descriptor; let network_id = self.state.network_id.ok_or(Error::WalletOpenNetworkId)?; let account_descriptors = account_descriptors.ok_or(Error::WalletOpenAccountDescriptors)?; @@ -1021,6 +1030,23 @@ impl Core { CoreWallet::AccountActivation { ids: _ } => { // TODO } + CoreWallet::AccountCreation { descriptor } => { + if let Some(account_collection) = self.account_collection.as_mut() { + account_collection.push(Account::from(descriptor)); + runtime().request_repaint(); + // if let Some(account) = account_collection.get(account_id) { + // account.update(descriptor); + // } + } + } + CoreWallet::AccountUpdate { descriptor } => { + let account_id = descriptor.account_id(); + if let Some(account_collection) = self.account_collection.as_ref() { + if let Some(account) = account_collection.get(account_id) { + account.update(descriptor); + } + } + } CoreWallet::WalletError { message: _ } => { // self.state.is_open = false; } @@ -1029,6 +1055,11 @@ impl Core { self.hint = None; self.state.is_open = false; self.account_collection = None; + self.wallet_descriptor = None; + + self.modules.clone().into_iter().for_each(|(_, module)| { + module.reset(self); + }); } CoreWallet::AccountSelection { id: _ } => { // self.selected_account = self.wallet().account().ok(); diff --git a/core/src/events.rs b/core/src/events.rs index df2a504..cdd0bfd 100644 --- a/core/src/events.rs +++ b/core/src/events.rs @@ -17,27 +17,16 @@ pub enum Events { WalletList { wallet_list: Arc>, }, - // AccountList { - // // account_list: Arc>>, - // account_list: Box>, - // }, Wallet { event: Box, }, - // TryUnlock(Secret), UnlockSuccess, UnlockFailure { message: String, }, Notify { - notification: UserNotification, + user_notification: UserNotification, }, Close, - // Send, - // Deposit, - // Overview, - // Transactions, - // Accounts, - // Settings, Exit, } diff --git a/core/src/imports.rs b/core/src/imports.rs index ad317a5..3d11bcb 100644 --- a/core/src/imports.rs +++ b/core/src/imports.rs @@ -34,10 +34,8 @@ pub use std::path::{Path, PathBuf}; pub use std::pin::Pin; pub use std::rc::Rc; pub use std::str::FromStr; -pub use std::sync::{ - atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering}, - OnceLock, -}; +pub use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering}; +pub use std::sync::OnceLock; pub use std::sync::{Arc, Mutex, MutexGuard, RwLock}; pub use std::time::Duration; diff --git a/core/src/modules/account_create.rs b/core/src/modules/account_create.rs index 248590a..c176926 100644 --- a/core/src/modules/account_create.rs +++ b/core/src/modules/account_create.rs @@ -6,6 +6,34 @@ use kaspa_wallet_core::runtime::{AccountCreateArgs, PrvKeyDataCreateArgs, Wallet use kaspa_wallet_core::storage::interface::AccessContext; use kaspa_wallet_core::storage::{AccessContextT, AccountKind}; +pub enum MnemonicSize { + Words12, + Words24, +} + +pub enum CreateAccountDescriptor { + Bip44, // derivation for the same private key + Bip32, + Legacy, + MultiSig, + Keypair, + // Keypair, + // MultiSig, +} + +impl CreateAccountDescriptor { + fn describe(&self) -> (&'static str, &'static str) { + match self { + Self::Bip44 => ("BIP44", "BIP44"), + Self::Bip32 => ("BIP32", "BIP32"), + Self::Legacy => ("Legacy", "Legacy"), + Self::MultiSig => ("MultiSig", "MultiSig"), + Self::Keypair => ("Keypair", "Keypair"), + } + } +} + + #[derive(Clone)] pub enum State { @@ -91,6 +119,18 @@ impl ModuleT for AccountCreate { match self.state.clone() { State::Start => { + + // let v= core.account_collection().map(|collection|{ + // HashMap::from_iter(collection.iter().map(|account|{ + // (account.descriptor().private_key_id(),account.clone()) + // // println!("account: {}", account.title()); + // })) + // }).unwrap_or_default(); + + // if let Some(account_collection) = core.account_collection() { + // } + + Panel::new(self) .with_caption("Create Account") .with_close_enabled(false, |_|{ @@ -99,6 +139,18 @@ impl ModuleT for AccountCreate { // ui.add_space(64.); ui.label("Please select an account type"); ui.label(" "); + }) + .with_body(|_this,ui|{ + + + + if ui.add(CompositeButton::new( + "Create a new HD account", + "BIP-44 " + )).clicked() { + + } + // ui.label("A wallet is stored in a file on your computer. You can create multiple wallet."); }) .with_footer(|_this,ui| { diff --git a/core/src/modules/account_manager.rs b/core/src/modules/account_manager.rs index c772217..778958b 100644 --- a/core/src/modules/account_manager.rs +++ b/core/src/modules/account_manager.rs @@ -1,5 +1,5 @@ use crate::imports::*; -use crate::primitives::account::Context; +use crate::primitives::account; use std::borrow::Cow; use kaspa_wallet_core::tx::{GeneratorSummary, PaymentOutput, Fees}; use kaspa_wallet_core::api::*; @@ -14,39 +14,58 @@ enum State { Receive { account: Account }, } +#[derive(Default)] enum Details { + #[default] Transactions, Account, UtxoSelector } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] enum Action { + #[default] None, Estimating, Sending, + // Reset, + Processing, +} + + +impl Action { + fn is_sending(&self) -> bool { + matches!(self, Action::Sending | Action::Estimating | Action::Processing) + } } -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Default, Clone, Copy, Eq, PartialEq)] +enum TransactionKind { + #[default] + None, + Send, + Transfer, + // Request, +} + + + +#[derive(Default, Clone, Copy, Eq, PartialEq)] enum Focus { + #[default] None, Address, Amount, Fees, } -impl Action { - fn is_sending(&self) -> bool { - matches!(self, Action::Sending | Action::Estimating) - } -} - #[derive(Default)] -enum Estimate { +enum Status { #[default] None, GeneratorSummary(GeneratorSummary), Error(String), + // Success(GeneratorSummary), } // impl Estimate { @@ -59,65 +78,85 @@ enum Estimate { // } // } -#[derive(Clone, Eq, PartialEq)] +#[derive(Default, Clone, Eq, PartialEq)] enum AddressStatus { - Valid, + #[default] None, + Valid, NetworkMismatch(NetworkType), Invalid(String), } -pub struct AccountManager { - #[allow(dead_code)] - runtime: Runtime, - +#[derive(Default)] +pub struct ManagerContext { selected: Option, - state: State, details : Details, + + // send state destination_address_string : String, send_amount_text: String, send_amount_sompi : u64, enable_priority_fees : bool, priority_fees_text : String, priority_fees_sompi : u64, - send_info: Option, - estimate : Arc>, + estimate : Arc>, address_status : AddressStatus, action : Action, + transaction_kind : TransactionKind, focus : Focus, wallet_secret : String, payment_secret : String, } +impl ManagerContext { + fn reset_send_state(&mut self) { + + println!("*** resetting send state..."); + + self.destination_address_string = String::default(); + self.send_amount_text = String::default(); + self.send_amount_sompi = 0; + self.enable_priority_fees = false; + self.priority_fees_text = String::default(); + self.priority_fees_sompi = 0; + *self.estimate.lock().unwrap() = Status::None; + self.address_status = AddressStatus::None; + self.action = Action::None; + self.transaction_kind = TransactionKind::None; + self.focus = Focus::None; + self.wallet_secret.zeroize(); + self.payment_secret.zeroize(); + } +} + +pub struct AccountManager { + #[allow(dead_code)] + runtime: Runtime, + + state: State, + context : ManagerContext, +} + impl AccountManager { pub fn new(runtime: Runtime) -> Self { Self { runtime, - selected: None, state: State::Select, - details : Details::Transactions, - destination_address_string : String::new(), - send_amount_text: String::new(), - send_amount_sompi : 0, - enable_priority_fees : false, - priority_fees_text : String::new(), - priority_fees_sompi : 0, - send_info : None, - estimate : Arc::new(Mutex::new(Estimate::None)), - address_status : AddressStatus::None, - action : Action::None, - focus : Focus::None, - wallet_secret : String::new(), - payment_secret : String::new(), + context : ManagerContext::default(), } } pub fn select(&mut self, account: Option) { - self.selected = account; + self.context.selected = account; } } impl ModuleT for AccountManager { + + fn reset(&mut self, _core: &mut Core) { + self.context = ManagerContext::default(); + } + fn render( &mut self, core: &mut Core, @@ -125,7 +164,7 @@ impl ModuleT for AccountManager { _frame: &mut eframe::Frame, ui: &mut egui::Ui, ) { - use egui_phosphor::light::{ARROW_CIRCLE_UP,QR_CODE}; + use egui_phosphor::light::{ARROW_CIRCLE_UP,ARROWS_DOWN_UP,QR_CODE}; let theme = theme(); @@ -169,14 +208,125 @@ impl ModuleT for AccountManager { } + // State::Create => { + + // //- ACCOUNT TYPE + // //- TODO ACCOUNT NAME + // //- PROMPT FOR PASSWORD + // //- PAYMENT PASSWORD? 25th WORD? + + + // } + State::Overview { account } => { let width = ui.available_width(); ui.horizontal(|ui| { - ui.heading("Kaspa Wallet"); + let wallet_name = if let Some(wallet_descriptor) = core.wallet_descriptor.as_ref() { + wallet_descriptor.title.as_deref().unwrap_or(wallet_descriptor.filename.as_str()) + } else { + ui.label("Missing wallet descriptor"); + return; + }; + + // let wallet_name = core.wallet_descriptor.as_ref().and_then(|descriptor|descriptor.title.clone()).as_deref().unwrap_or("NO NAME"); + let current_wallet_selector_id = ui.make_persistent_id("current_wallet_selector"); + let response = ui.add(Label::new(format!("Wallet: {} ⏷", wallet_name)).sense(Sense::click())); + + if response.clicked() { + ui.memory_mut(|mem| mem.toggle_popup(current_wallet_selector_id)); + } + egui::popup::popup_above_or_below_widget(ui, current_wallet_selector_id, &response, AboveOrBelow::Below, |ui| { + ui.set_min_width(200.0); + ui.label("Select a Wallet"); + ui.label(""); + + ScrollArea::vertical() + .id_source("popup_wallet_selector_scroll") + .auto_shrink([true; 2]) + .show(ui, |ui| { + + let wallet_list = core.wallet_list().clone(); + + wallet_list.into_iter().for_each(|wallet_descriptor| { + + let title = if let Some(title) = wallet_descriptor.title.clone() { + title + } else if wallet_descriptor.filename.as_str() == "kaspa" { + "Kaspa Wallet".to_string() + } else { + "NO NAME".to_string() + }; + + if ui.add(CompositeButton::new( + title, + wallet_descriptor.filename.clone(), + )).clicked() + { + core.get_mut::().open(wallet_descriptor.clone()); + core.select::(); + } + }); + + ui.label(""); + ui.separator(); + ui.label(""); + + if ui.medium_button( + "Create New Wallet", + ).clicked() + { + core.select::(); + } + + }); + + + }); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.label(format!("Account: {}", account.name_or_id())); + + let current_account_selector_id = ui.make_persistent_id("current_account_selector"); + let response = ui.add(Label::new(format!("Account: {} ⏷", account.name_or_id())).sense(Sense::click())); + + if response.clicked() { + ui.memory_mut(|mem| mem.toggle_popup(current_account_selector_id)); + } + egui::popup::popup_above_or_below_widget(ui, current_account_selector_id, &response, AboveOrBelow::Below, |ui| { + ui.set_min_width(200.0); + ui.label("Select Account"); + ui.label(""); + + egui::ScrollArea::vertical() + .id_source("popup_account_selector_scroll") + .auto_shrink([true; 2]) + .show(ui, |ui| { + + if let Some(account_collection) = core.account_collection() { + account_collection.iter().for_each(|account| { + if ui + .button(format!("Select {}\n{}", account.name_or_id(),account.balance().map(|balance|sompi_to_kaspa_string_with_suffix(balance.mature, &network_type)).unwrap_or("N/A".to_string()))) + .clicked() + { + self.state = State::Overview { + account: account.clone(), + }; + } + }); + + ui.label(""); + ui.separator(); + ui.label(""); + use egui_phosphor::light::FOLDER_NOTCH_PLUS; + if ui.medium_button(format!("{FOLDER_NOTCH_PLUS} Create New Account")).clicked() { + core.select::(); + } + } + + }); + + }); }); }); @@ -193,29 +343,69 @@ impl ModuleT for AccountManager { ui.vertical_centered(|ui| { - let context = if let Some(context) = account.context() { - context + let account_context = if let Some(account_context) = account.context() { + account_context } else { ui.label("Account is missing context"); return; }; - self.render_balance(core, ui, &account, &context, network_type); + self.render_balance(core, ui, &account, &account_context, network_type); - if self.action.is_sending() { - self.render_send_ui(core, ui, &account, &context, network_type); + if self.context.action.is_sending() { + self.render_send_ui(core, ui, &account, &account_context, network_type); } else { - self.render_qr(core, ui, &context); + self.render_qr(core, ui, &account_context); ui.vertical_centered(|ui|{ ui.horizontal(|ui| { + + + + // if let Some(response) = CenterLayoutBuilder::new() - .add(Button::new(format!("{} Send", ARROW_CIRCLE_UP)).min_size(theme.medium_button_size()), || { - self.action = Action::Estimating; + // .add(Button::new(format!("{} Send", ARROW_CIRCLE_UP)).min_size(theme.medium_button_size()), |(this, _):(&mut AccountManager, &mut Core)| { + // .add(Button::new(format!("{} Send", ARROW_CIRCLE_UP)).min_size(theme.medium_button_size()), |(this, _):(&mut AccountManager, &mut Core)| { + // .add(Button::new(format!("{} Send", ARROW_CIRCLE_UP)).min_size(theme.medium_button_size()), |(this, _):(&mut AccountManager, &mut Core)| { + .add(Button::new(format!("{} Send", ARROW_CIRCLE_UP)).min_size(theme.medium_button_size()), |(this, _):&mut (&mut AccountManager, &mut Core)| { + this.context.action = Action::Estimating; + this.context.transaction_kind = TransactionKind::Send; + // (Action::Estimating,TransactionKind::Send) + // TransactionKind::Send }) - .add(Button::new(format!("{} Request", QR_CODE)).min_size(theme.medium_button_size()), || {}) - .build(ui); + .add(Button::new(format!("{} Transfer", ARROWS_DOWN_UP)).min_size(theme.medium_button_size()), |(this,_)| { + this.context.action = Action::Estimating; + this.context.transaction_kind = TransactionKind::Transfer; + // (Action::Estimating,TransactionKind::Transfer) + // TransactionKind::Transfer + }) + .add(Button::new(format!("{} Request", QR_CODE)).min_size(theme.medium_button_size()), |(_,core)| { + core.select::(); + // TransactionKind::Request + // (Action::Estimating,TransactionKind::Transfer) + + }) + .build(ui,&mut (self,core));// { + + // match response { + // TransactionKind::Send => { + // self.context.action = Action::Estimating; + // self.context.transaction_kind = TransactionKind::Send; + // } + // TransactionKind::Transfer => { + // self.context.action = Action::Estimating; + // self.context.transaction_kind = TransactionKind::Transfer; + // } + // TransactionKind::Request => { + // self.context.action = Action::Estimating; + // self.context.transaction_kind = TransactionKind::Request; + // } + // _ => {} + // } + // } + + }); }); @@ -241,21 +431,21 @@ impl ModuleT for AccountManager { ui.add_space(32.); if ui.button("UTXOs").clicked() { - self.details = Details::UtxoSelector; + self.context.details = Details::UtxoSelector; } ui.separator(); if ui.button("Details").clicked() { - self.details = Details::Account; + self.context.details = Details::Account; } ui.separator(); if ui.button("Transactions").clicked() { - self.details = Details::Transactions; + self.context.details = Details::Transactions; } }); }); ui.separator(); - match self.details { + match self.context.details { Details::Transactions => { self.render_transactions(ui, core, &account, network_type, current_daa_score); } @@ -302,6 +492,32 @@ impl AccountManager { match &*descriptor { AccountDescriptor::Bip32(descriptor) => { descriptor.render(ui); + + let mut address_kind : Option = None; + if ui.medium_button("Generate New Receive Address").clicked() { + address_kind = Some(NewAddressKind::Receive); + } + if ui.medium_button("Generate New Change Address").clicked() { + address_kind = Some(NewAddressKind::Change); + } + + if let Some(address_kind) = address_kind { + let account_id = account.id(); + spawn(async move { + runtime() + .wallet() + .accounts_create_new_address(account_id, address_kind) + .await + .map_err(|err|Error::custom(format!("Failed to create new address\n{err}")))?; + // if let Err(err) = runtime().wallet().accounts_create_new_address(account_id, address_kind).await { + // log_error!("Failed to create new address: {err}"); + // } + + runtime().request_repaint(); + + Ok(()) + }); + } }, _ => { ui.label("Unknown descriptor type"); @@ -317,7 +533,7 @@ impl AccountManager { } - fn render_balance(&mut self, _core: &mut Core, ui : &mut Ui, account : &Account, context : &Context, network_type : NetworkType) { + fn render_balance(&mut self, _core: &mut Core, ui : &mut Ui, account : &Account, context : &account::AccountContext, network_type : NetworkType) { let theme = theme(); @@ -333,6 +549,7 @@ impl AccountManager { // }) .clicked() { ui.output_mut(|o| o.copied_text = context.address().to_string()); + runtime().notify(UserNotification::info("Address is copied to clipboard").short()) } ui.add_space(10.); @@ -386,9 +603,9 @@ impl AccountManager { } - fn render_qr(&mut self, _core: &mut Core, ui : &mut Ui, context : &Context) { + fn render_qr(&mut self, _core: &mut Core, ui : &mut Ui, context : &account::AccountContext) { - let scale = if self.action == Action::None { 1. } else { 0.35 }; + let scale = if self.context.action == Action::None { 1. } else { 0.35 }; ui.add( egui::Image::new(ImageSource::Bytes { uri : Cow::Borrowed("bytes://qr.svg"), bytes: context.qr() }) .fit_to_original_size(scale) @@ -397,20 +614,23 @@ impl AccountManager { } - fn render_send_ui(&mut self, _core: &mut Core, ui: &mut egui::Ui, account : &Account, _context : &Arc, network_type: NetworkType) { + fn render_send_ui(&mut self, _core: &mut Core, ui: &mut egui::Ui, account : &Account, _context : &Arc, network_type: NetworkType) { let theme = theme(); let size = egui::Vec2::new(300_f32, 32_f32); let mut request_estimate = false; + // let mut request_send = false; + + // println!("*** action: {:?}", self.context.action); ui.add_space(8.); ui.label("Sending funds"); ui.add_space(8.); TextEditor::new( - &mut self.destination_address_string, + &mut self.context.destination_address_string, // None, - &mut self.focus, + &mut self.context.focus, Focus::Address, |ui, text| { ui.label(egui::RichText::new("Enter destination address").size(12.).raised()); @@ -423,13 +643,13 @@ impl AccountManager { Ok(address) => { let address_network_type = NetworkType::try_from(address.prefix).expect("prefix to network type"); if address_network_type != network_type { - self.address_status = AddressStatus::NetworkMismatch(address_network_type); + self.context.address_status = AddressStatus::NetworkMismatch(address_network_type); } else { - self.address_status = AddressStatus::Valid; + self.context.address_status = AddressStatus::Valid; } } Err(err) => { - self.address_status = AddressStatus::Invalid(err.to_string()); + self.context.address_status = AddressStatus::Invalid(err.to_string()); } } }) @@ -438,7 +658,7 @@ impl AccountManager { }) .build(ui); - match &self.address_status { + match &self.context.address_status { AddressStatus::Valid => {}, AddressStatus::None => {}, AddressStatus::NetworkMismatch(address_network_type) => { @@ -449,9 +669,9 @@ impl AccountManager { } } - TextEditor::new( - &mut self.send_amount_text, - &mut self.focus, + let response = TextEditor::new( + &mut self.context.send_amount_text, + &mut self.context.focus, Focus::Amount, |ui, text| { ui.label(egui::RichText::new("Enter KAS amount to send").size(12.).raised()); @@ -462,21 +682,22 @@ impl AccountManager { .change(|_| { request_estimate = true; }) - .submit(|_, focus|{ - if self.enable_priority_fees { - *focus = Focus::Fees; - } else { - self.action = Action::Sending; + .build(ui); + + if response.text_edit_submit(ui) { + if self.context.enable_priority_fees { + self.context.focus = Focus::Fees; + } else if self.update_user_args() { + self.context.action = Action::Sending; } - }) - .build(ui); + } - ui.checkbox(&mut self.enable_priority_fees,i18n("Include Priority Fees")); + ui.checkbox(&mut self.context.enable_priority_fees,i18n("Include Priority Fees")); - if self.enable_priority_fees { + if self.context.enable_priority_fees { TextEditor::new( - &mut self.priority_fees_text, - &mut self.focus, + &mut self.context.priority_fees_text, + &mut self.context.focus, Focus::Fees, |ui, text| { ui.label(egui::RichText::new("Enter priority fees").size(12.).raised()); @@ -488,27 +709,21 @@ impl AccountManager { request_estimate = true; }) .submit(|_,_|{ - self.action = Action::Sending; + self.context.action = Action::Sending; }) .build(ui); } - if let Some(send_info) = &self.send_info { - ui.label(send_info); - } + let send_result = Payload::>::new("send_result"); - match self.action { + + match self.context.action { Action::Estimating => { - // if request_estimate { - // println!("request estimate: {}", request_estimate); - // } - if request_estimate && self.update_estimate_args() { + if request_estimate && self.update_user_args() { - self.send_info = None; - // self.send_amount_sompi = send_amount_sompi; - let priority_fees_sompi = if self.enable_priority_fees { - self.priority_fees_sompi + let priority_fees_sompi = if self.context.enable_priority_fees { + self.context.priority_fees_sompi } else { 0 }; let address = match network_type { @@ -517,14 +732,13 @@ impl AccountManager { _ => panic!("Unsupported network"), }; - let runtime = self.runtime.clone(); let account_id = account.id(); let payment_output = PaymentOutput { address, - amount: self.send_amount_sompi, + amount: self.context.send_amount_sompi, }; - let estimate = self.estimate.clone(); + let status = self.context.estimate.clone(); spawn(async move { let request = AccountsEstimateRequest { task_id: None, @@ -534,38 +748,35 @@ impl AccountManager { payload: None, }; - match runtime.wallet().accounts_estimate_call(request).await { + match runtime().wallet().accounts_estimate_call(request).await { Ok(response) => { - println!("estimate ok"); - *estimate.lock().unwrap() = Estimate::GeneratorSummary(response.generator_summary); + *status.lock().unwrap() = Status::GeneratorSummary(response.generator_summary); } Err(error) => { - println!("estimate error"); - *estimate.lock().unwrap() = Estimate::Error(error.to_string()); + *status.lock().unwrap() = Status::Error(error.to_string()); } } - runtime.egui_ctx().request_repaint(); + runtime().egui_ctx().request_repaint(); Ok(()) }); } - let ready_to_send = match &*self.estimate.lock().unwrap() { - Estimate::GeneratorSummary(estimate) => { - println!("rendering estimate..."); + let ready_to_send = match &*self.context.estimate.lock().unwrap() { + Status::GeneratorSummary(estimate) => { if let Some(final_transaction_amount) = estimate.final_transaction_amount { ui.label(format!("Final Amount: {}", sompi_to_kaspa_string_with_suffix(final_transaction_amount + estimate.aggregated_fees, &network_type))); } ui.label(format!("Fees: {}", sompi_to_kaspa_string_with_suffix(estimate.aggregated_fees, &network_type))); ui.label(format!("Transactions: {} UTXOs: {}", estimate.number_of_generated_transactions, estimate.aggregated_utxos)); - self.address_status == AddressStatus::Valid + self.context.address_status == AddressStatus::Valid } - Estimate::Error(error) => { + Status::Error(error) => { ui.label(RichText::new(error.to_string()).color(theme.error_color)); false } - Estimate::None => { + Status::None => { ui.label("Please enter KAS amount to send"); false } @@ -575,34 +786,48 @@ impl AccountManager { use egui_phosphor::light::{CHECK, X}; ui.vertical_centered(|ui|{ ui.horizontal(|ui| { - let mut reset = false; + // let mut reset_send_state = false; + // if let Some(action) = CenterLayoutBuilder::new() - .add_enabled(ready_to_send, Button::new(format!("{CHECK} Send")).min_size(theme.medium_button_size()), || { - self.action = Action::Sending; - }) - .add(Button::new(format!("{X} Cancel")).min_size(theme.medium_button_size()), || { - reset = true; + .add_enabled(ready_to_send, Button::new(format!("{CHECK} Send")).min_size(theme.medium_button_size()), |this: &mut AccountManager| { + this.context.action = Action::Sending; + // Action::Sending }) - .build(ui); + .add(Button::new(format!("{X} Cancel")).min_size(theme.medium_button_size()), |this| { + println!("Clicking CANCEL"); + // reset_send_state = true; + this.context.reset_send_state(); - if reset { - self.reset(); - } + // Action::Reset + }) + .build(ui, self) + // { + // println!("****** ACTION: {:?}", action); + // self.context.action = action; + // } + + // if reset_send_state { + // self.context.reset_send_state(); + // } }); }); }); } + // Action::Reset => { + // println!("Entering RESET"); + // self.context.reset_send_state(); + // self.context.action = Action::None; + // } + Action::Sending => { ui.label(egui::RichText::new("Enter wallet password").size(12.).raised()); let mut proceed_with_send = false; - // let mut send_amount_text = self.send_amount_text.clone(); let response = ui.add_sized( size, - TextEdit::singleline(&mut self.wallet_secret) - // .hint_text("Payment password...") + TextEdit::singleline(&mut self.context.wallet_secret) .password(true) .vertical_align(Align::Center), ); @@ -614,29 +839,29 @@ impl AccountManager { ui.horizontal(|ui| { - if ui.medium_button_enabled(!self.wallet_secret.is_empty(),"Send").clicked() { + if ui.medium_button_enabled(!self.context.wallet_secret.is_empty(),"Send").clicked() { proceed_with_send = true; } if proceed_with_send { - let priority_fees_sompi = if self.enable_priority_fees { - self.priority_fees_sompi + let priority_fees_sompi = if self.context.enable_priority_fees { + self.context.priority_fees_sompi } else { 0 }; - let address = Address::try_from(self.destination_address_string.as_str()).expect("Invalid address"); - let runtime = self.runtime.clone(); + let address = Address::try_from(self.context.destination_address_string.as_str()).expect("Invalid address"); let account_id = account.id(); let payment_output = PaymentOutput { address, - amount: self.send_amount_sompi, + amount: self.context.send_amount_sompi, }; - let wallet_secret = Secret::try_from(self.wallet_secret.clone()).expect("Invalid secret"); + let wallet_secret = Secret::try_from(self.context.wallet_secret.clone()).expect("Invalid secret"); let payment_secret = None; - - spawn(async move { + // let status = self.context.estimate.clone(); + + spawn_with_result(&send_result, async move { let request = AccountsSendRequest { - task_id: None, + // task_id: None, account_id, destination: payment_output.into(), wallet_secret, @@ -645,41 +870,59 @@ impl AccountManager { payload: None, }; - match runtime.wallet().accounts_send_call(request).await { - Ok(response) => { - println!("****** RESPONSE: {:?}", response); - // *estimate.lock().unwrap() = Estimate::GeneratorSummary(response.generator_summary); - } - Err(error) => { - println!("****** ERROR: {}", error); - // *estimate.lock().unwrap() = Estimate::Error(error.to_string()); - } - } + let generator_summary = runtime().wallet().accounts_send_call(request).await?.generator_summary; + // let result = match runtime().wallet().accounts_send_call(request).await; + + // { + // Ok(_response) => { + // // println!("****** RESPONSE: {:?}", response); + // // *estimate.lock().unwrap() = Estimate::GeneratorSummary(response.generator_summary); + // } + // Err(error) => { + // *status.lock().unwrap() = Status::Error(error.to_string()); + // // self.context.action = Action::Estimating; + // // println!("****** ERROR: {}", error); + // // *estimate.lock().unwrap() = Estimate::Error(error.to_string()); + // } + // } - runtime.egui_ctx().request_repaint(); - Ok(()) + runtime().request_repaint(); + Ok(generator_summary) }); - self.reset(); + self.context.action = Action::Processing; + // self.context.reset_send_state(); } if ui.medium_button("Cancel").clicked() { - - self.reset(); + self.context.reset_send_state(); } }); } - _=>{} + Action::Processing => { + ui.add_space(16.); + ui.add(egui::Spinner::new().size(92.)); + + if let Some(_result) = send_result.take() { + + // - TODO - SET AND DISPLAY AN ERROR + // - PRESENT CLOSE BUTTON BEFORE CONTINUING + // - RESET STATE TO ESTIMATING? + + self.context.action = Action::None; + } + } + Action::None => {} } } - fn update_estimate_args(&mut self) -> bool { + fn update_user_args(&mut self) -> bool { let mut valid = true; - match try_kaspa_str_to_sompi(self.send_amount_text.as_str()) { + match try_kaspa_str_to_sompi(self.context.send_amount_text.as_str()) { Ok(Some(sompi)) => { - self.send_amount_sompi = sompi; + self.context.send_amount_sompi = sompi; } Ok(None) => { self.user_error("Please enter an amount".to_string()); @@ -691,12 +934,12 @@ impl AccountManager { } } - match try_kaspa_str_to_sompi(self.priority_fees_text.as_str()) { + match try_kaspa_str_to_sompi(self.context.priority_fees_text.as_str()) { Ok(Some(sompi)) => { - self.priority_fees_sompi = sompi; + self.context.priority_fees_sompi = sompi; } Ok(None) => { - self.priority_fees_sompi = 0; + self.context.priority_fees_sompi = 0; } Err(err) => { self.user_error(format!("Invalid fee amount: {err}")); @@ -708,19 +951,19 @@ impl AccountManager { } fn user_error(&self, error : impl Into) { - *self.estimate.lock().unwrap() = Estimate::Error(error.into()); + *self.context.estimate.lock().unwrap() = Status::Error(error.into()); } - fn reset(&mut self) { - *self.estimate.lock().unwrap() = Estimate::None; - self.address_status = AddressStatus::None; - self.destination_address_string = String::default(); - self.send_amount_text = String::default(); - self.send_amount_sompi = 0; - self.action = Action::None; - self.focus = Focus::None; - self.wallet_secret.zeroize(); - self.payment_secret.zeroize(); - } + // fn reset_send_state(&mut self) { + // *self.context.estimate.lock().unwrap() = Estimate::None; + // self.context.address_status = AddressStatus::None; + // self.context.destination_address_string = String::default(); + // self.context.send_amount_text = String::default(); + // self.context.send_amount_sompi = 0; + // self.context.action = Action::None; + // self.context.focus = Focus::None; + // self.context.wallet_secret.zeroize(); + // self.context.payment_secret.zeroize(); + // } } \ No newline at end of file diff --git a/core/src/modules/block_dag.rs b/core/src/modules/block_dag.rs index 1469c43..d12bc9e 100644 --- a/core/src/modules/block_dag.rs +++ b/core/src/modules/block_dag.rs @@ -90,11 +90,13 @@ impl ModuleT for BlockDag { ui.separator(); + let mut reset_plot = false; let current_daa_score = core.state().current_daa_score().unwrap_or_default(); if self.last_daa_score != current_daa_score { if !self.running { self.running = true; + reset_plot = true; self.daa_cursor = current_daa_score as f64; } @@ -115,7 +117,7 @@ impl ModuleT for BlockDag { let pixels_per_daa = graph_width as f64 / default_daa_range; let bezier_steps = if pixels_per_daa < 2.0 { 2 } else { pixels_per_daa as usize / 3}; - let plot = Plot::new("block_dag") + let mut plot = Plot::new("block_dag") .width(graph_width) .height(graph_height) .include_x(default_daa_max) @@ -126,7 +128,6 @@ impl ModuleT for BlockDag { .y_axis_width(0) .show_axes([true, false]) .show_grid(true) - // .allow_drag([true, false]) .allow_drag([true, true]) .allow_scroll(true) .allow_double_click_reset(true) @@ -150,6 +151,13 @@ impl ModuleT for BlockDag { }) ; + if reset_plot { + // As of egui 0.24, we need to tap auto bounds once + // when the plot is re-positioned to get it to track + // the manually set bounds. + plot = plot.auto_bounds_x().auto_bounds_y(); + } + let mut graph_settled = true; let mut lines_parent = Vec::new(); let mut lines_vspc = Vec::new(); @@ -245,6 +253,7 @@ impl ModuleT for BlockDag { } fn deactivate(&mut self, _core: &mut Core) { + self.running = false; crate::runtime::runtime().block_dag_monitor_service().disable(); } } diff --git a/core/src/modules/mod.rs b/core/src/modules/mod.rs index 69fd6e5..4403943 100644 --- a/core/src/modules/mod.rs +++ b/core/src/modules/mod.rs @@ -54,6 +54,7 @@ pub trait ModuleT: Downcast { fn status_bar(&self, _ui: &mut Ui) {} fn activate(&mut self, _core: &mut Core) {} fn deactivate(&mut self, _core: &mut Core) {} + fn reset(&mut self, _core: &mut Core) {} fn init(&mut self, _core: &mut Core) {} @@ -95,6 +96,10 @@ impl Module { self.inner.module.borrow_mut().deactivate(core) } + pub fn reset(&self, core: &mut Core) { + self.inner.module.borrow_mut().reset(core) + } + pub fn status_bar(&self, ui: &mut Ui) { self.inner.module.borrow_mut().status_bar(ui) } diff --git a/core/src/modules/overview.rs b/core/src/modules/overview.rs index 855fea5..0608a1e 100644 --- a/core/src/modules/overview.rs +++ b/core/src/modules/overview.rs @@ -78,10 +78,6 @@ impl Overview { } }); - if let Some(system) = runtime().system() { - system.render(ui); - } - ui.add_space(48.); } @@ -163,7 +159,10 @@ impl Overview { ui.label("TODO"); }); - + if let Some(system) = runtime().system() { + system.render(ui); + } + CollapsingHeader::new(i18n("Build")) .default_open(true) .show(ui, |ui| { @@ -178,7 +177,7 @@ impl Overview { ui.label(format!("architecture {}", crate::app::CARGO_TARGET_TRIPLE )); - ui.label("Codename: \"This is the way\""); + ui.label(format!("Codename: \"{}\"", crate::app::CODENAME)); }); @@ -223,42 +222,47 @@ impl Overview { .default_open(false) .show(ui, |ui| { ui.vertical(|ui|{ - ui.label("Special thanks to the following people:"); - ui.horizontal_wrapped(|ui|{ - let mut nicks = [ - "142673", - "Bubblegum Lightning", - "coderofstuff", - "CryptoK", - "Elertan", - "hashdag", - "jablonx", - "jwj", - "lAmeR", - "matoo", - "msutton", - "Rhubarbarian", - "shaideshe", - "someone235", - "supertypo", - "Tim", - "Wolfie", - "KaffinPX" - ]; - nicks.sort(); - nicks.into_iter().for_each(|nick| { - ui.label(format!("@{nick}")); + ui.label("Special thanks Kaspa developers and the following community members:"); + // ui.horizontal(|ui|{ + ui.horizontal_wrapped(|ui|{ + ui.set_width(ui.available_width() - 64.); + let mut nicks = [ + "142673", + "Bape", + "Bubblegum Lightning", + "coderofstuff", + "CryptoK", + "Elertan", + "hashdag", + "jablonx", + "jwj", + "lAmeR", + "matoo", + "msutton", + "Rhubarbarian", + "shaideshe", + "someone235", + "supertypo", + "Tim", + "Wolfie", + "KaffinPX" + ]; + nicks.sort(); + nicks.into_iter().for_each(|nick| { + ui.label(format!("@{nick}")); + }); }); - }); + // ui.add_space(32.); + // }); }); }); CollapsingHeader::new(i18n("Donations")) - .default_open(true) - .show(ui, |ui| { - ui.label("Please support Kaspa NG development"); - ui.label("kaspatest:qqdr2mv4vkes6kvhgy8elsxhvzwde42629vnpcxe4f802346rnfkklrhz0x7x"); - }); + .default_open(true) + .show(ui, |ui| { + ui.label("Please support Kaspa NG development"); + ui.label("kaspatest:qqdr2mv4vkes6kvhgy8elsxhvzwde42629vnpcxe4f802346rnfkklrhz0x7x"); + }); }); diff --git a/core/src/modules/wallet_open.rs b/core/src/modules/wallet_open.rs index ca34855..c48ad73 100644 --- a/core/src/modules/wallet_open.rs +++ b/core/src/modules/wallet_open.rs @@ -5,8 +5,8 @@ use crate::imports::*; pub enum State { #[default] Select, - Unlock(Option>), - Unlocking, + Unlock { wallet_descriptor : WalletDescriptor, error : Option>}, + Unlocking { wallet_descriptor : WalletDescriptor }, } pub struct WalletOpen { @@ -15,7 +15,7 @@ pub struct WalletOpen { wallet_secret: String, pub state: State, pub message: Option, - selected_wallet: Option, + // selected_wallet: Option, } impl WalletOpen { @@ -25,14 +25,18 @@ impl WalletOpen { wallet_secret: String::new(), state: State::Select, message: None, - selected_wallet: None, + // selected_wallet: None, } } - pub fn lock(&mut self) { - // Go to unlock page - self.state = State::Unlock(None); + pub fn open(&mut self, wallet_descriptor: WalletDescriptor) { + self.state = State::Unlock { wallet_descriptor, error : None}; } + + // pub fn lock(&mut self) { + // // Go to unlock page + // self.state = State::Unlock(None); + // } } impl ModuleT for WalletOpen { @@ -46,7 +50,7 @@ impl ModuleT for WalletOpen { ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| { let size = egui::Vec2::new(200_f32, 40_f32); - let unlock_result = Payload::>::new("test"); + let unlock_result = Payload::>::new("wallet_unlock_result"); let text: &str = "Select a wallet to unlock"; @@ -59,14 +63,12 @@ impl ModuleT for WalletOpen { ui.label(text); }) .with_body(|this, ui| { - for wallet in core.wallet_list.iter() { - // let text = render_wallet_descriptor(wallet, ui); - let text = wallet.filename.clone(); - - // if ui.add_sized(size, egui::Button::new(wallet.filename.clone())).clicked() { - if ui.add_sized(size, egui::Button::new(text)).clicked() { - this.selected_wallet = Some(wallet.filename.clone()); - this.state = State::Unlock(None); + for wallet_descriptor in core.wallet_list.iter() { + if ui.add_sized(size, CompositeButton::new( + wallet_descriptor.title.as_deref().unwrap_or("NO NAME"), + wallet_descriptor.filename.clone(), + )).clicked() { + this.state = State::Unlock { wallet_descriptor : wallet_descriptor.clone(), error : None }; } } ui.label(" "); @@ -86,7 +88,7 @@ impl ModuleT for WalletOpen { .render(ui); } - State::Unlock(error) => { + State::Unlock{wallet_descriptor, error} => { // let width = ui.available_width(); // let theme = theme(); Panel::new(self) @@ -99,7 +101,8 @@ impl ModuleT for WalletOpen { // ui.label(" "); ui.label(format!( "Opening wallet: \"{}\"", - ctx.selected_wallet.as_ref().unwrap() + // ctx.selected_wallet.as_ref().unwrap() + wallet_descriptor.title.as_deref().unwrap_or(wallet_descriptor.filename.as_str()) )); ui.label(" "); // ui.add_space(24.); @@ -153,17 +156,13 @@ impl ModuleT for WalletOpen { ); ctx.wallet_secret.zeroize(); let wallet = ctx.runtime.wallet().clone(); - let wallet_name = ctx.selected_wallet.clone(); //.expect("Wallet name not set"); - + let wallet_descriptor_delegate = wallet_descriptor.clone(); spawn_with_result(&unlock_result, async move { - // let account_descriptors = - wallet.wallet_open(wallet_secret, wallet_name, true, true).await?; - // TODO - load transactions - // wallet.accounts_activate(None).await?; + wallet.wallet_open(wallet_secret, Some(wallet_descriptor_delegate.filename), true, true).await?; Ok(()) }); - ctx.state = State::Unlocking; + ctx.state = State::Unlocking { wallet_descriptor }; } ui.label(" "); @@ -188,7 +187,7 @@ impl ModuleT for WalletOpen { // } // }); } - State::Unlocking => { + State::Unlocking { wallet_descriptor } => { ui.heading("Unlocking"); // ui.separator(); ui.label(" "); @@ -207,7 +206,7 @@ impl ModuleT for WalletOpen { } Err(err) => { println!("Unlock error: {}", err); - self.state = State::Unlock(Some(Arc::new(err))); + self.state = State::Unlock { wallet_descriptor, error : Some(Arc::new(err)) }; } } // ui.label(format!("Result: {:?}", result)); diff --git a/core/src/notifications.rs b/core/src/notifications.rs index 281949e..a087bf4 100644 --- a/core/src/notifications.rs +++ b/core/src/notifications.rs @@ -60,6 +60,11 @@ impl UserNotification { Self::new(UserNotifyKind::Basic, text) } + pub fn short(mut self) -> Self { + self.duration = Some(Duration::from_millis(1500)); + self + } + pub fn render(self, toasts: &mut Toasts) { match self.kind { UserNotifyKind::Info => { diff --git a/core/src/primitives/account.rs b/core/src/primitives/account.rs index 7afe5d3..07ac6f6 100644 --- a/core/src/primitives/account.rs +++ b/core/src/primitives/account.rs @@ -1,11 +1,13 @@ +use kaspa_wallet_core::storage::AccountKind; + use crate::imports::*; -pub struct Context { +pub struct AccountContext { qr: load::Bytes, receive_address: Address, } -impl Context { +impl AccountContext { pub fn new(descriptor: &AccountDescriptor) -> Option> { if let Some(receive_address) = descriptor.receive_address() { let qr = render_qrcode(&receive_address.to_string(), 128, 128); @@ -33,7 +35,7 @@ struct Inner { balance: Mutex>, utxo_sizes: Mutex>, descriptor: Mutex, - context: Mutex>>, + context: Mutex>>, transactions: Mutex, total_transaction_count: AtomicU64, is_loading: AtomicBool, @@ -41,7 +43,7 @@ struct Inner { impl Inner { fn new(descriptor: AccountDescriptor) -> Self { - let context = Context::new(&descriptor); + let context = AccountContext::new(&descriptor); Self { id: *descriptor.account_id(), balance: Mutex::new(None), @@ -98,15 +100,13 @@ impl Account { // Ok(self.inner.runtime.receive_address()?.into()) // } - pub fn context(&self) -> Option> { + pub fn context(&self) -> Option> { self.inner.context.lock().unwrap().clone() } - pub fn update(&self, descriptor: AccountDescriptor) -> Result<()> { - *self.inner.context.lock().unwrap() = Context::new(&descriptor); + pub fn update(&self, descriptor: AccountDescriptor) { + *self.inner.context.lock().unwrap() = AccountContext::new(&descriptor); *self.inner.descriptor.lock().unwrap() = descriptor; - - Ok(()) } pub fn update_balance( @@ -167,3 +167,20 @@ impl std::fmt::Debug for Account { } pub type AccountCollection = Collection; + +pub trait DescribeAccount { + fn describe(&self) -> (&'static str, &'static str); +} + +impl DescribeAccount for AccountKind { + fn describe(&self) -> (&'static str, &'static str) { + match self { + AccountKind::Legacy => ("Legacy Account", "KDX, PWA (kaspanet.io)"), + AccountKind::Bip32 => ("Kaspa Core BIP32", "kaspawallet, kaspium"), + AccountKind::MultiSig => ("Multi-Signature", ""), + AccountKind::Keypair => ("Keypair", "secp256k1"), + AccountKind::Hardware => ("Hardware", ""), + _ => ("", ""), + } + } +} diff --git a/core/src/runtime/mod.rs b/core/src/runtime/mod.rs index d29dc41..b81b1fe 100644 --- a/core/src/runtime/mod.rs +++ b/core/src/runtime/mod.rs @@ -196,6 +196,14 @@ impl Runtime { Ok(()) } + pub fn notify(&self, user_notification: UserNotification) { + self.inner + .application_events + .sender + .try_send(Events::Notify { user_notification }) + .ok(); + } + pub fn spawn_task(&self, task: F) where F: Future> + Send + 'static, @@ -207,7 +215,7 @@ impl Runtime { .send(Events::Error(Box::new(err.to_string()))) .await .unwrap(); - println!("spawned task error: {:?}", err); + // println!("spawned task error: {:?}", err); } }); } diff --git a/core/src/runtime/system.rs b/core/src/runtime/system.rs index 142a198..7c8dcd4 100644 --- a/core/src/runtime/system.rs +++ b/core/src/runtime/system.rs @@ -8,6 +8,7 @@ cfg_if! { pub cpu_brand : Option, pub total_memory : u64, pub long_os_version : Option, + pub fd_limit : i32, } impl Default for System { @@ -32,12 +33,15 @@ cfg_if! { .first() .map(|cpu|(cpu.frequency(),cpu.brand().to_string())).unzip(); + let fd_limit = kaspa_utils::fd_budget::limit(); + Self { cpu_physical_core_count, cpu_frequency, cpu_brand, total_memory, long_os_version, + fd_limit, } } @@ -47,24 +51,19 @@ cfg_if! { CollapsingHeader::new(i18n("System")) .default_open(true) .show(ui, |ui| { + if let Some(os) = self.long_os_version.clone() { + ui.label(os); + } if let Some(cpu_physical_core_count) = self.cpu_physical_core_count { - ui.horizontal(|ui| { - if let Some(cpu_brand) = self.cpu_brand.as_ref() { - ui.label(cpu_brand.as_str()); - } - }); - ui.horizontal(|ui| { - let freq = self.cpu_frequency.map(|freq|format!(" @ {:.2} GHz", freq as f64 / 1000.0)).unwrap_or_default(); - ui.label(format!("{} CPU cores {freq}", cpu_physical_core_count)); - }); + if let Some(cpu_brand) = self.cpu_brand.as_ref() { + ui.label(cpu_brand.clone()); //format!("{cpu_brand}")); + } + let freq = self.cpu_frequency.map(|freq|format!(" @ {:.2} GHz", freq as f64 / 1000.0)).unwrap_or_default(); + ui.label(format!("{} CPU cores {freq}", cpu_physical_core_count)); } - - ui.horizontal(|ui| { - let os = self.long_os_version.clone().unwrap_or_default(); - ui.label(format!("{} RAM {os}", as_data_size(self.total_memory as f64, false))); - }); - + ui.label(format!("{} RAM", as_data_size(self.total_memory as f64, false))); + ui.label(format!("File descriptors: {}", self.fd_limit.separated_string())); }); } } diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs index 14881fd..b1e14a8 100644 --- a/core/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -82,24 +82,24 @@ pub fn icon_with_text(ui: &Ui, icon: &str, color: Color32, text: &str) -> Layout job } -type Handler<'panel> = Box; +type Handler<'layout, Context> = Box; #[derive(Default)] -pub struct CenterLayoutBuilder<'layout, W> +pub struct CenterLayoutBuilder<'layout, W, Context> where W: Widget, { - pub list: Vec<(bool, W, Handler<'layout>)>, + pub list: Vec<(bool, W, Handler<'layout, Context>)>, } -impl<'layout, W> CenterLayoutBuilder<'layout, W> +impl<'layout, W, Context> CenterLayoutBuilder<'layout, W, Context> where W: Widget, { pub fn new() -> Self { Self { list: Vec::new() } } - pub fn add(mut self, widget: W, handler: impl FnOnce() + 'layout) -> Self { + pub fn add(mut self, widget: W, handler: impl FnOnce(&mut Context) + 'layout) -> Self { self.list.push((true, widget, Box::new(handler))); self } @@ -107,13 +107,13 @@ where mut self, enabled: bool, widget: W, - handler: impl FnOnce() + 'layout, + handler: impl FnOnce(&mut Context) + 'layout, ) -> Self { self.list.push((enabled, widget, Box::new(handler))); self } - pub fn build(self, ui: &mut Ui) { + pub fn build(self, ui: &mut Ui, context: &mut Context) { let theme = theme(); let button_size = theme.medium_button_size(); let available_width = ui.available_width(); @@ -127,7 +127,7 @@ where .into_iter() .for_each(|(enabled, widget, handler)| { if ui.add_enabled(enabled, widget).clicked() { - handler(); + handler(context); } }); }