ui: modal show method refactoring, mnemonic word input, update translations

This commit is contained in:
ardocrat 2023-07-23 11:48:28 +03:00
parent 952796abff
commit 2f5011c36f
14 changed files with 376 additions and 226 deletions

View file

@ -4,7 +4,6 @@ continue: Continue
complete: Complete
wallets:
title: Wallets
new: New wallet
create_desc: Create or import existing wallet from saved recovery phrase.
add: Add wallet
name: 'Name:'
@ -13,13 +12,12 @@ wallets:
pass_empty: Enter password for wallet
create: Create
recover: Restore
saved_phrase: Saved phrase
words_count: 'Words count:'
word_number: 'Word #%{number}'
word_empty: 'Enter word #%{number} from your recovery phrase'
enter_word: 'Enter word #%{number}:'
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

View file

@ -4,7 +4,6 @@ continue: Продолжить
complete: Завершить
wallets:
title: Кошельки
new: Новый кошелёк
create_desc: Создайте или импортируйте существующий кошелёк из сохранённой фразы восстановления.
add: Добавить кошелёк
name: 'Название:'
@ -13,13 +12,12 @@ wallets:
pass_empty: Введите пароль для кошелька
create: Создать
recover: Восстановить
saved_phrase: Сохранённая фраза
words_count: 'Количество слов:'
word_number: 'Слово #%{number}'
word_empty: 'Введите слово #%{number} из вашей фразы восстановления'
enter_word: 'Введите слово #%{number}:'
not_valid_word: Введено недопустимое слово
create_phrase_desc: Безопасно запишите и сохраните свою фразу восстановления.
create_phrase_desc: Безопасно запишите и сохраните вашу фразу восстановления.
restore_phrase_desc: Введите слова из вашей сохранённой фразы восстановления.
conf_phrase_desc: Выберите слова в таком же порядке, как они отображены в вашей фразе восстановления.
setup_conn_desc: Выберите способ подключения кошелька
network:
self: Сеть

View file

@ -114,16 +114,16 @@ impl Modal {
self.closeable.load(Ordering::Relaxed)
}
/// Set title text on Modal creation.
/// Set title text on [`Modal`] creation.
pub fn title(mut self, title: String) -> Self {
self.title = Some(title.to_uppercase());
self
}
/// Set [`Modal`] instance to show at ui.
pub fn show(modal: Modal) {
/// Set [`Modal`] instance into state to show at ui.
pub fn show(self) {
let mut w_nav = MODAL_STATE.write().unwrap();
w_nav.modal = Some(modal);
w_nav.modal = Some(self);
}
/// Remove [`Modal`] from [`MODAL_STATE`] if it's showing and can be closed.

View file

@ -134,10 +134,10 @@ impl NetworkSettings {
t!("network_settings.reset_settings"));
View::button(ui, button_text, Colors::GOLD, || {
// Show modal to confirm settings reset.
let reset_modal = Modal::new(Self::RESET_SETTINGS_MODAL)
Modal::new(Self::RESET_SETTINGS_MODAL)
.position(ModalPosition::Center)
.title(t!("modal.confirmation"));
Modal::show(reset_modal);
.title(t!("modal.confirmation"))
.show();
});
// Show reminder to restart enabled node.
@ -200,10 +200,10 @@ impl NetworkSettings {
pub fn show_node_restart_required_modal() {
if Node::is_running() {
// Show modal to apply changes by node restart.
let port_modal = Modal::new(Self::NODE_RESTART_REQUIRED_MODAL)
Modal::new(Self::NODE_RESTART_REQUIRED_MODAL)
.position(ModalPosition::Center)
.title(t!("network.settings"));
Modal::show(port_modal);
.title(t!("network.settings"))
.show();
}
}

View file

@ -113,10 +113,10 @@ impl DandelionSetup {
// Setup values for modal.
self.epoch_edit = epoch;
// Show epoch setup modal.
let epoch_modal = Modal::new(Self::EPOCH_MODAL)
Modal::new(Self::EPOCH_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(epoch_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -198,11 +198,10 @@ impl DandelionSetup {
// Setup values for modal.
self.embargo_edit = embargo;
// Show embargo setup modal.
let embargo_modal = Modal::new(Self::EMBARGO_MODAL)
Modal::new(Self::EMBARGO_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(embargo_modal);
cb.show_keyboard();
.title(t!("network_settings.change_value"))
.show();
});
ui.add_space(6.0);
}
@ -283,10 +282,10 @@ impl DandelionSetup {
// Setup values for modal.
self.aggregation_edit = agg;
// Show aggregation setup modal.
let aggregation_modal = Modal::new(Self::AGGREGATION_MODAL)
Modal::new(Self::AGGREGATION_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(aggregation_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -368,10 +367,10 @@ impl DandelionSetup {
// Setup values for modal.
self.stem_prob_edit = stem_prob;
// Show stem probability setup modal.
let embargo_modal = Modal::new(Self::STEM_PROBABILITY_MODAL)
Modal::new(Self::STEM_PROBABILITY_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(embargo_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);

View file

@ -221,10 +221,10 @@ impl NodeSetup {
self.api_port_available_edit = self.is_api_port_available;
// Show API port modal.
let port_modal = Modal::new(Self::API_PORT_MODAL)
Modal::new(Self::API_PORT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(port_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -335,10 +335,10 @@ impl NodeSetup {
// Setup values for modal.
self.secret_edit = secret_value.unwrap_or("".to_string());
// Show secret edit modal.
let port_modal = Modal::new(modal_id)
Modal::new(modal_id)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(port_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
}
@ -455,11 +455,11 @@ impl NodeSetup {
View::button(ui, format!("{} {}", CLOCK_CLOCKWISE, ftl.clone()), Colors::BUTTON, || {
// Setup values for modal.
self.ftl_edit = ftl;
// Show stratum port modal.
let ftl_modal = Modal::new(Self::FTL_MODAL)
// Show ftl value setup modal.
Modal::new(Self::FTL_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(ftl_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -478,7 +478,7 @@ impl NodeSetup {
.color(Colors::GRAY));
ui.add_space(8.0);
// Draw stratum port text edit.
// Draw ftl value text edit.
let text_edit_resp = egui::TextEdit::singleline(&mut self.ftl_edit)
.id(Id::from(modal.id))
.font(TextStyle::Heading)

View file

@ -212,15 +212,15 @@ impl P2PSetup {
self.port_edit = port;
self.port_available_edit = self.is_port_available;
// Show p2p port modal.
let port_modal = Modal::new(Self::PORT_MODAL)
Modal::new(Self::PORT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(port_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
// Show error when stratum server port is unavailable.
// Show error when p2p port is unavailable.
if !self.is_port_available {
ui.add_space(6.0);
ui.label(RichText::new(t!("network_settings.port_unavailable"))
@ -371,10 +371,10 @@ impl P2PSetup {
_ => Self::DNS_SEEDS_TITLE.to_string()
};
// Show modal to add peer.
let peer_modal = Modal::new(modal_id)
Modal::new(modal_id)
.position(ModalPosition::CenterTop)
.title(modal_title);
Modal::show(peer_modal);
.title(modal_title)
.show();
cb.show_keyboard();
});
}
@ -580,10 +580,10 @@ impl P2PSetup {
// Setup values for modal.
self.ban_window_edit = ban_window;
// Show ban window period setup modal.
let ban_modal = Modal::new(Self::BAN_WINDOW_MODAL)
Modal::new(Self::BAN_WINDOW_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(ban_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -671,10 +671,10 @@ impl P2PSetup {
// Setup values for modal.
self.max_inbound_count = max_inbound;
// Show maximum number of inbound peers setup modal.
let max_inbound_modal = Modal::new(Self::MAX_INBOUND_MODAL)
Modal::new(Self::MAX_INBOUND_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(max_inbound_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -757,10 +757,10 @@ impl P2PSetup {
// Setup values for modal.
self.max_outbound_count = max_outbound;
// Show maximum number of outbound peers setup modal.
let max_outbound = Modal::new(Self::MAX_OUTBOUND_MODAL)
Modal::new(Self::MAX_OUTBOUND_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(max_outbound);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -843,10 +843,10 @@ impl P2PSetup {
// Setup values for modal.
self.min_outbound_count = min_outbound;
// Show maximum number of outbound peers setup modal.
let min_outbound = Modal::new(Self::MIN_OUTBOUND_MODAL)
Modal::new(Self::MIN_OUTBOUND_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(min_outbound);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);

View file

@ -115,10 +115,10 @@ impl PoolSetup {
// Setup values for modal.
self.fee_base_edit = fee;
// Show fee setup modal.
let fee_modal = Modal::new(Self::FEE_BASE_MODAL)
Modal::new(Self::FEE_BASE_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(fee_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -200,10 +200,10 @@ impl PoolSetup {
// Setup values for modal.
self.reorg_period_edit = period;
// Show reorg period setup modal.
let reorg_modal = Modal::new(Self::REORG_PERIOD_MODAL)
Modal::new(Self::REORG_PERIOD_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(reorg_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -285,10 +285,10 @@ impl PoolSetup {
// Setup values for modal.
self.pool_size_edit = size;
// Show pool size setup modal.
let size_modal = Modal::new(Self::POOL_SIZE_MODAL)
Modal::new(Self::POOL_SIZE_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(size_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -370,10 +370,10 @@ impl PoolSetup {
// Setup values for modal.
self.stempool_size_edit = size;
// Show stempool size setup modal.
let stem_modal = Modal::new(Self::STEMPOOL_SIZE_MODAL)
Modal::new(Self::STEMPOOL_SIZE_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(stem_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -455,10 +455,10 @@ impl PoolSetup {
// Setup values for modal.
self.max_weight_edit = weight;
// Show total tx weight setup modal.
let weight_modal = Modal::new(Self::MAX_WEIGHT_MODAL)
Modal::new(Self::MAX_WEIGHT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(weight_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);

View file

@ -163,10 +163,10 @@ impl StratumSetup {
self.stratum_port_edit = port;
self.stratum_port_available_edit = self.is_port_available;
// Show stratum port modal.
let port_modal = Modal::new(Self::STRATUM_PORT_MODAL)
Modal::new(Self::STRATUM_PORT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(port_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(12.0);
@ -270,10 +270,10 @@ impl StratumSetup {
self.attempt_time_edit = time;
// Show attempt time modal.
let time_modal = Modal::new(Self::ATTEMPT_TIME_MODAL)
Modal::new(Self::ATTEMPT_TIME_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(time_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -293,7 +293,7 @@ impl StratumSetup {
.color(Colors::GRAY));
ui.add_space(8.0);
// Draw stratum port text edit.
// Draw attempt time text edit.
let text_edit_resp = egui::TextEdit::singleline(&mut self.attempt_time_edit)
.id(Id::from(modal.id))
.font(TextStyle::Heading)
@ -360,11 +360,11 @@ impl StratumSetup {
// Setup values for modal.
self.min_share_diff_edit = diff;
// Show attempt time modal.
let diff_modal = Modal::new(Self::MIN_SHARE_DIFF_MODAL)
// Show share difficulty setup modal.
Modal::new(Self::MIN_SHARE_DIFF_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_settings.change_value"));
Modal::show(diff_modal);
.title(t!("network_settings.change_value"))
.show();
cb.show_keyboard();
});
ui.add_space(6.0);
@ -379,7 +379,7 @@ impl StratumSetup {
.color(Colors::GRAY));
ui.add_space(8.0);
// Draw stratum port text edit.
// Draw share difficulty text edit.
let text_edit_resp = egui::TextEdit::singleline(&mut self.min_share_diff_edit)
.id(Id::from(modal.id))
.font(TextStyle::Heading)

View file

@ -142,8 +142,9 @@ impl Root {
/// Show exit confirmation modal.
pub fn show_exit_modal() {
let exit_modal = Modal::new(Self::EXIT_MODAL_ID).title(t!("modal.confirmation"));
Modal::show(exit_modal);
Modal::new(Self::EXIT_MODAL_ID)
.title(t!("modal.confirmation"))
.show();
}
/// Draw exit confirmation modal content.

View file

@ -66,11 +66,11 @@ impl Default for WalletCreation {
impl WalletCreation {
/// Wallet name/password input modal identifier.
pub const MODAL_ID: &'static str = "create_wallet_modal";
pub const NAME_PASS_MODAL: &'static str = "name_pass_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() {
// Show wallet creation step description and confirmation bottom panel.
if self.step.is_some() {
egui::TopBottomPanel::bottom("wallet_creation_step_panel")
.frame(egui::Frame {
stroke: View::DEFAULT_STROKE,
@ -85,36 +85,55 @@ impl WalletCreation {
.show_inside(ui, |ui| {
ui.vertical_centered(|ui| {
if let Some(step) = &self.step {
// Setup step description text.
let step_text = match step {
// Setup step description text and availability.
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();
if mode == &PhraseMode::Generate {
let text = if mode == &PhraseMode::Generate {
t!("wallets.create_phrase_desc", "number" => size_value)
} else {
t!("wallets.restore_phrase_desc", "number" => size_value)
}
};
let available = !self
.mnemonic_setup
.mnemonic
.words
.contains(&"".to_string());
(text, available)
}
Step::ConfirmMnemonic => t!("wallets.conf_phrase_desc"),
Step::SetupConnection => t!("wallets.setup_conn_desc")
Step::ConfirmMnemonic => {
let text = t!("wallets.restore_phrase_desc");
let available = !self
.mnemonic_setup
.mnemonic
.confirm_words
.contains(&"".to_string());
(text, available)
},
Step::SetupConnection => (t!("wallets.setup_conn_desc"), true)
};
// 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 next step button if there are no empty words.
if step_available {
// Setup 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)
};
ui.add_space(4.0);
// Show button.
View::button(ui, next_text.to_uppercase(), color, || {
self.forward();
});
ui.add_space(4.0);
}
}
});
});
@ -165,16 +184,18 @@ impl WalletCreation {
ui.add_space(8.0);
let add_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add"));
View::button(ui, add_text, Colors::BUTTON, || {
Self::show_modal();
self.show_name_pass_modal();
});
});
}
Some(step) => {
match step {
Step::EnterMnemonic => {
self.mnemonic_setup.enter_ui(ui, cb);
self.mnemonic_setup.ui(ui);
}
Step::ConfirmMnemonic => {
self.mnemonic_setup.confirm_ui(ui);
}
Step::ConfirmMnemonic => {}
Step::SetupConnection => {}
}
}
@ -230,10 +251,17 @@ impl WalletCreation {
}
/// Start wallet creation from showing [`Modal`] to enter name and password.
pub fn show_modal() {
Modal::show(Modal::new(Self::MODAL_ID)
pub fn show_name_pass_modal(&mut self) {
// Reset modal values.
self.hide_pass = false;
self.modal_just_opened = true;
self.name_edit = "".to_string();
self.pass_edit = "".to_string();
// Show modal.
Modal::new(Self::NAME_PASS_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.add")));
.title(t!("wallets.add"))
.show();
}
/// Draw wallet creation [`Modal`] content.
@ -332,11 +360,6 @@ impl WalletCreation {
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
// Clear values.
self.hide_pass = false;
self.modal_just_opened = true;
self.name_edit = "".to_string();
self.pass_edit = "".to_string();
// Close modal.
cb.hide_keyboard();
modal.close();

View file

@ -12,35 +12,46 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::{RichText, ScrollArea};
use egui::{Id, RichText, ScrollArea, TextStyle, Widget};
use crate::gui::Colors;
use crate::gui::icons::PENCIL;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Root, View};
use crate::gui::views::{Modal, ModalPosition, Root, View};
use crate::gui::views::wallets::creation::types::{Mnemonic, PhraseMode, PhraseSize};
/// Mnemonic phrase setup content.
pub struct MnemonicSetup {
/// Current mnemonic phrase.
pub(crate) mnemonic: Mnemonic,
/// Word value for [`Modal`].
/// Current word number to edit at [`Modal`].
word_num_edit: usize,
/// Entered word value for [`Modal`].
word_edit: String,
/// Flag to check if entered word is valid.
valid_word_edit: bool
}
impl Default for MnemonicSetup {
fn default() -> Self {
Self {
mnemonic: Mnemonic::default(),
word_num_edit: 0,
word_edit: "".to_string(),
valid_word_edit: true
}
}
}
impl MnemonicSetup {
pub fn enter_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
/// Identifier for word input [`Modal`].
pub const WORD_INPUT_MODAL: &'static str = "word_input_modal";
/// Draw content for input step.
pub fn ui(&mut self, ui: &mut egui::Ui) {
ScrollArea::vertical()
.id_source("mnemonic_words_list")
.id_source("input_mnemonic_words_list")
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.add_space(10.0);
@ -53,7 +64,23 @@ impl MnemonicSetup {
ui.add_space(6.0);
// Show words setup.
self.word_list_ui(ui);
self.word_list_ui(ui, self.mnemonic.mode == PhraseMode::Import);
});
}
/// Draw content for confirmation step.
pub fn confirm_ui(&mut self, ui: &mut egui::Ui) {
ui.add_space(4.0);
ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("wallets.saved_phrase")).size(16.0).color(Colors::GRAY));
});
ui.add_space(6.0);
ScrollArea::vertical()
.id_source("confirm_mnemonic_words_list")
.auto_shrink([false; 2])
.show(ui, |ui| {
// Show words setup.
self.word_list_ui(ui, true);
});
}
@ -89,125 +116,103 @@ impl MnemonicSetup {
// 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);
});
for (index, word) in PhraseSize::VALUES.iter().enumerate() {
columns[index].vertical_centered(|ui| {
let text = word.value().to_string();
View::radio_value(ui, &mut size, word.clone(), text);
});
}
});
if size != self.mnemonic.size {
self.mnemonic.set_size(size);
}
}
/// Calculate word list columns count based on available ui width.
fn calc_columns_count(ui: &mut egui::Ui) -> usize {
let w = ui.available_width();
let min_panel_w = Root::SIDE_PANEL_MIN_WIDTH - 12.0;
let double_min_panel_w = min_panel_w * 2.0;
if w >= min_panel_w * 1.5 && w < double_min_panel_w {
3
} else if w >= double_min_panel_w {
4
} else {
2
}
}
/// Draw word list for mnemonic phrase.
fn word_list_ui(&self, ui: &mut egui::Ui) {
/// Draw list of words for mnemonic phrase.
fn word_list_ui(&mut self, ui: &mut egui::Ui, edit_words: bool) {
ui.add_space(6.0);
ui.scope(|ui| {
// Setup spacing between columns.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 6.0);
if self.mnemonic.mode == PhraseMode::Generate {
ui.add_space(6.0)
}
// Select list of words based on current mode and edit flag.
let words = match self.mnemonic.mode {
PhraseMode::Generate => {
if edit_words {
self.mnemonic.confirm_words.clone()
} else {
self.mnemonic.words.clone()
}
}
PhraseMode::Import => self.mnemonic.words.clone()
};
let mut word_number = 0;
let cols = Self::calc_columns_count(ui);
let _ = self.mnemonic.words.chunks(cols).map(|chunk| {
let cols = list_columns_count(ui);
let _ = words.chunks(cols).map(|chunk| {
let size = chunk.len();
word_number += 1;
if size > 1 {
ui.columns(cols, |columns| {
columns[0].horizontal(|ui| {
self.word_item_ui(ui, word_number, chunk, 0);
let word = chunk.get(0).unwrap();
self.word_item_ui(ui, word_number, word, edit_words);
});
columns[1].horizontal(|ui| {
word_number += 1;
self.word_item_ui(ui, word_number, chunk, 1);
let word = chunk.get(1).unwrap();
self.word_item_ui(ui, word_number, word, edit_words);
});
if size > 2 {
columns[2].horizontal(|ui| {
word_number += 1;
self.word_item_ui(ui, word_number, chunk, 2);
let word = chunk.get(2).unwrap();
self.word_item_ui(ui, word_number, word, edit_words);
});
}
if size > 3 {
columns[3].horizontal(|ui| {
word_number += 1;
self.word_item_ui(ui, word_number, chunk, 3);
let word = chunk.get(3).unwrap();
self.word_item_ui(ui, word_number, word, edit_words);
});
}
});
} else {
ui.columns(cols, |columns| {
columns[0].horizontal(|ui| {
self.word_item_ui(ui, word_number, chunk, 0);
let word = chunk.get(0).unwrap();
self.word_item_ui(ui, word_number, word, edit_words);
});
});
ui.add_space(12.0);
}
}).collect::<Vec<_>>();
});
ui.add_space(6.0);
}
/// Draw word item at given index from provided chunk.
fn word_item_ui(&self,
ui: &mut egui::Ui,
word_number: usize,
chunk: &[String],
index: usize) {
match self.mnemonic.mode {
PhraseMode::Generate => {
ui.add_space(12.0);
let word = chunk.get(index).unwrap();
let text = format!("#{} {}", word_number, word);
ui.label(RichText::new(text).size(17.0).color(Colors::BLACK));
}
PhraseMode::Import => {
let mut size = ui.available_size();
size.x = 90.0;
ui.allocate_ui(size, |ui| {
View::button(ui, PENCIL.to_string(), Colors::BUTTON, || {
//TODO: open modal
});
});
ui.label(RichText::new(format!("#{}", word_number))
.size(17.0)
.color(Colors::BLACK));
}
/// Draw word list item for current mode.
fn word_item_ui(&mut self, ui: &mut egui::Ui, word_number: 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_edit = word.clone();
self.valid_word_edit = true;
// Show word edit modal.
Modal::new(MnemonicSetup::WORD_INPUT_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.saved_phrase"))
.show();
});
ui.label(RichText::new(format!("#{} {}", word_number, word))
.size(17.0)
.color(Colors::BLACK));
} else {
ui.add_space(12.0);
let text = format!("#{} {}", word_number, word);
ui.label(RichText::new(text).size(17.0).color(Colors::BLACK));
}
}
@ -215,4 +220,95 @@ impl MnemonicSetup {
pub fn reset(&mut self) {
self.mnemonic = Mnemonic::default();
}
/// Show word input [`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| {
ui.label(RichText::new(t!("wallets.enter_word", "number" => self.word_num_edit))
.size(17.0)
.color(Colors::GRAY));
ui.add_space(8.0);
// Draw word value text edit.
let text_edit_resp = egui::TextEdit::singleline(&mut self.word_edit)
.id(Id::from(format!("{}{}", modal.id, self.word_num_edit)))
.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 word is not valid.
if !self.valid_word_edit {
ui.add_space(12.0);
ui.label(RichText::new(t!("wallets.not_valid_word"))
.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);
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!("continue"), Colors::WHITE, || {
// Check if word is valid.
let word_index = self.word_num_edit - 1;
if !self.mnemonic.is_valid_word(&self.word_edit, word_index) {
self.valid_word_edit = false;
return;
}
// 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();
if close_modal {
cb.hide_keyboard();
modal.close();
} else {
self.word_num_edit += 1;
self.word_edit = "".to_string();
}
});
});
});
ui.add_space(6.0);
});
}
}
/// Calculate word list columns count based on available ui width.
fn list_columns_count(ui: &mut egui::Ui) -> usize {
let w = ui.available_width();
let min_panel_w = Root::SIDE_PANEL_MIN_WIDTH - 12.0;
let double_min_panel_w = min_panel_w * 2.0;
if w >= min_panel_w * 1.5 && w < double_min_panel_w {
3
} else if w >= double_min_panel_w {
4
} else {
2
}
}

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use grin_keychain::mnemonic::from_entropy;
use grin_keychain::mnemonic::{from_entropy, search};
use rand::{Rng, thread_rng};
/// Wallet creation step.
@ -20,7 +20,7 @@ use rand::{Rng, thread_rng};
pub enum Step {
/// Mnemonic phrase input.
EnterMnemonic,
/// Mnemonic phrase confirmation for [`Mnemonic`].
/// Mnemonic phrase confirmation.
ConfirmMnemonic,
/// Wallet connection setup.
SetupConnection
@ -40,6 +40,14 @@ pub enum PhraseMode {
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 {
@ -69,8 +77,10 @@ pub struct Mnemonic {
pub(crate) mode: PhraseMode,
/// Size of phrase based on words count.
pub(crate) size: PhraseSize,
/// Words for phrase.
pub(crate) words: Vec<String>
/// Generated words.
pub(crate) words: Vec<String>,
/// Words to confirm the phrase.
pub(crate) confirm_words: Vec<String>
}
impl Default for Mnemonic {
@ -78,7 +88,8 @@ impl Default for Mnemonic {
let size = PhraseSize::Words24;
let mode = PhraseMode::Generate;
let words = Self::generate_words(&mode, &size);
Self { mode, size, words }
let confirm_words = Self::empty_words(&size);
Self { mode, size, words, confirm_words }
}
}
@ -87,15 +98,28 @@ impl Mnemonic {
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);
}
/// Setup words based on provided [`PhraseMode`] and [`PhraseSize`].
/// 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
}
/// Generate list of words based on provided [`PhraseMode`] and [`PhraseSize`].
fn generate_words(mode: &PhraseMode, size: &PhraseSize) -> Vec<String> {
match mode {
PhraseMode::Generate => {
@ -110,12 +134,17 @@ impl Mnemonic {
.collect::<Vec<String>>()
},
PhraseMode::Import => {
let mut words = Vec::with_capacity(size.value());
for _ in 0..size.value() {
words.push("".to_string())
}
words
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("".to_string())
}
words
}
}

View file

@ -20,7 +20,7 @@ use crate::gui::Colors;
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::WalletCreation;
use crate::gui::views::wallets::creation::{MnemonicSetup, WalletCreation};
use crate::gui::views::wallets::wallet::WalletContent;
/// Wallets content.
@ -45,7 +45,8 @@ impl Default for Wallets {
item_content: None,
creation_content: WalletCreation::default(),
modal_ids: vec![
WalletCreation::MODAL_ID
WalletCreation::NAME_PASS_MODAL,
MnemonicSetup::WORD_INPUT_MODAL
]
}
}
@ -63,7 +64,12 @@ impl Wallets {
if self.can_draw_modal() {
Modal::ui(ui, |ui, modal| {
match modal.id {
WalletCreation::MODAL_ID => self.creation_content.modal_ui(ui, modal, cb),
WalletCreation::NAME_PASS_MODAL => {
self.creation_content.modal_ui(ui, modal, cb);
},
MnemonicSetup::WORD_INPUT_MODAL => {
self.creation_content.mnemonic_setup.modal_ui(ui, modal, cb);
}
_ => {}
}
});
@ -72,9 +78,9 @@ impl Wallets {
// Show title panel.
self.title_ui(ui, frame);
// Show wallet content.
let is_wallet_panel_open = Self::is_dual_panel_mode(ui, frame) || self.list.is_empty();
let wallet_panel_width = self.wallet_panel_width(ui, frame);
// Show wallet content.
egui::SidePanel::right("wallet_panel")
.resizable(false)
.min_width(wallet_panel_width)
@ -105,7 +111,7 @@ impl Wallets {
});
// Show wallet creation button if wallet panel is not open.
if !is_wallet_panel_open {
self.add_wallet_btn_ui(ui);
self.create_wallet_btn_ui(ui);
}
}
}
@ -114,7 +120,7 @@ impl Wallets {
fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
// Setup title text.
let title_text = if self.creation_content.can_go_back() {
t!("wallets.new")
t!("wallets.add")
} else {
t!("wallets.title")
};
@ -178,7 +184,7 @@ impl Wallets {
}
/// Draw floating button to create the wallet.
fn add_wallet_btn_ui(&self, ui: &mut egui::Ui) {
fn create_wallet_btn_ui(&mut self, ui: &mut egui::Ui) {
egui::Window::new("create_wallet_button")
.title_bar(false)
.resizable(false)
@ -187,7 +193,7 @@ impl Wallets {
.frame(egui::Frame::default())
.show(ui.ctx(), |ui| {
View::round_button(ui, PLUS, || {
WalletCreation::show_modal();
self.creation_content.show_name_pass_modal();
});
});
}