txs: async tasks for wallet

This commit is contained in:
ardocrat
2025-06-19 09:18:20 +03:00
parent b540fcbf19
commit cd0e3485c5
19 changed files with 986 additions and 983 deletions
+4 -1
View File
@@ -468,9 +468,12 @@ impl View {
Spinner::new().size(Self::BIG_SPINNER_SIZE).color(Colors::gold()).ui(ui);
}
/// Size of big loading spinner.
pub const SMALL_SPINNER_SIZE: f32 = 32.0;
/// Draw small gold loading spinner.
pub fn small_loading_spinner(ui: &mut egui::Ui) {
Spinner::new().size(38.0).color(Colors::gold()).ui(ui);
Spinner::new().size(30.0).color(Colors::gold()).ui(ui);
}
/// Draw the button that looks like checkbox with callback on check.
+2 -2
View File
@@ -26,7 +26,7 @@ use crate::gui::views::wallets::wallet::types::{wallet_status_text, WalletConten
use crate::gui::views::wallets::WalletContent;
use crate::gui::views::{Content, Modal, TitlePanel, View};
use crate::gui::Colors;
use crate::wallet::types::ConnectionMethod;
use crate::wallet::types::{ConnectionMethod, WalletTask};
use crate::wallet::{Wallet, WalletList};
use crate::AppConfig;
@@ -619,7 +619,7 @@ impl WalletsContent {
fn select_wallet(&mut self, wallet: &Wallet, data: Option<String>, cb: &dyn PlatformCallbacks) {
self.wallet_content.account_content.close_qr_scan(cb);
if let Some(data) = data {
wallet.open_message(data);
wallet.task(WalletTask::OpenMessage(data));
}
self.wallets.select(Some(wallet.get_config().id));
}
@@ -24,6 +24,7 @@ use crate::gui::views::wallets::wallet::types::{WalletContentContainer, GRIN};
use crate::gui::views::{CameraContent, CameraScanContent, Content, Modal, View};
use crate::gui::Colors;
use crate::wallet::{Wallet, WalletConfig};
use crate::wallet::types::WalletTask;
/// Wallet account panel content.
pub struct AccountContent {
@@ -288,7 +289,7 @@ impl AccountContent {
//TODO: send with address
}
QrScanResult::Slatepack(m) => {
wallet.open_message(m.to_string());
wallet.task(WalletTask::OpenMessage(m));
}
_ => {
self.qr_scan_result = Some(result);
+33 -37
View File
@@ -16,7 +16,7 @@ use egui::scroll_area::ScrollBarVisibility;
use egui::{Id, Margin, RichText, ScrollArea};
use grin_chain::SyncStatus;
use std::time::Duration;
use grin_wallet_libwallet::Error;
use crate::gui::icons::{ARROWS_CLOCKWISE, FILE_ARROW_DOWN, FILE_ARROW_UP, GEAR_FINE, POWER, STACK};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::types::{LinePosition, ModalPosition};
@@ -30,7 +30,7 @@ use crate::gui::views::wallets::WalletTransactions;
use crate::gui::views::{Content, FilePickContent, FilePickContentType, Modal, View};
use crate::gui::Colors;
use crate::node::Node;
use crate::wallet::types::{ConnectionMethod, WalletTransaction};
use crate::wallet::types::{ConnectionMethod, WalletTask};
use crate::wallet::{ExternalConnection, Wallet};
use crate::AppConfig;
@@ -105,11 +105,6 @@ impl WalletContentContainer for WalletContent {
show_account = false;
}
// Consume inserted message.
if let Some(res) = wallet.consume_message_result() {
self.on_transaction(res);
}
// Show wallet tabs.
egui::TopBottomPanel::bottom("wallet_tabs")
.frame(egui::Frame {
@@ -314,13 +309,6 @@ impl WalletContent {
}
}
/// Callback on incoming transaction for user to take action.
fn on_transaction(&mut self, tx_result: Result<WalletTransaction, Error>) {
if let Ok(tx) = tx_result {
self.current_tab = Box::new(WalletTransactions::new(Some(tx)));
}
}
/// Check if it's possible to go back at navigation stack.
pub fn can_back(&self) -> bool {
self.account_content.can_back() || self.transport_content.can_back()
@@ -370,41 +358,49 @@ impl WalletContent {
});
});
columns[1].vertical_centered_justified(|ui| {
let active = if has_wallet_data {
Some(false)
if wallet.invoice_creating() {
ui.add_space(4.0);
View::small_loading_spinner(ui);
} else {
None
};
View::tab_button(ui, FILE_ARROW_DOWN, Some(Colors::green()), active, |_| {
self.invoice_request_content = Some(InvoiceRequestContent::default());
Modal::new(INVOICE_MODAL_ID)
.position(ModalPosition::CenterTop)
.title(t!("wallets.receive"))
.show();
});
let active = if has_wallet_data {
Some(false)
} else {
None
};
View::tab_button(ui, FILE_ARROW_DOWN, Some(Colors::green()), active, |_| {
self.invoice_request_content = Some(InvoiceRequestContent::default());
Modal::new(INVOICE_MODAL_ID)
.position(ModalPosition::CenterTop)
.title(t!("wallets.receive"))
.show();
});
}
});
columns[2].vertical_centered_justified(|ui| {
if wallet.message_opening() {
ui.add_space(4.0);
View::small_loading_spinner(ui);
} else {
let mut message = "".to_string();
self.file_pick_tab_button.ui(ui, cb, |m| {
message = m;
wallet.task(WalletTask::OpenMessage(m));
});
if !message.is_empty() {
wallet.open_message(message);
}
}
});
if can_send {
columns[3].vertical_centered_justified(|ui| {
View::tab_button(ui, FILE_ARROW_UP, Some(Colors::red()), Some(false), |_| {
self.send_request_content = Some(SendRequestContent::new(None));
Modal::new(SEND_MODAL_ID)
.position(ModalPosition::CenterTop)
.title(t!("wallets.send"))
.show();
});
if wallet.send_creating() {
ui.add_space(4.0);
View::small_loading_spinner(ui);
} else {
let (icon, color) = (FILE_ARROW_UP, Some(Colors::red()));
View::tab_button(ui, icon, color, Some(false), |_| {
self.send_request_content = Some(SendRequestContent::new(None));
Modal::new(SEND_MODAL_ID)
.position(ModalPosition::CenterTop)
.title(t!("wallets.send"))
.show();
});
}
});
}
let settings_index = if tabs_amount == 5 { 4 } else { 3 };
@@ -14,42 +14,23 @@
use egui::{Id, RichText};
use grin_core::core::amount_from_hr_string;
use grin_wallet_libwallet::Error;
use parking_lot::RwLock;
use std::sync::Arc;
use std::thread;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::wallet::WalletTransactionContent;
use crate::gui::views::{Modal, TextEdit, View};
use crate::gui::Colors;
use crate::wallet::types::WalletTransaction;
use crate::wallet::types::WalletTask;
use crate::wallet::Wallet;
/// Invoice request creation content.
pub struct InvoiceRequestContent {
/// Amount to receive.
amount_edit: String,
/// Flag to check if request is loading.
request_loading: bool,
/// Request result if there is no error.
request_result: Arc<RwLock<Option<Result<WalletTransaction, Error>>>>,
/// Flag to check if there is an error happened on request creation.
request_error: Option<String>,
/// Request result transaction content.
result_tx_content: Option<WalletTransactionContent>,
}
impl Default for InvoiceRequestContent {
fn default() -> Self {
Self {
amount_edit: "".to_string(),
request_loading: false,
request_result: Arc::new(RwLock::new(None)),
request_error: None,
result_tx_content: None,
}
}
}
@@ -61,42 +42,20 @@ impl InvoiceRequestContent {
wallet: &Wallet,
modal: &Modal,
cb: &dyn PlatformCallbacks) {
// Draw transaction information on request result.
if let Some(tx) = self.result_tx_content.as_mut() {
tx.ui(ui, wallet, modal, cb);
return;
}
// Setup callback on continue.
let on_continue = |m: &mut InvoiceRequestContent| {
if m.amount_edit.is_empty() {
return;
}
if let Ok(a) = amount_from_hr_string(m.amount_edit.as_str()) {
modal.disable_closing();
// Setup data for request.
let wallet = wallet.clone();
let result = m.request_result.clone();
// Send request at another thread.
m.request_loading = true;
thread::spawn(move || {
let res = wallet.issue_invoice(a);
let mut w_result = result.write();
*w_result = Some(res);
});
} else {
m.request_error = Some(t!("wallets.invoice_slatepack_err"));
m.amount_edit = "".to_string();
wallet.task(WalletTask::Receive(a));
Modal::close();
}
};
ui.add_space(6.0);
// Draw content on request loading.
if self.request_loading {
self.loading_request_ui(ui, modal);
return;
}
// Draw amount input content.
ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("wallets.enter_amount_receive"))
@@ -117,7 +76,6 @@ impl InvoiceRequestContent {
// Check value if input was changed.
if amount_edit_before != self.amount_edit {
self.request_error = None;
if !self.amount_edit.is_empty() {
self.amount_edit = self.amount_edit.trim().replace(",", ".");
match amount_from_hr_string(self.amount_edit.as_str()) {
@@ -146,16 +104,6 @@ impl InvoiceRequestContent {
}
}
// Show request creation error.
if let Some(err) = &self.request_error {
ui.add_space(12.0);
ui.vertical_centered(|ui| {
ui.label(RichText::new(err)
.size(17.0)
.color(Colors::red()));
});
}
ui.add_space(12.0);
// Setup spacing between buttons.
@@ -165,7 +113,6 @@ impl InvoiceRequestContent {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
self.amount_edit = "".to_string();
self.request_error = None;
Modal::close();
});
});
@@ -178,36 +125,4 @@ impl InvoiceRequestContent {
});
ui.add_space(6.0);
}
/// Draw loading request content.
fn loading_request_ui(&mut self, ui: &mut egui::Ui, modal: &Modal) {
ui.add_space(34.0);
ui.vertical_centered(|ui| {
View::big_loading_spinner(ui);
});
ui.add_space(50.0);
// Check if there is request result error.
if self.request_error.is_some() {
modal.enable_closing();
self.request_loading = false;
return;
}
// Update data on request result.
let r_request = self.request_result.read();
if r_request.is_some() {
modal.enable_closing();
let result = r_request.as_ref().unwrap();
match result {
Ok(tx) => {
self.result_tx_content = Some(WalletTransactionContent::new(tx));
}
Err(_) => {
self.request_error = Some(t!("wallets.invoice_slatepack_err"));
self.request_loading = false;
}
}
}
}
}
+27 -162
View File
@@ -12,19 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
use grin_wallet_libwallet::{Error, SlatepackAddress};
use parking_lot::RwLock;
use std::sync::Arc;
use std::thread;
use egui::{Id, RichText};
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
use grin_wallet_libwallet::SlatepackAddress;
use crate::gui::Colors;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::wallet::WalletTransactionContent;
use crate::gui::views::{CameraContent, Modal, TextEdit, View};
use crate::gui::views::types::ModalPosition;
use crate::wallet::types::WalletTransaction;
use crate::gui::Colors;
use crate::wallet::types::WalletTask;
use crate::wallet::Wallet;
/// Content to create a request to send funds.
@@ -36,18 +31,8 @@ pub struct SendRequestContent {
/// Flag to check if entered address is incorrect.
address_error: bool,
/// Flag to check if request is loading.
request_loading: bool,
/// Request result if there is no error.
request_result: Arc<RwLock<Option<Result<WalletTransaction, Error>>>>,
/// Flag to check if there is an error happened on request creation.
request_error: Option<String>,
/// Address QR code scanner content.
address_scan_content: Option<CameraContent>,
/// Request result transaction content.
result_tx_content: Option<WalletTransactionContent>,
}
impl SendRequestContent {
@@ -57,11 +42,7 @@ impl SendRequestContent {
amount_edit: "".to_string(),
address_edit: addr.unwrap_or("".to_string()),
address_error: false,
request_loading: false,
request_result: Arc::new(RwLock::new(None)),
request_error: None,
address_scan_content: None,
result_tx_content: None,
}
}
@@ -71,68 +52,6 @@ impl SendRequestContent {
wallet: &Wallet,
modal: &Modal,
cb: &dyn PlatformCallbacks) {
// Draw transaction information on request result.
if let Some(tx) = self.result_tx_content.as_mut() {
tx.ui(ui, wallet, modal, cb);
return;
}
// Setup callback on continue.
let on_continue = |m: &mut SendRequestContent| {
if m.amount_edit.is_empty() {
return;
}
// Check address to send over Tor if enabled.
let addr_str = m.address_edit.as_str();
if let Ok(addr) = SlatepackAddress::try_from(addr_str.trim()) {
if let Ok(a) = amount_from_hr_string(m.amount_edit.as_str()) {
Modal::change_position(ModalPosition::Center);
modal.disable_closing();
let mut wallet = wallet.clone();
let res = m.request_result.clone();
// Send request at another thread.
m.request_loading = true;
thread::spawn(move || {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let result = wallet.send_tor(a, &addr).await;
let mut w_res = res.write();
*w_res = Some(result);
});
});
}
} else if !addr_str.is_empty() {
m.address_error = true;
} else if let Ok(amount) = amount_from_hr_string(m.amount_edit.as_str()) {
Modal::change_position(ModalPosition::Center);
modal.disable_closing();
let wallet = wallet.clone();
let result = m.request_result.clone();
// Send request at another thread.
m.request_loading = true;
thread::spawn(move || {
let res = wallet.send(amount, None);
let mut w_result = result.write();
*w_result = Some(res);
});
} else {
m.request_error = Some(t!("wallets.send_slatepack_err"));
}
};
// Draw content on request loading.
if self.request_loading {
self.loading_request_ui(ui, modal);
return;
}
ui.add_space(6.0);
// Draw QR code scanner content if requested.
@@ -158,6 +77,7 @@ impl SendRequestContent {
ui.columns(2, |cols| {
cols[0].vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::white_or_black(false), || {
on_stop();
self.close();
});
});
@@ -267,17 +187,7 @@ impl SendRequestContent {
// Continue on Enter press.
if address_edit.enter_pressed {
on_continue(self);
}
// Show request creation error.
if let Some(err) = &self.request_error {
ui.vertical_centered(|ui| {
ui.label(RichText::new(err)
.size(17.0)
.color(Colors::red()));
});
ui.add_space(12.0);
self.on_continue(wallet);
}
// Setup spacing between buttons.
@@ -292,83 +202,38 @@ impl SendRequestContent {
columns[1].vertical_centered_justified(|ui| {
// Button to create Slatepack message request.
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
on_continue(self);
self.on_continue(wallet);
});
});
});
ui.add_space(6.0);
}
/// Callback when Continue button was pressed.
fn on_continue(&mut self, wallet: &Wallet) {
if self.amount_edit.is_empty() {
return;
}
// Check address to send over Tor if enabled.
let addr_str = self.address_edit.as_str();
if let Ok(r) = SlatepackAddress::try_from(addr_str.trim()) {
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
wallet.task(WalletTask::Send(a, Some(r)));
Modal::close();
}
} else if !addr_str.is_empty() {
self.address_error = true;
} else if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
wallet.task(WalletTask::Send(a, None));
Modal::close();
}
}
/// Close modal and clear data.
fn close(&mut self) {
self.amount_edit = "".to_string();
self.address_edit = "".to_string();
let mut w_res = self.request_result.write();
*w_res = None;
self.result_tx_content = None;
self.address_scan_content = None;
Modal::close();
}
/// Draw loading request content.
fn loading_request_ui(&mut self, ui: &mut egui::Ui, modal: &Modal) {
ui.add_space(40.0);
ui.vertical_centered(|ui| {
View::big_loading_spinner(ui);
});
ui.add_space(40.0);
if !self.address_edit.is_empty() {
ui.vertical_centered(|ui| {
ui.label(RichText::new(t!("transport.tor_sending", "amount" => self.amount_edit))
.size(17.0)
.color(Colors::inactive_text()));
});
ui.add_space(12.0);
}
// Update data on request result.
let has_res = {
let r_request = self.request_result.read();
r_request.is_some()
};
if has_res {
self.request_loading = false;
modal.enable_closing();
let r_request = self.request_result.read();
let result = r_request.as_ref().unwrap();
match result {
Ok(tx) => {
self.result_tx_content = Some(WalletTransactionContent::new(tx));
}
Err(err) => {
let m = match err {
Error::NotEnoughFunds { .. } => {
t!("wallets.pay_balance_error", "amount" => self.amount_edit)
}
_ => {
if !self.address_edit.is_empty() {
t!("transport.tor_send_error")
} else {
t!("wallets.send_slatepack_err")
}
}
};
self.request_error = Some(m);
}
}
}
// Check if there is request result error.
if self.request_error.is_some() {
Modal::change_position(ModalPosition::CenterTop);
modal.enable_closing();
let mut w_request = self.request_result.write();
*w_request = None;
self.request_loading = false;
}
}
}
@@ -130,7 +130,7 @@ impl WalletTransportContent {
if !Tor::is_service_running(service_id) {
let r = CornerRadius::default();
View::item_button(ui, r, POWER, Some(Colors::green()), || {
if let Ok(key) = wallet.secret_key() {
if let Ok(key) = wallet.get_secret_key() {
let api_port = wallet.foreign_api_port().unwrap();
Tor::start_service(api_port, key, service_id);
}
@@ -80,7 +80,7 @@ impl WalletTransportSettingsContent {
// Restart running service or rebuild client.
let service_id = &wallet.identifier();
if Tor::is_service_running(service_id) {
if let Ok(key) = wallet.secret_key() {
if let Ok(key) = wallet.get_secret_key() {
let api_port = wallet.foreign_api_port().unwrap();
Tor::restart_service(api_port, key, service_id);
}
+94 -65
View File
@@ -21,7 +21,7 @@ use grin_wallet_libwallet::TxLogEntryType;
use std::ops::Range;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::gui::icons::{ARCHIVE_BOX, ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, CALENDAR_CHECK, DOTS_THREE_CIRCLE, FILE_ARROW_DOWN, FILE_TEXT, GEAR_FINE, PROHIBIT, X_CIRCLE};
use crate::gui::icons::{ARCHIVE_BOX, ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, CALENDAR_CHECK, DOTS_THREE_CIRCLE, FILE_ARROW_DOWN, FILE_TEXT, GEAR_FINE, PROHIBIT, WARNING, X_CIRCLE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::types::{LinePosition, ModalPosition};
use crate::gui::views::wallets::types::WalletTab;
@@ -29,7 +29,7 @@ use crate::gui::views::wallets::wallet::types::{WalletTabType, GRIN};
use crate::gui::views::wallets::wallet::WalletTransactionContent;
use crate::gui::views::{Content, Modal, PullToRefresh, View};
use crate::gui::Colors;
use crate::wallet::types::{WalletData, WalletTransaction};
use crate::wallet::types::{WalletData, WalletTask, WalletTransaction, WalletTransactionAction};
use crate::wallet::Wallet;
/// Wallet transactions tab content.
@@ -50,6 +50,15 @@ impl WalletTab for WalletTransactions {
}
fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
if Modal::opened().is_none() {
// Show transaction modal on task result.
if let Some(id) = wallet.consume_tx_task_result() {
let tx = wallet.get_data().unwrap().tx_by_slate_id(id);
if let Some(tx) = tx {
self.show_tx_info_modal(tx.data.id);
}
}
}
self.modal_content_ui(ui, wallet, cb);
self.txs_ui(ui, wallet);
}
@@ -72,7 +81,7 @@ impl WalletTransactions {
manual_sync: None,
};
if let Some(tx) = &tx {
content.show_tx_info_modal(tx);
content.show_tx_info_modal(tx.data.id);
}
content
}
@@ -104,7 +113,7 @@ impl WalletTransactions {
return;
}
// Draw awaiting amount info if exists.
self.awaiting_info_ui(ui, &data);
self.awaiting_info_ui(ui, &data);
});
ui.add_space(4.0);
@@ -161,10 +170,9 @@ impl WalletTransactions {
r.nw = 0.0 as u8;
r.sw = 0.0 as u8;
View::item_button(ui, r, FILE_TEXT, None, || {
self.show_tx_info_modal(tx);
self.show_tx_info_modal(tx.data.id);
});
}
// Draw button to cancel transaction.
if tx.can_cancel() {
let (icon, color) = (PROHIBIT, Some(Colors::red()));
@@ -177,6 +185,10 @@ impl WalletTransactions {
.show();
});
}
//TODO: Draw button to repeat transaction task on error.
if tx.action_error.is_some() {
}
});
}
}
@@ -303,52 +315,68 @@ impl WalletTransactions {
|| tx.data.tx_type == TxLogEntryType::TxReceivedCancelled;
if is_canceled {
format!("{} {}", X_CIRCLE, t!("wallets.tx_canceled"))
} else if tx.finalizing {
format!("{} {}", DOTS_THREE_CIRCLE, t!("wallets.tx_finalizing"))
} else {
if tx.cancelling {
format!("{} {}", DOTS_THREE_CIRCLE, t!("wallets.tx_cancelling"))
} else if let Some(a) = &tx.action {
let error = if tx.action_error.is_none() {
"".to_string()
} else {
match tx.data.tx_type {
TxLogEntryType::TxReceived => {
format!("{} {}",
DOTS_THREE_CIRCLE,
t!("wallets.tx_receiving"))
},
TxLogEntryType::TxSent => {
format!("{} {}",
DOTS_THREE_CIRCLE,
t!("wallets.tx_sending"))
},
TxLogEntryType::ConfirmedCoinbase => {
let tx_h = tx.height.unwrap_or(1) - 1;
if tx_h != 0 {
let left_conf = height - tx_h;
if height >= tx_h && left_conf < COINBASE_MATURITY {
let conf_info = format!("{}/{}",
left_conf,
COINBASE_MATURITY);
format!("{} {} {}",
DOTS_THREE_CIRCLE,
t!("wallets.tx_confirming"),
conf_info
)
} else {
format!("{} {}",
DOTS_THREE_CIRCLE,
t!("wallets.tx_confirming"))
}
format!("{}: ", t!("error"))
};
let status = match a {
WalletTransactionAction::Cancelling => t!("wallets.tx_cancelling"),
WalletTransactionAction::Finalizing => t!("wallets.tx_finalizing"),
WalletTransactionAction::Posting => t!("wallets.tx_posting"),
WalletTransactionAction::SendingTor => t!("transport.tor_sending")
};
let icon = if error.is_empty() {
DOTS_THREE_CIRCLE
} else {
WARNING
};
format!("{} {}{}", icon, error, status)
} else {
match tx.data.tx_type {
TxLogEntryType::TxReceived => {
let text = match tx.finalized() {
true => t!("wallets.await_fin_amount"),
false => t!("wallets.tx_receiving")
};
format!("{} {}", DOTS_THREE_CIRCLE, text)
},
TxLogEntryType::TxSent => {
let text = match tx.finalized() {
true => t!("wallets.await_fin_amount"),
false => t!("wallets.tx_sending")
};
format!("{} {}", DOTS_THREE_CIRCLE, text)
},
TxLogEntryType::ConfirmedCoinbase => {
let tx_h = tx.height.unwrap_or(1) - 1;
if tx_h != 0 {
let left_conf = height - tx_h;
if height >= tx_h && left_conf < COINBASE_MATURITY {
let conf_info = format!("{}/{}",
left_conf,
COINBASE_MATURITY);
format!("{} {} {}",
DOTS_THREE_CIRCLE,
t!("wallets.tx_confirming"),
conf_info
)
} else {
format!("{} {}",
DOTS_THREE_CIRCLE,
t!("wallets.tx_confirming"))
}
},
_ => {
} else {
format!("{} {}",
DOTS_THREE_CIRCLE,
t!("wallets.tx_confirming"))
}
},
_ => {
format!("{} {}",
DOTS_THREE_CIRCLE,
t!("wallets.tx_confirming"))
}
}
}
@@ -439,8 +467,8 @@ impl WalletTransactions {
}
/// Show transaction information [`Modal`].
fn show_tx_info_modal(&mut self, tx: &WalletTransaction) {
let modal = WalletTransactionContent::new(tx);
fn show_tx_info_modal(&mut self, id: u32) {
let modal = WalletTransactionContent::new(id);
self.tx_info_content = Some(modal);
Modal::new(TX_INFO_MODAL)
.position(ModalPosition::Center)
@@ -450,28 +478,29 @@ impl WalletTransactions {
/// Confirmation [`Modal`] to cancel transaction.
fn cancel_confirmation_modal(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
let data = wallet.get_data().unwrap();
let data_txs = data.txs.unwrap();
let txs = data_txs.into_iter()
.filter(|tx| tx.data.id == self.confirm_cancel_tx_id.unwrap())
.collect::<Vec<WalletTransaction>>();
if txs.is_empty() {
Modal::close();
return;
}
let tx = txs.get(0).unwrap();
let amount = amount_to_hr_string(tx.amount, true);
let text = match tx.data.tx_type {
TxLogEntryType::TxReceived => {
t!("wallets.tx_receive_cancel_conf", "amount" => amount)
},
_ => {
t!("wallets.tx_send_cancel_conf", "amount" => amount)
}
};
// Show confirmation text.
ui.add_space(6.0);
ui.vertical_centered(|ui| {
// Setup confirmation text.
let data = wallet.get_data().unwrap();
let data_txs = data.txs.unwrap();
let txs = data_txs.into_iter()
.filter(|tx| tx.data.id == self.confirm_cancel_tx_id.unwrap())
.collect::<Vec<WalletTransaction>>();
if txs.is_empty() {
Modal::close();
return;
}
let tx = txs.get(0).unwrap();
let amount = amount_to_hr_string(tx.amount, true);
let text = match tx.data.tx_type {
TxLogEntryType::TxReceived => {
t!("wallets.tx_receive_cancel_conf", "amount" => amount)
},
_ => {
t!("wallets.tx_send_cancel_conf", "amount" => amount)
}
};
ui.label(RichText::new(text)
.size(17.0)
.color(Colors::text(false)));
@@ -492,7 +521,7 @@ impl WalletTransactions {
});
columns[1].vertical_centered_justified(|ui| {
View::button(ui, "OK".to_string(), Colors::white_or_black(false), || {
wallet.cancel(self.confirm_cancel_tx_id.unwrap());
wallet.task(WalletTask::Cancel(tx.clone()));
self.confirm_cancel_tx_id = None;
Modal::close();
});
+22 -19
View File
@@ -17,12 +17,12 @@ use grin_core::core::amount_to_hr_string;
use grin_util::ToHex;
use grin_wallet_libwallet::TxLogEntryType;
use crate::gui::icons::{COPY, CUBE, FILE_ARCHIVE, FILE_TEXT, HASH_STRAIGHT, PROHIBIT, QR_CODE, SCAN};
use crate::gui::icons::{CIRCLE_HALF, COPY, CUBE, FILE_ARCHIVE, FILE_TEXT, HASH_STRAIGHT, PROHIBIT, QR_CODE, SCAN};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::wallet::txs::WalletTransactions;
use crate::gui::views::{CameraContent, FilePickContent, FilePickContentType, Modal, QrCodeContent, View};
use crate::gui::Colors;
use crate::wallet::types::WalletTransaction;
use crate::wallet::types::{WalletTask, WalletTransaction};
use crate::wallet::Wallet;
/// Transaction information [`Modal`] content.
@@ -42,9 +42,9 @@ pub struct WalletTransactionContent {
impl WalletTransactionContent {
/// Create new content instance with [`Wallet`] from provided [`WalletTransaction`].
pub fn new(tx: &WalletTransaction) -> Self {
pub fn new(id: u32) -> Self {
Self {
tx_id: tx.data.id,
tx_id: id,
qr_code_content: None,
scan_qr_content: None,
file_pick_button: FilePickContent::new(
@@ -103,7 +103,7 @@ impl WalletTransactionContent {
modal.enable_closing();
self.scan_qr_content = None;
// Provide scan result as Slatepack message.
wallet.open_message(result.text());
wallet.task(WalletTask::OpenMessage(result.text()));
} else {
scan_content.ui(ui, cb);
}
@@ -133,8 +133,8 @@ impl WalletTransactionContent {
// Show transaction information.
self.info_ui(ui, modal, tx, wallet, cb);
// Show transaction sharing content.
if tx.can_finalize || tx.is_response {
// Show transaction sharing content when can cancel or finalized.
if tx.can_cancel() && !tx.finalized() {
self.share_ui(ui, wallet, tx, cb);
}
@@ -159,7 +159,7 @@ impl WalletTransactionContent {
tx: &WalletTransaction,
cb: &dyn PlatformCallbacks) {
let amount = amount_to_hr_string(tx.amount, true);
let desc_text = if tx.can_finalize {
let desc_text = if tx.can_finalize() {
if tx.data.tx_type == TxLogEntryType::TxSent {
t!("wallets.send_request_desc", "amount" => amount)
} else {
@@ -186,8 +186,8 @@ impl WalletTransactionContent {
// Draw button to show Slatepack message as QR code.
let qr_text = format!("{} {}", QR_CODE, t!("qr_code"));
View::button(ui, qr_text.clone(), Colors::white_or_black(false), || {
if let Some((_, d)) = wallet.read_slatepack_by_tx(tx) {
self.qr_code_content = Some(QrCodeContent::new(d, true));
if let Some(c) = wallet.read_slatepack(tx) {
self.qr_code_content = Some(QrCodeContent::new(c, true));
}
});
});
@@ -198,10 +198,12 @@ impl WalletTransactionContent {
share_text,
Colors::blue(),
Colors::white_or_black(false), || {
if let Some((s, d)) = wallet.read_slatepack_by_tx(tx) {
let name = format!("{}.{}.slatepack", s.id, s.state);
let data = d.as_bytes().to_vec();
cb.share_data(name, data).unwrap_or_default();
if let Some(slate_id) = tx.data.tx_slate_id {
let name = format!("{}.{}.slatepack", slate_id, tx.state);
if let Some(c) = wallet.read_slatepack(tx) {
let data = c.as_bytes().to_vec();
cb.share_data(name, data).unwrap_or_default();
}
}
});
});
@@ -242,10 +244,10 @@ impl WalletTransactionContent {
}
return;
}
if tx.can_finalize {
if tx.can_finalize() {
// Draw button to pick file.
self.file_pick_button.ui(ui, cb, |data| {
wallet.open_message(data);
wallet.task(WalletTask::OpenMessage(data));
});
// Draw button to scan QR code.
let r = CornerRadius::default();
@@ -257,13 +259,14 @@ impl WalletTransactionContent {
}
// Draw button to cancel transaction.
if tx.can_cancel() {
let r = if tx.can_finalize {
let r = if tx.can_finalize() {
CornerRadius::default()
} else {
View::item_rounding(0, 2, true)
};
View::item_button(ui, r, PROHIBIT, Some(Colors::red()), || {
wallet.cancel(tx.data.id);
wallet.task(WalletTask::Cancel(tx.clone()));
Modal::close();
});
}
});
@@ -280,7 +283,7 @@ impl WalletTransactionContent {
}
// Show receiver address.
if let Some(rec) = tx.receiver() {
let label = format!("{} {}", CUBE, t!("network_mining.address"));
let label = format!("{} {}", CIRCLE_HALF, t!("network_mining.address"));
info_item_ui(ui, rec.to_string(), label, true, cb);
}
}