diff --git a/locales/de.yml b/locales/de.yml index 9a05bba..0940082 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -152,6 +152,7 @@ transport: conn_error: Verbindungsproblem disconnected: Verbindung getrennt receiver_address: 'Empfängeraddresse:' + sender_address: 'Absenderadresse:' incorrect_addr_err: 'Eingegebene Addresse ist inkorrekt:' tor_send_error: Beim Senden über Tor ist ein Fehler aufgetreten. Stellen Sie sicher, dass der Empfänger online ist. Die Transaktion wurde abgebrochen. tor_autorun_desc: Gibt an, ob beim Öffnen des Wallets der Tor-Dienst gestartet werden soll, um Transaktionen synchron zu empfangen. diff --git a/locales/en.yml b/locales/en.yml index bf04432..1e53e3c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -152,6 +152,7 @@ transport: conn_error: Connection error disconnected: Disconnected receiver_address: 'Address of the receiver:' + sender_address: 'Address of the sender:' incorrect_addr_err: 'Entered address is incorrect:' tor_send_error: An error occurred during sending over Tor, make sure receiver is online, transaction was canceled. tor_autorun_desc: Whether to launch Tor service on wallet opening to receive transactions synchronously. diff --git a/locales/fr.yml b/locales/fr.yml index 1da1aee..9e7e507 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -152,6 +152,7 @@ transport: conn_error: Erreur de connexion disconnected: Déconnecté receiver_address: 'Adresse du destinataire:' + sender_address: "Adresse de l'expéditeur:" incorrect_addr_err: 'Adresse entrée incorrecte:' tor_send_error: "Une erreur s'est produite lors de l'envoi via Tor. Assurez-vous que le destinataire est en ligne, la transaction a été annulée." tor_autorun_desc: "Lancer automatiquement le service Tor à l'ouverture du portefeuille pour recevoir les transactions de manière synchronisée." diff --git a/locales/ru.yml b/locales/ru.yml index d65d576..0fb11e7 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -152,6 +152,7 @@ transport: conn_error: Ошибка подключения disconnected: Отключено receiver_address: 'Адрес получателя:' + sender_address: 'Адрес отправителя:' incorrect_addr_err: 'Введённый адрес неверен:' tor_send_error: Во время отправки через Tor произошла ошибка, убедитесь, что получатель находится онлайн, транзакция была отменена. tor_autorun_desc: Запускать ли Tor сервис при открытии кошелька для синхронного получения транзакций. diff --git a/locales/tr.yml b/locales/tr.yml index b15bd91..d7cf232 100644 --- a/locales/tr.yml +++ b/locales/tr.yml @@ -152,6 +152,7 @@ transport: conn_error: Bagalanti hatasi disconnected: Baglanti yok receiver_address: 'Alicinin adresi:' + sender_address: 'Gönderici adresi:' incorrect_addr_err: 'Girilen adres hatali:' tor_send_error: Tor adresi uzerinden gonderimde aksaklik olustu, alici online olmasi gerek, islem iptal edildi. tor_autorun_desc: Islemleri Tor adresi olarak AL,bunun için cuzdan acilisinda Tor hizmetinin baslatilip baslatilmayacagi. diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index e830520..6e9ec8b 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -152,6 +152,7 @@ transport: conn_error: 连接错误 disconnected: 已断开连接 receiver_address: '接收者的地址:' + sender_address: '发件人地址:' incorrect_addr_err: '输入的地址不正确:' tor_send_error: 通过 Tor 发送时出错,请确保接收方在线, 交易已取消. tor_autorun_desc: 是否在开钱包时启动 Tor 服务以同步接收交易. diff --git a/src/gui/views/camera.rs b/src/gui/views/camera.rs index 49ef280..c8a4d62 100644 --- a/src/gui/views/camera.rs +++ b/src/gui/views/camera.rs @@ -25,8 +25,8 @@ use std::thread; use crate::gui::Colors; use crate::gui::icons::CAMERA_ROTATE; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::View; use crate::gui::views::types::{QrScanResult, QrScanState}; +use crate::gui::views::{Modal, View}; use crate::wallet::WalletUtils; use crate::wallet::types::PhraseSize; @@ -88,6 +88,42 @@ impl CameraContent { ui.ctx().request_repaint(); } + /// Draw modal camera content. + pub fn modal_ui( + &mut self, + ui: &mut egui::Ui, + cb: &dyn PlatformCallbacks, + mut on_result: impl FnMut(Option), + ) { + if let Some(result) = self.qr_scan_result() { + on_result(Some(result)); + } else { + ui.add_space(6.0); + self.ui(ui, cb); + ui.add_space(6.0); + + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + // Show buttons to close modal or come back to sending input. + ui.columns(2, |cols| { + cols[0].vertical_centered_justified(|ui| { + View::button(ui, t!("close"), Colors::white_or_black(false), || { + cb.stop_camera(); + on_result(None); + Modal::close(); + }); + }); + cols[1].vertical_centered_justified(|ui| { + View::button(ui, t!("back"), Colors::white_or_black(false), || { + on_result(None); + }); + }); + }); + ui.add_space(6.0); + } + } + /// Draw camera image. fn image_ui(&mut self, ui: &mut egui::Ui, mut img: DynamicImage, rotation: u32) -> Rect { // Setup image rotation. diff --git a/src/gui/views/wallets/wallet/request/invoice.rs b/src/gui/views/wallets/wallet/request/invoice.rs index 5a1199f..f429a99 100644 --- a/src/gui/views/wallets/wallet/request/invoice.rs +++ b/src/gui/views/wallets/wallet/request/invoice.rs @@ -12,25 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::{Id, RichText}; -use grin_core::core::{amount_from_hr_string, amount_to_hr_string}; - use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, TextEdit, View}; +use crate::gui::views::{CameraContent, Modal, TextEdit, View}; use crate::wallet::Wallet; use crate::wallet::types::WalletTask; +use egui::{Id, RichText}; +use grin_core::core::{amount_from_hr_string, amount_to_hr_string}; +use grin_wallet_libwallet::SlatepackAddress; /// Invoice request creation content. pub struct InvoiceRequestContent { /// Amount to receive. amount_edit: String, + + /// Sender address. + address_edit: String, + /// Flag to check if entered address is incorrect. + address_error: bool, + + /// Address QR code scanner content. + address_scan_content: Option, } impl Default for InvoiceRequestContent { fn default() -> Self { Self { amount_edit: "".to_string(), + address_edit: "".to_string(), + address_error: false, + address_scan_content: None, } } } @@ -50,14 +61,36 @@ impl InvoiceRequestContent { return; } if let Ok(a) = amount_from_hr_string(m.amount_edit.as_str()) { - m.amount_edit = "".to_string(); - wallet.task(WalletTask::Receive(a)); + let addr_str = m.address_edit.as_str(); + let addr = if let Ok(r) = SlatepackAddress::try_from(addr_str.trim()) { + Some(r) + } else { + None + }; + wallet.task(WalletTask::Receive(a, addr)); Modal::close(); } }; ui.add_space(6.0); + // Draw QR code scanner content if requested. + if let Some(content) = self.address_scan_content.as_mut() { + let mut close_scan = true; + content.modal_ui(ui, cb, |result| { + if let Some(result) = result { + self.address_edit = result.text(); + } else { + modal.enable_closing(); + close_scan = true; + } + }); + if close_scan { + self.address_scan_content = None; + } + return; + } + // Draw amount input content. ui.vertical_centered(|ui| { ui.label( @@ -72,11 +105,9 @@ impl InvoiceRequestContent { let amount_edit_before = self.amount_edit.clone(); let mut amount_edit = TextEdit::new(Id::from(modal.id).with(wallet.get_config().id)) .h_center() - .numeric(); + .numeric() + .focus(Modal::first_draw()); amount_edit.ui(ui, &mut self.amount_edit, cb); - if amount_edit.enter_pressed { - on_continue(self); - } // Check value if input was changed. if amount_edit_before != self.amount_edit { @@ -109,7 +140,54 @@ impl InvoiceRequestContent { } } + ui.add_space(8.0); + + // Show address error or input description. + ui.vertical_centered(|ui| { + if self.address_error { + ui.label( + RichText::new(t!("transport.incorrect_addr_err")) + .size(17.0) + .color(Colors::red()), + ); + } else { + ui.label( + RichText::new(t!("transport.sender_address")) + .size(17.0) + .color(Colors::gray()), + ); + } + }); + ui.add_space(6.0); + + // Show address text edit. + let addr_edit_before = self.address_edit.clone(); + let address_edit_id = Id::from(modal.id) + .with("_address") + .with(wallet.get_config().id); + let mut address_edit = TextEdit::new(address_edit_id) + .paste() + .focus(false) + .scan_qr(); + if amount_edit.enter_pressed { + address_edit.focus_request(); + } + address_edit.ui(ui, &mut self.address_edit, cb); + // Check if scan button was pressed. + if address_edit.scan_pressed { + modal.disable_closing(); + self.address_scan_content = Some(CameraContent::default()); + } + ui.add_space(12.0); + // Check value if input was changed. + if addr_edit_before != self.address_edit { + self.address_error = false; + } + // Continue on Enter press. + if address_edit.enter_pressed { + on_continue(self); + } // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); @@ -121,7 +199,6 @@ impl InvoiceRequestContent { t!("modal.cancel"), Colors::white_or_black(false), || { - self.amount_edit = "".to_string(); Modal::close(); }, ); diff --git a/src/gui/views/wallets/wallet/request/send.rs b/src/gui/views/wallets/wallet/request/send.rs index fc3a978..8688d2f 100644 --- a/src/gui/views/wallets/wallet/request/send.rs +++ b/src/gui/views/wallets/wallet/request/send.rs @@ -90,40 +90,18 @@ impl SendRequestContent { ui.add_space(6.0); // Draw QR code scanner content if requested. - if let Some(scanner) = self.address_scan_content.as_mut() { - let on_stop = || { - cb.stop_camera(); - modal.enable_closing(); - }; - - if let Some(result) = scanner.qr_scan_result() { - self.address_edit = result.text(); - on_stop(); + if let Some(content) = self.address_scan_content.as_mut() { + let mut close_scan = true; + content.modal_ui(ui, cb, |result| { + if let Some(result) = result { + self.address_edit = result.text(); + } else { + modal.enable_closing(); + close_scan = true; + } + }); + if close_scan { self.address_scan_content = None; - } else { - ui.add_space(6.0); - scanner.ui(ui, cb); - ui.add_space(6.0); - - // Setup spacing between buttons. - ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - - // Show buttons to close modal or come back to sending input. - ui.columns(2, |cols| { - cols[0].vertical_centered_justified(|ui| { - View::button(ui, t!("close"), Colors::white_or_black(false), || { - on_stop(); - self.close(); - }); - }); - cols[1].vertical_centered_justified(|ui| { - View::button(ui, t!("back"), Colors::white_or_black(false), || { - on_stop(); - self.address_scan_content = None; - }); - }); - }); - ui.add_space(6.0); } return; } diff --git a/src/gui/views/wallets/wallet/txs/tx.rs b/src/gui/views/wallets/wallet/txs/tx.rs index 8d864ae..2530fba 100644 --- a/src/gui/views/wallets/wallet/txs/tx.rs +++ b/src/gui/views/wallets/wallet/txs/tx.rs @@ -168,10 +168,12 @@ impl WalletTransactionContent { cb: &dyn PlatformCallbacks, ) { if self.message.is_none() { - let slatepack_path = wallet - .get_config() - .get_slate_path(tx.data.tx_slate_id.unwrap(), &tx.state); - self.message = Some(fs::read_to_string(slatepack_path).unwrap_or("".to_string())); + if let Some(slate_state) = tx.data.tx_slate_state.as_ref() { + let slatepack_path = wallet + .get_config() + .get_slate_path(tx.data.tx_slate_id.unwrap(), slate_state); + self.message = Some(fs::read_to_string(slatepack_path).unwrap_or("".to_string())); + } } if let Some(m) = &self.message { if m.is_empty() { @@ -260,29 +262,31 @@ impl WalletTransactionContent { }); // Draw button to share response as file. - ui.add_space(8.0); - ui.vertical_centered(|ui| { - let share_text = format!("{} {}", FILE_TEXT, t!("share")); - View::colored_text_button( - ui, - share_text, - Colors::blue(), - Colors::white_or_black(false), - || { - if let Some(slate_id) = tx.data.tx_slate_id { - let name = format!("{}.{}.slatepack", slate_id, tx.state); - let data = m.as_bytes().to_vec(); - cb.share_data(name, data).unwrap_or_default(); - // Show message input or close modal. - if tx.can_finalize() { - finalization_needed = true; - } else { - Modal::close(); - } - } - }, - ); - }); + if let Some(slate_id) = tx.data.tx_slate_id { + if let Some(slate_state) = tx.data.tx_slate_state.as_ref() { + ui.add_space(8.0); + ui.vertical_centered(|ui| { + let share_text = format!("{} {}", FILE_TEXT, t!("share")); + View::colored_text_button( + ui, + share_text, + Colors::blue(), + Colors::white_or_black(false), + || { + let name = format!("{}.{}.slatepack", slate_id, slate_state); + let data = m.as_bytes().to_vec(); + cb.share_data(name, data).unwrap_or_default(); + // Show message input or close modal. + if tx.can_finalize() { + finalization_needed = true; + } else { + Modal::close(); + } + }, + ); + }); + } + } if finalization_needed { Modal::new(MessageInputContent::MODAL_ID) @@ -370,13 +374,13 @@ impl WalletTransactionContent { info_item_ui(ui, kernel.0.to_hex(), label, true, cb); } // Show receiver or sender address. - let addr = if tx.data.tx_type == TxLogEntryType::TxSent { - &tx.receiver + let (addr, label) = if tx.data.tx_type == TxLogEntryType::TxSent { + (&tx.receiver, t!("transport.receiver_address")) } else { - &tx.sender + (&tx.sender, t!("transport.sender_address")) }; if let Some(addr) = addr { - let label = format!("{} {}", CIRCLE_HALF, t!("network_mining.address")); + let label = format!("{} {}", CIRCLE_HALF, label.replace(":", "")); info_item_ui(ui, addr.to_string(), label, true, cb); } } diff --git a/src/wallet/types.rs b/src/wallet/types.rs index 41ffc8e..a69c54d 100644 --- a/src/wallet/types.rs +++ b/src/wallet/types.rs @@ -213,8 +213,6 @@ pub enum WalletTxAction { pub struct WalletTx { /// Information from database. pub data: TxLogEntry, - /// State of transaction Slate. - pub state: SlateState, /// Payment proof. pub(crate) proof: Option, @@ -240,7 +238,6 @@ impl WalletTx { pub fn new( tx: TxLogEntry, proof: Option, - wallet: &Wallet, height: Option, broadcasting_height: Option, action: Option, @@ -263,9 +260,8 @@ impl WalletTx { sender = Some(addr); } } - let mut t = Self { + let t = Self { data: tx, - state: SlateState::Unknown, proof, amount, receiver, @@ -275,15 +271,6 @@ impl WalletTx { action, action_error, }; - // Update Slate state for unconfirmed. - if !t.data.confirmed - && (t.data.tx_type == TxLogEntryType::TxSent - || t.data.tx_type == TxLogEntryType::TxReceived) - { - if let Some(slate_id) = t.data.tx_slate_id { - t.state = wallet.get_slate_state(slate_id, &t.data.tx_type) - } - } t } @@ -294,15 +281,16 @@ impl WalletTx { && (!self.sending_tor() || self.action_error.is_some()) && (self.data.tx_type == TxLogEntryType::TxSent || self.data.tx_type == TxLogEntryType::TxReceived) - && (self.state == SlateState::Invoice1 || self.state == SlateState::Standard1) + && (self.data.tx_slate_state == Some(SlateState::Invoice1) + || self.data.tx_slate_state == Some(SlateState::Standard1)) } /// Check if transaction was finalized. pub fn finalized(&self) -> bool { (self.data.tx_type == TxLogEntryType::TxSent || self.data.tx_type == TxLogEntryType::TxReceived) - && self.state == SlateState::Invoice3 - || self.state == SlateState::Standard3 + && self.data.tx_slate_state == Some(SlateState::Invoice3) + || self.data.tx_slate_state == Some(SlateState::Standard3) } /// Check if transaction is sending over Tor. @@ -420,7 +408,7 @@ pub enum WalletTask { SendTor(TxLogEntry, SlatepackAddress), /// Invoice creation. /// * amount - Receive(u64), + Receive(u64, Option), /// Transaction finalization. /// * tx id Finalize(u32), diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index a26692c..4f66f15 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -45,7 +45,7 @@ use grin_wallet_libwallet::{ VersionedSlate, WalletBackend, WalletInitStatus, WalletInst, WalletLCProvider, address, }; use grin_wallet_util::OnionV3Address; -use log::error; +use log::{debug, error}; use num_bigint::BigInt; use parking_lot::RwLock; use rand::Rng; @@ -936,7 +936,7 @@ impl Wallet { fn create_slatepack_message( &self, slate: &Slate, - _: Option, + address: Option, ) -> Result { let mut message = "".to_string(); let r_inst = self.instance.as_ref().read(); @@ -947,11 +947,11 @@ impl Wallet { self.keychain_mask().as_ref(), Some(&mut api), |api, m| { - // let recipients = match dest { - // Some(a) => vec![a], - // None => vec![], - // }; - message = api.create_slatepack_message(m, &slate, Some(0), vec![])?; + let addrs = match address { + Some(a) => vec![a], + None => vec![], + }; + message = api.create_slatepack_message(m, &slate, Some(0), addrs)?; Ok(()) }, )?; @@ -970,40 +970,6 @@ impl Wallet { fs::exists(slatepack_path).unwrap_or(false) } - /// Get possible state from tx type. - pub fn get_slate_state(&self, slate_id: Uuid, tx_type: &TxLogEntryType) -> SlateState { - let mut slate = Slate::blank(1, false); - slate.id = slate_id; - slate.state = match tx_type { - TxLogEntryType::TxReceived => SlateState::Invoice3, - _ => SlateState::Standard3, - }; - // Transaction was finalized. - if self.slatepack_exists(&slate) { - slate.state - } else { - slate.state = match tx_type { - TxLogEntryType::TxReceived => SlateState::Standard2, - _ => SlateState::Invoice2, - }; - // Transaction signed to be finalized. - if self.slatepack_exists(&slate) { - slate.state - } else { - // Transaction just was created. - slate.state = match tx_type { - TxLogEntryType::TxReceived => SlateState::Invoice1, - _ => SlateState::Standard1, - }; - if self.slatepack_exists(&slate) { - slate.state - } else { - SlateState::Unknown - } - } - } - } - /// Calculate transaction fee for provided amount. fn calculate_fee(&self, a: u64) -> Result { let r_inst = self.instance.as_ref().read(); @@ -1057,7 +1023,7 @@ impl Wallet { controller::owner_single_use(None, keychain_mask.as_ref(), Some(&mut api), |api, m| { let s = api.init_send_tx(m, args)?; // Create Slatepack message response. - let _ = self.create_slatepack_message(&s, dest)?; + let _ = self.create_slatepack_message(&s, None)?; // Lock outputs to for this transaction. api.tx_lock_outputs(m, &s)?; slate = Some(s); @@ -1118,7 +1084,11 @@ impl Wallet { } /// Initialize an invoice transaction to receive amount, return request for funds sender. - fn issue_invoice(&self, amount: u64) -> Result { + fn issue_invoice( + &self, + amount: u64, + address: Option, + ) -> Result { let args = IssueInvoiceTxArgs { dest_acct_name: None, amount, @@ -1130,7 +1100,7 @@ impl Wallet { let slate = api.issue_invoice_tx(self.keychain_mask().as_ref(), args)?; // Create Slatepack message response. - let _ = self.create_slatepack_message(&slate, None)?; + let _ = self.create_slatepack_message(&slate, address)?; Ok(slate) } @@ -1313,11 +1283,12 @@ impl Wallet { fn get_tx_slate(&self, tx_id: u32) -> Option { if let Some(tx) = self.retrieve_tx_by_id(Some(tx_id), None) { if let Some(slate_id) = tx.tx_slate_id { - let slate_state = self.get_slate_state(slate_id, &tx.tx_type); - let slatepack_path = self.get_config().get_slate_path(slate_id, &slate_state); - let msg = fs::read_to_string(slatepack_path).unwrap_or("".to_string()); - if let Ok((slate, _)) = self.parse_slatepack(&msg) { - return Some(slate); + if let Some(slate_state) = tx.tx_slate_state { + let slatepack_path = self.get_config().get_slate_path(slate_id, &slate_state); + let msg = fs::read_to_string(slatepack_path).unwrap_or("".to_string()); + if let Ok((slate, _)) = self.parse_slatepack(&msg) { + return Some(slate); + } } } } @@ -1889,9 +1860,10 @@ async fn handle_task(w: &Wallet, t: WalletTask) { send_tor(tx.clone(), &s, r).await; } } - WalletTask::Receive(a) => { + WalletTask::Receive(amount, address) => { w.invoice_creating.store(true, Ordering::Relaxed); - if let Ok(s) = w.issue_invoice(*a) { + debug!("receive: {} at {:?}", amount, address.clone()); + if let Ok(s) = w.issue_invoice(*amount, address.clone()) { sync_wallet_data(&w, false); let tx = w.retrieve_tx_by_id(None, Some(s.id)); if let Some(tx) = tx { @@ -2143,7 +2115,6 @@ fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> { let mut new = WalletTx::new( tx.clone(), proof.clone(), - wallet, height, broadcasting_height, action,