diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index 7c4ebdfa..cb362a8b 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -148,6 +148,11 @@ impl ContentContainer for WalletsContent { content.close_qr_scan(); return false; } + // Close account list. + if content.account_list_showing() { + content.close_qr_scan(); + return false; + } // Close opened wallet. self.wallet_content = None; return false; @@ -384,6 +389,10 @@ impl WalletsContent { let show_list = AppConfig::show_wallets_at_dual_panel(); let showing_settings = self.showing_settings(); let creating_wallet = self.creating_wallet(); + let account_list_showing = show_wallet && self.wallet_content + .as_ref() + .unwrap() + .account_list_showing(); let qr_scan = { let mut scan = false; if show_wallet { @@ -396,7 +405,9 @@ impl WalletsContent { || (dual_panel && !show_list)) && !creating_wallet && !showing_settings { let wallet_content = self.wallet_content.as_ref().unwrap(); let wallet_tab_type = wallet_content.current_tab.get_type(); - let title_text = if qr_scan { + let title_text = if account_list_showing { + t!("wallets.accounts") + } else if qr_scan { t!("scan_qr") } else { wallet_tab_type.name() @@ -422,7 +433,11 @@ impl WalletsContent { if dual_title { let wallet_content = self.wallet_content.as_ref().unwrap(); let wallet_tab_type = wallet_content.current_tab.get_type(); - let wallet_title_text = wallet_tab_type.name(); + let wallet_title_text = if account_list_showing { + t!("wallets.accounts") + } else { + wallet_tab_type.name() + }; let wallet_title_content = if wallet_tab_type == WalletTabType::Settings { TitleContentType::Title(wallet_title_text) } else { @@ -445,6 +460,7 @@ impl WalletsContent { }); } else if show_wallet && !dual_panel { View::title_button_big(ui, ARROW_LEFT, |_| { + // Close QR code scanner. let wallet_qr_scan = self.wallet_content .as_ref() .unwrap() @@ -454,7 +470,11 @@ impl WalletsContent { self.wallet_content.as_mut().unwrap().close_qr_scan(); return; } - self.wallet_content = None; + // Close account list. + if account_list_showing { + self.wallet_content.as_mut().unwrap().close_account_list(); + return; + } }); } else if self.creating_wallet() { let mut close = false; diff --git a/src/gui/views/wallets/creation/content.rs b/src/gui/views/wallets/creation/content.rs index 2d72150d..c0b0d31b 100644 --- a/src/gui/views/wallets/creation/content.rs +++ b/src/gui/views/wallets/creation/content.rs @@ -358,7 +358,7 @@ impl WalletCreationContent { if Node::is_running() && !Content::is_dual_panel_mode(ui.ctx()) { ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY); } - self.network_setup.content_ui(ui, cb); + self.network_setup.ui(ui, cb); } } } diff --git a/src/gui/views/wallets/wallet/account/content.rs b/src/gui/views/wallets/wallet/account/content.rs index 9f70af70..e97f769c 100644 --- a/src/gui/views/wallets/wallet/account/content.rs +++ b/src/gui/views/wallets/wallet/account/content.rs @@ -19,115 +19,79 @@ use grin_core::core::amount_to_hr_string; use crate::gui::icons::{FOLDER_USER, PACKAGE, SCAN, SPINNER, USERS_THREE}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::types::{ContentContainer, ModalPosition}; -use crate::gui::views::wallets::wallet::modals::WalletAccountsModal; -use crate::gui::views::wallets::wallet::types::GRIN; +use crate::gui::views::wallets::wallet::account::list::WalletAccountsContent; +use crate::gui::views::wallets::wallet::types::{WalletContentContainer, GRIN}; use crate::gui::views::{CameraContent, Content, Modal, View}; use crate::gui::Colors; +use crate::gui::views::types::ModalPosition; +use crate::gui::views::wallets::wallet::account::create::CreateAccountContent; use crate::wallet::{Wallet, WalletConfig}; /// Wallet account panel content. pub struct AccountContent { - /// Wallet instance. - wallet: Wallet, + /// Account list content. + accounts_content: Option, - /// Wallet accounts [`Modal`] content. - accounts_modal_content: WalletAccountsModal, + /// Account creation [`Modal`] content. + create_account_content: CreateAccountContent, /// QR code scan content. qr_scan_content: Option, } -/// Identifier for account list [`Modal`]. -const ACCOUNT_LIST_MODAL: &'static str = "account_list_modal"; +/// Account creation [`Modal`] identifier. +const CREATE_MODAL_ID: &'static str = "create_account_modal"; -impl ContentContainer for AccountContent { +impl WalletContentContainer for AccountContent { fn modal_ids(&self) -> Vec<&'static str> { vec![ - ACCOUNT_LIST_MODAL, + CREATE_MODAL_ID ] } - fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + fn modal_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { match modal.id { - ACCOUNT_LIST_MODAL => { - self.accounts_modal_content.ui(ui, &self.wallet, modal, cb); - } + CREATE_MODAL_ID => self.create_account_content.ui(ui, wallet, modal, cb), _ => {} } } - fn on_back(&mut self, _: &dyn PlatformCallbacks) -> bool { - true - } - - fn container_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { + fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { if self.qr_scan_content.is_some() { - if let Some(result) = self.qr_scan_content.as_ref().unwrap().qr_scan_result() { - // match result { - // QrScanResult::Address(address) => { - // self.current_tab = - // Box::new(WalletTransport::new(Some(address.to_string()))); - // } - // _ => { - // self.current_tab = - // Box::new(WalletMessages::new(Some(result.text()))) - // } - // } - // Stop camera and close scanning. - cb.stop_camera(); - self.qr_scan_content = None; - } else { - View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH, |ui| { - self.qr_scan_content.as_mut().unwrap().ui(ui, cb); - ui.add_space(6.0); - ui.vertical_centered_justified(|ui| { - View::button(ui, t!("close"), Colors::white_or_black(false), || { - cb.stop_camera(); - self.qr_scan_content = None; - }); - }); - ui.add_space(6.0); - }); - } + self.qr_scan_ui(ui, wallet, cb); } else { View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| { - self.account_ui(ui, cb); + if self.accounts_content.is_some() { + self.list_ui(ui, wallet); + } else { + self.account_ui(ui, wallet, cb); + } }); } } } -impl AccountContent { - /// Create new wallet account content. - pub fn new(wallet: Wallet) -> Self { - let accounts_modal = WalletAccountsModal::new(wallet.accounts()); +impl Default for AccountContent { + fn default() -> Self { Self { - wallet, - accounts_modal_content: accounts_modal, + accounts_content: None, + create_account_content: CreateAccountContent::default(), qr_scan_content: None, } } +} - /// Check if QR code scanner is showing. - pub fn qr_scan_showing(&self) -> bool { - self.qr_scan_content.is_some() - } - - /// Close QR code scanner. - pub fn close_qr_scan(&mut self) { - self.qr_scan_content = None; - } - +impl AccountContent { /// Draw wallet account content. fn account_ui(&mut self, ui: &mut egui::Ui, + wallet: &Wallet, cb: &dyn PlatformCallbacks) { // Check wallet data. - if self.wallet.get_data().is_none() { + if wallet.get_data().is_none() { return; } - let data = self.wallet.get_data().unwrap(); + let data = wallet.get_data().unwrap(); let mut rect = ui.available_rect_before_wrap(); rect.set_height(75.0); @@ -148,11 +112,18 @@ impl AccountContent { // Draw button to show list of accounts. View::item_button(ui, View::item_rounding(1, 3, true), USERS_THREE, None, || { - self.accounts_modal_content = WalletAccountsModal::new(self.wallet.accounts()); - Modal::new(ACCOUNT_LIST_MODAL) - .position(ModalPosition::CenterTop) - .title(t!("wallets.accounts")) - .show(); + let accounts = wallet.accounts(); + if accounts.len() == 1 { + self.create_account_content = CreateAccountContent::default(); + Modal::new(CREATE_MODAL_ID) + .position(ModalPosition::CenterTop) + .title(t!("wallets.accounts")) + .show(); + } else { + self.accounts_content = Some( + WalletAccountsContent::new(accounts, wallet.get_config().account) + ); + } }); let layout_size = ui.available_size(); @@ -172,7 +143,7 @@ impl AccountContent { ui.add_space(-2.0); // Show account label. - let account = self.wallet.get_config().account; + let account = wallet.get_config().account; let default_acc_label = WalletConfig::DEFAULT_ACCOUNT_LABEL.to_string(); let acc_label = if account == default_acc_label { t!("wallets.default_account") @@ -183,15 +154,15 @@ impl AccountContent { View::ellipsize_text(ui, acc_text, 15.0, Colors::text(false)); // Show confirmed height or sync progress. - let status_text = if !self.wallet.syncing() { + let status_text = if !wallet.syncing() { format!("{} {}", PACKAGE, data.info.last_confirmed_height) } else { - let info_progress = self.wallet.info_sync_progress(); + let info_progress = wallet.info_sync_progress(); if info_progress == 100 || info_progress == 0 { format!("{} {}", SPINNER, t!("wallets.wallet_loading")) } else { - if self.wallet.is_repairing() { - let rep_progress = self.wallet.repairing_progress(); + if wallet.is_repairing() { + let rep_progress = wallet.repairing_progress(); if rep_progress == 0 { format!("{} {}", SPINNER, t!("wallets.wallet_checking")) } else { @@ -212,9 +183,104 @@ impl AccountContent { status_text, 15.0, Colors::gray(), - self.wallet.syncing()); + wallet.syncing()); }) }); }); } + + /// Draw account list content. + fn list_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) { + if let Some(accounts) = self.accounts_content.as_mut() { + let mut selected = false; + accounts.ui(ui, |acc| { + let _ = wallet.set_active_account(&acc.label); + selected = true; + }); + if selected { + self.accounts_content = None; + return; + } + } else { + return; + } + + ui.add_space(2.0); + View::horizontal_line(ui, Colors::item_stroke()); + ui.add_space(6.0); + + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + // Show modal buttons. + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { + self.accounts_content = None; + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.add"), Colors::white_or_black(false), || { + self.accounts_content = None; + self.create_account_content = CreateAccountContent::default(); + Modal::new(CREATE_MODAL_ID) + .position(ModalPosition::CenterTop) + .title(t!("wallets.accounts")) + .show(); + }); + }); + }); + ui.add_space(6.0); + } + + /// Draw QR code scanner content. + fn qr_scan_ui(&mut self, ui: &mut egui::Ui, _: &Wallet, cb: &dyn PlatformCallbacks) { + if let Some(_) = self.qr_scan_content.as_ref().unwrap().qr_scan_result() { + // match result { + // QrScanResult::Address(address) => { + // self.current_tab = + // Box::new(WalletTransport::new(Some(address.to_string()))); + // } + // _ => { + // self.current_tab = + // Box::new(WalletMessages::new(Some(result.text()))) + // } + // } + // Stop camera and close scanning. + cb.stop_camera(); + self.qr_scan_content = None; + } else { + View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH, |ui| { + self.qr_scan_content.as_mut().unwrap().ui(ui, cb); + ui.add_space(6.0); + ui.vertical_centered_justified(|ui| { + View::button(ui, t!("close"), Colors::white_or_black(false), || { + cb.stop_camera(); + self.qr_scan_content = None; + }); + }); + ui.add_space(6.0); + }); + } + } + + /// Check if account list is showing. + pub fn account_list_showing(&self) -> bool { + self.accounts_content.is_some() + } + + /// Close account list. + pub fn close_account_list(&mut self) { + self.accounts_content = None; + } + + /// Check if QR code scanner is showing. + pub fn qr_scan_showing(&self) -> bool { + self.qr_scan_content.is_some() + } + + /// Close QR code scanner. + pub fn close_qr_scan(&mut self) { + self.qr_scan_content = None; + } } \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/account/create.rs b/src/gui/views/wallets/wallet/account/create.rs new file mode 100644 index 00000000..5b2ec836 --- /dev/null +++ b/src/gui/views/wallets/wallet/account/create.rs @@ -0,0 +1,103 @@ +// Copyright 2025 The Grim Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use egui::{Id, RichText}; + +use crate::gui::platform::PlatformCallbacks; +use crate::gui::views::{Modal, TextEdit, View}; +use crate::gui::Colors; +use crate::wallet::Wallet; + +/// Account creation [`Modal`] content. +pub struct CreateAccountContent { + /// Account label value. + account_label_edit: String, + /// Flag to check if error occurred during account creation. + account_creation_error: bool, +} + +impl Default for CreateAccountContent { + fn default() -> Self { + Self { + account_label_edit: "".to_string(), + account_creation_error: false, + } + } +} + +impl CreateAccountContent { + /// Draw account creation [`Modal`] content. + pub fn ui(&mut self, + ui: &mut egui::Ui, + wallet: &Wallet, + modal: &Modal, + cb: &dyn PlatformCallbacks) { + let on_create = |m: &mut CreateAccountContent| { + if m.account_label_edit.is_empty() { + return; + } + let label = &m.account_label_edit; + match wallet.create_account(label) { + Ok(_) => { + let _ = wallet.set_active_account(label); + Modal::close(); + }, + Err(_) => m.account_creation_error = true + }; + }; + + ui.add_space(6.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("wallets.new_account_desc")) + .size(17.0) + .color(Colors::gray())); + ui.add_space(8.0); + + // Draw account name edit. + let mut name_edit = TextEdit::new(Id::from(modal.id).with(wallet.get_config().id)); + name_edit.ui(ui, &mut self.account_label_edit, cb); + if name_edit.enter_pressed { + on_create(self); + } + + // Show error occurred during account creation. + if self.account_creation_error { + ui.add_space(12.0); + ui.label(RichText::new(t!("error")) + .size(17.0) + .color(Colors::red())); + } + ui.add_space(12.0); + }); + + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + // Show modal buttons. + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { + // Close modal. + Modal::close(); + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::button(ui, t!("create"), Colors::white_or_black(false), || { + on_create(self); + }); + }); + }); + ui.add_space(6.0); + } +} \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/account/list.rs b/src/gui/views/wallets/wallet/account/list.rs new file mode 100644 index 00000000..ccdf139e --- /dev/null +++ b/src/gui/views/wallets/wallet/account/list.rs @@ -0,0 +1,133 @@ +// Copyright 2025 The Grim Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::gui::icons::{CHECK, CHECK_FAT, FOLDER_USER, PATH}; +use crate::gui::views::wallets::wallet::types::GRIN; +use crate::gui::views::View; +use crate::gui::Colors; +use crate::wallet::types::WalletAccount; +use crate::wallet::WalletConfig; + +use egui::scroll_area::ScrollBarVisibility; +use egui::{Align, Layout, RichText, ScrollArea, StrokeKind}; +use grin_core::core::amount_to_hr_string; + +/// Wallet account list content. +pub struct WalletAccountsContent { + /// List of wallet accounts. + accounts: Vec, + /// Current wallet account label. + current_label: String, +} + +const ACCOUNT_ITEM_HEIGHT: f32 = 75.0; + +impl WalletAccountsContent { + /// Create new accounts content. + pub fn new(accounts: Vec, current: String) -> Self { + Self { accounts, current_label: current } + } + + /// Draw account list content. + pub fn ui(&mut self, ui: &mut egui::Ui, mut on_select: impl FnMut(WalletAccount)) { + let size = self.accounts.len(); + ScrollArea::vertical() + .id_salt("account_list_scroll") + .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) + .max_height(266.0) + .auto_shrink([true; 2]) + .show_rows(ui, ACCOUNT_ITEM_HEIGHT, size, |ui, row_range| { + for index in row_range { + // Add space before the first item. + if index == 0 { + ui.add_space(4.0); + } + let acc = self.accounts.get(index).unwrap().clone(); + self.account_item_ui(ui, &acc, index, size, || { + on_select(acc.clone()); + }); + if index == size - 1 { + ui.add_space(4.0); + } + } + }); + } + + /// Draw account item. + fn account_item_ui(&mut self, + ui: &mut egui::Ui, + acc: &WalletAccount, + index: usize, + size: usize, + mut on_select: impl FnMut()) { + // Setup layout size. + let mut rect = ui.available_rect_before_wrap(); + rect.set_height(ACCOUNT_ITEM_HEIGHT); + + // Draw round background. + let bg_rect = rect.clone(); + let item_rounding = View::item_rounding(index, size, false); + ui.painter().rect(bg_rect, + item_rounding, + Colors::fill(), + View::item_stroke(), + StrokeKind::Middle); + + ui.vertical(|ui| { + ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { + // Draw button to select account. + let is_current_account = self.current_label == acc.label; + if !is_current_account { + let button_rounding = View::item_rounding(index, size, true); + View::item_button(ui, button_rounding, CHECK, None, || { + on_select(); + }); + } else { + ui.add_space(12.0); + ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green())); + } + + let layout_size = ui.available_size(); + ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| { + ui.add_space(6.0); + ui.vertical(|ui| { + ui.add_space(4.0); + // Show spendable amount. + let amount = amount_to_hr_string(acc.spendable_amount, true); + let amount_text = format!("{} {}", amount, GRIN); + ui.label(RichText::new(amount_text) + .size(18.0) + .color(Colors::white_or_black(true))); + ui.add_space(-2.0); + + // Show account name. + let default_acc_label = WalletConfig::DEFAULT_ACCOUNT_LABEL.to_string(); + let acc_label = if acc.label == default_acc_label { + t!("wallets.default_account") + } else { + acc.label.to_owned() + }; + let acc_name = format!("{} {}", FOLDER_USER, acc_label); + View::ellipsize_text(ui, acc_name, 15.0, Colors::text(false)); + + // Show account BIP32 derivation path. + let acc_path = format!("{} {}", PATH, acc.path); + ui.label(RichText::new(acc_path).size(15.0).color(Colors::gray())); + ui.add_space(3.0); + }); + }); + }); + }); + } +} \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/account/mod.rs b/src/gui/views/wallets/wallet/account/mod.rs index b0ce0956..ee755e72 100644 --- a/src/gui/views/wallets/wallet/account/mod.rs +++ b/src/gui/views/wallets/wallet/account/mod.rs @@ -13,4 +13,7 @@ // limitations under the License. mod content; +mod list; +mod create; + pub use content::*; \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index c832814f..d3ad2c41 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -30,6 +30,7 @@ use crate::node::Node; use crate::wallet::types::ConnectionMethod; use crate::wallet::{ExternalConnection, Wallet}; use crate::AppConfig; +use crate::gui::views::wallets::wallet::types::WalletContentContainer; /// Wallet content. pub struct WalletContent { @@ -38,19 +39,16 @@ pub struct WalletContent { /// Current tab content to show. pub current_tab: Box, + /// Account panel content. account_content: AccountContent, } impl ContentContainer for WalletContent { - fn modal_ids(&self) -> Vec<&'static str> { - vec![] - } + fn modal_ids(&self) -> Vec<&'static str> { vec![] } fn modal_ui(&mut self, _: &mut egui::Ui, _: &Modal, _: &dyn PlatformCallbacks) {} - fn on_back(&mut self, _: &dyn PlatformCallbacks) -> bool { - true - } + fn on_back(&mut self, _: &dyn PlatformCallbacks) -> bool { true } fn container_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { ui.ctx().request_repaint_after(Duration::from_millis(1000)); @@ -62,11 +60,11 @@ impl ContentContainer for WalletContent { let wallet_id = wallet.identifier(); let data = wallet.get_data(); let show_qr_scan = self.account_content.qr_scan_showing(); - let hide_tabs = Self::block_navigation_on_sync(wallet); + let block_nav = Self::block_navigation_on_sync(wallet); // Show wallet account panel not on settings tab when navigation is not blocked and QR code // scanner is not showing and wallet data is not empty. - let mut show_account = self.current_tab.get_type() != WalletTabType::Settings && !hide_tabs + let mut show_account = self.current_tab.get_type() != WalletTabType::Settings && !block_nav && !wallet.sync_error() && data.is_some(); if wallet.get_current_connection() == ConnectionMethod::Integrated && !Node::is_running() { show_account = false; @@ -89,7 +87,7 @@ impl ContentContainer for WalletContent { }) .show_animated_inside(ui, show_account, |ui| { let rect = ui.available_rect_before_wrap(); - self.account_content.ui(ui, cb); + self.account_content.ui(ui, wallet, cb); // Draw content divider lines. let r = { let mut r = rect.clone(); @@ -105,7 +103,7 @@ impl ContentContainer for WalletContent { }); // Show wallet tabs. - let show_tabs = !hide_tabs && !self.account_content.qr_scan_showing(); + let show_tabs = !block_nav && !self.qr_scan_showing() && !self.account_list_showing(); egui::TopBottomPanel::bottom("wallet_tabs") .frame(egui::Frame { inner_margin: Margin { @@ -148,7 +146,7 @@ impl ContentContainer for WalletContent { .show_inside(ui, |ui| { let rect = ui.available_rect_before_wrap(); let tab_type = self.current_tab.get_type(); - let show_sync = (tab_type != WalletTabType::Settings || hide_tabs) && + let show_sync = (tab_type != WalletTabType::Settings || block_nav) && sync_ui(ui, &self.wallet); if !show_sync { if tab_type != WalletTabType::Txs { @@ -175,11 +173,12 @@ impl ContentContainer for WalletContent { r.max.y += 4.0; r }; - // Draw cover when QR code scanner is active. - if show_qr_scan { - View::content_cover_ui(ui, rect, "wallet_tab", || { + // Draw cover when account list or QR code scanner is active. + if !show_sync && (self.account_list_showing() || self.qr_scan_showing()) { + View::content_cover_ui(ui, rect, "wallet_content_cover", || { cb.stop_camera(); self.account_content.close_qr_scan(); + self.account_content.close_account_list(); }); } // Draw content divider line. @@ -193,7 +192,7 @@ impl ContentContainer for WalletContent { impl WalletContent { /// Create new instance with optional data. pub fn new(wallet: Wallet, data: Option) -> Self { - let account_content = AccountContent::new(wallet.clone()); + let account_content = AccountContent::default(); let mut content = Self { wallet, current_tab: Box::new(WalletTransactions::default()), @@ -205,6 +204,16 @@ impl WalletContent { content } + /// Check account list is showing. + pub fn account_list_showing(&self) -> bool { + self.account_content.account_list_showing() + } + + /// Close QR code scanner. + pub fn close_account_list(&mut self) { + self.account_content.close_account_list(); + } + /// Check if QR code scanner is opened. pub fn qr_scan_showing(&self) -> bool { self.account_content.qr_scan_showing() @@ -368,4 +377,4 @@ fn sync_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) { ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text())); }); }); -} +} \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/messages/request.rs b/src/gui/views/wallets/wallet/messages/request.rs index 31f6b385..36aa08f2 100644 --- a/src/gui/views/wallets/wallet/messages/request.rs +++ b/src/gui/views/wallets/wallet/messages/request.rs @@ -106,7 +106,7 @@ impl MessageRequestModal { // Draw content on request loading. if self.request_loading { - self.loading_request_ui(ui, wallet, modal); + self.loading_request_ui(ui, modal); return; } @@ -208,7 +208,7 @@ impl MessageRequestModal { } /// Draw loading request content. - fn loading_request_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal) { + fn loading_request_ui(&mut self, ui: &mut egui::Ui, modal: &Modal) { ui.add_space(34.0); ui.vertical_centered(|ui| { View::big_loading_spinner(ui); diff --git a/src/gui/views/wallets/wallet/mod.rs b/src/gui/views/wallets/wallet/mod.rs index b9794e8a..90dc16d0 100644 --- a/src/gui/views/wallets/wallet/mod.rs +++ b/src/gui/views/wallets/wallet/mod.rs @@ -29,5 +29,4 @@ pub use transport::WalletTransport; mod content; pub use content::WalletContent; -mod modals; mod account; \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/modals/accounts.rs b/src/gui/views/wallets/wallet/modals/accounts.rs deleted file mode 100644 index ff0c0be5..00000000 --- a/src/gui/views/wallets/wallet/modals/accounts.rs +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2024 The Grim Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use egui::{Align, Id, Layout, RichText, ScrollArea, StrokeKind}; -use egui::scroll_area::ScrollBarVisibility; -use grin_core::core::amount_to_hr_string; - -use crate::gui::Colors; -use crate::gui::icons::{CHECK, CHECK_FAT, FOLDER_USER, PATH}; -use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, TextEdit, View}; -use crate::gui::views::wallets::wallet::types::GRIN; -use crate::wallet::types::WalletAccount; -use crate::wallet::{Wallet, WalletConfig}; - -/// Wallet accounts [`Modal`] content. -pub struct WalletAccountsModal { - /// List of wallet accounts. - accounts: Vec, - /// Flag to check if account is creating. - account_creating: bool, - /// Account label value. - account_label_edit: String, - /// Flag to check if error occurred during account creation. - account_creation_error: bool, -} - -impl Default for WalletAccountsModal { - fn default() -> Self { - Self { - accounts: vec![], - account_creating: false, - account_label_edit: "".to_string(), - account_creation_error: false, - } - } -} - -impl WalletAccountsModal { - /// Create new instance from wallet accounts. - pub fn new(accounts: Vec) -> Self { - Self { - accounts, - account_creating: false, - account_label_edit: "".to_string(), - account_creation_error: false, - } - } - - /// Draw [`Modal`] content. - pub fn ui(&mut self, - ui: &mut egui::Ui, - wallet: &Wallet, - modal: &Modal, - cb: &dyn PlatformCallbacks) { - if self.account_creating { - let on_create = |m: &mut WalletAccountsModal| { - if m.account_label_edit.is_empty() { - return; - } - let label = &m.account_label_edit; - match wallet.create_account(label) { - Ok(_) => { - let _ = wallet.set_active_account(label); - Modal::close(); - }, - Err(_) => m.account_creation_error = true - }; - }; - - ui.add_space(6.0); - ui.vertical_centered(|ui| { - ui.label(RichText::new(t!("wallets.new_account_desc")) - .size(17.0) - .color(Colors::gray())); - ui.add_space(8.0); - - // Draw account name edit. - let mut name_edit = TextEdit::new(Id::from(modal.id).with(wallet.get_config().id)); - name_edit.ui(ui, &mut self.account_label_edit, cb); - if name_edit.enter_pressed { - on_create(self); - } - - // Show error occurred during account creation. - if self.account_creation_error { - ui.add_space(12.0); - ui.label(RichText::new(t!("error")) - .size(17.0) - .color(Colors::red())); - } - ui.add_space(12.0); - }); - - // Setup spacing between buttons. - ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - - // Show modal buttons. - ui.columns(2, |columns| { - columns[0].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { - // Close modal. - Modal::close(); - }); - }); - columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("create"), Colors::white_or_black(false), || { - on_create(self); - }); - }); - }); - ui.add_space(6.0); - } else { - ui.add_space(3.0); - - // Show list of accounts. - let size = self.accounts.len(); - ScrollArea::vertical() - .id_salt("account_list_modal_scroll") - .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) - .max_height(266.0) - .auto_shrink([true; 2]) - .show_rows(ui, ACCOUNT_ITEM_HEIGHT, size, |ui, row_range| { - for index in row_range { - // Add space before the first item. - if index == 0 { - ui.add_space(4.0); - } - let acc = self.accounts.get(index).unwrap(); - account_item_ui(ui, wallet, acc, index, size); - if index == size - 1 { - ui.add_space(4.0); - } - } - }); - - ui.add_space(2.0); - View::horizontal_line(ui, Colors::item_stroke()); - ui.add_space(6.0); - - // Setup spacing between buttons. - ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - - // Show modal buttons. - ui.columns(2, |columns| { - columns[0].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { - Modal::close(); - }); - }); - columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("create"), Colors::white_or_black(false), || { - self.account_creating = true; - }); - }); - }); - ui.add_space(6.0); - } - } - -} - -const ACCOUNT_ITEM_HEIGHT: f32 = 75.0; - -/// Draw account item. -fn account_item_ui(ui: &mut egui::Ui, - wallet: &Wallet, - acc: &WalletAccount, - index: usize, - size: usize) { - // Setup layout size. - let mut rect = ui.available_rect_before_wrap(); - rect.set_height(ACCOUNT_ITEM_HEIGHT); - - // Draw round background. - let bg_rect = rect.clone(); - let item_rounding = View::item_rounding(index, size, false); - ui.painter().rect(bg_rect, - item_rounding, - Colors::fill(), - View::item_stroke(), - StrokeKind::Middle); - - ui.vertical(|ui| { - ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { - // Draw button to select account. - let is_current_account = wallet.get_config().account == acc.label; - if !is_current_account { - let button_rounding = View::item_rounding(index, size, true); - View::item_button(ui, button_rounding, CHECK, None, || { - let _ = wallet.set_active_account(&acc.label); - Modal::close(); - }); - } else { - ui.add_space(12.0); - ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green())); - } - - let layout_size = ui.available_size(); - ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| { - ui.add_space(6.0); - ui.vertical(|ui| { - ui.add_space(4.0); - // Show spendable amount. - let amount = amount_to_hr_string(acc.spendable_amount, true); - let amount_text = format!("{} {}", amount, GRIN); - ui.label(RichText::new(amount_text).size(18.0).color(Colors::white_or_black(true))); - ui.add_space(-2.0); - - // Show account name. - let default_acc_label = WalletConfig::DEFAULT_ACCOUNT_LABEL.to_string(); - let acc_label = if acc.label == default_acc_label { - t!("wallets.default_account") - } else { - acc.label.to_owned() - }; - let acc_name = format!("{} {}", FOLDER_USER, acc_label); - View::ellipsize_text(ui, acc_name, 15.0, Colors::text(false)); - - // Show account BIP32 derivation path. - let acc_path = format!("{} {}", PATH, acc.path); - ui.label(RichText::new(acc_path).size(15.0).color(Colors::gray())); - ui.add_space(3.0); - }); - }); - }); - }); -} \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/modals/mod.rs b/src/gui/views/wallets/wallet/modals/mod.rs deleted file mode 100644 index 61a805c4..00000000 --- a/src/gui/views/wallets/wallet/modals/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 The Grim Developers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod accounts; -pub use accounts::*; \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/settings/common.rs b/src/gui/views/wallets/wallet/settings/common.rs index 5ac1550a..757a2de9 100644 --- a/src/gui/views/wallets/wallet/settings/common.rs +++ b/src/gui/views/wallets/wallet/settings/common.rs @@ -19,6 +19,7 @@ use crate::gui::icons::{CLOCK_COUNTDOWN, PASSWORD, PENCIL}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, TextEdit, View}; use crate::gui::views::types::ModalPosition; +use crate::gui::views::wallets::wallet::types::WalletContentContainer; use crate::wallet::Wallet; /// Common wallet settings content. @@ -44,24 +45,35 @@ const PASS_EDIT_MODAL: &'static str = "wallet_pass_edit_modal"; /// Identifier for minimum confirmations [`Modal`]. const MIN_CONFIRMATIONS_EDIT_MODAL: &'static str = "wallet_min_conf_edit_modal"; -impl Default for CommonSettings { - fn default() -> Self { - Self { - name_edit: "".to_string(), - wrong_pass: false, - old_pass_edit: "".to_string(), - new_pass_edit: "".to_string(), - min_confirmations_edit: "".to_string(), +impl WalletContentContainer for CommonSettings { + fn modal_ids(&self) -> Vec<&'static str> { + vec![ + NAME_EDIT_MODAL, + PASS_EDIT_MODAL, + MIN_CONFIRMATIONS_EDIT_MODAL + ] + } + + fn modal_ui(&mut self, + ui: &mut egui::Ui, + wallet: &Wallet, + modal: &Modal, + cb: &dyn PlatformCallbacks) { + match modal.id { + NAME_EDIT_MODAL => { + self.name_modal_ui(ui, wallet, modal, cb); + } + PASS_EDIT_MODAL => { + self.pass_modal_ui(ui, wallet, modal, cb); + } + MIN_CONFIRMATIONS_EDIT_MODAL => { + self.min_conf_modal_ui(ui, wallet, modal, cb); + } + _ => {} } } -} - -impl CommonSettings { - /// Draw common wallet settings content. - pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { - // Show modal content for this container. - self.modal_content_ui(ui, wallet, cb); + fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, _: &dyn PlatformCallbacks) { ui.vertical_centered(|ui| { let config = wallet.get_config(); // Show wallet name. @@ -135,37 +147,21 @@ impl CommonSettings { ui.add_space(6.0); }); } +} - /// Draw [`Modal`] content for this ui container. - fn modal_content_ui(&mut self, - ui: &mut egui::Ui, - wallet: &Wallet, - cb: &dyn PlatformCallbacks) { - match Modal::opened() { - None => {} - Some(id) => { - match id { - NAME_EDIT_MODAL => { - Modal::ui(ui.ctx(), cb, |ui, modal, cb| { - self.name_modal_ui(ui, wallet, modal, cb); - }); - } - PASS_EDIT_MODAL => { - Modal::ui(ui.ctx(), cb, |ui, modal, cb| { - self.pass_modal_ui(ui, wallet, modal, cb); - }); - } - MIN_CONFIRMATIONS_EDIT_MODAL => { - Modal::ui(ui.ctx(), cb, |ui, modal, cb| { - self.min_conf_modal_ui(ui, wallet, modal, cb); - }); - } - _ => {} - } - } +impl Default for CommonSettings { + fn default() -> Self { + Self { + name_edit: "".to_string(), + wrong_pass: false, + old_pass_edit: "".to_string(), + new_pass_edit: "".to_string(), + min_confirmations_edit: "".to_string(), } } +} +impl CommonSettings { /// Draw wallet name [`Modal`] content. fn name_modal_ui(&mut self, ui: &mut egui::Ui, diff --git a/src/gui/views/wallets/wallet/settings/connection.rs b/src/gui/views/wallets/wallet/settings/connection.rs index b96f094d..c0e83b56 100644 --- a/src/gui/views/wallets/wallet/settings/connection.rs +++ b/src/gui/views/wallets/wallet/settings/connection.rs @@ -14,15 +14,15 @@ use egui::{Align, Layout, RichText, StrokeKind}; -use crate::gui::Colors; use crate::gui::icons::{CHECK, CHECK_CIRCLE, CHECK_FAT, DOTS_THREE_CIRCLE, GLOBE, GLOBE_SIMPLE, PLUS_CIRCLE, X_CIRCLE}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; -use crate::gui::views::network::ConnectionsContent; use crate::gui::views::network::modals::ExternalConnectionModal; +use crate::gui::views::network::ConnectionsContent; use crate::gui::views::types::{ContentContainer, ModalPosition}; -use crate::wallet::{ConnectionsConfig, ExternalConnection, Wallet}; +use crate::gui::views::{Modal, View}; +use crate::gui::Colors; use crate::wallet::types::ConnectionMethod; +use crate::wallet::{ConnectionsConfig, ExternalConnection}; /// Wallet connection settings content. pub struct ConnectionSettings { @@ -65,32 +65,7 @@ impl ContentContainer for ConnectionSettings { true } - fn container_ui(&mut self, _: &mut egui::Ui, _: &dyn PlatformCallbacks) {} -} - -impl ConnectionSettings { - /// Draw existing wallet connection setup content. - pub fn wallet_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { - self.method = wallet.get_current_connection(); - - // Draw setup content. - let changed = self.content_ui(ui, cb); - - if changed { - wallet.update_connection(&self.method); - // Reopen wallet if connection changed. - if !wallet.reopen_needed() { - wallet.set_reopen(true); - wallet.close(); - } - } - } - - /// Draw connection setup content, returning `true` if connection was changed. - pub fn content_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) -> bool { - self.ui(ui, cb); - let mut changed = false; - + fn container_ui(&mut self, ui: &mut egui::Ui, _: &dyn PlatformCallbacks) { ui.add_space(2.0); View::sub_title(ui, format!("{} {}", GLOBE, t!("wallets.conn_method"))); View::horizontal_line(ui, Colors::stroke()); @@ -104,7 +79,6 @@ impl ConnectionSettings { if !is_current_method { View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || { self.method = ConnectionMethod::Integrated; - changed = true; }); } else { ui.add_space(14.0); @@ -163,15 +137,15 @@ impl ConnectionSettings { }; Self::ext_conn_item_ui(ui, c, is_current, i, ext_size, || { self.method = ConnectionMethod::External(c.id, c.url.clone()); - changed = true; }); }); } } }); - changed } +} +impl ConnectionSettings { /// Draw external connection item content. fn ext_conn_item_ui(ui: &mut egui::Ui, conn: &ExternalConnection, diff --git a/src/gui/views/wallets/wallet/settings/content.rs b/src/gui/views/wallets/wallet/settings/content.rs index c28925b8..69bc31d1 100644 --- a/src/gui/views/wallets/wallet/settings/content.rs +++ b/src/gui/views/wallets/wallet/settings/content.rs @@ -13,8 +13,10 @@ // limitations under the License. use crate::gui::platform::PlatformCallbacks; +use crate::gui::views::types::ContentContainer; use crate::gui::views::wallets::{CommonSettings, ConnectionSettings, RecoverySettings}; use crate::gui::views::wallets::types::{WalletTab, WalletTabType}; +use crate::gui::views::wallets::wallet::types::WalletContentContainer; use crate::wallet::Wallet; /// Wallet settings tab content. @@ -48,8 +50,20 @@ impl WalletTab for WalletSettings { cb: &dyn PlatformCallbacks) { // Show common wallet setup. self.common_setup.ui(ui, wallet, cb); + // Show wallet connections setup. - self.conn_setup.wallet_ui(ui, wallet, cb); + self.conn_setup.method = wallet.get_current_connection(); + let method = self.conn_setup.method.clone(); + self.conn_setup.ui(ui, cb); + if method != self.conn_setup.method { + wallet.update_connection(&self.conn_setup.method); + // Reopen wallet if connection changed. + if !wallet.reopen_needed() { + wallet.set_reopen(true); + wallet.close(); + } + } + // Show wallet recovery setup. self.recovery_setup.ui(ui, wallet, cb); } diff --git a/src/gui/views/wallets/wallet/settings/recovery.rs b/src/gui/views/wallets/wallet/settings/recovery.rs index 8ff30d1b..00c7ed90 100644 --- a/src/gui/views/wallets/wallet/settings/recovery.rs +++ b/src/gui/views/wallets/wallet/settings/recovery.rs @@ -21,6 +21,7 @@ use crate::gui::icons::{EYE, LIFEBUOY, STETHOSCOPE, TRASH, WRENCH}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, TextEdit, View}; use crate::gui::views::types::ModalPosition; +use crate::gui::views::wallets::wallet::types::WalletContentContainer; use crate::node::Node; use crate::wallet::types::ConnectionMethod; use crate::wallet::Wallet; @@ -41,21 +42,30 @@ const RECOVERY_PHRASE_MODAL: &'static str = "recovery_phrase_modal"; /// Identifier to confirm wallet deletion [`Modal`]. const DELETE_CONFIRMATION_MODAL: &'static str = "delete_wallet_confirmation_modal"; -impl Default for RecoverySettings { - fn default() -> Self { - Self { - wrong_pass: false, - pass_edit: "".to_string(), - recovery_phrase: None, +impl WalletContentContainer for RecoverySettings { + fn modal_ids(&self) -> Vec<&'static str> { + vec![ + RECOVERY_PHRASE_MODAL, + DELETE_CONFIRMATION_MODAL + ] + } + + fn modal_ui(&mut self, + ui: &mut egui::Ui, + wallet: &Wallet, + modal: &Modal, cb: &dyn PlatformCallbacks) { + match modal.id { + RECOVERY_PHRASE_MODAL => { + self.recovery_phrase_modal_ui(ui, wallet, modal, cb); + } + DELETE_CONFIRMATION_MODAL => { + self.deletion_modal_ui(ui, wallet); + } + _ => {} } } -} - -impl RecoverySettings { - pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { - // Show modal content for this ui container. - self.modal_content_ui(ui, wallet, cb); + fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, _: &dyn PlatformCallbacks) { ui.add_space(10.0); View::horizontal_line(ui, Colors::stroke()); ui.add_space(6.0); @@ -97,8 +107,8 @@ impl RecoverySettings { format!("{} {}", LIFEBUOY, t!("wallets.recover")), Colors::green(), Colors::white_or_black(false), || { - wallet.delete_db(true); - }); + wallet.delete_db(true); + }); ui.add_space(6.0); ui.label(RichText::new(t!("wallets.restore_wallet_desc")) .size(16.0) @@ -129,40 +139,27 @@ impl RecoverySettings { format!("{} {}", TRASH, t!("wallets.delete")), Colors::red(), Colors::white_or_black(false), || { - Modal::new(DELETE_CONFIRMATION_MODAL) - .position(ModalPosition::Center) - .title(t!("confirmation")) - .show(); - }); + Modal::new(DELETE_CONFIRMATION_MODAL) + .position(ModalPosition::Center) + .title(t!("confirmation")) + .show(); + }); ui.add_space(8.0); }); } +} - /// Draw [`Modal`] content for this ui container. - fn modal_content_ui(&mut self, - ui: &mut egui::Ui, - wallet: &Wallet, - cb: &dyn PlatformCallbacks) { - match Modal::opened() { - None => {} - Some(id) => { - match id { - RECOVERY_PHRASE_MODAL => { - Modal::ui(ui.ctx(), cb, |ui, modal, cb| { - self.recovery_phrase_modal_ui(ui, wallet, modal, cb); - }); - } - DELETE_CONFIRMATION_MODAL => { - Modal::ui(ui.ctx(), cb, |ui, _, _| { - self.deletion_modal_ui(ui, wallet); - }); - } - _ => {} - } - } +impl Default for RecoverySettings { + fn default() -> Self { + Self { + wrong_pass: false, + pass_edit: "".to_string(), + recovery_phrase: None, } } +} +impl RecoverySettings { /// Show recovery phrase [`Modal`]. fn show_recovery_phrase_modal(&mut self) { // Setup modal values. diff --git a/src/gui/views/wallets/wallet/transport/send.rs b/src/gui/views/wallets/wallet/transport/send.rs index 0dc1707b..3b9ee40b 100644 --- a/src/gui/views/wallets/wallet/transport/send.rs +++ b/src/gui/views/wallets/wallet/transport/send.rs @@ -78,7 +78,7 @@ impl TransportSendModal { // Draw sending content, progress or an error. if self.sending { - self.progress_ui(ui, wallet); + self.progress_ui(ui); } else if self.error { self.error_ui(ui, wallet, modal); } else { @@ -320,7 +320,7 @@ impl TransportSendModal { } /// Draw sending progress content. - fn progress_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) { + fn progress_ui(&mut self, ui: &mut egui::Ui) { ui.add_space(16.0); ui.vertical_centered(|ui| { View::small_loading_spinner(ui); diff --git a/src/gui/views/wallets/wallet/txs/content.rs b/src/gui/views/wallets/wallet/txs/content.rs index dc1d1860..a9c83845 100644 --- a/src/gui/views/wallets/wallet/txs/content.rs +++ b/src/gui/views/wallets/wallet/txs/content.rs @@ -253,7 +253,7 @@ impl WalletTransactions { }); } CANCEL_TX_CONFIRMATION_MODAL => { - Modal::ui(ui.ctx(), cb, |ui, _, cb| { + Modal::ui(ui.ctx(), cb, |ui, _, _| { self.cancel_confirmation_modal(ui, wallet); }); } diff --git a/src/gui/views/wallets/wallet/types.rs b/src/gui/views/wallets/wallet/types.rs index f29d0a15..81948d4f 100644 --- a/src/gui/views/wallets/wallet/types.rs +++ b/src/gui/views/wallets/wallet/types.rs @@ -14,6 +14,7 @@ use crate::gui::icons::{FOLDER_LOCK, FOLDER_OPEN, SPINNER, WARNING_CIRCLE}; use crate::gui::platform::PlatformCallbacks; +use crate::gui::views::Modal; use crate::wallet::Wallet; /// GRIN coin symbol. @@ -21,6 +22,28 @@ pub const GRIN: &str = "ツ"; /// Hint for Slatepack message input. pub const SLATEPACK_MESSAGE_HINT: &'static str = "BEGINSLATEPACK.\n...\n...\n...\nENDSLATEPACK."; +/// Content container to simplify modals management and navigation. +pub trait WalletContentContainer { + /// List of allowed [`Modal`] identifiers. + fn modal_ids(&self) -> Vec<&'static str>; + /// Draw modal content. + fn modal_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks); + /// Draw container content. + fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks); + /// Draw content, to call by parent container. + fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { + // Draw modal content. + if let Some(id) = Modal::opened() { + if self.modal_ids().contains(&id) { + Modal::ui(ui.ctx(), cb, |ui, modal, cb| { + self.modal_ui(ui, wallet, modal, cb); + }); + } + } + self.container_ui(ui, wallet, cb); + } +} + /// Wallet tab content interface. pub trait WalletTab { fn get_type(&self) -> WalletTabType;