wallet + ui: optimize wallet synchronization, add wallet repairing, fix wallet loading content, and wallet status, fix wallet settings panel width update translations
This commit is contained in:
parent
a0930d4bf3
commit
09d3835082
14 changed files with 1056 additions and 269 deletions
|
@ -2,11 +2,11 @@ copy: Copy
|
|||
paste: Paste
|
||||
continue: Continue
|
||||
complete: Complete
|
||||
closing: Closing
|
||||
loading: Loading
|
||||
loading_error: Loading error
|
||||
retry: Retry
|
||||
close: Close
|
||||
change: Change
|
||||
show: Show
|
||||
wallets:
|
||||
title: Wallets
|
||||
create_desc: Create or import existing wallet from saved recovery phrase.
|
||||
|
@ -14,9 +14,12 @@ wallets:
|
|||
name: 'Name:'
|
||||
pass: 'Password:'
|
||||
pass_empty: Enter password from the wallet
|
||||
current_pass: 'Current password:'
|
||||
new_pass: 'New password:'
|
||||
min_tx_conf_count: 'Minimum amount of confirmations for transactions:'
|
||||
create: Create
|
||||
recover: Restore
|
||||
saved_phrase: Saved phrase
|
||||
recovery_phrase: Recovery phrase
|
||||
words_count: 'Words count:'
|
||||
enter_word: 'Enter word #%{number}:'
|
||||
not_valid_word: Entered word is not valid
|
||||
|
@ -35,10 +38,22 @@ wallets:
|
|||
locked: Locked
|
||||
unlocked: Unlocked
|
||||
enable_node: 'Enable integrated node to use the wallet or change connection settings by selecting %{settings} at the bottom of the screen.'
|
||||
node_loading: 'Wallet will be loaded after integrated node synchronization, you can change connection settings by selecting %{settings} at the bottom of the screen.'
|
||||
loading: Loading
|
||||
closing: Closing
|
||||
checking: Checking
|
||||
wallet_loading: Loading wallet
|
||||
wallet_closing: Closing wallet
|
||||
wallet_checking: Checking wallet
|
||||
tx_loading: Loading transactions
|
||||
wallet_loading_err: 'An error occurred during loading the wallet, you can retry or change connection settings by selecting %{settings} at the bottom of the screen.'
|
||||
recovery: Recovery
|
||||
repair_wallet: Repair wallet
|
||||
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.
|
||||
delete: Delete wallet
|
||||
delete_conf: 'Are you sure you want to remove the wallet? Enter password to confirm:'
|
||||
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: Wallet
|
||||
send: Send
|
||||
receive: Receive
|
||||
|
|
|
@ -2,11 +2,11 @@ copy: Копировать
|
|||
paste: Вставить
|
||||
continue: Продолжить
|
||||
complete: Завершить
|
||||
closing: Закрывается
|
||||
loading: Загружается
|
||||
loading_error: Ошибка загрузки
|
||||
retry: Повторить
|
||||
close: Закрыть
|
||||
change: Изменить
|
||||
show: Показать
|
||||
wallets:
|
||||
title: Кошельки
|
||||
create_desc: Создайте или импортируйте существующий кошелёк из сохранённой фразы восстановления.
|
||||
|
@ -14,9 +14,12 @@ wallets:
|
|||
name: 'Название:'
|
||||
pass: 'Пароль:'
|
||||
pass_empty: Введите пароль от кошелька
|
||||
current_pass: 'Текущий пароль:'
|
||||
new_pass: 'Новый пароль:'
|
||||
min_tx_conf_count: 'Минимальное количество подтверждений для транзакций:'
|
||||
create: Создать
|
||||
recover: Восстановить
|
||||
saved_phrase: Сохранённая фраза
|
||||
recovery_phrase: Фраза восстановления
|
||||
words_count: 'Количество слов:'
|
||||
enter_word: 'Введите слово #%{number}:'
|
||||
not_valid_word: Введено недопустимое слово
|
||||
|
@ -35,10 +38,22 @@ wallets:
|
|||
locked: Заблокирован
|
||||
unlocked: Разблокирован
|
||||
enable_node: 'Чтобы использовать кошелёк, включите встроенный узел или измените настройки подключения, выбрав %{settings} внизу экрана.'
|
||||
node_loading: Кошелёк будет загружен после синхронизации встроенного узла, вы можете изменить настройки подключения, выбрав %{settings} внизу экрана.
|
||||
loading: Загружается
|
||||
closing: Закрывается
|
||||
checking: Проверяется
|
||||
wallet_loading: Загрузка кошелька
|
||||
wallet_closing: Закрытие кошелька
|
||||
wallet_checking: Проверка кошелька
|
||||
tx_loading: Загрузка транзакций
|
||||
wallet_loading_err: 'Во время загрузки кошелька произошла ошибка, вы можете повторить попытку или изменить настройки подключения, выбрав %{settings} внизу экрана.'
|
||||
recovery: Восстановление
|
||||
repair_wallet: Починить кошелёк
|
||||
repair_desc: Проверить кошелёк, исправляя и восстанавливая недостающие выходы, если это необходимо. Эта операция займёт время.
|
||||
repair_unavailable: Необходимо активное подключение к узлу и завершённая синхронизация кошелька.
|
||||
delete: Удалить кошелёк
|
||||
delete_conf: 'Вы уверены, что хотите удалить кошелек? Введите пароль для подтверждения:'
|
||||
delete_desc: Убедитесь, что вы сохранили вашу фразу восстановления, чтобы получить доступ к средствам в будущем.
|
||||
wallet_loading_err: 'Во время синхронизации кошелька произошла ошибка, вы можете повторить попытку или изменить настройки подключения, выбрав %{settings} внизу экрана.'
|
||||
wallet: Кошелёк
|
||||
send: Отправить
|
||||
receive: Получить
|
||||
|
|
|
@ -29,7 +29,7 @@ use crate::wallet::{ConnectionsConfig, ExternalConnection, Wallet, WalletList};
|
|||
|
||||
/// Wallets content.
|
||||
pub struct WalletsContent {
|
||||
/// Loaded list of wallets.
|
||||
/// List of wallets.
|
||||
wallets: WalletList,
|
||||
|
||||
/// Password to open wallet for [`Modal`].
|
||||
|
@ -415,8 +415,8 @@ impl WalletsContent {
|
|||
});
|
||||
}
|
||||
|
||||
// Show button to close opened wallet if wallet is not loading.
|
||||
if !wallet.is_closing() {
|
||||
// Show button to close opened wallet.
|
||||
if !wallet.is_closing() {
|
||||
View::item_button(ui, if !is_selected {
|
||||
Rounding::none()
|
||||
} else {
|
||||
|
@ -437,7 +437,7 @@ impl WalletsContent {
|
|||
View::ellipsize_text(ui, wallet.config.name.to_owned(), 18.0, name_color);
|
||||
|
||||
// Setup wallet connection text.
|
||||
let conn_text = if let Some(id) = wallet.config.ext_conn_id {
|
||||
let conn_text = if let Some(id) = wallet.get_current_ext_conn_id() {
|
||||
let ext_conn_url = match ConnectionsConfig::ext_conn(id) {
|
||||
None => ExternalConnection::DEFAULT_MAIN_URL.to_string(),
|
||||
Some(ext_conn) => ext_conn.url
|
||||
|
@ -451,13 +451,41 @@ impl WalletsContent {
|
|||
|
||||
// Setup wallet status text.
|
||||
let status_text = if wallet.is_open() {
|
||||
if wallet.is_closing() {
|
||||
format!("{} {}", SPINNER, t!("wallets.wallet_closing"))
|
||||
} else if wallet.get_data().is_none() {
|
||||
if wallet.load_error() {
|
||||
format!("{} {}", WARNING_CIRCLE, t!("loading_error"))
|
||||
if wallet.sync_error() {
|
||||
format!("{} {}", WARNING_CIRCLE, t!("loading_error"))
|
||||
} else if wallet.is_closing() {
|
||||
format!("{} {}", SPINNER, t!("wallets.closing"))
|
||||
} else if wallet.is_repairing() {
|
||||
let repair_progress = wallet.repairing_progress();
|
||||
if repair_progress == 0 {
|
||||
format!("{} {}", SPINNER, t!("wallets.checking"))
|
||||
} else {
|
||||
format!("{} {}", SPINNER, t!("loading"))
|
||||
format!("{} {}: {}%",
|
||||
SPINNER,
|
||||
t!("wallets.checking"),
|
||||
repair_progress)
|
||||
}
|
||||
} else if wallet.get_data().is_none() {
|
||||
let info_progress = wallet.info_sync_progress();
|
||||
if info_progress != 100 {
|
||||
if info_progress == 0 {
|
||||
format!("{} {}", SPINNER, t!("wallets.loading"))
|
||||
} else {
|
||||
format!("{} {}: {}%",
|
||||
SPINNER,
|
||||
t!("wallets.loading"),
|
||||
info_progress)
|
||||
}
|
||||
} else {
|
||||
let tx_progress = wallet.txs_sync_progress();
|
||||
if tx_progress == 0 {
|
||||
t!("wallets.tx_loading")
|
||||
} else {
|
||||
format!("{} {}: {}%",
|
||||
SPINNER,
|
||||
t!("wallets.tx_loading"),
|
||||
tx_progress)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
format!("{} {}", FOLDER_OPEN, t!("wallets.unlocked"))
|
||||
|
|
445
src/gui/views/wallets/setup/common.rs
Normal file
445
src/gui/views/wallets/setup/common.rs
Normal file
|
@ -0,0 +1,445 @@
|
|||
// 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::{Align, Id, Layout, RichText, TextStyle, Widget};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CLOCK_COUNTDOWN, EYE, EYE_SLASH, PASSWORD, PENCIL};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Common wallet setup content.
|
||||
pub struct CommonSetup {
|
||||
/// Wallet name [`Modal`] value.
|
||||
name_edit: String,
|
||||
|
||||
/// Flag to check if password change [`Modal`] was opened at first time to focus input field.
|
||||
first_edit_pass_opening: bool,
|
||||
/// Flag to check if wrong password was entered.
|
||||
wrong_pass: bool,
|
||||
/// Current wallet password [`Modal`] value.
|
||||
current_pass_edit: String,
|
||||
/// Flag to show/hide old password at [`egui::TextEdit`] field.
|
||||
hide_current_pass: bool,
|
||||
/// New wallet password [`Modal`] value.
|
||||
new_pass_edit: String,
|
||||
/// Flag to show/hide new password at [`egui::TextEdit`] field.
|
||||
hide_new_pass: bool,
|
||||
|
||||
/// Minimum confirmations number value.
|
||||
min_confirmations_edit: String
|
||||
}
|
||||
|
||||
/// Identifier for wallet name [`Modal`].
|
||||
const NAME_EDIT_MODAL: &'static str = "wallet_name_edit_modal";
|
||||
/// Identifier for wallet password [`Modal`].
|
||||
const PASS_EDIT_MODAL: &'static str = "wallet_pass_edit_modal";
|
||||
/// Identifier for minimum confirmations [`Modal`].
|
||||
const MIN_CONFIRMATIONS_EDIT_MODAL: &'static str = "wallet_min_conf_edit_modal";
|
||||
|
||||
impl Default for CommonSetup {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name_edit: "".to_string(),
|
||||
first_edit_pass_opening: true,
|
||||
wrong_pass: false,
|
||||
current_pass_edit: "".to_string(),
|
||||
hide_current_pass: true,
|
||||
new_pass_edit: "".to_string(),
|
||||
hide_new_pass: true,
|
||||
min_confirmations_edit: "".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonSetup {
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
_: &mut eframe::Frame,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Draw modal content for this ui container.
|
||||
self.modal_content_ui(ui, wallet, cb);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
// Show wallet name.
|
||||
ui.add_space(2.0);
|
||||
ui.label(RichText::new(t!("wallets.name")).size(16.0).color(Colors::GRAY));
|
||||
ui.add_space(2.0);
|
||||
ui.label(RichText::new(wallet.config.name.clone()).size(16.0).color(Colors::BLACK));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show wallet name setup.
|
||||
let name_text = format!("{} {}", PENCIL, t!("change"));
|
||||
View::button(ui, name_text, Colors::BUTTON, || {
|
||||
self.name_edit = wallet.config.name.clone();
|
||||
// Show wallet name modal.
|
||||
Modal::new(NAME_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
cb.show_keyboard();
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.pass")).size(16.0).color(Colors::GRAY));
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show wallet password setup.
|
||||
let pass_text = format!("{} {}", PASSWORD, t!("change"));
|
||||
View::button(ui, pass_text, Colors::BUTTON, || {
|
||||
// Setup modal values.
|
||||
self.first_edit_pass_opening = true;
|
||||
self.current_pass_edit = "".to_string();
|
||||
self.new_pass_edit = "".to_string();
|
||||
self.hide_current_pass = true;
|
||||
self.hide_new_pass = true;
|
||||
self.wrong_pass = false;
|
||||
// Show wallet password modal.
|
||||
Modal::new(PASS_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.wallet"))
|
||||
.show();
|
||||
cb.show_keyboard();
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.min_tx_conf_count")).size(16.0).color(Colors::GRAY));
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show minimum amount of confirmations value setup.
|
||||
let min_conf_text = format!("{} {}", CLOCK_COUNTDOWN, wallet.config.min_confirmations);
|
||||
View::button(ui, min_conf_text, Colors::BUTTON, || {
|
||||
self.min_confirmations_edit = wallet.config.min_confirmations.to_string();
|
||||
// Show minimum amount of confirmations value modal.
|
||||
Modal::new(MIN_CONFIRMATIONS_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
cb.show_keyboard();
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(4.0);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw modal content for current 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 {
|
||||
NAME_EDIT_MODAL => {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.name_modal_ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
PASS_EDIT_MODAL => {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.pass_modal_ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
MIN_CONFIRMATIONS_EDIT_MODAL => {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.min_conf_modal_ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw wallet name [`Modal`] content.
|
||||
fn name_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 wallet name edit.
|
||||
let text_edit_resp = egui::TextEdit::singleline(&mut self.name_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(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| {
|
||||
// Save button callback.
|
||||
let mut on_save = || {
|
||||
if !self.name_edit.is_empty() {
|
||||
wallet.config.name = self.name_edit.clone();
|
||||
wallet.config.save();
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
}
|
||||
};
|
||||
|
||||
View::on_enter_key(ui, || {
|
||||
(on_save)();
|
||||
});
|
||||
|
||||
View::button(ui, t!("modal.save"), Colors::WHITE, on_save);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw wallet pass [`Modal`] content.
|
||||
fn pass_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.current_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_current_pass { EYE } else { EYE_SLASH };
|
||||
View::button(ui, eye_icon.to_string(), Colors::WHITE, || {
|
||||
self.hide_current_pass = !self.hide_current_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 old_pass_resp = egui::TextEdit::singleline(&mut self.current_pass_edit)
|
||||
.id(Id::from(modal.id).with(wallet.config.id).with("old_pass"))
|
||||
.font(TextStyle::Heading)
|
||||
.desired_width(ui.available_width())
|
||||
.cursor_at_end(true)
|
||||
.password(self.hide_current_pass)
|
||||
.ui(ui);
|
||||
if old_pass_resp.clicked() {
|
||||
cb.show_keyboard();
|
||||
}
|
||||
|
||||
// Setup focus on input field on first modal opening.
|
||||
if self.first_edit_pass_opening {
|
||||
self.first_edit_pass_opening = false;
|
||||
old_pass_resp.request_focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.label(RichText::new(t!("wallets.new_pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::GRAY));
|
||||
ui.add_space(6.0);
|
||||
|
||||
let mut new_rect = ui.available_rect_before_wrap();
|
||||
new_rect.set_height(34.0);
|
||||
ui.allocate_ui_with_layout(new_rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to show/hide new password.
|
||||
let eye_icon = if self.hide_new_pass { EYE } else { EYE_SLASH };
|
||||
View::button(ui, eye_icon.to_string(), Colors::WHITE, || {
|
||||
self.hide_new_pass = !self.hide_new_pass;
|
||||
});
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
// Draw new wallet password text edit.
|
||||
let new_pass_resp = egui::TextEdit::singleline(&mut self.new_pass_edit)
|
||||
.id(Id::from(modal.id).with(wallet.config.id).with("new_pass"))
|
||||
.font(TextStyle::Heading)
|
||||
.desired_width(ui.available_width())
|
||||
.cursor_at_end(true)
|
||||
.password(self.hide_new_pass)
|
||||
.ui(ui);
|
||||
if new_pass_resp.clicked() {
|
||||
cb.show_keyboard();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Show information when password is empty.
|
||||
if self.current_pass_edit.is_empty() || self.new_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(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| {
|
||||
// Callback for button to continue.
|
||||
let mut on_continue = || {
|
||||
if self.new_pass_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
let old_pass = self.current_pass_edit.clone();
|
||||
let new_pass = self.new_pass_edit.clone();
|
||||
match wallet.change_password(old_pass, new_pass) {
|
||||
Ok(_) => {
|
||||
// Clear values.
|
||||
self.first_edit_pass_opening = true;
|
||||
self.current_pass_edit = "".to_string();
|
||||
self.new_pass_edit = "".to_string();
|
||||
self.hide_current_pass = true;
|
||||
self.hide_new_pass = true;
|
||||
self.wrong_pass = false;
|
||||
// Close modal.
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
}
|
||||
Err(_) => self.wrong_pass = true
|
||||
}
|
||||
};
|
||||
|
||||
// Continue on Enter key press.
|
||||
View::on_enter_key(ui, || {
|
||||
(on_continue)();
|
||||
});
|
||||
|
||||
View::button(ui, t!("change"), Colors::WHITE, on_continue);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw wallet name [`Modal`] content.
|
||||
fn min_conf_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.min_tx_conf_count"))
|
||||
.size(17.0)
|
||||
.color(Colors::GRAY));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Minimum amount of confirmations text edit.
|
||||
let text_edit_resp = egui::TextEdit::singleline(&mut self.min_confirmations_edit)
|
||||
.id(Id::from(modal.id))
|
||||
.font(TextStyle::Heading)
|
||||
.desired_width(48.0)
|
||||
.cursor_at_end(true)
|
||||
.ui(ui);
|
||||
text_edit_resp.request_focus();
|
||||
if text_edit_resp.clicked() {
|
||||
cb.show_keyboard();
|
||||
}
|
||||
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.min_confirmations_edit.parse::<u64>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
.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| {
|
||||
// Save button callback.
|
||||
let mut on_save = || {
|
||||
if let Ok(min_conf) = self.min_confirmations_edit.parse::<u64>() {
|
||||
wallet.config.min_confirmations = min_conf;
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
}
|
||||
};
|
||||
|
||||
View::on_enter_key(ui, || {
|
||||
(on_save)();
|
||||
});
|
||||
|
||||
View::button(ui, t!("modal.save"), Colors::WHITE, on_save);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ use egui::{Align, Id, Layout, RichText, Rounding, TextStyle, Widget};
|
|||
use url::Url;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK, CHECK_CIRCLE, CHECK_FAT, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE, GLOBE_SIMPLE, POWER, X_CIRCLE};
|
||||
use crate::gui::icons::{CHECK, CHECK_CIRCLE, CHECK_FAT, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE, PLUS_CIRCLE, POWER, X_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::views::types::{ModalContainer, ModalPosition};
|
||||
|
@ -164,7 +164,7 @@ impl ConnectionSetup {
|
|||
ui.add_space(6.0);
|
||||
|
||||
// Show button to add new external node connection.
|
||||
let add_node_text = format!("{} {}", GLOBE_SIMPLE, t!("wallets.add_node"));
|
||||
let add_node_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add_node"));
|
||||
View::button(ui, add_node_text, Colors::GOLD, || {
|
||||
self.show_add_ext_conn_modal(cb);
|
||||
});
|
||||
|
@ -203,16 +203,15 @@ impl ConnectionSetup {
|
|||
View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || {
|
||||
self.method = ConnectionMethod::Integrated;
|
||||
});
|
||||
} else {
|
||||
ui.add_space(14.0);
|
||||
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::GREEN));
|
||||
ui.add_space(14.0);
|
||||
}
|
||||
|
||||
if !Node::is_running() {
|
||||
// Draw button to start integrated node.
|
||||
let rounding = if is_current_method {
|
||||
View::item_rounding(0, 1, true)
|
||||
} else {
|
||||
Rounding::none()
|
||||
};
|
||||
View::item_button(ui, rounding, POWER, Some(Colors::GREEN), || {
|
||||
View::item_button(ui, Rounding::none(), POWER, Some(Colors::GREEN), || {
|
||||
Node::start();
|
||||
});
|
||||
}
|
||||
|
@ -273,7 +272,7 @@ impl ConnectionSetup {
|
|||
});
|
||||
} else {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::TITLE));
|
||||
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::GREEN));
|
||||
}
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
|
@ -285,7 +284,6 @@ impl ConnectionSetup {
|
|||
let conn_text = format!("{} {}", COMPUTER_TOWER, conn.url);
|
||||
View::ellipsize_text(ui, conn_text, 15.0, Colors::TITLE);
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup connection status text.
|
||||
let status_text = if let Some(available) = conn.available {
|
||||
if available {
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
// 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.
|
||||
|
||||
/// Main wallet setup content.
|
||||
pub struct MainSetup {
|
||||
|
||||
}
|
|
@ -15,8 +15,8 @@
|
|||
mod connection;
|
||||
pub use connection::ConnectionSetup;
|
||||
|
||||
mod main;
|
||||
pub use main::MainSetup;
|
||||
mod common;
|
||||
pub use common::CommonSetup;
|
||||
|
||||
mod recovery;
|
||||
pub use recovery::RecoverySetup;
|
|
@ -12,7 +12,170 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::RichText;
|
||||
use grin_chain::SyncStatus;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{EYE, STETHOSCOPE, TRASH, WRENCH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::node::Node;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Wallet recovery setup content.
|
||||
pub struct RecoverySetup {
|
||||
|
||||
/// Wallet password [`Modal`] value.
|
||||
pass_edit: String,
|
||||
/// Flag to check if wrong password was entered.
|
||||
wrong_pass: bool,
|
||||
/// Flag to show recovery phrase when password check was passed.
|
||||
show_recovery_phrase: bool,
|
||||
}
|
||||
|
||||
/// Identifier for recovery phrase [`Modal`].
|
||||
const RECOVERY_PHRASE_MODAL: &'static str = "recovery_phrase_modal";
|
||||
/// Identifier to confirm wallet deletion [`Modal`].
|
||||
const DELETE_CONFIRMATION_MODAL: &'static str = "delete_wallet_confirmation_modal";
|
||||
|
||||
impl Default for RecoverySetup {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
wrong_pass: false,
|
||||
pass_edit: "".to_string(),
|
||||
show_recovery_phrase: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RecoverySetup {
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
_: &mut eframe::Frame,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Draw modal content for this ui container.
|
||||
self.modal_content_ui(ui, wallet, cb);
|
||||
|
||||
ui.add_space(10.0);
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(6.0);
|
||||
View::sub_title(ui, format!("{} {}", WRENCH, t!("wallets.recovery")));
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(4.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
let integrated_node = wallet.get_current_ext_conn_id().is_none();
|
||||
let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
|
||||
|
||||
if wallet.sync_error() || (integrated_node && !integrated_node_ready) {
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new(t!("wallets.repair_unavailable"))
|
||||
.size(16.0)
|
||||
.color(Colors::RED));
|
||||
} 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);
|
||||
// Draw button to repair the wallet.
|
||||
let repair_text = format!("{} {}", STETHOSCOPE, t!("wallets.repair_wallet"));
|
||||
View::button(ui, repair_text, Colors::GOLD, || {
|
||||
wallet.repair();
|
||||
});
|
||||
}
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.repair_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT));
|
||||
|
||||
ui.add_space(6.0);
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let recovery_phrase_text = format!("{}:", t!("wallets.recovery_phrase"));
|
||||
ui.label(RichText::new(recovery_phrase_text).size(16.0).color(Colors::GRAY));
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw button to show recovery phrase.
|
||||
let repair_text = format!("{} {}", EYE, t!("show"));
|
||||
View::button(ui, repair_text, Colors::BUTTON, || {
|
||||
// Setup modal values.
|
||||
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);
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.delete_desc")).size(16.0).color(Colors::GRAY));
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw button to delete the wallet.
|
||||
let delete_text = format!("{} {}", TRASH, t!("wallets.delete"));
|
||||
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)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("modal.confirmation"))
|
||||
.show();
|
||||
cb.show_keyboard();
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw modal content for current 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 {
|
||||
RECOVERY_PHRASE_MODAL => {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.recovery_phrase_modal_ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
DELETE_CONFIRMATION_MODAL => {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.delete_confirmation_modal_ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw recovery phrase [`Modal`] content.
|
||||
fn recovery_phrase_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
|
||||
}
|
||||
|
||||
/// Draw recovery phrase [`Modal`] content.
|
||||
fn delete_confirmation_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
use std::time::Duration;
|
||||
|
||||
use egui::{Margin, RichText, ScrollArea};
|
||||
use egui::{Margin, RichText};
|
||||
use grin_chain::SyncStatus;
|
||||
|
||||
use crate::AppConfig;
|
||||
|
@ -30,7 +30,7 @@ use crate::wallet::Wallet;
|
|||
/// Selected and opened wallet content.
|
||||
pub struct WalletContent {
|
||||
/// Current tab content to show.
|
||||
current_tab: Box<dyn WalletTab>,
|
||||
pub current_tab: Box<dyn WalletTab>,
|
||||
}
|
||||
|
||||
impl Default for WalletContent {
|
||||
|
@ -46,8 +46,6 @@ impl WalletContent {
|
|||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Show wallet tabs panel.
|
||||
let not_show_tabs =
|
||||
wallet.is_closing() || (wallet.get_data().is_none() && !wallet.load_error());
|
||||
egui::TopBottomPanel::bottom("wallet_tabs")
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::FILL,
|
||||
|
@ -59,7 +57,7 @@ impl WalletContent {
|
|||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, !not_show_tabs, |ui| {
|
||||
.show_animated_inside(ui, !Self::block_navigation_on_sync(wallet), |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
// Setup tabs width.
|
||||
let available_width = ui.available_width();
|
||||
|
@ -85,38 +83,16 @@ impl WalletContent {
|
|||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui) + 4.0,
|
||||
right: View::get_right_inset() + 4.0,
|
||||
top: 4.0,
|
||||
top: 3.0,
|
||||
bottom: 4.0,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
let scroll_id = format!("wallet_tab_{}_{}",
|
||||
wallet.config.id,
|
||||
self.current_tab.get_type().id());
|
||||
ScrollArea::vertical()
|
||||
.id_source(scroll_id)
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
// Setup tab content width.
|
||||
let available_width = ui.available_width();
|
||||
if available_width == 0.0 {
|
||||
return;
|
||||
}
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
let width = f32::min(available_width, Root::SIDE_PANEL_WIDTH * 1.3);
|
||||
rect.set_width(width);
|
||||
|
||||
// Draw current tab content.
|
||||
ui.allocate_ui(rect.size(), |ui| {
|
||||
self.current_tab.ui(ui, frame, wallet, cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
self.current_tab.ui(ui, frame, wallet, cb);
|
||||
});
|
||||
|
||||
// Refresh content after 1 second for loaded wallet.
|
||||
// Refresh content after 1 second for synced wallet.
|
||||
if wallet.get_data().is_some() {
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
||||
} else {
|
||||
|
@ -159,12 +135,15 @@ impl WalletContent {
|
|||
});
|
||||
}
|
||||
|
||||
/// Content to draw when wallet is loading, returns `true` if wallet is not ready.
|
||||
pub fn loading_ui(ui: &mut egui::Ui, frame: &mut eframe::Frame, wallet: &Wallet) -> bool {
|
||||
if wallet.is_closing() {
|
||||
Self::loading_progress_ui(ui, wallet);
|
||||
/// Draw content when wallet is syncing and not ready to use, returns `true` at this case.
|
||||
pub fn sync_ui(ui: &mut egui::Ui, frame: &mut eframe::Frame, wallet: &Wallet) -> bool {
|
||||
if wallet.is_repairing() && !wallet.sync_error() {
|
||||
Self::sync_progress_ui(ui, wallet);
|
||||
return true;
|
||||
} else if wallet.config.ext_conn_id.is_none() {
|
||||
} else if wallet.is_closing() {
|
||||
Self::sync_progress_ui(ui, wallet);
|
||||
return true;
|
||||
} else if wallet.get_current_ext_conn_id().is_none() {
|
||||
if !Node::is_running() || Node::is_stopping() {
|
||||
let dual_panel_root = Root::is_dual_panel_mode(frame);
|
||||
View::center_content(ui, 108.0, |ui| {
|
||||
|
@ -182,48 +161,70 @@ impl WalletContent {
|
|||
}
|
||||
});
|
||||
return true
|
||||
} else if wallet.load_error()
|
||||
} else if wallet.sync_error()
|
||||
&& Node::get_sync_status() == Some(SyncStatus::NoSync) {
|
||||
Self::loading_error_ui(ui, wallet);
|
||||
Self::sync_error_ui(ui, wallet);
|
||||
return true;
|
||||
} else if wallet.get_data().is_none() {
|
||||
Self::loading_progress_ui(ui, wallet);
|
||||
Self::sync_progress_ui(ui, wallet);
|
||||
return true;
|
||||
}
|
||||
} else if wallet.sync_error() {
|
||||
Self::sync_error_ui(ui, wallet);
|
||||
return true;
|
||||
} else if wallet.get_data().is_none() {
|
||||
if wallet.load_error() {
|
||||
Self::loading_error_ui(ui, wallet);
|
||||
} else {
|
||||
Self::loading_progress_ui(ui, wallet);
|
||||
}
|
||||
Self::sync_progress_ui(ui, wallet);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Draw wallet loading error content.
|
||||
fn loading_error_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
/// Draw wallet sync error content.
|
||||
fn sync_error_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
View::center_content(ui, 108.0, |ui| {
|
||||
let text = t!("wallets.wallet_loading_err", "settings" => GEAR_FINE);
|
||||
ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT));
|
||||
ui.add_space(8.0);
|
||||
let retry_text = format!("{} {}", REPEAT, t!("retry"));
|
||||
View::button(ui, retry_text, Colors::GOLD, || {
|
||||
wallet.set_load_error(false);
|
||||
wallet.retry_sync();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw wallet loading progress content.
|
||||
pub fn loading_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
/// Check when to block tabs navigation on sync progress.
|
||||
pub fn block_navigation_on_sync(wallet: &Wallet) -> bool {
|
||||
let sync_error = wallet.sync_error();
|
||||
let integrated_node = wallet.get_current_ext_conn_id().is_none();
|
||||
let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
|
||||
let sync_after_opening = wallet.get_data().is_none() && !wallet.sync_error();
|
||||
// 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.
|
||||
(wallet.is_repairing() && (integrated_node_ready || !integrated_node) && !sync_error)
|
||||
|| wallet.is_closing() || (sync_after_opening && !integrated_node)
|
||||
}
|
||||
|
||||
/// Draw wallet sync progress content.
|
||||
pub fn sync_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
View::center_content(ui, 162.0, |ui| {
|
||||
View::big_loading_spinner(ui);
|
||||
ui.add_space(18.0);
|
||||
// Setup loading progress text.
|
||||
// Setup sync progress text.
|
||||
let text = {
|
||||
let info_progress = wallet.info_load_progress();
|
||||
let integrated_node = wallet.get_current_ext_conn_id().is_none();
|
||||
let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
|
||||
let info_progress = wallet.info_sync_progress();
|
||||
if wallet.is_closing() {
|
||||
t!("wallets.wallet_closing")
|
||||
} else if integrated_node && !integrated_node_ready {
|
||||
t!("wallets.node_loading", "settings" => GEAR_FINE)
|
||||
} else if wallet.is_repairing() {
|
||||
let repair_progress = wallet.repairing_progress();
|
||||
if repair_progress == 0 {
|
||||
t!("wallets.wallet_checking")
|
||||
} else {
|
||||
format!("{}: {}%", t!("wallets.wallet_checking"), repair_progress)
|
||||
}
|
||||
} else if info_progress != 100 {
|
||||
if info_progress == 0 {
|
||||
t!("wallets.wallet_loading")
|
||||
|
@ -231,7 +232,7 @@ impl WalletContent {
|
|||
format!("{}: {}%", t!("wallets.wallet_loading"), info_progress)
|
||||
}
|
||||
} else {
|
||||
let tx_progress = wallet.txs_load_progress();
|
||||
let tx_progress = wallet.txs_sync_progress();
|
||||
if tx_progress == 0 {
|
||||
t!("wallets.tx_loading")
|
||||
} else {
|
||||
|
|
|
@ -32,7 +32,7 @@ impl WalletTab for WalletInfo {
|
|||
frame: &mut eframe::Frame,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
if WalletContent::loading_ui(ui, frame, wallet) {
|
||||
if WalletContent::sync_ui(ui, frame, wallet) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ impl WalletTab for WalletReceive {
|
|||
frame: &mut eframe::Frame,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
if WalletContent::loading_ui(ui, frame, wallet) {
|
||||
if WalletContent::sync_ui(ui, frame, wallet) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ impl WalletTab for WalletSend {
|
|||
frame: &mut eframe::Frame,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
if WalletContent::loading_ui(ui, frame, wallet) {
|
||||
if WalletContent::sync_ui(ui, frame, wallet) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,24 +12,33 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Id, ScrollArea};
|
||||
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::wallets::setup::ConnectionSetup;
|
||||
use crate::gui::views::Root;
|
||||
use crate::gui::views::wallets::setup::{CommonSetup, ConnectionSetup, RecoverySetup};
|
||||
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
|
||||
use crate::gui::views::wallets::wallet::WalletContent;
|
||||
use crate::wallet::{ExternalConnection, Wallet};
|
||||
|
||||
/// Wallet settings tab content.
|
||||
pub struct WalletSettings {
|
||||
/// Common setup content.
|
||||
common_setup: CommonSetup,
|
||||
/// Connection setup content.
|
||||
conn_setup: ConnectionSetup
|
||||
conn_setup: ConnectionSetup,
|
||||
/// Recovery setup content.
|
||||
recovery_setup: RecoverySetup
|
||||
}
|
||||
|
||||
impl Default for WalletSettings {
|
||||
fn default() -> Self {
|
||||
// Check external connections availability on first tab opening.
|
||||
// Check external connections availability on opening.
|
||||
ExternalConnection::start_ext_conn_availability_check();
|
||||
Self {
|
||||
common_setup: CommonSetup::default(),
|
||||
conn_setup: ConnectionSetup::default(),
|
||||
recovery_setup: RecoverySetup::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,12 +53,36 @@ impl WalletTab for WalletSettings {
|
|||
frame: &mut eframe::Frame,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Show progress if wallet is loading after opening without an error.
|
||||
if wallet.is_closing() || (wallet.get_data().is_none() && !wallet.load_error()) {
|
||||
WalletContent::loading_progress_ui(ui, wallet);
|
||||
// Show loading progress if navigation is blocked.
|
||||
if WalletContent::block_navigation_on_sync(wallet) {
|
||||
WalletContent::sync_progress_ui(ui, wallet);
|
||||
return;
|
||||
}
|
||||
|
||||
self.conn_setup.wallet_ui(ui, frame, wallet, cb);
|
||||
ScrollArea::vertical()
|
||||
.id_source(Id::from("wallet_settings_scroll").with(wallet.config.id))
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
// Setup tab content width.
|
||||
let available_width = ui.available_width();
|
||||
if available_width == 0.0 {
|
||||
return;
|
||||
}
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
let width = f32::min(available_width, Root::SIDE_PANEL_WIDTH * 1.3);
|
||||
rect.set_width(width);
|
||||
|
||||
// Draw current tab content.
|
||||
ui.allocate_ui(rect.size(), |ui| {
|
||||
// Show common wallet setup.
|
||||
self.common_setup.ui(ui, frame, wallet, cb);
|
||||
// Show wallet connections setup.
|
||||
self.conn_setup.wallet_ui(ui, frame, wallet, cb);
|
||||
// Show wallet recovery setup.
|
||||
self.recovery_setup.ui(ui, frame, wallet, cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ use grin_core::global;
|
|||
use grin_keychain::{ExtKeychain, Keychain};
|
||||
use grin_util::secp::SecretKey;
|
||||
use grin_util::types::ZeroingString;
|
||||
use grin_wallet_api::Owner;
|
||||
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient};
|
||||
use grin_wallet_libwallet::{Error, NodeClient, StatusMessage, WalletInst, WalletLCProvider};
|
||||
use grin_wallet_libwallet::api_impl::owner::{retrieve_summary_info, retrieve_txs};
|
||||
|
@ -36,13 +37,15 @@ use crate::wallet::types::{ConnectionMethod, WalletData, WalletInstance};
|
|||
/// Contains wallet instance, configuration and state, handles wallet commands.
|
||||
#[derive(Clone)]
|
||||
pub struct Wallet {
|
||||
/// Wallet instance, initializing on wallet opening and clearing on wallet closing.
|
||||
instance: Option<WalletInstance>,
|
||||
/// Wallet configuration.
|
||||
pub config: WalletConfig,
|
||||
/// Wallet instance, initializing on wallet opening and clearing on wallet closing.
|
||||
instance: Option<WalletInstance>,
|
||||
/// [`WalletInstance`] external connection id applied after opening.
|
||||
instance_ext_conn_id: Option<i64>,
|
||||
|
||||
/// Wallet updating thread.
|
||||
thread: Arc<RwLock<Option<Thread>>>,
|
||||
/// Wallet sync thread.
|
||||
sync_thread: Arc<RwLock<Option<Thread>>>,
|
||||
|
||||
/// Flag to check if wallet reopening is needed.
|
||||
reopen: Arc<AtomicBool>,
|
||||
|
@ -52,33 +55,41 @@ pub struct Wallet {
|
|||
closing: Arc<AtomicBool>,
|
||||
|
||||
/// Error on wallet loading.
|
||||
load_error: Arc<AtomicBool>,
|
||||
/// Info loading progress in percents
|
||||
info_load_progress: Arc<AtomicU8>,
|
||||
/// Transactions loading progress in percents
|
||||
txs_load_progress: Arc<AtomicU8>,
|
||||
sync_error: Arc<AtomicBool>,
|
||||
/// Info loading progress in percents.
|
||||
info_sync_progress: Arc<AtomicU8>,
|
||||
/// Transactions loading progress in percents.
|
||||
txs_sync_progress: Arc<AtomicU8>,
|
||||
|
||||
/// Wallet data.
|
||||
data: Arc<RwLock<Option<WalletData>>>,
|
||||
/// Attempts amount to update wallet data.
|
||||
data_update_attempts: Arc<AtomicU8>
|
||||
sync_attempts: Arc<AtomicU8>,
|
||||
|
||||
/// Flag to check if wallet repairing and restoring missing outputs is needed.
|
||||
repair_needed: Arc<AtomicBool>,
|
||||
/// Wallet repair progress in percents.
|
||||
repair_progress: Arc<AtomicU8>
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
/// Create new [`Wallet`] instance with provided [`WalletConfig`].
|
||||
fn new(config: WalletConfig) -> Self {
|
||||
Self {
|
||||
instance: None,
|
||||
config,
|
||||
thread: Arc::from(RwLock::new(None)),
|
||||
instance: None,
|
||||
instance_ext_conn_id: None,
|
||||
sync_thread: Arc::from(RwLock::new(None)),
|
||||
reopen: Arc::new(AtomicBool::new(false)),
|
||||
is_open: Arc::from(AtomicBool::new(false)),
|
||||
closing: Arc::new(AtomicBool::new(false)),
|
||||
load_error: Arc::from(AtomicBool::new(false)),
|
||||
info_load_progress: Arc::from(AtomicU8::new(0)),
|
||||
txs_load_progress: Arc::from(AtomicU8::new(0)),
|
||||
sync_error: Arc::from(AtomicBool::new(false)),
|
||||
info_sync_progress: Arc::from(AtomicU8::new(0)),
|
||||
txs_sync_progress: Arc::from(AtomicU8::new(0)),
|
||||
data: Arc::from(RwLock::new(None)),
|
||||
data_update_attempts: Arc::new(AtomicU8::new(0))
|
||||
sync_attempts: Arc::new(AtomicU8::new(0)),
|
||||
repair_needed: Arc::new(AtomicBool::new(false)),
|
||||
repair_progress: Arc::new(AtomicU8::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,26 +177,34 @@ impl Wallet {
|
|||
Ok(Arc::new(Mutex::new(wallet)))
|
||||
}
|
||||
|
||||
/// Open the wallet and start update the data at separate thread.
|
||||
/// Open the wallet and start [`WalletData`] sync at separate thread.
|
||||
pub fn open(&mut self, password: String) -> Result<(), Error> {
|
||||
if self.is_open() {
|
||||
return Err(Error::GenericError("Already opened".to_string()));
|
||||
}
|
||||
|
||||
// Create new wallet instance.
|
||||
let instance = Self::create_wallet_instance(self.config.clone())?;
|
||||
self.instance = Some(instance.clone());
|
||||
// Create new wallet instance if sync thread was stopped.
|
||||
if self.sync_thread.write().unwrap().is_none() {
|
||||
let new_instance = Self::create_wallet_instance(self.config.clone())?;
|
||||
self.instance = Some(new_instance);
|
||||
self.instance_ext_conn_id = self.config.ext_conn_id;
|
||||
}
|
||||
|
||||
// Open the wallet.
|
||||
let instance = self.instance.clone().unwrap();
|
||||
let mut wallet_lock = instance.lock();
|
||||
let lc = wallet_lock.lc_provider()?;
|
||||
match lc.open_wallet(None, ZeroingString::from(password), false, false) {
|
||||
Ok(keychain) => {
|
||||
// Start data updating if thread was not launched.
|
||||
let mut thread_w = self.thread.write().unwrap();
|
||||
// Reset an error on opening.
|
||||
self.set_sync_error(false);
|
||||
self.reset_sync_attempts();
|
||||
|
||||
// Start new synchronization thread or wake up existing one.
|
||||
let mut thread_w = self.sync_thread.write().unwrap();
|
||||
if thread_w.is_none() {
|
||||
println!("create new thread");
|
||||
let thread = start_wallet(self.clone(), keychain.clone());
|
||||
// Start wallet synchronization.
|
||||
let thread = start_sync(self.clone(), keychain.clone());
|
||||
*thread_w = Some(thread);
|
||||
} else {
|
||||
println!("unfreeze thread");
|
||||
|
@ -201,51 +220,14 @@ impl Wallet {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Set wallet reopen status.
|
||||
pub fn set_reopen(&self, reopen: bool) {
|
||||
self.reopen.store(reopen, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Check if wallet reopen is needed.
|
||||
pub fn reopen_needed(&self) -> bool {
|
||||
self.reopen.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Get wallet transactions loading progress.
|
||||
pub fn txs_load_progress(&self) -> u8 {
|
||||
self.txs_load_progress.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Get wallet info loading progress.
|
||||
pub fn info_load_progress(&self) -> u8 {
|
||||
self.info_load_progress.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Check if wallet had an error on loading.
|
||||
pub fn load_error(&self) -> bool {
|
||||
self.load_error.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Set an error for wallet on loading.
|
||||
pub fn set_load_error(&self, error: bool) {
|
||||
self.load_error.store(error, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Get wallet data update attempts.
|
||||
fn get_data_update_attempts(&self) -> u8 {
|
||||
self.data_update_attempts.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Increment wallet data update attempts.
|
||||
fn increment_data_update_attempts(&self) {
|
||||
let mut attempts = self.get_data_update_attempts();
|
||||
attempts += 1;
|
||||
self.data_update_attempts.store(attempts, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Reset wallet data update attempts.
|
||||
fn reset_data_update_attempts(&self) {
|
||||
self.data_update_attempts.store(0, Ordering::Relaxed);
|
||||
/// Get current external connection id applied to [`WalletInstance`]
|
||||
/// after opening if sync is running or take it from configuration.
|
||||
pub fn get_current_ext_conn_id(&self) -> Option<i64> {
|
||||
if self.sync_thread.read().unwrap().is_some() {
|
||||
self.instance_ext_conn_id
|
||||
} else {
|
||||
self.config.ext_conn_id
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if wallet was open.
|
||||
|
@ -270,33 +252,76 @@ impl Wallet {
|
|||
let instance = wallet_close.instance.clone().unwrap();
|
||||
thread::spawn(move || {
|
||||
// Close the wallet.
|
||||
let mut wallet_lock = instance.lock();
|
||||
let lc = wallet_lock.lc_provider().unwrap();
|
||||
let _ = lc.close_wallet(None);
|
||||
wallet_close.instance = None;
|
||||
|
||||
// Clear wallet info.
|
||||
let mut w_data = wallet_close.data.write().unwrap();
|
||||
*w_data = None;
|
||||
|
||||
// Reset wallet loading values.
|
||||
wallet_close.info_load_progress.store(0, Ordering::Relaxed);
|
||||
wallet_close.txs_load_progress.store(0, Ordering::Relaxed);
|
||||
wallet_close.set_load_error(false);
|
||||
wallet_close.reset_data_update_attempts();
|
||||
{
|
||||
let mut wallet_lock = instance.lock();
|
||||
let lc = wallet_lock.lc_provider().unwrap();
|
||||
let _ = lc.close_wallet(None);
|
||||
}
|
||||
|
||||
// Mark wallet as not opened.
|
||||
wallet_close.closing.store(false, Ordering::Relaxed);
|
||||
wallet_close.is_open.store(false, Ordering::Relaxed);
|
||||
|
||||
// Wake up wallet thread.
|
||||
let thread_r = wallet_close.thread.read().unwrap();
|
||||
let thread_r = wallet_close.sync_thread.read().unwrap();
|
||||
if let Some(thread) = thread_r.as_ref() {
|
||||
thread.unpark();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Set wallet reopen status.
|
||||
pub fn set_reopen(&self, reopen: bool) {
|
||||
self.reopen.store(reopen, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Check if wallet reopen is needed.
|
||||
pub fn reopen_needed(&self) -> bool {
|
||||
self.reopen.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Get wallet transactions synchronization progress.
|
||||
pub fn txs_sync_progress(&self) -> u8 {
|
||||
self.txs_sync_progress.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Get wallet info synchronization progress.
|
||||
pub fn info_sync_progress(&self) -> u8 {
|
||||
self.info_sync_progress.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Check if wallet had an error on synchronization.
|
||||
pub fn sync_error(&self) -> bool {
|
||||
self.sync_error.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Retry synchronization on error.
|
||||
pub fn retry_sync(&self) {
|
||||
self.set_sync_error(false);
|
||||
}
|
||||
|
||||
/// Set an error for wallet on synchronization.
|
||||
fn set_sync_error(&self, error: bool) {
|
||||
self.sync_error.store(error, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Get current wallet synchronization attempts before setting an error.
|
||||
fn get_sync_attempts(&self) -> u8 {
|
||||
self.sync_attempts.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Increment wallet synchronization attempts before setting an error.
|
||||
fn increment_sync_attempts(&self) {
|
||||
let mut attempts = self.get_sync_attempts();
|
||||
attempts += 1;
|
||||
self.sync_attempts.store(attempts, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Reset wallet synchronization attempts.
|
||||
fn reset_sync_attempts(&self) {
|
||||
self.sync_attempts.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Get wallet data.
|
||||
pub fn get_data(&self) -> Option<WalletData> {
|
||||
let r_data = self.data.read().unwrap();
|
||||
|
@ -310,32 +335,66 @@ impl Wallet {
|
|||
let lc = wallet_lock.lc_provider()?;
|
||||
lc.change_password(None, ZeroingString::from(old), ZeroingString::from(new))
|
||||
}
|
||||
|
||||
/// 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();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if wallet is repairing.
|
||||
pub fn is_repairing(&self) -> bool {
|
||||
self.repair_needed.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Get wallet repairing progress.
|
||||
pub fn repairing_progress(&self) -> u8 {
|
||||
self.repair_progress.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn delete_wallet(&self) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Delay in seconds to update wallet data every minute as average block time.
|
||||
const DATA_UPDATE_DELAY: Duration = Duration::from_millis(60 * 1000);
|
||||
/// Delay in seconds to sync [`WalletData`] (60 seconds as average block time).
|
||||
const SYNC_DELAY: Duration = Duration::from_millis(60 * 1000);
|
||||
|
||||
/// Number of attempts to update data after wallet opening before setting an error.
|
||||
const DATA_UPDATE_ATTEMPTS: u8 = 10;
|
||||
/// Number of attempts to sync [`WalletData`] before setting an error.
|
||||
const SYNC_ATTEMPTS: u8 = 10;
|
||||
|
||||
/// Launch thread to update wallet data.
|
||||
fn start_wallet(wallet: Wallet, keychain: Option<SecretKey>) -> Thread {
|
||||
let wallet_update = wallet.clone();
|
||||
/// Launch thread to sync wallet data from node.
|
||||
fn start_sync(wallet: Wallet, keychain: Option<SecretKey>) -> Thread {
|
||||
// Reset progress values.
|
||||
wallet.info_sync_progress.store(0, Ordering::Relaxed);
|
||||
wallet.txs_sync_progress.store(0, Ordering::Relaxed);
|
||||
wallet.repair_progress.store(0, Ordering::Relaxed);
|
||||
|
||||
println!("create new thread");
|
||||
thread::spawn(move || loop {
|
||||
println!("start new cycle");
|
||||
// Stop updating if wallet was closed.
|
||||
if !wallet_update.is_open() {
|
||||
// Stop syncing if wallet was closed.
|
||||
if !wallet.is_open() {
|
||||
println!("finishing thread at start");
|
||||
let mut thread_w = wallet_update.thread.write().unwrap();
|
||||
// Clear thread instance.
|
||||
let mut thread_w = wallet.sync_thread.write().unwrap();
|
||||
*thread_w = None;
|
||||
|
||||
// Clear wallet info.
|
||||
let mut w_data = wallet.data.write().unwrap();
|
||||
*w_data = None;
|
||||
println!("finish at start complete");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set an error when required integrated node is not enabled and
|
||||
// skip next cycle of update when node sync is not finished.
|
||||
if wallet_update.config.ext_conn_id.is_none() {
|
||||
wallet_update.set_load_error(!Node::is_running() || Node::is_stopping());
|
||||
// Set an error when required integrated node is not enabled
|
||||
// and skip cycle when node sync is not finished.
|
||||
if wallet.get_current_ext_conn_id().is_none() {
|
||||
wallet.set_sync_error(!Node::is_running() || Node::is_stopping());
|
||||
if !Node::is_running() || Node::get_sync_status() != Some(SyncStatus::NoSync) {
|
||||
println!("integrated node wait");
|
||||
thread::park_timeout(Duration::from_millis(1000));
|
||||
|
@ -343,51 +402,60 @@ fn start_wallet(wallet: Wallet, keychain: Option<SecretKey>) -> Thread {
|
|||
}
|
||||
}
|
||||
|
||||
// Update wallet data if there is no error.
|
||||
if !wallet_update.load_error() {
|
||||
update_wallet_data(&wallet_update, keychain.clone());
|
||||
// Scan outputs if repair is needed or sync data if there is no error.
|
||||
if !wallet.sync_error() {
|
||||
if wallet.is_repairing() {
|
||||
scan_wallet(&wallet, keychain.clone())
|
||||
} else {
|
||||
sync_wallet_data(&wallet, keychain.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Stop updating if wallet was closed.
|
||||
if !wallet_update.is_open() {
|
||||
// Stop sync if wallet was closed.
|
||||
if !wallet.is_open() {
|
||||
println!("finishing thread after updating");
|
||||
let mut thread_w = wallet_update.thread.write().unwrap();
|
||||
// Clear thread instance.
|
||||
let mut thread_w = wallet.sync_thread.write().unwrap();
|
||||
*thread_w = None;
|
||||
|
||||
// Clear wallet info.
|
||||
let mut w_data = wallet.data.write().unwrap();
|
||||
*w_data = None;
|
||||
println!("finishing after updating complete");
|
||||
return;
|
||||
}
|
||||
|
||||
// Repeat after default delay or after 1 second if update was not success.
|
||||
let delay = if wallet_update.load_error()
|
||||
|| wallet_update.get_data_update_attempts() != 0 {
|
||||
// Repeat after default delay or after 1 second if sync was not success.
|
||||
let delay = if wallet.sync_error()
|
||||
|| wallet.get_sync_attempts() != 0 {
|
||||
Duration::from_millis(1000)
|
||||
} else {
|
||||
DATA_UPDATE_DELAY
|
||||
SYNC_DELAY
|
||||
};
|
||||
println!("park for {}", delay.as_millis());
|
||||
thread::park_timeout(delay);
|
||||
}).thread().clone()
|
||||
}
|
||||
|
||||
/// Handle [`WalletCommand::UpdateData`] command to update [`WalletData`].
|
||||
fn update_wallet_data(wallet: &Wallet, keychain: Option<SecretKey>) {
|
||||
println!("UPDATE start, attempts: {}", wallet.get_data_update_attempts());
|
||||
/// Retrieve [`WalletData`] from node.
|
||||
fn sync_wallet_data(wallet: &Wallet, keychain: Option<SecretKey>) {
|
||||
println!("SYNC start, attempts: {}", wallet.get_sync_attempts());
|
||||
|
||||
let wallet_scan = wallet.clone();
|
||||
let wallet_info = wallet.clone();
|
||||
let (info_tx, info_rx) = mpsc::channel::<StatusMessage>();
|
||||
// Update info loading progress at separate thread.
|
||||
// Update info sync progress at separate thread.
|
||||
thread::spawn(move || {
|
||||
while let Ok(m) = info_rx.recv() {
|
||||
println!("UPDATE INFO MESSAGE");
|
||||
println!("SYNC INFO MESSAGE");
|
||||
match m {
|
||||
StatusMessage::UpdatingOutputs(_) => {}
|
||||
StatusMessage::UpdatingTransactions(_) => {}
|
||||
StatusMessage::FullScanWarn(_) => {}
|
||||
StatusMessage::Scanning(_, progress) => {
|
||||
wallet_scan.info_load_progress.store(progress, Ordering::Relaxed);
|
||||
wallet_info.info_sync_progress.store(progress, Ordering::Relaxed);
|
||||
}
|
||||
StatusMessage::ScanningComplete(_) => {
|
||||
wallet_scan.info_load_progress.store(100, Ordering::Relaxed);
|
||||
wallet_info.info_sync_progress.store(100, Ordering::Relaxed);
|
||||
}
|
||||
StatusMessage::UpdateWarning(_) => {}
|
||||
}
|
||||
|
@ -406,37 +474,25 @@ fn update_wallet_data(wallet: &Wallet, keychain: Option<SecretKey>) {
|
|||
Ok(info) => {
|
||||
// Do not retrieve txs if wallet was closed.
|
||||
if !wallet.is_open() {
|
||||
println!("UPDATE stop at retrieve_summary_info");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add attempt if scanning was not complete
|
||||
// or set an error on initial request.
|
||||
if wallet.info_load_progress() != 100 {
|
||||
println!("UPDATE retrieve_summary_info was not completed");
|
||||
if wallet.get_data().is_none() {
|
||||
wallet.set_load_error(true);
|
||||
} else {
|
||||
wallet.increment_data_update_attempts();
|
||||
}
|
||||
} else {
|
||||
println!("UPDATE before retrieve_txs");
|
||||
|
||||
// Retrieve txs if retrieving info was success.
|
||||
if wallet.info_sync_progress() == 100 {
|
||||
let wallet_txs = wallet.clone();
|
||||
let (txs_tx, txs_rx) = mpsc::channel::<StatusMessage>();
|
||||
// Update txs loading progress at separate thread.
|
||||
// Update txs sync progress at separate thread.
|
||||
thread::spawn(move || {
|
||||
while let Ok(m) = txs_rx.recv() {
|
||||
println!("UPDATE TXS MESSAGE");
|
||||
println!("SYNC TXS MESSAGE");
|
||||
match m {
|
||||
StatusMessage::UpdatingOutputs(_) => {}
|
||||
StatusMessage::UpdatingTransactions(_) => {}
|
||||
StatusMessage::FullScanWarn(_) => {}
|
||||
StatusMessage::Scanning(_, progress) => {
|
||||
wallet_txs.txs_load_progress.store(progress, Ordering::Relaxed);
|
||||
wallet_txs.txs_sync_progress.store(progress, Ordering::Relaxed);
|
||||
}
|
||||
StatusMessage::ScanningComplete(_) => {
|
||||
wallet_txs.txs_load_progress.store(100, Ordering::Relaxed);
|
||||
wallet_txs.txs_sync_progress.store(100, Ordering::Relaxed);
|
||||
}
|
||||
StatusMessage::UpdateWarning(_) => {}
|
||||
}
|
||||
|
@ -454,53 +510,104 @@ fn update_wallet_data(wallet: &Wallet, keychain: Option<SecretKey>) {
|
|||
None
|
||||
) {
|
||||
Ok(txs) => {
|
||||
// Do not update data if wallet was closed.
|
||||
// Do not sync data if wallet was closed.
|
||||
if !wallet.is_open() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add attempt if retrieving was not complete
|
||||
// or set an error on initial request.
|
||||
if wallet.txs_load_progress() != 100 {
|
||||
if wallet.get_data().is_none() {
|
||||
wallet.set_load_error(true);
|
||||
} else {
|
||||
wallet.increment_data_update_attempts();
|
||||
}
|
||||
} else {
|
||||
// Save data if loading was completed.
|
||||
if wallet.txs_sync_progress() == 100 {
|
||||
// Reset attempts.
|
||||
wallet.reset_sync_attempts();
|
||||
// Set wallet data.
|
||||
let mut w_data = wallet.data.write().unwrap();
|
||||
*w_data = Some(WalletData { info: info.1, txs: txs.1 });
|
||||
|
||||
// Reset attempts.
|
||||
wallet.reset_data_update_attempts();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error on retrieve_txs {}", e);
|
||||
// Increment attempts value in case of error.
|
||||
wallet.increment_data_update_attempts();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error on retrieve_summary_info {}", e);
|
||||
// Increment attempts value in case of error.
|
||||
wallet.increment_data_update_attempts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset progress values.
|
||||
wallet.info_load_progress.store(0, Ordering::Relaxed);
|
||||
wallet.txs_load_progress.store(0, Ordering::Relaxed);
|
||||
// Reset progress.
|
||||
wallet.info_sync_progress.store(0, Ordering::Relaxed);
|
||||
wallet.txs_sync_progress.store(0, Ordering::Relaxed);
|
||||
|
||||
println!("UPDATE finish, attempts: {}", wallet.get_data_update_attempts());
|
||||
// Exit if wallet was closed.
|
||||
if !wallet.is_open() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set an error if data was not loaded after opening or increment attempts count.
|
||||
if wallet.get_data().is_none() {
|
||||
wallet.set_sync_error(true);
|
||||
} else {
|
||||
wallet.increment_sync_attempts();
|
||||
}
|
||||
|
||||
println!("SYNC cycle finished, attempts: {}", wallet.get_sync_attempts());
|
||||
|
||||
// Set an error if maximum number of attempts was reached.
|
||||
if wallet.get_data_update_attempts() >= DATA_UPDATE_ATTEMPTS {
|
||||
wallet.reset_data_update_attempts();
|
||||
wallet.set_load_error(true);
|
||||
if wallet.get_sync_attempts() >= SYNC_ATTEMPTS {
|
||||
wallet.reset_sync_attempts();
|
||||
wallet.set_sync_error(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Scan wallet's outputs, repairing and restoring missing outputs if required.
|
||||
fn scan_wallet(wallet: &Wallet, keychain: Option<SecretKey>) {
|
||||
println!("repair the wallet");
|
||||
let (info_tx, info_rx) = mpsc::channel::<StatusMessage>();
|
||||
// Update scan progress at separate thread.
|
||||
let wallet_scan = wallet.clone();
|
||||
thread::spawn(move || {
|
||||
while let Ok(m) = info_rx.recv() {
|
||||
println!("REPAIR WALLET MESSAGE");
|
||||
match m {
|
||||
StatusMessage::UpdatingOutputs(_) => {}
|
||||
StatusMessage::UpdatingTransactions(_) => {}
|
||||
StatusMessage::FullScanWarn(_) => {}
|
||||
StatusMessage::Scanning(_, progress) => {
|
||||
wallet_scan.repair_progress.store(progress, Ordering::Relaxed);
|
||||
}
|
||||
StatusMessage::ScanningComplete(_) => {
|
||||
wallet_scan.repair_progress.store(100, Ordering::Relaxed);
|
||||
}
|
||||
StatusMessage::UpdateWarning(_) => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Start wallet scanning.
|
||||
let api = Owner::new(wallet.instance.clone().unwrap(), Some(info_tx));
|
||||
match api.scan(keychain.as_ref(), Some(1), false) {
|
||||
Ok(()) => {
|
||||
println!("repair was complete");
|
||||
// Set sync error if scanning was not complete and wallet is open.
|
||||
if wallet.is_open() && wallet.repair_progress.load(Ordering::Relaxed) != 100 {
|
||||
wallet.set_sync_error(true);
|
||||
} else {
|
||||
wallet.repair_needed.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error on repair {}", e);
|
||||
// Set sync error if wallet is open.
|
||||
if wallet.is_open() {
|
||||
wallet.set_sync_error(true);
|
||||
} else {
|
||||
wallet.repair_needed.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset repair progress.
|
||||
wallet.repair_progress.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue