diff --git a/locales/en.yml b/locales/en.yml index 173bc0b..fab5930 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -43,6 +43,7 @@ wallets: send: Send receive: Receive settings: Wallet settings + change_server_confirmation: To apply change of connection settings, it is necessary to reopen the wallet. Reopen it now? network: self: Network type: 'Network type:' diff --git a/locales/ru.yml b/locales/ru.yml index 28095c7..261ffdc 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -43,6 +43,7 @@ wallets: send: Отправить receive: Получить settings: Настройки кошелька + change_server_confirmation: Чтобы применить изменение настроек соединения, необходимо переоткрыть кошелёк. Переоткрыть его сейчас? network: self: Сеть type: 'Тип сети:' diff --git a/src/gui/views/types.rs b/src/gui/views/types.rs index c60303e..c4be580 100644 --- a/src/gui/views/types.rs +++ b/src/gui/views/types.rs @@ -17,7 +17,7 @@ use crate::gui::views::Modal; /// Title content type, can be single title or with animated subtitle. pub enum TitleType { - /// Single text with possibility to align text at left side at default panel size width. + /// Single text with possibility to align text at left side for default panel size width. Single(String, bool), /// With animated subtitle text. WithSubTitle(String, String, bool) diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index 22f39ba..726ba29 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -333,6 +333,13 @@ impl WalletsContent { ui.allocate_ui(rect.size(), |ui| { let list = self.wallets.list().clone(); for wallet in &list { + // Check if wallet reopen is needed. + if !wallet.is_open() && wallet.reopen_needed() { + wallet.set_reopen(false); + self.wallets.select(Some(wallet.config.id)); + self.show_open_wallet_modal(cb); + } + // Draw wallet list item. self.wallet_item_ui(ui, wallet, cb); ui.add_space(5.0); @@ -430,7 +437,7 @@ impl WalletsContent { if wallet.is_closing() { format!("{} {}", SPINNER, t!("wallets.wallet_closing")) } else if wallet.get_data().is_none() { - if wallet.loading_error() { + if wallet.load_error() { format!("{} {}", WARNING_CIRCLE, t!("loading_error")) } else { format!("{} {}", SPINNER, t!("loading")) diff --git a/src/gui/views/wallets/creation/creation.rs b/src/gui/views/wallets/creation/creation.rs index 39437f6..2838107 100644 --- a/src/gui/views/wallets/creation/creation.rs +++ b/src/gui/views/wallets/creation/creation.rs @@ -24,8 +24,8 @@ use crate::gui::views::types::ModalPosition; use crate::gui::views::wallets::creation::MnemonicSetup; use crate::gui::views::wallets::creation::types::Step; use crate::gui::views::wallets::setup::ConnectionSetup; +use crate::wallet::{ExternalConnection, Wallet}; use crate::wallet::types::PhraseMode; -use crate::wallet::Wallet; /// Wallet creation content. pub struct WalletCreation { @@ -304,14 +304,19 @@ impl WalletCreation { } } } - Step::ConfirmMnemonic => Some(Step::SetupConnection), + Step::ConfirmMnemonic => { + // Check external connections availability on connection setup. + ExternalConnection::start_ext_conn_availability_check(); + Some(Step::SetupConnection) + }, Step::SetupConnection => { // Create wallet at last step. let name = self.name_edit.clone(); let pass = self.pass_edit.clone(); let phrase = self.mnemonic_setup.mnemonic.get_phrase(); let conn_method = &self.network_setup.method; - let wallet = Wallet::create(name, pass.clone(), phrase, conn_method).unwrap(); + let mut wallet + = Wallet::create(name, pass.clone(), phrase, conn_method).unwrap(); // Open created wallet. wallet.open(pass).unwrap(); // Pass created wallet to callback. diff --git a/src/gui/views/wallets/creation/mnemonic.rs b/src/gui/views/wallets/creation/mnemonic.rs index a7758d4..121d735 100644 --- a/src/gui/views/wallets/creation/mnemonic.rs +++ b/src/gui/views/wallets/creation/mnemonic.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::{Id, RichText, ScrollArea, TextStyle, Widget}; +use egui::{Id, RichText, TextStyle, Widget}; use crate::gui::Colors; use crate::gui::icons::PENCIL; diff --git a/src/gui/views/wallets/setup/connection.rs b/src/gui/views/wallets/setup/connection.rs index d78dadc..9aa30b4 100644 --- a/src/gui/views/wallets/setup/connection.rs +++ b/src/gui/views/wallets/setup/connection.rs @@ -12,18 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::{Id, RichText, ScrollArea, TextStyle, Widget}; +use egui::{Align, Id, Layout, RichText, Rounding, TextStyle, Widget}; use url::Url; use crate::gui::Colors; -use crate::gui::icons::{GLOBE, GLOBE_SIMPLE}; +use crate::gui::icons::{CHECK, CHECK_CIRCLE, CHECK_FAT, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE, GLOBE_SIMPLE, POWER, X_CIRCLE}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, View}; use crate::gui::views::types::{ModalContainer, ModalPosition}; +use crate::node::{Node, NodeConfig}; use crate::wallet::{ConnectionsConfig, ExternalConnection, Wallet}; use crate::wallet::types::ConnectionMethod; -/// Wallet node connection method setup content. +/// Wallet connection setup content. pub struct ConnectionSetup { /// Selected connection method. pub method: ConnectionMethod, @@ -37,13 +38,19 @@ pub struct ConnectionSetup { /// Flag to show URL format error. ext_node_url_error: bool, + /// Flag to show closing progress at confirmation [`Modal`] to reopen the [`Wallet`]. + show_closing_progress: bool, + /// [`Modal`] identifiers allowed at this ui container. modal_ids: Vec<&'static str> } -/// External connection [`Modal`] identifier. +/// Identifier for [`Modal`] to add external connection. pub const ADD_EXT_CONNECTION_MODAL: &'static str = "add_ext_connection_modal"; +/// Identifier for [`Modal`] to confirm wallet reopening after connection change. +pub const REOPEN_WALLET_CONFIRMATION_MODAL: &'static str = "change_conn_reopen_wallet_modal"; + impl Default for ConnectionSetup { fn default() -> Self { Self { @@ -52,6 +59,7 @@ impl Default for ConnectionSetup { ext_node_url_edit: "".to_string(), ext_node_secret_edit: "".to_string(), ext_node_url_error: false, + show_closing_progress: false, modal_ids: vec![ ADD_EXT_CONNECTION_MODAL ] @@ -91,6 +99,13 @@ impl ConnectionSetup { frame: &mut eframe::Frame, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { + // Show modal content to reopen the wallet. + if Modal::opened() == Some(REOPEN_WALLET_CONFIRMATION_MODAL) { + Modal::ui(ui.ctx(), |ui, modal| { + self.reopen_modal_content(ui, wallet, modal, cb); + }); + } + // Setup connection value from provided wallet. match wallet.config.ext_conn_id { None => self.method = ConnectionMethod::Integrated, @@ -101,19 +116,26 @@ impl ConnectionSetup { self.ui(ui, frame, cb); // Setup wallet connection value after change. - match self.method { + let changed = match self.method { ConnectionMethod::Integrated => { - if wallet.config.ext_conn_id.is_some() { + let changed = wallet.config.ext_conn_id.is_some(); + if changed { wallet.config.ext_conn_id = None; - wallet.config.save(); } + changed } ConnectionMethod::External(id) => { - if wallet.config.ext_conn_id != Some(id) { + let changed = wallet.config.ext_conn_id != Some(id); + if changed { wallet.config.ext_conn_id = Some(id); - wallet.config.save(); } + changed } + }; + + if changed { + wallet.config.save(); + self.show_reopen_confirmation_modal(); } } @@ -133,33 +155,155 @@ impl ConnectionSetup { ui.vertical_centered(|ui| { // Show integrated node selection. ui.add_space(6.0); - View::radio_value(ui, - &mut self.method, - ConnectionMethod::Integrated, - t!("network.node")); + self.integrated_node_item_ui(ui); - ui.add_space(10.0); - ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::GRAY)); - ui.add_space(6.0); + let ext_conn_list = ConnectionsConfig::ext_conn_list(); + if !ext_conn_list.is_empty() { + ui.add_space(6.0); + ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::GRAY)); + ui.add_space(6.0); - // Show button to add new external node connection. - let add_node_text = format!("{} {}", GLOBE_SIMPLE, t!("wallets.add_node")); - View::button(ui, add_node_text, Colors::GOLD, || { - self.show_add_ext_conn_modal(cb); - }); - ui.add_space(12.0); - - // Show external nodes URLs selection. - for conn in ConnectionsConfig::ext_conn_list() { - View::radio_value(ui, - &mut self.method, - ConnectionMethod::External(conn.id), - conn.url); + // Show button to add new external node connection. + let add_node_text = format!("{} {}", GLOBE_SIMPLE, t!("wallets.add_node")); + View::button(ui, add_node_text, Colors::GOLD, || { + self.show_add_ext_conn_modal(cb); + }); ui.add_space(12.0); + + // Show external connections. + for (index, conn) in ext_conn_list.iter().enumerate() { + ui.horizontal_wrapped(|ui| { + // Draw connection list item. + self.ext_conn_item_ui(ui, conn, index, ext_conn_list.len()); + }); + } } }); } + /// Draw integrated node connection item content. + fn integrated_node_item_ui(&mut self, ui: &mut egui::Ui) { + // Draw round background. + let mut rect = ui.available_rect_before_wrap(); + rect.set_height(78.0); + let rounding = View::item_rounding(0, 1, false); + ui.painter().rect(rect, rounding, Colors::FILL, View::ITEM_STROKE); + + ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { + // Setup padding for item buttons. + ui.style_mut().spacing.button_padding = egui::vec2(14.0, 0.0); + // Setup rounding for item buttons. + ui.style_mut().visuals.widgets.inactive.rounding = Rounding::same(8.0); + ui.style_mut().visuals.widgets.hovered.rounding = Rounding::same(8.0); + ui.style_mut().visuals.widgets.active.rounding = Rounding::same(8.0); + + // Draw button to select integrated node if it was not selected. + let is_current_method = self.method == ConnectionMethod::Integrated; + if !is_current_method { + View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || { + self.method = ConnectionMethod::Integrated; + }); + } + + if !Node::is_running() { + // Draw button to start integrated node. + let rounding = if is_current_method { + View::item_rounding(0, 1, true) + } else { + Rounding::none() + }; + View::item_button(ui, rounding, POWER, Some(Colors::GREEN), || { + Node::start(); + }); + } + + 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(3.0); + ui.label(RichText::new(t!("network.node")) + .size(18.0) + .color(Colors::TITLE)); + + // Setup node API address text. + let api_address = NodeConfig::get_api_address(); + let address_text = format!("{} http://{}", COMPUTER_TOWER, api_address); + ui.label(RichText::new(address_text).size(15.0).color(Colors::TEXT)); + ui.add_space(1.0); + + // Setup node status text. + let status_icon = if !Node::is_running() { + X_CIRCLE + } else if Node::not_syncing() { + CHECK_CIRCLE + } else { + DOTS_THREE_CIRCLE + }; + let status_text = format!("{} {}", status_icon, Node::get_sync_status_text()); + ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY)); + }) + }); + }); + } + + /// Draw external connection item content. + fn ext_conn_item_ui(&mut self, + ui: &mut egui::Ui, + conn: &ExternalConnection, + index: usize, + len: usize) { + // Setup layout size. + let mut rect = ui.available_rect_before_wrap(); + rect.set_height(52.0); + + // Draw round background. + let bg_rect = rect.clone(); + let item_rounding = View::item_rounding(index, len, false); + ui.painter().rect(bg_rect, item_rounding, Colors::FILL, View::ITEM_STROKE); + + ui.vertical(|ui| { + ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { + // Draw button to select connection. + let is_current_method = self.method == ConnectionMethod::External(conn.id); + if !is_current_method { + let button_rounding = View::item_rounding(index, len, true); + View::item_button(ui, button_rounding, CHECK, None, || { + self.method = ConnectionMethod::External(conn.id); + }); + } else { + ui.add_space(12.0); + ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::TITLE)); + } + + 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| { + // Draw connections URL. + ui.add_space(4.0); + let conn_text = format!("{} {}", COMPUTER_TOWER, conn.url); + View::ellipsize_text(ui, conn_text, 15.0, Colors::TITLE); + ui.add_space(1.0); + + // Setup connection status text. + let status_text = if let Some(available) = conn.available { + if available { + format!("{} {}", CHECK_CIRCLE, t!("network.available")) + } else { + format!("{} {}", X_CIRCLE, t!("network.not_available")) + } + } else { + format!("{} {}", DOTS_THREE_CIRCLE, t!("network.availability_check")) + }; + ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY)); + ui.add_space(3.0); + }); + }); + }); + }); + } + /// Show external connection adding [`Modal`]. fn show_add_ext_conn_modal(&mut self, cb: &dyn PlatformCallbacks) { // Setup values for Modal. @@ -257,6 +401,7 @@ impl ConnectionSetup { Some(self.ext_node_secret_edit.to_owned()) }; let ext_conn = ExternalConnection::new(url.clone(), secret); + ext_conn.check_conn_availability(); ConnectionsConfig::add_ext_conn(ext_conn.clone()); // Set added connection as current. @@ -279,4 +424,67 @@ impl ConnectionSetup { ui.add_space(6.0); }); } + + /// Show confirmation modal to reopen the [`Wallet`] after connection change. + fn show_reopen_confirmation_modal(&mut self,) { + self.show_closing_progress = false; + // Show modal. + Modal::new(REOPEN_WALLET_CONFIRMATION_MODAL) + .title(t!("modal.confirmation")) + .show(); + } + + /// Draw confirmation modal content to reopen the [`Wallet`]. + fn reopen_modal_content(&mut self, + ui: &mut egui::Ui, + wallet: &Wallet, + modal: &Modal, + cb: &dyn PlatformCallbacks) { + if self.show_closing_progress { + if !wallet.is_closing() { + modal.close(); + return; + } + ui.add_space(16.0); + ui.vertical_centered(|ui| { + View::small_loading_spinner(ui); + ui.add_space(12.0); + ui.label(RichText::new(t!("wallets.wallet_closing")) + .size(17.0) + .color(Colors::TEXT)); + }); + ui.add_space(10.0); + } else { + ui.add_space(8.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("wallets.change_server_confirmation")) + .size(17.0) + .color(Colors::TEXT)); + }); + ui.add_space(10.0); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::WHITE, || { + modal.close(); + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::button(ui, "OK".to_owned(), Colors::WHITE, || { + modal.disable_closing(); + self.show_closing_progress = true; + wallet.set_reopen(true); + wallet.close(); + }); + }); + }); + ui.add_space(6.0); + }); + } + } } \ No newline at end of file diff --git a/src/gui/views/wallets/setup/main.rs b/src/gui/views/wallets/setup/main.rs new file mode 100644 index 0000000..68632ff --- /dev/null +++ b/src/gui/views/wallets/setup/main.rs @@ -0,0 +1,18 @@ +// 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. + +/// Main wallet setup content. +pub struct MainSetup { + +} \ No newline at end of file diff --git a/src/gui/views/wallets/setup/mod.rs b/src/gui/views/wallets/setup/mod.rs index 3e49db6..a89544c 100644 --- a/src/gui/views/wallets/setup/mod.rs +++ b/src/gui/views/wallets/setup/mod.rs @@ -13,4 +13,10 @@ // limitations under the License. mod connection; -pub use connection::ConnectionSetup; \ No newline at end of file +pub use connection::ConnectionSetup; + +mod main; +pub use main::MainSetup; + +mod recovery; +pub use recovery::RecoverySetup; \ No newline at end of file diff --git a/src/gui/views/wallets/setup/recovery.rs b/src/gui/views/wallets/setup/recovery.rs new file mode 100644 index 0000000..c4ea21e --- /dev/null +++ b/src/gui/views/wallets/setup/recovery.rs @@ -0,0 +1,18 @@ +// 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. + +/// Wallet recovery setup content. +pub struct RecoverySetup { + +} \ 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 def7da8..35a2406 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -46,6 +46,8 @@ impl WalletContent { wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { // Show wallet tabs panel. + let not_show_tabs = + wallet.is_closing() || (wallet.get_data().is_none() && !wallet.load_error()); egui::TopBottomPanel::bottom("wallet_tabs") .frame(egui::Frame { fill: Colors::FILL, @@ -57,7 +59,7 @@ impl WalletContent { }, ..Default::default() }) - .show_inside(ui, |ui| { + .show_animated_inside(ui, !not_show_tabs, |ui| { ui.vertical_centered(|ui| { // Setup tabs width. let available_width = ui.available_width(); @@ -172,7 +174,7 @@ impl WalletContent { } }); return true - } else if wallet.loading_error() + } else if wallet.load_error() && Node::get_sync_status() == Some(SyncStatus::NoSync) { Self::loading_error_ui(ui, wallet); return true; @@ -181,7 +183,7 @@ impl WalletContent { return true; } } else if wallet.get_data().is_none() { - if wallet.loading_error() { + if wallet.load_error() { Self::loading_error_ui(ui, wallet); } else { Self::loading_progress_ui(ui, wallet); @@ -199,7 +201,7 @@ impl WalletContent { ui.add_space(8.0); let retry_text = format!("{} {}", REPEAT, t!("retry")); View::button(ui, retry_text, Colors::GOLD, || { - wallet.set_loading_error(false); + wallet.set_load_error(false); }); }); } @@ -211,7 +213,7 @@ impl WalletContent { ui.add_space(18.0); // Setup loading progress text. let text = { - let info_progress = wallet.info_loading_progress(); + let info_progress = wallet.info_load_progress(); if wallet.is_closing() { t!("wallets.wallet_closing") } else if info_progress != 100 { @@ -221,7 +223,7 @@ impl WalletContent { format!("{}: {}%", t!("wallets.wallet_loading"), info_progress) } } else { - let tx_progress = wallet.txs_loading_progress(); + let tx_progress = wallet.txs_load_progress(); if tx_progress == 0 { t!("wallets.tx_loading") } else { diff --git a/src/gui/views/wallets/wallet/info.rs b/src/gui/views/wallets/wallet/info.rs index 9aa408f..9013ac8 100644 --- a/src/gui/views/wallets/wallet/info.rs +++ b/src/gui/views/wallets/wallet/info.rs @@ -32,6 +32,8 @@ impl WalletTab for WalletInfo { frame: &mut eframe::Frame, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { - WalletContent::loading_ui(ui, frame, wallet); + if WalletContent::loading_ui(ui, frame, wallet) { + return; + } } } \ 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 0d6aa25..f844533 100644 --- a/src/gui/views/wallets/wallet/receive.rs +++ b/src/gui/views/wallets/wallet/receive.rs @@ -14,6 +14,7 @@ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType}; +use crate::gui::views::wallets::wallet::WalletContent; use crate::wallet::Wallet; /// Receive funds tab content. @@ -30,5 +31,8 @@ impl WalletTab for WalletReceive { frame: &mut eframe::Frame, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { + if WalletContent::loading_ui(ui, frame, wallet) { + return; + } } } \ 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 349f169..d8c9c1a 100644 --- a/src/gui/views/wallets/wallet/send.rs +++ b/src/gui/views/wallets/wallet/send.rs @@ -14,6 +14,7 @@ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType}; +use crate::gui::views::wallets::wallet::WalletContent; use crate::wallet::Wallet; /// Send funds tab content. @@ -30,5 +31,8 @@ impl WalletTab for WalletSend { frame: &mut eframe::Frame, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { + if WalletContent::loading_ui(ui, frame, wallet) { + return; + } } } \ 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 efd7e61..3381547 100644 --- a/src/gui/views/wallets/wallet/settings.rs +++ b/src/gui/views/wallets/wallet/settings.rs @@ -15,15 +15,25 @@ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::wallets::setup::ConnectionSetup; use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType}; -use crate::wallet::Wallet; +use crate::gui::views::wallets::wallet::WalletContent; +use crate::wallet::{ExternalConnection, Wallet}; /// Wallet settings tab content. -#[derive(Default)] pub struct WalletSettings { /// Connection setup content. conn_setup: ConnectionSetup } +impl Default for WalletSettings { + fn default() -> Self { + // Check external connections availability on first tab opening. + ExternalConnection::start_ext_conn_availability_check(); + Self { + conn_setup: ConnectionSetup::default(), + } + } +} + impl WalletTab for WalletSettings { fn get_type(&self) -> WalletTabType { WalletTabType::Settings @@ -34,6 +44,12 @@ impl WalletTab for WalletSettings { frame: &mut eframe::Frame, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { + // Show progress if wallet is loading after opening without an error. + if wallet.is_closing() || (wallet.get_data().is_none() && !wallet.load_error()) { + WalletContent::loading_progress_ui(ui, wallet); + return; + } + self.conn_setup.wallet_ui(ui, frame, wallet, cb); } } \ No newline at end of file diff --git a/src/wallet/connections/external.rs b/src/wallet/connections/external.rs index 5386d89..7e002a8 100644 --- a/src/wallet/connections/external.rs +++ b/src/wallet/connections/external.rs @@ -78,12 +78,6 @@ impl ExternalConnection { std::thread::spawn(move || { let chain_type = AppConfig::chain_type(); loop { - // Stop checking if connections are not showing or network type was changed. - if !AppConfig::show_connections_network_panel() - || chain_type != AppConfig::chain_type() { - break; - } - // Check external connections URLs availability. let conn_list = ConnectionsConfig::ext_conn_list(); for conn in conn_list { @@ -91,6 +85,12 @@ impl ExternalConnection { conn.check_conn_availability(); } + // Stop checking if connections are not showing or network type was changed. + if !AppConfig::show_connections_network_panel() + || chain_type != AppConfig::chain_type() { + break; + } + // Pause checking for delay value. std::thread::sleep(Self::AV_CHECK_DELAY); } diff --git a/src/wallet/list.rs b/src/wallet/list.rs index fe1ccbb..395a9f2 100644 --- a/src/wallet/list.rs +++ b/src/wallet/list.rs @@ -117,8 +117,14 @@ impl WalletList { } /// Open selected [`Wallet`]. - pub fn open_selected(&self, password: String) -> Result<(), Error> { - for w in self.list() { + pub fn open_selected(&mut self, password: String) -> Result<(), Error> { + let chain_type = AppConfig::chain_type(); + let list = if chain_type == ChainTypes::Mainnet { + &mut self.main_list + } else { + &mut self.test_list + }; + for w in list { if Some(w.config.id) == self.selected_id { return w.open(password.clone()); } diff --git a/src/wallet/wallets.rs b/src/wallet/wallets.rs index 8d6396c..7d4990f 100644 --- a/src/wallet/wallets.rs +++ b/src/wallet/wallets.rs @@ -36,25 +36,27 @@ use crate::wallet::types::{ConnectionMethod, WalletData, WalletInstance}; /// Contains wallet instance, configuration and state, handles wallet commands. #[derive(Clone)] pub struct Wallet { - /// Wallet instance. - instance: WalletInstance, + /// Wallet instance, initializing on wallet opening and clearing on wallet closing. + instance: Option, /// Wallet configuration. pub config: WalletConfig, /// Wallet updating thread. thread: Arc>>, + /// Flag to check if wallet reopening is needed. + reopen: Arc, /// Flag to check if wallet is open. is_open: Arc, /// Flag to check if wallet is loading. closing: Arc, /// Error on wallet loading. - loading_error: Arc, + load_error: Arc, /// Info loading progress in percents - info_loading_progress: Arc, + info_load_progress: Arc, /// Transactions loading progress in percents - txs_loading_progress: Arc, + txs_load_progress: Arc, /// Wallet data. data: Arc>>, @@ -63,17 +65,18 @@ pub struct Wallet { } impl Wallet { - /// Instantiate [`Wallet`] from provided [`WalletInstance`] and [`WalletConfig`]. - fn new(instance: WalletInstance, config: WalletConfig) -> Self { + /// Create new [`Wallet`] instance with provided [`WalletConfig`]. + fn new(config: WalletConfig) -> Self { Self { - instance, + instance: None, config, thread: Arc::from(RwLock::new(None)), + reopen: Arc::new(AtomicBool::new(false)), is_open: Arc::from(AtomicBool::new(false)), closing: Arc::new(AtomicBool::new(false)), - loading_error: Arc::from(AtomicBool::new(false)), - info_loading_progress: Arc::from(AtomicU8::new(0)), - txs_loading_progress: Arc::from(AtomicU8::new(0)), + load_error: Arc::from(AtomicBool::new(false)), + info_load_progress: Arc::from(AtomicU8::new(0)), + txs_load_progress: Arc::from(AtomicU8::new(0)), data: Arc::from(RwLock::new(None)), data_update_attempts: Arc::new(AtomicU8::new(0)) } @@ -87,10 +90,10 @@ impl Wallet { conn_method: &ConnectionMethod ) -> Result { let config = WalletConfig::create(name, conn_method); - let instance = Self::create_wallet_instance(config.clone())?; - let w = Wallet::new(instance, config); + let w = Wallet::new(config.clone()); { - let mut w_lock = w.instance.lock(); + let instance = Self::create_wallet_instance(config)?; + let mut w_lock = instance.lock(); let p = w_lock.lc_provider()?; p.create_wallet(None, Some(ZeroingString::from(mnemonic.clone())), @@ -106,18 +109,11 @@ impl Wallet { pub fn init(data_path: PathBuf) -> Option { let wallet_config = WalletConfig::load(data_path.clone()); if let Some(config) = wallet_config { - if let Ok(instance) = Self::create_wallet_instance(config.clone()) { - return Some(Wallet::new(instance, config)); - } + return Some(Wallet::new(config)); } None } - /// Reinitialize [`WalletInstance`] to apply new [`WalletConfig`]. - pub fn reinit(&self) { - //TODO: Reinit wallet. - } - /// Create [`WalletInstance`] from provided [`WalletConfig`]. fn create_wallet_instance(config: WalletConfig) -> Result { // Assume global chain type has already been initialized. @@ -171,10 +167,18 @@ impl Wallet { } /// Open the wallet and start update the data at separate thread. - pub fn open(&self, password: String) -> Result<(), Error> { - let mut wallet_lock = self.instance.lock(); - let lc = wallet_lock.lc_provider()?; + pub fn open(&mut self, password: String) -> Result<(), Error> { + if self.is_open() { + return Err(Error::GenericError("Already opened".to_string())); + } + + // Create new wallet instance. + let instance = Self::create_wallet_instance(self.config.clone())?; + self.instance = Some(instance.clone()); + // Open the wallet. + let mut wallet_lock = instance.lock(); + let lc = wallet_lock.lc_provider()?; match lc.open_wallet(None, ZeroingString::from(password), false, false) { Ok(keychain) => { // Start data updating if thread was not launched. @@ -189,29 +193,42 @@ impl Wallet { } self.is_open.store(true, Ordering::Relaxed); } - Err(e) => return Err(e) + Err(e) => { + self.instance = None; + return Err(e) + } } Ok(()) } + /// Set wallet reopen status. + pub fn set_reopen(&self, reopen: bool) { + self.reopen.store(reopen, Ordering::Relaxed); + } + + /// Check if wallet reopen is needed. + pub fn reopen_needed(&self) -> bool { + self.reopen.load(Ordering::Relaxed) + } + /// Get wallet transactions loading progress. - pub fn txs_loading_progress(&self) -> u8 { - self.txs_loading_progress.load(Ordering::Relaxed) + pub fn txs_load_progress(&self) -> u8 { + self.txs_load_progress.load(Ordering::Relaxed) } /// Get wallet info loading progress. - pub fn info_loading_progress(&self) -> u8 { - self.info_loading_progress.load(Ordering::Relaxed) + pub fn info_load_progress(&self) -> u8 { + self.info_load_progress.load(Ordering::Relaxed) } /// Check if wallet had an error on loading. - pub fn loading_error(&self) -> bool { - self.loading_error.load(Ordering::Relaxed) + pub fn load_error(&self) -> bool { + self.load_error.load(Ordering::Relaxed) } /// Set an error for wallet on loading. - pub fn set_loading_error(&self, error: bool) { - self.loading_error.store(error, Ordering::Relaxed); + pub fn set_load_error(&self, error: bool) { + self.load_error.store(error, Ordering::Relaxed); } /// Get wallet data update attempts. @@ -243,25 +260,29 @@ impl Wallet { /// Close the wallet. pub fn close(&self) { - if !self.is_open() { + if !self.is_open() || self.instance.is_none() { return; } self.closing.store(true, Ordering::Relaxed); // Close wallet at separate thread. - let wallet_close = self.clone(); + let mut wallet_close = self.clone(); + let instance = wallet_close.instance.clone().unwrap(); thread::spawn(move || { // Close the wallet. - let _ = Self::close_wallet(&wallet_close.instance); + let mut wallet_lock = instance.lock(); + let lc = wallet_lock.lc_provider().unwrap(); + let _ = lc.close_wallet(None); + wallet_close.instance = None; // Clear wallet info. let mut w_data = wallet_close.data.write().unwrap(); *w_data = None; // Reset wallet loading values. - wallet_close.info_loading_progress.store(0, Ordering::Relaxed); - wallet_close.txs_loading_progress.store(0, Ordering::Relaxed); - wallet_close.set_loading_error(false); + wallet_close.info_load_progress.store(0, Ordering::Relaxed); + wallet_close.txs_load_progress.store(0, Ordering::Relaxed); + wallet_close.set_load_error(false); wallet_close.reset_data_update_attempts(); // Mark wallet as not opened. @@ -276,13 +297,6 @@ impl Wallet { }); } - /// Close provided [`WalletInstance`]. - fn close_wallet(instance: &WalletInstance) -> Result<(), Error> { - let mut wallet_lock = instance.lock(); - let lc = wallet_lock.lc_provider()?; - lc.close_wallet(None) - } - /// Get wallet data. pub fn get_data(&self) -> Option { let r_data = self.data.read().unwrap(); @@ -313,7 +327,7 @@ fn start_wallet(wallet: Wallet, keychain: Option) -> Thread { // Set an error when required integrated node is not enabled and // skip next cycle of update when node sync is not finished. if wallet_update.config.ext_conn_id.is_none() { - wallet_update.set_loading_error(!Node::is_running() || Node::is_stopping()); + wallet_update.set_load_error(!Node::is_running() || Node::is_stopping()); if !Node::is_running() || Node::get_sync_status() != Some(SyncStatus::NoSync) { println!("integrated node wait"); thread::park_timeout(Duration::from_millis(1000)); @@ -322,7 +336,7 @@ fn start_wallet(wallet: Wallet, keychain: Option) -> Thread { } // Update wallet data if there is no error. - if !wallet_update.loading_error() { + if !wallet_update.load_error() { update_wallet_data(&wallet_update, keychain.clone()); } @@ -336,7 +350,7 @@ fn start_wallet(wallet: Wallet, keychain: Option) -> Thread { } // Repeat after default delay or after 1 second if update was not success. - let delay = if wallet_update.loading_error() + let delay = if wallet_update.load_error() || wallet_update.get_data_update_attempts() != 0 { Duration::from_millis(1000) } else { @@ -348,7 +362,7 @@ fn start_wallet(wallet: Wallet, keychain: Option) -> Thread { } /// Handle [`WalletCommand::UpdateData`] command to update [`WalletData`]. -fn update_wallet_data(wallet: &Wallet, key: Option) { +fn update_wallet_data(wallet: &Wallet, keychain: Option) { println!("UPDATE start, attempts: {}", wallet.get_data_update_attempts()); let wallet_scan = wallet.clone(); @@ -362,10 +376,10 @@ fn update_wallet_data(wallet: &Wallet, key: Option) { StatusMessage::UpdatingTransactions(_) => {} StatusMessage::FullScanWarn(_) => {} StatusMessage::Scanning(_, progress) => { - wallet_scan.info_loading_progress.store(progress, Ordering::Relaxed); + wallet_scan.info_load_progress.store(progress, Ordering::Relaxed); } StatusMessage::ScanningComplete(_) => { - wallet_scan.info_loading_progress.store(100, Ordering::Relaxed); + wallet_scan.info_load_progress.store(100, Ordering::Relaxed); } StatusMessage::UpdateWarning(_) => {} } @@ -373,108 +387,112 @@ fn update_wallet_data(wallet: &Wallet, key: Option) { }); // Retrieve wallet info. - match retrieve_summary_info( - wallet.instance.clone(), - key.as_ref(), - &Some(info_tx), - true, - wallet.config.min_confirmations - ) { - Ok(info) => { - // Do not retrieve txs if wallet was closed. - if !wallet.is_open() { - println!("UPDATE stop at retrieve_summary_info"); - return; - } - - // Add attempt if scanning was not complete - // or set an error on initial request. - if wallet.info_loading_progress() != 100 { - println!("UPDATE retrieve_summary_info was not completed"); - if wallet.get_data().is_none() { - wallet.set_loading_error(true); - } else { - wallet.increment_data_update_attempts(); + if let Some(instance) = &wallet.instance { + match retrieve_summary_info( + instance.clone(), + keychain.as_ref(), + &Some(info_tx), + true, + wallet.config.min_confirmations + ) { + Ok(info) => { + // Do not retrieve txs if wallet was closed. + if !wallet.is_open() { + println!("UPDATE stop at retrieve_summary_info"); + return; } - } else { - println!("UPDATE before retrieve_txs"); - let wallet_txs = wallet.clone(); - let (txs_tx, txs_rx) = mpsc::channel::(); - // Update txs loading progress at separate thread. - thread::spawn(move || { - while let Ok(m) = txs_rx.recv() { - println!("UPDATE TXS MESSAGE"); - match m { - StatusMessage::UpdatingOutputs(_) => {} - StatusMessage::UpdatingTransactions(_) => {} - StatusMessage::FullScanWarn(_) => {} - StatusMessage::Scanning(_, progress) => { - wallet_txs.txs_loading_progress.store(progress, Ordering::Relaxed); - } - StatusMessage::ScanningComplete(_) => { - wallet_txs.txs_loading_progress.store(100, Ordering::Relaxed); - } - StatusMessage::UpdateWarning(_) => {} - } - } - }); - - // Retrieve txs. - match retrieve_txs( - wallet.instance.clone(), - key.as_ref(), - &Some(txs_tx), - true, - None, - None, - None - ) { - Ok(txs) => { - // Do not update data if wallet was closed. - if !wallet.is_open() { - return; - } - - // Add attempt if retrieving was not complete - // or set an error on initial request. - if wallet.txs_loading_progress() != 100 { - if wallet.get_data().is_none() { - wallet.set_loading_error(true); - } else { - wallet.increment_data_update_attempts(); - } - } else { - // Set wallet data. - let mut w_data = wallet.data.write().unwrap(); - *w_data = Some(WalletData { info: info.1, txs: txs.1 }); - - // Reset attempts. - wallet.reset_data_update_attempts(); - } - } - Err(_) => { - // Increment attempts value in case of error. + // Add attempt if scanning was not complete + // or set an error on initial request. + if wallet.info_load_progress() != 100 { + println!("UPDATE retrieve_summary_info was not completed"); + if wallet.get_data().is_none() { + wallet.set_load_error(true); + } else { wallet.increment_data_update_attempts(); } + } else { + println!("UPDATE before retrieve_txs"); + + let wallet_txs = wallet.clone(); + let (txs_tx, txs_rx) = mpsc::channel::(); + // Update txs loading progress at separate thread. + thread::spawn(move || { + while let Ok(m) = txs_rx.recv() { + println!("UPDATE TXS MESSAGE"); + match m { + StatusMessage::UpdatingOutputs(_) => {} + StatusMessage::UpdatingTransactions(_) => {} + StatusMessage::FullScanWarn(_) => {} + StatusMessage::Scanning(_, progress) => { + wallet_txs.txs_load_progress.store(progress, Ordering::Relaxed); + } + StatusMessage::ScanningComplete(_) => { + wallet_txs.txs_load_progress.store(100, Ordering::Relaxed); + } + StatusMessage::UpdateWarning(_) => {} + } + } + }); + + // Retrieve txs. + match retrieve_txs( + instance.clone(), + keychain.as_ref(), + &Some(txs_tx), + true, + None, + None, + None + ) { + Ok(txs) => { + // Do not update data if wallet was closed. + if !wallet.is_open() { + return; + } + + // Add attempt if retrieving was not complete + // or set an error on initial request. + if wallet.txs_load_progress() != 100 { + if wallet.get_data().is_none() { + wallet.set_load_error(true); + } else { + wallet.increment_data_update_attempts(); + } + } else { + // Set wallet data. + let mut w_data = wallet.data.write().unwrap(); + *w_data = Some(WalletData { info: info.1, txs: txs.1 }); + + // Reset attempts. + wallet.reset_data_update_attempts(); + } + } + Err(e) => { + println!("error on retrieve_txs {}", e); + // Increment attempts value in case of error. + wallet.increment_data_update_attempts(); + } + } } } - } - Err(_) => { - // Increment attempts value in case of error. - wallet.increment_data_update_attempts(); + Err(e) => { + println!("error on retrieve_summary_info {}", e); + // Increment attempts value in case of error. + wallet.increment_data_update_attempts(); + } } } // Reset progress values. - wallet.info_loading_progress.store(0, Ordering::Relaxed); - wallet.txs_loading_progress.store(0, Ordering::Relaxed); + wallet.info_load_progress.store(0, Ordering::Relaxed); + wallet.txs_load_progress.store(0, Ordering::Relaxed); println!("UPDATE finish, attempts: {}", wallet.get_data_update_attempts()); // Set an error if maximum number of attempts was reached. if wallet.get_data_update_attempts() >= DATA_UPDATE_ATTEMPTS { wallet.reset_data_update_attempts(); - wallet.set_loading_error(true); + wallet.set_load_error(true); } } \ No newline at end of file