messages: async response creation, better errors handling

This commit is contained in:
ardocrat 2024-05-18 23:58:03 +03:00
parent d5fa21b6d7
commit d6e6f89324

View file

@ -13,12 +13,10 @@
// limitations under the License. // limitations under the License.
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread; use std::thread;
use egui::{Id, Margin, RichText, ScrollArea, TextBuffer}; use std::time::Duration;
use egui::os::OperatingSystem; use egui::{Id, Margin, RichText, ScrollArea};
use egui::scroll_area::ScrollBarVisibility; use egui::scroll_area::ScrollBarVisibility;
use egui::text_edit::TextEditState;
use grin_core::core::{amount_from_hr_string, amount_to_hr_string}; use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
use grin_wallet_libwallet::{Slate, SlateState}; use grin_wallet_libwallet::{Slate, SlateState};
use log::error; use log::error;
@ -29,7 +27,6 @@ use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, COPY, DOWNLOAD_SIMPLE, PROHIBIT,
use crate::gui::platform::PlatformCallbacks; use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, Root, View}; use crate::gui::views::{Modal, Root, View};
use crate::gui::views::types::{ModalPosition, TextEditOptions}; use crate::gui::views::types::{ModalPosition, TextEditOptions};
use crate::gui::views::views::LAST_SOFT_KEYBOARD_INPUT;
use crate::gui::views::wallets::wallet::types::{SLATEPACK_MESSAGE_HINT, WalletTab, WalletTabType}; use crate::gui::views::wallets::wallet::types::{SLATEPACK_MESSAGE_HINT, WalletTab, WalletTabType};
use crate::gui::views::wallets::wallet::WalletContent; use crate::gui::views::wallets::wallet::WalletContent;
use crate::wallet::types::WalletTransaction; use crate::wallet::types::WalletTransaction;
@ -64,6 +61,10 @@ pub struct WalletMessages {
message_edit: String, message_edit: String,
/// Parsed Slatepack message. /// Parsed Slatepack message.
message_slate: Option<Slate>, message_slate: Option<Slate>,
/// Flag to check if message request is loading.
message_loading: bool,
/// Message request result.
receive_pay_result: Arc<RwLock<Option<(Slate, Result<String, grin_wallet_libwallet::Error>)>>>,
/// Slatepack error on finalization, parse and response creation. /// Slatepack error on finalization, parse and response creation.
message_error: Option<MessageError>, message_error: Option<MessageError>,
/// Generated Slatepack response message. /// Generated Slatepack response message.
@ -80,7 +81,7 @@ pub struct WalletMessages {
/// Flag to check if there is an error happened on request creation at [`Modal`]. /// Flag to check if there is an error happened on request creation at [`Modal`].
request_error: Option<MessageError>, request_error: Option<MessageError>,
/// Flag to check if request is loading at [`Modal`]. /// Flag to check if request is loading at [`Modal`].
request_loading: Arc<AtomicBool>, request_loading: bool,
/// Request result if there is no error at [`Modal`]. /// Request result if there is no error at [`Modal`].
request_result: Arc<RwLock<Option<Result<(Slate, String), grin_wallet_libwallet::Error>>>>, request_result: Arc<RwLock<Option<Result<(Slate, String), grin_wallet_libwallet::Error>>>>,
} }
@ -141,13 +142,15 @@ impl WalletMessages {
send_request: false, send_request: false,
message_edit: message.unwrap_or("".to_string()), message_edit: message.unwrap_or("".to_string()),
message_slate: None, message_slate: None,
message_loading: false,
receive_pay_result: Arc::new(RwLock::new(None)),
message_error: None, message_error: None,
response_edit: "".to_string(), response_edit: "".to_string(),
dandelion, dandelion,
amount_edit: "".to_string(), amount_edit: "".to_string(),
request_edit: "".to_string(), request_edit: "".to_string(),
request_error: None, request_error: None,
request_loading: Arc::new(AtomicBool::new(false)), request_loading: false,
request_result: Arc::new(RwLock::new(None)), request_result: Arc::new(RwLock::new(None)),
} }
} }
@ -217,7 +220,7 @@ impl WalletMessages {
self.send_request = true; self.send_request = true;
self.amount_edit = "".to_string(); self.amount_edit = "".to_string();
self.request_error = None; self.request_error = None;
self.request_loading.store(false, Ordering::Relaxed); self.request_loading = false;
{ {
let mut w_result = self.request_result.write(); let mut w_result = self.request_result.write();
*w_result = None; *w_result = None;
@ -332,7 +335,7 @@ impl WalletMessages {
.id(input_id) .id(input_id)
.font(egui::TextStyle::Small) .font(egui::TextStyle::Small)
.desired_rows(5) .desired_rows(5)
.interactive(response_empty) .interactive(response_empty && !self.message_loading)
.hint_text(SLATEPACK_MESSAGE_HINT) .hint_text(SLATEPACK_MESSAGE_HINT)
.desired_width(f32::INFINITY) .desired_width(f32::INFINITY)
.show(ui) .show(ui)
@ -341,40 +344,9 @@ impl WalletMessages {
if response_empty && resp.clicked() { if response_empty && resp.clicked() {
cb.show_keyboard(); cb.show_keyboard();
} }
if response_empty { if response_empty && resp.has_focus() {
// Apply text from input on Android as temporary fix for egui. // Apply text from input on Android as temporary fix for egui.
let os = OperatingSystem::from_target_os(); View::on_soft_input(ui, input_id, message);
if os == OperatingSystem::Android && resp.has_focus() {
let mut w_input = LAST_SOFT_KEYBOARD_INPUT.write();
if !w_input.is_empty() {
let mut state = TextEditState::load(ui.ctx(), input_id).unwrap();
match state.cursor.char_range() {
None => {}
Some(range) => {
let mut r = range.clone();
let mut index = r.primary.index;
message.insert_text(w_input.as_str(), index);
index = index + 1;
if index == 0 {
r.primary.index = message.len();
r.secondary.index = r.primary.index;
} else {
r.primary.index = index;
r.secondary.index = r.primary.index;
}
state.cursor.set_char_range(Some(r));
TextEditState::store(state, ui.ctx(), input_id);
}
}
}
*w_input = "".to_string();
ui.ctx().request_repaint();
}
} }
ui.add_space(6.0); ui.add_space(6.0);
}); });
@ -389,7 +361,7 @@ impl WalletMessages {
// Draw buttons to clear/copy/paste. // Draw buttons to clear/copy/paste.
let fields_empty = self.message_edit.is_empty() && self.response_edit.is_empty(); let fields_empty = self.message_edit.is_empty() && self.response_edit.is_empty();
let columns_num = if fields_empty { 1 } else { 2 }; let columns_num = if fields_empty || self.message_loading { 1 } else { 2 };
let mut show_dandelion = false; let mut show_dandelion = false;
ui.scope(|ui| { ui.scope(|ui| {
// Setup spacing between buttons. // Setup spacing between buttons.
@ -419,16 +391,98 @@ impl WalletMessages {
}); });
} }
} else { } else {
let paste = format!("{} {}", CLIPBOARD_TEXT, t!("paste")); if self.message_loading {
View::button(ui, paste, Colors::BUTTON, || { View::small_loading_spinner(ui);
let buf = cb.get_string_from_buffer();
let previous = self.message_edit.clone(); // Check message loading result.
self.message_edit = buf.clone().trim().to_string(); let has_message_result = {
// Parse Slatepack message resetting message error. let r_res = self.receive_pay_result.read();
if buf != previous { r_res.is_some()
self.parse_message(wallet); };
if has_message_result {
let (slate, resp) = {
let r_res = self.receive_pay_result.read();
r_res.as_ref().unwrap().clone()
};
if resp.is_ok() {
self.response_edit = resp.as_ref().unwrap().clone();
} else {
let err = resp.as_ref().err().unwrap();
match err {
// Set already canceled transaction error message.
grin_wallet_libwallet::Error::TransactionWasCancelled {..}
=> {
self.message_error = Some(
MessageError::Response(
t!("wallets.resp_canceled_err")
)
);
}
// Set an error when there is not enough funds to pay.
grin_wallet_libwallet::Error::NotEnoughFunds {..} => {
let m = t!(
"wallets.pay_balance_error",
"amount" => amount_to_hr_string(slate.amount, true)
);
self.message_error = Some(MessageError::Response(m));
}
// Set default error message.
_ => {
self.message_error = Some(
MessageError::Response(
t!("wallets.resp_slatepack_err")
)
);
}
}
// Check if tx with same slate id already exists.
if self.message_error.is_none() {
let exists_tx = wallet.tx_by_slate(&slate).is_some();
if exists_tx {
let mut sl = slate.clone();
sl.state = if sl.state == SlateState::Standard1 {
SlateState::Standard2
} else {
SlateState::Invoice2
};
match wallet.read_slatepack(&sl) {
None => {
self.message_error = Some(
MessageError::Response(
t!("wallets.resp_slatepack_err")
)
);
}
Some(sp) => {
self.response_edit = sp;
}
}
}
}
}
// Setup message slate.
if self.message_error.is_none() {
self.message_slate = Some(slate);
}
// Clear message loading result and status.
{
let mut w_res = self.receive_pay_result.write();
*w_res = None;
}
self.message_loading = false;
} }
}); } else {
let paste = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
View::button(ui, paste, Colors::BUTTON, || {
let buf = cb.get_string_from_buffer();
let previous = self.message_edit.clone();
self.message_edit = buf.clone().trim().to_string();
// Parse Slatepack message resetting message error.
if buf != previous {
self.parse_message(wallet);
}
});
}
} }
}; };
if columns_num == 1 { if columns_num == 1 {
@ -546,52 +600,23 @@ impl WalletMessages {
// Make operation based on incoming state status. // Make operation based on incoming state status.
match slate.state { match slate.state {
SlateState::Standard1 | SlateState::Invoice1 => { SlateState::Standard1 | SlateState::Invoice1 => {
let resp = if slate.state == SlateState::Standard1 { let slate = slate.clone();
wallet.receive(&self.message_edit) let message = self.message_edit.clone();
} else { let message_result = self.receive_pay_result.clone();
wallet.pay(&self.message_edit) let wallet = wallet.clone();
}; // Create response to sender or receiver at separate thread.
if resp.is_ok() { self.message_loading = true;
self.response_edit = resp.unwrap(); thread::spawn(move || {
} else { thread::sleep(Duration::from_millis(3000));
match resp.err().unwrap() { let resp = if slate.state == SlateState::Standard1 {
grin_wallet_libwallet::Error::TransactionWasCancelled {..} => { wallet.receive(&message)
// Set already canceled transaction error message. } else {
self.message_error = Some( wallet.pay(&message)
MessageError::Response(t!("wallets.resp_canceled_err")) };
); let mut w_res = message_result.write();
return; *w_res = Some((slate, resp));
} });
_ => {} return;
}
// Check if tx with same slate id already exists.
let exists_tx = wallet.tx_by_slate(&slate).is_some();
if exists_tx {
let mut sl = slate.clone();
sl.state = if sl.state == SlateState::Standard1 {
SlateState::Standard2
} else {
SlateState::Invoice2
};
match wallet.read_slatepack(&sl) {
None => {
self.message_error = Some(
MessageError::Response(t!("wallets.resp_slatepack_err"))
);
}
Some(sp) => {
self.message_slate = Some(slate);
self.response_edit = sp;
}
}
return;
}
// Set default response error message.
self.message_error = Some(
MessageError::Response(t!("wallets.resp_slatepack_err"))
);
}
} }
SlateState::Standard2 | SlateState::Invoice2 => { SlateState::Standard2 | SlateState::Invoice2 => {
// Check if slatepack with same id and state already exists. // Check if slatepack with same id and state already exists.
@ -640,7 +665,7 @@ impl WalletMessages {
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
ui.add_space(6.0); ui.add_space(6.0);
if self.request_loading.load(Ordering::Relaxed) { if self.request_loading {
ui.add_space(42.0); ui.add_space(42.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
View::big_loading_spinner(ui); View::big_loading_spinner(ui);
@ -649,7 +674,7 @@ impl WalletMessages {
// Check if there is request result error. // Check if there is request result error.
if self.request_error.is_some() { if self.request_error.is_some() {
self.request_loading.store(false, Ordering::Relaxed); self.request_loading = false;
return; return;
} }
@ -677,7 +702,7 @@ impl WalletMessages {
} }
} }
} }
self.request_loading.store(false, Ordering::Relaxed); self.request_loading = false;
} }
} else if self.request_edit.is_empty() { } else if self.request_edit.is_empty() {
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
@ -775,7 +800,7 @@ impl WalletMessages {
let result = self.request_result.clone(); let result = self.request_result.clone();
// Send request at another thread. // Send request at another thread.
self.request_loading.store(true, Ordering::Relaxed); self.request_loading = true;
thread::spawn(move || { thread::spawn(move || {
let message = if send_request { let message = if send_request {
wallet.send(a) wallet.send(a)