diff --git a/locales/en.yml b/locales/en.yml index 8a268c3..256e727 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -29,7 +29,13 @@ wallets: wrong_pass: Entered password is wrong locked: Locked unlocked: Unlocked - enable_node_required: 'Enable integrated node to use the wallet or change connection settings by selecting %{settings} at the bottom of the screen.' + enable_node: 'Enable integrated node to use the wallet or change connection settings by selecting %{settings} at the bottom of the screen.' + wallet_loading: 'Wallet is loading' + wallet_loading_err: 'An error occurred during loading the wallet, you can retry or change connection settings by selecting %{settings} at the bottom of the screen.' + wallet: Wallet + send: Send + receive: Receive + settings: Wallet settings network: self: Network node: Integrated node diff --git a/locales/ru.yml b/locales/ru.yml index 18a7d60..10775ba 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -29,7 +29,13 @@ wallets: wrong_pass: Введён неправильный пароль locked: Заблокирован unlocked: Разблокирован - enable_node_required: 'Чтобы использовать кошелёк, включите встроенный узел или измените настройки подключения, выбрав %{settings} внизу экрана.' + enable_node: 'Чтобы использовать кошелёк, включите встроенный узел или измените настройки подключения, выбрав %{settings} внизу экрана.' + wallet_loading: 'Кошелёк загружается' + wallet_loading_err: 'Во время загрузки кошелька произошла ошибка, вы можете повторить загрузку или изменить настройки подключения, выбрав %{settings} внизу экрана.' + wallet: Кошелёк + send: Отправить + receive: Получить + settings: Настройки кошелька network: self: Сеть node: Встроенный узел diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index 43a488a..85a0920 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -115,7 +115,7 @@ impl WalletsContent { let dual_panel = is_dual_panel_mode(ui, frame); let open_wallet_panel = dual_panel || show_wallet || create_wallet || empty_list; let wallet_panel_width = self.wallet_panel_width(ui, empty_list, dual_panel, show_wallet); - let available_width_zero = ui.available_width() == 0.0; + let content_width = ui.available_width(); // Show title panel. self.title_ui(ui, frame, dual_panel, create_wallet, show_wallet); @@ -135,12 +135,14 @@ impl WalletsContent { }) .show_animated_inside(ui, open_wallet_panel, |ui| { // Do not draw content on zero width. - if available_width_zero { + if content_width == 0.0 { return; } if create_wallet || !show_wallet { // Show wallet creation content. - self.creation_content.ui(ui, frame, cb, |wallet| { + self.creation_content.ui(ui, frame, cb, |mut wallet| { + // Load the wallet. + Wallets::load(&mut wallet); // Add created wallet to list. self.wallets.add(wallet); }); @@ -148,7 +150,17 @@ impl WalletsContent { for mut wallet in self.wallets.list.clone() { // Show content for selected wallet. if self.wallets.is_selected(wallet.config.id) { - self.wallet_content.ui(ui, frame, &mut wallet, cb); + // Setup wallet content width. + let mut rect = ui.available_rect_before_wrap(); + let mut width = ui.available_width(); + if dual_panel && self.show_list_at_dual_panel { + width = content_width - Root::SIDE_PANEL_WIDTH; + } + rect.set_width(width); + // Show wallet content. + ui.allocate_ui_at_rect(rect, |ui| { + self.wallet_content.ui(ui, frame, &mut wallet, cb); + }); break; } } @@ -158,7 +170,7 @@ impl WalletsContent { // Show non-empty list if wallet is not creating. if !empty_list && !create_wallet { // Flag to check if wallet list is hidden on the screen. - let list_hidden = available_width_zero || (dual_panel && !self.show_list_at_dual_panel) + let list_hidden = content_width == 0.0 || (dual_panel && !self.show_list_at_dual_panel) || (!dual_panel && show_wallet); // Show wallet list panel. @@ -194,7 +206,7 @@ impl WalletsContent { = (!show_wallet && !dual_panel) || (dual_panel && show_wallet); // Show list of wallets. - let scroll = self.list_ui(ui, dual_panel, show_creation_btn, cb); + let scroll = self.wallet_list_ui(ui, dual_panel, show_creation_btn, cb); if show_creation_btn { // Setup right margin for button. @@ -278,11 +290,11 @@ impl WalletsContent { } /// Draw list of wallets. Returns `true` if scroller is showing. - fn list_ui(&mut self, - ui: &mut egui::Ui, - dual_panel: bool, - show_creation_btn: bool, - cb: &dyn PlatformCallbacks) -> bool { + fn wallet_list_ui(&mut self, + ui: &mut egui::Ui, + dual_panel: bool, + show_creation_btn: bool, + cb: &dyn PlatformCallbacks) -> bool { let mut scroller_showing = false; ui.scope(|ui| { // Setup scroll bar color. diff --git a/src/gui/views/wallets/mod.rs b/src/gui/views/wallets/mod.rs index 72adf5f..8af920c 100644 --- a/src/gui/views/wallets/mod.rs +++ b/src/gui/views/wallets/mod.rs @@ -19,4 +19,4 @@ mod content; pub use content::*; mod wallet; -use wallet::WalletContent; \ No newline at end of file +use wallet::*; \ 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 d7f13fe..6228b02 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -12,21 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::Margin; -use crate::gui::Colors; +use egui::{Margin, RichText}; +use crate::gui::Colors; +use crate::gui::icons::{DOWNLOAD, UPLOAD, WALLET, WRENCH}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::View; -use crate::wallet::Wallet; +use crate::gui::views::wallets::{WalletInfo, WalletReceive, WalletSend, WalletSettings}; +use crate::gui::views::wallets::types::{WalletTab, WalletTabType}; +use crate::node::Node; +use crate::wallet::{Wallet, Wallets}; /// Selected and opened wallet content. pub struct WalletContent { - + /// Current tab content to show. + current_tab: Box, } impl Default for WalletContent { fn default() -> Self { - Self {} + Self { current_tab: Box::new(WalletInfo::default()) } } } @@ -36,7 +41,23 @@ impl WalletContent { frame: &mut eframe::Frame, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { - // Show wallet content. + // Show bottom tabs. + egui::TopBottomPanel::bottom("wallet_tabs") + .frame(egui::Frame { + fill: Colors::FILL, + inner_margin: Margin { + left: View::far_left_inset_margin(ui) + 4.0, + right: View::get_right_inset() + 4.0, + top: 4.0, + bottom: View::get_bottom_inset() + 4.0, + }, + ..Default::default() + }) + .show_inside(ui, |ui| { + self.tabs_ui(ui); + }); + + // Show tab content. egui::CentralPanel::default() .frame(egui::Frame { stroke: View::DEFAULT_STROKE, @@ -50,10 +71,80 @@ impl WalletContent { ..Default::default() }) .show_inside(ui, |ui| { - ui.label(&wallet.config.name); - //TODO: wallet content }); + + // Refresh content after delay for loaded wallet. + if wallet.is_loaded() { + ui.ctx().request_repaint_after(Wallets::INFO_UPDATE_DELAY); + } else { + ui.ctx().request_repaint(); + } } + /// Draw tab buttons in the bottom of the screen. + fn tabs_ui(&mut self, ui: &mut egui::Ui) { + ui.scope(|ui| { + // Setup spacing between tabs. + ui.style_mut().spacing.item_spacing = egui::vec2(4.0, 0.0); + // Setup vertical padding inside tab button. + ui.style_mut().spacing.button_padding = egui::vec2(0.0, 8.0); + // Draw tab buttons. + let current_type = self.current_tab.get_type(); + ui.columns(4, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::tab_button(ui, WALLET, current_type == WalletTabType::Info, || { + self.current_tab = Box::new(WalletInfo::default()); + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::tab_button(ui, DOWNLOAD, current_type == WalletTabType::Receive, || { + self.current_tab = Box::new(WalletReceive::default()); + }); + }); + columns[2].vertical_centered_justified(|ui| { + View::tab_button(ui, UPLOAD, current_type == WalletTabType::Send, || { + self.current_tab = Box::new(WalletSend::default()); + }); + }); + columns[3].vertical_centered_justified(|ui| { + View::tab_button(ui, WRENCH, current_type == WalletTabType::Settings, || { + self.current_tab = Box::new(WalletSettings::default()); + }); + }); + }); + }); + } + + /// Content to draw when wallet is loading. + fn loading_ui(ui: &mut egui::Ui, wallet: &Wallet) { + if wallet.config.external_node_url.is_none() && !Node::is_running() { + + } else { + if let Some(error) = &wallet.loading_error { + // View::center_content(ui, 162.0, |ui| { + // let text = t!("wallets.enable_node", "settings" => WRENCH); + // View::big_loading_spinner(ui); + // ui.add_space(18.0); + // let text = if wallet.loading_progress == 0 { + // t!("wallet_loading") + // } else { + // format!("{}: {}%", t!("wallet_loading"), wallet.loading_progress) + // }; + // ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT)); + // }); + } else if !wallet.is_loaded() { + View::center_content(ui, 162.0, |ui| { + View::big_loading_spinner(ui); + ui.add_space(18.0); + let text = if wallet.loading_progress == 0 { + t!("wallets.wallet_loading") + } else { + format!("{}: {}%", t!("wallets.wallet_loading"), wallet.loading_progress) + }; + 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/info.rs b/src/gui/views/wallets/wallet/info.rs new file mode 100644 index 0000000..1048fc4 --- /dev/null +++ b/src/gui/views/wallets/wallet/info.rs @@ -0,0 +1,30 @@ +// Copyright 2023 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::platform::PlatformCallbacks; +use crate::gui::views::wallets::types::WalletTab; +use crate::gui::views::wallets::wallet::types::WalletTabType; + +/// Wallet info tab content. +#[derive(Default)] +pub struct WalletInfo; + +impl WalletTab for WalletInfo { + fn get_type(&self) -> WalletTabType { + WalletTabType::Info + } + + fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { + } +} \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/mod.rs b/src/gui/views/wallets/wallet/mod.rs index 23bedab..3251c69 100644 --- a/src/gui/views/wallets/wallet/mod.rs +++ b/src/gui/views/wallets/wallet/mod.rs @@ -12,10 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod content; -mod txs; -mod send; -mod settings; -mod receive; +pub mod types; +mod info; +pub use info::WalletInfo; + +mod receive; +pub use receive::WalletReceive; + +mod send; +pub use send::WalletSend; + +mod settings; +pub use settings::WalletSettings; + +mod content; pub use content::WalletContent; \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/receive.rs b/src/gui/views/wallets/wallet/receive.rs index aa04af4..06b79a1 100644 --- a/src/gui/views/wallets/wallet/receive.rs +++ b/src/gui/views/wallets/wallet/receive.rs @@ -10,4 +10,20 @@ // 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. \ No newline at end of file +// limitations under the License. + +use crate::gui::platform::PlatformCallbacks; +use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType}; + +/// Receive funds tab content. +#[derive(Default)] +pub struct WalletReceive; + +impl WalletTab for WalletReceive { + fn get_type(&self) -> WalletTabType { + WalletTabType::Receive + } + + fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { + } +} \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/send.rs b/src/gui/views/wallets/wallet/send.rs index 2035f55..7b899d3 100644 --- a/src/gui/views/wallets/wallet/send.rs +++ b/src/gui/views/wallets/wallet/send.rs @@ -11,3 +11,19 @@ // 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::platform::PlatformCallbacks; +use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType}; + +/// Send funds tab content. +#[derive(Default)] +pub struct WalletSend; + +impl WalletTab for WalletSend { + fn get_type(&self) -> WalletTabType { + WalletTabType::Send + } + + fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { + } +} \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/settings.rs b/src/gui/views/wallets/wallet/settings.rs index aa04af4..f9d63db 100644 --- a/src/gui/views/wallets/wallet/settings.rs +++ b/src/gui/views/wallets/wallet/settings.rs @@ -10,4 +10,20 @@ // 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. \ No newline at end of file +// limitations under the License. + +use crate::gui::platform::PlatformCallbacks; +use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType}; + +/// Wallet settings tab content. +#[derive(Default)] +pub struct WalletSettings; + +impl WalletTab for WalletSettings { + fn get_type(&self) -> WalletTabType { + WalletTabType::Settings + } + + fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { + } +} \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/txs.rs b/src/gui/views/wallets/wallet/types.rs similarity index 61% rename from src/gui/views/wallets/wallet/txs.rs rename to src/gui/views/wallets/wallet/types.rs index 2035f55..f85c175 100644 --- a/src/gui/views/wallets/wallet/txs.rs +++ b/src/gui/views/wallets/wallet/types.rs @@ -11,3 +11,20 @@ // 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::platform::PlatformCallbacks; + +/// Wallet tab content interface. +pub trait WalletTab { + fn get_type(&self) -> WalletTabType; + fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks); +} + +/// Type of [`WalletTab`] content. +#[derive(PartialEq)] +pub enum WalletTabType { + Info, + Receive, + Send, + Settings +} \ No newline at end of file diff --git a/src/wallet/connections/config.rs b/src/wallet/connections/config.rs index 0f421e1..fbeb399 100644 --- a/src/wallet/connections/config.rs +++ b/src/wallet/connections/config.rs @@ -18,13 +18,13 @@ use lazy_static::lazy_static; use serde_derive::{Deserialize, Serialize}; use crate::Settings; -use crate::wallet::{BASE_DIR_NAME, ExternalConnection}; +use crate::wallet::ExternalConnection; lazy_static! { /// Static connections state to be accessible globally. static ref CONNECTIONS_STATE: Arc> = Arc::new( RwLock::new( - Settings::init_config(Settings::get_config_path(CONFIG_FILE_NAME, Some(BASE_DIR_NAME))) + Settings::init_config(Settings::get_config_path(CONFIG_FILE_NAME, None)) ) ); } diff --git a/src/wallet/types.rs b/src/wallet/types.rs index 26914e3..b1adc2f 100644 --- a/src/wallet/types.rs +++ b/src/wallet/types.rs @@ -12,6 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::Arc; + +use grin_keychain::ExtKeychain; +use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient}; +use grin_wallet_libwallet::WalletInst; +use parking_lot::Mutex; + /// Mnemonic phrase setup mode. #[derive(PartialEq, Clone)] pub enum PhraseMode { @@ -55,4 +62,18 @@ impl PhraseSize { PhraseSize::Words24 => 32 } } -} \ No newline at end of file +} + +/// Wallet instance type. +pub type WalletInstance = Arc< + Mutex< + Box< + dyn WalletInst< + 'static, + DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, + HTTPNodeClient, + ExtKeychain, + >, + >, + >, +>; \ No newline at end of file diff --git a/src/wallet/wallets.rs b/src/wallet/wallets.rs index 2c4eee1..f5bb102 100644 --- a/src/wallet/wallets.rs +++ b/src/wallet/wallets.rs @@ -16,6 +16,7 @@ use std::{cmp, thread}; use std::path::PathBuf; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::Duration; use grin_core::global; use grin_core::global::ChainTypes; @@ -33,6 +34,7 @@ use crate::node::NodeConfig; use crate::wallet::{ConnectionsConfig, WalletConfig}; use crate::wallet::selection::lock_tx_context; use crate::wallet::tx::{add_inputs_to_slate, new_tx_slate}; +use crate::wallet::types::WalletInstance; use crate::wallet::updater::{cancel_tx, refresh_output_state, retrieve_txs}; /// [`Wallet`] list wrapper. @@ -53,6 +55,12 @@ impl Default for Wallets { } impl Wallets { + /// Delay in seconds to update outputs after wallet opening. + pub const OUTPUTS_UPDATE_DELAY: Duration = Duration::from_millis(30 * 1000); + + /// Delay in seconds to update wallet info. + pub const INFO_UPDATE_DELAY: Duration = Duration::from_millis(10 * 1000); + /// Initialize wallets from base directory for provided [`ChainType`]. fn init(chain_type: ChainTypes) -> Vec { let mut wallets = Vec::new(); @@ -121,21 +129,30 @@ impl Wallets { // Get pmmr range output indexes. match wallet.pmmr_range() { Ok((mut lowest_index, highest_index)) => { - println!("pmmr_range {} {}", lowest_index, highest_index); + println!("pmmr_range: {} {}", lowest_index, highest_index); let mut from_index = lowest_index; loop { + // Stop loop if wallet is not open. + if !wallet.is_open() { + break; + } + // Scan outputs for last retrieved index. - println!("scan_outputs {} {}", from_index, highest_index); + println!("scan_outputs from {} to {}", from_index, highest_index); match wallet.scan_outputs(from_index, highest_index) { Ok(last_index) => { - println!("last_index {}", last_index); + println!("retrieved index: {}", last_index); if lowest_index == 0 { lowest_index = last_index; } + + // Finish scanning or start new scan from last retrieved index. if last_index == highest_index { + println!("scan finished"); wallet.loading_progress = 100; - break; + wallet.is_loaded.store(true, Ordering::Relaxed); } else { + println!("continue scan"); from_index = last_index; } @@ -146,17 +163,28 @@ impl Wallets { (progress / range) as u8 * 100, 99 ); - println!("progress {}", wallet.loading_progress); + println!("progress: {}", wallet.loading_progress); } Err(e) => { - wallet.loading_error = Some(e); - break; + if !wallet.is_loaded() { + wallet.loading_error = Some(e); + break; + } } } + + // Stop loop if wallet is not open. + if !wallet.is_open() { + break; + } + + // Scan outputs at next cycle again. + println!("finished scanning, waiting"); + thread::sleep(Self::OUTPUTS_UPDATE_DELAY); } - wallet.is_loaded.store(true, Ordering::Relaxed); } Err(e) => { + wallet.loading_progress = 0; wallet.loading_error = Some(e); } } @@ -179,25 +207,11 @@ pub struct Wallet { /// Flag to check if wallet is loaded and ready to use. is_loaded: Arc, /// Error on wallet loading. - loading_error: Option, + pub loading_error: Option, /// Loading progress in percents - loading_progress: u8, + pub loading_progress: u8, } -/// Wallet instance type. -type WalletInstance = Arc< - Mutex< - Box< - dyn WalletInst< - 'static, - DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, - HTTPNodeClient, - ExtKeychain, - >, - >, - >, ->; - impl Wallet { /// Create wallet from provided instance and config. fn new(instance: WalletInstance, config: WalletConfig) -> Self { @@ -326,6 +340,11 @@ impl Wallet { Ok(()) } + /// Check if wallet is loaded and ready to use. + pub fn is_loaded(&self) -> bool { + self.is_loaded.load(Ordering::Relaxed) + } + /// Scan wallet outputs to check/repair the wallet. fn scan_outputs( &self,