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