From c155deedb5a16ae06b0e5cb3039eb71afd942271 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Thu, 19 Sep 2024 15:56:53 +0300 Subject: [PATCH] wallet: qr scan modal, connections content and default list, wallet creation and list refactoring, tx height --- src/gui/app.rs | 21 +- src/gui/views/camera.rs | 10 - src/gui/views/content.rs | 11 +- src/gui/views/mod.rs | 5 +- src/gui/views/network/connections.rs | 25 +- src/gui/views/network/setup/stratum.rs | 3 +- src/gui/views/scan.rs | 131 +++++ src/gui/views/wallets/content.rs | 454 ++++++++------- src/gui/views/wallets/creation/creation.rs | 551 ++++++++---------- src/gui/views/wallets/creation/mnemonic.rs | 97 +-- src/gui/views/wallets/modals/add.rs | 116 ++++ src/gui/views/wallets/modals/conn.rs | 83 ++- src/gui/views/wallets/modals/mod.rs | 5 +- src/gui/views/wallets/modals/open.rs | 22 +- src/gui/views/wallets/modals/wallets.rs | 57 +- src/gui/views/wallets/wallet/content.rs | 128 ++-- .../views/wallets/wallet/messages/content.rs | 9 +- .../views/wallets/wallet/messages/request.rs | 6 +- src/gui/views/wallets/wallet/modals/mod.rs | 5 +- .../views/wallets/wallet/settings/common.rs | 10 +- .../wallets/wallet/settings/connection.rs | 153 ++--- .../views/wallets/wallet/settings/content.rs | 2 +- .../views/wallets/wallet/settings/recovery.rs | 11 +- .../views/wallets/wallet/transport/content.rs | 8 +- .../views/wallets/wallet/transport/send.rs | 2 +- src/gui/views/wallets/wallet/txs/content.rs | 14 +- src/gui/views/wallets/wallet/txs/tx.rs | 16 +- src/gui/views/wallets/wallet/types.rs | 4 +- src/wallet/config.rs | 15 +- src/wallet/connections/config.rs | 61 +- src/wallet/connections/external.rs | 49 +- src/wallet/list.rs | 49 +- src/wallet/types.rs | 11 +- src/wallet/wallet.rs | 278 +++++---- 34 files changed, 1254 insertions(+), 1168 deletions(-) create mode 100644 src/gui/views/scan.rs create mode 100644 src/gui/views/wallets/modals/add.rs diff --git a/src/gui/app.rs b/src/gui/app.rs index f95176a..e6370e6 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -273,20 +273,21 @@ impl App { // Paint the title. let dual_wallets_panel = - ui.available_width() >= (Content::SIDE_PANEL_WIDTH * 3.0) + View::get_right_inset(); - let wallet_panel_opened = self.content.wallets.wallet_panel_opened(); - let hide_app_name = if dual_wallets_panel { - !wallet_panel_opened || (AppConfig::show_wallets_at_dual_panel() && - self.content.wallets.showing_wallet() && !self.content.wallets.creating_wallet()) + ui.available_width() >= (Content::SIDE_PANEL_WIDTH * 3.0) + + View::get_right_inset() + View::get_left_inset(); + let wallet_panel_opened = self.content.wallets.showing_wallet(); + let show_app_name = if dual_wallets_panel { + wallet_panel_opened && !AppConfig::show_wallets_at_dual_panel() } else if Content::is_dual_panel_mode(ui) { - !wallet_panel_opened + wallet_panel_opened } else { - !Content::is_network_panel_open() && !wallet_panel_opened + Content::is_network_panel_open() || wallet_panel_opened }; - let title_text = if hide_app_name { - "ツ".to_string() - } else { + let creating_wallet = self.content.wallets.creating_wallet(); + let title_text = if creating_wallet || show_app_name { format!("Grim {}", crate::VERSION) + } else { + "ツ".to_string() }; painter.text( title_rect.center(), diff --git a/src/gui/views/camera.rs b/src/gui/views/camera.rs index d7f4483..e0624ab 100644 --- a/src/gui/views/camera.rs +++ b/src/gui/views/camera.rs @@ -430,14 +430,4 @@ impl CameraContent { } None } - - /// Reset camera content state to default. - pub fn clear_state(&mut self) { - // Clear QR code scanning state. - let mut w_scan = self.qr_scan_state.write(); - *w_scan = QrScanState::default(); - // Clear UR data. - let mut w_data = self.ur_data.write(); - *w_data = None; - } } \ No newline at end of file diff --git a/src/gui/views/content.rs b/src/gui/views/content.rs index 2590a42..fc91ae0 100644 --- a/src/gui/views/content.rs +++ b/src/gui/views/content.rs @@ -25,7 +25,7 @@ use crate::gui::views::types::{ModalContainer, ModalPosition}; use crate::node::Node; use crate::{AppConfig, Settings}; use crate::gui::icons::{CHECK, CHECK_FAT, FILE_X}; -use crate::gui::views::network::{NetworkContent, NodeSetup}; +use crate::gui::views::network::NetworkContent; use crate::gui::views::wallets::WalletsContent; lazy_static! { @@ -272,14 +272,7 @@ impl Content { pub fn settings_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal) { ui.add_space(6.0); - // Draw chain type selection. - NodeSetup::chain_type_ui(ui); - - ui.add_space(8.0); - View::horizontal_line(ui, Colors::item_stroke()); - ui.add_space(8.0); - - // Draw theme selection. + // Show theme selection. Self::theme_selection_ui(ui); ui.add_space(8.0); diff --git a/src/gui/views/mod.rs b/src/gui/views/mod.rs index 3be7242..e379459 100644 --- a/src/gui/views/mod.rs +++ b/src/gui/views/mod.rs @@ -40,4 +40,7 @@ mod file_pick; pub use file_pick::*; mod pull_to_refresh; -pub use pull_to_refresh::*; \ No newline at end of file +pub use pull_to_refresh::*; + +mod scan; +pub use scan::*; \ No newline at end of file diff --git a/src/gui/views/network/connections.rs b/src/gui/views/network/connections.rs index 5345504..2c400bf 100644 --- a/src/gui/views/network/connections.rs +++ b/src/gui/views/network/connections.rs @@ -103,23 +103,20 @@ impl ConnectionsContent { ui.add_space(4.0); let ext_conn_list = ConnectionsConfig::ext_conn_list(); - if !ext_conn_list.is_empty() { + let ext_conn_size = ext_conn_list.len(); + if ext_conn_size != 0 { ui.add_space(8.0); - for (index, conn) in ext_conn_list.iter().enumerate() { + for (index, conn) in ext_conn_list.iter().filter(|c| !c.deleted).enumerate() { ui.horizontal_wrapped(|ui| { // Draw connection list item. - let len = ext_conn_list.len(); - Self::ext_conn_item_ui(ui, conn, index, len, |ui| { - // Draw buttons for non-default connections. - if conn.url != ExternalConnection::DEFAULT_MAIN_URL { - let button_rounding = View::item_rounding(index, len, true); - View::item_button(ui, button_rounding, TRASH, None, || { - ConnectionsConfig::remove_ext_conn(conn.id); - }); - View::item_button(ui, Rounding::default(), PENCIL, None, || { - self.show_add_ext_conn_modal(Some(conn.clone()), cb); - }); - } + Self::ext_conn_item_ui(ui, conn, index, ext_conn_size, |ui| { + let button_rounding = View::item_rounding(index, ext_conn_size, true); + View::item_button(ui, button_rounding, TRASH, None, || { + ConnectionsConfig::remove_ext_conn(conn.id); + }); + View::item_button(ui, Rounding::default(), PENCIL, None, || { + self.show_add_ext_conn_modal(Some(conn.clone()), cb); + }); }); }); } diff --git a/src/gui/views/network/setup/stratum.rs b/src/gui/views/network/setup/stratum.rs index 3d85aca..9d65b00 100644 --- a/src/gui/views/network/setup/stratum.rs +++ b/src/gui/views/network/setup/stratum.rs @@ -112,7 +112,8 @@ impl ModalContainer for StratumSetup { cb: &dyn PlatformCallbacks) { match modal.id { WALLET_SELECTION_MODAL => { - self.wallets_modal.ui(ui, modal, &mut self.wallets, cb, |id, _| { + self.wallets_modal.ui(ui, modal, &mut self.wallets, cb, |wallet, _| { + let id = wallet.get_config().id; NodeConfig::save_stratum_wallet_id(id); self.wallet_name = WalletConfig::name_by_id(id); }) diff --git a/src/gui/views/scan.rs b/src/gui/views/scan.rs new file mode 100644 index 0000000..4431e59 --- /dev/null +++ b/src/gui/views/scan.rs @@ -0,0 +1,131 @@ +// 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::scroll_area::ScrollBarVisibility; +use egui::{Id, ScrollArea}; + +use crate::gui::Colors; +use crate::gui::icons::COPY; +use crate::gui::platform::PlatformCallbacks; +use crate::gui::views::{CameraContent, Modal, View}; +use crate::gui::views::types::QrScanResult; + +/// QR code scan [`Modal`] content. +pub struct CameraScanModal { + /// Camera content for QR scan [`Modal`]. + camera_content: Option, + /// QR code scan result + qr_scan_result: Option, +} + +impl Default for CameraScanModal { + fn default() -> Self { + Self { + camera_content: None, + qr_scan_result: None, + } + } +} + +impl CameraScanModal { + /// Draw [`Modal`] content. + pub fn ui(&mut self, + ui: &mut egui::Ui, + modal: &Modal, + cb: &dyn PlatformCallbacks, + mut on_result: impl FnMut(&QrScanResult)) { + // Show scan result if exists or show camera content while scanning. + if let Some(result) = &self.qr_scan_result { + let mut result_text = result.text(); + View::horizontal_line(ui, Colors::item_stroke()); + ui.add_space(3.0); + ScrollArea::vertical() + .id_source(Id::from("qr_scan_result_input")) + .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) + .max_height(128.0) + .auto_shrink([false; 2]) + .show(ui, |ui| { + ui.add_space(7.0); + egui::TextEdit::multiline(&mut result_text) + .font(egui::TextStyle::Small) + .desired_rows(5) + .interactive(false) + .desired_width(f32::INFINITY) + .show(ui); + ui.add_space(6.0); + }); + ui.add_space(2.0); + View::horizontal_line(ui, Colors::item_stroke()); + ui.add_space(10.0); + + // Show copy button. + ui.vertical_centered(|ui| { + let copy_text = format!("{} {}", COPY, t!("copy")); + View::button(ui, copy_text, Colors::button(), || { + cb.copy_string_to_buffer(result_text.to_string()); + self.qr_scan_result = None; + modal.close(); + }); + }); + ui.add_space(10.0); + View::horizontal_line(ui, Colors::item_stroke()); + ui.add_space(6.0); + } else if let Some(result) = self.camera_content.get_or_insert(CameraContent::default()) + .qr_scan_result() { + cb.stop_camera(); + self.camera_content = None; + on_result(&result); + + // Set result and rename modal title. + self.qr_scan_result = Some(result); + Modal::set_title(t!("scan_result")); + } else { + ui.add_space(6.0); + self.camera_content.as_mut().unwrap().ui(ui, cb); + ui.add_space(6.0); + } + + if self.qr_scan_result.is_some() { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("close"), Colors::white_or_black(false), || { + self.qr_scan_result = None; + self.camera_content = None; + modal.close(); + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::button(ui, t!("repeat"), Colors::white_or_black(false), || { + Modal::set_title(t!("scan_qr")); + self.qr_scan_result = None; + self.camera_content = Some(CameraContent::default()); + cb.start_camera(); + }); + }); + }); + } else { + ui.vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { + cb.stop_camera(); + self.camera_content = None; + modal.close(); + }); + }); + } + ui.add_space(6.0); + } +} \ No newline at end of file diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index df5495b..f9cf10b 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -18,33 +18,36 @@ use egui::scroll_area::ScrollBarVisibility; use crate::AppConfig; use crate::gui::Colors; -use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_OPEN, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SUITCASE}; +use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_OPEN, FOLDER_PLUS, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SUITCASE}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, Content, TitlePanel, View}; use crate::gui::views::types::{ModalContainer, ModalPosition, TitleContentType, TitleType}; use crate::gui::views::wallets::creation::WalletCreation; -use crate::gui::views::wallets::modals::{OpenWalletModal, WalletConnectionModal, WalletsModal}; +use crate::gui::views::wallets::modals::{AddWalletModal, OpenWalletModal, WalletConnectionModal, WalletsModal}; use crate::gui::views::wallets::types::WalletTabType; -use crate::gui::views::wallets::wallet::types::status_text; +use crate::gui::views::wallets::wallet::types::wallet_status_text; use crate::gui::views::wallets::WalletContent; use crate::wallet::{Wallet, WalletList}; +use crate::wallet::types::ConnectionMethod; /// Wallets content. pub struct WalletsContent { /// List of wallets. wallets: WalletList, - /// Wallet selection [`Modal`] content. - wallet_selection_content: Option, + /// Initial wallet creation [`Modal`] content. + add_wallet_modal_content: Option, /// Wallet opening [`Modal`] content. open_wallet_content: Option, /// Wallet connection selection content. conn_selection_content: Option, + /// Wallet selection [`Modal`] content. + wallet_selection_content: Option, /// Selected [`Wallet`] content. - wallet_content: WalletContent, + wallet_content: Option, /// Wallet creation content. - creation_content: WalletCreation, + creation_content: Option, /// Flag to show [`Wallet`] list at dual panel mode. show_wallets_at_dual_panel: bool, @@ -53,14 +56,10 @@ pub struct WalletsContent { modal_ids: Vec<&'static str> } -/// Identifier for connection selection [`Modal`]. -const CONNECTION_SELECTION_MODAL: &'static str = "wallets_connection_selection"; - -/// Identifier for wallet opening [`Modal`]. +const ADD_WALLET_MODAL: &'static str = "wallets_add_modal"; const OPEN_WALLET_MODAL: &'static str = "wallets_open_wallet"; - -/// Identifier for wallet opening [`Modal`]. -const SELECT_WALLET_MODAL: &'static str = "wallets_select_wallet"; +const SELECT_CONNECTION_MODAL: &'static str = "wallets_select_conn_modal"; +const SELECT_WALLET_MODAL: &'static str = "wallets_select_modal"; impl Default for WalletsContent { fn default() -> Self { @@ -69,15 +68,16 @@ impl Default for WalletsContent { wallet_selection_content: None, open_wallet_content: None, conn_selection_content: None, - wallet_content: WalletContent::new(None), - creation_content: WalletCreation::default(), + wallet_content: None, + creation_content: None, show_wallets_at_dual_panel: AppConfig::show_wallets_at_dual_panel(), modal_ids: vec![ + ADD_WALLET_MODAL, OPEN_WALLET_MODAL, - WalletCreation::NAME_PASS_MODAL, - CONNECTION_SELECTION_MODAL, - SELECT_WALLET_MODAL - ] + SELECT_CONNECTION_MODAL, + SELECT_WALLET_MODAL, + ], + add_wallet_modal_content: None, } } } @@ -92,36 +92,50 @@ impl ModalContainer for WalletsContent { modal: &Modal, cb: &dyn PlatformCallbacks) { match modal.id { - OPEN_WALLET_MODAL => { - if let Some(content) = self.open_wallet_content.as_mut() { - content.ui(ui, modal, &mut self.wallets, cb, |data| { - // Setup wallet content. - self.wallet_content = WalletContent::new(data); + ADD_WALLET_MODAL => { + if let Some(content) = self.add_wallet_modal_content.as_mut() { + content.ui(ui, modal, cb, |name, pass| { + self.creation_content = Some( + WalletCreation::new(name.clone(), pass.clone()) + ); }); } + if self.creation_content.is_some() { + self.add_wallet_modal_content = None; + } }, - WalletCreation::NAME_PASS_MODAL => { - self.creation_content.name_pass_modal_ui(ui, modal, cb) + OPEN_WALLET_MODAL => { + let mut open = false; + if let Some(open_content) = self.open_wallet_content.as_mut() { + open_content.ui(ui, modal, cb, |wallet, data| { + self.wallet_content = Some(WalletContent::new(wallet, data)); + open = true; + }); + } + if open { + self.open_wallet_content = None; + } }, - CONNECTION_SELECTION_MODAL => { + SELECT_CONNECTION_MODAL => { if let Some(content) = self.conn_selection_content.as_mut() { - content.ui(ui, modal, cb, |id| { - // Update wallet connection on select. - let list = self.wallets.list(); - for w in list { - if self.wallets.selected_id == Some(w.get_config().id) { - w.update_ext_conn_id(id); - } + content.ui(ui, modal, cb, |conn| { + if let Some(wallet_content) = &self.wallet_content { + wallet_content.wallet.update_connection(&conn); } }); } } SELECT_WALLET_MODAL => { + let mut select = false; if let Some(content) = self.wallet_selection_content.as_mut() { - content.ui(ui, modal, &mut self.wallets, cb, |_, data| { - self.wallet_content = WalletContent::new(data); + content.ui(ui, modal, &mut self.wallets, cb, |wallet, data| { + self.wallet_content = Some(WalletContent::new(wallet, data)); + select = true; }); } + if select { + self.wallet_selection_content = None; + } } _ => {} } @@ -131,94 +145,52 @@ impl ModalContainer for WalletsContent { impl WalletsContent { /// Draw wallets content. pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { - // Draw modal content for current ui container. self.current_modal_ui(ui, cb); - // Setup wallet content flags. - let empty_list = self.wallets.is_current_list_empty(); - let create_wallet = self.creation_content.can_go_back(); - let show_wallet = self.wallets.is_selected_open(); - - // Setup panels parameters. + let creating_wallet = self.creating_wallet(); + let showing_wallet = self.showing_wallet() && !creating_wallet; let dual_panel = is_dual_panel_mode(ui); - let wallet_panel_width = self.wallet_panel_width(ui, empty_list, dual_panel, show_wallet); let content_width = ui.available_width(); - - let root_dual_panel = Content::is_dual_panel_mode(ui); - - // Flag to check if wallet list is hidden on the screen. - let list_hidden = content_width == 0.0 || empty_list || create_wallet - || (dual_panel && show_wallet && !self.show_wallets_at_dual_panel) - || (!dual_panel && show_wallet) || - (!root_dual_panel && Content::is_network_panel_open()); + let list_hidden = creating_wallet || self.wallets.list().is_empty() + || (dual_panel && showing_wallet && !self.show_wallets_at_dual_panel) + || (!dual_panel && showing_wallet); // Show title panel. - self.title_ui(ui, dual_panel, create_wallet, show_wallet); + self.title_ui(ui, dual_panel, showing_wallet); - // Show wallet panel content. - let wallet_panel_opened = self.wallet_panel_opened(); - egui::SidePanel::right("wallet_panel") - .resizable(false) - .exact_width(wallet_panel_width) - .frame(egui::Frame { - fill: if empty_list && !create_wallet - || (dual_panel && show_wallet && !self.show_wallets_at_dual_panel) { - Colors::fill_deep() + if showing_wallet { + egui::SidePanel::right("wallet_panel") + .resizable(false) + .exact_width(if list_hidden { + content_width } else { - if create_wallet { - Colors::white_or_black(false) - } else { - Colors::button() + content_width - Content::SIDE_PANEL_WIDTH + }) + .frame(egui::Frame { + fill: Colors::fill_deep(), + ..Default::default() + }) + .show_inside(ui, |ui| { + // Show opened wallet content. + if let Some(content) = self.wallet_content.as_mut() { + content.ui(ui, cb); } - }, - ..Default::default() - }) - .show_animated_inside(ui, wallet_panel_opened, |ui| { - if create_wallet || !show_wallet { - // Show wallet creation content. - self.creation_content.ui(ui, cb, |wallet| { - // Add created wallet to list. - self.wallets.add(wallet); - // Reset wallet content. - self.wallet_content = WalletContent::new(None); - }); - } else { - let selected_id = self.wallets.selected_id.clone(); - let list = self.wallets.mut_list(); - for wallet in list { - // Show content for selected wallet. - if selected_id == Some(wallet.get_config().id) { - // Setup wallet content width. - let mut rect = ui.available_rect_before_wrap(); - let mut width = ui.available_width(); - if dual_panel && self.show_wallets_at_dual_panel { - width = content_width - Content::SIDE_PANEL_WIDTH; - } - rect.set_width(width); - // Show wallet content. - ui.allocate_ui_at_rect(rect, |ui| { - self.wallet_content.ui(ui, wallet, cb); - }); - break; - } - } - } - }); + }); + } - // Show wallets bottom panel. - let show_bottom_panel = !list_hidden || dual_panel; - if show_bottom_panel { + if !list_hidden { egui::TopBottomPanel::bottom("wallets_bottom_panel") .frame(egui::Frame { fill: Colors::fill(), inner_margin: Margin { - left: View::get_left_inset() + View::TAB_ITEMS_PADDING, + left: View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING, right: View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING, top: View::TAB_ITEMS_PADDING, bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING, }, ..Default::default() }) + .resizable(false) .show_inside(ui, |ui| { // Setup spacing between tabs. ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0); @@ -226,20 +198,21 @@ impl WalletsContent { ui.style_mut().spacing.button_padding = egui::vec2(10.0, 4.0); ui.vertical_centered(|ui| { - let pressed = Modal::opened() == Some(WalletCreation::NAME_PASS_MODAL); + let pressed = Modal::opened() == Some(ADD_WALLET_MODAL); View::tab_button(ui, PLUS, pressed, || { - self.creation_content.show_name_pass_modal(cb); + self.show_add_wallet_modal(cb); }); }); }); - } - // Show wallet list. - egui::CentralPanel::default() - .frame(if list_hidden { - egui::Frame::default() - } else { - egui::Frame { + egui::SidePanel::left("wallet_list_panel") + .exact_width(if dual_panel && showing_wallet { + Content::SIDE_PANEL_WIDTH + } else { + content_width + }) + .resizable(false) + .frame(egui::Frame { stroke: View::item_stroke(), fill: Colors::fill_deep(), inner_margin: Margin { @@ -249,30 +222,76 @@ impl WalletsContent { bottom: 4.0, }, ..Default::default() - } + }) + .show_inside(ui, |ui| { + if !list_hidden && !dual_panel { + ui.ctx().request_repaint_after(Duration::from_millis(1000)); + } + // Show wallet list. + self.wallet_list_ui(ui, cb); + }); + } + + // Show central panel with wallet creation. + egui::CentralPanel::default() + .frame(egui::Frame { + stroke: View::item_stroke(), + fill: if self.creation_content.is_some() { + Colors::white_or_black(false) + } else { + Colors::fill_deep() + }, + ..Default::default() }) .show_inside(ui, |ui| { - if !list_hidden && !dual_panel { - ui.ctx().request_repaint_after(Duration::from_millis(1000)); - } - self.wallet_list_ui(ui, cb); - }); - } + if self.creation_content.is_some() { + let creation = self.creation_content.as_mut().unwrap(); + let pass = creation.pass.clone(); + let mut created = false; + // Show wallet creation content. + creation.ui(ui, cb, |wallet| { + self.wallets.add(wallet.clone()); + if let Ok(_) = wallet.open(pass.clone()) { + self.wallet_content = Some(WalletContent::new(wallet, None)); + } + created = true; + }); + if created { + self.creation_content = None; + } + } else if self.wallets.list().is_empty() { + View::center_content(ui, 350.0 + View::get_bottom_inset(), |ui| { + View::app_logo_name_version(ui); + ui.add_space(4.0); - /// Check if wallet panel is showing. - pub fn wallet_panel_opened(&self) -> bool { - let empty_list = self.wallets.is_current_list_empty(); - empty_list || self.creating_wallet() || self.showing_wallet() + let text = t!("wallets.create_desc"); + ui.label(RichText::new(text) + .size(16.0) + .color(Colors::gray()) + ); + ui.add_space(8.0); + // Show wallet creation button. + let add_text = format!("{} {}", FOLDER_PLUS, t!("wallets.add")); + View::button(ui, add_text, Colors::white_or_black(false), || { + self.show_add_wallet_modal(cb); + }); + }); + } + }); } /// Check if opened wallet is showing. pub fn showing_wallet(&self) -> bool { - self.wallets.is_selected_open() + if let Some(wallet_content) = &self.wallet_content { + let w = &wallet_content.wallet; + return w.is_open() && w.get_config().chain_type == AppConfig::chain_type(); + } + false } /// Check if wallet is creating. pub fn creating_wallet(&self) -> bool { - self.creation_content.can_go_back() + self.creation_content.is_some() } /// Handle data from deeplink or opened file. @@ -286,25 +305,38 @@ impl WalletsContent { Content::toggle_network_panel(); } // Pass data to opened selected wallet or show wallets selection. - if self.wallets.is_selected_open() { - if wallets_size == 1 { - self.wallet_content = WalletContent::new(data); + if self.wallet_content.is_some() { + if self.showing_wallet() { + if wallets_size == 1 { + let wallet_content = self.wallet_content.as_mut().unwrap(); + wallet_content.on_data(data); + } else { + self.show_wallet_selection_modal(data); + } } else { - self.show_wallet_selection_modal(data); - } - } else { - if wallets_size == 1 { - self.show_opening_modal(self.wallets.list()[0].get_config().id, data, cb); - } else { - self.show_wallet_selection_modal(data); + if wallets_size == 1 { + let wallet_content = self.wallet_content.as_ref().unwrap(); + self.show_opening_modal(wallet_content.wallet.clone(), data, cb); + } else { + self.show_wallet_selection_modal(data); + } } } } + /// Show initial wallet creation [`Modal`]. + pub fn show_add_wallet_modal(&mut self, cb: &dyn PlatformCallbacks) { + self.add_wallet_modal_content = Some(AddWalletModal::default()); + Modal::new(ADD_WALLET_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("wallets.add")) + .show(); + cb.show_keyboard(); + } + /// Show wallet selection with provided optional data. fn show_wallet_selection_modal(&mut self, data: Option) { self.wallet_selection_content = Some(WalletsModal::new(None, data, true)); - // Show wallet selection modal. Modal::new(SELECT_WALLET_MODAL) .position(ModalPosition::Center) .title(t!("network_settings.choose_wallet")) @@ -313,13 +345,17 @@ impl WalletsContent { /// Handle Back key event returning `false` when event was handled. pub fn on_back(&mut self) -> bool { - let can_go_back = self.creation_content.can_go_back(); - if can_go_back { - self.creation_content.back(); + if self.creation_content.is_some() { + // Close wallet creation. + let creation = self.creation_content.as_mut().unwrap(); + if creation.on_back() { + self.creation_content = None; + } return false } else { - if self.wallets.is_selected_open() { - self.wallets.select(None); + // Close opened wallet. + if self.showing_wallet() { + self.wallet_content = None; return false } } @@ -330,37 +366,39 @@ impl WalletsContent { fn title_ui(&mut self, ui: &mut egui::Ui, dual_panel: bool, - create_wallet: bool, show_wallet: bool) { let show_list = self.show_wallets_at_dual_panel; - + let creating_wallet = self.creating_wallet(); // Setup title. - let title_content = if self.wallets.is_selected_open() && (!dual_panel - || (dual_panel && !show_list)) && !create_wallet { - let title_text = self.wallet_content.current_tab.get_type().name().to_uppercase(); - if self.wallet_content.current_tab.get_type() == WalletTabType::Settings { + let title_content = if show_wallet && (!dual_panel + || (dual_panel && !show_list)) && !creating_wallet { + let wallet_content = self.wallet_content.as_ref().unwrap(); + let wallet_tab_type = wallet_content.current_tab.get_type(); + let title_text = wallet_tab_type.name().to_uppercase(); + if wallet_tab_type == WalletTabType::Settings { TitleType::Single(TitleContentType::Title(title_text)) } else { - let subtitle_text = self.wallets.selected_name(); + let subtitle_text = wallet_content.wallet.get_config().name; TitleType::Single(TitleContentType::WithSubTitle(title_text, subtitle_text, false)) } } else { - let title_text = if create_wallet { + let title_text = if creating_wallet { t!("wallets.add") } else { t!("wallets.title") }.to_uppercase(); - let dual_title = !create_wallet && show_wallet && dual_panel; + let dual_title = !creating_wallet && show_wallet && dual_panel; if dual_title { - let wallet_tab_type = self.wallet_content.current_tab.get_type(); - let wallet_tab_name = wallet_tab_type.name().to_uppercase(); - let title_content = if wallet_tab_type == WalletTabType::Settings { - TitleContentType::Title(wallet_tab_name) + 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().to_uppercase(); + let wallet_title_content = if wallet_tab_type == WalletTabType::Settings { + TitleContentType::Title(wallet_title_text) } else { - let subtitle_text = self.wallets.selected_name(); - TitleContentType::WithSubTitle(wallet_tab_name, subtitle_text, false) + let subtitle_text = wallet_content.wallet.get_config().name; + TitleContentType::WithSubTitle(wallet_title_text, subtitle_text, false) }; - TitleType::Dual(TitleContentType::Title(title_text), title_content) + TitleType::Dual(TitleContentType::Title(title_text), wallet_title_content) } else { TitleType::Single(TitleContentType::Title(title_text)) } @@ -370,12 +408,20 @@ impl WalletsContent { TitlePanel::new(Id::new("wallets_title_panel")).ui(title_content, |ui| { if show_wallet && !dual_panel { View::title_button_big(ui, ARROW_LEFT, |_| { - self.wallets.select(None); - }); - } else if create_wallet { - View::title_button_big(ui, ARROW_LEFT, |_| { - self.creation_content.back(); + self.wallet_content = None; }); + } else if self.creation_content.is_some() { + let mut close = false; + if let Some(creation) = self.creation_content.as_mut() { + View::title_button_big(ui, ARROW_LEFT, |_| { + if creation.on_back() { + close = true; + } + }); + } + if close { + self.creation_content = None; + } } else if show_wallet && dual_panel { let list_icon = if show_list { SIDEBAR_SIMPLE @@ -402,29 +448,6 @@ impl WalletsContent { }, ui); } - /// Calculate [`WalletContent`] panel width. - fn wallet_panel_width( - &self, - ui:&mut egui::Ui, - list_empty: bool, - dual_panel: bool, - show_wallet: bool - ) -> f32 { - let create_wallet = self.creation_content.can_go_back(); - let available_width = if list_empty || create_wallet || (show_wallet && !dual_panel) - || (show_wallet && !self.show_wallets_at_dual_panel) { - ui.available_width() - } else { - ui.available_width() - Content::SIDE_PANEL_WIDTH - }; - if dual_panel && show_wallet && self.show_wallets_at_dual_panel { - let min_width = Content::SIDE_PANEL_WIDTH + View::get_right_inset(); - f32::max(min_width, available_width) - } else { - available_width - } - } - /// Draw list of wallets. fn wallet_list_ui(&mut self, ui: &mut egui::Ui, @@ -445,7 +468,7 @@ impl WalletsContent { list.retain(|w| { let deleted = w.is_deleted(); if deleted { - self.wallets.select(None); + self.wallet_content = None; self.wallets.remove(w.get_config().id); ui.ctx().request_repaint(); } @@ -453,9 +476,9 @@ impl WalletsContent { }); for wallet in &list { // Check if wallet reopen is needed. - if !wallet.is_open() && wallet.reopen_needed() { + if wallet.reopen_needed() && !wallet.is_open() { wallet.set_reopen(false); - self.show_opening_modal(wallet.get_config().id, None, cb); + self.show_opening_modal(wallet.clone(), None, cb); } // Draw wallet list item. self.wallet_item_ui(ui, wallet, cb); @@ -472,9 +495,11 @@ impl WalletsContent { wallet: &Wallet, cb: &dyn PlatformCallbacks) { let config = wallet.get_config(); - let id = config.id; - let is_selected = self.wallets.selected_id == Some(id); - let current = is_selected && wallet.is_open(); + let current = if let Some(content) = &self.wallet_content { + content.wallet.get_config().id == config.id && wallet.is_open() + } else { + false + }; // Draw round background. let mut rect = ui.available_rect_before_wrap(); @@ -491,26 +516,25 @@ impl WalletsContent { if !wallet.is_open() { // Show button to open closed wallet. View::item_button(ui, View::item_rounding(0, 1, true), FOLDER_OPEN, None, || { - self.show_opening_modal(id, None, cb); + self.show_opening_modal(wallet.clone(), None, cb); }); // Show button to select connection if not syncing. if !wallet.syncing() { View::item_button(ui, Rounding::default(), GLOBE, None, || { - self.wallets.select(Some(id)); - self.show_connection_selector_modal(wallet); + self.wallet_content = Some(WalletContent::new(wallet.clone(), None)); + self.show_connection_selection_modal(wallet); }); } } else { - if !is_selected { + if !current { // Show button to select opened wallet. View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, None, || { - self.wallets.select(Some(id)); - self.wallet_content = WalletContent::new(None); + self.wallet_content = Some(WalletContent::new(wallet.clone(), None)); }); } // Show button to close opened wallet. if !wallet.is_closing() { - View::item_button(ui, if !is_selected { + View::item_button(ui, if !current { Rounding::default() } else { View::item_rounding(0, 1, true) @@ -526,7 +550,7 @@ impl WalletsContent { ui.vertical(|ui| { ui.add_space(3.0); // Show wallet name text. - let name_color = if is_selected { + let name_color = if current { Colors::white_or_black(true) } else { Colors::title(false) @@ -537,14 +561,16 @@ impl WalletsContent { }); // Show wallet status text. - View::ellipsize_text(ui, status_text(wallet), 15.0, Colors::text(false)); + View::ellipsize_text(ui, wallet_status_text(wallet), 15.0, Colors::text(false)); ui.add_space(1.0); // Show wallet connection text. - let conn_text = if let Some(conn) = wallet.get_current_ext_conn() { - format!("{} {}", GLOBE_SIMPLE, conn.url) - } else { - format!("{} {}", COMPUTER_TOWER, t!("network.node")) + let connection = wallet.get_current_connection(); + let conn_text = match connection { + ConnectionMethod::Integrated => { + format!("{} {}", COMPUTER_TOWER, t!("network.node")) + } + ConnectionMethod::External(_, url) => format!("{} {}", GLOBE_SIMPLE, url) }; ui.label(RichText::new(conn_text).size(15.0).color(Colors::gray())); ui.add_space(3.0); @@ -554,21 +580,23 @@ impl WalletsContent { } /// Show [`Modal`] to select connection for the wallet. - fn show_connection_selector_modal(&mut self, wallet: &Wallet) { - let ext_conn = wallet.get_current_ext_conn(); + fn show_connection_selection_modal(&mut self, wallet: &Wallet) { + let ext_conn = wallet.get_current_connection(); self.conn_selection_content = Some(WalletConnectionModal::new(ext_conn)); // Show modal. - Modal::new(CONNECTION_SELECTION_MODAL) + Modal::new(SELECT_CONNECTION_MODAL) .position(ModalPosition::CenterTop) .title(t!("wallets.conn_method")) .show(); } /// Show [`Modal`] to select and open wallet. - fn show_opening_modal(&mut self, id: i64, data: Option, cb: &dyn PlatformCallbacks) { - self.wallets.select(Some(id)); - self.open_wallet_content = Some(OpenWalletModal::new(data)); - // Show modal. + fn show_opening_modal(&mut self, + wallet: Wallet, + data: Option, + cb: &dyn PlatformCallbacks) { + self.wallet_content = Some(WalletContent::new(wallet.clone(), None)); + self.open_wallet_content = Some(OpenWalletModal::new(wallet, data)); Modal::new(OPEN_WALLET_MODAL) .position(ModalPosition::CenterTop) .title(t!("wallets.open")) diff --git a/src/gui/views/wallets/creation/creation.rs b/src/gui/views/wallets/creation/creation.rs index eaf3621..4b5b2a7 100644 --- a/src/gui/views/wallets/creation/creation.rs +++ b/src/gui/views/wallets/creation/creation.rs @@ -17,10 +17,10 @@ use egui::scroll_area::ScrollBarVisibility; use grin_util::ZeroingString; use crate::gui::Colors; -use crate::gui::icons::{CHECK, CLIPBOARD_TEXT, COPY, FOLDER_PLUS, SCAN, SHARE_FAT}; +use crate::gui::icons::{CHECK, CLIPBOARD_TEXT, COPY, SCAN}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, Content, View}; -use crate::gui::views::types::{ModalPosition, TextEditOptions}; +use crate::gui::views::{Modal, Content, View, CameraScanModal}; +use crate::gui::views::types::{ModalContainer, ModalPosition, QrScanResult}; use crate::gui::views::wallets::creation::MnemonicSetup; use crate::gui::views::wallets::creation::types::Step; use crate::gui::views::wallets::ConnectionSettings; @@ -30,77 +30,115 @@ use crate::wallet::types::PhraseMode; /// Wallet creation content. pub struct WalletCreation { - /// Wallet creation step. - step: Option, + /// Wallet name. + pub name: String, + /// Wallet password. + pub pass: ZeroingString, - /// Flag to check if wallet creation [`Modal`] was just opened to focus on first field. - modal_just_opened: bool, - /// Wallet name value. - name_edit: String, - /// Password to encrypt created wallet. - pass_edit: String, + /// Wallet creation step. + step: Step, + + /// QR code scanning [`Modal`] content. + scan_modal_content: Option, /// Mnemonic phrase setup content. - pub(crate) mnemonic_setup: MnemonicSetup, + mnemonic_setup: MnemonicSetup, /// Network setup content. - pub(crate) network_setup: ConnectionSettings, + network_setup: ConnectionSettings, /// Flag to check if an error occurred during wallet creation. creation_error: Option, + + /// [`Modal`] identifiers allowed at this ui container. + modal_ids: Vec<&'static str> } -impl Default for WalletCreation { - fn default() -> Self { - Self { - step: None, - modal_just_opened: true, - name_edit: String::from(""), - pass_edit: String::from(""), - mnemonic_setup: MnemonicSetup::default(), - network_setup: ConnectionSettings::default(), - creation_error: None, +const QR_CODE_PHRASE_SCAN_MODAL: &'static str = "qr_code_rec_phrase_scan_modal"; + +impl ModalContainer for WalletCreation { + fn modal_ids(&self) -> &Vec<&'static str> { + &self.modal_ids + } + + fn modal_ui(&mut self, + ui: &mut egui::Ui, + modal: &Modal, + cb: &dyn PlatformCallbacks) { + match modal.id { + QR_CODE_PHRASE_SCAN_MODAL => { + if let Some(content) = self.scan_modal_content.as_mut() { + content.ui(ui, modal, cb, |result| { + match result { + QrScanResult::Text(text) => { + self.mnemonic_setup.mnemonic.import(&text); + modal.close(); + } + QrScanResult::SeedQR(text) => { + self.mnemonic_setup.mnemonic.import(&text); + modal.close(); + } + _ => {} + } + }); + } + }, + + _ => {} } } } impl WalletCreation { - /// Wallet name/password input modal identifier. - pub const NAME_PASS_MODAL: &'static str = "name_pass_modal"; + /// Create new wallet creation instance from name and password. + pub fn new(name: String, pass: ZeroingString) -> Self { + Self { + name, + pass, + step: Step::EnterMnemonic, + scan_modal_content: None, + mnemonic_setup: MnemonicSetup::default(), + network_setup: ConnectionSettings::default(), + creation_error: None, + modal_ids: vec![ + QR_CODE_PHRASE_SCAN_MODAL + ], + } + } /// Draw wallet creation content. pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks, - on_create: impl FnOnce(Wallet)) { - // Show wallet creation step description and confirmation panel. - if self.step.is_some() { - egui::TopBottomPanel::bottom("wallet_creation_step_panel") - .frame(egui::Frame { - fill: Colors::fill(), - inner_margin: Margin { - left: View::far_left_inset_margin(ui) + 8.0, - right: View::get_right_inset() + 8.0, - top: 4.0, - bottom: View::get_bottom_inset(), - }, - ..Default::default() - }) - .show_inside(ui, |ui| { - ui.vertical_centered(|ui| { - ui.vertical_centered(|ui| { - View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 2.0, |ui| { - self.step_control_ui(ui, on_create, cb); - }); - }); + on_create: impl FnMut(Wallet)) { + self.current_modal_ui(ui, cb); + // Show wallet creation step description and confirmation panel. + egui::TopBottomPanel::bottom("wallet_creation_step_panel") + .frame(egui::Frame { + stroke: View::item_stroke(), + fill: Colors::fill_deep(), + inner_margin: Margin { + left: View::far_left_inset_margin(ui) + 8.0, + right: View::get_right_inset() + 8.0, + top: 4.0, + bottom: View::get_bottom_inset(), + }, + ..Default::default() + }) + .show_inside(ui, |ui| { + ui.vertical_centered(|ui| { + ui.vertical_centered(|ui| { + View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 2.0, |ui| { + self.step_control_ui(ui, on_create, cb); + }); }); + }); - } + }); // Show wallet creation step content panel. egui::CentralPanel::default() .frame(egui::Frame { - stroke: View::item_stroke(), inner_margin: Margin { left: View::far_left_inset_margin(ui) + 4.0, right: View::get_right_inset() + 4.0, @@ -110,18 +148,13 @@ impl WalletCreation { ..Default::default() }) .show_inside(ui, |ui| { - let id = if let Some(step) = &self.step { - format!("creation_step_scroll_{}", step.name()) - } else { - "creation_step_scroll".to_owned() - }; ScrollArea::vertical() - .id_source(id) + .id_source(Id::from(format!("creation_step_scroll_{}", self.step.name()))) .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) .auto_shrink([false; 2]) .show(ui, |ui| { ui.vertical_centered(|ui| { - let max_width = if self.step == Some(Step::SetupConnection) { + let max_width = if self.step == Step::SetupConnection { Content::SIDE_PANEL_WIDTH * 1.3 } else { Content::SIDE_PANEL_WIDTH * 2.0 @@ -139,56 +172,59 @@ impl WalletCreation { ui: &mut egui::Ui, on_create: impl FnOnce(Wallet), cb: &dyn PlatformCallbacks) { - if let Some(step) = self.step.clone() { - // Setup step description text and availability. - let (step_text, mut step_available) = match step { - Step::EnterMnemonic => { - let mode = &self.mnemonic_setup.mnemonic.mode(); - let (text, available) = match mode { - PhraseMode::Generate => (t!("wallets.create_phrase_desc"), true), - PhraseMode::Import => { - let available = !self.mnemonic_setup.mnemonic.has_empty_or_invalid(); - (t!("wallets.restore_phrase_desc"), available) - } - }; - (text, available) - } - Step::ConfirmMnemonic => { - let text = t!("wallets.restore_phrase_desc"); - let available = !self.mnemonic_setup.mnemonic.has_empty_or_invalid(); - (text, available) - } - Step::SetupConnection => { - (t!("wallets.setup_conn_desc"), self.creation_error.is_none()) - } - }; - - // Show step description or error. - let generate_step = step == Step::EnterMnemonic && - self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate; - if (self.mnemonic_setup.mnemonic.valid() && self.creation_error.is_none()) || - generate_step { - ui.add_space(2.0); - ui.label(RichText::new(step_text).size(16.0).color(Colors::gray())); - ui.add_space(2.0); - } else { - step_available = false; - // Show error text. - if let Some(err) = &self.creation_error { - ui.add_space(10.0); - ui.label(RichText::new(err) - .size(16.0) - .color(Colors::red())); - ui.add_space(10.0); - } else { - ui.add_space(2.0); - ui.label(RichText::new(&t!("wallets.not_valid_phrase")) - .size(16.0) - .color(Colors::red())); - ui.add_space(2.0); + let step = &self.step; + // Setup description and next step availability. + let (step_text, mut next) = match step { + Step::EnterMnemonic => { + let mode = &self.mnemonic_setup.mnemonic.mode(); + let (text, available) = match mode { + PhraseMode::Generate => (t!("wallets.create_phrase_desc"), true), + PhraseMode::Import => { + let available = !self.mnemonic_setup.mnemonic.has_empty_or_invalid(); + (t!("wallets.restore_phrase_desc"), available) + } }; + (text, available) } - if step == Step::EnterMnemonic { + Step::ConfirmMnemonic => { + let text = t!("wallets.restore_phrase_desc"); + let available = !self.mnemonic_setup.mnemonic.has_empty_or_invalid(); + (text, available) + } + Step::SetupConnection => { + (t!("wallets.setup_conn_desc"), self.creation_error.is_none()) + } + }; + + // Show step description or error. + let generate_step = step == &Step::EnterMnemonic && + self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate; + if (self.mnemonic_setup.mnemonic.valid() && self.creation_error.is_none()) || + generate_step { + ui.add_space(2.0); + ui.label(RichText::new(step_text).size(16.0).color(Colors::gray())); + ui.add_space(2.0); + } else { + next = false; + // Show error text. + if let Some(err) = &self.creation_error { + ui.add_space(10.0); + ui.label(RichText::new(err) + .size(16.0) + .color(Colors::red())); + ui.add_space(10.0); + } else { + ui.add_space(2.0); + ui.label(RichText::new(&t!("wallets.not_valid_phrase")) + .size(16.0) + .color(Colors::red())); + ui.add_space(2.0); + }; + } + + // Setup buttons. + match step { + Step::EnterMnemonic => { ui.add_space(4.0); // Setup spacing between buttons. @@ -197,28 +233,56 @@ impl WalletCreation { ui.columns(2, |columns| { // Show copy or paste button for mnemonic phrase step. columns[0].vertical_centered_justified(|ui| { - self.copy_or_paste_button_ui(ui, cb); + match self.mnemonic_setup.mnemonic.mode() { + PhraseMode::Generate => { + // Show copy button. + let c_t = format!("{} {}", COPY, t!("copy").to_uppercase()); + View::button(ui, + c_t.to_uppercase(), + Colors::white_or_black(false), || { + cb.copy_string_to_buffer(self.mnemonic_setup + .mnemonic + .get_phrase()); + }); + } + PhraseMode::Import => { + // Show paste button. + let p_t = format!("{} {}", + CLIPBOARD_TEXT, + t!("paste").to_uppercase()); + View::button(ui, p_t, Colors::white_or_black(false), || { + let data = ZeroingString::from(cb.get_string_from_buffer()); + self.mnemonic_setup.mnemonic.import(&data); + }); + } + } }); - + // Show next step or QR code scan button. columns[1].vertical_centered_justified(|ui| { - if step_available { - // Show next step button if there are no empty words. - self.next_step_button_ui(ui, step, on_create); + if next { + self.next_step_button_ui(ui, on_create); } else { - // Show QR code scan button. let scan_text = format!("{} {}", SCAN, t!("scan").to_uppercase()); View::button(ui, scan_text, Colors::white_or_black(false), || { - self.mnemonic_setup.show_qr_scan_modal(cb); + self.scan_modal_content = Some(CameraScanModal::default()); + // Show QR code scan modal. + Modal::new(QR_CODE_PHRASE_SCAN_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("scan_qr")) + .closeable(false) + .show(); + cb.start_camera(); }); } }); }); ui.add_space(4.0); - } else if step == Step::ConfirmMnemonic { + } + Step::ConfirmMnemonic => { ui.add_space(4.0); // Show next step or paste button. - if step_available { - self.next_step_button_ui(ui, step, on_create); + if next { + self.next_step_button_ui(ui, on_create); } else { let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase()); View::button(ui, paste_text, Colors::white_or_black(false), || { @@ -227,96 +291,64 @@ impl WalletCreation { }); } ui.add_space(4.0); - } else if step_available { - ui.add_space(4.0); - self.next_step_button_ui(ui, step, on_create); - ui.add_space(4.0); } - ui.add_space(4.0); - } - } - - /// Draw copy or paste button at [`Step::EnterMnemonic`]. - fn copy_or_paste_button_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { - match self.mnemonic_setup.mnemonic.mode() { - PhraseMode::Generate => { - // Show copy button. - let c_t = format!("{} {}", COPY, t!("copy").to_uppercase()); - View::button(ui, c_t.to_uppercase(), Colors::white_or_black(false), || { - cb.copy_string_to_buffer(self.mnemonic_setup.mnemonic.get_phrase()); - }); - } - PhraseMode::Import => { - // Show paste button. - let p_t = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase()); - View::button(ui, p_t, Colors::white_or_black(false), || { - let data = ZeroingString::from(cb.get_string_from_buffer()); - self.mnemonic_setup.mnemonic.import(&data); - }); + Step::SetupConnection => { + if next { + ui.add_space(4.0); + self.next_step_button_ui(ui, on_create); + ui.add_space(4.0); + } } } + ui.add_space(3.0); } /// Draw button to go to next [`Step`]. fn next_step_button_ui(&mut self, ui: &mut egui::Ui, - step: Step, on_create: impl FnOnce(Wallet)) { // Setup button text. - let (next_text, text_color, bg_color) = if step == Step::SetupConnection { + let (next_text, text_color, bg_color) = if self.step == Step::SetupConnection { (format!("{} {}", CHECK, t!("complete")), Colors::title(true), Colors::gold()) } else { - let text = format!("{} {}", SHARE_FAT, t!("continue")); - (text, Colors::text_button(), Colors::white_or_black(false)) + (t!("continue"), Colors::text_button(), Colors::white_or_black(false)) }; // Show next step button. View::colored_text_button(ui, next_text.to_uppercase(), text_color, bg_color, || { - self.step = if let Some(step) = &self.step { - match step { - Step::EnterMnemonic => { - if self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate { - Some(Step::ConfirmMnemonic) - } else { - Some(Step::SetupConnection) - } + self.step = match self.step { + Step::EnterMnemonic => { + if self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate { + Step::ConfirmMnemonic + } else { + Step::SetupConnection } - Step::ConfirmMnemonic => { - Some(Step::SetupConnection) - }, - Step::SetupConnection => { - // Create wallet at last step. - let conn_method = &self.network_setup.method; - match Wallet::create(&self.name_edit, - &self.pass_edit, - &self.mnemonic_setup.mnemonic, - conn_method) { - Ok(mut w) => { - // Open created wallet. - w.open(&self.pass_edit).unwrap(); - // Pass created wallet to callback. - (on_create)(w); - // Reset input data. - self.step = None; - self.name_edit = String::from(""); - self.pass_edit = String::from(""); - self.mnemonic_setup.reset(); - None - - } - Err(e) => { - self.creation_error = Some(format!("{:?}", e)); - Some(Step::SetupConnection) - } + } + Step::ConfirmMnemonic => { + Step::SetupConnection + }, + Step::SetupConnection => { + // Create wallet at last step. + match Wallet::create(&self.name, + &self.pass, + &self.mnemonic_setup.mnemonic, + &self.network_setup.method) { + Ok(w) => { + self.mnemonic_setup.reset(); + // Pass created wallet to callback. + (on_create)(w); + Step::EnterMnemonic + } + Err(e) => { + self.creation_error = Some(format!("{:?}", e)); + Step::SetupConnection } } } - } else { - Some(Step::EnterMnemonic) }; // Check external connections availability on connection setup. - if self.step == Some(Step::SetupConnection) { + if self.step == Step::SetupConnection { ExternalConnection::check_ext_conn_availability(None); } }); @@ -325,152 +357,31 @@ impl WalletCreation { /// Draw wallet creation [`Step`] content. fn step_content_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { match &self.step { - None => { - // Show wallet creation message if step is empty. - View::center_content(ui, 350.0 + View::get_bottom_inset(), |ui| { - // Show app logo. - View::app_logo_name_version(ui); - ui.add_space(4.0); - - let text = t!("wallets.create_desc"); - ui.label(RichText::new(text) - .size(16.0) - .color(Colors::gray()) - ); - ui.add_space(8.0); - // Show wallet creation button. - let add_text = format!("{} {}", FOLDER_PLUS, t!("wallets.add")); - View::button(ui, add_text, Colors::white_or_black(false), || { - self.show_name_pass_modal(cb); - }); - }); - } - Some(step) => { - match step { - Step::EnterMnemonic => self.mnemonic_setup.ui(ui, cb), - Step::ConfirmMnemonic => self.mnemonic_setup.confirm_ui(ui, cb), - Step::SetupConnection => { - // Redraw if node is running. - if Node::is_running() { - ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY); - } - self.network_setup.create_ui(ui, cb) - } + Step::EnterMnemonic => self.mnemonic_setup.ui(ui, cb), + Step::ConfirmMnemonic => self.mnemonic_setup.confirm_ui(ui, cb), + Step::SetupConnection => { + // Redraw if node is running. + if Node::is_running() { + ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY); } + self.network_setup.create_ui(ui, cb) } } } - /// Check if it's possible to go back for current step. - pub fn can_go_back(&self) -> bool { - self.step.is_some() - } - - /// Back to previous wallet creation [`Step`]. - pub fn back(&mut self) { + /// Back to previous wallet creation [`Step`], return `true` to close creation. + pub fn on_back(&mut self) -> bool { match &self.step { - None => {} - Some(step) => { - match step { - Step::EnterMnemonic => { - self.step = None; - self.name_edit = String::from(""); - self.pass_edit = String::from(""); - self.mnemonic_setup.reset(); - self.creation_error = None; - }, - Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic), - Step::SetupConnection => { - self.creation_error = None; - self.step = Some(Step::EnterMnemonic) - } - } + Step::ConfirmMnemonic => { + self.step = Step::EnterMnemonic; + false + }, + Step::SetupConnection => { + self.creation_error = None; + self.step = Step::EnterMnemonic; + false } + _ => true } } - - /// Start wallet creation from showing [`Modal`] to enter name and password. - pub fn show_name_pass_modal(&mut self, cb: &dyn PlatformCallbacks) { - // Reset modal values. - self.modal_just_opened = true; - self.name_edit = t!("wallets.default_wallet"); - self.pass_edit = String::from(""); - // Show modal. - Modal::new(Self::NAME_PASS_MODAL) - .position(ModalPosition::CenterTop) - .title(t!("wallets.add")) - .show(); - cb.show_keyboard(); - } - - /// Draw creating wallet name/password input [`Modal`] content. - pub fn name_pass_modal_ui(&mut self, - ui: &mut egui::Ui, - modal: &Modal, - cb: &dyn PlatformCallbacks) { - ui.add_space(6.0); - ui.vertical_centered(|ui| { - ui.label(RichText::new(t!("wallets.name")) - .size(17.0) - .color(Colors::gray())); - ui.add_space(8.0); - - // Show wallet name text edit. - let mut name_edit_opts = TextEditOptions::new(Id::from(modal.id).with("name")) - .no_focus(); - if self.modal_just_opened { - self.modal_just_opened = false; - name_edit_opts.focus = true; - } - View::text_edit(ui, cb, &mut self.name_edit, &mut name_edit_opts); - ui.add_space(8.0); - - ui.label(RichText::new(t!("wallets.pass")) - .size(17.0) - .color(Colors::gray())); - ui.add_space(8.0); - - // Draw wallet password text edit. - let mut pass_text_edit_opts = TextEditOptions::new(Id::from(modal.id).with("pass")) - .password() - .no_focus(); - View::text_edit(ui, cb, &mut self.pass_edit, &mut pass_text_edit_opts); - ui.add_space(12.0); - }); - - // Show modal buttons. - ui.scope(|ui| { - // Setup spacing between buttons. - ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - - ui.columns(2, |columns| { - columns[0].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { - // Close modal. - cb.hide_keyboard(); - modal.close(); - }); - }); - columns[1].vertical_centered_justified(|ui| { - let mut on_next = || { - // Check if input values are not empty. - if self.name_edit.is_empty() || self.pass_edit.is_empty() { - return; - } - self.step = Some(Step::EnterMnemonic); - cb.hide_keyboard(); - modal.close(); - }; - - // Go to next creation step on Enter button press. - View::on_enter_key(ui, || { - (on_next)(); - }); - - View::button(ui, t!("continue"), Colors::white_or_black(false), on_next); - }); - }); - ui.add_space(6.0); - }); - } -} +} \ No newline at end of file diff --git a/src/gui/views/wallets/creation/mnemonic.rs b/src/gui/views/wallets/creation/mnemonic.rs index b4cdc37..abfae80 100644 --- a/src/gui/views/wallets/creation/mnemonic.rs +++ b/src/gui/views/wallets/creation/mnemonic.rs @@ -17,8 +17,8 @@ use egui::{Id, RichText}; use crate::gui::Colors; use crate::gui::icons::PENCIL; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{CameraContent, Modal, Content, View}; -use crate::gui::views::types::{ModalContainer, ModalPosition, QrScanResult, TextEditOptions}; +use crate::gui::views::{Modal, Content, View}; +use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions}; use crate::wallet::Mnemonic; use crate::wallet::types::{PhraseMode, PhraseSize, PhraseWord}; @@ -34,11 +34,6 @@ pub struct MnemonicSetup { /// Flag to check if entered word is valid at [`Modal`]. valid_word_edit: bool, - /// Camera content for QR scan [`Modal`]. - camera_content: CameraContent, - /// Flag to check if recovery phrase was found at QR code scanning [`Modal`]. - scan_phrase_not_found: Option, - /// [`Modal`] identifiers allowed at this ui container. modal_ids: Vec<&'static str> } @@ -46,9 +41,6 @@ pub struct MnemonicSetup { /// Identifier for word input [`Modal`]. pub const WORD_INPUT_MODAL: &'static str = "word_input_modal"; -/// Identifier for QR code recovery phrase scan [`Modal`]. -const QR_CODE_PHRASE_SCAN_MODAL: &'static str = "qr_code_rec_phrase_scan_modal"; - impl Default for MnemonicSetup { fn default() -> Self { Self { @@ -56,11 +48,8 @@ impl Default for MnemonicSetup { word_index_edit: 0, word_edit: String::from(""), valid_word_edit: true, - camera_content: CameraContent::default(), - scan_phrase_not_found: None, modal_ids: vec![ - WORD_INPUT_MODAL, - QR_CODE_PHRASE_SCAN_MODAL + WORD_INPUT_MODAL ] } } @@ -77,7 +66,6 @@ impl ModalContainer for MnemonicSetup { cb: &dyn PlatformCallbacks) { match modal.id { WORD_INPUT_MODAL => self.word_modal_ui(ui, modal, cb), - QR_CODE_PHRASE_SCAN_MODAL => self.scan_qr_modal_ui(ui, modal, cb), _ => {} } } @@ -325,85 +313,6 @@ impl MnemonicSetup { ui.add_space(6.0); }); } - - /// Show QR code recovery phrase scanner [`Modal`]. - pub fn show_qr_scan_modal(&mut self, cb: &dyn PlatformCallbacks) { - self.scan_phrase_not_found = None; - // Show QR code scan modal. - Modal::new(QR_CODE_PHRASE_SCAN_MODAL) - .position(ModalPosition::CenterTop) - .title(t!("scan_qr")) - .closeable(false) - .show(); - cb.start_camera(); - } - - /// Draw QR code scan [`Modal`] content. - fn scan_qr_modal_ui(&mut self, - ui: &mut egui::Ui, - modal: &Modal, - cb: &dyn PlatformCallbacks) { - // Show scan result if exists or show camera content while scanning. - if let Some(_) = &self.scan_phrase_not_found { - ui.add_space(6.0); - ui.vertical_centered(|ui| { - ui.label(RichText::new(t!("wallets.rec_phrase_not_found")) - .size(17.0) - .color(Colors::red())); - }); - ui.add_space(6.0); - } else if let Some(result) = self.camera_content.qr_scan_result() { - cb.stop_camera(); - self.camera_content.clear_state(); - match &result { - QrScanResult::Text(text) => { - self.mnemonic.import(text); - if self.mnemonic.valid() { - modal.close(); - return; - } - } - _ => {} - } - - // Set an error when found phrase was not valid. - self.scan_phrase_not_found = Some(true); - Modal::set_title(t!("scan_result")); - } else { - ui.add_space(6.0); - self.camera_content.ui(ui, cb); - ui.add_space(6.0); - } - - if self.scan_phrase_not_found.is_some() { - // Setup spacing between buttons. - ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - - ui.columns(2, |columns| { - columns[0].vertical_centered_justified(|ui| { - View::button(ui, t!("close"), Colors::white_or_black(false), || { - self.scan_phrase_not_found = None; - modal.close(); - }); - }); - columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("repeat"), Colors::white_or_black(false), || { - Modal::set_title(t!("scan_qr")); - self.scan_phrase_not_found = None; - cb.start_camera(); - }); - }); - }); - } else { - ui.vertical_centered_justified(|ui| { - View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { - cb.stop_camera(); - modal.close(); - }); - }); - } - ui.add_space(6.0); - } } /// Calculate word list columns count based on available ui width. diff --git a/src/gui/views/wallets/modals/add.rs b/src/gui/views/wallets/modals/add.rs new file mode 100644 index 0000000..4975021 --- /dev/null +++ b/src/gui/views/wallets/modals/add.rs @@ -0,0 +1,116 @@ +// 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::{Id, RichText}; +use grin_util::ZeroingString; + +use crate::gui::Colors; +use crate::gui::platform::PlatformCallbacks; +use crate::gui::views::{Modal, View}; +use crate::gui::views::types::TextEditOptions; + +/// Initial wallet creation [`Modal`] content. +pub struct AddWalletModal { + /// Flag to check if it's first draw to focus on first field. + first_draw: bool, + /// Wallet name. + pub name_edit: String, + /// Password to encrypt created wallet. + pub pass_edit: String, +} + +impl Default for AddWalletModal { + fn default() -> Self { + Self { + first_draw: true, + name_edit: t!("wallets.default_wallet"), + pass_edit: "".to_string(), + } + } +} + +impl AddWalletModal { + /// Draw creating wallet name/password input [`Modal`] content. + pub fn ui(&mut self, + ui: &mut egui::Ui, + modal: &Modal, + cb: &dyn PlatformCallbacks, + mut on_input: impl FnMut(String, ZeroingString)) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("wallets.name")) + .size(17.0) + .color(Colors::gray())); + ui.add_space(8.0); + + // Show wallet name text edit. + let mut name_edit_opts = TextEditOptions::new(Id::from(modal.id).with("name")) + .no_focus(); + if self.first_draw { + self.first_draw = false; + name_edit_opts.focus = true; + } + View::text_edit(ui, cb, &mut self.name_edit, &mut name_edit_opts); + ui.add_space(8.0); + + ui.label(RichText::new(t!("wallets.pass")) + .size(17.0) + .color(Colors::gray())); + ui.add_space(8.0); + + // Draw wallet password text edit. + let mut pass_text_edit_opts = TextEditOptions::new(Id::from(modal.id).with("pass")) + .password() + .no_focus(); + View::text_edit(ui, cb, &mut self.pass_edit, &mut pass_text_edit_opts); + ui.add_space(12.0); + }); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { + // Close modal. + cb.hide_keyboard(); + modal.close(); + }); + }); + columns[1].vertical_centered_justified(|ui| { + let mut on_next = || { + let name = self.name_edit.clone(); + let pass = self.pass_edit.clone(); + if name.is_empty() || pass.is_empty() { + return; + } + cb.hide_keyboard(); + modal.close(); + on_input(name, ZeroingString::from(pass)); + }; + + // Go to next creation step on Enter button press. + View::on_enter_key(ui, || { + (on_next)(); + }); + + View::button(ui, t!("continue"), Colors::white_or_black(false), on_next); + }); + }); + ui.add_space(6.0); + }); + } +} \ No newline at end of file diff --git a/src/gui/views/wallets/modals/conn.rs b/src/gui/views/wallets/modals/conn.rs index b048305..19f3c0a 100644 --- a/src/gui/views/wallets/modals/conn.rs +++ b/src/gui/views/wallets/modals/conn.rs @@ -22,27 +22,24 @@ use crate::gui::views::{Modal, View}; use crate::gui::views::network::ConnectionsContent; use crate::gui::views::network::modals::ExternalConnectionModal; use crate::wallet::{ConnectionsConfig, ExternalConnection}; +use crate::wallet::types::ConnectionMethod; -/// Wallet connection [`Modal`] content. +/// Wallet connection selection [`Modal`] content. pub struct WalletConnectionModal { - /// Current external connection. - pub ext_conn: Option, + /// Current connection method. + pub conn: ConnectionMethod, - /// Flag to show connection creation. - show_conn_creation: bool, - - /// External connection creation content. - add_ext_conn_content: ExternalConnectionModal + /// External connection content. + ext_conn_content: Option } impl WalletConnectionModal { /// Create from provided wallet connection. - pub fn new(ext_conn: Option) -> Self { + pub fn new(conn: ConnectionMethod) -> Self { ExternalConnection::check_ext_conn_availability(None); Self { - ext_conn, - show_conn_creation: false, - add_ext_conn_content: ExternalConnectionModal::new(None), + conn, + ext_conn_content: None, } } @@ -51,17 +48,17 @@ impl WalletConnectionModal { ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks, - on_select: impl Fn(Option)) { - ui.add_space(4.0); - - // Draw external connection creation content. - if self.show_conn_creation { - self.add_ext_conn_content.ui(ui, cb, modal, |conn| { - on_select(Some(conn.id)); + on_select: impl Fn(ConnectionMethod)) { + // Draw external connection content. + if let Some(ext_content) = self.ext_conn_content.as_mut() { + ext_content.ui(ui, cb, modal, |conn| { + on_select(ConnectionMethod::External(conn.id, conn.url)); }); return; } + ui.add_space(4.0); + let ext_conn_list = ConnectionsConfig::ext_conn_list(); ScrollArea::vertical() .max_height(if ext_conn_list.len() < 4 { @@ -77,52 +74,54 @@ impl WalletConnectionModal { // Show integrated node selection. ConnectionsContent::integrated_node_item_ui(ui, |ui| { - let is_current_method = self.ext_conn.is_none(); - if !is_current_method { - View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || { - self.ext_conn = None; - on_select(None); - modal.close(); - }); - } else { - ui.add_space(14.0); - ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green())); - ui.add_space(14.0); + match self.conn { + ConnectionMethod::Integrated => { + ui.add_space(14.0); + ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green())); + ui.add_space(14.0); + } + _ => { + View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || { + on_select(ConnectionMethod::Integrated); + modal.close(); + }); + } } }); - // Show button to add new external node connection. ui.add_space(8.0); ui.vertical_centered(|ui| { 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!("{} {}", PLUS_CIRCLE, t!("wallets.add_node")); View::button(ui, add_node_text, Colors::button(), || { - self.show_conn_creation = true; + self.ext_conn_content = Some(ExternalConnectionModal::new(None)); }); }); ui.add_space(4.0); if !ext_conn_list.is_empty() { ui.add_space(8.0); - for (index, conn) in ext_conn_list.iter().enumerate() { + for (index, conn) in ext_conn_list.iter().filter(|c| !c.deleted).enumerate() { + if conn.deleted { + continue; + } ui.horizontal_wrapped(|ui| { - // Draw external connection item. let len = ext_conn_list.len(); ConnectionsContent::ext_conn_item_ui(ui, conn, index, len, |ui| { - // Draw button to select connection. - let is_current_method = if let Some(c) = self.ext_conn.as_ref() { - c.id == conn.id - } else { - false + let current_ext_conn = match self.conn { + ConnectionMethod::Integrated => false, + ConnectionMethod::External(id, _) => id == conn.id }; - if !is_current_method { + if !current_ext_conn { let button_rounding = View::item_rounding(index, len, true); View::item_button(ui, button_rounding, CHECK, None, || { - self.ext_conn = Some(conn.clone()); - on_select(Some(conn.id)); + on_select( + ConnectionMethod::External(conn.id, conn.url.clone()) + ); modal.close(); }); } else { diff --git a/src/gui/views/wallets/modals/mod.rs b/src/gui/views/wallets/modals/mod.rs index bdb7bb6..615497b 100644 --- a/src/gui/views/wallets/modals/mod.rs +++ b/src/gui/views/wallets/modals/mod.rs @@ -19,4 +19,7 @@ mod wallets; pub use wallets::*; mod open; -pub use open::*; \ No newline at end of file +pub use open::*; + +mod add; +pub use add::*; \ No newline at end of file diff --git a/src/gui/views/wallets/modals/open.rs b/src/gui/views/wallets/modals/open.rs index c585d99..6e8c64b 100644 --- a/src/gui/views/wallets/modals/open.rs +++ b/src/gui/views/wallets/modals/open.rs @@ -13,15 +13,19 @@ // limitations under the License. use egui::{Id, RichText}; +use grin_util::ZeroingString; use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, View}; use crate::gui::views::types::TextEditOptions; -use crate::wallet::WalletList; +use crate::wallet::Wallet; /// Wallet opening [`Modal`] content. pub struct OpenWalletModal { + /// Wallet to open. + wallet: Wallet, + /// Password to open wallet. pass_edit: String, /// Flag to check if wrong password was entered. @@ -33,8 +37,9 @@ pub struct OpenWalletModal { impl OpenWalletModal { /// Create new content instance. - pub fn new(data: Option) -> Self { + pub fn new(wallet: Wallet, data: Option) -> Self { Self { + wallet, pass_edit: "".to_string(), wrong_pass: false, data, @@ -44,9 +49,8 @@ impl OpenWalletModal { pub fn ui(&mut self, ui: &mut egui::Ui, modal: &Modal, - wallets: &mut WalletList, cb: &dyn PlatformCallbacks, - mut on_continue: impl FnMut(Option)) { + mut on_continue: impl FnMut(Wallet, Option)) { ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("wallets.pass")) @@ -90,18 +94,16 @@ impl OpenWalletModal { columns[1].vertical_centered_justified(|ui| { // Callback for button to continue. let mut on_continue = || { - if self.pass_edit.is_empty() { + let pass = self.pass_edit.clone(); + if pass.is_empty() { return; } - match wallets.open_selected(&self.pass_edit) { + match self.wallet.open(ZeroingString::from(pass)) { Ok(_) => { - // Clear values. self.pass_edit = "".to_string(); - self.wrong_pass = false; - // Close modal. cb.hide_keyboard(); modal.close(); - on_continue(self.data.clone()); + on_continue(self.wallet.clone(), self.data.clone()); } Err(_) => self.wrong_pass = true } diff --git a/src/gui/views/wallets/modals/wallets.rs b/src/gui/views/wallets/modals/wallets.rs index b8b846b..f76a8f5 100644 --- a/src/gui/views/wallets/modals/wallets.rs +++ b/src/gui/views/wallets/modals/wallets.rs @@ -21,13 +21,14 @@ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, View}; use crate::gui::views::types::ModalPosition; use crate::gui::views::wallets::modals::OpenWalletModal; -use crate::gui::views::wallets::wallet::types::status_text; +use crate::gui::views::wallets::wallet::types::wallet_status_text; use crate::wallet::{Wallet, WalletList}; +use crate::wallet::types::ConnectionMethod; /// Wallet list [`Modal`] content pub struct WalletsModal { /// Selected wallet id. - selected: Option, + selected_id: Option, /// Optional data to pass after wallet selection. data: Option, @@ -40,24 +41,21 @@ pub struct WalletsModal { impl WalletsModal { /// Create new content instance. - pub fn new(selected: Option, data: Option, can_open: bool) -> Self { - Self { selected, data, can_open, open_wallet_content: None } + pub fn new(selected_id: Option, data: Option, can_open: bool) -> Self { + Self { selected_id, data, can_open, open_wallet_content: None } } /// Draw content. pub fn ui(&mut self, ui: &mut egui::Ui, modal: &Modal, - wallets: &mut WalletList, + wallets: &WalletList, cb: &dyn PlatformCallbacks, - mut on_select: impl FnMut(i64, Option)) { - // Draw wallet opening content if requested. + mut on_select: impl FnMut(Wallet, Option)) { + // Draw wallet opening modal content. if let Some(open_content) = self.open_wallet_content.as_mut() { - open_content.ui(ui, modal, wallets, cb, |data| { - modal.close(); - if let Some(id) = self.selected { - on_select(id, data); - } + open_content.ui(ui, modal, cb, |wallet, data| { + on_select(wallet, data); self.data = None; }); return; @@ -73,11 +71,11 @@ impl WalletsModal { ui.add_space(2.0); ui.vertical_centered(|ui| { let data = self.data.clone(); - for wallet in wallets.clone().list() { + for wallet in wallets.list() { // Draw wallet list item. - self.wallet_item_ui(ui, wallet, wallets, |id| { + self.wallet_item_ui(ui, wallet, || { modal.close(); - on_select(id, data.clone()); + on_select(wallet.clone(), data.clone()); }); ui.add_space(5.0); } @@ -102,8 +100,7 @@ impl WalletsModal { fn wallet_item_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, - wallets: &mut WalletList, - mut select: impl FnMut(i64)) { + on_select: impl FnOnce()) { let config = wallet.get_config(); let id = config.id; @@ -122,24 +119,24 @@ impl WalletsModal { FOLDER_OPEN }; View::item_button(ui, View::item_rounding(0, 1, true), icon, None, || { - wallets.select(Some(id)); if wallet.is_open() { - select(id); + on_select(); } else { - self.selected = wallets.selected_id; Modal::change_position(ModalPosition::CenterTop); - self.open_wallet_content = Some(OpenWalletModal::new(self.data.clone())); + self.open_wallet_content = Some( + OpenWalletModal::new(wallet.clone(), self.data.clone()) + ); } }); } else { // Draw button to select wallet. - let current = self.selected.unwrap_or(0) == id; + let current = self.selected_id.unwrap_or(0) == id; if current { ui.add_space(12.0); ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green())); } else { View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || { - select(id); + on_select(); }); } } @@ -156,17 +153,19 @@ impl WalletsModal { }); // Show wallet connection text. - let conn = if let Some(conn) = wallet.get_current_ext_conn() { - format!("{} {}", GLOBE_SIMPLE, conn.url) - } else { - format!("{} {}", COMPUTER_TOWER, t!("network.node")) + let connection = wallet.get_current_connection(); + let conn_text = match connection { + ConnectionMethod::Integrated => { + format!("{} {}", COMPUTER_TOWER, t!("network.node")) + } + ConnectionMethod::External(_, url) => format!("{} {}", GLOBE_SIMPLE, url) }; - View::ellipsize_text(ui, conn, 15.0, Colors::text(false)); + ui.label(RichText::new(conn_text).size(15.0).color(Colors::text(false))); ui.add_space(1.0); // Show wallet API text or open status. if self.can_open { - ui.label(RichText::new(status_text(wallet)) + ui.label(RichText::new(wallet_status_text(wallet)) .size(15.0) .color(Colors::gray())); } else { diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index f235666..fd21394 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -21,23 +21,25 @@ use crate::AppConfig; use crate::gui::Colors; use crate::gui::icons::{ARROWS_CLOCKWISE, BRIDGE, CHAT_CIRCLE_TEXT, FOLDER_USER, GEAR_FINE, GRAPH, PACKAGE, POWER, SCAN, SPINNER, USERS_THREE}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, Content, View}; +use crate::gui::views::{Modal, Content, View, CameraScanModal}; use crate::gui::views::types::{ModalPosition, QrScanResult}; use crate::gui::views::wallets::{WalletTransactions, WalletMessages, WalletTransport}; use crate::gui::views::wallets::types::{GRIN, WalletTab, WalletTabType}; -use crate::gui::views::wallets::wallet::modals::{WalletAccountsModal, WalletScanModal}; +use crate::gui::views::wallets::wallet::modals::WalletAccountsModal; use crate::gui::views::wallets::wallet::WalletSettings; use crate::node::Node; use crate::wallet::{Wallet, WalletConfig}; -use crate::wallet::types::WalletData; +use crate::wallet::types::{ConnectionMethod, WalletData}; -/// Selected and opened wallet content. +/// Wallet content. pub struct WalletContent { + /// Selected and opened wallet. + pub wallet: Wallet, + /// Wallet accounts [`Modal`] content. accounts_modal_content: Option, - /// QR code scan [`Modal`] content. - scan_modal_content: Option, + scan_modal_content: Option, /// Current tab content to show. pub current_tab: Box, @@ -51,37 +53,41 @@ const QR_CODE_SCAN_MODAL: &'static str = "qr_code_scan_modal"; impl WalletContent { /// Create new instance with optional data. - pub fn new(data: Option) -> Self { + pub fn new(wallet: Wallet, data: Option) -> Self { let mut content = Self { + wallet, accounts_modal_content: None, scan_modal_content: None, current_tab: Box::new(WalletTransactions::default()), }; - // Provide data to messages. if data.is_some() { - content.current_tab = Box::new(WalletMessages::new(data)); + content.on_data(data); } content } + /// Handle data from deeplink or opened file. + pub fn on_data(&mut self, data: Option) { + // Provide data to messages. + self.current_tab = Box::new(WalletMessages::new(data)); + } + /// Draw wallet content. - pub fn ui(&mut self, - ui: &mut egui::Ui, - wallet: &mut Wallet, - cb: &dyn PlatformCallbacks) { - // Show modal content for this ui container. - self.modal_content_ui(ui, wallet, cb); + pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { + self.modal_content_ui(ui, cb); let dual_panel = Content::is_dual_panel_mode(ui); + let wallet = &self.wallet; let data = wallet.get_data(); let data_empty = data.is_none(); + let hide_tabs = Self::block_navigation_on_sync(wallet); // Show wallet balance panel not on Settings tab with selected non-repairing // wallet, when there is no error and data is not empty. let mut show_balance = self.current_tab.get_type() != WalletTabType::Settings && !data_empty && !wallet.sync_error() && !wallet.is_repairing() && !wallet.is_closing(); - if wallet.get_current_ext_conn().is_none() && !Node::is_running() { + if wallet.get_current_connection() == ConnectionMethod::Integrated && !Node::is_running() { show_balance = false; } egui::TopBottomPanel::top(Id::from("wallet_balance").with(wallet.identifier())) @@ -117,13 +123,12 @@ impl WalletContent { } // Draw account info. View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| { - self.account_ui(ui, wallet, data.unwrap(), cb); + self.account_ui(ui, data.unwrap(), cb); }); }); }); // Show wallet tabs panel. - let show_tabs = !Self::block_navigation_on_sync(wallet); egui::TopBottomPanel::bottom("wallet_tabs_content") .frame(egui::Frame { fill: Colors::fill(), @@ -135,7 +140,7 @@ impl WalletContent { }, ..Default::default() }) - .show_animated_inside(ui, show_tabs, |ui| { + .show_animated_inside(ui, !hide_tabs, |ui| { ui.vertical_centered(|ui| { // Draw wallet tabs. View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| { @@ -162,7 +167,7 @@ impl WalletContent { ..Default::default() }) .show_inside(ui, |ui| { - self.current_tab.ui(ui, wallet, cb); + self.current_tab.ui(ui, &self.wallet, cb); }); // Refresh content after 1 second for synced wallet. @@ -173,11 +178,21 @@ impl WalletContent { } } + /// Check when to block tabs navigation on sync progress. + pub fn block_navigation_on_sync(wallet: &Wallet) -> bool { + let sync_error = wallet.sync_error(); + let integrated_node = wallet.get_current_connection() == ConnectionMethod::Integrated; + let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync); + let sync_after_opening = wallet.get_data().is_none() && !wallet.sync_error(); + // Block navigation if wallet is repairing and integrated node is not launching + // and if wallet is closing or syncing after opening when there is no data to show. + (wallet.is_repairing() && (integrated_node_ready || !integrated_node) && !sync_error) + || wallet.is_closing() || (sync_after_opening && + (!integrated_node || integrated_node_ready)) + } + /// Draw [`Modal`] content for this ui container. - fn modal_content_ui(&mut self, - ui: &mut egui::Ui, - wallet: &Wallet, - cb: &dyn PlatformCallbacks) { + fn modal_content_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { match Modal::opened() { None => {} Some(id) => { @@ -185,29 +200,30 @@ impl WalletContent { ACCOUNT_LIST_MODAL => { if let Some(content) = self.accounts_modal_content.as_mut() { Modal::ui(ui.ctx(), |ui, modal| { - content.ui(ui, wallet, modal, cb); + content.ui(ui, &self.wallet, modal, cb); }); } } QR_CODE_SCAN_MODAL => { + let mut success = false; if let Some(content) = self.scan_modal_content.as_mut() { Modal::ui(ui.ctx(), |ui, modal| { - content.ui(ui, wallet, modal, cb, |result| { + content.ui(ui, modal, cb, |result| { match result { QrScanResult::Slatepack(message) => { - modal.close(); + success = true; let msg = Some(message.to_string()); let messages = WalletMessages::new(msg); self.current_tab = Box::new(messages); return; } QrScanResult::Address(receiver) => { - let balance = wallet.get_data() + success = true; + let balance = self.wallet.get_data() .unwrap() .info .amount_currently_spendable; if balance > 0 { - modal.close(); let mut transport = WalletTransport::default(); let rec = Some(receiver.to_string()); transport.show_send_tor_modal(cb, rec); @@ -217,9 +233,15 @@ impl WalletContent { } _ => {} } + if success { + modal.close(); + } }); }); } + if success { + self.scan_modal_content = None; + } } _ => {} } @@ -230,7 +252,6 @@ impl WalletContent { /// Draw wallet account content. fn account_ui(&mut self, ui: &mut egui::Ui, - wallet: &Wallet, data: WalletData, cb: &dyn PlatformCallbacks) { let mut rect = ui.available_rect_before_wrap(); @@ -240,10 +261,9 @@ impl WalletContent { ui.painter().rect(rect, rounding, Colors::button(), View::hover_stroke()); ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { - // Draw button to scan QR code. + // Draw button to show QR code scanner. View::item_button(ui, View::item_rounding(0, 2, true), SCAN, None, || { - self.scan_modal_content = Some(WalletScanModal::default()); - // Show QR code scan modal. + self.scan_modal_content = Some(CameraScanModal::default()); Modal::new(QR_CODE_SCAN_MODAL) .position(ModalPosition::CenterTop) .title(t!("scan_qr")) @@ -254,8 +274,9 @@ impl WalletContent { // Draw button to show list of accounts. View::item_button(ui, View::item_rounding(1, 3, true), USERS_THREE, None, || { - self.accounts_modal_content = Some(WalletAccountsModal::new(wallet.accounts())); - // Show account list modal. + self.accounts_modal_content = Some( + WalletAccountsModal::new(self.wallet.accounts()) + ); Modal::new(ACCOUNT_LIST_MODAL) .position(ModalPosition::CenterTop) .title(t!("wallets.accounts")) @@ -279,7 +300,7 @@ impl WalletContent { ui.add_space(-2.0); // Show account label. - let account = wallet.get_config().account; + let account = self.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") @@ -290,15 +311,15 @@ impl WalletContent { View::ellipsize_text(ui, acc_text, 15.0, Colors::text(false)); // Show confirmed height or sync progress. - let status_text = if !wallet.syncing() { + let status_text = if !self.wallet.syncing() { format!("{} {}", PACKAGE, data.info.last_confirmed_height) } else { - let info_progress = wallet.info_sync_progress(); + let info_progress = self.wallet.info_sync_progress(); if info_progress == 100 || info_progress == 0 { format!("{} {}", SPINNER, t!("wallets.wallet_loading")) } else { - if wallet.is_repairing() { - let rep_progress = wallet.repairing_progress(); + if self.wallet.is_repairing() { + let rep_progress = self.wallet.repairing_progress(); if rep_progress == 0 { format!("{} {}", SPINNER, t!("wallets.wallet_checking")) } else { @@ -315,7 +336,11 @@ impl WalletContent { } } }; - View::animate_text(ui, status_text, 15.0, Colors::gray(), wallet.syncing()); + View::animate_text(ui, + status_text, + 15.0, + Colors::gray(), + self.wallet.syncing()); }) }); }); @@ -367,7 +392,7 @@ impl WalletContent { } else if wallet.is_closing() { Self::sync_progress_ui(ui, wallet); return true; - } else if wallet.get_current_ext_conn().is_none() { + } else if wallet.get_current_connection() == ConnectionMethod::Integrated { if !Node::is_running() || Node::is_stopping() { View::center_content(ui, 108.0, |ui| { View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.5, |ui| { @@ -418,19 +443,6 @@ impl WalletContent { }); } - /// Check when to block tabs navigation on sync progress. - pub fn block_navigation_on_sync(wallet: &Wallet) -> bool { - let sync_error = wallet.sync_error(); - let integrated_node = wallet.get_current_ext_conn().is_none(); - let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync); - let sync_after_opening = wallet.get_data().is_none() && !wallet.sync_error(); - // Block navigation if wallet is repairing and integrated node is not launching - // and if wallet is closing or syncing after opening when there is no data to show. - (wallet.is_repairing() && (integrated_node_ready || !integrated_node) && !sync_error) - || wallet.is_closing() || (sync_after_opening && - (!integrated_node || integrated_node_ready)) - } - /// Draw wallet sync progress content. pub fn sync_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) { View::center_content(ui, 162.0, |ui| { @@ -439,13 +451,13 @@ impl WalletContent { ui.add_space(18.0); // Setup sync progress text. let text = { - let integrated_node = wallet.get_current_ext_conn().is_none(); - let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync); + let int_node = wallet.get_current_connection() == ConnectionMethod::Integrated; + let int_ready = Node::get_sync_status() == Some(SyncStatus::NoSync); let info_progress = wallet.info_sync_progress(); if wallet.is_closing() { t!("wallets.wallet_closing") - } else if integrated_node && !integrated_node_ready { + } else if int_node && !int_ready { t!("wallets.node_loading", "settings" => GEAR_FINE) } else if wallet.is_repairing() { let repair_progress = wallet.repairing_progress(); diff --git a/src/gui/views/wallets/wallet/messages/content.rs b/src/gui/views/wallets/wallet/messages/content.rs index 5c02a60..331930f 100644 --- a/src/gui/views/wallets/wallet/messages/content.rs +++ b/src/gui/views/wallets/wallet/messages/content.rs @@ -74,7 +74,7 @@ impl WalletTab for WalletMessages { WalletTabType::Messages } - fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { + fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { if WalletContent::sync_ui(ui, wallet) { return; } @@ -130,7 +130,7 @@ impl WalletMessages { /// Draw manual wallet transaction interaction content. pub fn ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, cb: &dyn PlatformCallbacks) { if self.first_draw { // Parse provided message on first draw. @@ -158,7 +158,7 @@ impl WalletMessages { /// Draw [`Modal`] content for this ui container. fn modal_content_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, cb: &dyn PlatformCallbacks) { match Modal::opened() { None => {} @@ -244,7 +244,7 @@ impl WalletMessages { /// Draw Slatepack message input content. fn input_slatepack_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, cb: &dyn PlatformCallbacks) { // Setup description text. if !self.message_error.is_empty() { @@ -524,7 +524,6 @@ impl WalletMessages { return; } else if let Some(result) = self.message_camera_content.qr_scan_result() { cb.stop_camera(); - self.message_camera_content.clear_state(); match &result { QrScanResult::Slatepack(text) => { self.message_edit = text.to_string(); diff --git a/src/gui/views/wallets/wallet/messages/request.rs b/src/gui/views/wallets/wallet/messages/request.rs index 6517f7b..0828fcf 100644 --- a/src/gui/views/wallets/wallet/messages/request.rs +++ b/src/gui/views/wallets/wallet/messages/request.rs @@ -62,7 +62,7 @@ impl MessageRequestModal { /// Draw [`Modal`] content. pub fn ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { // Draw transaction information on request result. @@ -147,7 +147,7 @@ impl MessageRequestModal { /// Draw amount input content. fn amount_input_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { ui.vertical_centered(|ui| { @@ -211,7 +211,7 @@ impl MessageRequestModal { } /// Draw loading request content. - fn loading_request_ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, modal: &Modal) { + fn loading_request_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal) { ui.add_space(34.0); ui.vertical_centered(|ui| { View::big_loading_spinner(ui); diff --git a/src/gui/views/wallets/wallet/modals/mod.rs b/src/gui/views/wallets/wallet/modals/mod.rs index 7a350f7..61a805c 100644 --- a/src/gui/views/wallets/wallet/modals/mod.rs +++ b/src/gui/views/wallets/wallet/modals/mod.rs @@ -13,7 +13,4 @@ // limitations under the License. mod accounts; -pub use accounts::*; - -mod scan; -pub use scan::*; \ No newline at end of file +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 f60d26e..60b5362 100644 --- a/src/gui/views/wallets/wallet/settings/common.rs +++ b/src/gui/views/wallets/wallet/settings/common.rs @@ -61,7 +61,7 @@ impl Default for CommonSettings { impl CommonSettings { /// Draw common wallet settings content. - pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { + 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); @@ -146,7 +146,7 @@ impl CommonSettings { /// Draw [`Modal`] content for this ui container. fn modal_content_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, cb: &dyn PlatformCallbacks) { match Modal::opened() { None => {} @@ -176,7 +176,7 @@ impl CommonSettings { /// Draw wallet name [`Modal`] content. fn name_modal_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { ui.add_space(6.0); @@ -230,7 +230,7 @@ impl CommonSettings { /// Draw wallet pass [`Modal`] content. fn pass_modal_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { let wallet_id = wallet.get_config().id; @@ -328,7 +328,7 @@ impl CommonSettings { /// Draw wallet name [`Modal`] content. fn min_conf_modal_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { ui.add_space(6.0); diff --git a/src/gui/views/wallets/wallet/settings/connection.rs b/src/gui/views/wallets/wallet/settings/connection.rs index 1d4bc76..eaf5f64 100644 --- a/src/gui/views/wallets/wallet/settings/connection.rs +++ b/src/gui/views/wallets/wallet/settings/connection.rs @@ -29,9 +29,6 @@ pub struct ConnectionSettings { /// Selected connection method. pub method: ConnectionMethod, - /// Current wallet external connection. - curr_ext_conn: Option, - /// External connection [`Modal`] content. ext_conn_modal: ExternalConnectionModal, @@ -44,7 +41,6 @@ impl Default for ConnectionSettings { ExternalConnection::check_ext_conn_availability(None); Self { method: ConnectionMethod::Integrated, - curr_ext_conn: None, ext_conn_modal: ExternalConnectionModal::new(None), modal_ids: vec![ ExternalConnectionModal::WALLET_ID @@ -74,52 +70,29 @@ impl ModalContainer for ConnectionSettings { impl ConnectionSettings { /// Draw wallet creation setup content. pub fn create_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { - self.ui(ui, None, cb); + self.ui(ui, cb); } /// Draw existing wallet connection setup content. - pub fn wallet_ui(&mut self, ui: &mut egui::Ui, w: &mut Wallet, cb: &dyn PlatformCallbacks) { - // Setup connection value from provided wallet. - match w.get_config().ext_conn_id { - None => self.method = ConnectionMethod::Integrated, - Some(id) => self.method = ConnectionMethod::External(id) - } + pub fn wallet_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { + self.method = wallet.get_current_connection(); // Draw setup content. - self.ui(ui, Some(w), cb); + let changed = self.ui(ui, cb); - // Setup wallet connection value after change. - let changed = match self.method { - ConnectionMethod::Integrated => { - let changed = w.get_current_ext_conn().is_some(); - if changed { - w.update_ext_conn_id(None); - } - changed - } - ConnectionMethod::External(id) => { - let changed = w.get_config().ext_conn_id != Some(id); - if changed { - w.update_ext_conn_id(Some(id)); - } - changed - } - }; - - // Reopen wallet if connection changed. if changed { - if !w.reopen_needed() { - w.set_reopen(true); - w.close(); + wallet.update_connection(&self.method); + // Reopen wallet if connection changed. + if !wallet.reopen_needed() { + wallet.set_reopen(true); + wallet.close(); } } } - /// Draw connection setup content. - fn ui(&mut self, - ui: &mut egui::Ui, - wallet: Option<&Wallet>, - cb: &dyn PlatformCallbacks) { + /// Draw connection setup content, returning `true` if connection was changed. + fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) -> bool { + let mut changed = false; // Draw modal content for current ui container. self.current_modal_ui(ui, cb); @@ -129,14 +102,14 @@ impl ConnectionSettings { ui.add_space(6.0); ui.vertical_centered(|ui| { - // Show integrated node selection. ui.add_space(6.0); + // Show integrated node selection. ConnectionsContent::integrated_node_item_ui(ui, |ui| { - // 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; + changed = true; }); } else { ui.add_space(14.0); @@ -145,7 +118,6 @@ impl ConnectionSettings { } }); - // Show external connections. ui.add_space(8.0); ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::gray())); ui.add_space(6.0); @@ -153,45 +125,58 @@ impl ConnectionSettings { // Show button to add new external node connection. let add_node_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add_node")); View::button(ui, add_node_text, Colors::button(), || { - self.show_add_ext_conn_modal(cb); + self.ext_conn_modal = ExternalConnectionModal::new(None); + Modal::new(ExternalConnectionModal::WALLET_ID) + .position(ModalPosition::CenterTop) + .title(t!("wallets.add_node")) + .show(); + cb.show_keyboard(); }); ui.add_space(4.0); - let mut ext_conn_list = ConnectionsConfig::ext_conn_list(); - - // Check if current external connection was deleted to show at 1st place. - if let Some(wallet) = wallet { - if let Some(conn) = wallet.get_current_ext_conn() { - if ext_conn_list.iter() - .filter(|c| c.id == conn.id) - .collect::>().is_empty() { - if self.curr_ext_conn.is_none() { - self.curr_ext_conn = Some(conn); - } - ext_conn_list.insert(0, self.curr_ext_conn.as_ref().unwrap().clone()); - } + // Check if it's current method. + let is_current = |m: &ConnectionMethod, c: &ExternalConnection| -> Option { + match m { + ConnectionMethod::External(id, _) => if c.deleted && *id == c.id { + None + } else { + Some(*id == c.id) + }, + _ => Some(false) } - } + }; - if !ext_conn_list.is_empty() { + let method = &self.method.clone(); + let ext_conn_list = ConnectionsConfig::ext_conn_list(); + let ext_list = ext_conn_list.iter().filter(|c| { + !c.deleted || is_current(method, c).unwrap_or(true) + }).collect::>(); + let ext_size = ext_list.len(); + if ext_size != 0 { ui.add_space(8.0); - for (index, conn) in ext_conn_list.iter().enumerate() { + + for (i, c) in ext_list.iter().enumerate() { ui.horizontal_wrapped(|ui| { // Draw external connection item. - self.ext_conn_item_ui(ui, wallet, conn, index, ext_conn_list.len()); + let is_current = is_current(method, c); + Self::ext_conn_item_ui(ui, c, is_current, i, ext_size, || { + self.method = ConnectionMethod::External(c.id, c.url.clone()); + changed = true; + }); }); } } }); + changed } /// Draw external connection item content. - fn ext_conn_item_ui(&mut self, - ui: &mut egui::Ui, - wallet: Option<&Wallet>, + fn ext_conn_item_ui(ui: &mut egui::Ui, conn: &ExternalConnection, + is_current: Option, index: usize, - len: usize) { + len: usize, + mut on_select: impl FnMut()) { // Setup layout size. let mut rect = ui.available_rect_before_wrap(); rect.set_height(52.0); @@ -203,24 +188,15 @@ impl ConnectionSettings { 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 = if let Some(wallet) = wallet { - if let Some(cur) = wallet.get_config().ext_conn_id { - cur == conn.id - } else { - false - } - } else { - 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 { + if is_current.unwrap_or(true) { ui.add_space(12.0); ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green())); + } else { + // Draw button to select connection. + let button_rounding = View::item_rounding(index, len, true); + View::item_button(ui, button_rounding, CHECK, None, || { + on_select(); + }); } let layout_size = ui.available_size(); @@ -235,7 +211,11 @@ impl ConnectionSettings { // Setup connection status text. let status_text = if let Some(available) = conn.available { if available { - format!("{} {}", CHECK_CIRCLE, t!("network.available")) + format!("{} {}", CHECK_CIRCLE, if is_current.is_none() { + t!("transport.connected") + } else { + t!("network.available") + }) } else { format!("{} {}", X_CIRCLE, t!("network.not_available")) } @@ -249,15 +229,4 @@ impl ConnectionSettings { }); }); } - - /// Show external connection adding [`Modal`]. - fn show_add_ext_conn_modal(&mut self, cb: &dyn PlatformCallbacks) { - self.ext_conn_modal = ExternalConnectionModal::new(None); - // Show modal. - Modal::new(ExternalConnectionModal::WALLET_ID) - .position(ModalPosition::CenterTop) - .title(t!("wallets.add_node")) - .show(); - cb.show_keyboard(); - } } \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/settings/content.rs b/src/gui/views/wallets/wallet/settings/content.rs index 4a77572..6f9ebae 100644 --- a/src/gui/views/wallets/wallet/settings/content.rs +++ b/src/gui/views/wallets/wallet/settings/content.rs @@ -50,7 +50,7 @@ impl WalletTab for WalletSettings { fn ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, cb: &dyn PlatformCallbacks) { // Show loading progress if navigation is blocked. if WalletContent::block_navigation_on_sync(wallet) { diff --git a/src/gui/views/wallets/wallet/settings/recovery.rs b/src/gui/views/wallets/wallet/settings/recovery.rs index 686d059..e1541cd 100644 --- a/src/gui/views/wallets/wallet/settings/recovery.rs +++ b/src/gui/views/wallets/wallet/settings/recovery.rs @@ -22,6 +22,7 @@ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, View}; use crate::gui::views::types::{ModalPosition, TextEditOptions}; use crate::node::Node; +use crate::wallet::types::ConnectionMethod; use crate::wallet::Wallet; /// Wallet recovery settings content. @@ -51,7 +52,7 @@ impl Default for RecoverySettings { } impl RecoverySettings { - pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { + 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); @@ -63,7 +64,7 @@ impl RecoverySettings { ui.add_space(4.0); ui.vertical_centered(|ui| { - let integrated_node = wallet.get_current_ext_conn().is_none(); + let integrated_node = wallet.get_current_connection() == ConnectionMethod::Integrated; let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync); if wallet.sync_error() || (integrated_node && !integrated_node_ready) { ui.add_space(2.0); @@ -136,7 +137,7 @@ impl RecoverySettings { /// Draw [`Modal`] content for this ui container. fn modal_content_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, cb: &dyn PlatformCallbacks) { match Modal::opened() { None => {} @@ -175,7 +176,7 @@ impl RecoverySettings { /// Draw recovery phrase [`Modal`] content. fn recovery_phrase_modal_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { ui.add_space(6.0); @@ -260,7 +261,7 @@ impl RecoverySettings { /// Draw wallet deletion [`Modal`] content. fn deletion_modal_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, modal: &Modal) { ui.add_space(8.0); ui.vertical_centered(|ui| { diff --git a/src/gui/views/wallets/wallet/transport/content.rs b/src/gui/views/wallets/wallet/transport/content.rs index 4876d1d..1200515 100644 --- a/src/gui/views/wallets/wallet/transport/content.rs +++ b/src/gui/views/wallets/wallet/transport/content.rs @@ -47,7 +47,7 @@ impl WalletTab for WalletTransport { fn ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, cb: &dyn PlatformCallbacks) { if WalletContent::sync_ui(ui, wallet) { return; @@ -106,7 +106,7 @@ impl Default for WalletTransport { impl WalletTransport { /// Draw wallet transport content. - pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { + pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { ui.add_space(3.0); ui.label(RichText::new(t!("transport.desc")) .size(16.0) @@ -120,7 +120,7 @@ impl WalletTransport { /// Draw [`Modal`] content for this ui container. fn modal_content_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, cb: &dyn PlatformCallbacks) { match Modal::opened() { None => {} @@ -152,7 +152,7 @@ impl WalletTransport { } /// Draw Tor transport content. - fn tor_ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { + fn tor_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { let data = wallet.get_data().unwrap(); // Draw header content. diff --git a/src/gui/views/wallets/wallet/transport/send.rs b/src/gui/views/wallets/wallet/transport/send.rs index 76e08b3..e191262 100644 --- a/src/gui/views/wallets/wallet/transport/send.rs +++ b/src/gui/views/wallets/wallet/transport/send.rs @@ -74,7 +74,7 @@ impl TransportSendModal { /// Draw [`Modal`] content. pub fn ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { // Draw transaction information on request result. diff --git a/src/gui/views/wallets/wallet/txs/content.rs b/src/gui/views/wallets/wallet/txs/content.rs index e7a63dd..3c5a268 100644 --- a/src/gui/views/wallets/wallet/txs/content.rs +++ b/src/gui/views/wallets/wallet/txs/content.rs @@ -56,7 +56,7 @@ impl WalletTab for WalletTransactions { WalletTabType::Txs } - fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { + fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) { if WalletContent::sync_ui(ui, wallet) { return; } @@ -100,7 +100,7 @@ impl WalletTransactions { /// Draw transactions content. fn txs_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, data: &WalletData, cb: &dyn PlatformCallbacks) { let amount_conf = data.info.amount_awaiting_confirmation; @@ -245,7 +245,7 @@ impl WalletTransactions { /// Draw [`Modal`] content for this ui container. fn modal_content_ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, cb: &dyn PlatformCallbacks) { match Modal::opened() { None => {} @@ -360,8 +360,8 @@ impl WalletTransactions { TxLogEntryType::TxSent | TxLogEntryType::TxReceived => { let height = data.info.last_confirmed_height; let min_conf = data.info.minimum_confirmations; - if tx.conf_height.is_none() || (tx.conf_height.unwrap() != 0 && - height - tx.conf_height.unwrap() > min_conf - 1) { + if tx.height.is_none() || (tx.height.unwrap() != 0 && + height - tx.height.unwrap() > min_conf - 1) { let (i, t) = if tx.data.tx_type == TxLogEntryType::TxSent { (ARROW_CIRCLE_UP, t!("wallets.tx_sent")) } else { @@ -369,7 +369,7 @@ impl WalletTransactions { }; format!("{} {}", i, t) } else { - let tx_height = tx.conf_height.unwrap() - 1; + let tx_height = tx.height.unwrap() - 1; let left_conf = height - tx_height; let conf_info = if tx_height != 0 && height >= tx_height && left_conf < min_conf { @@ -428,7 +428,7 @@ impl WalletTransactions { } /// Confirmation [`Modal`] to cancel transaction. - fn cancel_confirmation_modal(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, modal: &Modal) { + fn cancel_confirmation_modal(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal) { ui.add_space(6.0); ui.vertical_centered(|ui| { // Setup confirmation text. diff --git a/src/gui/views/wallets/wallet/txs/tx.rs b/src/gui/views/wallets/wallet/txs/tx.rs index cfc4ae7..95a57ca 100644 --- a/src/gui/views/wallets/wallet/txs/tx.rs +++ b/src/gui/views/wallets/wallet/txs/tx.rs @@ -21,7 +21,7 @@ use grin_util::ToHex; use grin_wallet_libwallet::{Error, Slate, SlateState, TxLogEntryType}; use parking_lot::RwLock; use crate::gui::Colors; -use crate::gui::icons::{BROOM, CHECK, CLIPBOARD_TEXT, COPY, FILE_ARCHIVE, FILE_TEXT, HASH_STRAIGHT, PROHIBIT, QR_CODE, SCAN}; +use crate::gui::icons::{BROOM, CHECK, CLIPBOARD_TEXT, COPY, CUBE, FILE_ARCHIVE, FILE_TEXT, HASH_STRAIGHT, PROHIBIT, QR_CODE, SCAN}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{CameraContent, FilePickButton, Modal, QrCodeContent, View}; @@ -106,7 +106,7 @@ impl WalletTransactionModal { /// Draw [`Modal`] content. pub fn ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { // Check values and setup transaction data. @@ -171,16 +171,23 @@ impl WalletTransactionModal { } }); - // Show transaction ID info. + // Show identifier. if let Some(id) = tx.data.tx_slate_id { let label = format!("{} {}", HASH_STRAIGHT, t!("id")); Self::info_item_ui(ui, id.to_string(), label, true, cb); } - // Show transaction kernel info. + // Show kernel. if let Some(kernel) = tx.data.kernel_excess { let label = format!("{} {}", FILE_ARCHIVE, t!("kernel")); Self::info_item_ui(ui, kernel.0.to_hex(), label, true, cb); } + // Show block height. + if let Some(height) = tx.height { + if height != 0 { + let label = format!("{} {}", CUBE, t!("network_node.block")); + Self::info_item_ui(ui, height.to_string(), label, true, cb); + } + } } // Show Slatepack message or reset QR code state if not available. @@ -335,7 +342,6 @@ impl WalletTransactionModal { if let Some(qr_scan_content) = self.qr_scan_content.as_mut() { if let Some(result) = qr_scan_content.qr_scan_result() { cb.stop_camera(); - qr_scan_content.clear_state(); // Setup value to finalization input field. self.finalize_edit = result.text(); diff --git a/src/gui/views/wallets/wallet/types.rs b/src/gui/views/wallets/wallet/types.rs index 750fc02..f29d0a1 100644 --- a/src/gui/views/wallets/wallet/types.rs +++ b/src/gui/views/wallets/wallet/types.rs @@ -26,7 +26,7 @@ pub trait WalletTab { fn get_type(&self) -> WalletTabType; fn ui(&mut self, ui: &mut egui::Ui, - wallet: &mut Wallet, + wallet: &Wallet, cb: &dyn PlatformCallbacks); } @@ -52,7 +52,7 @@ impl WalletTabType { } /// Get wallet status text. -pub fn status_text(wallet: &Wallet) -> String { +pub fn wallet_status_text(wallet: &Wallet) -> String { if wallet.is_open() { if wallet.sync_error() { format!("{} {}", WARNING_CIRCLE, t!("error")) diff --git a/src/wallet/config.rs b/src/wallet/config.rs index 08b262b..8da141b 100644 --- a/src/wallet/config.rs +++ b/src/wallet/config.rs @@ -22,6 +22,7 @@ use rand::Rng; use serde_derive::{Deserialize, Serialize}; use crate::{AppConfig, Settings}; +use crate::wallet::ConnectionsConfig; use crate::wallet::types::ConnectionMethod; /// Wallet configuration. @@ -75,7 +76,7 @@ impl WalletConfig { name, ext_conn_id: match conn_method { ConnectionMethod::Integrated => None, - ConnectionMethod::External(id) => Some(*id) + ConnectionMethod::External(id, _) => Some(*id) }, min_confirmations: MIN_CONFIRMATIONS_DEFAULT, use_dandelion: Some(true), @@ -116,6 +117,18 @@ impl WalletConfig { None } + /// Get wallet connection method. + pub fn connection(&self) -> ConnectionMethod { + if let Some(ext_conn_id) = self.ext_conn_id { + if let Some(conn) = ConnectionsConfig::ext_conn(ext_conn_id) { + if !conn.deleted { + return ConnectionMethod::External(conn.id, conn.url); + } + } + } + ConnectionMethod::Integrated + } + /// Save wallet config. pub fn save(&self) { let config_path = Self::get_config_file_path(self.chain_type, self.id); diff --git a/src/wallet/connections/config.rs b/src/wallet/connections/config.rs index e790fd5..b0b18b6 100644 --- a/src/wallet/connections/config.rs +++ b/src/wallet/connections/config.rs @@ -38,13 +38,7 @@ impl ConnectionsConfig { if !path.exists() || parsed.is_err() { let default_config = ConnectionsConfig { chain_type: *chain_type, - external: if chain_type == &ChainTypes::Mainnet { - vec![ - ExternalConnection::default_main() - ] - } else { - vec![] - }, + external: ExternalConnection::default(chain_type), }; Settings::write_to_file(&default_config, path); default_config @@ -53,11 +47,17 @@ impl ConnectionsConfig { } } - /// Save connections configuration to the file. - pub fn save(&self) { - let chain_type = AppConfig::chain_type(); - let sub_dir = Some(chain_type.shortname()); - Settings::write_to_file(self, Settings::config_path(Self::FILE_NAME, sub_dir)); + /// Save connections configuration. + pub fn save(&mut self) { + // Check deleted external connections. + let mut config = self.clone(); + config.external = config.external.iter() + .map(|c| c.clone()) + .filter(|c| !c.deleted) + .collect::>(); + + let sub_dir = Some(AppConfig::chain_type().shortname()); + Settings::write_to_file(&config, Settings::config_path(Self::FILE_NAME, sub_dir)); } /// Get [`ExternalConnection`] list. @@ -68,29 +68,19 @@ impl ConnectionsConfig { /// Save [`ExternalConnection`] in configuration. pub fn add_ext_conn(conn: ExternalConnection) { - // Do not update default connection. - if conn.url == ExternalConnection::DEFAULT_MAIN_URL { - return; - } let mut w_config = Settings::conn_config_to_update(); - let mut exists = false; - for c in w_config.external.iter_mut() { - // Update connection if config exists. - if c.id == conn.id { - c.url = conn.url.clone(); - c.secret = conn.secret.clone(); - exists = true; - break; - } - } - // Create new connection if URL not exists. - if !exists { + if let Some(pos) = w_config.external.iter().position(|c| { + c.id == conn.id + }) { + w_config.external.remove(pos); + w_config.external.insert(pos, conn); + } else { w_config.external.push(conn); } w_config.save(); } - /// Get [`ExternalConnection`] by provided identifier. + /// Get external node connection with provided identifier. pub fn ext_conn(id: i64) -> Option { let r_config = Settings::conn_config_to_read(); for c in &r_config.external { @@ -113,13 +103,16 @@ impl ConnectionsConfig { } } - /// Remove external node connection by provided identifier. + /// Remove [`ExternalConnection`] with provided identifier. pub fn remove_ext_conn(id: i64) { let mut w_config = Settings::conn_config_to_update(); - let index = w_config.external.iter().position(|c| c.id == id); - if let Some(i) = index { - w_config.external.remove(i); - w_config.save(); + if let Some(pos) = w_config.external.iter().position(|c| { + c.id == id + }) { + if let Some(conn) = w_config.external.get_mut(pos) { + conn.deleted = true; + w_config.save(); + } } } } \ No newline at end of file diff --git a/src/wallet/connections/external.rs b/src/wallet/connections/external.rs index 917d1dc..05371c9 100644 --- a/src/wallet/connections/external.rs +++ b/src/wallet/connections/external.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use grin_core::global::ChainTypes; use grin_util::to_base64; use serde_derive::{Deserialize, Serialize}; @@ -28,23 +29,53 @@ pub struct ExternalConnection { pub secret: Option, /// Flag to check if server is available. - #[serde(skip_serializing)] - pub available: Option + #[serde(skip_serializing, skip_deserializing)] + pub available: Option, + + /// Flag to check if connection was deleted. + #[serde(skip_serializing, skip_deserializing)] + pub deleted: bool } -impl ExternalConnection { - /// Default external node URL for main network. - pub const DEFAULT_MAIN_URL: &'static str = "https://grinnode.live:3413"; +/// Default external node URL for main network. +const DEFAULT_MAIN_URLS: [&'static str; 2] = [ + "https://grincoin.org", + "https://grinnode.live:3413" + ]; - /// Create default external connection. - pub fn default_main() -> Self { - Self { id: 1, url: Self::DEFAULT_MAIN_URL.to_string(), secret: None, available: None } +/// Default external node URL for main network. +const DEFAULT_TEST_URLS: [&'static str; 1] = [ + "https://testnet.grincoin.org" + ]; + +impl ExternalConnection { + /// Get default connections for provided chain type. + pub fn default(chain_type: &ChainTypes) -> Vec { + let urls = match chain_type { + ChainTypes::Mainnet => DEFAULT_MAIN_URLS.to_vec(), + _ => DEFAULT_TEST_URLS.to_vec() + }; + urls.iter().enumerate().map(|(index, url)| { + ExternalConnection { + id: index as i64, + url: url.to_string(), + secret: None, + available: None, + deleted: false, + } + }).collect::>() } /// Create new external connection. pub fn new(url: String, secret: Option) -> Self { let id = chrono::Utc::now().timestamp(); - Self { id, url, secret, available: None } + Self { + id, + url, + secret, + available: None, + deleted: false + } } /// Check connection availability. diff --git a/src/wallet/list.rs b/src/wallet/list.rs index 598fa3b..295b39e 100644 --- a/src/wallet/list.rs +++ b/src/wallet/list.rs @@ -13,7 +13,6 @@ // limitations under the License. use grin_core::global::ChainTypes; -use grin_wallet_libwallet::Error; use crate::AppConfig; use crate::wallet::{Wallet, WalletConfig}; @@ -25,14 +24,12 @@ pub struct WalletList { pub main_list: Vec, /// List of wallets for [`ChainTypes::Testnet`]. pub test_list: Vec, - /// Selected [`Wallet`] identifier. - pub selected_id: Option, } impl Default for WalletList { fn default() -> Self { let (main_list, test_list) = Self::init(); - Self { main_list, test_list, selected_id: None } + Self { main_list, test_list } } } @@ -85,7 +82,6 @@ impl WalletList { /// Add created [`Wallet`] to the list. pub fn add(&mut self, wallet: Wallet) { - self.selected_id = Some(wallet.get_config().id); let list = self.mut_list(); list.insert(0, wallet); } @@ -100,47 +96,4 @@ impl WalletList { } } } - - /// Select [`Wallet`] with provided identifier. - pub fn select(&mut self, id: Option) { - self.selected_id = id; - } - - /// Get selected [`Wallet`] name. - pub fn selected_name(&self) -> String { - for w in self.list() { - let config = w.get_config(); - if Some(config.id) == self.selected_id { - return config.name.clone() - } - } - t!("wallets.unlocked") - } - - /// Check if selected [`Wallet`] is open. - pub fn is_selected_open(&self) -> bool { - for w in self.list() { - let config = w.get_config(); - if Some(config.id) == self.selected_id { - return w.is_open() - } - } - false - } - - /// Check if current list is empty. - pub fn is_current_list_empty(&self) -> bool { - self.list().is_empty() - } - - /// Open selected [`Wallet`]. - pub fn open_selected(&mut self, password: &String) -> Result<(), Error> { - let selected_id = self.selected_id.clone(); - for w in self.mut_list() { - if Some(w.get_config().id) == selected_id { - return w.open(password); - } - } - Err(Error::GenericError("Wallet is not selected".to_string())) - } } \ No newline at end of file diff --git a/src/wallet/types.rs b/src/wallet/types.rs index 067f1a6..e35c304 100644 --- a/src/wallet/types.rs +++ b/src/wallet/types.rs @@ -18,6 +18,7 @@ use grin_keychain::ExtKeychain; use grin_util::Mutex; use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient}; use grin_wallet_libwallet::{TxLogEntry, TxLogEntryType, WalletInfo, WalletInst}; +use serde_derive::{Deserialize, Serialize}; /// Mnemonic phrase word. #[derive(Clone)] @@ -107,12 +108,12 @@ impl PhraseSize { } /// Wallet connection method. -#[derive(PartialEq)] +#[derive(Serialize, Deserialize, Clone, PartialEq)] pub enum ConnectionMethod { /// Integrated node. Integrated, - /// External node, contains connection identifier. - External(i64) + /// External node, contains connection identifier and URL. + External(i64, String) } /// Wallet instance type. @@ -162,8 +163,8 @@ pub struct WalletTransaction { pub can_finalize: bool, /// Flag to check if transaction is finalizing. pub finalizing: bool, - /// Block height when tx was confirmed. - pub conf_height: Option, + /// Block height where tx was included. + pub height: Option, /// Flag to check if tx was received after sync from node. pub from_node: bool, } diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index 56c1171..2a4c10a 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -28,7 +28,6 @@ use serde_json::{json, Value}; use grin_api::{ApiServer, Router}; use grin_chain::SyncStatus; use grin_core::global; -use grin_core::global::ChainTypes; use grin_keychain::{ExtKeychain, Identifier, Keychain}; use grin_util::{Mutex, ToHex}; use grin_util::secp::SecretKey; @@ -46,7 +45,7 @@ use rand::Rng; use crate::AppConfig; use crate::node::{Node, NodeConfig}; use crate::tor::Tor; -use crate::wallet::{ConnectionsConfig, ExternalConnection, Mnemonic, WalletConfig}; +use crate::wallet::{ConnectionsConfig, Mnemonic, WalletConfig}; use crate::wallet::store::TxHeightStore; use crate::wallet::types::{ConnectionMethod, WalletAccount, WalletData, WalletInstance, WalletTransaction}; @@ -56,9 +55,9 @@ pub struct Wallet { /// Wallet configuration. config: Arc>, /// Wallet instance, initializing on wallet opening and clearing on wallet closing. - instance: Option, - /// [`WalletInstance`] external connection URL. - external_connection: Arc>>, + instance: Arc>>, + /// Connection of current wallet instance. + connection: Arc>, /// Wallet Slatepack address to receive txs at transport. slatepack_address: Arc>>, @@ -103,10 +102,11 @@ pub struct Wallet { impl Wallet { /// Create new [`Wallet`] instance with provided [`WalletConfig`]. fn new(config: WalletConfig) -> Self { + let connection = config.connection(); Self { config: Arc::new(RwLock::new(config)), - instance: None, - external_connection: Arc::new(RwLock::new(None)), + instance: Arc::new(RwLock::new(None)), + connection: Arc::new(RwLock::new(connection)), slatepack_address: Arc::new(RwLock::new(None)), sync_thread: Arc::from(RwLock::new(None)), foreign_api_server: Arc::new(RwLock::new(None)), @@ -128,7 +128,7 @@ impl Wallet { /// Create new wallet. pub fn create( name: &String, - password: &String, + password: &ZeroingString, mnemonic: &Mnemonic, conn_method: &ConnectionMethod ) -> Result { @@ -141,7 +141,7 @@ impl Wallet { p.create_wallet(None, Some(ZeroingString::from(mnemonic.get_phrase())), mnemonic.size().entropy_size(), - ZeroingString::from(password.clone()), + password.clone(), false, )?; } @@ -178,16 +178,7 @@ impl Wallet { if let Some(conn) = ConnectionsConfig::ext_conn(id) { (conn.url, conn.secret) } else { - if chain_type == ChainTypes::Mainnet { - // Save default external connection if previous was deleted. - let default = ExternalConnection::default_main(); - config.ext_conn_id = Some(default.id); - config.save(); - - (default.url, default.secret) - } else { - integrated() - } + integrated() } } else { integrated() @@ -223,7 +214,8 @@ impl Wallet { /// Get parent key identifier for current account. pub fn get_parent_key_id(&self) -> Result { - let instance = self.instance.clone().unwrap(); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); let mut w_lock = instance.lock(); let lc = w_lock.lc_provider()?; let w_inst = lc.wallet_inst()?; @@ -232,7 +224,8 @@ impl Wallet { /// Get wallet [`SecretKey`] for transports. pub fn secret_key(&self) -> Result { - let instance = self.instance.clone().unwrap(); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); let mut w_lock = instance.lock(); let lc = w_lock.lc_provider()?; let w_inst = lc.wallet_inst()?; @@ -305,36 +298,47 @@ impl Wallet { } /// Update external connection identifier. - pub fn update_ext_conn_id(&self, id: Option) { + pub fn update_connection(&self, conn: &ConnectionMethod) { let mut w_config = self.config.write(); - w_config.ext_conn_id = id; + w_config.ext_conn_id = match conn { + ConnectionMethod::Integrated => None, + ConnectionMethod::External(id, _) => Some(id.clone()) + }; w_config.save(); } /// Open the wallet and start [`WalletData`] sync at separate thread. - pub fn open(&mut self, password: &String) -> Result<(), Error> { + pub fn open(&self, password: ZeroingString) -> Result<(), Error> { if self.is_open() { return Err(Error::GenericError("Already opened".to_string())); } // Create new wallet instance if sync thread was stopped or instance was not created. - if self.sync_thread.read().is_none() || self.instance.is_none() { + let has_instance = { + let r_inst = self.instance.as_ref().read(); + r_inst.is_some() + }; + if self.sync_thread.read().is_none() || !has_instance { let mut config = self.get_config(); - let new_instance = Self::create_wallet_instance(&mut config)?; - self.instance = Some(new_instance); - // Setup current external connection. + // Setup current connection. { - let mut w_conn = self.external_connection.write(); - *w_conn = self.get_current_ext_conn(); + let mut w_conn = self.connection.write(); + *w_conn = config.connection(); } + let new_instance = Self::create_wallet_instance(&mut config)?; + let mut w_inst = self.instance.write(); + *w_inst = Some(new_instance); } // Open the wallet. { - let instance = self.instance.clone().unwrap(); + let instance = { + let r_inst = self.instance.as_ref().read(); + r_inst.clone().unwrap() + }; let mut wallet_lock = instance.lock(); let lc = wallet_lock.lc_provider()?; - match lc.open_wallet(None, ZeroingString::from(password.clone()), false, false) { + match lc.open_wallet(None, password, false, false) { Ok(_) => { // Reset an error on opening. self.set_sync_error(false); @@ -357,7 +361,8 @@ impl Wallet { } Err(e) => { if !self.syncing() { - self.instance = None; + let mut w_inst = self.instance.write(); + *w_inst = None; } return Err(e) } @@ -365,7 +370,9 @@ impl Wallet { } // Set slatepack address. - let mut api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let mut api = Owner::new(instance, None); controller::owner_single_use(None, None, Some(&mut api), |api, m| { let mut w_address = self.slatepack_address.write(); *w_address = Some(api.get_slatepack_address(m, 0)?.to_string()); @@ -377,25 +384,13 @@ impl Wallet { /// Get external connection URL applied to [`WalletInstance`] /// after wallet opening if sync is running or get it from config. - pub fn get_current_ext_conn(&self) -> Option { + pub fn get_current_connection(&self) -> ConnectionMethod { if self.sync_thread.read().is_some() { - let r_conn = self.external_connection.read(); + let r_conn = self.connection.read(); r_conn.clone() } else { let config = self.get_config(); - if let Some(id) = config.ext_conn_id { - return match ConnectionsConfig::ext_conn(id) { - None => { - if config.chain_type == ChainTypes::Mainnet { - Some(ExternalConnection::default_main()) - } else { - None - } - }, - Some(ext_conn) => Some(ext_conn) - } - } - None + config.connection() } } @@ -411,7 +406,11 @@ impl Wallet { /// Close the wallet. pub fn close(&self) { - if !self.is_open() || self.instance.is_none() { + let has_instance = { + let r_inst = self.instance.read(); + r_inst.is_some() + }; + if !self.is_open() || !has_instance { return; } self.closing.store(true, Ordering::Relaxed); @@ -419,6 +418,7 @@ impl Wallet { // Close wallet at separate thread. let wallet_close = self.clone(); let service_id = wallet_close.identifier(); + let conn = wallet_close.connection.clone(); thread::spawn(move || { // Stop running API server. let api_server_exists = { @@ -431,12 +431,18 @@ impl Wallet { } // Stop running Tor service. Tor::stop_service(&service_id); + // Close the wallet. - let instance = wallet_close.instance.clone().unwrap(); + let r_inst = wallet_close.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); Self::close_wallet(&instance); - // Mark wallet as not opened. wallet_close.closing.store(false, Ordering::Relaxed); wallet_close.is_open.store(false, Ordering::Relaxed); + // Setup current connection. + { + let mut w_conn = conn.write(); + *w_conn = wallet_close.get_config().connection(); + } // Start sync to exit from thread. wallet_close.sync(); }); @@ -451,7 +457,9 @@ impl Wallet { /// Create account into wallet. pub fn create_account(&self, label: &String) -> Result<(), Error> { - let mut api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let mut api = Owner::new(instance, None); controller::owner_single_use(None, None, Some(&mut api), |api, m| { api.create_account_path(m, label)?; @@ -472,7 +480,9 @@ impl Wallet { /// Set active account from provided label. pub fn set_active_account(&self, label: &String) -> Result<(), Error> { - let mut api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let mut api = Owner::new(instance, None); controller::owner_single_use(None, None, Some(&mut api), |api, m| { api.set_active_account(m, label)?; // Set Slatepack address. @@ -580,7 +590,9 @@ impl Wallet { /// Parse Slatepack message into [`Slate`]. pub fn parse_slatepack(&self, text: &String) -> Result { - let mut api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let mut api = Owner::new(instance, None); return match parse_slatepack(&mut api, None, None, Some(text.clone())) { Ok(s) => Ok(s.0), Err(e) => Err(e) @@ -590,7 +602,9 @@ impl Wallet { /// Create Slatepack message from provided slate. fn create_slatepack_message(&self, slate: &Slate) -> Result { let mut message = "".to_string(); - let mut api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let mut api = Owner::new(instance, None); controller::owner_single_use(None, None, Some(&mut api), |api, m| { message = api.create_slatepack_message(m, &slate, Some(0), vec![])?; Ok(()) @@ -678,7 +692,9 @@ impl Wallet { selection_strategy_is_use_all: false, ..Default::default() }; - let api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let api = Owner::new(instance, None); let slate = api.init_send_tx(None, args)?; // Lock outputs to for this transaction. @@ -708,7 +724,8 @@ impl Wallet { // Function to cancel initialized tx in case of error. let cancel_tx = || { - let instance = self.instance.clone().unwrap(); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); let id = slate.clone().id; cancel_tx(instance, None, &None, None, Some(id.clone())).unwrap(); @@ -749,7 +766,9 @@ impl Wallet { let mut ret_slate = None; match Slate::deserialize_upgrade(&serde_json::to_string(&slate_value).unwrap()) { Ok(s) => { - let mut api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let mut api = Owner::new(instance, None); controller::owner_single_use(None, None, Some(&mut api), |api, m| { // Finalize transaction. return if let Ok(slate) = api.finalize_tx(m, &s) { @@ -791,7 +810,9 @@ impl Wallet { amount, target_slate_version: None, }; - let api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let api = Owner::new(instance, None); let slate = api.issue_invoice_tx(None, args)?; // Create Slatepack message response. @@ -815,7 +836,9 @@ impl Wallet { selection_strategy_is_use_all: false, ..Default::default() }; - let api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let api = Owner::new(instance, None); let slate = api.process_invoice_tx(None, &slate, args)?; api.tx_lock_outputs(None, &slate)?; @@ -834,7 +857,9 @@ impl Wallet { /// Handle message to receive funds, return response to sender. pub fn receive(&self, message: &String) -> Result { if let Ok(mut slate) = self.parse_slatepack(message) { - let api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let api = Owner::new(instance, None); controller::foreign_single_use(api.wallet_inst.clone(), None, |api| { slate = api.receive_tx(&slate, Some(self.get_config().account.as_str()), None)?; Ok(()) @@ -854,7 +879,9 @@ impl Wallet { /// Finalize transaction from provided message as sender or invoice issuer with Dandelion. pub fn finalize(&self, message: &String) -> Result { if let Ok(mut slate) = self.parse_slatepack(message) { - let api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let api = Owner::new(instance, None); slate = api.finalize_tx(None, &slate)?; // Save Slatepack message to file. let _ = self.create_slatepack_message(&slate)?; @@ -874,7 +901,9 @@ impl Wallet { /// Post transaction to blockchain. fn post(&self, slate: &Slate) -> Result { // Post transaction to blockchain. - let api = Owner::new(self.instance.clone().unwrap(), None); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let api = Owner::new(instance, None); api.post_tx(None, slate, self.can_use_dandelion())?; // Refresh wallet info. @@ -884,7 +913,7 @@ impl Wallet { } /// Cancel transaction. - pub fn cancel(&mut self, id: u32) { + pub fn cancel(&self, id: u32) { // Setup cancelling status. { let mut w_data = self.data.write(); @@ -906,7 +935,8 @@ impl Wallet { if wallet.syncing() { thread::sleep(Duration::from_millis(1000)); } - let instance = wallet.instance.clone().unwrap(); + let r_inst = wallet.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); let _ = cancel_tx(instance, None, &None, Some(id), None); // Refresh wallet info to update statuses. @@ -914,9 +944,38 @@ impl Wallet { }); } + /// Get possible transaction confirmation height from db or node. + fn tx_height(&self, tx: &TxLogEntry, store: &TxHeightStore) -> Result, Error> { + let mut tx_height = None; + if tx.kernel_lookup_min_height.is_some() && tx.kernel_excess.is_some() && tx.confirmed { + if let Some(height) = store.read_tx_height(tx.id) { + tx_height = Some(height); + } else { + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let mut w_lock = instance.lock(); + let w = w_lock.lc_provider()?.wallet_inst()?; + if let Ok(res) = w.w2n_client().get_kernel( + tx.kernel_excess.as_ref().unwrap(), + tx.kernel_lookup_min_height, + None + ) { + if let Some((_, h, _)) = res { + tx_height = Some(h); + store.write_tx_height(tx.id, h); + } else { + tx_height = Some(0); + } + } + } + } + Ok(tx_height) + } + /// Change wallet password. pub fn change_password(&self, old: String, new: String) -> Result<(), Error> { - let instance = self.instance.clone().unwrap(); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); let mut wallet_lock = instance.lock(); let lc = wallet_lock.lc_provider()?; lc.change_password(None, ZeroingString::from(old), ZeroingString::from(new)) @@ -961,7 +1020,8 @@ impl Wallet { /// Get recovery phrase. pub fn get_recovery(&self, password: String) -> Result { - let instance = self.instance.clone().unwrap(); + let r_inst = self.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); let mut wallet_lock = instance.lock(); let lc = wallet_lock.lc_provider().unwrap(); lc.get_mnemonic(None, ZeroingString::from(password)) @@ -1041,7 +1101,7 @@ fn start_sync(wallet: Wallet) -> Thread { } // Check integrated node state. - if wallet.get_current_ext_conn().is_none() { + if wallet.get_current_connection() == ConnectionMethod::Integrated { let not_enabled = !Node::is_running() || Node::is_stopping(); if not_enabled { // Reset loading progress. @@ -1151,7 +1211,9 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) { let config = wallet.get_config(); // Retrieve wallet info. - if let Some(instance) = &wallet.instance { + let r_inst = wallet.instance.as_ref().read(); + if r_inst.is_some() { + let instance = r_inst.clone().unwrap(); if let Ok(info) = retrieve_summary_info( instance.clone(), None, @@ -1224,7 +1286,6 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) { let data = wallet.get_data().unwrap(); let data_txs = data.txs.unwrap_or(vec![]); - // Create wallet txs. let mut new_txs: Vec = vec![]; for tx in &account_txs { // Setup transaction amount. @@ -1263,58 +1324,20 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) { false }; - // Setup confirmation and cancelling status. - let mut conf_height = None; - let mut setup_conf_height = |t: &TxLogEntry, current_empty: bool| -> bool { - if current_empty && t.kernel_lookup_min_height.is_some() && - t.kernel_excess.is_some() && t.confirmed { - // Get tx height from database or from node. - if let Some(height) = tx_height_store.read_tx_height(t.id) { - conf_height = Some(height); - } else { - let mut w_lock = wallet.instance.as_ref().unwrap().lock(); - let w = w_lock.lc_provider() - .unwrap() - .wallet_inst() - .unwrap(); - if let Ok(res) = w.w2n_client().get_kernel( - t.kernel_excess.as_ref().unwrap(), - t.kernel_lookup_min_height, - None - ) { - if res.is_some() { - let h = res.unwrap().1; - conf_height = Some(h); - tx_height_store.write_tx_height(t.id, h); - } else { - conf_height = Some(0); - } - } else { - conf_height = None; - } - } - return true; - } - false - }; - + // Setup confirmation height and cancelling status + let mut conf_height = wallet.tx_height(tx, &tx_height_store).unwrap_or(None); let mut cancelling = false; - if data_txs.is_empty() { - setup_conf_height(tx, true); - } else { - for t in &data_txs { - if t.data.id == tx.id { - if !setup_conf_height(tx, t.conf_height.is_none() || - t.conf_height.unwrap() == 0) { - conf_height = t.conf_height; - } - if t.cancelling && - tx.tx_type != TxLogEntryType::TxReceivedCancelled && - tx.tx_type != TxLogEntryType::TxSentCancelled { - cancelling = true; - } - break; + for t in &data_txs { + if t.data.id == tx.id { + if conf_height.is_none() { + conf_height = t.height; } + if t.cancelling && + tx.tx_type != TxLogEntryType::TxReceivedCancelled && + tx.tx_type != TxLogEntryType::TxSentCancelled { + cancelling = true; + } + break; } } @@ -1325,7 +1348,7 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) { cancelling, can_finalize, finalizing, - conf_height, + height: conf_height, from_node: !fresh_sync || from_node }); } @@ -1391,7 +1414,8 @@ fn start_api_server(wallet: &Wallet) -> Result<(ApiServer, u16), Error> { let api_addr = format!("{}:{}", host, free_port); // Start Foreign API server thread. - let instance = wallet.instance.clone().unwrap(); + let r_inst = wallet.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); let api_handler_v2 = ForeignAPIHandlerV2::new(instance, Arc::new(Mutex::new(None)), false, @@ -1425,7 +1449,9 @@ fn update_accounts(wallet: &Wallet, current_height: u64, current_spendable: Opti let mut w_data = wallet.accounts.write(); *w_data = accounts; } else { - let mut api = Owner::new(wallet.instance.clone().unwrap(), None); + let r_inst = wallet.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let mut api = Owner::new(instance, None); let _ = controller::owner_single_use(None, None, Some(&mut api), |api, m| { let mut accounts = vec![]; for a in api.accounts(m)? { @@ -1489,7 +1515,9 @@ fn repair_wallet(wallet: &Wallet) { }); // Start wallet scanning. - let api = Owner::new(wallet.instance.clone().unwrap(), Some(info_tx)); + let r_inst = wallet.instance.as_ref().read(); + let instance = r_inst.clone().unwrap(); + let api = Owner::new(instance, Some(info_tx)); match api.scan(None, Some(1), false) { Ok(()) => { // Set sync error if scanning was not complete and wallet is open.