ui + wallets: wallet connection settings, reopen wallet on connection change, recreate wallet instance on opening, update translations, wallet instance refactoring

This commit is contained in:
ardocrat
2023-08-12 04:24:23 +03:00
parent f5d213bb4c
commit e86cba50ca
18 changed files with 509 additions and 193 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ use crate::gui::views::Modal;
/// Title content type, can be single title or with animated subtitle.
pub enum TitleType {
/// Single text with possibility to align text at left side at default panel size width.
/// Single text with possibility to align text at left side for default panel size width.
Single(String, bool),
/// With animated subtitle text.
WithSubTitle(String, String, bool)
+8 -1
View File
@@ -333,6 +333,13 @@ impl WalletsContent {
ui.allocate_ui(rect.size(), |ui| {
let list = self.wallets.list().clone();
for wallet in &list {
// Check if wallet reopen is needed.
if !wallet.is_open() && wallet.reopen_needed() {
wallet.set_reopen(false);
self.wallets.select(Some(wallet.config.id));
self.show_open_wallet_modal(cb);
}
// Draw wallet list item.
self.wallet_item_ui(ui, wallet, cb);
ui.add_space(5.0);
@@ -430,7 +437,7 @@ impl WalletsContent {
if wallet.is_closing() {
format!("{} {}", SPINNER, t!("wallets.wallet_closing"))
} else if wallet.get_data().is_none() {
if wallet.loading_error() {
if wallet.load_error() {
format!("{} {}", WARNING_CIRCLE, t!("loading_error"))
} else {
format!("{} {}", SPINNER, t!("loading"))
+8 -3
View File
@@ -24,8 +24,8 @@ use crate::gui::views::types::ModalPosition;
use crate::gui::views::wallets::creation::MnemonicSetup;
use crate::gui::views::wallets::creation::types::Step;
use crate::gui::views::wallets::setup::ConnectionSetup;
use crate::wallet::{ExternalConnection, Wallet};
use crate::wallet::types::PhraseMode;
use crate::wallet::Wallet;
/// Wallet creation content.
pub struct WalletCreation {
@@ -304,14 +304,19 @@ impl WalletCreation {
}
}
}
Step::ConfirmMnemonic => Some(Step::SetupConnection),
Step::ConfirmMnemonic => {
// Check external connections availability on connection setup.
ExternalConnection::start_ext_conn_availability_check();
Some(Step::SetupConnection)
},
Step::SetupConnection => {
// Create wallet at last step.
let name = self.name_edit.clone();
let pass = self.pass_edit.clone();
let phrase = self.mnemonic_setup.mnemonic.get_phrase();
let conn_method = &self.network_setup.method;
let wallet = Wallet::create(name, pass.clone(), phrase, conn_method).unwrap();
let mut wallet
= Wallet::create(name, pass.clone(), phrase, conn_method).unwrap();
// Open created wallet.
wallet.open(pass).unwrap();
// Pass created wallet to callback.
+1 -1
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::{Id, RichText, ScrollArea, TextStyle, Widget};
use egui::{Id, RichText, TextStyle, Widget};
use crate::gui::Colors;
use crate::gui::icons::PENCIL;
+237 -29
View File
@@ -12,18 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::{Id, RichText, ScrollArea, TextStyle, Widget};
use egui::{Align, Id, Layout, RichText, Rounding, TextStyle, Widget};
use url::Url;
use crate::gui::Colors;
use crate::gui::icons::{GLOBE, GLOBE_SIMPLE};
use crate::gui::icons::{CHECK, CHECK_CIRCLE, CHECK_FAT, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE, GLOBE_SIMPLE, POWER, X_CIRCLE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, View};
use crate::gui::views::types::{ModalContainer, ModalPosition};
use crate::node::{Node, NodeConfig};
use crate::wallet::{ConnectionsConfig, ExternalConnection, Wallet};
use crate::wallet::types::ConnectionMethod;
/// Wallet node connection method setup content.
/// Wallet connection setup content.
pub struct ConnectionSetup {
/// Selected connection method.
pub method: ConnectionMethod,
@@ -37,13 +38,19 @@ pub struct ConnectionSetup {
/// Flag to show URL format error.
ext_node_url_error: bool,
/// Flag to show closing progress at confirmation [`Modal`] to reopen the [`Wallet`].
show_closing_progress: bool,
/// [`Modal`] identifiers allowed at this ui container.
modal_ids: Vec<&'static str>
}
/// External connection [`Modal`] identifier.
/// Identifier for [`Modal`] to add external connection.
pub const ADD_EXT_CONNECTION_MODAL: &'static str = "add_ext_connection_modal";
/// Identifier for [`Modal`] to confirm wallet reopening after connection change.
pub const REOPEN_WALLET_CONFIRMATION_MODAL: &'static str = "change_conn_reopen_wallet_modal";
impl Default for ConnectionSetup {
fn default() -> Self {
Self {
@@ -52,6 +59,7 @@ impl Default for ConnectionSetup {
ext_node_url_edit: "".to_string(),
ext_node_secret_edit: "".to_string(),
ext_node_url_error: false,
show_closing_progress: false,
modal_ids: vec![
ADD_EXT_CONNECTION_MODAL
]
@@ -91,6 +99,13 @@ impl ConnectionSetup {
frame: &mut eframe::Frame,
wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) {
// Show modal content to reopen the wallet.
if Modal::opened() == Some(REOPEN_WALLET_CONFIRMATION_MODAL) {
Modal::ui(ui.ctx(), |ui, modal| {
self.reopen_modal_content(ui, wallet, modal, cb);
});
}
// Setup connection value from provided wallet.
match wallet.config.ext_conn_id {
None => self.method = ConnectionMethod::Integrated,
@@ -101,19 +116,26 @@ impl ConnectionSetup {
self.ui(ui, frame, cb);
// Setup wallet connection value after change.
match self.method {
let changed = match self.method {
ConnectionMethod::Integrated => {
if wallet.config.ext_conn_id.is_some() {
let changed = wallet.config.ext_conn_id.is_some();
if changed {
wallet.config.ext_conn_id = None;
wallet.config.save();
}
changed
}
ConnectionMethod::External(id) => {
if wallet.config.ext_conn_id != Some(id) {
let changed = wallet.config.ext_conn_id != Some(id);
if changed {
wallet.config.ext_conn_id = Some(id);
wallet.config.save();
}
changed
}
};
if changed {
wallet.config.save();
self.show_reopen_confirmation_modal();
}
}
@@ -133,33 +155,155 @@ impl ConnectionSetup {
ui.vertical_centered(|ui| {
// Show integrated node selection.
ui.add_space(6.0);
View::radio_value(ui,
&mut self.method,
ConnectionMethod::Integrated,
t!("network.node"));
self.integrated_node_item_ui(ui);
ui.add_space(10.0);
ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::GRAY));
ui.add_space(6.0);
let ext_conn_list = ConnectionsConfig::ext_conn_list();
if !ext_conn_list.is_empty() {
ui.add_space(6.0);
ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::GRAY));
ui.add_space(6.0);
// Show button to add new external node connection.
let add_node_text = format!("{} {}", GLOBE_SIMPLE, t!("wallets.add_node"));
View::button(ui, add_node_text, Colors::GOLD, || {
self.show_add_ext_conn_modal(cb);
});
ui.add_space(12.0);
// Show external nodes URLs selection.
for conn in ConnectionsConfig::ext_conn_list() {
View::radio_value(ui,
&mut self.method,
ConnectionMethod::External(conn.id),
conn.url);
// Show button to add new external node connection.
let add_node_text = format!("{} {}", GLOBE_SIMPLE, t!("wallets.add_node"));
View::button(ui, add_node_text, Colors::GOLD, || {
self.show_add_ext_conn_modal(cb);
});
ui.add_space(12.0);
// Show external connections.
for (index, conn) in ext_conn_list.iter().enumerate() {
ui.horizontal_wrapped(|ui| {
// Draw connection list item.
self.ext_conn_item_ui(ui, conn, index, ext_conn_list.len());
});
}
}
});
}
/// Draw integrated node connection item content.
fn integrated_node_item_ui(&mut self, ui: &mut egui::Ui) {
// Draw round background.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(78.0);
let rounding = View::item_rounding(0, 1, false);
ui.painter().rect(rect, rounding, Colors::FILL, View::ITEM_STROKE);
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Setup padding for item buttons.
ui.style_mut().spacing.button_padding = egui::vec2(14.0, 0.0);
// Setup rounding for item buttons.
ui.style_mut().visuals.widgets.inactive.rounding = Rounding::same(8.0);
ui.style_mut().visuals.widgets.hovered.rounding = Rounding::same(8.0);
ui.style_mut().visuals.widgets.active.rounding = Rounding::same(8.0);
// Draw button to select integrated node if it was not selected.
let is_current_method = self.method == ConnectionMethod::Integrated;
if !is_current_method {
View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || {
self.method = ConnectionMethod::Integrated;
});
}
if !Node::is_running() {
// Draw button to start integrated node.
let rounding = if is_current_method {
View::item_rounding(0, 1, true)
} else {
Rounding::none()
};
View::item_button(ui, rounding, POWER, Some(Colors::GREEN), || {
Node::start();
});
}
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
ui.add_space(3.0);
ui.label(RichText::new(t!("network.node"))
.size(18.0)
.color(Colors::TITLE));
// Setup node API address text.
let api_address = NodeConfig::get_api_address();
let address_text = format!("{} http://{}", COMPUTER_TOWER, api_address);
ui.label(RichText::new(address_text).size(15.0).color(Colors::TEXT));
ui.add_space(1.0);
// Setup node status text.
let status_icon = if !Node::is_running() {
X_CIRCLE
} else if Node::not_syncing() {
CHECK_CIRCLE
} else {
DOTS_THREE_CIRCLE
};
let status_text = format!("{} {}", status_icon, Node::get_sync_status_text());
ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY));
})
});
});
}
/// Draw external connection item content.
fn ext_conn_item_ui(&mut self,
ui: &mut egui::Ui,
conn: &ExternalConnection,
index: usize,
len: usize) {
// Setup layout size.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(52.0);
// Draw round background.
let bg_rect = rect.clone();
let item_rounding = View::item_rounding(index, len, false);
ui.painter().rect(bg_rect, item_rounding, Colors::FILL, View::ITEM_STROKE);
ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to select connection.
let is_current_method = self.method == ConnectionMethod::External(conn.id);
if !is_current_method {
let button_rounding = View::item_rounding(index, len, true);
View::item_button(ui, button_rounding, CHECK, None, || {
self.method = ConnectionMethod::External(conn.id);
});
} else {
ui.add_space(12.0);
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::TITLE));
}
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
// Draw connections URL.
ui.add_space(4.0);
let conn_text = format!("{} {}", COMPUTER_TOWER, conn.url);
View::ellipsize_text(ui, conn_text, 15.0, Colors::TITLE);
ui.add_space(1.0);
// Setup connection status text.
let status_text = if let Some(available) = conn.available {
if available {
format!("{} {}", CHECK_CIRCLE, t!("network.available"))
} else {
format!("{} {}", X_CIRCLE, t!("network.not_available"))
}
} else {
format!("{} {}", DOTS_THREE_CIRCLE, t!("network.availability_check"))
};
ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY));
ui.add_space(3.0);
});
});
});
});
}
/// Show external connection adding [`Modal`].
fn show_add_ext_conn_modal(&mut self, cb: &dyn PlatformCallbacks) {
// Setup values for Modal.
@@ -257,6 +401,7 @@ impl ConnectionSetup {
Some(self.ext_node_secret_edit.to_owned())
};
let ext_conn = ExternalConnection::new(url.clone(), secret);
ext_conn.check_conn_availability();
ConnectionsConfig::add_ext_conn(ext_conn.clone());
// Set added connection as current.
@@ -279,4 +424,67 @@ impl ConnectionSetup {
ui.add_space(6.0);
});
}
/// Show confirmation modal to reopen the [`Wallet`] after connection change.
fn show_reopen_confirmation_modal(&mut self,) {
self.show_closing_progress = false;
// Show modal.
Modal::new(REOPEN_WALLET_CONFIRMATION_MODAL)
.title(t!("modal.confirmation"))
.show();
}
/// Draw confirmation modal content to reopen the [`Wallet`].
fn reopen_modal_content(&mut self,
ui: &mut egui::Ui,
wallet: &Wallet,
modal: &Modal,
cb: &dyn PlatformCallbacks) {
if self.show_closing_progress {
if !wallet.is_closing() {
modal.close();
return;
}
ui.add_space(16.0);
ui.vertical_centered(|ui| {
View::small_loading_spinner(ui);
ui.add_space(12.0);
ui.label(RichText::new(t!("wallets.wallet_closing"))
.size(17.0)
.color(Colors::TEXT));
});
ui.add_space(10.0);
} else {
ui.add_space(8.0);
ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("wallets.change_server_confirmation"))
.size(17.0)
.color(Colors::TEXT));
});
ui.add_space(10.0);
// Show modal buttons.
ui.scope(|ui| {
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
modal.close();
});
});
columns[1].vertical_centered_justified(|ui| {
View::button(ui, "OK".to_owned(), Colors::WHITE, || {
modal.disable_closing();
self.show_closing_progress = true;
wallet.set_reopen(true);
wallet.close();
});
});
});
ui.add_space(6.0);
});
}
}
}
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2023 The Grim Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// Main wallet setup content.
pub struct MainSetup {
}
+7 -1
View File
@@ -13,4 +13,10 @@
// limitations under the License.
mod connection;
pub use connection::ConnectionSetup;
pub use connection::ConnectionSetup;
mod main;
pub use main::MainSetup;
mod recovery;
pub use recovery::RecoverySetup;
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2023 The Grim Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// Wallet recovery setup content.
pub struct RecoverySetup {
}
+8 -6
View File
@@ -46,6 +46,8 @@ impl WalletContent {
wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) {
// Show wallet tabs panel.
let not_show_tabs =
wallet.is_closing() || (wallet.get_data().is_none() && !wallet.load_error());
egui::TopBottomPanel::bottom("wallet_tabs")
.frame(egui::Frame {
fill: Colors::FILL,
@@ -57,7 +59,7 @@ impl WalletContent {
},
..Default::default()
})
.show_inside(ui, |ui| {
.show_animated_inside(ui, !not_show_tabs, |ui| {
ui.vertical_centered(|ui| {
// Setup tabs width.
let available_width = ui.available_width();
@@ -172,7 +174,7 @@ impl WalletContent {
}
});
return true
} else if wallet.loading_error()
} else if wallet.load_error()
&& Node::get_sync_status() == Some(SyncStatus::NoSync) {
Self::loading_error_ui(ui, wallet);
return true;
@@ -181,7 +183,7 @@ impl WalletContent {
return true;
}
} else if wallet.get_data().is_none() {
if wallet.loading_error() {
if wallet.load_error() {
Self::loading_error_ui(ui, wallet);
} else {
Self::loading_progress_ui(ui, wallet);
@@ -199,7 +201,7 @@ impl WalletContent {
ui.add_space(8.0);
let retry_text = format!("{} {}", REPEAT, t!("retry"));
View::button(ui, retry_text, Colors::GOLD, || {
wallet.set_loading_error(false);
wallet.set_load_error(false);
});
});
}
@@ -211,7 +213,7 @@ impl WalletContent {
ui.add_space(18.0);
// Setup loading progress text.
let text = {
let info_progress = wallet.info_loading_progress();
let info_progress = wallet.info_load_progress();
if wallet.is_closing() {
t!("wallets.wallet_closing")
} else if info_progress != 100 {
@@ -221,7 +223,7 @@ impl WalletContent {
format!("{}: {}%", t!("wallets.wallet_loading"), info_progress)
}
} else {
let tx_progress = wallet.txs_loading_progress();
let tx_progress = wallet.txs_load_progress();
if tx_progress == 0 {
t!("wallets.tx_loading")
} else {
+3 -1
View File
@@ -32,6 +32,8 @@ impl WalletTab for WalletInfo {
frame: &mut eframe::Frame,
wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) {
WalletContent::loading_ui(ui, frame, wallet);
if WalletContent::loading_ui(ui, frame, wallet) {
return;
}
}
}
+4
View File
@@ -14,6 +14,7 @@
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
use crate::gui::views::wallets::wallet::WalletContent;
use crate::wallet::Wallet;
/// Receive funds tab content.
@@ -30,5 +31,8 @@ impl WalletTab for WalletReceive {
frame: &mut eframe::Frame,
wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) {
if WalletContent::loading_ui(ui, frame, wallet) {
return;
}
}
}
+4
View File
@@ -14,6 +14,7 @@
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
use crate::gui::views::wallets::wallet::WalletContent;
use crate::wallet::Wallet;
/// Send funds tab content.
@@ -30,5 +31,8 @@ impl WalletTab for WalletSend {
frame: &mut eframe::Frame,
wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) {
if WalletContent::loading_ui(ui, frame, wallet) {
return;
}
}
}
+18 -2
View File
@@ -15,15 +15,25 @@
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::setup::ConnectionSetup;
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
use crate::wallet::Wallet;
use crate::gui::views::wallets::wallet::WalletContent;
use crate::wallet::{ExternalConnection, Wallet};
/// Wallet settings tab content.
#[derive(Default)]
pub struct WalletSettings {
/// Connection setup content.
conn_setup: ConnectionSetup
}
impl Default for WalletSettings {
fn default() -> Self {
// Check external connections availability on first tab opening.
ExternalConnection::start_ext_conn_availability_check();
Self {
conn_setup: ConnectionSetup::default(),
}
}
}
impl WalletTab for WalletSettings {
fn get_type(&self) -> WalletTabType {
WalletTabType::Settings
@@ -34,6 +44,12 @@ impl WalletTab for WalletSettings {
frame: &mut eframe::Frame,
wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) {
// Show progress if wallet is loading after opening without an error.
if wallet.is_closing() || (wallet.get_data().is_none() && !wallet.load_error()) {
WalletContent::loading_progress_ui(ui, wallet);
return;
}
self.conn_setup.wallet_ui(ui, frame, wallet, cb);
}
}