wallet + ui: delete wallet, show recovery phrase, update translations

This commit is contained in:
ardocrat 2023-08-14 03:17:54 +03:00
parent 09d3835082
commit 48630fc6be
9 changed files with 294 additions and 140 deletions

View file

@ -7,6 +7,7 @@ retry: Retry
close: Close close: Close
change: Change change: Change
show: Show show: Show
delete: Delete
wallets: wallets:
title: Wallets title: Wallets
create_desc: Create or import existing wallet from saved recovery phrase. create_desc: Create or import existing wallet from saved recovery phrase.
@ -51,7 +52,7 @@ wallets:
repair_desc: Check a wallet, repairing and restoring missing outputs if required. This operation will take time. repair_desc: Check a wallet, repairing and restoring missing outputs if required. This operation will take time.
repair_unavailable: You need an active connection to the node and completed wallet synchronization. repair_unavailable: You need an active connection to the node and completed wallet synchronization.
delete: Delete wallet delete: Delete wallet
delete_conf: 'Are you sure you want to remove the wallet? Enter password to confirm:' delete_conf: Are you sure you want to delete the wallet?
delete_desc: Make sure you have saved your recovery phrase to access funds in the future. delete_desc: Make sure you have saved your recovery phrase to access funds in the future.
wallet_loading_err: 'An error occurred during synchronization of the wallet, you can retry or change connection settings by selecting %{settings} at the bottom of the screen.' wallet_loading_err: 'An error occurred during synchronization of the wallet, you can retry or change connection settings by selecting %{settings} at the bottom of the screen.'
wallet: Wallet wallet: Wallet

View file

@ -7,6 +7,7 @@ retry: Повторить
close: Закрыть close: Закрыть
change: Изменить change: Изменить
show: Показать show: Показать
delete: Удалить
wallets: wallets:
title: Кошельки title: Кошельки
create_desc: Создайте или импортируйте существующий кошелёк из сохранённой фразы восстановления. create_desc: Создайте или импортируйте существующий кошелёк из сохранённой фразы восстановления.
@ -51,7 +52,7 @@ wallets:
repair_desc: Проверить кошелёк, исправляя и восстанавливая недостающие выходы, если это необходимо. Эта операция займёт время. repair_desc: Проверить кошелёк, исправляя и восстанавливая недостающие выходы, если это необходимо. Эта операция займёт время.
repair_unavailable: Необходимо активное подключение к узлу и завершённая синхронизация кошелька. repair_unavailable: Необходимо активное подключение к узлу и завершённая синхронизация кошелька.
delete: Удалить кошелёк delete: Удалить кошелёк
delete_conf: 'Вы уверены, что хотите удалить кошелек? Введите пароль для подтверждения:' delete_conf: Вы уверены, что хотите удалить кошелек?
delete_desc: Убедитесь, что вы сохранили вашу фразу восстановления, чтобы получить доступ к средствам в будущем. delete_desc: Убедитесь, что вы сохранили вашу фразу восстановления, чтобы получить доступ к средствам в будущем.
wallet_loading_err: 'Во время синхронизации кошелька произошла ошибка, вы можете повторить попытку или изменить настройки подключения, выбрав %{settings} внизу экрана.' wallet_loading_err: 'Во время синхронизации кошелька произошла ошибка, вы можете повторить попытку или изменить настройки подключения, выбрав %{settings} внизу экрана.'
wallet: Кошелёк wallet: Кошелёк

View file

@ -14,7 +14,6 @@
use egui::{Align, Align2, Layout, Margin, RichText, Rounding, ScrollArea, TextStyle, Widget}; use egui::{Align, Align2, Layout, Margin, RichText, Rounding, ScrollArea, TextStyle, Widget};
use egui_extras::{Size, StripBuilder}; use egui_extras::{Size, StripBuilder};
use grin_core::global::ChainTypes;
use crate::AppConfig; use crate::AppConfig;
use crate::gui::Colors; use crate::gui::Colors;
@ -142,15 +141,11 @@ impl WalletsContent {
self.wallets.add(wallet); self.wallets.add(wallet);
}); });
} else { } else {
let chain_type = AppConfig::chain_type(); let selected_id = self.wallets.selected_id.clone();
let list = if chain_type == ChainTypes::Mainnet { let list = self.wallets.mut_list();
&mut self.wallets.main_list for wallet in list {
} else {
&mut self.wallets.test_list
};
for wallet in list.iter_mut() {
// Show content for selected wallet. // Show content for selected wallet.
if self.wallets.selected_id == Some(wallet.config.id) { if selected_id == Some(wallet.config.id) {
// Setup wallet content width. // Setup wallet content width.
let mut rect = ui.available_rect_before_wrap(); let mut rect = ui.available_rect_before_wrap();
let mut width = ui.available_width(); let mut width = ui.available_width();
@ -348,7 +343,9 @@ impl WalletsContent {
rect.set_width(width); rect.set_width(width);
ui.allocate_ui(rect.size(), |ui| { ui.allocate_ui(rect.size(), |ui| {
let list = self.wallets.list().clone(); let mut list = self.wallets.list().clone();
// Remove deleted wallet from the list.
list.retain(|w| !w.is_deleted());
for wallet in &list { for wallet in &list {
// Check if wallet reopen is needed. // Check if wallet reopen is needed.
if !wallet.is_open() && wallet.reopen_needed() { if !wallet.is_open() && wallet.reopen_needed() {
@ -479,7 +476,7 @@ impl WalletsContent {
} else { } else {
let tx_progress = wallet.txs_sync_progress(); let tx_progress = wallet.txs_sync_progress();
if tx_progress == 0 { if tx_progress == 0 {
t!("wallets.tx_loading") format!("{} {}", SPINNER, t!("wallets.tx_loading"))
} else { } else {
format!("{} {}: {}%", format!("{} {}: {}%",
SPINNER, SPINNER,

View file

@ -71,7 +71,7 @@ impl CommonSetup {
_: &mut eframe::Frame, _: &mut eframe::Frame,
wallet: &mut Wallet, wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
// Draw modal content for this ui container. // Show modal content for this ui container.
self.modal_content_ui(ui, wallet, cb); self.modal_content_ui(ui, wallet, cb);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
@ -142,7 +142,7 @@ impl CommonSetup {
}); });
} }
/// Draw modal content for current ui container. /// Draw [`Modal`] content for this ui container.
fn modal_content_ui(&mut self, fn modal_content_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &mut Wallet,

View file

@ -38,9 +38,6 @@ pub struct ConnectionSetup {
/// Flag to show URL format error. /// Flag to show URL format error.
ext_node_url_error: bool, ext_node_url_error: bool,
/// Flag to show closing progress at confirmation [`Modal`] to reopen the [`Wallet`].
show_closing_progress: bool,
/// [`Modal`] identifiers allowed at this ui container. /// [`Modal`] identifiers allowed at this ui container.
modal_ids: Vec<&'static str> modal_ids: Vec<&'static str>
} }
@ -59,7 +56,6 @@ impl Default for ConnectionSetup {
ext_node_url_edit: "".to_string(), ext_node_url_edit: "".to_string(),
ext_node_secret_edit: "".to_string(), ext_node_secret_edit: "".to_string(),
ext_node_url_error: false, ext_node_url_error: false,
show_closing_progress: false,
modal_ids: vec![ modal_ids: vec![
ADD_EXT_CONNECTION_MODAL ADD_EXT_CONNECTION_MODAL
] ]
@ -135,7 +131,11 @@ impl ConnectionSetup {
if changed { if changed {
wallet.config.save(); wallet.config.save();
self.show_reopen_confirmation_modal(); // Show reopen confirmation modal.
Modal::new(REOPEN_WALLET_CONFIRMATION_MODAL)
.position(ModalPosition::Center)
.title(t!("modal.confirmation"))
.show();
} }
} }
@ -423,36 +423,12 @@ impl ConnectionSetup {
}); });
} }
/// Show confirmation modal to reopen the [`Wallet`] after connection change.
fn show_reopen_confirmation_modal(&mut self,) {
self.show_closing_progress = false;
// Show modal.
Modal::new(REOPEN_WALLET_CONFIRMATION_MODAL)
.title(t!("modal.confirmation"))
.show();
}
/// Draw confirmation modal content to reopen the [`Wallet`]. /// Draw confirmation modal content to reopen the [`Wallet`].
fn reopen_modal_content(&mut self, fn reopen_modal_content(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &Wallet, wallet: &Wallet,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
if self.show_closing_progress {
if !wallet.is_closing() {
modal.close();
return;
}
ui.add_space(16.0);
ui.vertical_centered(|ui| {
View::small_loading_spinner(ui);
ui.add_space(12.0);
ui.label(RichText::new(t!("wallets.wallet_closing"))
.size(17.0)
.color(Colors::TEXT));
});
ui.add_space(10.0);
} else {
ui.add_space(8.0); ui.add_space(8.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("wallets.change_server_confirmation")) ui.label(RichText::new(t!("wallets.change_server_confirmation"))
@ -475,14 +451,13 @@ impl ConnectionSetup {
columns[1].vertical_centered_justified(|ui| { columns[1].vertical_centered_justified(|ui| {
View::button(ui, "OK".to_owned(), Colors::WHITE, || { View::button(ui, "OK".to_owned(), Colors::WHITE, || {
modal.disable_closing(); modal.disable_closing();
self.show_closing_progress = true;
wallet.set_reopen(true); wallet.set_reopen(true);
wallet.close(); wallet.close();
modal.close()
}); });
}); });
}); });
ui.add_space(6.0); ui.add_space(6.0);
}); });
} }
}
} }

View file

@ -12,10 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use egui::RichText; use egui::{Align, Id, Layout, RichText, TextStyle, Widget};
use grin_chain::SyncStatus; use grin_chain::SyncStatus;
use grin_util::ZeroingString;
use crate::gui::Colors; use crate::gui::Colors;
use crate::gui::icons::{EYE, STETHOSCOPE, TRASH, WRENCH}; use crate::gui::icons::{EYE, EYE_SLASH, STETHOSCOPE, TRASH, WRENCH};
use crate::gui::platform::PlatformCallbacks; use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, View}; use crate::gui::views::{Modal, View};
use crate::gui::views::types::ModalPosition; use crate::gui::views::types::ModalPosition;
@ -28,8 +30,11 @@ pub struct RecoverySetup {
pass_edit: String, pass_edit: String,
/// Flag to check if wrong password was entered. /// Flag to check if wrong password was entered.
wrong_pass: bool, wrong_pass: bool,
/// Flag to show recovery phrase when password check was passed. /// Flag to show/hide old password at [`egui::TextEdit`] field.
show_recovery_phrase: bool, hide_pass: bool,
/// Recovery phrase value.
recovery_phrase: Option<ZeroingString>,
} }
/// Identifier for recovery phrase [`Modal`]. /// Identifier for recovery phrase [`Modal`].
@ -41,8 +46,9 @@ impl Default for RecoverySetup {
fn default() -> Self { fn default() -> Self {
Self { Self {
wrong_pass: false, wrong_pass: false,
hide_pass: false,
pass_edit: "".to_string(), pass_edit: "".to_string(),
show_recovery_phrase: false, recovery_phrase: None,
} }
} }
} }
@ -53,7 +59,7 @@ impl RecoverySetup {
_: &mut eframe::Frame, _: &mut eframe::Frame,
wallet: &mut Wallet, wallet: &mut Wallet,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
// Draw modal content for this ui container. // Show modal content for this ui container.
self.modal_content_ui(ui, wallet, cb); self.modal_content_ui(ui, wallet, cb);
ui.add_space(10.0); ui.add_space(10.0);
@ -66,17 +72,12 @@ impl RecoverySetup {
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
let integrated_node = wallet.get_current_ext_conn_id().is_none(); let integrated_node = wallet.get_current_ext_conn_id().is_none();
let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync); let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
if wallet.sync_error() || (integrated_node && !integrated_node_ready) { if wallet.sync_error() || (integrated_node && !integrated_node_ready) {
ui.add_space(8.0); ui.add_space(6.0);
ui.label(RichText::new(t!("wallets.repair_unavailable")) ui.label(RichText::new(t!("wallets.repair_unavailable"))
.size(16.0) .size(16.0)
.color(Colors::RED)); .color(Colors::RED));
} else if wallet.is_repairing() { } else if !wallet.is_repairing() {
ui.add_space(8.0);
View::small_loading_spinner(ui);
ui.add_space(1.0);
} else {
ui.add_space(6.0); ui.add_space(6.0);
// Draw button to repair the wallet. // Draw button to repair the wallet.
let repair_text = format!("{} {}", STETHOSCOPE, t!("wallets.repair_wallet")); let repair_text = format!("{} {}", STETHOSCOPE, t!("wallets.repair_wallet"));
@ -94,49 +95,35 @@ impl RecoverySetup {
View::horizontal_line(ui, Colors::ITEM_STROKE); View::horizontal_line(ui, Colors::ITEM_STROKE);
ui.add_space(6.0); ui.add_space(6.0);
let recovery_phrase_text = format!("{}:", t!("wallets.recovery_phrase")); let recovery_text = format!("{}:", t!("wallets.recovery_phrase"));
ui.label(RichText::new(recovery_phrase_text).size(16.0).color(Colors::GRAY)); ui.label(RichText::new(recovery_text).size(16.0).color(Colors::GRAY));
ui.add_space(6.0); ui.add_space(6.0);
// Draw button to show recovery phrase. // Draw button to show recovery phrase.
let repair_text = format!("{} {}", EYE, t!("show")); let show_text = format!("{} {}", EYE, t!("show"));
View::button(ui, repair_text, Colors::BUTTON, || { View::button(ui, show_text, Colors::BUTTON, || {
// Setup modal values. self.show_recovery_phrase_modal(cb);
self.pass_edit = "".to_string();
self.wrong_pass = false;
self.show_recovery_phrase = false;
// Show recovery phrase modal.
Modal::new(RECOVERY_PHRASE_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.recovery_phrase"))
.show();
cb.show_keyboard();
}); });
ui.add_space(12.0); ui.add_space(12.0);
View::horizontal_line(ui, Colors::ITEM_STROKE); View::horizontal_line(ui, Colors::ITEM_STROKE);
ui.add_space(6.0); ui.add_space(6.0);
ui.label(RichText::new(t!("wallets.delete_desc")).size(16.0).color(Colors::GRAY)); ui.label(RichText::new(t!("wallets.delete_desc")).size(16.0).color(Colors::TEXT));
ui.add_space(6.0); ui.add_space(6.0);
// Draw button to delete the wallet. // Draw button to delete the wallet.
let delete_text = format!("{} {}", TRASH, t!("wallets.delete")); let delete_text = format!("{} {}", TRASH, t!("wallets.delete"));
View::button(ui, delete_text, Colors::GOLD, || { View::button(ui, delete_text, Colors::GOLD, || {
// Setup modal values.
self.pass_edit = "".to_string();
self.wrong_pass = false;
// Show wallet deletion confirmation modal.
Modal::new(DELETE_CONFIRMATION_MODAL) Modal::new(DELETE_CONFIRMATION_MODAL)
.position(ModalPosition::CenterTop) .position(ModalPosition::Center)
.title(t!("modal.confirmation")) .title(t!("modal.confirmation"))
.show(); .show();
cb.show_keyboard();
}); });
ui.add_space(8.0); ui.add_space(8.0);
}); });
} }
/// Draw modal content for current ui container. /// Draw [`Modal`] content for this ui container.
fn modal_content_ui(&mut self, fn modal_content_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &mut Wallet,
@ -152,7 +139,7 @@ impl RecoverySetup {
} }
DELETE_CONFIRMATION_MODAL => { DELETE_CONFIRMATION_MODAL => {
Modal::ui(ui.ctx(), |ui, modal| { Modal::ui(ui.ctx(), |ui, modal| {
self.delete_confirmation_modal_ui(ui, wallet, modal, cb); self.deletion_modal_ui(ui, wallet, modal, cb);
}); });
} }
_ => {} _ => {}
@ -161,21 +148,157 @@ impl RecoverySetup {
} }
} }
/// Show recovery phrase [`Modal`].
fn show_recovery_phrase_modal(&mut self, cb: &dyn PlatformCallbacks) {
// Setup modal values.
self.pass_edit = "".to_string();
self.wrong_pass = false;
self.hide_pass = true;
self.recovery_phrase = None;
// Show recovery phrase modal.
Modal::new(RECOVERY_PHRASE_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("wallets.recovery_phrase"))
.show();
cb.show_keyboard();
}
/// Draw recovery phrase [`Modal`] content. /// Draw recovery phrase [`Modal`] content.
fn recovery_phrase_modal_ui(&mut self, fn recovery_phrase_modal_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &mut Wallet,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
ui.add_space(6.0);
if self.recovery_phrase.is_some() {
ui.vertical_centered(|ui| {
ui.label(RichText::new(self.recovery_phrase.clone().unwrap().to_string())
.size(17.0)
.color(Colors::BLACK));
});
ui.add_space(6.0);
ui.vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::WHITE, || {
self.recovery_phrase = None;
modal.close();
});
});
} else {
ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("wallets.pass"))
.size(17.0)
.color(Colors::GRAY));
ui.add_space(6.0);
});
let mut rect = ui.available_rect_before_wrap();
rect.set_height(34.0);
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to show/hide current 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;
});
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
// Draw current wallet password text edit.
let pass_resp = egui::TextEdit::singleline(&mut self.pass_edit)
.id(Id::from(modal.id).with(wallet.config.id).with("recovery_phrase"))
.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();
}
pass_resp.request_focus();
});
});
// Show information when password is empty.
ui.vertical_centered(|ui| {
if self.pass_edit.is_empty() {
ui.add_space(8.0);
ui.label(RichText::new(t!("wallets.pass_empty"))
.size(17.0)
.color(Colors::INACTIVE_TEXT));
} else if self.wrong_pass {
ui.add_space(8.0);
ui.label(RichText::new(t!("wallets.wrong_pass"))
.size(17.0)
.color(Colors::RED));
}
ui.add_space(10.0);
});
// Show modal buttons.
ui.scope(|ui| {
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
self.recovery_phrase = None;
modal.close();
});
});
columns[1].vertical_centered_justified(|ui| {
View::button(ui, "OK".to_owned(), Colors::WHITE, || {
match wallet.get_recovery(self.pass_edit.clone()) {
Ok(phrase) => {
self.wrong_pass = false;
self.recovery_phrase = Some(phrase);
cb.hide_keyboard();
}
Err(_) => {
self.wrong_pass = true;
}
}
});
});
});
});
}
ui.add_space(6.0);
} }
/// Draw recovery phrase [`Modal`] content. /// Draw wallet deletion [`Modal`] content.
fn delete_confirmation_modal_ui(&mut self, fn deletion_modal_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &mut Wallet, wallet: &mut Wallet,
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
ui.add_space(8.0);
ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("wallets.delete_conf"))
.size(17.0)
.color(Colors::TEXT));
});
ui.add_space(10.0);
// Show modal buttons.
ui.scope(|ui| {
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
modal.close();
});
});
columns[1].vertical_centered_justified(|ui| {
View::button(ui, t!("delete"), Colors::WHITE, || {
modal.disable_closing();
wallet.set_reopen(true);
wallet.delete_wallet();
});
});
});
ui.add_space(6.0);
});
} }
} }

View file

@ -198,8 +198,8 @@ impl WalletContent {
let integrated_node = wallet.get_current_ext_conn_id().is_none(); let integrated_node = wallet.get_current_ext_conn_id().is_none();
let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync); let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
let sync_after_opening = wallet.get_data().is_none() && !wallet.sync_error(); let sync_after_opening = wallet.get_data().is_none() && !wallet.sync_error();
// Block navigation if wallet is repairing and integrated node is not launching, // Block navigation if wallet is repairing and integrated node is not launching
// or wallet is closing or syncing after opening when there is no data to show. // and if wallet is closing or syncing after opening when there is no data to show.
(wallet.is_repairing() && (integrated_node_ready || !integrated_node) && !sync_error) (wallet.is_repairing() && (integrated_node_ready || !integrated_node) && !sync_error)
|| wallet.is_closing() || (sync_after_opening && !integrated_node) || wallet.is_closing() || (sync_after_opening && !integrated_node)
} }

View file

@ -66,26 +66,40 @@ impl WalletList {
/// Get [`Wallet`] list for current [`ChainTypes`]. /// Get [`Wallet`] list for current [`ChainTypes`].
pub fn list(&self) -> &Vec<Wallet> { pub fn list(&self) -> &Vec<Wallet> {
let chain_type = AppConfig::chain_type(); if AppConfig::chain_type() == ChainTypes::Mainnet {
if chain_type == ChainTypes::Mainnet {
&self.main_list &self.main_list
} else { } else {
&self.test_list &self.test_list
} }
} }
/// Add created [`Wallet`] to the list. /// Get mutable [`Wallet`] list for current [`ChainTypes`].
pub fn add(&mut self, wallet: Wallet) { pub fn mut_list(&mut self) -> &mut Vec<Wallet> {
self.selected_id = Some(wallet.config.id); if AppConfig::chain_type() == ChainTypes::Mainnet {
let chain_type = AppConfig::chain_type();
let list = if chain_type == ChainTypes::Mainnet {
&mut self.main_list &mut self.main_list
} else { } else {
&mut self.test_list &mut self.test_list
}; }
}
/// Add created [`Wallet`] to the list.
pub fn add(&mut self, wallet: Wallet) {
self.selected_id = Some(wallet.config.id);
let list = self.mut_list();
list.insert(0, wallet); list.insert(0, wallet);
} }
/// Remove [`Wallet`] with provided identifier.
pub fn remove(&mut self, id: i64) {
let list = self.mut_list();
for (index, wallet) in list.iter().enumerate() {
if wallet.config.id == id {
list.remove(index);
return;
}
}
}
/// Select [`Wallet`] with provided identifier. /// Select [`Wallet`] with provided identifier.
pub fn select(&mut self, id: Option<i64>) { pub fn select(&mut self, id: Option<i64>) {
self.selected_id = id; self.selected_id = id;
@ -118,14 +132,9 @@ impl WalletList {
/// Open selected [`Wallet`]. /// Open selected [`Wallet`].
pub fn open_selected(&mut self, password: String) -> Result<(), Error> { pub fn open_selected(&mut self, password: String) -> Result<(), Error> {
let chain_type = AppConfig::chain_type(); let selected_id = self.selected_id.clone();
let list = if chain_type == ChainTypes::Mainnet { for w in self.mut_list() {
&mut self.main_list if Some(w.config.id) == selected_id {
} else {
&mut self.test_list
};
for w in list {
if Some(w.config.id) == self.selected_id {
return w.open(password.clone()); return w.open(password.clone());
} }
} }

View file

@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::{fs, thread};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, mpsc, RwLock}; use std::sync::{Arc, mpsc, RwLock};
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::thread;
use std::thread::Thread; use std::thread::Thread;
use std::time::Duration; use std::time::Duration;
@ -53,6 +53,8 @@ pub struct Wallet {
is_open: Arc<AtomicBool>, is_open: Arc<AtomicBool>,
/// Flag to check if wallet is loading. /// Flag to check if wallet is loading.
closing: Arc<AtomicBool>, closing: Arc<AtomicBool>,
/// Flag to check if wallet was deleted to remove it from list.
deleted: Arc<AtomicBool>,
/// Error on wallet loading. /// Error on wallet loading.
sync_error: Arc<AtomicBool>, sync_error: Arc<AtomicBool>,
@ -83,6 +85,7 @@ impl Wallet {
reopen: Arc::new(AtomicBool::new(false)), reopen: Arc::new(AtomicBool::new(false)),
is_open: Arc::from(AtomicBool::new(false)), is_open: Arc::from(AtomicBool::new(false)),
closing: Arc::new(AtomicBool::new(false)), closing: Arc::new(AtomicBool::new(false)),
deleted: Arc::new(AtomicBool::new(false)),
sync_error: Arc::from(AtomicBool::new(false)), sync_error: Arc::from(AtomicBool::new(false)),
info_sync_progress: Arc::from(AtomicU8::new(0)), info_sync_progress: Arc::from(AtomicU8::new(0)),
txs_sync_progress: Arc::from(AtomicU8::new(0)), txs_sync_progress: Arc::from(AtomicU8::new(0)),
@ -183,8 +186,8 @@ impl Wallet {
return Err(Error::GenericError("Already opened".to_string())); return Err(Error::GenericError("Already opened".to_string()));
} }
// Create new wallet instance if sync thread was stopped. // Create new wallet instance if sync thread was stopped or instance was not created.
if self.sync_thread.write().unwrap().is_none() { if self.sync_thread.write().unwrap().is_none() || self.instance.is_none() {
let new_instance = Self::create_wallet_instance(self.config.clone())?; let new_instance = Self::create_wallet_instance(self.config.clone())?;
self.instance = Some(new_instance); self.instance = Some(new_instance);
self.instance_ext_conn_id = self.config.ext_conn_id; self.instance_ext_conn_id = self.config.ext_conn_id;
@ -248,15 +251,11 @@ impl Wallet {
self.closing.store(true, Ordering::Relaxed); self.closing.store(true, Ordering::Relaxed);
// Close wallet at separate thread. // Close wallet at separate thread.
let mut wallet_close = self.clone(); let wallet_close = self.clone();
let instance = wallet_close.instance.clone().unwrap(); let instance = wallet_close.instance.clone().unwrap();
thread::spawn(move || { thread::spawn(move || {
// Close the wallet. // Close the wallet.
{ Self::close_wallet(&instance);
let mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider().unwrap();
let _ = lc.close_wallet(None);
}
// Mark wallet as not opened. // Mark wallet as not opened.
wallet_close.closing.store(false, Ordering::Relaxed); wallet_close.closing.store(false, Ordering::Relaxed);
@ -270,6 +269,13 @@ impl Wallet {
}); });
} }
/// Close wallet for provided [`WalletInstance`].
fn close_wallet(instance: &WalletInstance) {
let mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider().unwrap();
let _ = lc.close_wallet(None);
}
/// Set wallet reopen status. /// Set wallet reopen status.
pub fn set_reopen(&self, reopen: bool) { pub fn set_reopen(&self, reopen: bool) {
self.reopen.store(reopen, Ordering::Relaxed); self.reopen.store(reopen, Ordering::Relaxed);
@ -356,8 +362,50 @@ impl Wallet {
self.repair_progress.load(Ordering::Relaxed) self.repair_progress.load(Ordering::Relaxed)
} }
pub fn delete_wallet(&self) { /// Get recovery phrase.
pub fn get_recovery(&self, password: String) -> Result<ZeroingString, Error> {
let instance = self.instance.clone().unwrap();
let mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider().unwrap();
lc.get_mnemonic(None, ZeroingString::from(password))
}
/// Close the wallet, delete its files and mark it as deleted.
pub fn delete_wallet(&self) {
if !self.is_open() || self.instance.is_none() {
return;
}
self.closing.store(true, Ordering::Relaxed);
// Delete wallet at separate thread.
let wallet_delete = self.clone();
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());
}
// 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();
}
});
}
/// Check if wallet was deleted to remove it from list.
pub fn is_deleted(&self) -> bool {
self.deleted.load(Ordering::Relaxed)
} }
} }