ui + config: wallet connections setup, initial wallet config, wallet list state, update translations

This commit is contained in:
ardocrat
2023-07-25 03:42:52 +03:00
parent 5cefb61bb3
commit f461f27e4c
23 changed files with 564 additions and 127 deletions
+5 -5
View File
@@ -20,7 +20,7 @@ use lazy_static::lazy_static;
use crate::gui::Colors;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Wallets, Modal, ModalContainer, Network, View};
use crate::gui::views::{WalletsContent, Modal, ModalContainer, Network, View};
use crate::node::Node;
lazy_static! {
@@ -32,8 +32,8 @@ lazy_static! {
pub struct Root {
/// Side panel [`Network`] content.
network: Network,
/// Central panel [`Wallets`] content.
wallets: Wallets,
/// Central panel [`WalletsContent`] content.
wallets: WalletsContent,
/// Check if app exit is allowed on close event of [`eframe::App`] implementation.
pub(crate) exit_allowed: bool,
@@ -52,7 +52,7 @@ impl Default for Root {
let exit_allowed = os == OperatingSystem::Android || os == OperatingSystem::IOS;
Self {
network: Network::default(),
wallets: Wallets::default(),
wallets: WalletsContent::default(),
exit_allowed,
show_exit_progress: false,
allowed_modal_ids: vec![
@@ -117,7 +117,7 @@ impl Root {
(is_panel_open, panel_width)
}
/// Check if ui can show [`Network`] and [`Wallets`] at same time.
/// Check if ui can show [`Network`] and [`WalletsContent`] at same time.
pub fn is_dual_panel_mode(frame: &mut eframe::Frame) -> bool {
let w = frame.info().window_info.size.x;
let h = frame.info().window_info.size.y;
+16 -20
View File
@@ -14,14 +14,15 @@
use egui::{Margin, RichText, TextStyle, vec2, Widget};
use egui_extras::{RetainedImage, Size, StripBuilder};
use crate::built_info;
use crate::built_info;
use crate::gui::Colors;
use crate::gui::icons::{CHECK, EYE, EYE_SLASH, PLUS_CIRCLE, SHARE_FAT};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalPosition, View};
use crate::gui::views::wallets::creation::{ConnectionSetup, MnemonicSetup};
use crate::gui::views::wallets::creation::MnemonicSetup;
use crate::gui::views::wallets::creation::types::{PhraseMode, Step};
use crate::gui::views::wallets::setup::ConnectionSetup;
/// Wallet creation content.
pub struct WalletCreation {
@@ -51,8 +52,8 @@ impl Default for WalletCreation {
Self {
step: None,
modal_just_opened: true,
name_edit: "".to_string(),
pass_edit: "".to_string(),
name_edit: String::from(""),
pass_edit: String::from(""),
hide_pass: true,
mnemonic_setup: MnemonicSetup::default(),
network_setup: ConnectionSetup::default(),
@@ -89,17 +90,16 @@ impl WalletCreation {
let (step_text, step_available) = match step {
Step::EnterMnemonic => {
let mode = &self.mnemonic_setup.mnemonic.mode;
let size_value = self.mnemonic_setup.mnemonic.size.value();
let text = if mode == &PhraseMode::Generate {
t!("wallets.create_phrase_desc", "number" => size_value)
t!("wallets.create_phrase_desc")
} else {
t!("wallets.restore_phrase_desc", "number" => size_value)
t!("wallets.restore_phrase_desc")
};
let available = !self
.mnemonic_setup
.mnemonic
.words
.contains(&"".to_string());
.contains(&String::from(""));
(text, available)
}
Step::ConfirmMnemonic => {
@@ -108,7 +108,7 @@ impl WalletCreation {
.mnemonic_setup
.mnemonic
.confirm_words
.contains(&"".to_string());
.contains(&String::from(""));
(text, available)
},
Step::SetupConnection => (t!("wallets.setup_conn_desc"), true)
@@ -190,13 +190,9 @@ impl WalletCreation {
}
Some(step) => {
match step {
Step::EnterMnemonic => {
self.mnemonic_setup.ui(ui);
}
Step::ConfirmMnemonic => {
self.mnemonic_setup.confirm_ui(ui);
}
Step::SetupConnection => {}
Step::EnterMnemonic => self.mnemonic_setup.ui(ui),
Step::ConfirmMnemonic => self.mnemonic_setup.confirm_ui(ui),
Step::SetupConnection => self.network_setup.ui(ui, cb)
}
}
}
@@ -216,8 +212,8 @@ impl WalletCreation {
Step::EnterMnemonic => {
// Clear values if it needs to go back on first step.
self.step = None;
self.name_edit = "".to_string();
self.pass_edit = "".to_string();
self.name_edit = String::from("");
self.pass_edit = String::from("");
self.mnemonic_setup.reset();
}
Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic),
@@ -255,8 +251,8 @@ impl WalletCreation {
// Reset modal values.
self.hide_pass = false;
self.modal_just_opened = true;
self.name_edit = "".to_string();
self.pass_edit = "".to_string();
self.name_edit = String::from("");
self.pass_edit = String::from("");
// Show modal.
Modal::new(Self::NAME_PASS_MODAL)
.position(ModalPosition::CenterTop)
+10 -6
View File
@@ -38,7 +38,7 @@ impl Default for MnemonicSetup {
Self {
mnemonic: Mnemonic::default(),
word_num_edit: 0,
word_edit: "".to_string(),
word_edit: String::from(""),
valid_word_edit: true
}
}
@@ -192,12 +192,12 @@ impl MnemonicSetup {
}
/// Draw word list item for current mode.
fn word_item_ui(&mut self, ui: &mut egui::Ui, word_number: usize, word: &String, edit: bool) {
fn word_item_ui(&mut self, ui: &mut egui::Ui, num: usize, word: &String, edit: bool) {
if edit {
ui.add_space(6.0);
View::button(ui, PENCIL.to_string(), Colors::BUTTON, || {
// Setup modal values.
self.word_num_edit = word_number;
self.word_num_edit = num;
self.word_edit = word.clone();
self.valid_word_edit = true;
// Show word edit modal.
@@ -206,12 +206,12 @@ impl MnemonicSetup {
.title(t!("wallets.saved_phrase"))
.show();
});
ui.label(RichText::new(format!("#{} {}", word_number, word))
ui.label(RichText::new(format!("#{} {}", num, word))
.size(17.0)
.color(Colors::BLACK));
} else {
ui.add_space(12.0);
let text = format!("#{} {}", word_number, word);
let text = format!("#{} {}", num, word);
ui.label(RichText::new(text).size(17.0).color(Colors::BLACK));
}
}
@@ -274,14 +274,18 @@ impl MnemonicSetup {
self.valid_word_edit = false;
return;
}
self.valid_word_edit = true;
// Select list where to save word.
let words = match self.mnemonic.mode {
PhraseMode::Generate => &mut self.mnemonic.confirm_words,
PhraseMode::Import => &mut self.mnemonic.words
};
// Save word at list.
words.remove(word_index);
words.insert(word_index, self.word_edit.clone());
// Close modal or go to next word to edit.
let close_modal = words.len() == self.word_num_edit
|| !words.get(self.word_num_edit).unwrap().is_empty();
@@ -290,7 +294,7 @@ impl MnemonicSetup {
modal.close();
} else {
self.word_num_edit += 1;
self.word_edit = "".to_string();
self.word_edit = String::from("");
}
};
// Call save on Enter key press.
-3
View File
@@ -15,9 +15,6 @@
mod mnemonic;
pub use mnemonic::MnemonicSetup;
mod connection;
pub use connection::ConnectionSetup;
mod creation;
pub use creation::WalletCreation;
+9 -4
View File
@@ -14,6 +14,7 @@
use grin_keychain::mnemonic::{from_entropy, search};
use rand::{Rng, thread_rng};
use zeroize::{Zeroize, ZeroizeOnDrop};
/// Wallet creation step.
#[derive(PartialEq)]
@@ -27,7 +28,8 @@ pub enum Step {
}
/// Mnemonic phrase setup mode.
#[derive(PartialEq, Clone)]
/// Will be completely cleaned from memory on drop.
#[derive(PartialEq, Clone, Zeroize, ZeroizeOnDrop)]
pub enum PhraseMode {
/// Generate new mnemonic phrase.
Generate,
@@ -36,7 +38,8 @@ pub enum PhraseMode {
}
/// Mnemonic phrase size based on words count.
#[derive(PartialEq, Clone)]
/// Will be completely cleaned from memory on drop.
#[derive(PartialEq, Clone, Zeroize, ZeroizeOnDrop)]
pub enum PhraseSize { Words12, Words15, Words18, Words21, Words24 }
impl PhraseSize {
@@ -72,6 +75,8 @@ impl PhraseSize {
}
/// Mnemonic phrase container.
/// Will be completely cleaned from memory on drop.
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct Mnemonic {
/// Phrase setup mode.
pub(crate) mode: PhraseMode,
@@ -130,7 +135,7 @@ impl Mnemonic {
}
from_entropy(&entropy).unwrap()
.split(" ")
.map(|s| s.to_string())
.map(|s| String::from(s))
.collect::<Vec<String>>()
},
PhraseMode::Import => {
@@ -143,7 +148,7 @@ impl Mnemonic {
fn empty_words(size: &PhraseSize) -> Vec<String> {
let mut words = Vec::with_capacity(size.value());
for _ in 0..size.value() {
words.push("".to_string())
words.push(String::from(""))
}
words
}
+2 -1
View File
@@ -12,8 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
mod wallets;
mod creation;
mod wallet;
mod setup;
mod wallets;
pub use wallets::*;
+165
View File
@@ -0,0 +1,165 @@
// 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.
use egui::{Id, RichText, ScrollArea, TextStyle, Widget};
use url::Url;
use crate::AppConfig;
use crate::gui::Colors;
use crate::gui::icons::{GLOBE, GLOBE_SIMPLE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, View};
use crate::gui::views::wallets::setup::ConnectionMethod;
/// Wallet node connection method setup content.
pub struct ConnectionSetup {
/// Selected connection method.
method: ConnectionMethod,
/// External node connection URL value for [`Modal`].
ext_node_url_edit: String,
/// Flag to show URL format error.
ext_node_url_error: bool,
}
impl Default for ConnectionSetup {
fn default() -> Self {
Self {
method: ConnectionMethod::Integrated,
ext_node_url_edit: "".to_string(),
ext_node_url_error: false
}
}
}
impl ConnectionSetup {
/// External node connection [`Modal`] identifier.
pub const ADD_CONNECTION_URL_MODAL: &'static str = "add_connection_url_modal";
//TODO: Setup for provided wallet
// pub fn new() -> Self {
// Self { method: ConnectionMethod::Integrated }
// }
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
ScrollArea::vertical()
.id_source("wallet_connection_setup")
.auto_shrink([false; 2])
.show(ui, |ui| {
View::sub_title(ui, format!("{} {}", GLOBE, t!("wallets.conn_method")));
View::horizontal_line(ui, Colors::STROKE);
ui.add_space(4.0);
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"));
ui.add_space(10.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_url"));
View::button(ui, add_node_text, Colors::GOLD, || {
// Setup values for Modal.
self.ext_node_url_edit = "".to_string();
self.ext_node_url_error = false;
// Show modal.
Modal::new(Self::ADD_CONNECTION_URL_MODAL)
.title(t!("wallets.ext_conn"))
.show();
cb.show_keyboard();
});
ui.add_space(12.0);
// Show external nodes URLs selection.
for conn in AppConfig::external_nodes_urls() {
View::radio_value(ui,
&mut self.method,
ConnectionMethod::External(conn.clone()),
conn);
ui.add_space(12.0);
}
});
});
}
/// Draw external connections setup.
fn external_conn_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
}
/// Draw modal content.
pub fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
ui.add_space(6.0);
ui.vertical_centered(|ui| {
// Draw external node URL text edit.
let text_edit_resp = egui::TextEdit::singleline(&mut self.ext_node_url_edit)
.id(Id::from(modal.id))
.font(TextStyle::Heading)
.desired_width(ui.available_width())
.cursor_at_end(true)
.ui(ui);
text_edit_resp.request_focus();
if text_edit_resp.clicked() {
cb.show_keyboard();
}
// Show error when specified URL is not valid.
if self.ext_node_url_error {
ui.add_space(12.0);
ui.label(RichText::new(t!("wallets.invalid_url"))
.size(17.0)
.color(Colors::RED));
}
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);
// Add button callback.
let on_add = || {
let error = Url::parse(self.ext_node_url_edit.as_str()).is_err();
self.ext_node_url_error = error;
if !error {
AppConfig::add_external_node_url(self.ext_node_url_edit.clone());
// Close modal.
cb.hide_keyboard();
modal.close();
}
};
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
// Close modal.
cb.hide_keyboard();
modal.close();
});
});
columns[1].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.add"), Colors::WHITE, on_add);
});
});
ui.add_space(6.0);
});
}
}
@@ -12,15 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::gui::platform::PlatformCallbacks;
mod connection;
pub use connection::ConnectionSetup;
#[derive(Default)]
pub struct ConnectionSetup {
}
impl ConnectionSetup {
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
}
}
mod types;
pub use types::*;
+22
View File
@@ -0,0 +1,22 @@
// 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 node connection method type.
#[derive(PartialEq)]
pub enum ConnectionMethod {
/// Integrated node connection.
Integrated,
/// External node connection.
External(String)
}
+4 -3
View File
@@ -17,16 +17,17 @@ use egui::Margin;
use crate::gui::Colors;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::View;
use crate::wallet::Wallet;
/// Selected wallet list item content.
pub struct WalletContent {
/// Current wallet instance.
item: String
wallet: Wallet
}
impl WalletContent {
fn new(item: String) -> Self {
Self { item }
fn new(wallet: Wallet) -> Self {
Self { wallet }
}
}
+14 -9
View File
@@ -21,12 +21,14 @@ use crate::gui::icons::{ARROW_LEFT, GEAR, GLOBE, PLUS};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, ModalContainer, Root, TitlePanel, TitleType, View};
use crate::gui::views::wallets::creation::{MnemonicSetup, WalletCreation};
use crate::gui::views::wallets::setup::ConnectionSetup;
use crate::gui::views::wallets::wallet::WalletContent;
use crate::wallet::{Wallet, WalletList};
/// Wallets content.
pub struct Wallets {
pub struct WalletsContent {
/// List of wallets.
list: Vec<String>,
list: Vec<Wallet>,
/// Selected list item content.
item_content: Option<WalletContent>,
@@ -37,28 +39,28 @@ pub struct Wallets {
modal_ids: Vec<&'static str>
}
impl Default for Wallets {
impl Default for WalletsContent {
fn default() -> Self {
//TODO load list.
Self {
list: vec![],
list: WalletList::list(),
item_content: None,
creation_content: WalletCreation::default(),
modal_ids: vec![
WalletCreation::NAME_PASS_MODAL,
MnemonicSetup::WORD_INPUT_MODAL
MnemonicSetup::WORD_INPUT_MODAL,
ConnectionSetup::ADD_CONNECTION_URL_MODAL
]
}
}
}
impl ModalContainer for Wallets {
impl ModalContainer for WalletsContent {
fn modal_ids(&self) -> &Vec<&'static str> {
&self.modal_ids
}
}
impl Wallets {
impl WalletsContent {
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
// Show modal content for current ui container.
if self.can_draw_modal() {
@@ -70,6 +72,9 @@ impl Wallets {
MnemonicSetup::WORD_INPUT_MODAL => {
self.creation_content.mnemonic_setup.modal_ui(ui, modal, cb);
}
ConnectionSetup::ADD_CONNECTION_URL_MODAL => {
self.creation_content.network_setup.modal_ui(ui, modal, cb);
}
_ => {}
}
});
@@ -176,7 +181,7 @@ impl Wallets {
}
}
/// Check if ui can show [`Wallets`] list and [`WalletContent`] content at same time.
/// Check if ui can show [`WalletsContent`] list and [`WalletContent`] content at same time.
fn is_dual_panel_mode(ui: &mut egui::Ui, frame: &mut eframe::Frame) -> bool {
let dual_panel_root = Root::is_dual_panel_mode(frame);
let max_width = ui.available_width();