mirror of
https://github.com/jwinterm/grim.git
synced 2026-10-24 16:38:35 +02:00
ui + wallets: modal width detection fix, connections refactoring, mnemonic location refactoring, wallet creation callback, wallet loading, wallet list state refactoring, fix integrated node wallet connection, update translations, remove zeroize dependency
This commit is contained in:
@@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::RwLock;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::RwLock;
|
||||
|
||||
use egui::{Align2, RichText, Rounding, Stroke, Vec2};
|
||||
use egui::epaint::RectShape;
|
||||
@@ -167,17 +167,17 @@ impl Modal {
|
||||
}
|
||||
|
||||
/// Draw opened [`Modal`] content.
|
||||
pub fn ui(ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
|
||||
pub fn ui(ctx: &egui::Context, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
|
||||
if let Some(modal) = &MODAL_STATE.read().unwrap().modal {
|
||||
if modal.is_open() {
|
||||
modal.window_ui(ui, add_content);
|
||||
modal.window_ui(ctx, add_content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw [`egui::Window`] with provided content.
|
||||
fn window_ui(&self, ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
|
||||
let rect = ui.ctx().screen_rect();
|
||||
fn window_ui(&self, ctx: &egui::Context, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
|
||||
let rect = ctx.screen_rect();
|
||||
egui::Window::new("modal_bg_window")
|
||||
.title_bar(false)
|
||||
.resizable(false)
|
||||
@@ -187,13 +187,13 @@ impl Modal {
|
||||
fill: Colors::SEMI_TRANSPARENT,
|
||||
..Default::default()
|
||||
})
|
||||
.show(ui.ctx(), |ui| {
|
||||
.show(ctx, |ui| {
|
||||
ui.set_min_size(rect.size());
|
||||
});
|
||||
|
||||
// Setup width of modal content.
|
||||
let side_insets = View::get_left_inset() + View::get_right_inset();
|
||||
let available_width = ui.available_width() - (side_insets + Self::DEFAULT_MARGIN);
|
||||
let available_width = rect.width() - (side_insets + Self::DEFAULT_MARGIN);
|
||||
let width = f32::min(available_width, Self::DEFAULT_WIDTH);
|
||||
|
||||
// Show main content Window at given position.
|
||||
@@ -209,7 +209,7 @@ impl Modal {
|
||||
fill: Colors::YELLOW,
|
||||
..Default::default()
|
||||
})
|
||||
.show(ui.ctx(), |ui| {
|
||||
.show(ctx, |ui| {
|
||||
if self.title.is_some() {
|
||||
self.title_ui(ui);
|
||||
}
|
||||
@@ -217,7 +217,7 @@ impl Modal {
|
||||
}).unwrap().response.layer_id;
|
||||
|
||||
// Always show main content Window above background Window.
|
||||
ui.ctx().move_to_top(layer_id);
|
||||
ctx.move_to_top(layer_id);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ impl NetworkContent {
|
||||
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() {
|
||||
Modal::ui(ui, |ui, modal| {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.current_tab.as_mut().on_modal_ui(ui, modal, cb);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ impl Root {
|
||||
|
||||
/// Draw exit confirmation modal content.
|
||||
fn exit_modal_content(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
Modal::ui(ui, |ui, modal| {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
if self.show_exit_progress {
|
||||
if !Node::is_running() {
|
||||
self.exit(frame);
|
||||
|
||||
@@ -14,18 +14,25 @@
|
||||
|
||||
use egui::{Align, Align2, Layout, Margin, RichText, Rounding, ScrollArea, TextStyle, Widget};
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
use grin_core::global::ChainTypes;
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, EYE, EYE_SLASH, FOLDER_LOCK, FOLDER_OPEN, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SUITCASE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, ModalContainer, ModalPosition, 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::gui::views::wallets::WalletContent;
|
||||
use crate::wallet::{Wallet, Wallets};
|
||||
|
||||
/// Wallets content.
|
||||
pub struct WalletsContent {
|
||||
/// Chain type for loaded wallets.
|
||||
chain_type: ChainTypes,
|
||||
/// Loaded list of wallets.
|
||||
wallets: Wallets,
|
||||
|
||||
/// Password to open wallet for [`Modal`].
|
||||
pass_edit: String,
|
||||
/// Flag to show/hide password at [`egui::TextEdit`] field.
|
||||
@@ -48,6 +55,8 @@ pub struct WalletsContent {
|
||||
impl Default for WalletsContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
chain_type: AppConfig::chain_type(),
|
||||
wallets: Wallets::default(),
|
||||
pass_edit: "".to_string(),
|
||||
hide_pass: true,
|
||||
wrong_pass: false,
|
||||
@@ -72,12 +81,12 @@ impl ModalContainer for WalletsContent {
|
||||
|
||||
impl WalletsContent {
|
||||
/// Identifier for wallet opening [`Modal`].
|
||||
pub const OPEN_WALLET_MODAL: &'static str = "open_wallet_modal";
|
||||
const OPEN_WALLET_MODAL: &'static str = "open_wallet_modal";
|
||||
|
||||
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() {
|
||||
Modal::ui(ui, |ui, modal| {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
match modal.id {
|
||||
Self::OPEN_WALLET_MODAL => {
|
||||
self.open_wallet_modal_ui(ui, modal, cb);
|
||||
@@ -96,17 +105,17 @@ impl WalletsContent {
|
||||
});
|
||||
}
|
||||
|
||||
// Get wallets.
|
||||
let wallets = Wallets::list();
|
||||
let empty_list = wallets.is_empty();
|
||||
// Setup list of wallets if chain type was changed.
|
||||
let chain_type = AppConfig::chain_type();
|
||||
if self.chain_type != chain_type {
|
||||
self.wallets.reinit(&chain_type);
|
||||
self.chain_type = chain_type;
|
||||
}
|
||||
let empty_list = self.wallets.list.is_empty();
|
||||
|
||||
// Setup wallet content flags.
|
||||
let create_wallet = self.creation_content.can_go_back();
|
||||
let show_wallet = if let Some(id) = Wallets::selected_id() {
|
||||
Wallets::is_open(id)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let show_wallet = self.wallets.is_selected_open();
|
||||
|
||||
// Setup panels parameters.
|
||||
let dual_panel = is_dual_panel_mode(ui, frame);
|
||||
@@ -135,15 +144,17 @@ impl WalletsContent {
|
||||
if available_width_zero {
|
||||
return;
|
||||
}
|
||||
|
||||
if create_wallet || !show_wallet {
|
||||
// Show wallet creation content
|
||||
self.creation_content.ui(ui, cb);
|
||||
// Show wallet creation content.
|
||||
self.creation_content.ui(ui, cb, |wallet| {
|
||||
// Add created wallet to list.
|
||||
self.wallets.add(wallet);
|
||||
});
|
||||
} else {
|
||||
for wallet in wallets.iter() {
|
||||
for mut wallet in self.wallets.list.clone() {
|
||||
// Show content for selected wallet.
|
||||
if Some(wallet.config.id) == Wallets::selected_id() {
|
||||
self.wallet_content.ui(ui, frame, &wallet, cb);
|
||||
if self.wallets.is_selected(wallet.config.id) {
|
||||
self.wallet_content.ui(ui, frame, &mut wallet, cb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -189,7 +200,7 @@ impl WalletsContent {
|
||||
= (!show_wallet && !dual_panel) || (dual_panel && show_wallet);
|
||||
|
||||
// Show list of wallets.
|
||||
let scroll = self.list_ui(ui, dual_panel, show_creation_btn, &wallets, cb);
|
||||
let scroll = self.list_ui(ui, dual_panel, show_creation_btn, cb);
|
||||
|
||||
if show_creation_btn {
|
||||
// Setup right margin for button.
|
||||
@@ -221,7 +232,7 @@ impl WalletsContent {
|
||||
TitlePanel::ui(title_content, |ui, frame| {
|
||||
if show_wallet && !dual_panel {
|
||||
View::title_button(ui, ARROW_LEFT, || {
|
||||
Wallets::select(None);
|
||||
self.wallets.select(None);
|
||||
});
|
||||
} else if create_wallet {
|
||||
View::title_button(ui, ARROW_LEFT, || {
|
||||
@@ -277,7 +288,6 @@ impl WalletsContent {
|
||||
ui: &mut egui::Ui,
|
||||
dual_panel: bool,
|
||||
show_creation_btn: bool,
|
||||
wallets: &Vec<Wallet>,
|
||||
cb: &dyn PlatformCallbacks) -> bool {
|
||||
let mut scroller_showing = false;
|
||||
ui.scope(|ui| {
|
||||
@@ -303,18 +313,14 @@ impl WalletsContent {
|
||||
rect.set_width(width);
|
||||
|
||||
ui.allocate_ui(rect.size(), |ui| {
|
||||
for (index, w) in wallets.iter().enumerate() {
|
||||
for mut wallet in self.wallets.list.clone() {
|
||||
// Draw wallet list item.
|
||||
self.wallet_item_ui(ui, w, cb);
|
||||
// Add space after last item.
|
||||
let last_item = index == wallets.len() - 1;
|
||||
if !last_item {
|
||||
ui.add_space(5.0);
|
||||
}
|
||||
// Add space for wallet creation button.
|
||||
if show_creation_btn && last_item {
|
||||
ui.add_space(57.0);
|
||||
}
|
||||
self.wallet_item_ui(ui, &mut wallet, cb);
|
||||
ui.add_space(5.0);
|
||||
}
|
||||
// Add space for wallet creation button.
|
||||
if show_creation_btn {
|
||||
ui.add_space(52.0);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -326,11 +332,13 @@ impl WalletsContent {
|
||||
}
|
||||
|
||||
/// Draw wallet list item.
|
||||
fn wallet_item_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
fn wallet_item_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
let id = wallet.config.id;
|
||||
let is_selected = Some(id) == Wallets::selected_id();
|
||||
let is_open = Wallets::is_open(id);
|
||||
let is_current = is_open && is_selected;
|
||||
let is_selected = self.wallets.is_selected(id);
|
||||
let is_current = wallet.is_open() && is_selected;
|
||||
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
@@ -348,21 +356,21 @@ impl WalletsContent {
|
||||
ui.style_mut().visuals.widgets.hovered.rounding = Rounding::same(8.0);
|
||||
ui.style_mut().visuals.widgets.active.rounding = Rounding::same(8.0);
|
||||
|
||||
if !is_open {
|
||||
if !wallet.is_open() {
|
||||
// Show button to open closed wallet.
|
||||
View::item_button(ui, [false, true], FOLDER_OPEN, || {
|
||||
Wallets::select(Some(id));
|
||||
self.wallets.select(Some(id));
|
||||
self.show_open_wallet_modal(cb);
|
||||
});
|
||||
} else if !is_selected {
|
||||
// Show button to select opened wallet.
|
||||
View::item_button(ui, [false, true], CARET_RIGHT, || {
|
||||
Wallets::select(Some(id));
|
||||
self.wallets.select(Some(id));
|
||||
});
|
||||
|
||||
// Show button to close opened wallet.
|
||||
View::item_button(ui, [false, false], LOCK_KEY, || {
|
||||
Wallets::close(id).unwrap()
|
||||
let _ = wallet.close();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -386,7 +394,7 @@ impl WalletsContent {
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup wallet status text.
|
||||
let status_text = if Wallets::is_open(id) {
|
||||
let status_text = if wallet.is_open() {
|
||||
format!("{} {}", FOLDER_OPEN, t!("wallets.unlocked"))
|
||||
} else {
|
||||
format!("{} {}", FOLDER_LOCK, t!("wallets.locked"))
|
||||
@@ -497,23 +505,18 @@ impl WalletsContent {
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
|
||||
// Clear values.
|
||||
self.pass_edit = "".to_string();
|
||||
self.wrong_pass = false;
|
||||
self.hide_pass = true;
|
||||
// Close modal.
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
// Callback for continue button.
|
||||
// Callback for button to continue.
|
||||
let mut on_continue = || {
|
||||
if self.pass_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
let selected_id = Wallets::selected_id().unwrap();
|
||||
match Wallets::open(selected_id, self.pass_edit.clone()) {
|
||||
match self.wallets.open_selected(self.pass_edit.clone()) {
|
||||
Ok(_) => {
|
||||
// Clear values.
|
||||
self.pass_edit = "".to_string();
|
||||
@@ -526,6 +529,7 @@ impl WalletsContent {
|
||||
Err(_) => self.wrong_pass = true
|
||||
}
|
||||
};
|
||||
|
||||
// Continue on Enter key press.
|
||||
View::on_enter_key(ui, || {
|
||||
(on_continue)();
|
||||
|
||||
@@ -21,9 +21,10 @@ use crate::gui::icons::{CHECK, EYE, EYE_SLASH, FOLDER_PLUS, SHARE_FAT};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, ModalPosition, View};
|
||||
use crate::gui::views::wallets::creation::MnemonicSetup;
|
||||
use crate::gui::views::wallets::creation::types::{PhraseMode, Step};
|
||||
use crate::gui::views::wallets::creation::types::Step;
|
||||
use crate::gui::views::wallets::setup::ConnectionSetup;
|
||||
use crate::wallet::Wallets;
|
||||
use crate::wallet::types::PhraseMode;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Wallet creation content.
|
||||
pub struct WalletCreation {
|
||||
@@ -61,7 +62,7 @@ impl Default for WalletCreation {
|
||||
logo: RetainedImage::from_image_bytes(
|
||||
"logo.png",
|
||||
include_bytes!("../../../../../img/logo.png"),
|
||||
).unwrap(),
|
||||
).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +71,10 @@ impl WalletCreation {
|
||||
/// Wallet name/password input modal identifier.
|
||||
pub const NAME_PASS_MODAL: &'static str = "name_pass_modal";
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_create: impl FnOnce(Wallet)) {
|
||||
// Show wallet creation step description and confirmation panel.
|
||||
if self.step.is_some() {
|
||||
egui::TopBottomPanel::bottom("wallet_creation_step_panel")
|
||||
@@ -87,7 +91,7 @@ impl WalletCreation {
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
self.step_control_ui(ui);
|
||||
self.step_control_ui(ui, on_create);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -109,8 +113,16 @@ impl WalletCreation {
|
||||
});
|
||||
}
|
||||
|
||||
/// Reset wallet creation to default values.
|
||||
fn reset(&mut self) {
|
||||
self.step = None;
|
||||
self.name_edit = String::from("");
|
||||
self.pass_edit = String::from("");
|
||||
self.mnemonic_setup.reset();
|
||||
}
|
||||
|
||||
/// Draw [`Step`] description and confirmation control.
|
||||
fn step_control_ui(&mut self, ui: &mut egui::Ui) {
|
||||
fn step_control_ui(&mut self, ui: &mut egui::Ui, on_create: impl FnOnce(Wallet)) {
|
||||
if let Some(step) = &self.step {
|
||||
// Setup step description text and availability.
|
||||
let (step_text, mut step_available) = match step {
|
||||
@@ -164,7 +176,7 @@ impl WalletCreation {
|
||||
ui.add_space(4.0);
|
||||
// Show button.
|
||||
View::button(ui, next_text.to_uppercase(), color, || {
|
||||
self.forward();
|
||||
self.forward(Some(on_create));
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
@@ -223,13 +235,7 @@ impl WalletCreation {
|
||||
None => {}
|
||||
Some(step) => {
|
||||
match step {
|
||||
Step::EnterMnemonic => {
|
||||
// Clear values if it needs to go back on first step.
|
||||
self.step = None;
|
||||
self.name_edit = String::from("");
|
||||
self.pass_edit = String::from("");
|
||||
self.mnemonic_setup.reset();
|
||||
}
|
||||
Step::EnterMnemonic => self.reset(),
|
||||
Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic),
|
||||
Step::SetupConnection => self.step = Some(Step::EnterMnemonic)
|
||||
}
|
||||
@@ -238,37 +244,41 @@ impl WalletCreation {
|
||||
}
|
||||
|
||||
/// Go to the next wallet creation [`Step`].
|
||||
fn forward(&mut self) {
|
||||
self.step = match &self.step {
|
||||
None => Some(Step::EnterMnemonic),
|
||||
Some(step) => {
|
||||
match step {
|
||||
Step::EnterMnemonic => {
|
||||
if self.mnemonic_setup.mnemonic.mode == PhraseMode::Generate {
|
||||
Some(Step::ConfirmMnemonic)
|
||||
fn forward(&mut self, on_create: Option<impl FnOnce(Wallet)>) {
|
||||
self.step = if let Some(step) = &self.step {
|
||||
match step {
|
||||
Step::EnterMnemonic => {
|
||||
if self.mnemonic_setup.mnemonic.mode == PhraseMode::Generate {
|
||||
Some(Step::ConfirmMnemonic)
|
||||
} else {
|
||||
// Check if entered phrase was valid.
|
||||
if self.mnemonic_setup.valid_phrase {
|
||||
Some(Step::SetupConnection)
|
||||
} else {
|
||||
// Check if entered phrase was valid.
|
||||
if self.mnemonic_setup.valid_phrase {
|
||||
Some(Step::SetupConnection)
|
||||
} else {
|
||||
Some(Step::EnterMnemonic)
|
||||
}
|
||||
Some(Step::EnterMnemonic)
|
||||
}
|
||||
}
|
||||
Step::ConfirmMnemonic => Some(Step::SetupConnection),
|
||||
Step::SetupConnection => {
|
||||
// Create wallet at last step.
|
||||
Wallets::create_wallet(
|
||||
self.name_edit.clone(),
|
||||
self.pass_edit.clone(),
|
||||
self.mnemonic_setup.mnemonic.get_phrase(),
|
||||
self.network_setup.get_ext_conn_url()
|
||||
).unwrap();
|
||||
None
|
||||
}
|
||||
}
|
||||
Step::ConfirmMnemonic => 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 ext_conn = self.network_setup.get_ext_conn_url();
|
||||
let wallet = Wallet::create(name, pass.clone(), phrase, ext_conn).unwrap();
|
||||
// Open created wallet.
|
||||
wallet.open(pass).unwrap();
|
||||
// Pass created wallet to callback.
|
||||
(on_create.unwrap())(wallet);
|
||||
// Reset creation data.
|
||||
self.reset();
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Some(Step::EnterMnemonic)
|
||||
};
|
||||
}
|
||||
|
||||
/// Start wallet creation from showing [`Modal`] to enter name and password.
|
||||
@@ -379,7 +389,7 @@ impl WalletCreation {
|
||||
if self.name_edit.is_empty() || self.pass_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.forward();
|
||||
self.step = Some(Step::EnterMnemonic);
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
};
|
||||
|
||||
@@ -18,7 +18,8 @@ use crate::gui::Colors;
|
||||
use crate::gui::icons::PENCIL;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, ModalPosition, Root, View};
|
||||
use crate::gui::views::wallets::creation::types::{Mnemonic, PhraseMode, PhraseSize};
|
||||
use crate::wallet::Mnemonic;
|
||||
use crate::wallet::types::{PhraseMode, PhraseSize};
|
||||
|
||||
/// Mnemonic phrase setup content.
|
||||
pub struct MnemonicSetup {
|
||||
|
||||
@@ -12,10 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use grin_keychain::mnemonic::{from_entropy, search, to_entropy};
|
||||
use rand::{Rng, thread_rng};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
/// Wallet creation step.
|
||||
#[derive(PartialEq)]
|
||||
pub enum Step {
|
||||
@@ -25,141 +21,4 @@ pub enum Step {
|
||||
ConfirmMnemonic,
|
||||
/// Wallet connection setup.
|
||||
SetupConnection
|
||||
}
|
||||
|
||||
/// Mnemonic phrase setup mode.
|
||||
/// Will be completely cleaned from memory on drop.
|
||||
#[derive(PartialEq, Clone, Zeroize, ZeroizeOnDrop)]
|
||||
pub enum PhraseMode {
|
||||
/// Generate new mnemonic phrase.
|
||||
Generate,
|
||||
/// Import existing mnemonic phrase.
|
||||
Import
|
||||
}
|
||||
|
||||
/// Mnemonic phrase size based on words count.
|
||||
/// Will be completely cleaned from memory on drop.
|
||||
#[derive(PartialEq, Clone, Zeroize, ZeroizeOnDrop)]
|
||||
pub enum PhraseSize { Words12, Words15, Words18, Words21, Words24 }
|
||||
|
||||
impl PhraseSize {
|
||||
pub const VALUES: [PhraseSize; 5] = [
|
||||
PhraseSize::Words12,
|
||||
PhraseSize::Words15,
|
||||
PhraseSize::Words18,
|
||||
PhraseSize::Words21,
|
||||
PhraseSize::Words24
|
||||
];
|
||||
|
||||
/// Gen words count number.
|
||||
pub fn value(&self) -> usize {
|
||||
match *self {
|
||||
PhraseSize::Words12 => 12,
|
||||
PhraseSize::Words15 => 15,
|
||||
PhraseSize::Words18 => 18,
|
||||
PhraseSize::Words21 => 21,
|
||||
PhraseSize::Words24 => 24
|
||||
}
|
||||
}
|
||||
|
||||
/// Gen entropy size for current phrase size.
|
||||
pub fn entropy_size(&self) -> usize {
|
||||
match *self {
|
||||
PhraseSize::Words12 => 16,
|
||||
PhraseSize::Words15 => 20,
|
||||
PhraseSize::Words18 => 24,
|
||||
PhraseSize::Words21 => 28,
|
||||
PhraseSize::Words24 => 32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mnemonic phrase container.
|
||||
/// Will be completely cleaned from memory on drop.
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct Mnemonic {
|
||||
/// Phrase setup mode.
|
||||
pub(crate) mode: PhraseMode,
|
||||
/// Size of phrase based on words count.
|
||||
pub(crate) size: PhraseSize,
|
||||
/// Generated words.
|
||||
pub(crate) words: Vec<String>,
|
||||
/// Words to confirm the phrase.
|
||||
pub(crate) confirm_words: Vec<String>
|
||||
}
|
||||
|
||||
impl Default for Mnemonic {
|
||||
fn default() -> Self {
|
||||
let size = PhraseSize::Words24;
|
||||
let mode = PhraseMode::Generate;
|
||||
let words = Self::generate_words(&mode, &size);
|
||||
let confirm_words = Self::empty_words(&size);
|
||||
Self { mode, size, words, confirm_words }
|
||||
}
|
||||
}
|
||||
|
||||
impl Mnemonic {
|
||||
/// Change mnemonic phrase setup [`PhraseMode`].
|
||||
pub fn set_mode(&mut self, mode: PhraseMode) {
|
||||
self.mode = mode;
|
||||
self.words = Self::generate_words(&self.mode, &self.size);
|
||||
self.confirm_words = Self::empty_words(&self.size);
|
||||
}
|
||||
|
||||
/// Change mnemonic phrase words [`PhraseSize`].
|
||||
pub fn set_size(&mut self, size: PhraseSize) {
|
||||
self.size = size;
|
||||
self.words = Self::generate_words(&self.mode, &self.size);
|
||||
self.confirm_words = Self::empty_words(&self.size);
|
||||
}
|
||||
|
||||
/// Check if provided word is in BIP39 format and equal to non-empty generated word at index.
|
||||
pub fn is_valid_word(&self, word: &String, index: usize) -> bool {
|
||||
let valid = search(word).is_ok();
|
||||
let equal = if let Some(gen_word) = self.words.get(index) {
|
||||
gen_word.is_empty() || gen_word == word
|
||||
} else {
|
||||
false
|
||||
};
|
||||
valid && equal
|
||||
}
|
||||
|
||||
/// Check if current phrase is valid.
|
||||
pub fn is_valid_phrase(&self) -> bool {
|
||||
to_entropy(self.get_phrase().as_str()).is_ok()
|
||||
}
|
||||
|
||||
/// Get phrase from words.
|
||||
pub fn get_phrase(&self) -> String {
|
||||
self.words.iter().map(|x| x.to_string() + " ").collect::<String>()
|
||||
}
|
||||
|
||||
/// Generate list of words based on provided [`PhraseMode`] and [`PhraseSize`].
|
||||
fn generate_words(mode: &PhraseMode, size: &PhraseSize) -> Vec<String> {
|
||||
match mode {
|
||||
PhraseMode::Generate => {
|
||||
let mut rng = thread_rng();
|
||||
let mut entropy: Vec<u8> = Vec::with_capacity(size.entropy_size());
|
||||
for _ in 0..size.entropy_size() {
|
||||
entropy.push(rng.gen());
|
||||
}
|
||||
from_entropy(&entropy).unwrap()
|
||||
.split(" ")
|
||||
.map(|s| String::from(s))
|
||||
.collect::<Vec<String>>()
|
||||
},
|
||||
PhraseMode::Import => {
|
||||
Self::empty_words(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate empty list of words based on provided [`PhraseSize`].
|
||||
fn empty_words(size: &PhraseSize) -> Vec<String> {
|
||||
let mut words = Vec::with_capacity(size.value());
|
||||
for _ in 0..size.value() {
|
||||
words.push(String::from(""))
|
||||
}
|
||||
words
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,10 @@
|
||||
// limitations under the License.
|
||||
|
||||
mod creation;
|
||||
mod wallet;
|
||||
mod setup;
|
||||
|
||||
mod content;
|
||||
pub use content::*;
|
||||
pub use content::*;
|
||||
|
||||
mod wallet;
|
||||
use wallet::WalletContent;
|
||||
@@ -15,13 +15,12 @@
|
||||
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, ModalPosition, View};
|
||||
use crate::gui::views::wallets::setup::ConnectionMethod;
|
||||
use crate::wallet::ExternalConnection;
|
||||
use crate::wallet::{ConnectionsConfig, ExternalConnection};
|
||||
|
||||
/// Wallet node connection method setup content.
|
||||
pub struct ConnectionSetup {
|
||||
@@ -106,7 +105,7 @@ impl ConnectionSetup {
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Show external nodes URLs selection.
|
||||
for conn in AppConfig::external_connections() {
|
||||
for conn in ConnectionsConfig::external_connections() {
|
||||
View::radio_value(ui,
|
||||
&mut self.method,
|
||||
ConnectionMethod::External(conn.url.clone()),
|
||||
@@ -196,7 +195,7 @@ impl ConnectionSetup {
|
||||
Some(self.ext_node_secret_edit.to_owned())
|
||||
};
|
||||
let ext_conn = ExternalConnection::new(url.clone(), secret);
|
||||
AppConfig::add_external_connection(ext_conn);
|
||||
ConnectionsConfig::add_external_connection(ext_conn);
|
||||
|
||||
// Set added method as current.
|
||||
self.method = ConnectionMethod::External(url);
|
||||
|
||||
@@ -34,7 +34,7 @@ impl WalletContent {
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
wallet: &Wallet,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Show wallet content.
|
||||
egui::CentralPanel::default()
|
||||
@@ -54,4 +54,6 @@ impl WalletContent {
|
||||
//TODO: wallet content
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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.
|
||||
|
||||
mod content;
|
||||
mod txs;
|
||||
mod send;
|
||||
mod settings;
|
||||
mod receive;
|
||||
|
||||
pub use content::WalletContent;
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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.
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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.
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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.
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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.
|
||||
Reference in New Issue
Block a user