diff --git a/Cargo.lock b/Cargo.lock index 3248df6..2a29ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1548,6 +1548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9278f4337b526f0d57e5375e5a7340a311fa6ee8f9fcc75721ac50af13face02" dependencies = [ "egui", + "image", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 194b47e..44cb0e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ grin_wallet_impls = { path = "../grin/wallet/impls" } pollster = "0.3.0" wgpu = "0.16.1" egui = { version = "0.22.0", default-features = false } -egui_extras = "0.22.0" +egui_extras = { version = "0.22.0", features = ["image"] } ## other futures = "0.3" diff --git a/img/logo.png b/img/logo.png new file mode 100644 index 0000000..7440957 Binary files /dev/null and b/img/logo.png differ diff --git a/locales/en.yml b/locales/en.yml index 39eae3e..142e4ac 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -11,13 +11,16 @@ wallets: pass: 'Password:' name_empty: Enter name of wallet pass_empty: Enter password for wallet - word_number: 'Word #%{number}' - word_empty: 'Enter word #%{number} from recovery phrase' - not_valid_word: Entered word is not valid create: Create - import: Import - mnemonic_desc: 'Safely write down and save your %{}-word recovery phrase below. If you lose your device, you will need them to restore access to your funds.' - mnemonic_conf: Select words in the same order as they are displayed in your recovery phrase. + recover: Restore + words_count: 'Words count:' + word_number: 'Word #%{number}' + word_empty: 'Enter word #%{number} from your recovery phrase' + not_valid_word: Entered word is not valid + create_phrase_desc: Safely write down and save your recovery phrase. + restore_phrase_desc: Enter words from your saved recovery phrase. + conf_phrase_desc: Select words in the same order as they are displayed in your recovery phrase. + setup_conn_desc: Choose wallet connection method. network: self: Network node: Integrated node diff --git a/locales/ru.yml b/locales/ru.yml index 8286ab3..22ca972 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -11,13 +11,16 @@ wallets: pass: 'Пароль:' name_empty: Введите название кошелька pass_empty: Введите пароль для кошелька - word_number: 'Слово #%{number}' - word_empty: 'Введите слово #%{number} из фразы восстановления' - not_valid_word: Введено недопустимое слово create: Создать - import: Импортировать - mnemonic_desc: 'Безопасно запишите и сохраните свою фразу восстановления из %{} слов ниже. Они понадобятся вам, чтобы восстановить доступ к вашим средствам, если вы потеряете устройство.' - mnemonic_conf: Выберите слова в таком же порядке, как они отображены в вашей фразе восстановления. + recover: Восстановить + words_count: 'Количество слов:' + word_number: 'Слово #%{number}' + word_empty: 'Введите слово #%{number} из вашей фразы восстановления' + not_valid_word: Введено недопустимое слово + create_phrase_desc: Безопасно запишите и сохраните свою фразу восстановления. + restore_phrase_desc: Введите слова из вашей сохранённой фразы восстановления. + conf_phrase_desc: Выберите слова в таком же порядке, как они отображены в вашей фразе восстановления. + setup_conn_desc: Выберите способ подключения кошелька network: self: Сеть node: Встроенный узел diff --git a/src/gui/app.rs b/src/gui/app.rs index f3fc8be..c2d1040 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -49,6 +49,8 @@ impl eframe::App for PlatformApp { BACK_BUTTON_PRESSED.store(false, Ordering::Relaxed); } self.root.on_back(); + // Request repaint to update previous content. + ctx.request_repaint(); } // Show main content. diff --git a/src/gui/views/wallets/creation/connection.rs b/src/gui/views/wallets/creation/connection.rs index 42d3e48..cabc67a 100644 --- a/src/gui/views/wallets/creation/connection.rs +++ b/src/gui/views/wallets/creation/connection.rs @@ -13,7 +13,6 @@ // limitations under the License. use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::wallets::creation::StepControl; #[derive(Default)] pub struct ConnectionSetup { @@ -21,7 +20,7 @@ pub struct ConnectionSetup { } impl ConnectionSetup { - pub fn ui(&mut self, ui: &mut egui::Ui, step: &dyn StepControl, cb: &dyn PlatformCallbacks) { + pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { } } \ No newline at end of file diff --git a/src/gui/views/wallets/creation/content.rs b/src/gui/views/wallets/creation/creation.rs similarity index 68% rename from src/gui/views/wallets/creation/content.rs rename to src/gui/views/wallets/creation/creation.rs index b96a832..cf0cf32 100644 --- a/src/gui/views/wallets/creation/content.rs +++ b/src/gui/views/wallets/creation/creation.rs @@ -12,29 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::{Margin, RichText, TextStyle, Widget}; -use egui_extras::{Size, StripBuilder}; +use egui::{Margin, RichText, TextStyle, vec2, Widget}; +use egui_extras::{RetainedImage, Size, StripBuilder}; +use crate::built_info; use crate::gui::Colors; -use crate::gui::icons::{EYE, EYE_SLASH, PLUS_CIRCLE}; +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, StepControl}; -use crate::gui::views::wallets::creation::mnemonic::PhraseMode; - -/// Wallet creation step. -enum Step { - /// Mnemonic phrase input. - EnterMnemonic, - /// Mnemonic phrase confirmation for [`Mnemonic`]. - ConfirmMnemonic, - /// Wallet connection setup. - SetupConnection -} +use crate::gui::views::wallets::creation::{ConnectionSetup, MnemonicSetup}; +use crate::gui::views::wallets::creation::types::{PhraseMode, Step}; /// Wallet creation content. pub struct WalletCreation { - /// Wallet creation ui step. + /// Wallet creation step. step: Option, /// Flag to check if [`Modal`] just was opened to focus on first field. @@ -50,6 +41,9 @@ pub struct WalletCreation { pub(crate) mnemonic_setup: MnemonicSetup, /// Network setup content. pub(crate) network_setup: ConnectionSetup, + + /// App logo image. + logo: RetainedImage, } impl Default for WalletCreation { @@ -62,52 +56,10 @@ impl Default for WalletCreation { hide_pass: true, mnemonic_setup: MnemonicSetup::default(), network_setup: ConnectionSetup::default(), - } - } -} - - -impl StepControl for WalletCreation { - /// Go to next wallet creation [`Step`]. - fn next_step(&mut self) { - self.step = match &self.step { - None => Some(Step::EnterMnemonic), - Some(step) => { - match step { - Step::EnterMnemonic => { - if self.mnemonic_setup.get_mnemonic_mode() == &PhraseMode::Generate { - Some(Step::SetupConnection) - } else { - Some(Step::ConfirmMnemonic) - } - } - Step::ConfirmMnemonic => Some(Step::SetupConnection), - Step::SetupConnection => { - //TODO: Confirm mnemonic - None - } - } - } - } - } - - /// Go to previous wallet creation [`Step`]. - fn prev_step(&mut self) { - match &self.step { - None => {} - Some(step) => { - match step { - 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.mnemonic_setup.reset(); - } - Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic), - Step::SetupConnection => self.step = Some(Step::ConfirmMnemonic) - } - } + logo: RetainedImage::from_image_bytes( + "logo.png", + include_bytes!("../../../../../img/logo.png"), + ).unwrap(), } } } @@ -117,10 +69,62 @@ impl WalletCreation { pub const MODAL_ID: &'static str = "create_wallet_modal"; pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { + // Show wallet creation step description and confirmation panel. + if self.can_go_back() { + egui::TopBottomPanel::bottom("wallet_creation_step_panel") + .frame(egui::Frame { + stroke: View::DEFAULT_STROKE, + inner_margin: Margin { + left: View::far_left_inset_margin(ui) + 4.0, + right: View::get_right_inset() + 4.0, + top: 4.0, + bottom: View::get_bottom_inset() + 4.0, + }, + ..Default::default() + }) + .show_inside(ui, |ui| { + ui.vertical_centered(|ui| { + if let Some(step) = &self.step { + // Setup step description text. + let step_text = match step { + Step::EnterMnemonic => { + let mode = &self.mnemonic_setup.mnemonic.mode; + let size_value = self.mnemonic_setup.mnemonic.size.value(); + if mode == &PhraseMode::Generate { + t!("wallets.create_phrase_desc", "number" => size_value) + } else { + t!("wallets.restore_phrase_desc", "number" => size_value) + } + } + Step::ConfirmMnemonic => t!("wallets.conf_phrase_desc"), + Step::SetupConnection => t!("wallets.setup_conn_desc") + }; + // Show step description. + ui.label(RichText::new(step_text).size(16.0).color(Colors::GRAY)); + ui.add_space(4.0); + + // Setup next step button text. + let (next_text, color) = if step == &Step::SetupConnection { + (format!("{} {}", CHECK, t!("complete")), Colors::GOLD) + } else { + let text = format!("{} {}", SHARE_FAT, t!("continue")); + (text, Colors::WHITE) + }; + // Show next step button. + View::button(ui, next_text.to_uppercase(), color, || { + self.forward(); + }); + ui.add_space(4.0); + } + }); + }); + } + // Show wallet creation step content. egui::CentralPanel::default() .frame(egui::Frame { stroke: View::DEFAULT_STROKE, + fill: if self.step.is_none() { Colors::FILL_DARK } else { Colors::WHITE }, inner_margin: Margin { left: View::far_left_inset_margin(ui) + 4.0, right: View::get_right_inset() + 4.0, @@ -139,11 +143,24 @@ impl WalletCreation { match &self.step { None => { // Show wallet creation message if step is empty. - View::center_content(ui, 124.0 + View::get_bottom_inset(), |ui| { + View::center_content(ui, 415.0 + View::get_bottom_inset(), |ui| { + ui.add( + egui::Image::new(self.logo.texture_id(ui.ctx()), vec2(200.0, 200.0)) + ); + ui.add_space(-15.0); + ui.label(RichText::new("GRIM") + .size(24.0) + .color(Colors::BLACK) + ); + ui.label(RichText::new(built_info::PKG_VERSION) + .size(16.0) + .color(Colors::BLACK) + ); + ui.add_space(4.0); let text = t!("wallets.create_desc"); ui.label(RichText::new(text) .size(16.0) - .color(Colors::INACTIVE_TEXT) + .color(Colors::GRAY) ); ui.add_space(8.0); let add_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add")); @@ -155,7 +172,7 @@ impl WalletCreation { Some(step) => { match step { Step::EnterMnemonic => { - self.mnemonic_setup.ui(ui, self, cb); + self.mnemonic_setup.ui(ui, cb); } Step::ConfirmMnemonic => {} Step::SetupConnection => {} @@ -169,9 +186,47 @@ impl WalletCreation { self.step.is_some() } - /// Back button key event handling. - pub fn go_back(&mut self) { - self.prev_step(); + /// Back to previous wallet creation [`Step`]. + pub fn back(&mut self) { + match &self.step { + None => {} + Some(step) => { + match step { + 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.mnemonic_setup.reset(); + } + Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic), + Step::SetupConnection => self.step = Some(Step::EnterMnemonic) + } + } + } + } + + /// 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) + } else { + Some(Step::SetupConnection) + } + } + Step::ConfirmMnemonic => Some(Step::SetupConnection), + Step::SetupConnection => { + //TODO: Confirm mnemonic + None + } + } + } + } } /// Start wallet creation from showing [`Modal`] to enter name and password. @@ -181,17 +236,6 @@ impl WalletCreation { .title(t!("wallets.add"))); } - /// Callback to go to next step for wallet creation from [`Modal`]. - fn on_modal_confirmation(&mut self, modal: &Modal, cb: &dyn PlatformCallbacks) { - // 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(); - } - /// Draw wallet creation [`Modal`] content. pub fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { ui.add_space(6.0); @@ -300,7 +344,13 @@ impl WalletCreation { }); columns[1].vertical_centered_justified(|ui| { View::button(ui, t!("continue"), Colors::WHITE, || { - self.on_modal_confirmation(modal, cb); + // Check if input values are not empty. + if self.name_edit.is_empty() || self.pass_edit.is_empty() { + return; + } + self.forward(); + cb.hide_keyboard(); + modal.close(); }); }); }); diff --git a/src/gui/views/wallets/creation/mnemonic.rs b/src/gui/views/wallets/creation/mnemonic.rs index 950718c..209665b 100644 --- a/src/gui/views/wallets/creation/mnemonic.rs +++ b/src/gui/views/wallets/creation/mnemonic.rs @@ -12,89 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use grin_keychain::mnemonic::from_entropy; -use rand::{Rng, thread_rng}; +use egui::{RichText, ScrollArea}; + +use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::wallets::creation::StepControl; - -/// Mnemonic phrase setup mode. -#[derive(PartialEq)] -pub enum PhraseMode { - /// Generate new mnemonic phrase. - Generate, - /// Import existing mnemonic phrase. - Import -} - -/// Mnemonic phrase type based on words count. -pub enum PhraseType { Words12, Words15, Words18, Words21, Words24 } - -impl PhraseType { - pub fn value(&self) -> usize { - match *self { - PhraseType::Words12 => 12, - PhraseType::Words15 => 15, - PhraseType::Words18 => 18, - PhraseType::Words21 => 21, - PhraseType::Words24 => 24 - } - } -} - -/// Mnemonic phrase container. -pub struct Mnemonic { - /// Phrase setup mode. - pub(crate) mode: PhraseMode, - /// Type of phrase based on words count. - size: PhraseType, - /// Words for phrase. - words: Vec -} - -impl Default for Mnemonic { - fn default() -> Self { - let size = PhraseType::Words12; - let size_value = size.value(); - Self { mode: PhraseMode::Generate, size, words: Vec::with_capacity(size_value) } - } -} - -impl Mnemonic { - /// Change mnemonic phrase setup [`PhraseMode`]. - fn set_mode(&mut self, mode: PhraseMode) { - self.mode = mode; - self.setup_words(); - } - - /// Change mnemonic phrase words [`PhraseType`]. - fn set_size(&mut self, size: PhraseType) { - self.size = size; - self.setup_words(); - } - - /// Setup words based on current [`PhraseMode`] and [`PhraseType`]. - fn setup_words(&mut self) { - self.words = match self.mode { - PhraseMode::Generate => { - let mut rng = thread_rng(); - let mut entropy: Vec = Vec::with_capacity(self.size.value()); - for _ in 0..self.size.value() { - entropy.push(rng.gen()); - } - from_entropy(&entropy).unwrap() - .split(" ") - .map(|s| s.to_string()) - .collect::>() - }, - PhraseMode::Import => Vec::with_capacity(self.size.value()) - }; - } -} +use crate::gui::views::{Root, View}; +use crate::gui::views::wallets::creation::types::{Mnemonic, PhraseMode, PhraseSize}; /// Mnemonic phrase setup content. pub struct MnemonicSetup { /// Current mnemonic phrase. - mnemonic: Mnemonic, + pub(crate) mnemonic: Mnemonic, /// Word value for [`Modal`]. word_edit: String, } @@ -109,12 +37,172 @@ impl Default for MnemonicSetup { } impl MnemonicSetup { - pub fn ui(&self, ui: &mut egui::Ui, step: &dyn StepControl, cb: &dyn PlatformCallbacks) { + pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { + ScrollArea::vertical() + .id_source("mnemonic_words_list") + .auto_shrink([false; 2]) + .show(ui, |ui| { + ui.add_space(10.0); + // Show mode and type setup. + self.mode_type_ui(ui); + + ui.add_space(12.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(12.0); + + // Show words setup. + self.words_ui(ui); + }); } - pub fn get_mnemonic_mode(&self) -> &PhraseMode { - &self.mnemonic.mode + /// Draw mode and size setup. + fn mode_type_ui(&mut self, ui: &mut egui::Ui) { + // Show mode setup. + let mut mode = self.mnemonic.mode.clone(); + ui.columns(2, |columns| { + columns[0].vertical_centered(|ui| { + let create_mode = PhraseMode::Generate; + let create_text = t!("wallets.create"); + View::radio_value(ui, &mut mode, create_mode, create_text); + }); + columns[1].vertical_centered(|ui| { + let import_mode = PhraseMode::Import; + let import_text = t!("wallets.recover"); + View::radio_value(ui, &mut mode, import_mode, import_text); + }); + }); + if mode != self.mnemonic.mode { + self.mnemonic.set_mode(mode) + } + + ui.add_space(10.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("wallets.words_count")) + .size(16.0) + .color(Colors::GRAY) + ); + }); + ui.add_space(6.0); + + // Show mnemonic phrase size setup. + let mut size = self.mnemonic.size.clone(); + ui.columns(5, |columns| { + columns[0].vertical_centered(|ui| { + let words12 = PhraseSize::Words12; + let text = words12.value().to_string(); + View::radio_value(ui, &mut size, words12, text); + }); + columns[1].vertical_centered(|ui| { + let words15 = PhraseSize::Words15; + let text = words15.value().to_string(); + View::radio_value(ui, &mut size, words15, text); + }); + columns[2].vertical_centered(|ui| { + let words18 = PhraseSize::Words18; + let text = words18.value().to_string(); + View::radio_value(ui, &mut size, words18, text); + }); + columns[3].vertical_centered(|ui| { + let words21 = PhraseSize::Words21; + let text = words21.value().to_string(); + View::radio_value(ui, &mut size, words21, text); + }); + columns[4].vertical_centered(|ui| { + let words24 = PhraseSize::Words24; + let text = words24.value().to_string(); + View::radio_value(ui, &mut size, words24, text); + }); + }); + if size != self.mnemonic.size { + self.mnemonic.set_size(size); + } + } + + /// Draw words setup based on selected [`PhraseMode`]. + fn words_ui(&mut self, ui: &mut egui::Ui) { + ui.vertical_centered(|ui| { + // Show word list based on setup mode. + match self.mnemonic.mode { + PhraseMode::Generate => self.word_list_generate_ui(ui), + PhraseMode::Import => self.word_list_import_ui(ui) + } + }); + } + + /// Draw word list for [`PhraseMode::Generate`] mode. + fn word_list_generate_ui(&mut self, ui: &mut egui::Ui) { + // Calculate rows count based on available ui width. + const PADDING: f32 = 24.0; + let w = ui.available_width(); + let min_panel_w = Root::SIDE_PANEL_MIN_WIDTH; + let double_min_panel_w = (min_panel_w * 2.0) - PADDING; + let cols = if w >= (min_panel_w * 1.5) - PADDING && w < double_min_panel_w { + 3 + } else if w >= double_min_panel_w { + 4 + } else { + 2 + }; + + // Show words amount. + let mut word_number = 0; + let _ = self.mnemonic.words.chunks(cols).map(|chunk| { + let size = chunk.len(); + word_number += 1; + if size > 1 { + ui.columns(cols, |columns| { + columns[0].horizontal(|ui| { + ui.add_space(PADDING); + Self::generated_word_ui(ui, word_number, chunk, 0); + }); + columns[1].horizontal(|ui| { + ui.add_space(PADDING); + word_number += 1; + Self::generated_word_ui(ui, word_number, chunk, 1); + }); + if size > 2 { + columns[2].horizontal(|ui| { + ui.add_space(PADDING); + word_number += 1; + Self::generated_word_ui(ui, word_number, chunk, 2); + }); + } + if size > 3 { + columns[3].horizontal(|ui| { + ui.add_space(PADDING); + word_number += 1; + Self::generated_word_ui(ui, word_number, chunk, 3); + }); + } + }); + } else { + ui.columns(cols, |columns| { + columns[0].horizontal(|ui| { + ui.add_space(PADDING); + Self::generated_word_ui(ui, word_number, chunk, 0); + }); + }); + ui.add_space(12.0); + } + ui.add_space(8.0); + }).collect::>(); + } + + /// Draw generated word at given index from provided chunk. + fn generated_word_ui(ui: &mut egui::Ui, + word_number: usize, + chunk: &[String], + index: usize) { + let word = chunk.get(index).unwrap(); + let text = format!("#{} {}", word_number, word); + ui.label(RichText::new(text).size(16.0).color(Colors::BLACK)); + } + + + /// Draw word list for [`PhraseMode::Import`] mode. + fn word_list_import_ui(&mut self, ui: &mut egui::Ui) { + } /// Reset mnemonic phrase to default values. diff --git a/src/gui/views/wallets/creation/mod.rs b/src/gui/views/wallets/creation/mod.rs index f9b7b2a..bc1b941 100644 --- a/src/gui/views/wallets/creation/mod.rs +++ b/src/gui/views/wallets/creation/mod.rs @@ -18,13 +18,7 @@ pub use mnemonic::MnemonicSetup; mod connection; pub use connection::ConnectionSetup; -mod content; -pub use content::WalletCreation; +mod creation; +pub use creation::WalletCreation; -/// Interface to provide moving between wallet creation steps. -pub trait StepControl { - /// Go to next wallet creation step. - fn next_step(&mut self); - /// Go to previous wallet creation Step. - fn prev_step(&mut self); -} \ No newline at end of file +pub mod types; \ No newline at end of file diff --git a/src/gui/views/wallets/creation/types.rs b/src/gui/views/wallets/creation/types.rs new file mode 100644 index 0000000..94bac21 --- /dev/null +++ b/src/gui/views/wallets/creation/types.rs @@ -0,0 +1,115 @@ +// 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 grin_keychain::mnemonic::from_entropy; +use rand::{Rng, thread_rng}; + +/// Wallet creation step. +#[derive(PartialEq)] +pub enum Step { + /// Mnemonic phrase input. + EnterMnemonic, + /// Mnemonic phrase confirmation for [`Mnemonic`]. + ConfirmMnemonic, + /// Wallet connection setup. + SetupConnection +} + +/// Mnemonic phrase setup mode. +#[derive(PartialEq, Clone)] +pub enum PhraseMode { + /// Generate new mnemonic phrase. + Generate, + /// Import existing mnemonic phrase. + Import +} + +/// Mnemonic phrase size based on words count. +#[derive(PartialEq, Clone)] +pub enum PhraseSize { Words12, Words15, Words18, Words21, Words24 } + +impl PhraseSize { + /// 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. +pub struct Mnemonic { + /// Phrase setup mode. + pub(crate) mode: PhraseMode, + /// Size of phrase based on words count. + pub(crate) size: PhraseSize, + /// Words for phrase. + pub(crate) words: Vec +} + +impl Default for Mnemonic { + fn default() -> Self { + let size = PhraseSize::Words24; + let mode = PhraseMode::Generate; + let words = Self::generate_words(&mode, &size); + Self { mode, size, 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); + } + + /// 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); + } + + /// Setup words based on provided [`PhraseMode`] and [`PhraseSize`]. + fn generate_words(mode: &PhraseMode, size: &PhraseSize) -> Vec { + match mode { + PhraseMode::Generate => { + let mut rng = thread_rng(); + let mut entropy: Vec = Vec::with_capacity(size.entropy_size()); + for _ in 0..size.entropy_size() { + entropy.push(rng.gen()); + } + from_entropy(&entropy).unwrap() + .split(" ") + .map(|s| s.to_string()) + .collect::>() + }, + PhraseMode::Import => Vec::with_capacity(size.value()) + } + } +} \ No newline at end of file diff --git a/src/gui/views/wallets/wallets.rs b/src/gui/views/wallets/wallets.rs index 3a34caf..447d9c0 100644 --- a/src/gui/views/wallets/wallets.rs +++ b/src/gui/views/wallets/wallets.rs @@ -113,18 +113,23 @@ impl Wallets { /// Draw title content. fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { // Setup title text. - let title_content = TitleType::Single(t!("wallets.title").to_uppercase()); + let title_text = if self.creation_content.can_go_back() { + t!("wallets.new") + } else { + t!("wallets.title") + }; + let title_content = TitleType::Single(title_text.to_uppercase()); // Draw title panel. TitlePanel::ui(title_content, |ui, frame| { - if !Root::is_dual_panel_mode(frame) { + if self.creation_content.can_go_back() { + View::title_button(ui, ARROW_LEFT, || { + self.creation_content.back(); + }); + } else if !Root::is_dual_panel_mode(frame) { View::title_button(ui, GLOBE, || { Root::toggle_network_panel(); }); - } else if self.creation_content.can_go_back() { - View::title_button(ui, ARROW_LEFT, || { - self.creation_content.go_back(); - }); }; }, |ui, frame| { View::title_button(ui, GEAR, || { @@ -192,7 +197,7 @@ impl Wallets { pub fn on_back(&mut self) -> bool { let can_go_back = self.creation_content.can_go_back(); if can_go_back { - self.creation_content.go_back(); + self.creation_content.back(); } !can_go_back } diff --git a/src/lib.rs b/src/lib.rs index 1c28c58..5735930 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,11 @@ use crate::node::Node; i18n!("locales"); +// Include build information. +pub mod built_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} + mod node; mod wallet; pub mod gui;