ui: title panel refactoring, initial wallet creation, update translations
This commit is contained in:
parent
83b8de5ad6
commit
8a60e31555
18 changed files with 890 additions and 145 deletions
|
@ -1,7 +1,23 @@
|
|||
copy: Copy
|
||||
paste: Paste
|
||||
accounts:
|
||||
title: Accounts
|
||||
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:'
|
||||
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.
|
||||
network:
|
||||
self: Network
|
||||
node: Integrated node
|
||||
|
|
|
@ -1,7 +1,23 @@
|
|||
copy: Копировать
|
||||
paste: Вставить
|
||||
accounts:
|
||||
title: Аккаунты
|
||||
continue: Продолжить
|
||||
complete: Завершить
|
||||
wallets:
|
||||
title: Кошельки
|
||||
new: Новый кошелёк
|
||||
create_desc: Создайте или импортируйте существующий кошелёк из сохранённой фразы восстановления.
|
||||
add: Добавить кошелёк
|
||||
name: 'Название:'
|
||||
pass: 'Пароль:'
|
||||
name_empty: Введите название кошелька
|
||||
pass_empty: Введите пароль для кошелька
|
||||
word_number: 'Слово #%{number}'
|
||||
word_empty: 'Введите слово #%{number} из фразы восстановления'
|
||||
not_valid_word: Введено недопустимое слово
|
||||
create: Создать
|
||||
import: Импортировать
|
||||
mnemonic_desc: 'Безопасно запишите и сохраните свою фразу восстановления из %{} слов ниже. Они понадобятся вам, чтобы восстановить доступ к вашим средствам, если вы потеряете устройство.'
|
||||
mnemonic_conf: Выберите слова в таком же порядке, как они отображены в вашей фразе восстановления.
|
||||
network:
|
||||
self: Сеть
|
||||
node: Встроенный узел
|
||||
|
|
|
@ -12,12 +12,20 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use egui::Context;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::Root;
|
||||
|
||||
lazy_static! {
|
||||
/// State to check if platform Back button was pressed.
|
||||
static ref BACK_BUTTON_PRESSED: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
|
||||
/// Implements ui entry point and contains platform-specific callbacks.
|
||||
pub struct PlatformApp<Platform> {
|
||||
/// Platform specific callbacks handler.
|
||||
|
@ -34,9 +42,13 @@ impl<Platform> PlatformApp<Platform> {
|
|||
|
||||
impl<Platform: PlatformCallbacks> eframe::App for PlatformApp<Platform> {
|
||||
fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) {
|
||||
// Handle Esc keyboard key event.
|
||||
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
Root::on_back();
|
||||
// Handle Esc keyboard key event and platform Back button key event.
|
||||
let back_button_pressed = back_button_pressed();
|
||||
if ctx.input(|i| i.key_pressed(egui::Key::Escape) || back_button_pressed) {
|
||||
if back_button_pressed {
|
||||
BACK_BUTTON_PRESSED.store(false, Ordering::Relaxed);
|
||||
}
|
||||
self.root.on_back();
|
||||
}
|
||||
|
||||
// Show main content.
|
||||
|
@ -56,6 +68,13 @@ impl<Platform: PlatformCallbacks> eframe::App for PlatformApp<Platform> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Check if platform Back button was pressed.
|
||||
fn back_button_pressed() -> bool {
|
||||
BACK_BUTTON_PRESSED.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(target_os = "android")]
|
||||
#[allow(non_snake_case)]
|
||||
|
@ -66,8 +85,5 @@ pub extern "C" fn Java_mw_gri_android_MainActivity_onBack(
|
|||
_class: jni::objects::JObject,
|
||||
_activity: jni::objects::JObject,
|
||||
) {
|
||||
Root::on_back();
|
||||
}
|
||||
|
||||
|
||||
|
||||
BACK_BUTTON_PRESSED.store(true, Ordering::Relaxed);
|
||||
}
|
|
@ -27,5 +27,5 @@ pub use root::*;
|
|||
mod network;
|
||||
pub use network::*;
|
||||
|
||||
mod accounts;
|
||||
pub use accounts::*;
|
||||
mod wallets;
|
||||
pub use wallets::*;
|
32
src/gui/views/network/connections.rs
Normal file
32
src/gui/views/network/connections.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
// 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 crate::gui::platform::PlatformCallbacks;
|
||||
|
||||
/// Network connections content.
|
||||
pub struct ConnectionsContent {
|
||||
|
||||
}
|
||||
|
||||
impl Default for ConnectionsContent {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectionsContent {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
||||
|
||||
}
|
||||
}
|
|
@ -28,4 +28,8 @@ mod setup;
|
|||
pub use setup::*;
|
||||
|
||||
mod network;
|
||||
pub use network::*;
|
||||
pub use network::*;
|
||||
|
||||
|
||||
mod connections;
|
||||
pub use connections::*;
|
|
@ -19,17 +19,18 @@ use crate::AppConfig;
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, POWER};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, ModalContainer, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings, Root, TitleAction, TitlePanel, TitleType, View};
|
||||
use crate::gui::views::{Modal, ModalContainer, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings, Root, TitlePanel, TitleType, View};
|
||||
use crate::gui::views::network::setup::{DandelionSetup, NodeSetup, P2PSetup, PoolSetup, StratumSetup};
|
||||
use crate::node::Node;
|
||||
|
||||
/// Network tab content interface.
|
||||
pub trait NetworkTab {
|
||||
fn get_type(&self) -> NetworkTabType;
|
||||
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks);
|
||||
fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks);
|
||||
}
|
||||
|
||||
|
||||
/// Type of [`NetworkTab`] content.
|
||||
#[derive(PartialEq)]
|
||||
pub enum NetworkTabType {
|
||||
Node,
|
||||
|
@ -108,15 +109,17 @@ impl ModalContainer for Network {
|
|||
|
||||
impl Network {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
||||
// Show modal content if it's opened.
|
||||
// Show modal content for current ui container.
|
||||
if self.can_draw_modal() {
|
||||
Modal::ui(ui, |ui, modal| {
|
||||
self.current_tab.as_mut().on_modal_ui(ui, modal, cb);
|
||||
});
|
||||
}
|
||||
|
||||
// Show title panel.
|
||||
self.title_ui(ui, frame);
|
||||
|
||||
// Show bottom tabs.
|
||||
egui::TopBottomPanel::bottom("network_tabs")
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::FILL,
|
||||
|
@ -127,6 +130,7 @@ impl Network {
|
|||
self.tabs_ui(ui);
|
||||
});
|
||||
|
||||
// Show tab content.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: View::DEFAULT_STROKE,
|
||||
|
@ -147,7 +151,7 @@ impl Network {
|
|||
/// Calculate tabs inner margin based on display insets (cutouts).
|
||||
fn tabs_inner_margin(ui: &mut egui::Ui, frame: &mut eframe::Frame) -> Margin {
|
||||
Margin {
|
||||
left: View::far_left_inset_margin(ui) + 4.0,
|
||||
left: View::get_left_inset() + 4.0,
|
||||
right: View::far_right_inset_margin(ui, frame) + 4.0,
|
||||
top: 4.0,
|
||||
bottom: View::get_bottom_inset() + 4.0,
|
||||
|
@ -157,7 +161,7 @@ impl Network {
|
|||
/// Calculate content inner margin based on display insets (cutouts).
|
||||
fn content_inner_margin(ui: &mut egui::Ui, frame: &mut eframe::Frame) -> Margin {
|
||||
Margin {
|
||||
left: View::far_left_inset_margin(ui) + 4.0,
|
||||
left: View::get_left_inset() + 4.0,
|
||||
right: View::far_right_inset_margin(ui, frame) + 4.0,
|
||||
top: 3.0,
|
||||
bottom: 4.0,
|
||||
|
@ -206,27 +210,30 @@ impl Network {
|
|||
let subtitle_text = Node::get_sync_status_text();
|
||||
let not_syncing = Node::not_syncing();
|
||||
let title_content = TitleType::WithSubTitle(title_text, subtitle_text, !not_syncing);
|
||||
|
||||
// Draw title panel.
|
||||
TitlePanel::ui(title_content, TitleAction::new(DOTS_THREE_OUTLINE_VERTICAL, || {
|
||||
//TODO: Show connections
|
||||
}), if !Root::is_dual_panel_mode(frame) {
|
||||
TitleAction::new(CARDHOLDER, || {
|
||||
Root::toggle_side_panel();
|
||||
})
|
||||
} else {
|
||||
None
|
||||
TitlePanel::ui(title_content, |ui, frame| {
|
||||
View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || {
|
||||
//TODO: Show connections
|
||||
});
|
||||
}, |ui, frame| {
|
||||
if !Root::is_dual_panel_mode(frame) {
|
||||
View::title_button(ui, CARDHOLDER, || {
|
||||
Root::toggle_network_panel();
|
||||
});
|
||||
}
|
||||
}, ui, frame);
|
||||
}
|
||||
|
||||
/// Content to draw when node is disabled.
|
||||
pub fn disabled_node_ui(ui: &mut egui::Ui) {
|
||||
View::center_content(ui, 162.0, |ui| {
|
||||
View::center_content(ui, 156.0, |ui| {
|
||||
let text = t!("network.disabled_server", "dots" => DOTS_THREE_OUTLINE_VERTICAL);
|
||||
ui.label(RichText::new(text)
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT)
|
||||
);
|
||||
ui.add_space(10.0);
|
||||
ui.add_space(8.0);
|
||||
View::button(ui, format!("{} {}", POWER, t!("network.enable_node")), Colors::GOLD, || {
|
||||
Node::start();
|
||||
});
|
||||
|
|
|
@ -11,30 +11,31 @@
|
|||
// 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 std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::RichText;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use crate::gui::Colors;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Accounts, Modal, ModalContainer, Network, View};
|
||||
use crate::gui::views::{Wallets, Modal, ModalContainer, Network, View};
|
||||
use crate::node::Node;
|
||||
|
||||
lazy_static! {
|
||||
/// To check if side panel is open from any part of ui.
|
||||
static ref SIDE_PANEL_OPEN: AtomicBool = AtomicBool::new(false);
|
||||
/// Global state to check if [`Network`] panel is open.
|
||||
static ref NETWORK_PANEL_OPEN: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
|
||||
/// Contains main ui content, handles side panel state.
|
||||
pub struct Root {
|
||||
/// Side panel content.
|
||||
side_panel: Network,
|
||||
/// Central panel content.
|
||||
central_content: Accounts,
|
||||
/// Side panel [`Network`] content.
|
||||
network: Network,
|
||||
/// Central panel [`Wallets`] content.
|
||||
wallets: Wallets,
|
||||
|
||||
/// Check if app exit is allowed on close event of [`eframe::App`] platform implementation.
|
||||
/// Check if app exit is allowed on close event of [`eframe::App`] implementation.
|
||||
pub(crate) exit_allowed: bool,
|
||||
|
||||
/// Flag to show exit progress at [`Modal`].
|
||||
|
@ -50,8 +51,8 @@ impl Default for Root {
|
|||
let os = OperatingSystem::from_target_os();
|
||||
let exit_allowed = os == OperatingSystem::Android || os == OperatingSystem::IOS;
|
||||
Self {
|
||||
side_panel: Network::default(),
|
||||
central_content: Accounts::default(),
|
||||
network: Network::default(),
|
||||
wallets: Wallets::default(),
|
||||
exit_allowed,
|
||||
show_exit_progress: false,
|
||||
allowed_modal_ids: vec![
|
||||
|
@ -75,33 +76,39 @@ impl Root {
|
|||
pub const SIDE_PANEL_MIN_WIDTH: f32 = 400.0;
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
||||
// Show opened exit confirmation Modal content.
|
||||
// Show opened exit confirmation modal content.
|
||||
if self.can_draw_modal() {
|
||||
self.exit_modal_content(ui, frame, cb);
|
||||
}
|
||||
|
||||
let (is_panel_open, panel_width) = Self::side_panel_state_width(frame);
|
||||
let (is_panel_open, panel_width) = Self::network_panel_state_width(frame);
|
||||
// Show network content.
|
||||
egui::SidePanel::left("network_panel")
|
||||
.resizable(false)
|
||||
.exact_width(panel_width)
|
||||
.frame(egui::Frame::none())
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::WHITE,
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, is_panel_open, |ui| {
|
||||
// Show network content on side panel.
|
||||
self.side_panel.ui(ui, frame, cb);
|
||||
self.network.ui(ui, frame, cb);
|
||||
});
|
||||
|
||||
// Show wallets content.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame::none())
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::FILL_DARK,
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
// Show accounts content on central panel.
|
||||
self.central_content.ui(ui, frame, cb);
|
||||
self.wallets.ui(ui, frame, cb);
|
||||
});
|
||||
}
|
||||
|
||||
/// Get side panel state and width.
|
||||
fn side_panel_state_width(frame: &mut eframe::Frame) -> (bool, f32) {
|
||||
/// Get [`Network`] panel state and width.
|
||||
fn network_panel_state_width(frame: &mut eframe::Frame) -> (bool, f32) {
|
||||
let dual_panel_mode = Self::is_dual_panel_mode(frame);
|
||||
let is_panel_open = dual_panel_mode || Self::is_side_panel_open();
|
||||
let is_panel_open = dual_panel_mode || Self::is_network_panel_open();
|
||||
let panel_width = if dual_panel_mode {
|
||||
Self::SIDE_PANEL_MIN_WIDTH + View::get_left_inset()
|
||||
} else {
|
||||
|
@ -110,7 +117,7 @@ impl Root {
|
|||
(is_panel_open, panel_width)
|
||||
}
|
||||
|
||||
/// Check if ui can show [`Network`] and [`Accounts`] at same time.
|
||||
/// Check if ui can show [`Network`] and [`Wallets`] at same time.
|
||||
pub fn is_dual_panel_mode(frame: &mut eframe::Frame) -> bool {
|
||||
let w = frame.info().window_info.size.x;
|
||||
let h = frame.info().window_info.size.y;
|
||||
|
@ -123,14 +130,14 @@ impl Root {
|
|||
}
|
||||
|
||||
/// Toggle [`Network`] panel state.
|
||||
pub fn toggle_side_panel() {
|
||||
let is_open = SIDE_PANEL_OPEN.load(Ordering::Relaxed);
|
||||
SIDE_PANEL_OPEN.store(!is_open, Ordering::Relaxed);
|
||||
pub fn toggle_network_panel() {
|
||||
let is_open = NETWORK_PANEL_OPEN.load(Ordering::Relaxed);
|
||||
NETWORK_PANEL_OPEN.store(!is_open, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Check if side panel is open.
|
||||
pub fn is_side_panel_open() -> bool {
|
||||
SIDE_PANEL_OPEN.load(Ordering::Relaxed)
|
||||
/// Check if [`Network`] panel is open.
|
||||
pub fn is_network_panel_open() -> bool {
|
||||
NETWORK_PANEL_OPEN.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Show exit confirmation modal.
|
||||
|
@ -215,26 +222,12 @@ impl Root {
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle platform-specific Back key code event.
|
||||
pub fn on_back() {
|
||||
/// Handle Back key event.
|
||||
pub fn on_back(&mut self) {
|
||||
if Modal::on_back() {
|
||||
Self::show_exit_modal()
|
||||
if self.wallets.on_back() {
|
||||
Self::show_exit_modal()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(target_os = "android")]
|
||||
#[allow(non_snake_case)]
|
||||
#[no_mangle]
|
||||
/// Handle Back key code event from Android.
|
||||
pub extern "C" fn Java_mw_gri_android_MainActivity_onBack(
|
||||
_env: jni::JNIEnv,
|
||||
_class: jni::objects::JObject,
|
||||
_activity: jni::objects::JObject,
|
||||
) {
|
||||
Root::on_back();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -19,18 +19,6 @@ use egui_extras::{Size, StripBuilder};
|
|||
use crate::gui::Colors;
|
||||
use crate::gui::views::View;
|
||||
|
||||
/// Title action button.
|
||||
pub struct TitleAction {
|
||||
pub(crate) icon: Box<&'static str>,
|
||||
pub(crate) on_click: Box<dyn Fn()>,
|
||||
}
|
||||
|
||||
impl TitleAction {
|
||||
pub fn new(icon: &'static str, on_click: fn()) -> Option<Self> {
|
||||
Option::from(Self { icon: Box::new(icon), on_click: Box::new(on_click) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents title content, can be single title or with animated sub-title.
|
||||
pub enum TitleType {
|
||||
Single(String),
|
||||
|
@ -44,8 +32,8 @@ impl TitlePanel {
|
|||
pub const DEFAULT_HEIGHT: f32 = 54.0;
|
||||
|
||||
pub fn ui(title: TitleType,
|
||||
left_action: Option<TitleAction>,
|
||||
right_action: Option<TitleAction>,
|
||||
mut left_content: impl FnMut(&mut egui::Ui, &mut eframe::Frame),
|
||||
mut right_content: impl FnMut(&mut egui::Ui, &mut eframe::Frame),
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame) {
|
||||
// Setup identifier.
|
||||
|
@ -68,7 +56,9 @@ impl TitlePanel {
|
|||
.size(Size::exact(Self::DEFAULT_HEIGHT))
|
||||
.horizontal(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
Self::action_ui(ui, left_action);
|
||||
ui.centered_and_justified(|ui| {
|
||||
(left_content)(ui, frame);
|
||||
});
|
||||
});
|
||||
match title {
|
||||
TitleType::Single(text) => {
|
||||
|
@ -86,7 +76,9 @@ impl TitlePanel {
|
|||
}
|
||||
}
|
||||
strip.cell(|ui| {
|
||||
Self::action_ui(ui, right_action);
|
||||
ui.centered_and_justified(|ui| {
|
||||
(right_content)(ui, frame);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -102,18 +94,6 @@ impl TitlePanel {
|
|||
}
|
||||
}
|
||||
|
||||
/// Draw panel [`TitleAction`].
|
||||
fn action_ui(ui: &mut egui::Ui, action: Option<TitleAction>) {
|
||||
if action.is_some() {
|
||||
let action = action.unwrap();
|
||||
ui.centered_and_justified(|ui| {
|
||||
View::title_button(ui, &action.icon, || {
|
||||
(action.on_click)();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw title text for [`TitleType::WithSubTitle`] type.
|
||||
fn with_sub_title(builder: StripBuilder, title: String, subtitle: String, animate_sub: bool) {
|
||||
builder
|
||||
|
|
27
src/gui/views/wallets/creation/connection.rs
Normal file
27
src/gui/views/wallets/creation/connection.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
// 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 crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::wallets::creation::StepControl;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ConnectionSetup {
|
||||
|
||||
}
|
||||
|
||||
impl ConnectionSetup {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, step: &dyn StepControl, cb: &dyn PlatformCallbacks) {
|
||||
|
||||
}
|
||||
}
|
310
src/gui/views/wallets/creation/content.rs
Normal file
310
src/gui/views/wallets/creation/content.rs
Normal file
|
@ -0,0 +1,310 @@
|
|||
// 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::{Margin, RichText, TextStyle, Widget};
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{EYE, EYE_SLASH, PLUS_CIRCLE};
|
||||
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
|
||||
}
|
||||
|
||||
/// Wallet creation content.
|
||||
pub struct WalletCreation {
|
||||
/// Wallet creation ui step.
|
||||
step: Option<Step>,
|
||||
|
||||
/// Flag to check if [`Modal`] just was opened to focus on first field.
|
||||
modal_just_opened: bool,
|
||||
/// Wallet name value.
|
||||
name_edit: String,
|
||||
/// Password to encrypt created wallet.
|
||||
pass_edit: String,
|
||||
/// Flag to show/hide password at [`egui::TextEdit`] field.
|
||||
hide_pass: bool,
|
||||
|
||||
/// Mnemonic phrase setup content.
|
||||
pub(crate) mnemonic_setup: MnemonicSetup,
|
||||
/// Network setup content.
|
||||
pub(crate) network_setup: ConnectionSetup,
|
||||
}
|
||||
|
||||
impl Default for WalletCreation {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
step: None,
|
||||
modal_just_opened: true,
|
||||
name_edit: "".to_string(),
|
||||
pass_edit: "".to_string(),
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletCreation {
|
||||
/// Wallet name/password input modal identifier.
|
||||
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 content.
|
||||
egui::CentralPanel::default()
|
||||
.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: 3.0,
|
||||
bottom: 4.0,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
self.step_ui(ui, cb);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw wallet creation [`Step`] content.
|
||||
fn step_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
match &self.step {
|
||||
None => {
|
||||
// Show wallet creation message if step is empty.
|
||||
View::center_content(ui, 124.0 + View::get_bottom_inset(), |ui| {
|
||||
let text = t!("wallets.create_desc");
|
||||
ui.label(RichText::new(text)
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT)
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
let add_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add"));
|
||||
View::button(ui, add_text, Colors::BUTTON, || {
|
||||
Self::show_modal();
|
||||
});
|
||||
});
|
||||
}
|
||||
Some(step) => {
|
||||
match step {
|
||||
Step::EnterMnemonic => {
|
||||
self.mnemonic_setup.ui(ui, self, cb);
|
||||
}
|
||||
Step::ConfirmMnemonic => {}
|
||||
Step::SetupConnection => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if it's possible to go back for current step.
|
||||
pub fn can_go_back(&self) -> bool {
|
||||
self.step.is_some()
|
||||
}
|
||||
|
||||
/// Back button key event handling.
|
||||
pub fn go_back(&mut self) {
|
||||
self.prev_step();
|
||||
}
|
||||
|
||||
/// Start wallet creation from showing [`Modal`] to enter name and password.
|
||||
pub fn show_modal() {
|
||||
Modal::show(Modal::new(Self::MODAL_ID)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.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);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.name"))
|
||||
.size(17.0)
|
||||
.color(Colors::GRAY));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show wallet name text edit.
|
||||
let name_resp = egui::TextEdit::singleline(&mut self.name_edit)
|
||||
.id(ui.id().with("wallet_name_edit"))
|
||||
.font(TextStyle::Heading)
|
||||
.desired_width(ui.available_width())
|
||||
.cursor_at_end(true)
|
||||
.ui(ui);
|
||||
ui.add_space(8.0);
|
||||
if name_resp.clicked() {
|
||||
cb.show_keyboard();
|
||||
}
|
||||
|
||||
// Check if modal was just opened to show focus on name text input.
|
||||
if self.modal_just_opened {
|
||||
self.modal_just_opened = false;
|
||||
cb.show_keyboard();
|
||||
name_resp.request_focus();
|
||||
}
|
||||
|
||||
ui.label(RichText::new(t!("wallets.pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::GRAY));
|
||||
ui.add_space(8.0);
|
||||
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::exact(34.0))
|
||||
.vertical(|mut strip| {
|
||||
strip.strip(|builder| {
|
||||
builder
|
||||
.size(Size::remainder())
|
||||
.size(Size::exact(48.0))
|
||||
.horizontal(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
ui.add_space(2.0);
|
||||
// Draw wallet password text edit.
|
||||
let pass_resp = egui::TextEdit::singleline(&mut self.pass_edit)
|
||||
.id(ui.id().with("wallet_pass_edit"))
|
||||
.font(TextStyle::Heading)
|
||||
.desired_width(ui.available_width())
|
||||
.cursor_at_end(true)
|
||||
.password(self.hide_pass)
|
||||
.ui(ui);
|
||||
if pass_resp.clicked() {
|
||||
cb.show_keyboard();
|
||||
}
|
||||
|
||||
// Hide keyboard if input fields has no focus.
|
||||
if !pass_resp.has_focus() && !name_resp.has_focus() {
|
||||
cb.hide_keyboard();
|
||||
}
|
||||
});
|
||||
strip.cell(|ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
// Draw button to show/hide password.
|
||||
let eye_icon = if self.hide_pass { EYE } else { EYE_SLASH };
|
||||
View::button(ui, eye_icon.to_string(), Colors::WHITE, || {
|
||||
self.hide_pass = !self.hide_pass;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
// Show information when specified values are empty.
|
||||
if self.name_edit.is_empty() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("wallets.name_empty"))
|
||||
.size(17.0)
|
||||
.color(Colors::INACTIVE_TEXT));
|
||||
} else if self.pass_edit.is_empty() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("wallets.pass_empty"))
|
||||
.size(17.0)
|
||||
.color(Colors::INACTIVE_TEXT));
|
||||
}
|
||||
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, || {
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("continue"), Colors::WHITE, || {
|
||||
self.on_modal_confirmation(modal, cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
}
|
124
src/gui/views/wallets/creation/mnemonic.rs
Normal file
124
src/gui/views/wallets/creation/mnemonic.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
// 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};
|
||||
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<String>
|
||||
}
|
||||
|
||||
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<u8> = 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::<Vec<String>>()
|
||||
},
|
||||
PhraseMode::Import => Vec::with_capacity(self.size.value())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Mnemonic phrase setup content.
|
||||
pub struct MnemonicSetup {
|
||||
/// Current mnemonic phrase.
|
||||
mnemonic: Mnemonic,
|
||||
/// Word value for [`Modal`].
|
||||
word_edit: String,
|
||||
}
|
||||
|
||||
impl Default for MnemonicSetup {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mnemonic: Mnemonic::default(),
|
||||
word_edit: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MnemonicSetup {
|
||||
pub fn ui(&self, ui: &mut egui::Ui, step: &dyn StepControl, cb: &dyn PlatformCallbacks) {
|
||||
|
||||
}
|
||||
|
||||
pub fn get_mnemonic_mode(&self) -> &PhraseMode {
|
||||
&self.mnemonic.mode
|
||||
}
|
||||
|
||||
/// Reset mnemonic phrase to default values.
|
||||
pub fn reset(&mut self) {
|
||||
self.mnemonic = Mnemonic::default();
|
||||
}
|
||||
}
|
30
src/gui/views/wallets/creation/mod.rs
Normal file
30
src/gui/views/wallets/creation/mod.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
// 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 mnemonic;
|
||||
pub use mnemonic::MnemonicSetup;
|
||||
|
||||
mod connection;
|
||||
pub use connection::ConnectionSetup;
|
||||
|
||||
mod content;
|
||||
pub use content::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);
|
||||
}
|
|
@ -12,5 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod accounts;
|
||||
pub use accounts::*;
|
||||
mod wallets;
|
||||
mod creation;
|
||||
mod wallet;
|
||||
|
||||
pub use wallets::*;
|
|
@ -12,46 +12,41 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{GLOBE, PLUS};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Root, TitleAction, TitleType, TitlePanel, View};
|
||||
use egui::Margin;
|
||||
|
||||
/// Accounts content.
|
||||
pub struct Accounts {
|
||||
/// List of accounts.
|
||||
list: Vec<String>
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::View;
|
||||
|
||||
/// Selected wallet list item content.
|
||||
pub struct WalletContent {
|
||||
/// Current wallet instance.
|
||||
item: String
|
||||
}
|
||||
|
||||
impl Default for Accounts {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
list: vec![],
|
||||
}
|
||||
impl WalletContent {
|
||||
fn new(item: String) -> Self {
|
||||
Self { item }
|
||||
}
|
||||
}
|
||||
|
||||
impl Accounts {
|
||||
impl WalletContent {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
|
||||
let title_content = TitleType::Single(t!("accounts.title").to_uppercase());
|
||||
TitlePanel::ui(title_content, if !Root::is_dual_panel_mode(frame) {
|
||||
TitleAction::new(GLOBE, || {
|
||||
Root::toggle_side_panel();
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}, TitleAction::new(PLUS, || {
|
||||
//TODO: add account
|
||||
}), ui, frame);
|
||||
|
||||
// Show wallet content.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: View::DEFAULT_STROKE,
|
||||
fill: Colors::FILL_DARK,
|
||||
fill: Colors::WHITE,
|
||||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui) + 4.0,
|
||||
right: View::far_right_inset_margin(ui, frame) + 4.0,
|
||||
top: 3.0,
|
||||
bottom: 4.0,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
//TODO: accounts list
|
||||
//TODO: wallet content
|
||||
});
|
||||
}
|
||||
}
|
199
src/gui/views/wallets/wallets.rs
Normal file
199
src/gui/views/wallets/wallets.rs
Normal file
|
@ -0,0 +1,199 @@
|
|||
// 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 std::cmp::max;
|
||||
|
||||
use egui::{Align2, Margin, Vec2};
|
||||
|
||||
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::wallet::WalletContent;
|
||||
|
||||
/// Wallets content.
|
||||
pub struct Wallets {
|
||||
/// List of wallets.
|
||||
list: Vec<String>,
|
||||
|
||||
/// Selected list item content.
|
||||
item_content: Option<WalletContent>,
|
||||
/// Wallet creation content.
|
||||
creation_content: WalletCreation,
|
||||
|
||||
/// [`Modal`] ids allowed at this ui container.
|
||||
modal_ids: Vec<&'static str>
|
||||
}
|
||||
|
||||
impl Default for Wallets {
|
||||
fn default() -> Self {
|
||||
//TODO load list.
|
||||
Self {
|
||||
list: vec![],
|
||||
item_content: None,
|
||||
creation_content: WalletCreation::default(),
|
||||
modal_ids: vec![
|
||||
WalletCreation::MODAL_ID
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalContainer for Wallets {
|
||||
fn modal_ids(&self) -> &Vec<&'static str> {
|
||||
&self.modal_ids
|
||||
}
|
||||
}
|
||||
|
||||
impl Wallets {
|
||||
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| {
|
||||
match modal.id {
|
||||
WalletCreation::MODAL_ID => self.creation_content.modal_ui(ui, modal, cb),
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
egui::SidePanel::right("wallet_panel")
|
||||
.resizable(false)
|
||||
.min_width(wallet_panel_width)
|
||||
.frame(egui::Frame {
|
||||
fill: if self.list.is_empty() { Colors::FILL_DARK } else { Colors::WHITE },
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, is_wallet_panel_open, |ui| {
|
||||
self.wallet_content_ui(ui, frame, cb);
|
||||
});
|
||||
|
||||
// Show list of wallets.
|
||||
if !self.list.is_empty() {
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: View::DEFAULT_STROKE,
|
||||
fill: Colors::FILL_DARK,
|
||||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui) + 4.0,
|
||||
right: View::far_right_inset_margin(ui, frame) + 4.0,
|
||||
top: 3.0,
|
||||
bottom: 4.0,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
//TODO: wallets list
|
||||
});
|
||||
// Show wallet creation button if wallet panel is not open.
|
||||
if !is_wallet_panel_open {
|
||||
self.add_wallet_btn_ui(ui);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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());
|
||||
|
||||
// Draw title panel.
|
||||
TitlePanel::ui(title_content, |ui, frame| {
|
||||
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, || {
|
||||
//TODO: show settings.
|
||||
});
|
||||
}, ui, frame);
|
||||
}
|
||||
|
||||
/// Draw [`WalletContent`] ui.
|
||||
fn wallet_content_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
if self.list.is_empty() || self.item_content.is_none() {
|
||||
self.creation_content.ui(ui, cb)
|
||||
} else {
|
||||
self.item_content.as_mut().unwrap().ui(ui, frame, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get [`WalletContent`] panel width.
|
||||
fn wallet_panel_width(&self, ui: &mut egui::Ui, frame: &mut eframe::Frame) -> f32 {
|
||||
if Self::is_dual_panel_mode(ui, frame) {
|
||||
let min_width = (Root::SIDE_PANEL_MIN_WIDTH + View::get_right_inset()) as i64;
|
||||
let available_width = if self.list.is_empty() {
|
||||
ui.available_width()
|
||||
} else {
|
||||
ui.available_width() - Root::SIDE_PANEL_MIN_WIDTH
|
||||
} as i64;
|
||||
max(min_width, available_width) as f32
|
||||
} else {
|
||||
let dual_panel_root = Root::is_dual_panel_mode(frame);
|
||||
if dual_panel_root {
|
||||
ui.available_width()
|
||||
} else {
|
||||
frame.info().window_info.size.x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if ui can show [`Wallets`] list and [`WalletContent`] content at same time.
|
||||
fn is_dual_panel_mode(ui: &mut egui::Ui, frame: &mut eframe::Frame) -> bool {
|
||||
let dual_panel_root = Root::is_dual_panel_mode(frame);
|
||||
let max_width = ui.available_width();
|
||||
dual_panel_root && max_width >= (Root::SIDE_PANEL_MIN_WIDTH * 2.0) + View::get_right_inset()
|
||||
}
|
||||
|
||||
/// Draw floating button to create the wallet.
|
||||
fn add_wallet_btn_ui(&self, ui: &mut egui::Ui) {
|
||||
egui::Window::new("create_wallet_button")
|
||||
.title_bar(false)
|
||||
.resizable(false)
|
||||
.collapsible(false)
|
||||
.anchor(Align2::RIGHT_BOTTOM, Vec2::new(-8.0, -8.0))
|
||||
.frame(egui::Frame::default())
|
||||
.show(ui.ctx(), |ui| {
|
||||
View::round_button(ui, PLUS, || {
|
||||
WalletCreation::show_modal();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Handle Back key event.
|
||||
/// Return `false` when event was handled.
|
||||
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();
|
||||
}
|
||||
!can_go_back
|
||||
}
|
||||
}
|
|
@ -12,6 +12,6 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod wallet;
|
||||
mod wallet;
|
||||
|
||||
// pub use self::wallet::{init, init_from_seed};
|
||||
|
|
|
@ -12,10 +12,3 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub fn create_from_seed() {
|
||||
|
||||
}
|
||||
|
||||
pub fn create() {
|
||||
|
||||
}
|
Loading…
Reference in a new issue