diff --git a/locales/en.yml b/locales/en.yml index e7a62618..07a9f59c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -9,6 +9,7 @@ change: Change show: Show delete: Delete clear: Clear +create: Create wallets: await_conf_amount: Awaiting confirmation await_fin_amount: Awaiting finalization @@ -23,7 +24,6 @@ wallets: current_pass: 'Current password:' new_pass: 'New password:' min_tx_conf_count: 'Minimum amount of confirmations for transactions:' - create: Create recover: Restore recovery_phrase: Recovery phrase words_count: 'Words count:' @@ -53,6 +53,8 @@ wallets: wallet_checking: Checking wallet tx_loading: Loading transactions default_account: Default account + create_account: Create account + label: 'Label:' tx_sent: Sent tx_received: Received tx_sending: Sending @@ -123,7 +125,6 @@ network_node: height: Height difficulty: Difficulty time: Time - transactions: Transactions main_pool: Main pool stem_pool: Stem pool data: Data diff --git a/locales/ru.yml b/locales/ru.yml index 91110e63..d815505c 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -9,6 +9,7 @@ change: Изменить show: Показать delete: Удалить clear: Очистить +create: Создать wallets: await_conf_amount: Ожидает подтверждения await_fin_amount: Ожидает завершения @@ -23,7 +24,6 @@ wallets: current_pass: 'Текущий пароль:' new_pass: 'Новый пароль:' min_tx_conf_count: 'Минимальное количество подтверждений для транзакций:' - create: Создать recover: Восстановить recovery_phrase: Фраза восстановления words_count: 'Количество слов:' @@ -53,6 +53,8 @@ wallets: wallet_checking: Проверка кошелька tx_loading: Загрузка транзакций default_account: Стандартный аккаунт + create_account: Создать аккаунт + label: 'Метка:' tx_sent: Отправлено tx_received: Получено tx_sending: Отправка @@ -123,7 +125,6 @@ network_node: height: Высота difficulty: Сложность time: Время - transactions: Транзакции main_pool: Основной пул stem_pool: Stem пул data: Данные diff --git a/src/gui/views/wallets/creation/mnemonic.rs b/src/gui/views/wallets/creation/mnemonic.rs index 8098d884..551ada61 100644 --- a/src/gui/views/wallets/creation/mnemonic.rs +++ b/src/gui/views/wallets/creation/mnemonic.rs @@ -119,7 +119,7 @@ impl MnemonicSetup { ui.columns(2, |columns| { columns[0].vertical_centered(|ui| { let create_mode = PhraseMode::Generate; - let create_text = t!("wallets.create"); + let create_text = t!("create"); View::radio_value(ui, &mut mode, create_mode, create_text); }); columns[1].vertical_centered(|ui| { diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index cdd983cd..a062c73d 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -14,7 +14,7 @@ use std::time::Duration; -use egui::{Align, Layout, Margin, RichText, Rounding}; +use egui::{Align, Id, Layout, Margin, RichText, Rounding, TextStyle, Widget}; use grin_chain::SyncStatus; use grin_core::core::amount_to_hr_string; @@ -22,31 +22,47 @@ use crate::AppConfig; use crate::gui::Colors; use crate::gui::icons::{DOWNLOAD, FILE_ARCHIVE, GEAR_FINE, LIST, PACKAGE, PLUS, POWER, REPEAT, UPLOAD, WALLET}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Root, View}; +use crate::gui::views::{Modal, Root, View}; +use crate::gui::views::types::ModalPosition; use crate::gui::views::wallets::{WalletInfo, WalletReceive, WalletSend, WalletSettings}; use crate::gui::views::wallets::types::{WalletTab, WalletTabType}; use crate::node::Node; +use crate::wallet::{Wallet, WalletConfig}; use crate::wallet::types::WalletData; -use crate::wallet::Wallet; /// Selected and opened wallet content. pub struct WalletContent { + /// Account label [`Modal`] value. + pub account_label_edit: String, + /// Flag to check if error occurred during account creation at [`Modal`]. + pub account_creation_error: bool, + /// Current tab content to show. pub current_tab: Box, } impl Default for WalletContent { fn default() -> Self { - Self { current_tab: Box::new(WalletInfo::default()) } + Self { + account_label_edit: "".to_string(), + account_creation_error: false, + current_tab: Box::new(WalletInfo::default()) + } } } +/// Identifier for account creation [`Modal`]. +const CREATE_ACCOUNT_MODAL: &'static str = "create_account_modal"; + impl WalletContent { pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { + // Show modal content for this ui container. + self.modal_content_ui(ui, wallet, cb); + let data = wallet.get_data(); let data_empty = data.is_none(); @@ -74,7 +90,7 @@ impl WalletContent { ui.vertical_centered(|ui| { // Draw wallet tabs. View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.35, |ui| { - Self::account_ui(ui, data.as_ref().unwrap(), &wallet.config.account); + Self::account_ui(ui, data.as_ref().unwrap(), &wallet.config.account, cb); }); }); }); @@ -120,8 +136,31 @@ impl WalletContent { } } + /// Draw [`Modal`] content for this ui container. + fn modal_content_ui(&mut self, + ui: &mut egui::Ui, + wallet: &mut Wallet, + cb: &dyn PlatformCallbacks) { + match Modal::opened() { + None => {} + Some(id) => { + match id { + CREATE_ACCOUNT_MODAL => { + Modal::ui(ui.ctx(), |ui, modal| { + self.create_account_modal_ui(ui, wallet, modal, cb); + }); + } + _ => {} + } + } + } + } + /// Draw wallet account content. - fn account_ui(ui: &mut egui::Ui, data: &WalletData, account: &Option) { + fn account_ui(ui: &mut egui::Ui, + data: &WalletData, + account: &String, + cb: &dyn PlatformCallbacks) { let mut rect = ui.available_rect_before_wrap(); rect.set_height(75.0); // Draw round background. @@ -134,7 +173,12 @@ impl WalletContent { // Draw button to add new account. View::item_button(ui, View::item_rounding(0, 2, true), PLUS, None, || { - //TODO add account modal. + // Show account creation modal. + Modal::new(CREATE_ACCOUNT_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("wallets.create_account")) + .show(); + cb.show_keyboard(); }); // Draw button to show list of accounts. @@ -152,14 +196,16 @@ impl WalletContent { let amount_text = format!("{} ツ", amount); ui.label(RichText::new(amount_text).size(18.0).color(Colors::BLACK)); - // Show account name. - let account_name = match account { - None => t!("wallets.default_account"), - Some(name) => name.to_owned() + // Show account label. + let default_acc_label = &WalletConfig::DEFAULT_ACCOUNT_LABEL.to_string(); + let acc_label = if account == default_acc_label { + t!("wallets.default_account") + } else { + account.to_owned() }; - let account_text = format!("{} {}", FILE_ARCHIVE, account_name); + let acc_text = format!("{} {}", FILE_ARCHIVE, acc_label); ui.add_space(-2.0); - View::ellipsize_text(ui, account_text, 15.0, Colors::TEXT); + View::ellipsize_text(ui, acc_text, 15.0, Colors::TEXT); // Show confirmed height. let height_text = format!("{} {}", PACKAGE, data.info.last_confirmed_height); @@ -169,6 +215,84 @@ impl WalletContent { }); } + /// Draw account creation [`Modal`] content. + fn create_account_modal_ui(&mut self, + ui: &mut egui::Ui, + wallet: &mut Wallet, + 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); + + // Draw account name edit. + let text_edit_resp = egui::TextEdit::singleline(&mut self.account_label_edit) + .id(Id::from(modal.id).with(wallet.config.id)) + .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(); + } + ui.add_space(8.0); + }); + + // Show error occurred during account creation.. + if self.account_creation_error { + ui.add_space(2.0); + ui.label(RichText::new(t!("error")) + .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| { + // Create button callback. + let mut on_create = || { + if !self.account_label_edit.is_empty() { + let label = &self.account_label_edit; + match wallet.create_account(label) { + Ok(_) => match wallet.set_active_account(label) { + Ok(_) => { + cb.hide_keyboard(); + modal.close(); + } + Err(_) => self.account_creation_error = true + }, + Err(_) => self.account_creation_error = true + }; + } + }; + + View::on_enter_key(ui, || { + (on_create)(); + }); + + View::button(ui, t!("create"), Colors::WHITE, on_create); + }); + }); + ui.add_space(6.0); + }); + } + /// Draw tab buttons in the bottom of the screen. fn tabs_ui(&mut self, ui: &mut egui::Ui) { ui.scope(|ui| { diff --git a/src/wallet/config.rs b/src/wallet/config.rs index 9f0b43fd..ae26a767 100644 --- a/src/wallet/config.rs +++ b/src/wallet/config.rs @@ -14,6 +14,7 @@ use std::fs; use std::path::PathBuf; +use std::string::ToString; use grin_core::global::ChainTypes; use serde_derive::{Deserialize, Serialize}; @@ -24,8 +25,8 @@ use crate::wallet::types::ConnectionMethod; /// Wallet configuration. #[derive(Serialize, Deserialize, Clone)] pub struct WalletConfig { - /// Last chosen wallet account label. - pub account: Option, + /// Current wallet account label. + pub account: String, /// Chain type for current wallet. pub chain_type: ChainTypes, /// Identifier for a wallet. @@ -39,7 +40,7 @@ pub struct WalletConfig { } /// Base wallets directory name. -pub const BASE_DIR_NAME: &'static str = "wallets"; +const BASE_DIR_NAME: &'static str = "wallets"; /// Wallet configuration file name. const CONFIG_FILE_NAME: &'static str = "grim-wallet.toml"; /// Slatepacks directory name. @@ -49,6 +50,9 @@ const SLATEPACKS_DIR_NAME: &'static str = "slatepacks"; const MIN_CONFIRMATIONS_DEFAULT: u64 = 10; impl WalletConfig { + /// Default account name value. + pub const DEFAULT_ACCOUNT_LABEL: &'static str = "default"; + /// Create new wallet config. pub fn create(name: String, conn_method: &ConnectionMethod) -> WalletConfig { // Setup configuration path. @@ -57,7 +61,7 @@ impl WalletConfig { let config_path = Self::get_config_file_path(chain_type, id); // Write configuration to the file. let config = WalletConfig { - account: None, + account: Self::DEFAULT_ACCOUNT_LABEL.to_string(), chain_type, id, name, @@ -128,4 +132,10 @@ impl WalletConfig { let config_path = Self::get_config_file_path(self.chain_type, self.id); Settings::write_to_file(self, config_path); } + + /// Save account label value. + pub fn save_account(&mut self, label: &String) { + self.account = label.to_owned(); + self.save(); + } } \ No newline at end of file diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index 3951a42b..7df7e5e6 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -227,10 +227,14 @@ impl Wallet { self.set_sync_error(false); self.reset_sync_attempts(); + // Set current account. + let wallet_inst = lc.wallet_inst()?; + let label = self.config.account.to_owned(); + wallet_inst.set_parent_key_id_by_name(label.as_str())?; + // Start new synchronization thread or wake up existing one. let mut thread_w = self.sync_thread.write().unwrap(); if thread_w.is_none() { - // Start wallet synchronization. let thread = start_sync(self.clone()); *thread_w = Some(thread); } else { @@ -288,11 +292,9 @@ impl Wallet { wallet_close.foreign_api_server.read().unwrap().is_some() }; if api_server_exists { - let _ = thread::spawn(move || { - let mut api_server_w = wallet_close.foreign_api_server.write().unwrap(); - api_server_w.as_mut().unwrap().stop(); - *api_server_w = None; - }).join(); + let mut api_server_w = wallet_close.foreign_api_server.write().unwrap(); + api_server_w.as_mut().unwrap().stop(); + *api_server_w = None; } // Close the wallet. @@ -302,11 +304,8 @@ impl Wallet { wallet_close.closing.store(false, Ordering::Relaxed); wallet_close.is_open.store(false, Ordering::Relaxed); - // Wake up wallet thread. - let thread_r = wallet_close.sync_thread.read().unwrap(); - if let Some(thread) = thread_r.as_ref() { - thread.unpark(); - } + // Wake up thread to exit. + wallet_close.refresh(); }); } @@ -318,15 +317,34 @@ impl Wallet { } /// Create account into wallet. - pub fn create_account(&self, label: String) -> Result<(), Error> { + pub fn create_account(&self, label: &String) -> Result<(), Error> { let mut api = Owner::new(self.instance.clone().unwrap(), None); controller::owner_single_use(None, None, Some(&mut api), |api, m| { - api.create_account_path(m, &label)?; - println!("Account: '{}' Created!", label); + api.create_account_path(m, label)?; Ok(()) }) } + /// Set active account from provided label. + pub fn set_active_account(&mut self, label: &String) -> Result<(), Error> { + let instance = self.instance.clone().unwrap(); + let mut wallet_lock = instance.lock(); + let lc = wallet_lock.lc_provider()?; + let wallet_inst = lc.wallet_inst()?; + wallet_inst.set_parent_key_id_by_name(label.as_str())?; + + // Save account label into config. + self.config.save_account(label); + + // Clear wallet info. + let mut w_data = self.data.write().unwrap(); + *w_data = None; + + // Refresh wallet data. + self.refresh(); + Ok(()) + } + /// Get list of accounts for the wallet. pub fn accounts(&self) -> Vec { let mut api = Owner::new(self.instance.clone().unwrap(), None); @@ -396,17 +414,21 @@ impl Wallet { r_data.clone() } + /// Wake up wallet thread to refresh wallet info and update statuses. + fn refresh(&self) { + let thread_r = self.sync_thread.read().unwrap(); + if let Some(thread) = thread_r.as_ref() { + thread.unpark(); + } + } + /// Receive transaction via Slatepack Message. pub fn receive(&self, message: String) -> Result { let mut api = Owner::new(self.instance.clone().unwrap(), None); match parse_slatepack(&mut api, None, None, Some(message.clone())) { Ok((mut slate, _)) => { controller::foreign_single_use(api.wallet_inst.clone(), None, |api| { - let account = if let Some(acc) = self.config.clone().account { - acc - } else { - "default".to_string() - }; + let account = self.config.clone().account; slate = api.receive_tx(&slate, Some(account.as_str()), None)?; Ok(()) })?; @@ -426,6 +448,9 @@ impl Wallet { output.write_all(response.as_bytes())?; output.sync_all()?; + // Refresh wallet info. + self.refresh(); + Ok(response) } Err(_) => { @@ -447,15 +472,12 @@ impl Wallet { } // Launch tx cancelling at separate thread. - let mut wallet_cancel = self.clone(); + let wallet_cancel = self.clone(); let instance = wallet_cancel.instance.clone().unwrap(); thread::spawn(move || { let _ = cancel_tx(instance, None, &None, Some(id), None); - // Wake up wallet thread to update statuses. - let thread_r = wallet_cancel.sync_thread.read().unwrap(); - if let Some(thread) = thread_r.as_ref() { - thread.unpark(); - } + // Refresh wallet info to update statuses. + wallet_cancel.refresh(); }); } @@ -480,11 +502,7 @@ impl Wallet { /// Initiate wallet repair by scanning its outputs. pub fn repair(&self) { self.repair_needed.store(true, Ordering::Relaxed); - // Wake up wallet thread. - let thread_r = self.sync_thread.read().unwrap(); - if let Some(thread) = thread_r.as_ref() { - thread.unpark(); - } + self.refresh(); } /// Check if wallet is repairing. @@ -514,27 +532,23 @@ impl Wallet { // Delete wallet at separate thread. let wallet_delete = self.clone(); + let instance = wallet_delete.instance.clone().unwrap(); thread::spawn(move || { - thread::sleep(Duration::from_millis(1000)); - if let Some(instance) = wallet_delete.instance { - // Close the wallet. - Self::close_wallet(&instance); - // Remove wallet files. - let mut wallet_lock = instance.lock(); - let _ = wallet_lock.lc_provider().unwrap(); - let _ = fs::remove_dir_all(wallet_delete.config.get_data_path()); - } + // Close the wallet. + Self::close_wallet(&instance); + + // Remove wallet files. + let mut wallet_lock = instance.lock(); + let _ = wallet_lock.lc_provider().unwrap(); + let _ = fs::remove_dir_all(wallet_delete.config.get_data_path()); // Mark wallet as not opened and deleted. wallet_delete.closing.store(false, Ordering::Relaxed); wallet_delete.is_open.store(false, Ordering::Relaxed); wallet_delete.deleted.store(true, Ordering::Relaxed); - // Wake up wallet thread. - let thread_r = wallet_delete.sync_thread.read().unwrap(); - if let Some(thread) = thread_r.as_ref() { - thread.unpark(); - } + // Wake up thread to exit. + wallet_delete.refresh(); }); } @@ -863,4 +877,4 @@ fn scan_wallet(wallet: &Wallet) { // Reset repair progress. wallet.repair_progress.store(0, Ordering::Relaxed); -} +} \ No newline at end of file