wallet: qr scan modal, connections content and default list, wallet creation and list refactoring, tx height

This commit is contained in:
ardocrat 2024-09-19 15:56:53 +03:00
parent 3bc8c407b4
commit c155deedb5
34 changed files with 1254 additions and 1168 deletions

View file

@ -273,20 +273,21 @@ impl<Platform: PlatformCallbacks> App<Platform> {
// Paint the title. // Paint the title.
let dual_wallets_panel = let dual_wallets_panel =
ui.available_width() >= (Content::SIDE_PANEL_WIDTH * 3.0) + View::get_right_inset(); ui.available_width() >= (Content::SIDE_PANEL_WIDTH * 3.0)
let wallet_panel_opened = self.content.wallets.wallet_panel_opened(); + View::get_right_inset() + View::get_left_inset();
let hide_app_name = if dual_wallets_panel { let wallet_panel_opened = self.content.wallets.showing_wallet();
!wallet_panel_opened || (AppConfig::show_wallets_at_dual_panel() && let show_app_name = if dual_wallets_panel {
self.content.wallets.showing_wallet() && !self.content.wallets.creating_wallet()) wallet_panel_opened && !AppConfig::show_wallets_at_dual_panel()
} else if Content::is_dual_panel_mode(ui) { } else if Content::is_dual_panel_mode(ui) {
!wallet_panel_opened wallet_panel_opened
} else { } else {
!Content::is_network_panel_open() && !wallet_panel_opened Content::is_network_panel_open() || wallet_panel_opened
}; };
let title_text = if hide_app_name { let creating_wallet = self.content.wallets.creating_wallet();
"".to_string() let title_text = if creating_wallet || show_app_name {
} else {
format!("Grim {}", crate::VERSION) format!("Grim {}", crate::VERSION)
} else {
"".to_string()
}; };
painter.text( painter.text(
title_rect.center(), title_rect.center(),

View file

@ -430,14 +430,4 @@ impl CameraContent {
} }
None 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;
}
} }

View file

@ -25,7 +25,7 @@ use crate::gui::views::types::{ModalContainer, ModalPosition};
use crate::node::Node; use crate::node::Node;
use crate::{AppConfig, Settings}; use crate::{AppConfig, Settings};
use crate::gui::icons::{CHECK, CHECK_FAT, FILE_X}; 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; use crate::gui::views::wallets::WalletsContent;
lazy_static! { lazy_static! {
@ -272,14 +272,7 @@ impl Content {
pub fn settings_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal) { pub fn settings_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal) {
ui.add_space(6.0); ui.add_space(6.0);
// Draw chain type selection. // Show theme 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.
Self::theme_selection_ui(ui); Self::theme_selection_ui(ui);
ui.add_space(8.0); ui.add_space(8.0);

View file

@ -41,3 +41,6 @@ pub use file_pick::*;
mod pull_to_refresh; mod pull_to_refresh;
pub use pull_to_refresh::*; pub use pull_to_refresh::*;
mod scan;
pub use scan::*;

View file

@ -103,23 +103,20 @@ impl ConnectionsContent {
ui.add_space(4.0); ui.add_space(4.0);
let ext_conn_list = ConnectionsConfig::ext_conn_list(); 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); 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| { ui.horizontal_wrapped(|ui| {
// Draw connection list item. // Draw connection list item.
let len = ext_conn_list.len(); Self::ext_conn_item_ui(ui, conn, index, ext_conn_size, |ui| {
Self::ext_conn_item_ui(ui, conn, index, len, |ui| { let button_rounding = View::item_rounding(index, ext_conn_size, true);
// 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, || { View::item_button(ui, button_rounding, TRASH, None, || {
ConnectionsConfig::remove_ext_conn(conn.id); ConnectionsConfig::remove_ext_conn(conn.id);
}); });
View::item_button(ui, Rounding::default(), PENCIL, None, || { View::item_button(ui, Rounding::default(), PENCIL, None, || {
self.show_add_ext_conn_modal(Some(conn.clone()), cb); self.show_add_ext_conn_modal(Some(conn.clone()), cb);
}); });
}
}); });
}); });
} }

View file

@ -112,7 +112,8 @@ impl ModalContainer for StratumSetup {
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
match modal.id { match modal.id {
WALLET_SELECTION_MODAL => { 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); NodeConfig::save_stratum_wallet_id(id);
self.wallet_name = WalletConfig::name_by_id(id); self.wallet_name = WalletConfig::name_by_id(id);
}) })

131
src/gui/views/scan.rs Normal file
View file

@ -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<CameraContent>,
/// QR code scan result
qr_scan_result: Option<QrScanResult>,
}
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);
}
}

View file

@ -18,33 +18,36 @@ use egui::scroll_area::ScrollBarVisibility;
use crate::AppConfig; use crate::AppConfig;
use crate::gui::Colors; 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::platform::PlatformCallbacks;
use crate::gui::views::{Modal, Content, TitlePanel, View}; use crate::gui::views::{Modal, Content, TitlePanel, View};
use crate::gui::views::types::{ModalContainer, ModalPosition, TitleContentType, TitleType}; use crate::gui::views::types::{ModalContainer, ModalPosition, TitleContentType, TitleType};
use crate::gui::views::wallets::creation::WalletCreation; 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::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::gui::views::wallets::WalletContent;
use crate::wallet::{Wallet, WalletList}; use crate::wallet::{Wallet, WalletList};
use crate::wallet::types::ConnectionMethod;
/// Wallets content. /// Wallets content.
pub struct WalletsContent { pub struct WalletsContent {
/// List of wallets. /// List of wallets.
wallets: WalletList, wallets: WalletList,
/// Wallet selection [`Modal`] content. /// Initial wallet creation [`Modal`] content.
wallet_selection_content: Option<WalletsModal>, add_wallet_modal_content: Option<AddWalletModal>,
/// Wallet opening [`Modal`] content. /// Wallet opening [`Modal`] content.
open_wallet_content: Option<OpenWalletModal>, open_wallet_content: Option<OpenWalletModal>,
/// Wallet connection selection content. /// Wallet connection selection content.
conn_selection_content: Option<WalletConnectionModal>, conn_selection_content: Option<WalletConnectionModal>,
/// Wallet selection [`Modal`] content.
wallet_selection_content: Option<WalletsModal>,
/// Selected [`Wallet`] content. /// Selected [`Wallet`] content.
wallet_content: WalletContent, wallet_content: Option<WalletContent>,
/// Wallet creation content. /// Wallet creation content.
creation_content: WalletCreation, creation_content: Option<WalletCreation>,
/// Flag to show [`Wallet`] list at dual panel mode. /// Flag to show [`Wallet`] list at dual panel mode.
show_wallets_at_dual_panel: bool, show_wallets_at_dual_panel: bool,
@ -53,14 +56,10 @@ pub struct WalletsContent {
modal_ids: Vec<&'static str> modal_ids: Vec<&'static str>
} }
/// Identifier for connection selection [`Modal`]. const ADD_WALLET_MODAL: &'static str = "wallets_add_modal";
const CONNECTION_SELECTION_MODAL: &'static str = "wallets_connection_selection";
/// Identifier for wallet opening [`Modal`].
const OPEN_WALLET_MODAL: &'static str = "wallets_open_wallet"; const OPEN_WALLET_MODAL: &'static str = "wallets_open_wallet";
const SELECT_CONNECTION_MODAL: &'static str = "wallets_select_conn_modal";
/// Identifier for wallet opening [`Modal`]. const SELECT_WALLET_MODAL: &'static str = "wallets_select_modal";
const SELECT_WALLET_MODAL: &'static str = "wallets_select_wallet";
impl Default for WalletsContent { impl Default for WalletsContent {
fn default() -> Self { fn default() -> Self {
@ -69,15 +68,16 @@ impl Default for WalletsContent {
wallet_selection_content: None, wallet_selection_content: None,
open_wallet_content: None, open_wallet_content: None,
conn_selection_content: None, conn_selection_content: None,
wallet_content: WalletContent::new(None), wallet_content: None,
creation_content: WalletCreation::default(), creation_content: None,
show_wallets_at_dual_panel: AppConfig::show_wallets_at_dual_panel(), show_wallets_at_dual_panel: AppConfig::show_wallets_at_dual_panel(),
modal_ids: vec![ modal_ids: vec![
ADD_WALLET_MODAL,
OPEN_WALLET_MODAL, OPEN_WALLET_MODAL,
WalletCreation::NAME_PASS_MODAL, SELECT_CONNECTION_MODAL,
CONNECTION_SELECTION_MODAL, SELECT_WALLET_MODAL,
SELECT_WALLET_MODAL ],
] add_wallet_modal_content: None,
} }
} }
} }
@ -92,36 +92,50 @@ impl ModalContainer for WalletsContent {
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
match modal.id { match modal.id {
OPEN_WALLET_MODAL => { ADD_WALLET_MODAL => {
if let Some(content) = self.open_wallet_content.as_mut() { if let Some(content) = self.add_wallet_modal_content.as_mut() {
content.ui(ui, modal, &mut self.wallets, cb, |data| { content.ui(ui, modal, cb, |name, pass| {
// Setup wallet content. self.creation_content = Some(
self.wallet_content = WalletContent::new(data); WalletCreation::new(name.clone(), pass.clone())
);
}); });
} }
}, if self.creation_content.is_some() {
WalletCreation::NAME_PASS_MODAL => { self.add_wallet_modal_content = None;
self.creation_content.name_pass_modal_ui(ui, modal, cb)
},
CONNECTION_SELECTION_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);
} }
},
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;
}
},
SELECT_CONNECTION_MODAL => {
if let Some(content) = self.conn_selection_content.as_mut() {
content.ui(ui, modal, cb, |conn| {
if let Some(wallet_content) = &self.wallet_content {
wallet_content.wallet.update_connection(&conn);
} }
}); });
} }
} }
SELECT_WALLET_MODAL => { SELECT_WALLET_MODAL => {
let mut select = false;
if let Some(content) = self.wallet_selection_content.as_mut() { if let Some(content) = self.wallet_selection_content.as_mut() {
content.ui(ui, modal, &mut self.wallets, cb, |_, data| { content.ui(ui, modal, &mut self.wallets, cb, |wallet, data| {
self.wallet_content = WalletContent::new(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 { impl WalletsContent {
/// Draw wallets content. /// Draw wallets content.
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { 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); self.current_modal_ui(ui, cb);
// Setup wallet content flags. let creating_wallet = self.creating_wallet();
let empty_list = self.wallets.is_current_list_empty(); let showing_wallet = self.showing_wallet() && !creating_wallet;
let create_wallet = self.creation_content.can_go_back();
let show_wallet = self.wallets.is_selected_open();
// Setup panels parameters.
let dual_panel = is_dual_panel_mode(ui); 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 content_width = ui.available_width();
let list_hidden = creating_wallet || self.wallets.list().is_empty()
let root_dual_panel = Content::is_dual_panel_mode(ui); || (dual_panel && showing_wallet && !self.show_wallets_at_dual_panel)
|| (!dual_panel && showing_wallet);
// 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());
// Show title panel. // 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. if showing_wallet {
let wallet_panel_opened = self.wallet_panel_opened();
egui::SidePanel::right("wallet_panel") egui::SidePanel::right("wallet_panel")
.resizable(false) .resizable(false)
.exact_width(wallet_panel_width) .exact_width(if list_hidden {
content_width
} else {
content_width - Content::SIDE_PANEL_WIDTH
})
.frame(egui::Frame { .frame(egui::Frame {
fill: if empty_list && !create_wallet fill: Colors::fill_deep(),
|| (dual_panel && show_wallet && !self.show_wallets_at_dual_panel) {
Colors::fill_deep()
} else {
if create_wallet {
Colors::white_or_black(false)
} else {
Colors::button()
}
},
..Default::default() ..Default::default()
}) })
.show_animated_inside(ui, wallet_panel_opened, |ui| { .show_inside(ui, |ui| {
if create_wallet || !show_wallet { // Show opened wallet content.
// Show wallet creation content. if let Some(content) = self.wallet_content.as_mut() {
self.creation_content.ui(ui, cb, |wallet| { content.ui(ui, cb);
// 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. if !list_hidden {
let show_bottom_panel = !list_hidden || dual_panel;
if show_bottom_panel {
egui::TopBottomPanel::bottom("wallets_bottom_panel") egui::TopBottomPanel::bottom("wallets_bottom_panel")
.frame(egui::Frame { .frame(egui::Frame {
fill: Colors::fill(), fill: Colors::fill(),
inner_margin: Margin { 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, right: View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING,
top: View::TAB_ITEMS_PADDING, top: View::TAB_ITEMS_PADDING,
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING, bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
}, },
..Default::default() ..Default::default()
}) })
.resizable(false)
.show_inside(ui, |ui| { .show_inside(ui, |ui| {
// Setup spacing between tabs. // Setup spacing between tabs.
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0); 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.style_mut().spacing.button_padding = egui::vec2(10.0, 4.0);
ui.vertical_centered(|ui| { 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, || { View::tab_button(ui, PLUS, pressed, || {
self.creation_content.show_name_pass_modal(cb); self.show_add_wallet_modal(cb);
}); });
}); });
}); });
}
// Show wallet list. egui::SidePanel::left("wallet_list_panel")
egui::CentralPanel::default() .exact_width(if dual_panel && showing_wallet {
.frame(if list_hidden { Content::SIDE_PANEL_WIDTH
egui::Frame::default()
} else { } else {
egui::Frame { content_width
})
.resizable(false)
.frame(egui::Frame {
stroke: View::item_stroke(), stroke: View::item_stroke(),
fill: Colors::fill_deep(), fill: Colors::fill_deep(),
inner_margin: Margin { inner_margin: Margin {
@ -249,30 +222,76 @@ impl WalletsContent {
bottom: 4.0, bottom: 4.0,
}, },
..Default::default() ..Default::default()
}
}) })
.show_inside(ui, |ui| { .show_inside(ui, |ui| {
if !list_hidden && !dual_panel { if !list_hidden && !dual_panel {
ui.ctx().request_repaint_after(Duration::from_millis(1000)); ui.ctx().request_repaint_after(Duration::from_millis(1000));
} }
// Show wallet list.
self.wallet_list_ui(ui, cb); self.wallet_list_ui(ui, cb);
}); });
} }
/// Check if wallet panel is showing. // Show central panel with wallet creation.
pub fn wallet_panel_opened(&self) -> bool { egui::CentralPanel::default()
let empty_list = self.wallets.is_current_list_empty(); .frame(egui::Frame {
empty_list || self.creating_wallet() || self.showing_wallet() 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 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);
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. /// Check if opened wallet is showing.
pub fn showing_wallet(&self) -> bool { 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. /// Check if wallet is creating.
pub fn creating_wallet(&self) -> bool { pub fn creating_wallet(&self) -> bool {
self.creation_content.can_go_back() self.creation_content.is_some()
} }
/// Handle data from deeplink or opened file. /// Handle data from deeplink or opened file.
@ -286,25 +305,38 @@ impl WalletsContent {
Content::toggle_network_panel(); Content::toggle_network_panel();
} }
// Pass data to opened selected wallet or show wallets selection. // Pass data to opened selected wallet or show wallets selection.
if self.wallets.is_selected_open() { if self.wallet_content.is_some() {
if self.showing_wallet() {
if wallets_size == 1 { if wallets_size == 1 {
self.wallet_content = WalletContent::new(data); let wallet_content = self.wallet_content.as_mut().unwrap();
wallet_content.on_data(data);
} else { } else {
self.show_wallet_selection_modal(data); self.show_wallet_selection_modal(data);
} }
} else { } else {
if wallets_size == 1 { if wallets_size == 1 {
self.show_opening_modal(self.wallets.list()[0].get_config().id, data, cb); let wallet_content = self.wallet_content.as_ref().unwrap();
self.show_opening_modal(wallet_content.wallet.clone(), data, cb);
} else { } else {
self.show_wallet_selection_modal(data); 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. /// Show wallet selection with provided optional data.
fn show_wallet_selection_modal(&mut self, data: Option<String>) { fn show_wallet_selection_modal(&mut self, data: Option<String>) {
self.wallet_selection_content = Some(WalletsModal::new(None, data, true)); self.wallet_selection_content = Some(WalletsModal::new(None, data, true));
// Show wallet selection modal.
Modal::new(SELECT_WALLET_MODAL) Modal::new(SELECT_WALLET_MODAL)
.position(ModalPosition::Center) .position(ModalPosition::Center)
.title(t!("network_settings.choose_wallet")) .title(t!("network_settings.choose_wallet"))
@ -313,13 +345,17 @@ impl WalletsContent {
/// Handle Back key event returning `false` when event was handled. /// Handle Back key event returning `false` when event was handled.
pub fn on_back(&mut self) -> bool { pub fn on_back(&mut self) -> bool {
let can_go_back = self.creation_content.can_go_back(); if self.creation_content.is_some() {
if can_go_back { // Close wallet creation.
self.creation_content.back(); let creation = self.creation_content.as_mut().unwrap();
if creation.on_back() {
self.creation_content = None;
}
return false return false
} else { } else {
if self.wallets.is_selected_open() { // Close opened wallet.
self.wallets.select(None); if self.showing_wallet() {
self.wallet_content = None;
return false return false
} }
} }
@ -330,37 +366,39 @@ impl WalletsContent {
fn title_ui(&mut self, fn title_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
dual_panel: bool, dual_panel: bool,
create_wallet: bool,
show_wallet: bool) { show_wallet: bool) {
let show_list = self.show_wallets_at_dual_panel; let show_list = self.show_wallets_at_dual_panel;
let creating_wallet = self.creating_wallet();
// Setup title. // Setup title.
let title_content = if self.wallets.is_selected_open() && (!dual_panel let title_content = if show_wallet && (!dual_panel
|| (dual_panel && !show_list)) && !create_wallet { || (dual_panel && !show_list)) && !creating_wallet {
let title_text = self.wallet_content.current_tab.get_type().name().to_uppercase(); let wallet_content = self.wallet_content.as_ref().unwrap();
if self.wallet_content.current_tab.get_type() == WalletTabType::Settings { 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)) TitleType::Single(TitleContentType::Title(title_text))
} else { } 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)) TitleType::Single(TitleContentType::WithSubTitle(title_text, subtitle_text, false))
} }
} else { } else {
let title_text = if create_wallet { let title_text = if creating_wallet {
t!("wallets.add") t!("wallets.add")
} else { } else {
t!("wallets.title") t!("wallets.title")
}.to_uppercase(); }.to_uppercase();
let dual_title = !create_wallet && show_wallet && dual_panel; let dual_title = !creating_wallet && show_wallet && dual_panel;
if dual_title { if dual_title {
let wallet_tab_type = self.wallet_content.current_tab.get_type(); let wallet_content = self.wallet_content.as_ref().unwrap();
let wallet_tab_name = wallet_tab_type.name().to_uppercase(); let wallet_tab_type = wallet_content.current_tab.get_type();
let title_content = if wallet_tab_type == WalletTabType::Settings { let wallet_title_text = wallet_tab_type.name().to_uppercase();
TitleContentType::Title(wallet_tab_name) let wallet_title_content = if wallet_tab_type == WalletTabType::Settings {
TitleContentType::Title(wallet_title_text)
} else { } else {
let subtitle_text = self.wallets.selected_name(); let subtitle_text = wallet_content.wallet.get_config().name;
TitleContentType::WithSubTitle(wallet_tab_name, subtitle_text, false) 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 { } else {
TitleType::Single(TitleContentType::Title(title_text)) TitleType::Single(TitleContentType::Title(title_text))
} }
@ -370,12 +408,20 @@ impl WalletsContent {
TitlePanel::new(Id::new("wallets_title_panel")).ui(title_content, |ui| { TitlePanel::new(Id::new("wallets_title_panel")).ui(title_content, |ui| {
if show_wallet && !dual_panel { if show_wallet && !dual_panel {
View::title_button_big(ui, ARROW_LEFT, |_| { View::title_button_big(ui, ARROW_LEFT, |_| {
self.wallets.select(None); self.wallet_content = None;
}); });
} else if create_wallet { } 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, |_| { View::title_button_big(ui, ARROW_LEFT, |_| {
self.creation_content.back(); if creation.on_back() {
close = true;
}
}); });
}
if close {
self.creation_content = None;
}
} else if show_wallet && dual_panel { } else if show_wallet && dual_panel {
let list_icon = if show_list { let list_icon = if show_list {
SIDEBAR_SIMPLE SIDEBAR_SIMPLE
@ -402,29 +448,6 @@ impl WalletsContent {
}, ui); }, 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. /// Draw list of wallets.
fn wallet_list_ui(&mut self, fn wallet_list_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
@ -445,7 +468,7 @@ impl WalletsContent {
list.retain(|w| { list.retain(|w| {
let deleted = w.is_deleted(); let deleted = w.is_deleted();
if deleted { if deleted {
self.wallets.select(None); self.wallet_content = None;
self.wallets.remove(w.get_config().id); self.wallets.remove(w.get_config().id);
ui.ctx().request_repaint(); ui.ctx().request_repaint();
} }
@ -453,9 +476,9 @@ impl WalletsContent {
}); });
for wallet in &list { for wallet in &list {
// Check if wallet reopen is needed. // Check if wallet reopen is needed.
if !wallet.is_open() && wallet.reopen_needed() { if wallet.reopen_needed() && !wallet.is_open() {
wallet.set_reopen(false); 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. // Draw wallet list item.
self.wallet_item_ui(ui, wallet, cb); self.wallet_item_ui(ui, wallet, cb);
@ -472,9 +495,11 @@ impl WalletsContent {
wallet: &Wallet, wallet: &Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
let config = wallet.get_config(); let config = wallet.get_config();
let id = config.id; let current = if let Some(content) = &self.wallet_content {
let is_selected = self.wallets.selected_id == Some(id); content.wallet.get_config().id == config.id && wallet.is_open()
let current = is_selected && wallet.is_open(); } else {
false
};
// Draw round background. // Draw round background.
let mut rect = ui.available_rect_before_wrap(); let mut rect = ui.available_rect_before_wrap();
@ -491,26 +516,25 @@ impl WalletsContent {
if !wallet.is_open() { if !wallet.is_open() {
// Show button to open closed wallet. // Show button to open closed wallet.
View::item_button(ui, View::item_rounding(0, 1, true), FOLDER_OPEN, None, || { 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. // Show button to select connection if not syncing.
if !wallet.syncing() { if !wallet.syncing() {
View::item_button(ui, Rounding::default(), GLOBE, None, || { View::item_button(ui, Rounding::default(), GLOBE, None, || {
self.wallets.select(Some(id)); self.wallet_content = Some(WalletContent::new(wallet.clone(), None));
self.show_connection_selector_modal(wallet); self.show_connection_selection_modal(wallet);
}); });
} }
} else { } else {
if !is_selected { if !current {
// Show button to select opened wallet. // Show button to select opened wallet.
View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, None, || { View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, None, || {
self.wallets.select(Some(id)); self.wallet_content = Some(WalletContent::new(wallet.clone(), None));
self.wallet_content = WalletContent::new(None);
}); });
} }
// Show button to close opened wallet. // Show button to close opened wallet.
if !wallet.is_closing() { if !wallet.is_closing() {
View::item_button(ui, if !is_selected { View::item_button(ui, if !current {
Rounding::default() Rounding::default()
} else { } else {
View::item_rounding(0, 1, true) View::item_rounding(0, 1, true)
@ -526,7 +550,7 @@ impl WalletsContent {
ui.vertical(|ui| { ui.vertical(|ui| {
ui.add_space(3.0); ui.add_space(3.0);
// Show wallet name text. // Show wallet name text.
let name_color = if is_selected { let name_color = if current {
Colors::white_or_black(true) Colors::white_or_black(true)
} else { } else {
Colors::title(false) Colors::title(false)
@ -537,14 +561,16 @@ impl WalletsContent {
}); });
// Show wallet status text. // 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); ui.add_space(1.0);
// Show wallet connection text. // Show wallet connection text.
let conn_text = if let Some(conn) = wallet.get_current_ext_conn() { let connection = wallet.get_current_connection();
format!("{} {}", GLOBE_SIMPLE, conn.url) let conn_text = match connection {
} else { ConnectionMethod::Integrated => {
format!("{} {}", COMPUTER_TOWER, t!("network.node")) 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.label(RichText::new(conn_text).size(15.0).color(Colors::gray()));
ui.add_space(3.0); ui.add_space(3.0);
@ -554,21 +580,23 @@ impl WalletsContent {
} }
/// Show [`Modal`] to select connection for the wallet. /// Show [`Modal`] to select connection for the wallet.
fn show_connection_selector_modal(&mut self, wallet: &Wallet) { fn show_connection_selection_modal(&mut self, wallet: &Wallet) {
let ext_conn = wallet.get_current_ext_conn(); let ext_conn = wallet.get_current_connection();
self.conn_selection_content = Some(WalletConnectionModal::new(ext_conn)); self.conn_selection_content = Some(WalletConnectionModal::new(ext_conn));
// Show modal. // Show modal.
Modal::new(CONNECTION_SELECTION_MODAL) Modal::new(SELECT_CONNECTION_MODAL)
.position(ModalPosition::CenterTop) .position(ModalPosition::CenterTop)
.title(t!("wallets.conn_method")) .title(t!("wallets.conn_method"))
.show(); .show();
} }
/// Show [`Modal`] to select and open wallet. /// Show [`Modal`] to select and open wallet.
fn show_opening_modal(&mut self, id: i64, data: Option<String>, cb: &dyn PlatformCallbacks) { fn show_opening_modal(&mut self,
self.wallets.select(Some(id)); wallet: Wallet,
self.open_wallet_content = Some(OpenWalletModal::new(data)); data: Option<String>,
// Show modal. 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) Modal::new(OPEN_WALLET_MODAL)
.position(ModalPosition::CenterTop) .position(ModalPosition::CenterTop)
.title(t!("wallets.open")) .title(t!("wallets.open"))

View file

@ -17,10 +17,10 @@ use egui::scroll_area::ScrollBarVisibility;
use grin_util::ZeroingString; use grin_util::ZeroingString;
use crate::gui::Colors; 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::platform::PlatformCallbacks;
use crate::gui::views::{Modal, Content, View}; use crate::gui::views::{Modal, Content, View, CameraScanModal};
use crate::gui::views::types::{ModalPosition, TextEditOptions}; use crate::gui::views::types::{ModalContainer, ModalPosition, QrScanResult};
use crate::gui::views::wallets::creation::MnemonicSetup; use crate::gui::views::wallets::creation::MnemonicSetup;
use crate::gui::views::wallets::creation::types::Step; use crate::gui::views::wallets::creation::types::Step;
use crate::gui::views::wallets::ConnectionSettings; use crate::gui::views::wallets::ConnectionSettings;
@ -30,53 +30,93 @@ use crate::wallet::types::PhraseMode;
/// Wallet creation content. /// Wallet creation content.
pub struct WalletCreation { pub struct WalletCreation {
/// Wallet creation step. /// Wallet name.
step: Option<Step>, pub name: String,
/// Wallet password.
pub pass: ZeroingString,
/// Flag to check if wallet creation [`Modal`] was just opened to focus on first field. /// Wallet creation step.
modal_just_opened: bool, step: Step,
/// Wallet name value.
name_edit: String, /// QR code scanning [`Modal`] content.
/// Password to encrypt created wallet. scan_modal_content: Option<CameraScanModal>,
pass_edit: String,
/// Mnemonic phrase setup content. /// Mnemonic phrase setup content.
pub(crate) mnemonic_setup: MnemonicSetup, mnemonic_setup: MnemonicSetup,
/// Network setup content. /// Network setup content.
pub(crate) network_setup: ConnectionSettings, network_setup: ConnectionSettings,
/// Flag to check if an error occurred during wallet creation. /// Flag to check if an error occurred during wallet creation.
creation_error: Option<String>, creation_error: Option<String>,
/// [`Modal`] identifiers allowed at this ui container.
modal_ids: Vec<&'static str>
} }
impl Default for WalletCreation { const QR_CODE_PHRASE_SCAN_MODAL: &'static str = "qr_code_rec_phrase_scan_modal";
fn default() -> Self {
Self { impl ModalContainer for WalletCreation {
step: None, fn modal_ids(&self) -> &Vec<&'static str> {
modal_just_opened: true, &self.modal_ids
name_edit: String::from(""), }
pass_edit: String::from(""),
mnemonic_setup: MnemonicSetup::default(), fn modal_ui(&mut self,
network_setup: ConnectionSettings::default(), ui: &mut egui::Ui,
creation_error: None, 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 { impl WalletCreation {
/// Wallet name/password input modal identifier. /// Create new wallet creation instance from name and password.
pub const NAME_PASS_MODAL: &'static str = "name_pass_modal"; 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. /// Draw wallet creation content.
pub fn ui(&mut self, pub fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
cb: &dyn PlatformCallbacks, cb: &dyn PlatformCallbacks,
on_create: impl FnOnce(Wallet)) { on_create: impl FnMut(Wallet)) {
self.current_modal_ui(ui, cb);
// Show wallet creation step description and confirmation panel. // Show wallet creation step description and confirmation panel.
if self.step.is_some() {
egui::TopBottomPanel::bottom("wallet_creation_step_panel") egui::TopBottomPanel::bottom("wallet_creation_step_panel")
.frame(egui::Frame { .frame(egui::Frame {
fill: Colors::fill(), stroke: View::item_stroke(),
fill: Colors::fill_deep(),
inner_margin: Margin { inner_margin: Margin {
left: View::far_left_inset_margin(ui) + 8.0, left: View::far_left_inset_margin(ui) + 8.0,
right: View::get_right_inset() + 8.0, right: View::get_right_inset() + 8.0,
@ -95,12 +135,10 @@ impl WalletCreation {
}); });
}); });
}
// Show wallet creation step content panel. // Show wallet creation step content panel.
egui::CentralPanel::default() egui::CentralPanel::default()
.frame(egui::Frame { .frame(egui::Frame {
stroke: View::item_stroke(),
inner_margin: Margin { inner_margin: Margin {
left: View::far_left_inset_margin(ui) + 4.0, left: View::far_left_inset_margin(ui) + 4.0,
right: View::get_right_inset() + 4.0, right: View::get_right_inset() + 4.0,
@ -110,18 +148,13 @@ impl WalletCreation {
..Default::default() ..Default::default()
}) })
.show_inside(ui, |ui| { .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() ScrollArea::vertical()
.id_source(id) .id_source(Id::from(format!("creation_step_scroll_{}", self.step.name())))
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
.auto_shrink([false; 2]) .auto_shrink([false; 2])
.show(ui, |ui| { .show(ui, |ui| {
ui.vertical_centered(|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 Content::SIDE_PANEL_WIDTH * 1.3
} else { } else {
Content::SIDE_PANEL_WIDTH * 2.0 Content::SIDE_PANEL_WIDTH * 2.0
@ -139,9 +172,9 @@ impl WalletCreation {
ui: &mut egui::Ui, ui: &mut egui::Ui,
on_create: impl FnOnce(Wallet), on_create: impl FnOnce(Wallet),
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
if let Some(step) = self.step.clone() { let step = &self.step;
// Setup step description text and availability. // Setup description and next step availability.
let (step_text, mut step_available) = match step { let (step_text, mut next) = match step {
Step::EnterMnemonic => { Step::EnterMnemonic => {
let mode = &self.mnemonic_setup.mnemonic.mode(); let mode = &self.mnemonic_setup.mnemonic.mode();
let (text, available) = match mode { let (text, available) = match mode {
@ -164,7 +197,7 @@ impl WalletCreation {
}; };
// Show step description or error. // Show step description or error.
let generate_step = step == Step::EnterMnemonic && let generate_step = step == &Step::EnterMnemonic &&
self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate; self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate;
if (self.mnemonic_setup.mnemonic.valid() && self.creation_error.is_none()) || if (self.mnemonic_setup.mnemonic.valid() && self.creation_error.is_none()) ||
generate_step { generate_step {
@ -172,7 +205,7 @@ impl WalletCreation {
ui.label(RichText::new(step_text).size(16.0).color(Colors::gray())); ui.label(RichText::new(step_text).size(16.0).color(Colors::gray()));
ui.add_space(2.0); ui.add_space(2.0);
} else { } else {
step_available = false; next = false;
// Show error text. // Show error text.
if let Some(err) = &self.creation_error { if let Some(err) = &self.creation_error {
ui.add_space(10.0); ui.add_space(10.0);
@ -188,7 +221,10 @@ impl WalletCreation {
ui.add_space(2.0); ui.add_space(2.0);
}; };
} }
if step == Step::EnterMnemonic {
// Setup buttons.
match step {
Step::EnterMnemonic => {
ui.add_space(4.0); ui.add_space(4.0);
// Setup spacing between buttons. // Setup spacing between buttons.
@ -197,28 +233,56 @@ impl WalletCreation {
ui.columns(2, |columns| { ui.columns(2, |columns| {
// Show copy or paste button for mnemonic phrase step. // Show copy or paste button for mnemonic phrase step.
columns[0].vertical_centered_justified(|ui| { 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| { columns[1].vertical_centered_justified(|ui| {
if step_available { if next {
// Show next step button if there are no empty words. self.next_step_button_ui(ui, on_create);
self.next_step_button_ui(ui, step, on_create);
} else { } else {
// Show QR code scan button.
let scan_text = format!("{} {}", SCAN, t!("scan").to_uppercase()); let scan_text = format!("{} {}", SCAN, t!("scan").to_uppercase());
View::button(ui, scan_text, Colors::white_or_black(false), || { 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); ui.add_space(4.0);
} else if step == Step::ConfirmMnemonic { }
Step::ConfirmMnemonic => {
ui.add_space(4.0); ui.add_space(4.0);
// Show next step or paste button. // Show next step or paste button.
if step_available { if next {
self.next_step_button_ui(ui, step, on_create); self.next_step_button_ui(ui, on_create);
} else { } else {
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase()); let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase());
View::button(ui, paste_text, Colors::white_or_black(false), || { View::button(ui, paste_text, Colors::white_or_black(false), || {
@ -227,96 +291,64 @@ impl WalletCreation {
}); });
} }
ui.add_space(4.0); 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);
} }
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(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);
});
}
} }
ui.add_space(3.0);
} }
/// Draw button to go to next [`Step`]. /// Draw button to go to next [`Step`].
fn next_step_button_ui(&mut self, fn next_step_button_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
step: Step,
on_create: impl FnOnce(Wallet)) { on_create: impl FnOnce(Wallet)) {
// Setup button text. // 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()) (format!("{} {}", CHECK, t!("complete")), Colors::title(true), Colors::gold())
} else { } else {
let text = format!("{} {}", SHARE_FAT, t!("continue")); (t!("continue"), Colors::text_button(), Colors::white_or_black(false))
(text, Colors::text_button(), Colors::white_or_black(false))
}; };
// Show next step button. // Show next step button.
View::colored_text_button(ui, next_text.to_uppercase(), text_color, bg_color, || { View::colored_text_button(ui, next_text.to_uppercase(), text_color, bg_color, || {
self.step = if let Some(step) = &self.step { self.step = match self.step {
match step {
Step::EnterMnemonic => { Step::EnterMnemonic => {
if self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate { if self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate {
Some(Step::ConfirmMnemonic) Step::ConfirmMnemonic
} else { } else {
Some(Step::SetupConnection) Step::SetupConnection
} }
} }
Step::ConfirmMnemonic => { Step::ConfirmMnemonic => {
Some(Step::SetupConnection) Step::SetupConnection
}, },
Step::SetupConnection => { Step::SetupConnection => {
// Create wallet at last step. // Create wallet at last step.
let conn_method = &self.network_setup.method; match Wallet::create(&self.name,
match Wallet::create(&self.name_edit, &self.pass,
&self.pass_edit,
&self.mnemonic_setup.mnemonic, &self.mnemonic_setup.mnemonic,
conn_method) { &self.network_setup.method) {
Ok(mut w) => { Ok(w) => {
// Open created wallet. self.mnemonic_setup.reset();
w.open(&self.pass_edit).unwrap();
// Pass created wallet to callback. // Pass created wallet to callback.
(on_create)(w); (on_create)(w);
// Reset input data. Step::EnterMnemonic
self.step = None;
self.name_edit = String::from("");
self.pass_edit = String::from("");
self.mnemonic_setup.reset();
None
} }
Err(e) => { Err(e) => {
self.creation_error = Some(format!("{:?}", e)); self.creation_error = Some(format!("{:?}", e));
Some(Step::SetupConnection) Step::SetupConnection
} }
} }
} }
}
} else {
Some(Step::EnterMnemonic)
}; };
// Check external connections availability on connection setup. // Check external connections availability on connection setup.
if self.step == Some(Step::SetupConnection) { if self.step == Step::SetupConnection {
ExternalConnection::check_ext_conn_availability(None); ExternalConnection::check_ext_conn_availability(None);
} }
}); });
@ -325,28 +357,6 @@ impl WalletCreation {
/// Draw wallet creation [`Step`] content. /// Draw wallet creation [`Step`] content.
fn step_content_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { fn step_content_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
match &self.step { 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::EnterMnemonic => self.mnemonic_setup.ui(ui, cb),
Step::ConfirmMnemonic => self.mnemonic_setup.confirm_ui(ui, cb), Step::ConfirmMnemonic => self.mnemonic_setup.confirm_ui(ui, cb),
Step::SetupConnection => { Step::SetupConnection => {
@ -358,119 +368,20 @@ impl WalletCreation {
} }
} }
} }
}
}
/// Check if it's possible to go back for current step. /// Back to previous wallet creation [`Step`], return `true` to close creation.
pub fn can_go_back(&self) -> bool { pub fn on_back(&mut self) -> bool {
self.step.is_some()
}
/// Back to previous wallet creation [`Step`].
pub fn back(&mut self) {
match &self.step { match &self.step {
None => {} Step::ConfirmMnemonic => {
Some(step) => { self.step = Step::EnterMnemonic;
match step { false
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 => { Step::SetupConnection => {
self.creation_error = None; self.creation_error = None;
self.step = Some(Step::EnterMnemonic) 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);
});
}
} }

View file

@ -17,8 +17,8 @@ use egui::{Id, RichText};
use crate::gui::Colors; use crate::gui::Colors;
use crate::gui::icons::PENCIL; use crate::gui::icons::PENCIL;
use crate::gui::platform::PlatformCallbacks; use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{CameraContent, Modal, Content, View}; use crate::gui::views::{Modal, Content, View};
use crate::gui::views::types::{ModalContainer, ModalPosition, QrScanResult, TextEditOptions}; use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions};
use crate::wallet::Mnemonic; use crate::wallet::Mnemonic;
use crate::wallet::types::{PhraseMode, PhraseSize, PhraseWord}; use crate::wallet::types::{PhraseMode, PhraseSize, PhraseWord};
@ -34,11 +34,6 @@ pub struct MnemonicSetup {
/// Flag to check if entered word is valid at [`Modal`]. /// Flag to check if entered word is valid at [`Modal`].
valid_word_edit: bool, 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<bool>,
/// [`Modal`] identifiers allowed at this ui container. /// [`Modal`] identifiers allowed at this ui container.
modal_ids: Vec<&'static str> modal_ids: Vec<&'static str>
} }
@ -46,9 +41,6 @@ pub struct MnemonicSetup {
/// Identifier for word input [`Modal`]. /// Identifier for word input [`Modal`].
pub const WORD_INPUT_MODAL: &'static str = "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 { impl Default for MnemonicSetup {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -56,11 +48,8 @@ impl Default for MnemonicSetup {
word_index_edit: 0, word_index_edit: 0,
word_edit: String::from(""), word_edit: String::from(""),
valid_word_edit: true, valid_word_edit: true,
camera_content: CameraContent::default(),
scan_phrase_not_found: None,
modal_ids: vec![ modal_ids: vec![
WORD_INPUT_MODAL, WORD_INPUT_MODAL
QR_CODE_PHRASE_SCAN_MODAL
] ]
} }
} }
@ -77,7 +66,6 @@ impl ModalContainer for MnemonicSetup {
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
match modal.id { match modal.id {
WORD_INPUT_MODAL => self.word_modal_ui(ui, modal, cb), 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); 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. /// Calculate word list columns count based on available ui width.

View file

@ -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);
});
}
}

View file

@ -22,27 +22,24 @@ use crate::gui::views::{Modal, View};
use crate::gui::views::network::ConnectionsContent; use crate::gui::views::network::ConnectionsContent;
use crate::gui::views::network::modals::ExternalConnectionModal; use crate::gui::views::network::modals::ExternalConnectionModal;
use crate::wallet::{ConnectionsConfig, ExternalConnection}; use crate::wallet::{ConnectionsConfig, ExternalConnection};
use crate::wallet::types::ConnectionMethod;
/// Wallet connection [`Modal`] content. /// Wallet connection selection [`Modal`] content.
pub struct WalletConnectionModal { pub struct WalletConnectionModal {
/// Current external connection. /// Current connection method.
pub ext_conn: Option<ExternalConnection>, pub conn: ConnectionMethod,
/// Flag to show connection creation. /// External connection content.
show_conn_creation: bool, ext_conn_content: Option<ExternalConnectionModal>
/// External connection creation content.
add_ext_conn_content: ExternalConnectionModal
} }
impl WalletConnectionModal { impl WalletConnectionModal {
/// Create from provided wallet connection. /// Create from provided wallet connection.
pub fn new(ext_conn: Option<ExternalConnection>) -> Self { pub fn new(conn: ConnectionMethod) -> Self {
ExternalConnection::check_ext_conn_availability(None); ExternalConnection::check_ext_conn_availability(None);
Self { Self {
ext_conn, conn,
show_conn_creation: false, ext_conn_content: None,
add_ext_conn_content: ExternalConnectionModal::new(None),
} }
} }
@ -51,17 +48,17 @@ impl WalletConnectionModal {
ui: &mut egui::Ui, ui: &mut egui::Ui,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks, cb: &dyn PlatformCallbacks,
on_select: impl Fn(Option<i64>)) { on_select: impl Fn(ConnectionMethod)) {
ui.add_space(4.0); // Draw external connection content.
if let Some(ext_content) = self.ext_conn_content.as_mut() {
// Draw external connection creation content. ext_content.ui(ui, cb, modal, |conn| {
if self.show_conn_creation { on_select(ConnectionMethod::External(conn.id, conn.url));
self.add_ext_conn_content.ui(ui, cb, modal, |conn| {
on_select(Some(conn.id));
}); });
return; return;
} }
ui.add_space(4.0);
let ext_conn_list = ConnectionsConfig::ext_conn_list(); let ext_conn_list = ConnectionsConfig::ext_conn_list();
ScrollArea::vertical() ScrollArea::vertical()
.max_height(if ext_conn_list.len() < 4 { .max_height(if ext_conn_list.len() < 4 {
@ -77,52 +74,54 @@ impl WalletConnectionModal {
// Show integrated node selection. // Show integrated node selection.
ConnectionsContent::integrated_node_item_ui(ui, |ui| { ConnectionsContent::integrated_node_item_ui(ui, |ui| {
let is_current_method = self.ext_conn.is_none(); match self.conn {
if !is_current_method { ConnectionMethod::Integrated => {
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.add_space(14.0);
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green())); ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green()));
ui.add_space(14.0); 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.add_space(8.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("wallets.ext_conn")) ui.label(RichText::new(t!("wallets.ext_conn"))
.size(16.0) .size(16.0)
.color(Colors::gray())); .color(Colors::gray()));
ui.add_space(6.0); ui.add_space(6.0);
// Show button to add new external node connection.
let add_node_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add_node")); let add_node_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add_node"));
View::button(ui, add_node_text, Colors::button(), || { 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); ui.add_space(4.0);
if !ext_conn_list.is_empty() { if !ext_conn_list.is_empty() {
ui.add_space(8.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() {
if conn.deleted {
continue;
}
ui.horizontal_wrapped(|ui| { ui.horizontal_wrapped(|ui| {
// Draw external connection item.
let len = ext_conn_list.len(); let len = ext_conn_list.len();
ConnectionsContent::ext_conn_item_ui(ui, conn, index, len, |ui| { ConnectionsContent::ext_conn_item_ui(ui, conn, index, len, |ui| {
// Draw button to select connection. let current_ext_conn = match self.conn {
let is_current_method = if let Some(c) = self.ext_conn.as_ref() { ConnectionMethod::Integrated => false,
c.id == conn.id ConnectionMethod::External(id, _) => id == conn.id
} else {
false
}; };
if !is_current_method { if !current_ext_conn {
let button_rounding = View::item_rounding(index, len, true); let button_rounding = View::item_rounding(index, len, true);
View::item_button(ui, button_rounding, CHECK, None, || { View::item_button(ui, button_rounding, CHECK, None, || {
self.ext_conn = Some(conn.clone()); on_select(
on_select(Some(conn.id)); ConnectionMethod::External(conn.id, conn.url.clone())
);
modal.close(); modal.close();
}); });
} else { } else {

View file

@ -20,3 +20,6 @@ pub use wallets::*;
mod open; mod open;
pub use open::*; pub use open::*;
mod add;
pub use add::*;

View file

@ -13,15 +13,19 @@
// limitations under the License. // limitations under the License.
use egui::{Id, RichText}; use egui::{Id, RichText};
use grin_util::ZeroingString;
use crate::gui::Colors; use crate::gui::Colors;
use crate::gui::platform::PlatformCallbacks; use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, View}; use crate::gui::views::{Modal, View};
use crate::gui::views::types::TextEditOptions; use crate::gui::views::types::TextEditOptions;
use crate::wallet::WalletList; use crate::wallet::Wallet;
/// Wallet opening [`Modal`] content. /// Wallet opening [`Modal`] content.
pub struct OpenWalletModal { pub struct OpenWalletModal {
/// Wallet to open.
wallet: Wallet,
/// Password to open wallet. /// Password to open wallet.
pass_edit: String, pass_edit: String,
/// Flag to check if wrong password was entered. /// Flag to check if wrong password was entered.
@ -33,8 +37,9 @@ pub struct OpenWalletModal {
impl OpenWalletModal { impl OpenWalletModal {
/// Create new content instance. /// Create new content instance.
pub fn new(data: Option<String>) -> Self { pub fn new(wallet: Wallet, data: Option<String>) -> Self {
Self { Self {
wallet,
pass_edit: "".to_string(), pass_edit: "".to_string(),
wrong_pass: false, wrong_pass: false,
data, data,
@ -44,9 +49,8 @@ impl OpenWalletModal {
pub fn ui(&mut self, pub fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
modal: &Modal, modal: &Modal,
wallets: &mut WalletList,
cb: &dyn PlatformCallbacks, cb: &dyn PlatformCallbacks,
mut on_continue: impl FnMut(Option<String>)) { mut on_continue: impl FnMut(Wallet, Option<String>)) {
ui.add_space(6.0); ui.add_space(6.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("wallets.pass")) ui.label(RichText::new(t!("wallets.pass"))
@ -90,18 +94,16 @@ impl OpenWalletModal {
columns[1].vertical_centered_justified(|ui| { columns[1].vertical_centered_justified(|ui| {
// Callback for button to continue. // Callback for button to continue.
let mut on_continue = || { let mut on_continue = || {
if self.pass_edit.is_empty() { let pass = self.pass_edit.clone();
if pass.is_empty() {
return; return;
} }
match wallets.open_selected(&self.pass_edit) { match self.wallet.open(ZeroingString::from(pass)) {
Ok(_) => { Ok(_) => {
// Clear values.
self.pass_edit = "".to_string(); self.pass_edit = "".to_string();
self.wrong_pass = false;
// Close modal.
cb.hide_keyboard(); cb.hide_keyboard();
modal.close(); modal.close();
on_continue(self.data.clone()); on_continue(self.wallet.clone(), self.data.clone());
} }
Err(_) => self.wrong_pass = true Err(_) => self.wrong_pass = true
} }

View file

@ -21,13 +21,14 @@ use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, View}; use crate::gui::views::{Modal, View};
use crate::gui::views::types::ModalPosition; use crate::gui::views::types::ModalPosition;
use crate::gui::views::wallets::modals::OpenWalletModal; 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::{Wallet, WalletList};
use crate::wallet::types::ConnectionMethod;
/// Wallet list [`Modal`] content /// Wallet list [`Modal`] content
pub struct WalletsModal { pub struct WalletsModal {
/// Selected wallet id. /// Selected wallet id.
selected: Option<i64>, selected_id: Option<i64>,
/// Optional data to pass after wallet selection. /// Optional data to pass after wallet selection.
data: Option<String>, data: Option<String>,
@ -40,24 +41,21 @@ pub struct WalletsModal {
impl WalletsModal { impl WalletsModal {
/// Create new content instance. /// Create new content instance.
pub fn new(selected: Option<i64>, data: Option<String>, can_open: bool) -> Self { pub fn new(selected_id: Option<i64>, data: Option<String>, can_open: bool) -> Self {
Self { selected, data, can_open, open_wallet_content: None } Self { selected_id, data, can_open, open_wallet_content: None }
} }
/// Draw content. /// Draw content.
pub fn ui(&mut self, pub fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
modal: &Modal, modal: &Modal,
wallets: &mut WalletList, wallets: &WalletList,
cb: &dyn PlatformCallbacks, cb: &dyn PlatformCallbacks,
mut on_select: impl FnMut(i64, Option<String>)) { mut on_select: impl FnMut(Wallet, Option<String>)) {
// Draw wallet opening content if requested. // Draw wallet opening modal content.
if let Some(open_content) = self.open_wallet_content.as_mut() { if let Some(open_content) = self.open_wallet_content.as_mut() {
open_content.ui(ui, modal, wallets, cb, |data| { open_content.ui(ui, modal, cb, |wallet, data| {
modal.close(); on_select(wallet, data);
if let Some(id) = self.selected {
on_select(id, data);
}
self.data = None; self.data = None;
}); });
return; return;
@ -73,11 +71,11 @@ impl WalletsModal {
ui.add_space(2.0); ui.add_space(2.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
let data = self.data.clone(); let data = self.data.clone();
for wallet in wallets.clone().list() { for wallet in wallets.list() {
// Draw wallet list item. // Draw wallet list item.
self.wallet_item_ui(ui, wallet, wallets, |id| { self.wallet_item_ui(ui, wallet, || {
modal.close(); modal.close();
on_select(id, data.clone()); on_select(wallet.clone(), data.clone());
}); });
ui.add_space(5.0); ui.add_space(5.0);
} }
@ -102,8 +100,7 @@ impl WalletsModal {
fn wallet_item_ui(&mut self, fn wallet_item_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &Wallet, wallet: &Wallet,
wallets: &mut WalletList, on_select: impl FnOnce()) {
mut select: impl FnMut(i64)) {
let config = wallet.get_config(); let config = wallet.get_config();
let id = config.id; let id = config.id;
@ -122,24 +119,24 @@ impl WalletsModal {
FOLDER_OPEN FOLDER_OPEN
}; };
View::item_button(ui, View::item_rounding(0, 1, true), icon, None, || { View::item_button(ui, View::item_rounding(0, 1, true), icon, None, || {
wallets.select(Some(id));
if wallet.is_open() { if wallet.is_open() {
select(id); on_select();
} else { } else {
self.selected = wallets.selected_id;
Modal::change_position(ModalPosition::CenterTop); 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 { } else {
// Draw button to select wallet. // Draw button to select wallet.
let current = self.selected.unwrap_or(0) == id; let current = self.selected_id.unwrap_or(0) == id;
if current { if current {
ui.add_space(12.0); ui.add_space(12.0);
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green())); ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green()));
} else { } else {
View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || { 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. // Show wallet connection text.
let conn = if let Some(conn) = wallet.get_current_ext_conn() { let connection = wallet.get_current_connection();
format!("{} {}", GLOBE_SIMPLE, conn.url) let conn_text = match connection {
} else { ConnectionMethod::Integrated => {
format!("{} {}", COMPUTER_TOWER, t!("network.node")) 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); ui.add_space(1.0);
// Show wallet API text or open status. // Show wallet API text or open status.
if self.can_open { if self.can_open {
ui.label(RichText::new(status_text(wallet)) ui.label(RichText::new(wallet_status_text(wallet))
.size(15.0) .size(15.0)
.color(Colors::gray())); .color(Colors::gray()));
} else { } else {

View file

@ -21,23 +21,25 @@ use crate::AppConfig;
use crate::gui::Colors; 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::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::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::types::{ModalPosition, QrScanResult};
use crate::gui::views::wallets::{WalletTransactions, WalletMessages, WalletTransport}; use crate::gui::views::wallets::{WalletTransactions, WalletMessages, WalletTransport};
use crate::gui::views::wallets::types::{GRIN, WalletTab, WalletTabType}; 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::gui::views::wallets::wallet::WalletSettings;
use crate::node::Node; use crate::node::Node;
use crate::wallet::{Wallet, WalletConfig}; 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 { pub struct WalletContent {
/// Selected and opened wallet.
pub wallet: Wallet,
/// Wallet accounts [`Modal`] content. /// Wallet accounts [`Modal`] content.
accounts_modal_content: Option<WalletAccountsModal>, accounts_modal_content: Option<WalletAccountsModal>,
/// QR code scan [`Modal`] content. /// QR code scan [`Modal`] content.
scan_modal_content: Option<WalletScanModal>, scan_modal_content: Option<CameraScanModal>,
/// Current tab content to show. /// Current tab content to show.
pub current_tab: Box<dyn WalletTab>, pub current_tab: Box<dyn WalletTab>,
@ -51,37 +53,41 @@ const QR_CODE_SCAN_MODAL: &'static str = "qr_code_scan_modal";
impl WalletContent { impl WalletContent {
/// Create new instance with optional data. /// Create new instance with optional data.
pub fn new(data: Option<String>) -> Self { pub fn new(wallet: Wallet, data: Option<String>) -> Self {
let mut content = Self { let mut content = Self {
wallet,
accounts_modal_content: None, accounts_modal_content: None,
scan_modal_content: None, scan_modal_content: None,
current_tab: Box::new(WalletTransactions::default()), current_tab: Box::new(WalletTransactions::default()),
}; };
// Provide data to messages.
if data.is_some() { if data.is_some() {
content.current_tab = Box::new(WalletMessages::new(data)); content.on_data(data);
} }
content content
} }
/// Handle data from deeplink or opened file.
pub fn on_data(&mut self, data: Option<String>) {
// Provide data to messages.
self.current_tab = Box::new(WalletMessages::new(data));
}
/// Draw wallet content. /// Draw wallet content.
pub fn ui(&mut self, pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
ui: &mut egui::Ui, self.modal_content_ui(ui, cb);
wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) {
// Show modal content for this ui container.
self.modal_content_ui(ui, wallet, cb);
let dual_panel = Content::is_dual_panel_mode(ui); let dual_panel = Content::is_dual_panel_mode(ui);
let wallet = &self.wallet;
let data = wallet.get_data(); let data = wallet.get_data();
let data_empty = data.is_none(); 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 // Show wallet balance panel not on Settings tab with selected non-repairing
// wallet, when there is no error and data is not empty. // wallet, when there is no error and data is not empty.
let mut show_balance = self.current_tab.get_type() != WalletTabType::Settings && !data_empty let mut show_balance = self.current_tab.get_type() != WalletTabType::Settings && !data_empty
&& !wallet.sync_error() && !wallet.is_repairing() && !wallet.is_closing(); && !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; show_balance = false;
} }
egui::TopBottomPanel::top(Id::from("wallet_balance").with(wallet.identifier())) egui::TopBottomPanel::top(Id::from("wallet_balance").with(wallet.identifier()))
@ -117,13 +123,12 @@ impl WalletContent {
} }
// Draw account info. // Draw account info.
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| { 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. // Show wallet tabs panel.
let show_tabs = !Self::block_navigation_on_sync(wallet);
egui::TopBottomPanel::bottom("wallet_tabs_content") egui::TopBottomPanel::bottom("wallet_tabs_content")
.frame(egui::Frame { .frame(egui::Frame {
fill: Colors::fill(), fill: Colors::fill(),
@ -135,7 +140,7 @@ impl WalletContent {
}, },
..Default::default() ..Default::default()
}) })
.show_animated_inside(ui, show_tabs, |ui| { .show_animated_inside(ui, !hide_tabs, |ui| {
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
// Draw wallet tabs. // Draw wallet tabs.
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| { View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
@ -162,7 +167,7 @@ impl WalletContent {
..Default::default() ..Default::default()
}) })
.show_inside(ui, |ui| { .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. // 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. /// Draw [`Modal`] content for this ui container.
fn modal_content_ui(&mut self, fn modal_content_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
ui: &mut egui::Ui,
wallet: &Wallet,
cb: &dyn PlatformCallbacks) {
match Modal::opened() { match Modal::opened() {
None => {} None => {}
Some(id) => { Some(id) => {
@ -185,29 +200,30 @@ impl WalletContent {
ACCOUNT_LIST_MODAL => { ACCOUNT_LIST_MODAL => {
if let Some(content) = self.accounts_modal_content.as_mut() { if let Some(content) = self.accounts_modal_content.as_mut() {
Modal::ui(ui.ctx(), |ui, modal| { Modal::ui(ui.ctx(), |ui, modal| {
content.ui(ui, wallet, modal, cb); content.ui(ui, &self.wallet, modal, cb);
}); });
} }
} }
QR_CODE_SCAN_MODAL => { QR_CODE_SCAN_MODAL => {
let mut success = false;
if let Some(content) = self.scan_modal_content.as_mut() { if let Some(content) = self.scan_modal_content.as_mut() {
Modal::ui(ui.ctx(), |ui, modal| { Modal::ui(ui.ctx(), |ui, modal| {
content.ui(ui, wallet, modal, cb, |result| { content.ui(ui, modal, cb, |result| {
match result { match result {
QrScanResult::Slatepack(message) => { QrScanResult::Slatepack(message) => {
modal.close(); success = true;
let msg = Some(message.to_string()); let msg = Some(message.to_string());
let messages = WalletMessages::new(msg); let messages = WalletMessages::new(msg);
self.current_tab = Box::new(messages); self.current_tab = Box::new(messages);
return; return;
} }
QrScanResult::Address(receiver) => { QrScanResult::Address(receiver) => {
let balance = wallet.get_data() success = true;
let balance = self.wallet.get_data()
.unwrap() .unwrap()
.info .info
.amount_currently_spendable; .amount_currently_spendable;
if balance > 0 { if balance > 0 {
modal.close();
let mut transport = WalletTransport::default(); let mut transport = WalletTransport::default();
let rec = Some(receiver.to_string()); let rec = Some(receiver.to_string());
transport.show_send_tor_modal(cb, rec); 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. /// Draw wallet account content.
fn account_ui(&mut self, fn account_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &Wallet,
data: WalletData, data: WalletData,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
let mut rect = ui.available_rect_before_wrap(); 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.painter().rect(rect, rounding, Colors::button(), View::hover_stroke());
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { 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, || { View::item_button(ui, View::item_rounding(0, 2, true), SCAN, None, || {
self.scan_modal_content = Some(WalletScanModal::default()); self.scan_modal_content = Some(CameraScanModal::default());
// Show QR code scan modal.
Modal::new(QR_CODE_SCAN_MODAL) Modal::new(QR_CODE_SCAN_MODAL)
.position(ModalPosition::CenterTop) .position(ModalPosition::CenterTop)
.title(t!("scan_qr")) .title(t!("scan_qr"))
@ -254,8 +274,9 @@ impl WalletContent {
// Draw button to show list of accounts. // Draw button to show list of accounts.
View::item_button(ui, View::item_rounding(1, 3, true), USERS_THREE, None, || { View::item_button(ui, View::item_rounding(1, 3, true), USERS_THREE, None, || {
self.accounts_modal_content = Some(WalletAccountsModal::new(wallet.accounts())); self.accounts_modal_content = Some(
// Show account list modal. WalletAccountsModal::new(self.wallet.accounts())
);
Modal::new(ACCOUNT_LIST_MODAL) Modal::new(ACCOUNT_LIST_MODAL)
.position(ModalPosition::CenterTop) .position(ModalPosition::CenterTop)
.title(t!("wallets.accounts")) .title(t!("wallets.accounts"))
@ -279,7 +300,7 @@ impl WalletContent {
ui.add_space(-2.0); ui.add_space(-2.0);
// Show account label. // 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 default_acc_label = WalletConfig::DEFAULT_ACCOUNT_LABEL.to_string();
let acc_label = if account == default_acc_label { let acc_label = if account == default_acc_label {
t!("wallets.default_account") t!("wallets.default_account")
@ -290,15 +311,15 @@ impl WalletContent {
View::ellipsize_text(ui, acc_text, 15.0, Colors::text(false)); View::ellipsize_text(ui, acc_text, 15.0, Colors::text(false));
// Show confirmed height or sync progress. // 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) format!("{} {}", PACKAGE, data.info.last_confirmed_height)
} else { } else {
let info_progress = wallet.info_sync_progress(); let info_progress = self.wallet.info_sync_progress();
if info_progress == 100 || info_progress == 0 { if info_progress == 100 || info_progress == 0 {
format!("{} {}", SPINNER, t!("wallets.wallet_loading")) format!("{} {}", SPINNER, t!("wallets.wallet_loading"))
} else { } else {
if wallet.is_repairing() { if self.wallet.is_repairing() {
let rep_progress = wallet.repairing_progress(); let rep_progress = self.wallet.repairing_progress();
if rep_progress == 0 { if rep_progress == 0 {
format!("{} {}", SPINNER, t!("wallets.wallet_checking")) format!("{} {}", SPINNER, t!("wallets.wallet_checking"))
} else { } 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() { } else if wallet.is_closing() {
Self::sync_progress_ui(ui, wallet); Self::sync_progress_ui(ui, wallet);
return true; 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() { if !Node::is_running() || Node::is_stopping() {
View::center_content(ui, 108.0, |ui| { View::center_content(ui, 108.0, |ui| {
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.5, |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. /// Draw wallet sync progress content.
pub fn sync_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) { pub fn sync_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) {
View::center_content(ui, 162.0, |ui| { View::center_content(ui, 162.0, |ui| {
@ -439,13 +451,13 @@ impl WalletContent {
ui.add_space(18.0); ui.add_space(18.0);
// Setup sync progress text. // Setup sync progress text.
let text = { let text = {
let integrated_node = wallet.get_current_ext_conn().is_none(); let int_node = wallet.get_current_connection() == ConnectionMethod::Integrated;
let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync); let int_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
let info_progress = wallet.info_sync_progress(); let info_progress = wallet.info_sync_progress();
if wallet.is_closing() { if wallet.is_closing() {
t!("wallets.wallet_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) t!("wallets.node_loading", "settings" => GEAR_FINE)
} else if wallet.is_repairing() { } else if wallet.is_repairing() {
let repair_progress = wallet.repairing_progress(); let repair_progress = wallet.repairing_progress();

View file

@ -74,7 +74,7 @@ impl WalletTab for WalletMessages {
WalletTabType::Messages 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) { if WalletContent::sync_ui(ui, wallet) {
return; return;
} }
@ -130,7 +130,7 @@ impl WalletMessages {
/// Draw manual wallet transaction interaction content. /// Draw manual wallet transaction interaction content.
pub fn ui(&mut self, pub fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
if self.first_draw { if self.first_draw {
// Parse provided message on first draw. // Parse provided message on first draw.
@ -158,7 +158,7 @@ impl WalletMessages {
/// Draw [`Modal`] content for this ui container. /// Draw [`Modal`] content for this ui container.
fn modal_content_ui(&mut self, fn modal_content_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
match Modal::opened() { match Modal::opened() {
None => {} None => {}
@ -244,7 +244,7 @@ impl WalletMessages {
/// Draw Slatepack message input content. /// Draw Slatepack message input content.
fn input_slatepack_ui(&mut self, fn input_slatepack_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
// Setup description text. // Setup description text.
if !self.message_error.is_empty() { if !self.message_error.is_empty() {
@ -524,7 +524,6 @@ impl WalletMessages {
return; return;
} else if let Some(result) = self.message_camera_content.qr_scan_result() { } else if let Some(result) = self.message_camera_content.qr_scan_result() {
cb.stop_camera(); cb.stop_camera();
self.message_camera_content.clear_state();
match &result { match &result {
QrScanResult::Slatepack(text) => { QrScanResult::Slatepack(text) => {
self.message_edit = text.to_string(); self.message_edit = text.to_string();

View file

@ -62,7 +62,7 @@ impl MessageRequestModal {
/// Draw [`Modal`] content. /// Draw [`Modal`] content.
pub fn ui(&mut self, pub fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
// Draw transaction information on request result. // Draw transaction information on request result.
@ -147,7 +147,7 @@ impl MessageRequestModal {
/// Draw amount input content. /// Draw amount input content.
fn amount_input_ui(&mut self, fn amount_input_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
@ -211,7 +211,7 @@ impl MessageRequestModal {
} }
/// Draw loading request content. /// 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.add_space(34.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
View::big_loading_spinner(ui); View::big_loading_spinner(ui);

View file

@ -14,6 +14,3 @@
mod accounts; mod accounts;
pub use accounts::*; pub use accounts::*;
mod scan;
pub use scan::*;

View file

@ -61,7 +61,7 @@ impl Default for CommonSettings {
impl CommonSettings { impl CommonSettings {
/// Draw common wallet settings content. /// 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. // Show modal content for this ui container.
self.modal_content_ui(ui, wallet, cb); self.modal_content_ui(ui, wallet, cb);
@ -146,7 +146,7 @@ impl CommonSettings {
/// Draw [`Modal`] content for this ui container. /// Draw [`Modal`] content for this ui container.
fn modal_content_ui(&mut self, fn modal_content_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
match Modal::opened() { match Modal::opened() {
None => {} None => {}
@ -176,7 +176,7 @@ impl CommonSettings {
/// Draw wallet name [`Modal`] content. /// Draw wallet name [`Modal`] content.
fn name_modal_ui(&mut self, fn name_modal_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
ui.add_space(6.0); ui.add_space(6.0);
@ -230,7 +230,7 @@ impl CommonSettings {
/// Draw wallet pass [`Modal`] content. /// Draw wallet pass [`Modal`] content.
fn pass_modal_ui(&mut self, fn pass_modal_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
let wallet_id = wallet.get_config().id; let wallet_id = wallet.get_config().id;
@ -328,7 +328,7 @@ impl CommonSettings {
/// Draw wallet name [`Modal`] content. /// Draw wallet name [`Modal`] content.
fn min_conf_modal_ui(&mut self, fn min_conf_modal_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
ui.add_space(6.0); ui.add_space(6.0);

View file

@ -29,9 +29,6 @@ pub struct ConnectionSettings {
/// Selected connection method. /// Selected connection method.
pub method: ConnectionMethod, pub method: ConnectionMethod,
/// Current wallet external connection.
curr_ext_conn: Option<ExternalConnection>,
/// External connection [`Modal`] content. /// External connection [`Modal`] content.
ext_conn_modal: ExternalConnectionModal, ext_conn_modal: ExternalConnectionModal,
@ -44,7 +41,6 @@ impl Default for ConnectionSettings {
ExternalConnection::check_ext_conn_availability(None); ExternalConnection::check_ext_conn_availability(None);
Self { Self {
method: ConnectionMethod::Integrated, method: ConnectionMethod::Integrated,
curr_ext_conn: None,
ext_conn_modal: ExternalConnectionModal::new(None), ext_conn_modal: ExternalConnectionModal::new(None),
modal_ids: vec![ modal_ids: vec![
ExternalConnectionModal::WALLET_ID ExternalConnectionModal::WALLET_ID
@ -74,52 +70,29 @@ impl ModalContainer for ConnectionSettings {
impl ConnectionSettings { impl ConnectionSettings {
/// Draw wallet creation setup content. /// Draw wallet creation setup content.
pub fn create_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { 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. /// Draw existing wallet connection setup content.
pub fn wallet_ui(&mut self, ui: &mut egui::Ui, w: &mut Wallet, cb: &dyn PlatformCallbacks) { pub fn wallet_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
// Setup connection value from provided wallet. self.method = wallet.get_current_connection();
match w.get_config().ext_conn_id {
None => self.method = ConnectionMethod::Integrated,
Some(id) => self.method = ConnectionMethod::External(id)
}
// Draw setup content. // 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 { if changed {
w.update_ext_conn_id(None); wallet.update_connection(&self.method);
}
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. // Reopen wallet if connection changed.
if changed { if !wallet.reopen_needed() {
if !w.reopen_needed() { wallet.set_reopen(true);
w.set_reopen(true); wallet.close();
w.close();
} }
} }
} }
/// Draw connection setup content. /// Draw connection setup content, returning `true` if connection was changed.
fn ui(&mut self, fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) -> bool {
ui: &mut egui::Ui, let mut changed = false;
wallet: Option<&Wallet>,
cb: &dyn PlatformCallbacks) {
// Draw modal content for current ui container. // Draw modal content for current ui container.
self.current_modal_ui(ui, cb); self.current_modal_ui(ui, cb);
@ -129,14 +102,14 @@ impl ConnectionSettings {
ui.add_space(6.0); ui.add_space(6.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
// Show integrated node selection.
ui.add_space(6.0); ui.add_space(6.0);
// Show integrated node selection.
ConnectionsContent::integrated_node_item_ui(ui, |ui| { 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; let is_current_method = self.method == ConnectionMethod::Integrated;
if !is_current_method { if !is_current_method {
View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || { View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || {
self.method = ConnectionMethod::Integrated; self.method = ConnectionMethod::Integrated;
changed = true;
}); });
} else { } else {
ui.add_space(14.0); ui.add_space(14.0);
@ -145,7 +118,6 @@ impl ConnectionSettings {
} }
}); });
// Show external connections.
ui.add_space(8.0); ui.add_space(8.0);
ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::gray())); ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::gray()));
ui.add_space(6.0); ui.add_space(6.0);
@ -153,45 +125,58 @@ impl ConnectionSettings {
// Show button to add new external node connection. // Show button to add new external node connection.
let add_node_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add_node")); let add_node_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add_node"));
View::button(ui, add_node_text, Colors::button(), || { 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); ui.add_space(4.0);
let mut ext_conn_list = ConnectionsConfig::ext_conn_list(); // Check if it's current method.
let is_current = |m: &ConnectionMethod, c: &ExternalConnection| -> Option<bool> {
match m {
ConnectionMethod::External(id, _) => if c.deleted && *id == c.id {
None
} else {
Some(*id == c.id)
},
_ => Some(false)
}
};
// Check if current external connection was deleted to show at 1st place. let method = &self.method.clone();
if let Some(wallet) = wallet { let ext_conn_list = ConnectionsConfig::ext_conn_list();
if let Some(conn) = wallet.get_current_ext_conn() { let ext_list = ext_conn_list.iter().filter(|c| {
if ext_conn_list.iter() !c.deleted || is_current(method, c).unwrap_or(true)
.filter(|c| c.id == conn.id) }).collect::<Vec<&ExternalConnection>>();
.collect::<Vec<&ExternalConnection>>().is_empty() { let ext_size = ext_list.len();
if self.curr_ext_conn.is_none() { if ext_size != 0 {
self.curr_ext_conn = Some(conn);
}
ext_conn_list.insert(0, self.curr_ext_conn.as_ref().unwrap().clone());
}
}
}
if !ext_conn_list.is_empty() {
ui.add_space(8.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| { ui.horizontal_wrapped(|ui| {
// Draw external connection item. // 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. /// Draw external connection item content.
fn ext_conn_item_ui(&mut self, fn ext_conn_item_ui(ui: &mut egui::Ui,
ui: &mut egui::Ui,
wallet: Option<&Wallet>,
conn: &ExternalConnection, conn: &ExternalConnection,
is_current: Option<bool>,
index: usize, index: usize,
len: usize) { len: usize,
mut on_select: impl FnMut()) {
// Setup layout size. // Setup layout size.
let mut rect = ui.available_rect_before_wrap(); let mut rect = ui.available_rect_before_wrap();
rect.set_height(52.0); rect.set_height(52.0);
@ -203,24 +188,15 @@ impl ConnectionSettings {
ui.vertical(|ui| { ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to select connection. if is_current.unwrap_or(true) {
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 {
ui.add_space(12.0); ui.add_space(12.0);
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green())); 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(); let layout_size = ui.available_size();
@ -235,7 +211,11 @@ impl ConnectionSettings {
// Setup connection status text. // Setup connection status text.
let status_text = if let Some(available) = conn.available { let status_text = if let Some(available) = conn.available {
if 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 { } else {
format!("{} {}", X_CIRCLE, t!("network.not_available")) 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();
}
} }

View file

@ -50,7 +50,7 @@ impl WalletTab for WalletSettings {
fn ui(&mut self, fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
// Show loading progress if navigation is blocked. // Show loading progress if navigation is blocked.
if WalletContent::block_navigation_on_sync(wallet) { if WalletContent::block_navigation_on_sync(wallet) {

View file

@ -22,6 +22,7 @@ use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, View}; use crate::gui::views::{Modal, View};
use crate::gui::views::types::{ModalPosition, TextEditOptions}; use crate::gui::views::types::{ModalPosition, TextEditOptions};
use crate::node::Node; use crate::node::Node;
use crate::wallet::types::ConnectionMethod;
use crate::wallet::Wallet; use crate::wallet::Wallet;
/// Wallet recovery settings content. /// Wallet recovery settings content.
@ -51,7 +52,7 @@ impl Default for RecoverySettings {
} }
impl 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. // Show modal content for this ui container.
self.modal_content_ui(ui, wallet, cb); self.modal_content_ui(ui, wallet, cb);
@ -63,7 +64,7 @@ impl RecoverySettings {
ui.add_space(4.0); ui.add_space(4.0);
ui.vertical_centered(|ui| { 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); let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
if wallet.sync_error() || (integrated_node && !integrated_node_ready) { if wallet.sync_error() || (integrated_node && !integrated_node_ready) {
ui.add_space(2.0); ui.add_space(2.0);
@ -136,7 +137,7 @@ impl RecoverySettings {
/// Draw [`Modal`] content for this ui container. /// Draw [`Modal`] content for this ui container.
fn modal_content_ui(&mut self, fn modal_content_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
match Modal::opened() { match Modal::opened() {
None => {} None => {}
@ -175,7 +176,7 @@ impl RecoverySettings {
/// Draw recovery phrase [`Modal`] content. /// Draw recovery phrase [`Modal`] content.
fn recovery_phrase_modal_ui(&mut self, fn recovery_phrase_modal_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
ui.add_space(6.0); ui.add_space(6.0);
@ -260,7 +261,7 @@ impl RecoverySettings {
/// Draw wallet deletion [`Modal`] content. /// Draw wallet deletion [`Modal`] content.
fn deletion_modal_ui(&mut self, fn deletion_modal_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
modal: &Modal) { modal: &Modal) {
ui.add_space(8.0); ui.add_space(8.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {

View file

@ -47,7 +47,7 @@ impl WalletTab for WalletTransport {
fn ui(&mut self, fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
if WalletContent::sync_ui(ui, wallet) { if WalletContent::sync_ui(ui, wallet) {
return; return;
@ -106,7 +106,7 @@ impl Default for WalletTransport {
impl WalletTransport { impl WalletTransport {
/// Draw wallet transport content. /// 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.add_space(3.0);
ui.label(RichText::new(t!("transport.desc")) ui.label(RichText::new(t!("transport.desc"))
.size(16.0) .size(16.0)
@ -120,7 +120,7 @@ impl WalletTransport {
/// Draw [`Modal`] content for this ui container. /// Draw [`Modal`] content for this ui container.
fn modal_content_ui(&mut self, fn modal_content_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
match Modal::opened() { match Modal::opened() {
None => {} None => {}
@ -152,7 +152,7 @@ impl WalletTransport {
} }
/// Draw Tor transport content. /// 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(); let data = wallet.get_data().unwrap();
// Draw header content. // Draw header content.

View file

@ -74,7 +74,7 @@ impl TransportSendModal {
/// Draw [`Modal`] content. /// Draw [`Modal`] content.
pub fn ui(&mut self, pub fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
// Draw transaction information on request result. // Draw transaction information on request result.

View file

@ -56,7 +56,7 @@ impl WalletTab for WalletTransactions {
WalletTabType::Txs 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) { if WalletContent::sync_ui(ui, wallet) {
return; return;
} }
@ -100,7 +100,7 @@ impl WalletTransactions {
/// Draw transactions content. /// Draw transactions content.
fn txs_ui(&mut self, fn txs_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
data: &WalletData, data: &WalletData,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
let amount_conf = data.info.amount_awaiting_confirmation; let amount_conf = data.info.amount_awaiting_confirmation;
@ -245,7 +245,7 @@ impl WalletTransactions {
/// Draw [`Modal`] content for this ui container. /// Draw [`Modal`] content for this ui container.
fn modal_content_ui(&mut self, fn modal_content_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
match Modal::opened() { match Modal::opened() {
None => {} None => {}
@ -360,8 +360,8 @@ impl WalletTransactions {
TxLogEntryType::TxSent | TxLogEntryType::TxReceived => { TxLogEntryType::TxSent | TxLogEntryType::TxReceived => {
let height = data.info.last_confirmed_height; let height = data.info.last_confirmed_height;
let min_conf = data.info.minimum_confirmations; let min_conf = data.info.minimum_confirmations;
if tx.conf_height.is_none() || (tx.conf_height.unwrap() != 0 && if tx.height.is_none() || (tx.height.unwrap() != 0 &&
height - tx.conf_height.unwrap() > min_conf - 1) { height - tx.height.unwrap() > min_conf - 1) {
let (i, t) = if tx.data.tx_type == TxLogEntryType::TxSent { let (i, t) = if tx.data.tx_type == TxLogEntryType::TxSent {
(ARROW_CIRCLE_UP, t!("wallets.tx_sent")) (ARROW_CIRCLE_UP, t!("wallets.tx_sent"))
} else { } else {
@ -369,7 +369,7 @@ impl WalletTransactions {
}; };
format!("{} {}", i, t) format!("{} {}", i, t)
} else { } else {
let tx_height = tx.conf_height.unwrap() - 1; let tx_height = tx.height.unwrap() - 1;
let left_conf = height - tx_height; let left_conf = height - tx_height;
let conf_info = if tx_height != 0 && height >= tx_height && let conf_info = if tx_height != 0 && height >= tx_height &&
left_conf < min_conf { left_conf < min_conf {
@ -428,7 +428,7 @@ impl WalletTransactions {
} }
/// Confirmation [`Modal`] to cancel transaction. /// 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.add_space(6.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
// Setup confirmation text. // Setup confirmation text.

View file

@ -21,7 +21,7 @@ use grin_util::ToHex;
use grin_wallet_libwallet::{Error, Slate, SlateState, TxLogEntryType}; use grin_wallet_libwallet::{Error, Slate, SlateState, TxLogEntryType};
use parking_lot::RwLock; use parking_lot::RwLock;
use crate::gui::Colors; 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::platform::PlatformCallbacks;
use crate::gui::views::{CameraContent, FilePickButton, Modal, QrCodeContent, View}; use crate::gui::views::{CameraContent, FilePickButton, Modal, QrCodeContent, View};
@ -106,7 +106,7 @@ impl WalletTransactionModal {
/// Draw [`Modal`] content. /// Draw [`Modal`] content.
pub fn ui(&mut self, pub fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
// Check values and setup transaction data. // 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 { if let Some(id) = tx.data.tx_slate_id {
let label = format!("{} {}", HASH_STRAIGHT, t!("id")); let label = format!("{} {}", HASH_STRAIGHT, t!("id"));
Self::info_item_ui(ui, id.to_string(), label, true, cb); 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 { if let Some(kernel) = tx.data.kernel_excess {
let label = format!("{} {}", FILE_ARCHIVE, t!("kernel")); let label = format!("{} {}", FILE_ARCHIVE, t!("kernel"));
Self::info_item_ui(ui, kernel.0.to_hex(), label, true, cb); 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. // 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(qr_scan_content) = self.qr_scan_content.as_mut() {
if let Some(result) = qr_scan_content.qr_scan_result() { if let Some(result) = qr_scan_content.qr_scan_result() {
cb.stop_camera(); cb.stop_camera();
qr_scan_content.clear_state();
// Setup value to finalization input field. // Setup value to finalization input field.
self.finalize_edit = result.text(); self.finalize_edit = result.text();

View file

@ -26,7 +26,7 @@ pub trait WalletTab {
fn get_type(&self) -> WalletTabType; fn get_type(&self) -> WalletTabType;
fn ui(&mut self, fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &Wallet,
cb: &dyn PlatformCallbacks); cb: &dyn PlatformCallbacks);
} }
@ -52,7 +52,7 @@ impl WalletTabType {
} }
/// Get wallet status text. /// 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.is_open() {
if wallet.sync_error() { if wallet.sync_error() {
format!("{} {}", WARNING_CIRCLE, t!("error")) format!("{} {}", WARNING_CIRCLE, t!("error"))

View file

@ -22,6 +22,7 @@ use rand::Rng;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use crate::{AppConfig, Settings}; use crate::{AppConfig, Settings};
use crate::wallet::ConnectionsConfig;
use crate::wallet::types::ConnectionMethod; use crate::wallet::types::ConnectionMethod;
/// Wallet configuration. /// Wallet configuration.
@ -75,7 +76,7 @@ impl WalletConfig {
name, name,
ext_conn_id: match conn_method { ext_conn_id: match conn_method {
ConnectionMethod::Integrated => None, ConnectionMethod::Integrated => None,
ConnectionMethod::External(id) => Some(*id) ConnectionMethod::External(id, _) => Some(*id)
}, },
min_confirmations: MIN_CONFIRMATIONS_DEFAULT, min_confirmations: MIN_CONFIRMATIONS_DEFAULT,
use_dandelion: Some(true), use_dandelion: Some(true),
@ -116,6 +117,18 @@ impl WalletConfig {
None 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. /// Save wallet config.
pub fn save(&self) { pub fn save(&self) {
let config_path = Self::get_config_file_path(self.chain_type, self.id); let config_path = Self::get_config_file_path(self.chain_type, self.id);

View file

@ -38,13 +38,7 @@ impl ConnectionsConfig {
if !path.exists() || parsed.is_err() { if !path.exists() || parsed.is_err() {
let default_config = ConnectionsConfig { let default_config = ConnectionsConfig {
chain_type: *chain_type, chain_type: *chain_type,
external: if chain_type == &ChainTypes::Mainnet { external: ExternalConnection::default(chain_type),
vec![
ExternalConnection::default_main()
]
} else {
vec![]
},
}; };
Settings::write_to_file(&default_config, path); Settings::write_to_file(&default_config, path);
default_config default_config
@ -53,11 +47,17 @@ impl ConnectionsConfig {
} }
} }
/// Save connections configuration to the file. /// Save connections configuration.
pub fn save(&self) { pub fn save(&mut self) {
let chain_type = AppConfig::chain_type(); // Check deleted external connections.
let sub_dir = Some(chain_type.shortname()); let mut config = self.clone();
Settings::write_to_file(self, Settings::config_path(Self::FILE_NAME, sub_dir)); config.external = config.external.iter()
.map(|c| c.clone())
.filter(|c| !c.deleted)
.collect::<Vec<ExternalConnection>>();
let sub_dir = Some(AppConfig::chain_type().shortname());
Settings::write_to_file(&config, Settings::config_path(Self::FILE_NAME, sub_dir));
} }
/// Get [`ExternalConnection`] list. /// Get [`ExternalConnection`] list.
@ -68,29 +68,19 @@ impl ConnectionsConfig {
/// Save [`ExternalConnection`] in configuration. /// Save [`ExternalConnection`] in configuration.
pub fn add_ext_conn(conn: ExternalConnection) { 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 w_config = Settings::conn_config_to_update();
let mut exists = false; if let Some(pos) = w_config.external.iter().position(|c| {
for c in w_config.external.iter_mut() { c.id == conn.id
// Update connection if config exists. }) {
if c.id == conn.id { w_config.external.remove(pos);
c.url = conn.url.clone(); w_config.external.insert(pos, conn);
c.secret = conn.secret.clone(); } else {
exists = true;
break;
}
}
// Create new connection if URL not exists.
if !exists {
w_config.external.push(conn); w_config.external.push(conn);
} }
w_config.save(); w_config.save();
} }
/// Get [`ExternalConnection`] by provided identifier. /// Get external node connection with provided identifier.
pub fn ext_conn(id: i64) -> Option<ExternalConnection> { pub fn ext_conn(id: i64) -> Option<ExternalConnection> {
let r_config = Settings::conn_config_to_read(); let r_config = Settings::conn_config_to_read();
for c in &r_config.external { 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) { pub fn remove_ext_conn(id: i64) {
let mut w_config = Settings::conn_config_to_update(); let mut w_config = Settings::conn_config_to_update();
let index = w_config.external.iter().position(|c| c.id == id); if let Some(pos) = w_config.external.iter().position(|c| {
if let Some(i) = index { c.id == id
w_config.external.remove(i); }) {
if let Some(conn) = w_config.external.get_mut(pos) {
conn.deleted = true;
w_config.save(); w_config.save();
} }
} }
}
} }

View file

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use grin_core::global::ChainTypes;
use grin_util::to_base64; use grin_util::to_base64;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@ -28,23 +29,53 @@ pub struct ExternalConnection {
pub secret: Option<String>, pub secret: Option<String>,
/// Flag to check if server is available. /// Flag to check if server is available.
#[serde(skip_serializing)] #[serde(skip_serializing, skip_deserializing)]
pub available: Option<bool> pub available: Option<bool>,
/// Flag to check if connection was deleted.
#[serde(skip_serializing, skip_deserializing)]
pub deleted: bool
} }
impl ExternalConnection { /// Default external node URL for main network.
/// Default external node URL for main network. const DEFAULT_MAIN_URLS: [&'static str; 2] = [
pub const DEFAULT_MAIN_URL: &'static str = "https://grinnode.live:3413"; "https://grincoin.org",
"https://grinnode.live:3413"
];
/// Create default external connection. /// Default external node URL for main network.
pub fn default_main() -> Self { const DEFAULT_TEST_URLS: [&'static str; 1] = [
Self { id: 1, url: Self::DEFAULT_MAIN_URL.to_string(), secret: None, available: None } "https://testnet.grincoin.org"
];
impl ExternalConnection {
/// Get default connections for provided chain type.
pub fn default(chain_type: &ChainTypes) -> Vec<ExternalConnection> {
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::<Vec<ExternalConnection>>()
} }
/// Create new external connection. /// Create new external connection.
pub fn new(url: String, secret: Option<String>) -> Self { pub fn new(url: String, secret: Option<String>) -> Self {
let id = chrono::Utc::now().timestamp(); let id = chrono::Utc::now().timestamp();
Self { id, url, secret, available: None } Self {
id,
url,
secret,
available: None,
deleted: false
}
} }
/// Check connection availability. /// Check connection availability.

View file

@ -13,7 +13,6 @@
// limitations under the License. // limitations under the License.
use grin_core::global::ChainTypes; use grin_core::global::ChainTypes;
use grin_wallet_libwallet::Error;
use crate::AppConfig; use crate::AppConfig;
use crate::wallet::{Wallet, WalletConfig}; use crate::wallet::{Wallet, WalletConfig};
@ -25,14 +24,12 @@ pub struct WalletList {
pub main_list: Vec<Wallet>, pub main_list: Vec<Wallet>,
/// List of wallets for [`ChainTypes::Testnet`]. /// List of wallets for [`ChainTypes::Testnet`].
pub test_list: Vec<Wallet>, pub test_list: Vec<Wallet>,
/// Selected [`Wallet`] identifier.
pub selected_id: Option<i64>,
} }
impl Default for WalletList { impl Default for WalletList {
fn default() -> Self { fn default() -> Self {
let (main_list, test_list) = Self::init(); 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. /// Add created [`Wallet`] to the list.
pub fn add(&mut self, wallet: Wallet) { pub fn add(&mut self, wallet: Wallet) {
self.selected_id = Some(wallet.get_config().id);
let list = self.mut_list(); let list = self.mut_list();
list.insert(0, wallet); list.insert(0, wallet);
} }
@ -100,47 +96,4 @@ impl WalletList {
} }
} }
} }
/// Select [`Wallet`] with provided identifier.
pub fn select(&mut self, id: Option<i64>) {
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()))
}
} }

View file

@ -18,6 +18,7 @@ use grin_keychain::ExtKeychain;
use grin_util::Mutex; use grin_util::Mutex;
use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient}; use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient};
use grin_wallet_libwallet::{TxLogEntry, TxLogEntryType, WalletInfo, WalletInst}; use grin_wallet_libwallet::{TxLogEntry, TxLogEntryType, WalletInfo, WalletInst};
use serde_derive::{Deserialize, Serialize};
/// Mnemonic phrase word. /// Mnemonic phrase word.
#[derive(Clone)] #[derive(Clone)]
@ -107,12 +108,12 @@ impl PhraseSize {
} }
/// Wallet connection method. /// Wallet connection method.
#[derive(PartialEq)] #[derive(Serialize, Deserialize, Clone, PartialEq)]
pub enum ConnectionMethod { pub enum ConnectionMethod {
/// Integrated node. /// Integrated node.
Integrated, Integrated,
/// External node, contains connection identifier. /// External node, contains connection identifier and URL.
External(i64) External(i64, String)
} }
/// Wallet instance type. /// Wallet instance type.
@ -162,8 +163,8 @@ pub struct WalletTransaction {
pub can_finalize: bool, pub can_finalize: bool,
/// Flag to check if transaction is finalizing. /// Flag to check if transaction is finalizing.
pub finalizing: bool, pub finalizing: bool,
/// Block height when tx was confirmed. /// Block height where tx was included.
pub conf_height: Option<u64>, pub height: Option<u64>,
/// Flag to check if tx was received after sync from node. /// Flag to check if tx was received after sync from node.
pub from_node: bool, pub from_node: bool,
} }

View file

@ -28,7 +28,6 @@ use serde_json::{json, Value};
use grin_api::{ApiServer, Router}; use grin_api::{ApiServer, Router};
use grin_chain::SyncStatus; use grin_chain::SyncStatus;
use grin_core::global; use grin_core::global;
use grin_core::global::ChainTypes;
use grin_keychain::{ExtKeychain, Identifier, Keychain}; use grin_keychain::{ExtKeychain, Identifier, Keychain};
use grin_util::{Mutex, ToHex}; use grin_util::{Mutex, ToHex};
use grin_util::secp::SecretKey; use grin_util::secp::SecretKey;
@ -46,7 +45,7 @@ use rand::Rng;
use crate::AppConfig; use crate::AppConfig;
use crate::node::{Node, NodeConfig}; use crate::node::{Node, NodeConfig};
use crate::tor::Tor; 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::store::TxHeightStore;
use crate::wallet::types::{ConnectionMethod, WalletAccount, WalletData, WalletInstance, WalletTransaction}; use crate::wallet::types::{ConnectionMethod, WalletAccount, WalletData, WalletInstance, WalletTransaction};
@ -56,9 +55,9 @@ pub struct Wallet {
/// Wallet configuration. /// Wallet configuration.
config: Arc<RwLock<WalletConfig>>, config: Arc<RwLock<WalletConfig>>,
/// Wallet instance, initializing on wallet opening and clearing on wallet closing. /// Wallet instance, initializing on wallet opening and clearing on wallet closing.
instance: Option<WalletInstance>, instance: Arc<RwLock<Option<WalletInstance>>>,
/// [`WalletInstance`] external connection URL. /// Connection of current wallet instance.
external_connection: Arc<RwLock<Option<ExternalConnection>>>, connection: Arc<RwLock<ConnectionMethod>>,
/// Wallet Slatepack address to receive txs at transport. /// Wallet Slatepack address to receive txs at transport.
slatepack_address: Arc<RwLock<Option<String>>>, slatepack_address: Arc<RwLock<Option<String>>>,
@ -103,10 +102,11 @@ pub struct Wallet {
impl Wallet { impl Wallet {
/// Create new [`Wallet`] instance with provided [`WalletConfig`]. /// Create new [`Wallet`] instance with provided [`WalletConfig`].
fn new(config: WalletConfig) -> Self { fn new(config: WalletConfig) -> Self {
let connection = config.connection();
Self { Self {
config: Arc::new(RwLock::new(config)), config: Arc::new(RwLock::new(config)),
instance: None, instance: Arc::new(RwLock::new(None)),
external_connection: Arc::new(RwLock::new(None)), connection: Arc::new(RwLock::new(connection)),
slatepack_address: Arc::new(RwLock::new(None)), slatepack_address: Arc::new(RwLock::new(None)),
sync_thread: Arc::from(RwLock::new(None)), sync_thread: Arc::from(RwLock::new(None)),
foreign_api_server: Arc::new(RwLock::new(None)), foreign_api_server: Arc::new(RwLock::new(None)),
@ -128,7 +128,7 @@ impl Wallet {
/// Create new wallet. /// Create new wallet.
pub fn create( pub fn create(
name: &String, name: &String,
password: &String, password: &ZeroingString,
mnemonic: &Mnemonic, mnemonic: &Mnemonic,
conn_method: &ConnectionMethod conn_method: &ConnectionMethod
) -> Result<Wallet, Error> { ) -> Result<Wallet, Error> {
@ -141,7 +141,7 @@ impl Wallet {
p.create_wallet(None, p.create_wallet(None,
Some(ZeroingString::from(mnemonic.get_phrase())), Some(ZeroingString::from(mnemonic.get_phrase())),
mnemonic.size().entropy_size(), mnemonic.size().entropy_size(),
ZeroingString::from(password.clone()), password.clone(),
false, false,
)?; )?;
} }
@ -177,18 +177,9 @@ impl Wallet {
let (node_api_url, node_secret) = if let Some(id) = config.ext_conn_id { let (node_api_url, node_secret) = if let Some(id) = config.ext_conn_id {
if let Some(conn) = ConnectionsConfig::ext_conn(id) { if let Some(conn) = ConnectionsConfig::ext_conn(id) {
(conn.url, conn.secret) (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 { } else {
integrated() integrated()
} }
}
} else { } else {
integrated() integrated()
}; };
@ -223,7 +214,8 @@ impl Wallet {
/// Get parent key identifier for current account. /// Get parent key identifier for current account.
pub fn get_parent_key_id(&self) -> Result<Identifier, Error> { pub fn get_parent_key_id(&self) -> Result<Identifier, Error> {
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 mut w_lock = instance.lock();
let lc = w_lock.lc_provider()?; let lc = w_lock.lc_provider()?;
let w_inst = lc.wallet_inst()?; let w_inst = lc.wallet_inst()?;
@ -232,7 +224,8 @@ impl Wallet {
/// Get wallet [`SecretKey`] for transports. /// Get wallet [`SecretKey`] for transports.
pub fn secret_key(&self) -> Result<SecretKey, Error> { pub fn secret_key(&self) -> Result<SecretKey, Error> {
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 mut w_lock = instance.lock();
let lc = w_lock.lc_provider()?; let lc = w_lock.lc_provider()?;
let w_inst = lc.wallet_inst()?; let w_inst = lc.wallet_inst()?;
@ -305,36 +298,47 @@ impl Wallet {
} }
/// Update external connection identifier. /// Update external connection identifier.
pub fn update_ext_conn_id(&self, id: Option<i64>) { pub fn update_connection(&self, conn: &ConnectionMethod) {
let mut w_config = self.config.write(); 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(); w_config.save();
} }
/// Open the wallet and start [`WalletData`] sync at separate thread. /// 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() { if self.is_open() {
return Err(Error::GenericError("Already opened".to_string())); return Err(Error::GenericError("Already opened".to_string()));
} }
// Create new wallet instance if sync thread was stopped or instance was not created. // 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 mut config = self.get_config();
let new_instance = Self::create_wallet_instance(&mut config)?; // Setup current connection.
self.instance = Some(new_instance);
// Setup current external connection.
{ {
let mut w_conn = self.external_connection.write(); let mut w_conn = self.connection.write();
*w_conn = self.get_current_ext_conn(); *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. // 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 mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider()?; 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(_) => { Ok(_) => {
// Reset an error on opening. // Reset an error on opening.
self.set_sync_error(false); self.set_sync_error(false);
@ -357,7 +361,8 @@ impl Wallet {
} }
Err(e) => { Err(e) => {
if !self.syncing() { if !self.syncing() {
self.instance = None; let mut w_inst = self.instance.write();
*w_inst = None;
} }
return Err(e) return Err(e)
} }
@ -365,7 +370,9 @@ impl Wallet {
} }
// Set slatepack address. // 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| { controller::owner_single_use(None, None, Some(&mut api), |api, m| {
let mut w_address = self.slatepack_address.write(); let mut w_address = self.slatepack_address.write();
*w_address = Some(api.get_slatepack_address(m, 0)?.to_string()); *w_address = Some(api.get_slatepack_address(m, 0)?.to_string());
@ -377,25 +384,13 @@ impl Wallet {
/// Get external connection URL applied to [`WalletInstance`] /// Get external connection URL applied to [`WalletInstance`]
/// after wallet opening if sync is running or get it from config. /// after wallet opening if sync is running or get it from config.
pub fn get_current_ext_conn(&self) -> Option<ExternalConnection> { pub fn get_current_connection(&self) -> ConnectionMethod {
if self.sync_thread.read().is_some() { if self.sync_thread.read().is_some() {
let r_conn = self.external_connection.read(); let r_conn = self.connection.read();
r_conn.clone() r_conn.clone()
} else { } else {
let config = self.get_config(); let config = self.get_config();
if let Some(id) = config.ext_conn_id { config.connection()
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
} }
} }
@ -411,7 +406,11 @@ impl Wallet {
/// Close the wallet. /// Close the wallet.
pub fn close(&self) { 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; return;
} }
self.closing.store(true, Ordering::Relaxed); self.closing.store(true, Ordering::Relaxed);
@ -419,6 +418,7 @@ impl Wallet {
// Close wallet at separate thread. // Close wallet at separate thread.
let wallet_close = self.clone(); let wallet_close = self.clone();
let service_id = wallet_close.identifier(); let service_id = wallet_close.identifier();
let conn = wallet_close.connection.clone();
thread::spawn(move || { thread::spawn(move || {
// Stop running API server. // Stop running API server.
let api_server_exists = { let api_server_exists = {
@ -431,12 +431,18 @@ impl Wallet {
} }
// Stop running Tor service. // Stop running Tor service.
Tor::stop_service(&service_id); Tor::stop_service(&service_id);
// Close the wallet. // 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); Self::close_wallet(&instance);
// Mark wallet as not opened.
wallet_close.closing.store(false, Ordering::Relaxed); wallet_close.closing.store(false, Ordering::Relaxed);
wallet_close.is_open.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. // Start sync to exit from thread.
wallet_close.sync(); wallet_close.sync();
}); });
@ -451,7 +457,9 @@ impl Wallet {
/// Create account into wallet. /// Create account into wallet.
pub fn create_account(&self, label: &String) -> Result<(), Error> { 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| { controller::owner_single_use(None, None, Some(&mut api), |api, m| {
api.create_account_path(m, label)?; api.create_account_path(m, label)?;
@ -472,7 +480,9 @@ impl Wallet {
/// Set active account from provided label. /// Set active account from provided label.
pub fn set_active_account(&self, label: &String) -> Result<(), Error> { 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| { controller::owner_single_use(None, None, Some(&mut api), |api, m| {
api.set_active_account(m, label)?; api.set_active_account(m, label)?;
// Set Slatepack address. // Set Slatepack address.
@ -580,7 +590,9 @@ impl Wallet {
/// Parse Slatepack message into [`Slate`]. /// Parse Slatepack message into [`Slate`].
pub fn parse_slatepack(&self, text: &String) -> Result<Slate, grin_wallet_controller::Error> { pub fn parse_slatepack(&self, text: &String) -> Result<Slate, grin_wallet_controller::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);
return match parse_slatepack(&mut api, None, None, Some(text.clone())) { return match parse_slatepack(&mut api, None, None, Some(text.clone())) {
Ok(s) => Ok(s.0), Ok(s) => Ok(s.0),
Err(e) => Err(e) Err(e) => Err(e)
@ -590,7 +602,9 @@ impl Wallet {
/// Create Slatepack message from provided slate. /// Create Slatepack message from provided slate.
fn create_slatepack_message(&self, slate: &Slate) -> Result<String, Error> { fn create_slatepack_message(&self, slate: &Slate) -> Result<String, Error> {
let mut message = "".to_string(); 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| { controller::owner_single_use(None, None, Some(&mut api), |api, m| {
message = api.create_slatepack_message(m, &slate, Some(0), vec![])?; message = api.create_slatepack_message(m, &slate, Some(0), vec![])?;
Ok(()) Ok(())
@ -678,7 +692,9 @@ impl Wallet {
selection_strategy_is_use_all: false, selection_strategy_is_use_all: false,
..Default::default() ..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)?; let slate = api.init_send_tx(None, args)?;
// Lock outputs to for this transaction. // Lock outputs to for this transaction.
@ -708,7 +724,8 @@ impl Wallet {
// Function to cancel initialized tx in case of error. // Function to cancel initialized tx in case of error.
let cancel_tx = || { 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; let id = slate.clone().id;
cancel_tx(instance, None, &None, None, Some(id.clone())).unwrap(); cancel_tx(instance, None, &None, None, Some(id.clone())).unwrap();
@ -749,7 +766,9 @@ impl Wallet {
let mut ret_slate = None; let mut ret_slate = None;
match Slate::deserialize_upgrade(&serde_json::to_string(&slate_value).unwrap()) { match Slate::deserialize_upgrade(&serde_json::to_string(&slate_value).unwrap()) {
Ok(s) => { 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| { controller::owner_single_use(None, None, Some(&mut api), |api, m| {
// Finalize transaction. // Finalize transaction.
return if let Ok(slate) = api.finalize_tx(m, &s) { return if let Ok(slate) = api.finalize_tx(m, &s) {
@ -791,7 +810,9 @@ impl Wallet {
amount, amount,
target_slate_version: None, 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)?; let slate = api.issue_invoice_tx(None, args)?;
// Create Slatepack message response. // Create Slatepack message response.
@ -815,7 +836,9 @@ impl Wallet {
selection_strategy_is_use_all: false, selection_strategy_is_use_all: false,
..Default::default() ..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)?; let slate = api.process_invoice_tx(None, &slate, args)?;
api.tx_lock_outputs(None, &slate)?; api.tx_lock_outputs(None, &slate)?;
@ -834,7 +857,9 @@ impl Wallet {
/// Handle message to receive funds, return response to sender. /// Handle message to receive funds, return response to sender.
pub fn receive(&self, message: &String) -> Result<WalletTransaction, Error> { pub fn receive(&self, message: &String) -> Result<WalletTransaction, Error> {
if let Ok(mut slate) = self.parse_slatepack(message) { 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| { controller::foreign_single_use(api.wallet_inst.clone(), None, |api| {
slate = api.receive_tx(&slate, Some(self.get_config().account.as_str()), None)?; slate = api.receive_tx(&slate, Some(self.get_config().account.as_str()), None)?;
Ok(()) Ok(())
@ -854,7 +879,9 @@ impl Wallet {
/// Finalize transaction from provided message as sender or invoice issuer with Dandelion. /// Finalize transaction from provided message as sender or invoice issuer with Dandelion.
pub fn finalize(&self, message: &String) -> Result<WalletTransaction, Error> { pub fn finalize(&self, message: &String) -> Result<WalletTransaction, Error> {
if let Ok(mut slate) = self.parse_slatepack(message) { 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)?; slate = api.finalize_tx(None, &slate)?;
// Save Slatepack message to file. // Save Slatepack message to file.
let _ = self.create_slatepack_message(&slate)?; let _ = self.create_slatepack_message(&slate)?;
@ -874,7 +901,9 @@ impl Wallet {
/// Post transaction to blockchain. /// Post transaction to blockchain.
fn post(&self, slate: &Slate) -> Result<WalletTransaction, Error> { fn post(&self, slate: &Slate) -> Result<WalletTransaction, Error> {
// Post transaction to blockchain. // 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())?; api.post_tx(None, slate, self.can_use_dandelion())?;
// Refresh wallet info. // Refresh wallet info.
@ -884,7 +913,7 @@ impl Wallet {
} }
/// Cancel transaction. /// Cancel transaction.
pub fn cancel(&mut self, id: u32) { pub fn cancel(&self, id: u32) {
// Setup cancelling status. // Setup cancelling status.
{ {
let mut w_data = self.data.write(); let mut w_data = self.data.write();
@ -906,7 +935,8 @@ impl Wallet {
if wallet.syncing() { if wallet.syncing() {
thread::sleep(Duration::from_millis(1000)); 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); let _ = cancel_tx(instance, None, &None, Some(id), None);
// Refresh wallet info to update statuses. // 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<Option<u64>, 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. /// Change wallet password.
pub fn change_password(&self, old: String, new: String) -> Result<(), Error> { 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 mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider()?; let lc = wallet_lock.lc_provider()?;
lc.change_password(None, ZeroingString::from(old), ZeroingString::from(new)) lc.change_password(None, ZeroingString::from(old), ZeroingString::from(new))
@ -961,7 +1020,8 @@ impl Wallet {
/// Get recovery phrase. /// Get recovery phrase.
pub fn get_recovery(&self, password: String) -> Result<ZeroingString, Error> { pub fn get_recovery(&self, password: String) -> Result<ZeroingString, 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 mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider().unwrap(); let lc = wallet_lock.lc_provider().unwrap();
lc.get_mnemonic(None, ZeroingString::from(password)) lc.get_mnemonic(None, ZeroingString::from(password))
@ -1041,7 +1101,7 @@ fn start_sync(wallet: Wallet) -> Thread {
} }
// Check integrated node state. // 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(); let not_enabled = !Node::is_running() || Node::is_stopping();
if not_enabled { if not_enabled {
// Reset loading progress. // Reset loading progress.
@ -1151,7 +1211,9 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
let config = wallet.get_config(); let config = wallet.get_config();
// Retrieve wallet info. // 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( if let Ok(info) = retrieve_summary_info(
instance.clone(), instance.clone(),
None, None,
@ -1224,7 +1286,6 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
let data = wallet.get_data().unwrap(); let data = wallet.get_data().unwrap();
let data_txs = data.txs.unwrap_or(vec![]); let data_txs = data.txs.unwrap_or(vec![]);
// Create wallet txs.
let mut new_txs: Vec<WalletTransaction> = vec![]; let mut new_txs: Vec<WalletTransaction> = vec![];
for tx in &account_txs { for tx in &account_txs {
// Setup transaction amount. // Setup transaction amount.
@ -1263,50 +1324,13 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
false false
}; };
// Setup confirmation and cancelling status. // Setup confirmation height and cancelling status
let mut conf_height = None; let mut conf_height = wallet.tx_height(tx, &tx_height_store).unwrap_or(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
};
let mut cancelling = false; let mut cancelling = false;
if data_txs.is_empty() {
setup_conf_height(tx, true);
} else {
for t in &data_txs { for t in &data_txs {
if t.data.id == tx.id { if t.data.id == tx.id {
if !setup_conf_height(tx, t.conf_height.is_none() || if conf_height.is_none() {
t.conf_height.unwrap() == 0) { conf_height = t.height;
conf_height = t.conf_height;
} }
if t.cancelling && if t.cancelling &&
tx.tx_type != TxLogEntryType::TxReceivedCancelled && tx.tx_type != TxLogEntryType::TxReceivedCancelled &&
@ -1316,7 +1340,6 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
break; break;
} }
} }
}
// Add transaction to the list. // Add transaction to the list.
new_txs.push(WalletTransaction { new_txs.push(WalletTransaction {
@ -1325,7 +1348,7 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
cancelling, cancelling,
can_finalize, can_finalize,
finalizing, finalizing,
conf_height, height: conf_height,
from_node: !fresh_sync || from_node 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); let api_addr = format!("{}:{}", host, free_port);
// Start Foreign API server thread. // 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, let api_handler_v2 = ForeignAPIHandlerV2::new(instance,
Arc::new(Mutex::new(None)), Arc::new(Mutex::new(None)),
false, false,
@ -1425,7 +1449,9 @@ fn update_accounts(wallet: &Wallet, current_height: u64, current_spendable: Opti
let mut w_data = wallet.accounts.write(); let mut w_data = wallet.accounts.write();
*w_data = accounts; *w_data = accounts;
} else { } 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 _ = controller::owner_single_use(None, None, Some(&mut api), |api, m| {
let mut accounts = vec![]; let mut accounts = vec![];
for a in api.accounts(m)? { for a in api.accounts(m)? {
@ -1489,7 +1515,9 @@ fn repair_wallet(wallet: &Wallet) {
}); });
// Start wallet scanning. // 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) { match api.scan(None, Some(1), false) {
Ok(()) => { Ok(()) => {
// Set sync error if scanning was not complete and wallet is open. // Set sync error if scanning was not complete and wallet is open.