mirror of
https://code.gri.mw/GUI/grim.git
synced 2026-07-18 12:48:55 +00:00
387 lines
15 KiB
Rust
387 lines
15 KiB
Rust
// 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, Margin, RichText, ScrollArea};
|
|
use egui::scroll_area::ScrollBarVisibility;
|
|
use grin_util::ZeroingString;
|
|
|
|
use crate::gui::Colors;
|
|
use crate::gui::icons::{CHECK, CLIPBOARD_TEXT, COPY, SCAN};
|
|
use crate::gui::platform::PlatformCallbacks;
|
|
use crate::gui::views::{Modal, Content, View, CameraScanModal};
|
|
use crate::gui::views::types::{ModalContainer, ModalPosition, QrScanResult};
|
|
use crate::gui::views::wallets::creation::MnemonicSetup;
|
|
use crate::gui::views::wallets::creation::types::Step;
|
|
use crate::gui::views::wallets::ConnectionSettings;
|
|
use crate::node::Node;
|
|
use crate::wallet::{ExternalConnection, Wallet};
|
|
use crate::wallet::types::PhraseMode;
|
|
|
|
/// Wallet creation content.
|
|
pub struct WalletCreation {
|
|
/// Wallet name.
|
|
pub name: String,
|
|
/// Wallet password.
|
|
pub pass: ZeroingString,
|
|
|
|
/// Wallet creation step.
|
|
step: Step,
|
|
|
|
/// QR code scanning [`Modal`] content.
|
|
scan_modal_content: Option<CameraScanModal>,
|
|
|
|
/// Mnemonic phrase setup content.
|
|
mnemonic_setup: MnemonicSetup,
|
|
/// Network setup content.
|
|
network_setup: ConnectionSettings,
|
|
|
|
/// Flag to check if an error occurred during wallet creation.
|
|
creation_error: Option<String>,
|
|
|
|
/// [`Modal`] identifiers allowed at this ui container.
|
|
modal_ids: Vec<&'static str>
|
|
}
|
|
|
|
const QR_CODE_PHRASE_SCAN_MODAL: &'static str = "qr_code_rec_phrase_scan_modal";
|
|
|
|
impl ModalContainer for WalletCreation {
|
|
fn modal_ids(&self) -> &Vec<&'static str> {
|
|
&self.modal_ids
|
|
}
|
|
|
|
fn modal_ui(&mut self,
|
|
ui: &mut egui::Ui,
|
|
modal: &Modal,
|
|
cb: &dyn PlatformCallbacks) {
|
|
match modal.id {
|
|
QR_CODE_PHRASE_SCAN_MODAL => {
|
|
if let Some(content) = self.scan_modal_content.as_mut() {
|
|
content.ui(ui, modal, cb, |result| {
|
|
match result {
|
|
QrScanResult::Text(text) => {
|
|
self.mnemonic_setup.mnemonic.import(&text);
|
|
modal.close();
|
|
}
|
|
QrScanResult::SeedQR(text) => {
|
|
self.mnemonic_setup.mnemonic.import(&text);
|
|
modal.close();
|
|
}
|
|
_ => {}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl WalletCreation {
|
|
/// Create new wallet creation instance from name and password.
|
|
pub fn new(name: String, pass: ZeroingString) -> Self {
|
|
Self {
|
|
name,
|
|
pass,
|
|
step: Step::EnterMnemonic,
|
|
scan_modal_content: None,
|
|
mnemonic_setup: MnemonicSetup::default(),
|
|
network_setup: ConnectionSettings::default(),
|
|
creation_error: None,
|
|
modal_ids: vec![
|
|
QR_CODE_PHRASE_SCAN_MODAL
|
|
],
|
|
}
|
|
}
|
|
|
|
/// Draw wallet creation content.
|
|
pub fn ui(&mut self,
|
|
ui: &mut egui::Ui,
|
|
cb: &dyn PlatformCallbacks,
|
|
on_create: impl FnMut(Wallet)) {
|
|
self.current_modal_ui(ui, cb);
|
|
|
|
// Show wallet creation step description and confirmation panel.
|
|
egui::TopBottomPanel::bottom("wallet_creation_step_panel")
|
|
.frame(egui::Frame {
|
|
stroke: View::item_stroke(),
|
|
fill: Colors::fill_deep(),
|
|
inner_margin: Margin {
|
|
left: View::far_left_inset_margin(ui) + 8.0,
|
|
right: View::get_right_inset() + 8.0,
|
|
top: 4.0,
|
|
bottom: View::get_bottom_inset(),
|
|
},
|
|
..Default::default()
|
|
})
|
|
.show_inside(ui, |ui| {
|
|
ui.vertical_centered(|ui| {
|
|
ui.vertical_centered(|ui| {
|
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 2.0, |ui| {
|
|
self.step_control_ui(ui, on_create, cb);
|
|
});
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
// Show wallet creation step content panel.
|
|
egui::CentralPanel::default()
|
|
.frame(egui::Frame {
|
|
inner_margin: Margin {
|
|
left: View::far_left_inset_margin(ui) + 4.0,
|
|
right: View::get_right_inset() + 4.0,
|
|
top: 3.0,
|
|
bottom: 4.0,
|
|
},
|
|
..Default::default()
|
|
})
|
|
.show_inside(ui, |ui| {
|
|
ScrollArea::vertical()
|
|
.id_source(Id::from(format!("creation_step_scroll_{}", self.step.name())))
|
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
|
.auto_shrink([false; 2])
|
|
.show(ui, |ui| {
|
|
ui.vertical_centered(|ui| {
|
|
let max_width = if self.step == Step::SetupConnection {
|
|
Content::SIDE_PANEL_WIDTH * 1.3
|
|
} else {
|
|
Content::SIDE_PANEL_WIDTH * 2.0
|
|
};
|
|
View::max_width_ui(ui, max_width, |ui| {
|
|
self.step_content_ui(ui, cb);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/// Draw [`Step`] description and confirmation control.
|
|
fn step_control_ui(&mut self,
|
|
ui: &mut egui::Ui,
|
|
on_create: impl FnOnce(Wallet),
|
|
cb: &dyn PlatformCallbacks) {
|
|
let step = &self.step;
|
|
// Setup description and next step availability.
|
|
let (step_text, mut next) = match step {
|
|
Step::EnterMnemonic => {
|
|
let mode = &self.mnemonic_setup.mnemonic.mode();
|
|
let (text, available) = match mode {
|
|
PhraseMode::Generate => (t!("wallets.create_phrase_desc"), true),
|
|
PhraseMode::Import => {
|
|
let available = !self.mnemonic_setup.mnemonic.has_empty_or_invalid();
|
|
(t!("wallets.restore_phrase_desc"), available)
|
|
}
|
|
};
|
|
(text, available)
|
|
}
|
|
Step::ConfirmMnemonic => {
|
|
let text = t!("wallets.restore_phrase_desc");
|
|
let available = !self.mnemonic_setup.mnemonic.has_empty_or_invalid();
|
|
(text, available)
|
|
}
|
|
Step::SetupConnection => {
|
|
(t!("wallets.setup_conn_desc"), self.creation_error.is_none())
|
|
}
|
|
};
|
|
|
|
// Show step description or error.
|
|
let generate_step = step == &Step::EnterMnemonic &&
|
|
self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate;
|
|
if (self.mnemonic_setup.mnemonic.valid() && self.creation_error.is_none()) ||
|
|
generate_step {
|
|
ui.add_space(2.0);
|
|
ui.label(RichText::new(step_text).size(16.0).color(Colors::gray()));
|
|
ui.add_space(2.0);
|
|
} else {
|
|
next = false;
|
|
// Show error text.
|
|
if let Some(err) = &self.creation_error {
|
|
ui.add_space(10.0);
|
|
ui.label(RichText::new(err)
|
|
.size(16.0)
|
|
.color(Colors::red()));
|
|
ui.add_space(10.0);
|
|
} else {
|
|
ui.add_space(2.0);
|
|
ui.label(RichText::new(&t!("wallets.not_valid_phrase"))
|
|
.size(16.0)
|
|
.color(Colors::red()));
|
|
ui.add_space(2.0);
|
|
};
|
|
}
|
|
|
|
// Setup buttons.
|
|
match step {
|
|
Step::EnterMnemonic => {
|
|
ui.add_space(4.0);
|
|
|
|
// Setup spacing between buttons.
|
|
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
|
|
|
ui.columns(2, |columns| {
|
|
// Show copy or paste button for mnemonic phrase step.
|
|
columns[0].vertical_centered_justified(|ui| {
|
|
match self.mnemonic_setup.mnemonic.mode() {
|
|
PhraseMode::Generate => {
|
|
// Show copy button.
|
|
let c_t = format!("{} {}", COPY, t!("copy").to_uppercase());
|
|
View::button(ui,
|
|
c_t.to_uppercase(),
|
|
Colors::white_or_black(false), || {
|
|
cb.copy_string_to_buffer(self.mnemonic_setup
|
|
.mnemonic
|
|
.get_phrase());
|
|
});
|
|
}
|
|
PhraseMode::Import => {
|
|
// Show paste button.
|
|
let p_t = format!("{} {}",
|
|
CLIPBOARD_TEXT,
|
|
t!("paste").to_uppercase());
|
|
View::button(ui, p_t, Colors::white_or_black(false), || {
|
|
let data = ZeroingString::from(cb.get_string_from_buffer());
|
|
self.mnemonic_setup.mnemonic.import(&data);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
// Show next step or QR code scan button.
|
|
columns[1].vertical_centered_justified(|ui| {
|
|
if next {
|
|
self.next_step_button_ui(ui, on_create);
|
|
} else {
|
|
let scan_text = format!("{} {}", SCAN, t!("scan").to_uppercase());
|
|
View::button(ui, scan_text, Colors::white_or_black(false), || {
|
|
self.scan_modal_content = Some(CameraScanModal::default());
|
|
// Show QR code scan modal.
|
|
Modal::new(QR_CODE_PHRASE_SCAN_MODAL)
|
|
.position(ModalPosition::CenterTop)
|
|
.title(t!("scan_qr"))
|
|
.closeable(false)
|
|
.show();
|
|
cb.start_camera();
|
|
});
|
|
}
|
|
});
|
|
});
|
|
ui.add_space(4.0);
|
|
}
|
|
Step::ConfirmMnemonic => {
|
|
ui.add_space(4.0);
|
|
// Show next step or paste button.
|
|
if next {
|
|
self.next_step_button_ui(ui, on_create);
|
|
} else {
|
|
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase());
|
|
View::button(ui, paste_text, Colors::white_or_black(false), || {
|
|
let data = ZeroingString::from(cb.get_string_from_buffer());
|
|
self.mnemonic_setup.mnemonic.import(&data);
|
|
});
|
|
}
|
|
ui.add_space(4.0);
|
|
}
|
|
Step::SetupConnection => {
|
|
if next {
|
|
ui.add_space(4.0);
|
|
self.next_step_button_ui(ui, on_create);
|
|
ui.add_space(4.0);
|
|
}
|
|
}
|
|
}
|
|
ui.add_space(3.0);
|
|
}
|
|
|
|
/// Draw button to go to next [`Step`].
|
|
fn next_step_button_ui(&mut self,
|
|
ui: &mut egui::Ui,
|
|
on_create: impl FnOnce(Wallet)) {
|
|
// Setup button text.
|
|
let (next_text, text_color, bg_color) = if self.step == Step::SetupConnection {
|
|
(format!("{} {}", CHECK, t!("complete")), Colors::title(true), Colors::gold())
|
|
} else {
|
|
(t!("continue"), Colors::text_button(), Colors::white_or_black(false))
|
|
};
|
|
|
|
// Show next step button.
|
|
View::colored_text_button_ui(ui, next_text.to_uppercase(), text_color, bg_color, |ui| {
|
|
self.step = match self.step {
|
|
Step::EnterMnemonic => {
|
|
if self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate {
|
|
Step::ConfirmMnemonic
|
|
} else {
|
|
Step::SetupConnection
|
|
}
|
|
}
|
|
Step::ConfirmMnemonic => {
|
|
Step::SetupConnection
|
|
},
|
|
Step::SetupConnection => {
|
|
// Create wallet at last step.
|
|
match Wallet::create(&self.name,
|
|
&self.pass,
|
|
&self.mnemonic_setup.mnemonic,
|
|
&self.network_setup.method) {
|
|
Ok(w) => {
|
|
self.mnemonic_setup.reset();
|
|
// Pass created wallet to callback.
|
|
(on_create)(w);
|
|
Step::EnterMnemonic
|
|
}
|
|
Err(e) => {
|
|
self.creation_error = Some(format!("{:?}", e));
|
|
Step::SetupConnection
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Check external connections availability on connection setup.
|
|
if self.step == Step::SetupConnection {
|
|
ExternalConnection::check(None, ui.ctx());
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Draw wallet creation [`Step`] content.
|
|
fn step_content_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
|
match &self.step {
|
|
Step::EnterMnemonic => self.mnemonic_setup.ui(ui, cb),
|
|
Step::ConfirmMnemonic => self.mnemonic_setup.confirm_ui(ui, cb),
|
|
Step::SetupConnection => {
|
|
// Redraw if node is running.
|
|
if Node::is_running() && !Content::is_dual_panel_mode(ui) {
|
|
ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY);
|
|
}
|
|
self.network_setup.create_ui(ui, cb)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Back to previous wallet creation [`Step`], return `true` to close creation.
|
|
pub fn on_back(&mut self) -> bool {
|
|
match &self.step {
|
|
Step::ConfirmMnemonic => {
|
|
self.step = Step::EnterMnemonic;
|
|
false
|
|
},
|
|
Step::SetupConnection => {
|
|
self.creation_error = None;
|
|
self.step = Step::EnterMnemonic;
|
|
false
|
|
}
|
|
_ => true
|
|
}
|
|
}
|
|
} |