Open .slatepack file with the app #13
17 changed files with 2051 additions and 2438 deletions
|
@ -30,8 +30,8 @@ use crate::gui::views::View;
|
|||
|
||||
/// QR code image from text.
|
||||
pub struct QrCodeContent {
|
||||
/// Text to create QR code.
|
||||
pub(crate) text: String,
|
||||
/// QR code text.
|
||||
text: String,
|
||||
|
||||
/// Flag to draw animated QR with Uniform Resources
|
||||
/// https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md
|
||||
|
@ -62,18 +62,18 @@ impl QrCodeContent {
|
|||
}
|
||||
|
||||
/// Draw QR code.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, text: String, cb: &dyn PlatformCallbacks) {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
if self.animated {
|
||||
// Show animated QR code.
|
||||
self.animated_ui(ui, text, cb);
|
||||
self.animated_ui(ui, cb);
|
||||
} else {
|
||||
// Show static QR code.
|
||||
self.static_ui(ui, text, cb);
|
||||
self.static_ui(ui, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw animated QR code content.
|
||||
fn animated_ui(&mut self, ui: &mut egui::Ui, text: String, cb: &dyn PlatformCallbacks) {
|
||||
fn animated_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
if !self.has_image() {
|
||||
let space = (ui.available_width() - View::BIG_SPINNER_SIZE) / 2.0;
|
||||
ui.vertical_centered(|ui| {
|
||||
|
@ -84,7 +84,7 @@ impl QrCodeContent {
|
|||
|
||||
// Create multiple vector images from text if not creating.
|
||||
if !self.loading() {
|
||||
self.create_svg_list(text);
|
||||
self.create_svg_list();
|
||||
}
|
||||
} else {
|
||||
let svg_list = {
|
||||
|
@ -111,7 +111,7 @@ impl QrCodeContent {
|
|||
|
||||
// Show QR code text.
|
||||
ui.add_space(6.0);
|
||||
View::ellipsize_text(ui, text.clone(), 16.0, Colors::inactive_text());
|
||||
View::ellipsize_text(ui, self.text.clone(), 16.0, Colors::inactive_text());
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
|
@ -131,7 +131,7 @@ impl QrCodeContent {
|
|||
w_state.exporting = true;
|
||||
}
|
||||
// Create GIF to export.
|
||||
self.create_qr_gif(text, DEFAULT_QR_SIZE as usize);
|
||||
self.create_qr_gif();
|
||||
});
|
||||
} else {
|
||||
ui.vertical_centered(|ui| {
|
||||
|
@ -171,7 +171,7 @@ impl QrCodeContent {
|
|||
}
|
||||
|
||||
/// Draw static QR code content.
|
||||
fn static_ui(&mut self, ui: &mut egui::Ui, text: String, cb: &dyn PlatformCallbacks) {
|
||||
fn static_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
if !self.has_image() {
|
||||
let space = (ui.available_width() - View::BIG_SPINNER_SIZE) / 2.0;
|
||||
ui.vertical_centered(|ui| {
|
||||
|
@ -182,7 +182,7 @@ impl QrCodeContent {
|
|||
|
||||
// Create vector image from text if not creating.
|
||||
if !self.loading() {
|
||||
self.create_svg(text);
|
||||
self.create_svg();
|
||||
}
|
||||
} else {
|
||||
// Create image from SVG data.
|
||||
|
@ -194,7 +194,7 @@ impl QrCodeContent {
|
|||
|
||||
// Show QR code text.
|
||||
ui.add_space(6.0);
|
||||
View::ellipsize_text(ui, text.clone(), 16.0, Colors::inactive_text());
|
||||
View::ellipsize_text(ui, self.text.clone(), 16.0, Colors::inactive_text());
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show button to share QR.
|
||||
|
@ -204,7 +204,8 @@ impl QrCodeContent {
|
|||
share_text,
|
||||
Colors::blue(),
|
||||
Colors::white_or_black(false), || {
|
||||
if let Ok(qr) = QrCode::encode_text(text.as_str(), qrcodegen::QrCodeEcc::Low) {
|
||||
let text = self.text.as_str();
|
||||
if let Ok(qr) = QrCode::encode_text(text, qrcodegen::QrCodeEcc::Low) {
|
||||
if let Some(data) = Self::qr_to_image_data(qr, DEFAULT_QR_SIZE as usize) {
|
||||
let mut png = vec![];
|
||||
let png_enc = PngEncoder::new_with_quality(&mut png,
|
||||
|
@ -267,8 +268,9 @@ impl QrCodeContent {
|
|||
}
|
||||
|
||||
/// Create multiple vector QR code images at separate thread.
|
||||
fn create_svg_list(&self, text: String) {
|
||||
fn create_svg_list(&self) {
|
||||
let qr_state = self.qr_image_state.clone();
|
||||
let text = self.text.clone();
|
||||
thread::spawn(move || {
|
||||
let mut encoder = ur::Encoder::bytes(text.as_bytes(), 100).unwrap();
|
||||
let mut data = Vec::with_capacity(encoder.fragment_count());
|
||||
|
@ -294,8 +296,9 @@ impl QrCodeContent {
|
|||
}
|
||||
|
||||
/// Create vector QR code image at separate thread.
|
||||
fn create_svg(&self, text: String) {
|
||||
fn create_svg(&self) {
|
||||
let qr_state = self.qr_image_state.clone();
|
||||
let text = self.text.clone();
|
||||
thread::spawn(move || {
|
||||
if let Ok(qr) = QrCode::encode_text(text.as_str(), qrcodegen::QrCodeEcc::Low) {
|
||||
let svg = Self::qr_to_svg(qr, 0);
|
||||
|
@ -332,13 +335,14 @@ impl QrCodeContent {
|
|||
}
|
||||
|
||||
/// Create GIF image at separate thread.
|
||||
fn create_qr_gif(&self, text: String, size: usize) {
|
||||
fn create_qr_gif(&self) {
|
||||
{
|
||||
let mut w_state = self.qr_image_state.write();
|
||||
w_state.gif_creating = true;
|
||||
|
||||
}
|
||||
let qr_state = self.qr_image_state.clone();
|
||||
let text = self.text.clone();
|
||||
thread::spawn(move || {
|
||||
// Setup GIF image encoder.
|
||||
let mut gif = vec![];
|
||||
|
@ -354,7 +358,7 @@ impl QrCodeContent {
|
|||
) {
|
||||
// Create an image from QR data.
|
||||
let image = qr.render()
|
||||
.max_dimensions(size as u32, size as u32)
|
||||
.max_dimensions(DEFAULT_QR_SIZE, DEFAULT_QR_SIZE)
|
||||
.dark_color(image::Rgb([0, 0, 0]))
|
||||
.light_color(image::Rgb([255, 255, 255]))
|
||||
.build();
|
||||
|
|
|
@ -148,7 +148,7 @@ impl WalletContent {
|
|||
ui.vertical_centered(|ui| {
|
||||
// Draw wallet tabs.
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.tabs_ui(ui, wallet);
|
||||
self.tabs_ui(ui);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -468,7 +468,7 @@ impl WalletContent {
|
|||
QrScanResult::Slatepack(message) => {
|
||||
// Redirect to messages to handle parsed message.
|
||||
let mut messages =
|
||||
WalletMessages::new(wallet.can_use_dandelion(), Some(message.to_string()));
|
||||
WalletMessages::new(Some(message.to_string()));
|
||||
messages.parse_message(wallet);
|
||||
modal.close();
|
||||
self.current_tab = Box::new(messages);
|
||||
|
@ -477,8 +477,7 @@ impl WalletContent {
|
|||
QrScanResult::Address(receiver) => {
|
||||
if wallet.get_data().unwrap().info.amount_currently_spendable > 0 {
|
||||
// Redirect to send amount with Tor.
|
||||
let addr = wallet.slatepack_address().unwrap();
|
||||
let mut transport = WalletTransport::new(addr.clone());
|
||||
let mut transport = WalletTransport::default();
|
||||
modal.close();
|
||||
transport.show_send_tor_modal(cb, Some(receiver.to_string()));
|
||||
self.current_tab = Box::new(transport);
|
||||
|
@ -528,7 +527,7 @@ impl WalletContent {
|
|||
}
|
||||
|
||||
/// Draw tab buttons in the bottom of the screen.
|
||||
fn tabs_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
fn tabs_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between tabs.
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0);
|
||||
|
@ -547,14 +546,13 @@ impl WalletContent {
|
|||
let is_messages = current_type == WalletTabType::Messages;
|
||||
View::tab_button(ui, CHAT_CIRCLE_TEXT, is_messages, || {
|
||||
self.current_tab = Box::new(
|
||||
WalletMessages::new(wallet.can_use_dandelion(), None)
|
||||
WalletMessages::new(None)
|
||||
);
|
||||
});
|
||||
});
|
||||
columns[2].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, BRIDGE, current_type == WalletTabType::Transport, || {
|
||||
let addr = wallet.slatepack_address().unwrap();
|
||||
self.current_tab = Box::new(WalletTransport::new(addr));
|
||||
self.current_tab = Box::new(WalletTransport::default());
|
||||
});
|
||||
});
|
||||
columns[3].vertical_centered_justified(|ui| {
|
||||
|
|
File diff suppressed because it is too large
Load diff
541
src/gui/views/wallets/wallet/messages/content.rs
Normal file
541
src/gui/views/wallets/wallet/messages/content.rs
Normal file
|
@ -0,0 +1,541 @@
|
|||
// Copyright 2024 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 std::sync::Arc;
|
||||
use std::thread;
|
||||
use egui::{Id, Margin, RichText, ScrollArea};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use grin_core::core::amount_to_hr_string;
|
||||
use grin_wallet_libwallet::{Error, Slate, SlateState};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, DOWNLOAD_SIMPLE, SCAN, UPLOAD_SIMPLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{FilePickButton, Modal, Content, View, CameraContent};
|
||||
use crate::gui::views::types::{ModalPosition, QrScanResult};
|
||||
use crate::gui::views::wallets::wallet::messages::request::MessageRequestModal;
|
||||
use crate::gui::views::wallets::wallet::types::{SLATEPACK_MESSAGE_HINT, WalletTab, WalletTabType};
|
||||
use crate::gui::views::wallets::wallet::{WalletContent, WalletTransactionModal};
|
||||
use crate::wallet::types::WalletTransaction;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Slatepack messages interaction tab content.
|
||||
pub struct WalletMessages {
|
||||
/// Slatepacks message input text.
|
||||
message_edit: String,
|
||||
/// Flag to check if message request is loading.
|
||||
message_loading: bool,
|
||||
/// Error on finalization, parse or response creation.
|
||||
message_error: String,
|
||||
/// Parsed message result with finalization flag and transaction.
|
||||
message_result: Arc<RwLock<Option<(Slate, Result<WalletTransaction, Error>)>>>,
|
||||
|
||||
/// Wallet transaction [`Modal`] content.
|
||||
tx_info_content: Option<WalletTransactionModal>,
|
||||
|
||||
/// Invoice or sending request creation [`Modal`] content.
|
||||
request_modal_content: Option<MessageRequestModal>,
|
||||
|
||||
/// Camera content for Slatepack message QR code scanning [`Modal`].
|
||||
message_camera_content: CameraContent,
|
||||
/// Flag to check if there is an error on scanning Slatepack message QR code at [`Modal`].
|
||||
message_scan_error: bool,
|
||||
|
||||
/// Button to parse picked file content.
|
||||
file_pick_button: FilePickButton,
|
||||
}
|
||||
|
||||
/// Identifier for amount input [`Modal`] to create invoice or sending request.
|
||||
const REQUEST_MODAL: &'static str = "messages_request";
|
||||
|
||||
/// Identifier for [`Modal`] modal to show transaction information.
|
||||
const TX_INFO_MODAL: &'static str = "messages_tx_info";
|
||||
|
||||
/// Identifier for [`Modal`] to scan Slatepack message from QR code.
|
||||
const SCAN_QR_MESSAGE_MODAL: &'static str = "qr_slatepack_message_scan_modal";
|
||||
|
||||
impl WalletTab for WalletMessages {
|
||||
fn get_type(&self) -> WalletTabType {
|
||||
WalletTabType::Messages
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
|
||||
if WalletContent::sync_ui(ui, wallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show modal content for this ui container.
|
||||
self.modal_content_ui(ui, wallet, cb);
|
||||
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: View::item_stroke(),
|
||||
fill: Colors::white_or_black(false),
|
||||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui) + 4.0,
|
||||
right: View::get_right_inset() + 4.0,
|
||||
top: 3.0,
|
||||
bottom: 4.0,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
ScrollArea::vertical()
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.id_source(Id::from("wallet_messages").with(wallet.get_config().id))
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.ui(ui, wallet, cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletMessages {
|
||||
/// Create new content instance, put message into input if provided.
|
||||
pub fn new(message: Option<String>) -> Self {
|
||||
Self {
|
||||
message_edit: message.unwrap_or("".to_string()),
|
||||
message_loading: false,
|
||||
message_error: "".to_string(),
|
||||
message_result: Arc::new(Default::default()),
|
||||
tx_info_content: None,
|
||||
request_modal_content: None,
|
||||
message_camera_content: Default::default(),
|
||||
message_scan_error: false,
|
||||
file_pick_button: FilePickButton::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw manual wallet transaction interaction content.
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(3.0);
|
||||
|
||||
// Show creation of request to send or receive funds.
|
||||
self.request_ui(ui, wallet, cb);
|
||||
|
||||
ui.add_space(12.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show Slatepack message input field.
|
||||
self.input_slatepack_ui(ui, wallet, cb);
|
||||
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw [`Modal`] content for this 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 {
|
||||
REQUEST_MODAL => {
|
||||
if let Some(content) = self.request_modal_content.as_mut() {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
content.ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
TX_INFO_MODAL => {
|
||||
if let Some(content) = self.tx_info_content.as_mut() {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
content.ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
SCAN_QR_MESSAGE_MODAL => {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.qr_message_scan_modal_ui(ui, modal, wallet, cb);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw creation of request to send or receive funds.
|
||||
fn request_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.label(RichText::new(t!("wallets.create_request_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()));
|
||||
ui.add_space(7.0);
|
||||
|
||||
// Show send button only if balance is not empty.
|
||||
let data = wallet.get_data().unwrap();
|
||||
if data.info.amount_currently_spendable > 0 {
|
||||
// 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| {
|
||||
let send_text = format!("{} {}", UPLOAD_SIMPLE, t!("wallets.send"));
|
||||
View::colored_text_button(ui, send_text, Colors::red(), Colors::button(), || {
|
||||
self.show_request_modal(false, cb);
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
self.receive_button_ui(ui, cb);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
self.receive_button_ui(ui, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw invoice request creation button.
|
||||
fn receive_button_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let receive_text = format!("{} {}", DOWNLOAD_SIMPLE, t!("wallets.receive"));
|
||||
View::colored_text_button(ui, receive_text, Colors::green(), Colors::button(), || {
|
||||
self.show_request_modal(true, cb);
|
||||
});
|
||||
}
|
||||
|
||||
/// Show [`Modal`] to create invoice or sending request.
|
||||
fn show_request_modal(&mut self, invoice: bool, cb: &dyn PlatformCallbacks) {
|
||||
self.request_modal_content = Some(MessageRequestModal::new(invoice));
|
||||
let title = if invoice {
|
||||
t!("wallets.receive")
|
||||
} else {
|
||||
t!("wallets.send")
|
||||
};
|
||||
Modal::new(REQUEST_MODAL).position(ModalPosition::CenterTop).title(title).show();
|
||||
cb.show_keyboard();
|
||||
}
|
||||
|
||||
/// Draw Slatepack message input content.
|
||||
fn input_slatepack_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Setup description text.
|
||||
if !self.message_error.is_empty() {
|
||||
ui.label(RichText::new(&self.message_error).size(16.0).color(Colors::red()));
|
||||
} else {
|
||||
ui.label(RichText::new(t!("wallets.input_slatepack_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()));
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(3.0);
|
||||
|
||||
// Save message to check for changes.
|
||||
let message_before = self.message_edit.clone();
|
||||
|
||||
let scroll_id = Id::from("message_input_scroll").with(wallet.get_config().id);
|
||||
ScrollArea::vertical()
|
||||
.id_source(scroll_id)
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.max_height(128.0)
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.add_space(7.0);
|
||||
let input_id = scroll_id.with("_input");
|
||||
let resp = egui::TextEdit::multiline(&mut self.message_edit)
|
||||
.id(input_id)
|
||||
.font(egui::TextStyle::Small)
|
||||
.desired_rows(5)
|
||||
.interactive(!self.message_loading)
|
||||
.hint_text(SLATEPACK_MESSAGE_HINT)
|
||||
.desired_width(f32::INFINITY)
|
||||
.show(ui)
|
||||
.response;
|
||||
// Show soft keyboard on click.
|
||||
if resp.clicked() {
|
||||
resp.request_focus();
|
||||
cb.show_keyboard();
|
||||
}
|
||||
if resp.has_focus() {
|
||||
// Apply text from input on Android as temporary fix for egui.
|
||||
View::on_soft_input(ui, input_id, &mut self.message_edit);
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Parse message if input field was changed.
|
||||
if message_before != self.message_edit {
|
||||
self.parse_message(wallet);
|
||||
}
|
||||
|
||||
if self.message_loading {
|
||||
View::small_loading_spinner(ui);
|
||||
// Check loading result.
|
||||
let has_tx = {
|
||||
let r_res = self.message_result.read();
|
||||
r_res.is_some()
|
||||
};
|
||||
if has_tx {
|
||||
let mut w_res = self.message_result.write();
|
||||
let tx_res = w_res.as_ref().unwrap();
|
||||
let slate = &tx_res.0;
|
||||
match &tx_res.1 {
|
||||
Ok(tx) => {
|
||||
self.message_edit.clear();
|
||||
// Show transaction modal on success.
|
||||
self.tx_info_content = Some(WalletTransactionModal::new(wallet, tx, false));
|
||||
Modal::new(TX_INFO_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.tx"))
|
||||
.show();
|
||||
*w_res = None;
|
||||
}
|
||||
Err(err) => {
|
||||
match err {
|
||||
// Set already canceled transaction error message.
|
||||
Error::TransactionWasCancelled {..} => {
|
||||
self.message_error = t!("wallets.resp_canceled_err");
|
||||
}
|
||||
// Set an error when there is not enough funds to pay.
|
||||
Error::NotEnoughFunds {..} => {
|
||||
let m = t!(
|
||||
"wallets.pay_balance_error",
|
||||
"amount" => amount_to_hr_string(slate.amount, true)
|
||||
);
|
||||
self.message_error = m;
|
||||
}
|
||||
// Set default error message.
|
||||
_ => {
|
||||
let finalize = slate.state == SlateState::Standard2 ||
|
||||
slate.state == SlateState::Invoice2;
|
||||
self.message_error = if finalize {
|
||||
t!("wallets.finalize_slatepack_err")
|
||||
} else {
|
||||
t!("wallets.resp_slatepack_err")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.message_loading = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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| {
|
||||
// Draw button to scan Slatepack message QR code.
|
||||
let scan_text = format!("{} {}", SCAN, t!("scan"));
|
||||
View::button(ui, scan_text, Colors::button(), || {
|
||||
self.message_edit.clear();
|
||||
self.message_error.clear();
|
||||
self.show_qr_message_scan_modal(cb);
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
// Draw button to paste text from clipboard.
|
||||
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);
|
||||
self.parse_message(wallet);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
});
|
||||
|
||||
if self.message_edit.is_empty() {
|
||||
// Draw button to choose file.
|
||||
let mut parsed_text = "".to_string();
|
||||
self.file_pick_button.ui(ui, cb, |text| {
|
||||
parsed_text = text;
|
||||
});
|
||||
self.message_edit = parsed_text;
|
||||
self.parse_message(wallet);
|
||||
} else {
|
||||
// Draw button to clear message input.
|
||||
let clear_text = format!("{} {}", BROOM, t!("clear"));
|
||||
View::button(ui, clear_text, Colors::button(), || {
|
||||
self.message_edit.clear();
|
||||
self.message_error.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse message input making operation based on incoming status.
|
||||
pub fn parse_message(&mut self, wallet: &Wallet) {
|
||||
self.message_error.clear();
|
||||
self.message_edit = self.message_edit.trim().to_string();
|
||||
if self.message_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Ok(mut slate) = wallet.parse_slatepack(&self.message_edit) {
|
||||
// Try to setup empty amount from transaction by id.
|
||||
if slate.amount == 0 {
|
||||
let _ = wallet.get_data().unwrap().txs.as_ref().unwrap().iter().map(|tx| {
|
||||
if tx.data.tx_slate_id == Some(slate.id) {
|
||||
if slate.amount == 0 {
|
||||
slate.amount = tx.amount;
|
||||
}
|
||||
}
|
||||
tx
|
||||
}).collect::<Vec<&WalletTransaction>>();
|
||||
}
|
||||
|
||||
// Check if message with same id and state already exists to show tx modal.
|
||||
let exists = wallet.read_slatepack(&slate).is_some();
|
||||
if exists {
|
||||
if let Some(tx) = wallet.tx_by_slate(&slate).as_ref() {
|
||||
self.message_edit.clear();
|
||||
self.tx_info_content = Some(WalletTransactionModal::new(wallet, tx, false));
|
||||
Modal::new(TX_INFO_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.tx"))
|
||||
.show();
|
||||
} else {
|
||||
self.message_error = t!("wallets.parse_slatepack_err");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Create response or finalize at separate thread.
|
||||
let sl = slate.clone();
|
||||
let message = self.message_edit.clone();
|
||||
let message_result = self.message_result.clone();
|
||||
let wallet = wallet.clone();
|
||||
|
||||
self.message_loading = true;
|
||||
thread::spawn(move || {
|
||||
let result = match slate.state {
|
||||
SlateState::Standard1 | SlateState::Invoice1 => {
|
||||
if sl.state != SlateState::Standard1 {
|
||||
wallet.pay(&message)
|
||||
} else {
|
||||
wallet.receive(&message)
|
||||
}
|
||||
}
|
||||
SlateState::Standard2 | SlateState::Invoice2 => {
|
||||
wallet.finalize(&message)
|
||||
}
|
||||
_ => {
|
||||
if let Some(tx) = wallet.tx_by_slate(&slate) {
|
||||
Ok(tx)
|
||||
} else {
|
||||
Err(Error::GenericError(t!("wallets.parse_slatepack_err")))
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut w_res = message_result.write();
|
||||
*w_res = Some((slate, result));
|
||||
});
|
||||
} else {
|
||||
self.message_error = t!("wallets.parse_slatepack_err");
|
||||
}
|
||||
}
|
||||
|
||||
/// Show QR code Slatepack message scanner [`Modal`].
|
||||
pub fn show_qr_message_scan_modal(&mut self, cb: &dyn PlatformCallbacks) {
|
||||
self.message_scan_error = false;
|
||||
// Show QR code scan modal.
|
||||
Modal::new(SCAN_QR_MESSAGE_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("scan_qr"))
|
||||
.closeable(false)
|
||||
.show();
|
||||
cb.start_camera();
|
||||
}
|
||||
|
||||
/// Draw QR code scanner [`Modal`] content.
|
||||
fn qr_message_scan_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
if self.message_scan_error {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
let err_text = format!("{}", t!("wallets.parse_slatepack_err")).replace(":", ".");
|
||||
ui.label(RichText::new(err_text)
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
|
||||
// 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!("close"), Colors::white_or_black(false), || {
|
||||
self.message_scan_error = false;
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("repeat"), Colors::white_or_black(false), || {
|
||||
Modal::set_title(t!("scan_qr"));
|
||||
self.message_scan_error = false;
|
||||
cb.start_camera();
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
return;
|
||||
} else if let Some(result) = self.message_camera_content.qr_scan_result() {
|
||||
cb.stop_camera();
|
||||
self.message_camera_content.clear_state();
|
||||
match &result {
|
||||
QrScanResult::Slatepack(text) => {
|
||||
self.message_edit = text.to_string();
|
||||
self.parse_message(wallet);
|
||||
modal.close();
|
||||
}
|
||||
_ => {
|
||||
self.message_scan_error = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui.add_space(6.0);
|
||||
self.message_camera_content.ui(ui, cb);
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
cb.stop_camera();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
}
|
18
src/gui/views/wallets/wallet/messages/mod.rs
Normal file
18
src/gui/views/wallets/wallet/messages/mod.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2024 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.
|
||||
|
||||
mod content;
|
||||
pub use content::*;
|
||||
|
||||
mod request;
|
260
src/gui/views/wallets/wallet/messages/request.rs
Normal file
260
src/gui/views/wallets/wallet/messages/request.rs
Normal file
|
@ -0,0 +1,260 @@
|
|||
// Copyright 2024 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 std::sync::Arc;
|
||||
use std::thread;
|
||||
use parking_lot::RwLock;
|
||||
use egui::{Id, RichText};
|
||||
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
|
||||
use grin_wallet_libwallet::Error;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::views::types::TextEditOptions;
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionModal;
|
||||
use crate::wallet::types::WalletTransaction;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Invoice or sending request creation [`Modal`] content.
|
||||
pub struct MessageRequestModal {
|
||||
/// Flag to check if invoice or sending request was opened.
|
||||
invoice: bool,
|
||||
|
||||
/// Amount to send or 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<WalletTransactionModal>,
|
||||
}
|
||||
|
||||
impl MessageRequestModal {
|
||||
/// Create new content instance.
|
||||
pub fn new(invoice: bool) -> Self {
|
||||
Self {
|
||||
invoice,
|
||||
amount_edit: "".to_string(),
|
||||
request_loading: false,
|
||||
request_result: Arc::new(RwLock::new(None)),
|
||||
request_error: None,
|
||||
result_tx_content: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut 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;
|
||||
}
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw content on request loading.
|
||||
if self.request_loading {
|
||||
self.loading_request_ui(ui, wallet, modal);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw amount input content.
|
||||
self.amount_input_ui(ui, wallet, modal, cb);
|
||||
|
||||
// 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.
|
||||
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_or_black(false), || {
|
||||
self.amount_edit = "".to_string();
|
||||
self.request_error = None;
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
// Button to create Slatepack message request.
|
||||
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
|
||||
if self.amount_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
|
||||
cb.hide_keyboard();
|
||||
modal.disable_closing();
|
||||
// Setup data for request.
|
||||
let wallet = wallet.clone();
|
||||
let invoice = self.invoice.clone();
|
||||
let result = self.request_result.clone();
|
||||
// Send request at another thread.
|
||||
self.request_loading = true;
|
||||
thread::spawn(move || {
|
||||
let res = if invoice {
|
||||
wallet.issue_invoice(a)
|
||||
} else {
|
||||
wallet.send(a)
|
||||
};
|
||||
let mut w_result = result.write();
|
||||
*w_result = Some(res);
|
||||
});
|
||||
} else {
|
||||
let err = if self.invoice {
|
||||
t!("wallets.invoice_slatepack_err")
|
||||
} else {
|
||||
t!("wallets.send_slatepack_err")
|
||||
};
|
||||
self.request_error = Some(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw amount input content.
|
||||
fn amount_input_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.vertical_centered(|ui| {
|
||||
let enter_text = if self.invoice {
|
||||
t!("wallets.enter_amount_receive")
|
||||
} else {
|
||||
let data = wallet.get_data().unwrap();
|
||||
let amount = amount_to_hr_string(data.info.amount_currently_spendable, true);
|
||||
t!("wallets.enter_amount_send","amount" => amount)
|
||||
};
|
||||
ui.label(RichText::new(enter_text)
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw request amount text input.
|
||||
let amount_edit_id = Id::from(modal.id).with(wallet.get_config().id);
|
||||
let mut amount_edit_opts = TextEditOptions::new(amount_edit_id).h_center();
|
||||
let amount_edit_before = self.amount_edit.clone();
|
||||
View::text_edit(ui, cb, &mut self.amount_edit, &mut amount_edit_opts);
|
||||
|
||||
// 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()) {
|
||||
Ok(a) => {
|
||||
if !self.amount_edit.contains(".") {
|
||||
// To avoid input of several "0".
|
||||
if a == 0 {
|
||||
self.amount_edit = "0".to_string();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Check input after ".".
|
||||
let parts = self.amount_edit
|
||||
.split(".")
|
||||
.collect::<Vec<&str>>();
|
||||
if parts.len() == 2 && parts[1].len() > 9 {
|
||||
self.amount_edit = amount_edit_before;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Do not input amount more than balance in sending.
|
||||
if !self.invoice {
|
||||
let b = wallet.get_data().unwrap().info.amount_currently_spendable;
|
||||
if b < a {
|
||||
self.amount_edit = amount_edit_before;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
self.amount_edit = amount_edit_before;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw loading request content.
|
||||
fn loading_request_ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, 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(WalletTransactionModal::new(wallet, tx, false));
|
||||
}
|
||||
Err(err) => {
|
||||
match err {
|
||||
Error::NotEnoughFunds { .. } => {
|
||||
let m = t!(
|
||||
"wallets.pay_balance_error",
|
||||
"amount" => self.amount_edit
|
||||
);
|
||||
self.request_error = Some(m);
|
||||
}
|
||||
_ => {
|
||||
let m = if self.invoice {
|
||||
t!("wallets.invoice_slatepack_err")
|
||||
} else {
|
||||
t!("wallets.send_slatepack_err")
|
||||
};
|
||||
self.request_error = Some(m);
|
||||
}
|
||||
}
|
||||
self.request_loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ pub struct CommonSettings {
|
|||
new_pass_edit: String,
|
||||
|
||||
/// Minimum confirmations number value.
|
||||
min_confirmations_edit: String
|
||||
min_confirmations_edit: String,
|
||||
}
|
||||
|
||||
/// Identifier for wallet name [`Modal`].
|
||||
|
@ -54,25 +54,26 @@ impl Default for CommonSettings {
|
|||
wrong_pass: false,
|
||||
old_pass_edit: "".to_string(),
|
||||
new_pass_edit: "".to_string(),
|
||||
min_confirmations_edit: "".to_string()
|
||||
min_confirmations_edit: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonSettings {
|
||||
/// Draw common wallet settings content.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
|
||||
// Show modal content for this ui container.
|
||||
self.modal_content_ui(ui, wallet, cb);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
let wallet_name = wallet.get_config().name;
|
||||
let config = wallet.get_config();
|
||||
// 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_name.clone())
|
||||
ui.label(RichText::new(&config.name)
|
||||
.size(16.0)
|
||||
.color(Colors::white_or_black(true)));
|
||||
ui.add_space(8.0);
|
||||
|
@ -80,7 +81,7 @@ impl CommonSettings {
|
|||
// Show wallet name setup.
|
||||
let name_text = format!("{} {}", PENCIL, t!("change"));
|
||||
View::button(ui, name_text, Colors::button(), || {
|
||||
self.name_edit = wallet_name;
|
||||
self.name_edit = config.name;
|
||||
// Show wallet name modal.
|
||||
Modal::new(NAME_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
|
@ -118,10 +119,9 @@ impl CommonSettings {
|
|||
ui.add_space(6.0);
|
||||
|
||||
// Show minimum amount of confirmations value setup.
|
||||
let min_confirmations = wallet.get_config().min_confirmations;
|
||||
let min_conf_text = format!("{} {}", CLOCK_COUNTDOWN, min_confirmations);
|
||||
let min_conf_text = format!("{} {}", CLOCK_COUNTDOWN, config.min_confirmations);
|
||||
View::button(ui, min_conf_text, Colors::button(), || {
|
||||
self.min_confirmations_edit = min_confirmations.to_string();
|
||||
self.min_confirmations_edit = config.min_confirmations.to_string();
|
||||
// Show minimum amount of confirmations value modal.
|
||||
Modal::new(MIN_CONFIRMATIONS_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
|
@ -131,8 +131,15 @@ impl CommonSettings {
|
|||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Setup ability to post wallet transactions with Dandelion.
|
||||
View::checkbox(ui, wallet.can_use_dandelion(), t!("wallets.use_dandelion"), || {
|
||||
wallet.update_use_dandelion(!wallet.can_use_dandelion());
|
||||
});
|
||||
|
||||
ui.add_space(6.0);
|
||||
View::horizontal_line(ui, Colors::stroke());
|
||||
ui.add_space(4.0);
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -232,7 +232,7 @@ impl RecoverySettings {
|
|||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, "OK".to_owned(), Colors::white_or_black(false), || {
|
||||
let mut on_next = || {
|
||||
match wallet.get_recovery(self.pass_edit.clone()) {
|
||||
Ok(phrase) => {
|
||||
self.wrong_pass = false;
|
||||
|
@ -243,6 +243,12 @@ impl RecoverySettings {
|
|||
self.wrong_pass = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
View::on_enter_key(ui, || {
|
||||
(on_next)();
|
||||
});
|
||||
View::button(ui, "OK".to_owned(), Colors::white_or_black(false), || {
|
||||
on_next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,944 +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.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea};
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use parking_lot::RwLock;
|
||||
use tor_rtcompat::BlockOn;
|
||||
use tor_rtcompat::tokio::TokioNativeTlsRuntime;
|
||||
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
|
||||
use grin_wallet_libwallet::SlatepackAddress;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK_CIRCLE, COPY, DOTS_THREE_CIRCLE, EXPORT, GEAR_SIX, GLOBE_SIMPLE, POWER, QR_CODE, SHIELD_CHECKERED, SHIELD_SLASH, WARNING_CIRCLE, X_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, Modal, QrCodeContent, Content, View};
|
||||
use crate::gui::views::types::{ModalPosition, TextEditOptions};
|
||||
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
|
||||
use crate::gui::views::wallets::wallet::WalletContent;
|
||||
use crate::tor::{Tor, TorBridge, TorConfig};
|
||||
use crate::wallet::types::WalletData;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Wallet transport tab content.
|
||||
pub struct WalletTransport {
|
||||
/// Flag to check if transaction is sending over Tor to show progress at [`Modal`].
|
||||
tor_sending: Arc<RwLock<bool>>,
|
||||
/// Flag to check if error occurred during sending of transaction over Tor at [`Modal`].
|
||||
tor_send_error: Arc<RwLock<bool>>,
|
||||
/// Flag to check if transaction sent successfully over Tor [`Modal`].
|
||||
tor_success: Arc<RwLock<bool>>,
|
||||
/// Entered amount value for [`Modal`].
|
||||
amount_edit: String,
|
||||
/// Entered address value for [`Modal`].
|
||||
address_edit: String,
|
||||
/// Flag to check if entered address is incorrect at [`Modal`].
|
||||
address_error: bool,
|
||||
/// Flag to check if QR code scanner is opened at address [`Modal`].
|
||||
show_address_scan: bool,
|
||||
/// Address QR code scanner [`Modal`] content.
|
||||
address_scan_content: CameraContent,
|
||||
/// Flag to check if [`Modal`] was just opened to focus on first field.
|
||||
modal_just_opened: bool,
|
||||
|
||||
/// QR code address image [`Modal`] content.
|
||||
qr_address_content: QrCodeContent,
|
||||
|
||||
/// Flag to check if Tor settings were changed.
|
||||
tor_settings_changed: bool,
|
||||
/// Tor bridge binary path edit text.
|
||||
bridge_bin_path_edit: String,
|
||||
/// Tor bridge connection line edit text.
|
||||
bridge_conn_line_edit: String,
|
||||
/// Flag to check if QR code scanner is opened at bridge [`Modal`].
|
||||
show_bridge_scan: bool,
|
||||
/// Address QR code scanner [`Modal`] content.
|
||||
bridge_qr_scan_content: CameraContent,
|
||||
}
|
||||
|
||||
impl WalletTab for WalletTransport {
|
||||
fn get_type(&self) -> WalletTabType {
|
||||
WalletTabType::Transport
|
||||
}
|
||||
|
||||
fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
if WalletContent::sync_ui(ui, wallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show modal content for this ui container.
|
||||
self.modal_content_ui(ui, wallet, cb);
|
||||
|
||||
// Show transport content panel.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: View::item_stroke(),
|
||||
fill: Colors::white_or_black(false),
|
||||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui) + 4.0,
|
||||
right: View::get_right_inset() + 4.0,
|
||||
top: 3.0,
|
||||
bottom: 4.0,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
ScrollArea::vertical()
|
||||
.id_source(Id::from("wallet_transport").with(wallet.get_config().id))
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.ui(ui, wallet, cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier for [`Modal`] to send amount over Tor.
|
||||
const SEND_TOR_MODAL: &'static str = "send_tor_modal";
|
||||
|
||||
/// Identifier for [`Modal`] to setup Tor service.
|
||||
const TOR_SETTINGS_MODAL: &'static str = "tor_settings_modal";
|
||||
|
||||
/// Identifier for [`Modal`] to show QR code address image.
|
||||
const QR_ADDRESS_MODAL: &'static str = "qr_address_modal";
|
||||
|
||||
impl WalletTransport {
|
||||
/// Create new content instance from provided Slatepack address text.
|
||||
pub fn new(addr: String) -> Self {
|
||||
// Setup Tor bridge binary path edit text.
|
||||
let bridge = TorConfig::get_bridge();
|
||||
let (bin_path, conn_line) = if let Some(b) = bridge {
|
||||
(b.binary_path(), b.connection_line())
|
||||
} else {
|
||||
("".to_string(), "".to_string())
|
||||
};
|
||||
Self {
|
||||
tor_sending: Arc::new(RwLock::new(false)),
|
||||
tor_send_error: Arc::new(RwLock::new(false)),
|
||||
tor_success: Arc::new(RwLock::new(false)),
|
||||
amount_edit: "".to_string(),
|
||||
address_edit: "".to_string(),
|
||||
address_error: false,
|
||||
show_address_scan: false,
|
||||
address_scan_content: CameraContent::default(),
|
||||
modal_just_opened: false,
|
||||
qr_address_content: QrCodeContent::new(addr, false),
|
||||
tor_settings_changed: false,
|
||||
bridge_bin_path_edit: bin_path,
|
||||
bridge_conn_line_edit: conn_line,
|
||||
show_bridge_scan: false,
|
||||
bridge_qr_scan_content: CameraContent::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw wallet transport content.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(3.0);
|
||||
ui.label(RichText::new(t!("transport.desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()));
|
||||
ui.add_space(7.0);
|
||||
|
||||
// Draw Tor content.
|
||||
self.tor_ui(ui, wallet, cb);
|
||||
}
|
||||
|
||||
/// Draw [`Modal`] content for this 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 {
|
||||
SEND_TOR_MODAL => {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.send_tor_modal_ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
TOR_SETTINGS_MODAL => {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.tor_settings_modal_ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
QR_ADDRESS_MODAL => {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.qr_address_modal_ui(ui, modal, cb);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw Tor transport content.
|
||||
fn tor_ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
|
||||
let data = wallet.get_data().unwrap();
|
||||
|
||||
// Draw header content.
|
||||
self.tor_header_ui(ui, wallet);
|
||||
|
||||
// Draw receive info content.
|
||||
if wallet.slatepack_address().is_some() {
|
||||
self.tor_receive_ui(ui, wallet, &data, cb);
|
||||
}
|
||||
|
||||
// Draw send content.
|
||||
if data.info.amount_currently_spendable > 0 && wallet.foreign_api_port().is_some() {
|
||||
self.tor_send_ui(ui, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw Tor transport header content.
|
||||
fn tor_header_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(78.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(0, 2, false);
|
||||
ui.painter().rect(bg_rect, item_rounding, Colors::button(), View::item_stroke());
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to setup Tor transport.
|
||||
let button_rounding = View::item_rounding(0, 2, true);
|
||||
View::item_button(ui, button_rounding, GEAR_SIX, None, || {
|
||||
self.show_tor_settings_modal();
|
||||
});
|
||||
|
||||
// Draw button to enable/disable Tor listener for current wallet.
|
||||
let service_id = &wallet.identifier();
|
||||
if !Tor::is_service_starting(service_id) && wallet.foreign_api_port().is_some() {
|
||||
if !Tor::is_service_running(service_id) {
|
||||
View::item_button(ui, Rounding::default(), POWER, Some(Colors::green()), || {
|
||||
if let Ok(key) = wallet.secret_key() {
|
||||
let api_port = wallet.foreign_api_port().unwrap();
|
||||
Tor::start_service(api_port, key, service_id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
View::item_button(ui, Rounding::default(), POWER, Some(Colors::red()), || {
|
||||
Tor::stop_service(service_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
ui.label(RichText::new(t!("transport.tor_network"))
|
||||
.size(18.0)
|
||||
.color(Colors::title(false)));
|
||||
});
|
||||
|
||||
// Setup Tor status text.
|
||||
let is_running = Tor::is_service_running(service_id);
|
||||
let is_starting = Tor::is_service_starting(service_id);
|
||||
let has_error = Tor::is_service_failed(service_id);
|
||||
let (icon, text) = if wallet.foreign_api_port().is_none() {
|
||||
(DOTS_THREE_CIRCLE, t!("wallets.loading"))
|
||||
} else if is_starting {
|
||||
(DOTS_THREE_CIRCLE, t!("transport.connecting"))
|
||||
} else if has_error {
|
||||
(WARNING_CIRCLE, t!("transport.conn_error"))
|
||||
} else if is_running {
|
||||
(CHECK_CIRCLE, t!("transport.connected"))
|
||||
} else {
|
||||
(X_CIRCLE, t!("transport.disconnected"))
|
||||
};
|
||||
let status_text = format!("{} {}", icon, text);
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::text(false)));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup bridges status text.
|
||||
let bridge = TorConfig::get_bridge();
|
||||
let bridges_text = match &bridge {
|
||||
None => {
|
||||
format!("{} {}", SHIELD_SLASH, t!("transport.bridges_disabled"))
|
||||
}
|
||||
Some(b) => {
|
||||
let name = b.protocol_name().to_uppercase();
|
||||
format!("{} {}",
|
||||
SHIELD_CHECKERED,
|
||||
t!("transport.bridge_name", "b" = name))
|
||||
}
|
||||
};
|
||||
|
||||
ui.label(RichText::new(bridges_text).size(15.0).color(Colors::gray()));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Show Tor transport settings [`Modal`].
|
||||
fn show_tor_settings_modal(&mut self) {
|
||||
self.tor_settings_changed = false;
|
||||
// Show Tor settings modal.
|
||||
Modal::new(TOR_SETTINGS_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("transport.tor_settings"))
|
||||
.closeable(false)
|
||||
.show();
|
||||
}
|
||||
|
||||
/// Draw Tor transport settings [`Modal`] content.
|
||||
fn tor_settings_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw QR code scanner content if requested.
|
||||
if self.show_bridge_scan {
|
||||
let mut on_stop = |content: &mut CameraContent| {
|
||||
cb.stop_camera();
|
||||
content.clear_state();
|
||||
modal.enable_closing();
|
||||
self.show_bridge_scan = false;
|
||||
};
|
||||
|
||||
if let Some(result) = self.bridge_qr_scan_content.qr_scan_result() {
|
||||
self.bridge_conn_line_edit = result.text();
|
||||
on_stop(&mut self.bridge_qr_scan_content);
|
||||
cb.show_keyboard();
|
||||
} else {
|
||||
self.bridge_qr_scan_content.ui(ui, cb);
|
||||
ui.add_space(12.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(&mut self.bridge_qr_scan_content);
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
cols[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("back"), Colors::white_or_black(false), || {
|
||||
on_stop(&mut self.bridge_qr_scan_content);
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not show bridges setup on Android.
|
||||
let os = OperatingSystem::from_target_os();
|
||||
let show_bridges = os != OperatingSystem::Android;
|
||||
if show_bridges {
|
||||
let bridge = TorConfig::get_bridge();
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.bridges_desc"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
|
||||
// Draw checkbox to enable/disable bridges.
|
||||
View::checkbox(ui, bridge.is_some(), t!("transport.bridges"), || {
|
||||
// Save value.
|
||||
let value = if bridge.is_some() {
|
||||
None
|
||||
} else {
|
||||
let default_bridge = TorConfig::get_obfs4();
|
||||
self.bridge_bin_path_edit = default_bridge.binary_path();
|
||||
self.bridge_conn_line_edit = default_bridge.connection_line();
|
||||
Some(default_bridge)
|
||||
};
|
||||
TorConfig::save_bridge(value);
|
||||
self.tor_settings_changed = true;
|
||||
});
|
||||
});
|
||||
|
||||
// Draw bridges selection and path.
|
||||
if bridge.is_some() {
|
||||
let current_bridge = bridge.unwrap();
|
||||
let mut bridge = current_bridge.clone();
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
// Draw Obfs4 bridge selector.
|
||||
let obfs4 = TorConfig::get_obfs4();
|
||||
let name = obfs4.protocol_name().to_uppercase();
|
||||
View::radio_value(ui, &mut bridge, obfs4, name);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
// Draw Snowflake bridge selector.
|
||||
let snowflake = TorConfig::get_snowflake();
|
||||
let name = snowflake.protocol_name().to_uppercase();
|
||||
View::radio_value(ui, &mut bridge, snowflake, name);
|
||||
});
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Check if bridge type was changed to save.
|
||||
if current_bridge != bridge {
|
||||
self.tor_settings_changed = true;
|
||||
TorConfig::save_bridge(Some(bridge.clone()));
|
||||
self.bridge_bin_path_edit = bridge.binary_path();
|
||||
self.bridge_conn_line_edit = bridge.connection_line();
|
||||
}
|
||||
|
||||
// Draw binary path text edit.
|
||||
let bin_edit_id = Id::from(modal.id)
|
||||
.with(wallet.get_config().id)
|
||||
.with("_bin_edit");
|
||||
let mut bin_edit_opts = TextEditOptions::new(bin_edit_id)
|
||||
.paste()
|
||||
.no_focus();
|
||||
let bin_edit_before = self.bridge_bin_path_edit.clone();
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.bin_file"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
ui.add_space(6.0);
|
||||
View::text_edit(ui, cb, &mut self.bridge_bin_path_edit, &mut bin_edit_opts);
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
|
||||
// Draw connection line text edit.
|
||||
let conn_edit_before = self.bridge_conn_line_edit.clone();
|
||||
let conn_edit_id = Id::from(modal.id)
|
||||
.with(wallet.get_config().id)
|
||||
.with("_conn_edit");
|
||||
let mut conn_edit_opts = TextEditOptions::new(conn_edit_id)
|
||||
.paste()
|
||||
.no_focus()
|
||||
.scan_qr();
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.conn_line"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
ui.add_space(6.0);
|
||||
View::text_edit(ui, cb, &mut self.bridge_conn_line_edit, &mut conn_edit_opts);
|
||||
// Check if scan button was pressed.
|
||||
if conn_edit_opts.scan_pressed {
|
||||
cb.hide_keyboard();
|
||||
modal.disable_closing();
|
||||
conn_edit_opts.scan_pressed = false;
|
||||
self.show_bridge_scan = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Check if bin path or connection line text was changed to save bridge.
|
||||
if conn_edit_before != self.bridge_conn_line_edit ||
|
||||
bin_edit_before != self.bridge_bin_path_edit {
|
||||
let bin_path = self.bridge_bin_path_edit.trim().to_string();
|
||||
let conn_line = self.bridge_conn_line_edit.trim().to_string();
|
||||
let b = match bridge {
|
||||
TorBridge::Snowflake(_, _) => {
|
||||
TorBridge::Snowflake(bin_path, conn_line)
|
||||
},
|
||||
TorBridge::Obfs4(_, _) => {
|
||||
TorBridge::Obfs4(bin_path, conn_line)
|
||||
}
|
||||
};
|
||||
TorConfig::save_bridge(Some(b));
|
||||
self.tor_settings_changed = true;
|
||||
}
|
||||
|
||||
ui.add_space(2.0);
|
||||
}
|
||||
|
||||
ui.add_space(6.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.tor_autorun_desc"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
|
||||
// Show Tor service autorun checkbox.
|
||||
let autorun = wallet.auto_start_tor_listener();
|
||||
View::checkbox(ui, autorun, t!("network.autorun"), || {
|
||||
wallet.update_auto_start_tor_listener(!autorun);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
if self.tor_settings_changed {
|
||||
self.tor_settings_changed = false;
|
||||
// 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() {
|
||||
let api_port = wallet.foreign_api_port().unwrap();
|
||||
Tor::restart_service(api_port, key, service_id);
|
||||
}
|
||||
} else {
|
||||
Tor::rebuild_client();
|
||||
}
|
||||
}
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw Tor receive content.
|
||||
fn tor_receive_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
data: &WalletData,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
let slatepack_addr = wallet.slatepack_address().unwrap();
|
||||
let service_id = &wallet.identifier();
|
||||
let can_send = data.info.amount_currently_spendable > 0;
|
||||
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(52.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = if can_send {
|
||||
View::item_rounding(1, 3, false)
|
||||
} else {
|
||||
View::item_rounding(1, 2, false)
|
||||
};
|
||||
ui.painter().rect(bg_rect, item_rounding, Colors::button(), View::item_stroke());
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to setup Tor transport.
|
||||
let button_rounding = if can_send {
|
||||
View::item_rounding(1, 3, true)
|
||||
} else {
|
||||
View::item_rounding(1, 2, true)
|
||||
};
|
||||
View::item_button(ui, button_rounding, QR_CODE, None, || {
|
||||
// Show QR code image address modal.
|
||||
self.qr_address_content.clear_state();
|
||||
Modal::new(QR_ADDRESS_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_mining.address"))
|
||||
.show();
|
||||
});
|
||||
|
||||
// Show button to enable/disable Tor listener for current wallet.
|
||||
View::item_button(ui, Rounding::default(), COPY, None, || {
|
||||
cb.copy_string_to_buffer(slatepack_addr.clone());
|
||||
});
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
|
||||
// Show wallet Slatepack address.
|
||||
let address_color = if Tor::is_service_starting(service_id) ||
|
||||
wallet.foreign_api_port().is_none() {
|
||||
Colors::inactive_text()
|
||||
} else if Tor::is_service_running(service_id) {
|
||||
Colors::green()
|
||||
} else {
|
||||
Colors::red()
|
||||
};
|
||||
View::ellipsize_text(ui, slatepack_addr, 15.0, address_color);
|
||||
|
||||
let address_label = format!("{} {}",
|
||||
GLOBE_SIMPLE,
|
||||
t!("network_mining.address"));
|
||||
ui.label(RichText::new(address_label).size(15.0).color(Colors::gray()));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw QR code image address [`Modal`] content.
|
||||
fn qr_address_modal_ui(&mut self, ui: &mut egui::Ui, m: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw QR code content.
|
||||
let text = self.qr_address_content.text.clone();
|
||||
self.qr_address_content.ui(ui, text.clone(), cb);
|
||||
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
self.qr_address_content.clear_state();
|
||||
m.close();
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw Tor send content.
|
||||
fn tor_send_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(55.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(1, 2, false);
|
||||
ui.painter().rect(bg_rect, item_rounding, Colors::fill(), View::item_stroke());
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::top_down(Align::Center), |ui| {
|
||||
ui.add_space(7.0);
|
||||
// Draw button to open sending modal.
|
||||
let send_text = format!("{} {}", EXPORT, t!("wallets.send"));
|
||||
View::button(ui, send_text, Colors::white_or_black(false), || {
|
||||
self.show_send_tor_modal(cb, None);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Show [`Modal`] to send over Tor.
|
||||
pub fn show_send_tor_modal(&mut self, cb: &dyn PlatformCallbacks, address: Option<String>) {
|
||||
{
|
||||
let mut w_send_err = self.tor_send_error.write();
|
||||
*w_send_err = false;
|
||||
let mut w_sending = self.tor_sending.write();
|
||||
*w_sending = false;
|
||||
let mut w_success = self.tor_success.write();
|
||||
*w_success = false;
|
||||
}
|
||||
self.modal_just_opened = true;
|
||||
self.amount_edit = "".to_string();
|
||||
self.address_edit = address.unwrap_or("".to_string());
|
||||
self.address_error = false;
|
||||
// Show modal.
|
||||
Modal::new(SEND_TOR_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.send"))
|
||||
.show();
|
||||
cb.show_keyboard();
|
||||
}
|
||||
|
||||
/// Check if error occurred during sending over Tor at [`Modal`].
|
||||
fn has_tor_send_error(&self) -> bool {
|
||||
let r_send_err = self.tor_send_error.read();
|
||||
r_send_err.clone()
|
||||
}
|
||||
|
||||
/// Check if transaction is sending over Tor to show progress at [`Modal`].
|
||||
fn tor_sending(&self) -> bool {
|
||||
let r_sending = self.tor_sending.read();
|
||||
r_sending.clone()
|
||||
}
|
||||
|
||||
/// Check if transaction sent over Tor with success at [`Modal`].
|
||||
fn tor_success(&self) -> bool {
|
||||
let r_success = self.tor_success.read();
|
||||
r_success.clone()
|
||||
}
|
||||
|
||||
/// Draw amount input [`Modal`] content to send over Tor.
|
||||
fn send_tor_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
|
||||
let has_send_err = self.has_tor_send_error();
|
||||
let sending = self.tor_sending();
|
||||
if !has_send_err && !sending {
|
||||
// Draw QR code scanner content if requested.
|
||||
if self.show_address_scan {
|
||||
let mut on_stop = |content: &mut CameraContent| {
|
||||
cb.stop_camera();
|
||||
content.clear_state();
|
||||
modal.enable_closing();
|
||||
self.show_address_scan = false;
|
||||
};
|
||||
|
||||
if let Some(result) = self.address_scan_content.qr_scan_result() {
|
||||
self.address_edit = result.text();
|
||||
self.modal_just_opened = true;
|
||||
on_stop(&mut self.address_scan_content);
|
||||
cb.show_keyboard();
|
||||
} else {
|
||||
self.address_scan_content.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(&mut self.address_scan_content);
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
cols[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("back"), Colors::white_or_black(false), || {
|
||||
self.modal_just_opened = true;
|
||||
on_stop(&mut self.address_scan_content);
|
||||
cb.show_keyboard();
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
let data = wallet.get_data().unwrap();
|
||||
let amount = amount_to_hr_string(data.info.amount_currently_spendable, true);
|
||||
let enter_text = t!("wallets.enter_amount_send","amount" => amount);
|
||||
ui.label(RichText::new(enter_text)
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw amount text edit.
|
||||
let amount_edit_id = Id::from(modal.id).with("amount").with(wallet.get_config().id);
|
||||
let mut amount_edit_opts = TextEditOptions::new(amount_edit_id).h_center().no_focus();
|
||||
let amount_edit_before = self.amount_edit.clone();
|
||||
if self.modal_just_opened {
|
||||
self.modal_just_opened = false;
|
||||
amount_edit_opts.focus = true;
|
||||
}
|
||||
View::text_edit(ui, cb, &mut self.amount_edit, &mut amount_edit_opts);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Check value if input was changed.
|
||||
if amount_edit_before != self.amount_edit {
|
||||
if !self.amount_edit.is_empty() {
|
||||
// Trim text, replace "," by "." and parse amount.
|
||||
self.amount_edit = self.amount_edit.trim().replace(",", ".");
|
||||
match amount_from_hr_string(self.amount_edit.as_str()) {
|
||||
Ok(a) => {
|
||||
if !self.amount_edit.contains(".") {
|
||||
// To avoid input of several "0".
|
||||
if a == 0 {
|
||||
self.amount_edit = "0".to_string();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Check input after ".".
|
||||
let parts = self.amount_edit.split(".").collect::<Vec<&str>>();
|
||||
if parts.len() == 2 && parts[1].len() > 9 {
|
||||
self.amount_edit = amount_edit_before;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Do not input amount more than balance in sending.
|
||||
let b = wallet.get_data().unwrap().info.amount_currently_spendable;
|
||||
if b < a {
|
||||
self.amount_edit = amount_edit_before;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
self.amount_edit = amount_edit_before;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.receiver_address"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
}
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw 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_opts = TextEditOptions::new(address_edit_id)
|
||||
.paste()
|
||||
.no_focus()
|
||||
.scan_qr();
|
||||
View::text_edit(ui, cb, &mut self.address_edit, &mut address_edit_opts);
|
||||
// Check if scan button was pressed.
|
||||
if address_edit_opts.scan_pressed {
|
||||
cb.hide_keyboard();
|
||||
modal.disable_closing();
|
||||
address_edit_opts.scan_pressed = false;
|
||||
self.show_address_scan = true;
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Check value if input was changed.
|
||||
if addr_edit_before != self.address_edit {
|
||||
self.address_error = false;
|
||||
}
|
||||
|
||||
// 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_or_black(false), || {
|
||||
self.amount_edit = "".to_string();
|
||||
self.address_edit = "".to_string();
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
|
||||
if self.amount_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check entered address.
|
||||
let addr_str = self.address_edit.as_str();
|
||||
if let Ok(addr) = SlatepackAddress::try_from(addr_str) {
|
||||
// Parse amount and send over Tor.
|
||||
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
|
||||
cb.hide_keyboard();
|
||||
modal.disable_closing();
|
||||
let mut w_sending = self.tor_sending.write();
|
||||
*w_sending = true;
|
||||
{
|
||||
let send_error = self.tor_send_error.clone();
|
||||
let send_success = self.tor_success.clone();
|
||||
let mut wallet = wallet.clone();
|
||||
thread::spawn(move || {
|
||||
let runtime = TokioNativeTlsRuntime::create().unwrap();
|
||||
runtime
|
||||
.block_on(async {
|
||||
if wallet.send_tor(a, &addr)
|
||||
.await
|
||||
.is_some() {
|
||||
let mut w_send_success = send_success.write();
|
||||
*w_send_success = true;
|
||||
} else {
|
||||
let mut w_send_error = send_error.write();
|
||||
*w_send_error = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.address_error = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
} else if has_send_err {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.tor_send_error"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
|
||||
// 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_or_black(false), || {
|
||||
self.amount_edit = "".to_string();
|
||||
self.address_edit = "".to_string();
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("repeat"), Colors::white_or_black(false), || {
|
||||
// Parse amount and send over Tor.
|
||||
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
|
||||
let mut w_send_error = self.tor_send_error.write();
|
||||
*w_send_error = false;
|
||||
let mut w_sending = self.tor_sending.write();
|
||||
*w_sending = true;
|
||||
{
|
||||
let addr_text = self.address_edit.clone();
|
||||
let send_error = self.tor_send_error.clone();
|
||||
let send_success = self.tor_success.clone();
|
||||
let mut wallet = wallet.clone();
|
||||
thread::spawn(move || {
|
||||
let runtime = TokioNativeTlsRuntime::create().unwrap();
|
||||
runtime
|
||||
.block_on(async {
|
||||
let addr_str = addr_text.as_str();
|
||||
let addr = &SlatepackAddress::try_from(addr_str)
|
||||
.unwrap();
|
||||
if wallet.send_tor(a, &addr)
|
||||
.await
|
||||
.is_some() {
|
||||
let mut w_send_success = send_success.write();
|
||||
*w_send_success = true;
|
||||
} else {
|
||||
let mut w_send_error = send_error.write();
|
||||
*w_send_error = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
} else {
|
||||
ui.add_space(16.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
View::small_loading_spinner(ui);
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("transport.tor_sending", "amount" => self.amount_edit))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Close modal on success sending.
|
||||
if self.tor_success() {
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
397
src/gui/views/wallets/wallet/transport/content.rs
Normal file
397
src/gui/views/wallets/wallet/transport/content.rs
Normal file
|
@ -0,0 +1,397 @@
|
|||
// 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, Margin, RichText, Rounding, ScrollArea};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK_CIRCLE, COPY, DOTS_THREE_CIRCLE, EXPORT, GEAR_SIX, GLOBE_SIMPLE, POWER, QR_CODE, SHIELD_CHECKERED, SHIELD_SLASH, WARNING_CIRCLE, X_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, QrCodeContent, Content, View};
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::wallet::transport::send::TransportSendModal;
|
||||
use crate::gui::views::wallets::wallet::transport::settings::TransportSettingsModal;
|
||||
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
|
||||
use crate::gui::views::wallets::wallet::WalletContent;
|
||||
use crate::tor::{Tor, TorConfig};
|
||||
use crate::wallet::types::WalletData;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Wallet transport tab content.
|
||||
pub struct WalletTransport {
|
||||
/// Sending [`Modal`] content.
|
||||
send_modal_content: Option<TransportSendModal>,
|
||||
|
||||
/// QR code address image [`Modal`] content.
|
||||
qr_address_content: Option<QrCodeContent>,
|
||||
|
||||
/// Tor settings [`Modal`] content.
|
||||
settings_modal_content: Option<TransportSettingsModal>,
|
||||
}
|
||||
|
||||
impl WalletTab for WalletTransport {
|
||||
fn get_type(&self) -> WalletTabType {
|
||||
WalletTabType::Transport
|
||||
}
|
||||
|
||||
fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
if WalletContent::sync_ui(ui, wallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show modal content for this ui container.
|
||||
self.modal_content_ui(ui, wallet, cb);
|
||||
|
||||
// Show transport content panel.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: View::item_stroke(),
|
||||
fill: Colors::white_or_black(false),
|
||||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui) + 4.0,
|
||||
right: View::get_right_inset() + 4.0,
|
||||
top: 3.0,
|
||||
bottom: 4.0,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
ScrollArea::vertical()
|
||||
.id_source(Id::from("wallet_transport").with(wallet.get_config().id))
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.ui(ui, wallet, cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier for [`Modal`] to send amount over Tor.
|
||||
const SEND_TOR_MODAL: &'static str = "send_tor_modal";
|
||||
|
||||
/// Identifier for [`Modal`] to setup Tor service.
|
||||
const TOR_SETTINGS_MODAL: &'static str = "tor_settings_modal";
|
||||
|
||||
/// Identifier for [`Modal`] to show QR code address image.
|
||||
const QR_ADDRESS_MODAL: &'static str = "qr_address_modal";
|
||||
|
||||
impl Default for WalletTransport {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
send_modal_content: None,
|
||||
qr_address_content: None,
|
||||
settings_modal_content: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletTransport {
|
||||
/// Draw wallet transport content.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(3.0);
|
||||
ui.label(RichText::new(t!("transport.desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()));
|
||||
ui.add_space(7.0);
|
||||
|
||||
// Draw Tor transport content.
|
||||
self.tor_ui(ui, wallet, cb);
|
||||
}
|
||||
|
||||
/// Draw [`Modal`] content for this 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 {
|
||||
SEND_TOR_MODAL => {
|
||||
if let Some(content) = self.send_modal_content.as_mut() {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
content.ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
TOR_SETTINGS_MODAL => {
|
||||
if let Some(content) = self.settings_modal_content.as_mut() {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
content.ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
QR_ADDRESS_MODAL => {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.qr_address_modal_ui(ui, modal, cb);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw Tor transport content.
|
||||
fn tor_ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
|
||||
let data = wallet.get_data().unwrap();
|
||||
|
||||
// Draw header content.
|
||||
self.tor_header_ui(ui, wallet);
|
||||
|
||||
// Draw receive info content.
|
||||
if wallet.slatepack_address().is_some() {
|
||||
self.tor_receive_ui(ui, wallet, &data, cb);
|
||||
}
|
||||
|
||||
// Draw send content.
|
||||
let service_id = &wallet.identifier();
|
||||
if data.info.amount_currently_spendable > 0 && wallet.foreign_api_port().is_some() &&
|
||||
!Tor::is_service_starting(service_id) {
|
||||
self.tor_send_ui(ui, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw Tor transport header content.
|
||||
fn tor_header_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(78.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(0, 2, false);
|
||||
ui.painter().rect(bg_rect, item_rounding, Colors::button(), View::item_stroke());
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to setup Tor transport.
|
||||
let button_rounding = View::item_rounding(0, 2, true);
|
||||
View::item_button(ui, button_rounding, GEAR_SIX, None, || {
|
||||
self.settings_modal_content = Some(TransportSettingsModal::default());
|
||||
// Show Tor settings modal.
|
||||
Modal::new(TOR_SETTINGS_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("transport.tor_settings"))
|
||||
.closeable(false)
|
||||
.show();
|
||||
});
|
||||
|
||||
// Draw button to enable/disable Tor listener for current wallet.
|
||||
let service_id = &wallet.identifier();
|
||||
if !Tor::is_service_starting(service_id) && wallet.foreign_api_port().is_some() {
|
||||
if !Tor::is_service_running(service_id) {
|
||||
View::item_button(ui, Rounding::default(), POWER, Some(Colors::green()), || {
|
||||
if let Ok(key) = wallet.secret_key() {
|
||||
let api_port = wallet.foreign_api_port().unwrap();
|
||||
Tor::start_service(api_port, key, service_id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
View::item_button(ui, Rounding::default(), POWER, Some(Colors::red()), || {
|
||||
Tor::stop_service(service_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
ui.label(RichText::new(t!("transport.tor_network"))
|
||||
.size(18.0)
|
||||
.color(Colors::title(false)));
|
||||
});
|
||||
|
||||
// Setup Tor status text.
|
||||
let is_running = Tor::is_service_running(service_id);
|
||||
let is_starting = Tor::is_service_starting(service_id);
|
||||
let has_error = Tor::is_service_failed(service_id);
|
||||
let (icon, text) = if wallet.foreign_api_port().is_none() {
|
||||
(DOTS_THREE_CIRCLE, t!("wallets.loading"))
|
||||
} else if is_starting {
|
||||
(DOTS_THREE_CIRCLE, t!("transport.connecting"))
|
||||
} else if has_error {
|
||||
(WARNING_CIRCLE, t!("transport.conn_error"))
|
||||
} else if is_running {
|
||||
(CHECK_CIRCLE, t!("transport.connected"))
|
||||
} else {
|
||||
(X_CIRCLE, t!("transport.disconnected"))
|
||||
};
|
||||
let status_text = format!("{} {}", icon, text);
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::text(false)));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup bridges status text.
|
||||
let bridge = TorConfig::get_bridge();
|
||||
let bridges_text = match &bridge {
|
||||
None => {
|
||||
format!("{} {}", SHIELD_SLASH, t!("transport.bridges_disabled"))
|
||||
}
|
||||
Some(b) => {
|
||||
let name = b.protocol_name().to_uppercase();
|
||||
format!("{} {}",
|
||||
SHIELD_CHECKERED,
|
||||
t!("transport.bridge_name", "b" = name))
|
||||
}
|
||||
};
|
||||
|
||||
ui.label(RichText::new(bridges_text).size(15.0).color(Colors::gray()));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw Tor receive content.
|
||||
fn tor_receive_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
data: &WalletData,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
let addr = wallet.slatepack_address().unwrap();
|
||||
let service_id = &wallet.identifier();
|
||||
let can_send = data.info.amount_currently_spendable > 0;
|
||||
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(52.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = if can_send {
|
||||
View::item_rounding(1, 3, false)
|
||||
} else {
|
||||
View::item_rounding(1, 2, false)
|
||||
};
|
||||
ui.painter().rect(bg_rect, item_rounding, Colors::button(), View::item_stroke());
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to setup Tor transport.
|
||||
let button_rounding = if can_send {
|
||||
View::item_rounding(1, 3, true)
|
||||
} else {
|
||||
View::item_rounding(1, 2, true)
|
||||
};
|
||||
View::item_button(ui, button_rounding, QR_CODE, None, || {
|
||||
// Show QR code image address modal.
|
||||
self.qr_address_content = Some(QrCodeContent::new(addr.clone(), false));
|
||||
Modal::new(QR_ADDRESS_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_mining.address"))
|
||||
.show();
|
||||
});
|
||||
|
||||
// Show button to enable/disable Tor listener for current wallet.
|
||||
View::item_button(ui, Rounding::default(), COPY, None, || {
|
||||
cb.copy_string_to_buffer(addr.clone());
|
||||
});
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
|
||||
// Show wallet Slatepack address.
|
||||
let address_color = if Tor::is_service_starting(service_id) ||
|
||||
wallet.foreign_api_port().is_none() {
|
||||
Colors::inactive_text()
|
||||
} else if Tor::is_service_running(service_id) {
|
||||
Colors::green()
|
||||
} else {
|
||||
Colors::red()
|
||||
};
|
||||
View::ellipsize_text(ui, addr, 15.0, address_color);
|
||||
|
||||
let address_label = format!("{} {}",
|
||||
GLOBE_SIMPLE,
|
||||
t!("network_mining.address"));
|
||||
ui.label(RichText::new(address_label).size(15.0).color(Colors::gray()));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw QR code image address [`Modal`] content.
|
||||
fn qr_address_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw QR code content.
|
||||
if let Some(content) = self.qr_address_content.as_mut() {
|
||||
content.ui(ui, cb);
|
||||
} else {
|
||||
modal.close();
|
||||
return;
|
||||
}
|
||||
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
self.qr_address_content = None;
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw Tor send content.
|
||||
fn tor_send_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(55.0);
|
||||
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(1, 2, false);
|
||||
ui.painter().rect(bg_rect, item_rounding, Colors::fill(), View::item_stroke());
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::top_down(Align::Center), |ui| {
|
||||
ui.add_space(7.0);
|
||||
// Draw button to open sending modal.
|
||||
let send_text = format!("{} {}", EXPORT, t!("wallets.send"));
|
||||
View::button(ui, send_text, Colors::white_or_black(false), || {
|
||||
self.show_send_tor_modal(cb, None);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Show [`Modal`] to send over Tor.
|
||||
pub fn show_send_tor_modal(&mut self, cb: &dyn PlatformCallbacks, address: Option<String>) {
|
||||
self.send_modal_content = Some(TransportSendModal::new(address));
|
||||
// Show modal.
|
||||
Modal::new(SEND_TOR_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.send"))
|
||||
.show();
|
||||
cb.show_keyboard();
|
||||
}
|
||||
}
|
19
src/gui/views/wallets/wallet/transport/mod.rs
Normal file
19
src/gui/views/wallets/wallet/transport/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2024 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.
|
||||
|
||||
mod content;
|
||||
pub use content::*;
|
||||
|
||||
mod send;
|
||||
mod settings;
|
357
src/gui/views/wallets/wallet/transport/send.rs
Normal file
357
src/gui/views/wallets/wallet/transport/send.rs
Normal file
|
@ -0,0 +1,357 @@
|
|||
// Copyright 2024 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 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::{Error, SlatepackAddress};
|
||||
use parking_lot::RwLock;
|
||||
use tor_rtcompat::BlockOn;
|
||||
use tor_rtcompat::tokio::TokioNativeTlsRuntime;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
|
||||
use crate::gui::views::{CameraContent, Modal, View};
|
||||
use crate::gui::views::types::TextEditOptions;
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionModal;
|
||||
use crate::wallet::types::WalletTransaction;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Transport sending [`Modal`] content.
|
||||
pub struct TransportSendModal {
|
||||
/// Flag to focus on first input field after opening.
|
||||
first_draw: bool,
|
||||
|
||||
/// Flag to check if transaction is sending to show progress.
|
||||
sending: bool,
|
||||
/// Flag to check if there is an error to repeat.
|
||||
error: bool,
|
||||
/// Transaction result.
|
||||
send_result: Arc<RwLock<Option<Result<WalletTransaction, Error>>>>,
|
||||
|
||||
/// Entered amount value.
|
||||
amount_edit: String,
|
||||
/// Entered address value.
|
||||
address_edit: String,
|
||||
/// Flag to check if entered address is incorrect.
|
||||
address_error: bool,
|
||||
|
||||
/// Address QR code scanner content.
|
||||
address_scan_content: Option<CameraContent>,
|
||||
|
||||
/// Transaction information content.
|
||||
tx_info_content: Option<WalletTransactionModal>,
|
||||
}
|
||||
|
||||
impl TransportSendModal {
|
||||
/// Create new instance from provided address.
|
||||
pub fn new(addr: Option<String>) -> Self {
|
||||
Self {
|
||||
first_draw: true,
|
||||
sending: false,
|
||||
error: false,
|
||||
send_result: Arc::new(RwLock::new(None)),
|
||||
amount_edit: "".to_string(),
|
||||
address_edit: addr.unwrap_or("".to_string()),
|
||||
address_error: false,
|
||||
address_scan_content: None,
|
||||
tx_info_content: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Draw transaction information on request result.
|
||||
if let Some(tx) = self.tx_info_content.as_mut() {
|
||||
tx.ui(ui, wallet, modal, cb);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw sending content, progress or an error.
|
||||
if self.sending {
|
||||
self.progress_ui(ui, wallet);
|
||||
} else if self.error {
|
||||
self.error_ui(ui, wallet, modal, cb);
|
||||
} else {
|
||||
self.content_ui(ui, wallet, modal, cb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw content to send.
|
||||
fn content_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Draw QR code scanner content if requested.
|
||||
if let Some(scanner) = self.address_scan_content.as_mut() {
|
||||
let mut on_stop = || {
|
||||
self.first_draw = true;
|
||||
cb.stop_camera();
|
||||
modal.enable_closing();
|
||||
};
|
||||
|
||||
if let Some(result) = scanner.qr_scan_result() {
|
||||
self.address_edit = result.text();
|
||||
on_stop();
|
||||
self.address_scan_content = None;
|
||||
cb.show_keyboard();
|
||||
} else {
|
||||
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.address_scan_content = None;
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
cols[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("back"), Colors::white_or_black(false), || {
|
||||
on_stop();
|
||||
self.address_scan_content = None;
|
||||
cb.show_keyboard();
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
let data = wallet.get_data().unwrap();
|
||||
let amount = amount_to_hr_string(data.info.amount_currently_spendable, true);
|
||||
let enter_text = t!("wallets.enter_amount_send","amount" => amount);
|
||||
ui.label(RichText::new(enter_text)
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw amount text edit.
|
||||
let amount_edit_id = Id::from(modal.id).with("amount").with(wallet.get_config().id);
|
||||
let mut amount_edit_opts = TextEditOptions::new(amount_edit_id).h_center().no_focus();
|
||||
let amount_edit_before = self.amount_edit.clone();
|
||||
if self.first_draw {
|
||||
self.first_draw = false;
|
||||
amount_edit_opts.focus = true;
|
||||
}
|
||||
View::text_edit(ui, cb, &mut self.amount_edit, &mut amount_edit_opts);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Check value if input was changed.
|
||||
if amount_edit_before != self.amount_edit {
|
||||
if !self.amount_edit.is_empty() {
|
||||
// Trim text, replace "," by "." and parse amount.
|
||||
self.amount_edit = self.amount_edit.trim().replace(",", ".");
|
||||
match amount_from_hr_string(self.amount_edit.as_str()) {
|
||||
Ok(a) => {
|
||||
if !self.amount_edit.contains(".") {
|
||||
// To avoid input of several "0".
|
||||
if a == 0 {
|
||||
self.amount_edit = "0".to_string();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Check input after ".".
|
||||
let parts = self.amount_edit.split(".").collect::<Vec<&str>>();
|
||||
if parts.len() == 2 && parts[1].len() > 9 {
|
||||
self.amount_edit = amount_edit_before;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Do not input amount more than balance in sending.
|
||||
let b = wallet.get_data().unwrap().info.amount_currently_spendable;
|
||||
if b < a {
|
||||
self.amount_edit = amount_edit_before;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
self.amount_edit = amount_edit_before;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.receiver_address"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
}
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw 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_opts = TextEditOptions::new(address_edit_id)
|
||||
.paste()
|
||||
.no_focus()
|
||||
.scan_qr();
|
||||
View::text_edit(ui, cb, &mut self.address_edit, &mut address_edit_opts);
|
||||
// Check if scan button was pressed.
|
||||
if address_edit_opts.scan_pressed {
|
||||
cb.hide_keyboard();
|
||||
modal.disable_closing();
|
||||
address_edit_opts.scan_pressed = false;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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_or_black(false), || {
|
||||
self.close(modal, cb);
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
|
||||
self.send(wallet, modal, cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw error content.
|
||||
fn error_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.tor_send_error"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
|
||||
// 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_or_black(false), || {
|
||||
self.close(modal, cb);
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("repeat"), Colors::white_or_black(false), || {
|
||||
self.send(wallet, modal, cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Close modal and clear data.
|
||||
fn close(&mut self, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
self.amount_edit = "".to_string();
|
||||
self.address_edit = "".to_string();
|
||||
|
||||
let mut w_res = self.send_result.write();
|
||||
*w_res = None;
|
||||
|
||||
self.tx_info_content = None;
|
||||
self.address_scan_content = None;
|
||||
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
}
|
||||
|
||||
/// Send entered amount to address.
|
||||
fn send(&mut self, wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
if self.amount_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
let addr_str = self.address_edit.as_str();
|
||||
if let Ok(addr) = SlatepackAddress::try_from(addr_str) {
|
||||
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
|
||||
cb.hide_keyboard();
|
||||
modal.disable_closing();
|
||||
// Send amount over Tor.
|
||||
let mut wallet = wallet.clone();
|
||||
let res = self.send_result.clone();
|
||||
self.sending = true;
|
||||
thread::spawn(move || {
|
||||
let runtime = TokioNativeTlsRuntime::create().unwrap();
|
||||
runtime
|
||||
.block_on(async {
|
||||
let result = wallet.send_tor(a, &addr).await;
|
||||
let mut w_res = res.write();
|
||||
*w_res = Some(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
self.address_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw sending progress content.
|
||||
fn progress_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
ui.add_space(16.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
View::small_loading_spinner(ui);
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("transport.tor_sending", "amount" => self.amount_edit))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Check sending result.
|
||||
let has_result = {
|
||||
let r_result = self.send_result.read();
|
||||
r_result.is_some()
|
||||
};
|
||||
if has_result {
|
||||
{
|
||||
let res = self.send_result.read().clone().unwrap();
|
||||
match res {
|
||||
Ok(tx) => {
|
||||
self.tx_info_content = Some(WalletTransactionModal::new(wallet, &tx, false));
|
||||
}
|
||||
Err(_) => {
|
||||
self.error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut w_res = self.send_result.write();
|
||||
*w_res = None;
|
||||
self.sending = false;
|
||||
}
|
||||
}
|
||||
}
|
258
src/gui/views/wallets/wallet/transport/settings.rs
Normal file
258
src/gui/views/wallets/wallet/transport/settings.rs
Normal file
|
@ -0,0 +1,258 @@
|
|||
// Copyright 2024 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::os::OperatingSystem;
|
||||
use egui::{Id, RichText};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, Modal, View};
|
||||
use crate::gui::views::types::TextEditOptions;
|
||||
use crate::tor::{Tor, TorBridge, TorConfig};
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Transport settings [`Modal`] content.
|
||||
pub struct TransportSettingsModal {
|
||||
/// Flag to check if Tor settings were changed.
|
||||
settings_changed: bool,
|
||||
|
||||
/// Tor bridge binary path edit text.
|
||||
bridge_bin_path_edit: String,
|
||||
/// Tor bridge connection line edit text.
|
||||
bridge_conn_line_edit: String,
|
||||
/// Address QR code scanner [`Modal`] content.
|
||||
bridge_qr_scan_content: Option<CameraContent>,
|
||||
}
|
||||
|
||||
impl Default for TransportSettingsModal {
|
||||
fn default() -> Self {
|
||||
// Setup Tor bridge binary path edit text.
|
||||
let bridge = TorConfig::get_bridge();
|
||||
let (bin_path, conn_line) = if let Some(b) = bridge {
|
||||
(b.binary_path(), b.connection_line())
|
||||
} else {
|
||||
("".to_string(), "".to_string())
|
||||
};
|
||||
Self {
|
||||
settings_changed: false,
|
||||
bridge_bin_path_edit: bin_path,
|
||||
bridge_conn_line_edit: conn_line,
|
||||
bridge_qr_scan_content: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransportSettingsModal {
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw QR code scanner content if requested.
|
||||
if let Some(scanner) = self.bridge_qr_scan_content.as_mut() {
|
||||
let on_stop = || {
|
||||
cb.stop_camera();
|
||||
modal.enable_closing();
|
||||
};
|
||||
|
||||
if let Some(result) = scanner.qr_scan_result() {
|
||||
self.bridge_conn_line_edit = result.text();
|
||||
on_stop();
|
||||
self.bridge_qr_scan_content = None;
|
||||
cb.show_keyboard();
|
||||
} else {
|
||||
scanner.ui(ui, cb);
|
||||
ui.add_space(12.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.bridge_qr_scan_content = None;
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
cols[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("back"), Colors::white_or_black(false), || {
|
||||
on_stop();
|
||||
self.bridge_qr_scan_content = None;
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not show bridges setup on Android.
|
||||
let os = OperatingSystem::from_target_os();
|
||||
let show_bridges = os != OperatingSystem::Android;
|
||||
if show_bridges {
|
||||
let bridge = TorConfig::get_bridge();
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.bridges_desc"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
|
||||
// Draw checkbox to enable/disable bridges.
|
||||
View::checkbox(ui, bridge.is_some(), t!("transport.bridges"), || {
|
||||
// Save value.
|
||||
let value = if bridge.is_some() {
|
||||
None
|
||||
} else {
|
||||
let default_bridge = TorConfig::get_obfs4();
|
||||
self.bridge_bin_path_edit = default_bridge.binary_path();
|
||||
self.bridge_conn_line_edit = default_bridge.connection_line();
|
||||
Some(default_bridge)
|
||||
};
|
||||
TorConfig::save_bridge(value);
|
||||
self.settings_changed = true;
|
||||
});
|
||||
});
|
||||
|
||||
// Draw bridges selection and path.
|
||||
if bridge.is_some() {
|
||||
let current_bridge = bridge.unwrap();
|
||||
let mut bridge = current_bridge.clone();
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
// Draw Obfs4 bridge selector.
|
||||
let obfs4 = TorConfig::get_obfs4();
|
||||
let name = obfs4.protocol_name().to_uppercase();
|
||||
View::radio_value(ui, &mut bridge, obfs4, name);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
// Draw Snowflake bridge selector.
|
||||
let snowflake = TorConfig::get_snowflake();
|
||||
let name = snowflake.protocol_name().to_uppercase();
|
||||
View::radio_value(ui, &mut bridge, snowflake, name);
|
||||
});
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Check if bridge type was changed to save.
|
||||
if current_bridge != bridge {
|
||||
self.settings_changed = true;
|
||||
TorConfig::save_bridge(Some(bridge.clone()));
|
||||
self.bridge_bin_path_edit = bridge.binary_path();
|
||||
self.bridge_conn_line_edit = bridge.connection_line();
|
||||
}
|
||||
|
||||
// Draw binary path text edit.
|
||||
let bin_edit_id = Id::from(modal.id)
|
||||
.with(wallet.get_config().id)
|
||||
.with("_bin_edit");
|
||||
let mut bin_edit_opts = TextEditOptions::new(bin_edit_id)
|
||||
.paste()
|
||||
.no_focus();
|
||||
let bin_edit_before = self.bridge_bin_path_edit.clone();
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.bin_file"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
ui.add_space(6.0);
|
||||
View::text_edit(ui, cb, &mut self.bridge_bin_path_edit, &mut bin_edit_opts);
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
|
||||
// Draw connection line text edit.
|
||||
let conn_edit_before = self.bridge_conn_line_edit.clone();
|
||||
let conn_edit_id = Id::from(modal.id)
|
||||
.with(wallet.get_config().id)
|
||||
.with("_conn_edit");
|
||||
let mut conn_edit_opts = TextEditOptions::new(conn_edit_id)
|
||||
.paste()
|
||||
.no_focus()
|
||||
.scan_qr();
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.conn_line"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
ui.add_space(6.0);
|
||||
View::text_edit(ui, cb, &mut self.bridge_conn_line_edit, &mut conn_edit_opts);
|
||||
// Check if scan button was pressed.
|
||||
if conn_edit_opts.scan_pressed {
|
||||
cb.hide_keyboard();
|
||||
modal.disable_closing();
|
||||
conn_edit_opts.scan_pressed = false;
|
||||
self.bridge_qr_scan_content = Some(CameraContent::default());
|
||||
}
|
||||
});
|
||||
|
||||
// Check if bin path or connection line text was changed to save bridge.
|
||||
if conn_edit_before != self.bridge_conn_line_edit ||
|
||||
bin_edit_before != self.bridge_bin_path_edit {
|
||||
let bin_path = self.bridge_bin_path_edit.trim().to_string();
|
||||
let conn_line = self.bridge_conn_line_edit.trim().to_string();
|
||||
let b = match bridge {
|
||||
TorBridge::Snowflake(_, _) => {
|
||||
TorBridge::Snowflake(bin_path, conn_line)
|
||||
},
|
||||
TorBridge::Obfs4(_, _) => {
|
||||
TorBridge::Obfs4(bin_path, conn_line)
|
||||
}
|
||||
};
|
||||
TorConfig::save_bridge(Some(b));
|
||||
self.settings_changed = true;
|
||||
}
|
||||
|
||||
ui.add_space(2.0);
|
||||
}
|
||||
|
||||
ui.add_space(6.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.tor_autorun_desc"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
|
||||
// Show Tor service autorun checkbox.
|
||||
let autorun = wallet.auto_start_tor_listener();
|
||||
View::checkbox(ui, autorun, t!("network.autorun"), || {
|
||||
wallet.update_auto_start_tor_listener(!autorun);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
if self.settings_changed {
|
||||
self.settings_changed = false;
|
||||
// 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() {
|
||||
let api_port = wallet.foreign_api_port().unwrap();
|
||||
Tor::restart_service(api_port, key, service_id);
|
||||
}
|
||||
} else {
|
||||
Tor::rebuild_client();
|
||||
}
|
||||
}
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ use grin_core::core::amount_to_hr_string;
|
|||
use grin_wallet_libwallet::TxLogEntryType;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, ARROW_CLOCKWISE, BRIDGE, CALENDAR_CHECK, CHAT_CIRCLE_TEXT, CHECK, CHECK_CIRCLE, DOTS_THREE_CIRCLE, FILE_TEXT, GEAR_FINE, PROHIBIT, X_CIRCLE};
|
||||
use crate::gui::icons::{ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, BRIDGE, CALENDAR_CHECK, CHAT_CIRCLE_TEXT, CHECK, CHECK_CIRCLE, DOTS_THREE_CIRCLE, FILE_TEXT, GEAR_FINE, PROHIBIT, X_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, PullToRefresh, Content, View};
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
|
@ -226,19 +226,6 @@ impl WalletTransactions {
|
|||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
// Draw button to repost transaction.
|
||||
if tx.can_repost(data) {
|
||||
let r = Rounding::default();
|
||||
let (icon, color) = (ARROW_CLOCKWISE, Colors::green());
|
||||
View::item_button(ui, r, icon, Some(color), || {
|
||||
cb.hide_keyboard();
|
||||
// Post tx after getting slate from slatepack file.
|
||||
if let Some((s, _)) = wallet.read_slate_by_tx(tx) {
|
||||
let _ = wallet.post(&s, wallet.can_use_dandelion());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -249,7 +236,7 @@ impl WalletTransactions {
|
|||
if refresh_resp.should_refresh() {
|
||||
self.manual_sync = Some(now);
|
||||
if !wallet.syncing() {
|
||||
wallet.sync(true);
|
||||
wallet.sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -339,7 +326,7 @@ impl WalletTransactions {
|
|||
|| tx.data.tx_type == TxLogEntryType::TxReceivedCancelled;
|
||||
if is_canceled {
|
||||
format!("{} {}", X_CIRCLE, t!("wallets.tx_canceled"))
|
||||
} else if tx.posting {
|
||||
} else if tx.finalizing {
|
||||
format!("{} {}", DOTS_THREE_CIRCLE, t!("wallets.tx_finalizing"))
|
||||
} else {
|
||||
if tx.cancelling {
|
||||
|
@ -431,8 +418,7 @@ impl WalletTransactions {
|
|||
|
||||
/// Show transaction information [`Modal`].
|
||||
fn show_tx_info_modal(&mut self, wallet: &Wallet, tx: &WalletTransaction, finalize: bool) {
|
||||
let mut modal = WalletTransactionModal::new(wallet, tx);
|
||||
modal.show_finalization = finalize;
|
||||
let modal = WalletTransactionModal::new(wallet, tx, finalize);
|
||||
self.tx_info_content = Some(modal);
|
||||
Modal::new(TX_INFO_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
|
|
|
@ -21,7 +21,7 @@ use grin_util::ToHex;
|
|||
use grin_wallet_libwallet::{Error, Slate, SlateState, TxLogEntryType};
|
||||
use parking_lot::RwLock;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_CLOCKWISE, BROOM, CHECK, CLIPBOARD_TEXT, COPY, FILE_ARCHIVE, FILE_TEXT, HASH_STRAIGHT, PROHIBIT, QR_CODE, SCAN};
|
||||
use crate::gui::icons::{BROOM, CHECK, CLIPBOARD_TEXT, COPY, FILE_ARCHIVE, FILE_TEXT, HASH_STRAIGHT, PROHIBIT, QR_CODE, SCAN};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
|
||||
use crate::gui::views::{CameraContent, FilePickButton, Modal, QrCodeContent, View};
|
||||
|
@ -41,7 +41,7 @@ pub struct WalletTransactionModal {
|
|||
response_edit: String,
|
||||
|
||||
/// Flag to show transaction finalization input.
|
||||
pub show_finalization: bool,
|
||||
show_finalization: bool,
|
||||
/// Finalization Slatepack message input value.
|
||||
finalize_edit: String,
|
||||
/// Flag to check if error happened during transaction finalization.
|
||||
|
@ -49,17 +49,13 @@ pub struct WalletTransactionModal {
|
|||
/// Flag to check if transaction is finalizing.
|
||||
finalizing: bool,
|
||||
/// Transaction finalization result.
|
||||
final_result: Arc<RwLock<Option<Result<Slate, Error>>>>,
|
||||
final_result: Arc<RwLock<Option<Result<WalletTransaction, Error>>>>,
|
||||
|
||||
/// Flag to check if QR code is showing.
|
||||
show_qr: bool,
|
||||
/// QR code Slatepack message image content.
|
||||
qr_code_content: QrCodeContent,
|
||||
qr_code_content: Option<QrCodeContent>,
|
||||
|
||||
/// Flag to check if QR code scanner is showing.
|
||||
show_scanner: bool,
|
||||
/// QR code scanner content.
|
||||
scanner_content: CameraContent,
|
||||
qr_scan_content: Option<CameraContent>,
|
||||
|
||||
/// Button to parse picked file content.
|
||||
file_pick_button: FilePickButton,
|
||||
|
@ -67,7 +63,7 @@ pub struct WalletTransactionModal {
|
|||
|
||||
impl WalletTransactionModal {
|
||||
/// Create new content instance with [`Wallet`] from provided [`WalletTransaction`].
|
||||
pub fn new(wallet: &Wallet, tx: &WalletTransaction) -> Self {
|
||||
pub fn new(wallet: &Wallet, tx: &WalletTransaction, show_finalization: bool) -> Self {
|
||||
Self {
|
||||
tx_id: tx.data.id,
|
||||
slate_id: match tx.data.tx_slate_id {
|
||||
|
@ -98,13 +94,11 @@ impl WalletTransactionModal {
|
|||
},
|
||||
finalize_edit: "".to_string(),
|
||||
finalize_error: false,
|
||||
show_finalization: false,
|
||||
show_finalization,
|
||||
finalizing: false,
|
||||
final_result: Arc::new(RwLock::new(None)),
|
||||
show_qr: false,
|
||||
qr_code_content: QrCodeContent::new("".to_string(), true),
|
||||
show_scanner: false,
|
||||
scanner_content: CameraContent::default(),
|
||||
qr_code_content: None,
|
||||
qr_scan_content: None,
|
||||
file_pick_button: FilePickButton::default(),
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +127,7 @@ impl WalletTransactionModal {
|
|||
}
|
||||
let tx = txs.get(0).unwrap();
|
||||
|
||||
if !self.show_qr && !self.show_scanner {
|
||||
if self.qr_code_content.is_none() && self.qr_scan_content.is_none() {
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show transaction amount status and time.
|
||||
|
@ -174,25 +168,6 @@ impl WalletTransactionModal {
|
|||
wallet.cancel(tx.data.id);
|
||||
});
|
||||
}
|
||||
|
||||
// Draw button to repost transaction.
|
||||
if wallet_loaded && tx.can_repost(&data) {
|
||||
let r = if self.show_finalization {
|
||||
Rounding::default()
|
||||
} else {
|
||||
let mut r = r.clone();
|
||||
r.nw = 0.0;
|
||||
r.sw = 0.0;
|
||||
r
|
||||
};
|
||||
View::item_button(ui, r, ARROW_CLOCKWISE, Some(Colors::green()), || {
|
||||
cb.hide_keyboard();
|
||||
// Post tx after getting slate from slatepack file.
|
||||
if let Some((s, _)) = wallet.read_slate_by_tx(tx) {
|
||||
let _ = wallet.post(&s, wallet.can_use_dandelion());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Show transaction ID info.
|
||||
|
@ -207,54 +182,49 @@ impl WalletTransactionModal {
|
|||
}
|
||||
}
|
||||
|
||||
// Show Slatepack message or reset flag to show QR if not available.
|
||||
if !tx.posting && !tx.data.confirmed && !tx.cancelling &&
|
||||
// Show Slatepack message or reset QR code state if not available.
|
||||
if !tx.finalizing && !tx.data.confirmed && !tx.cancelling &&
|
||||
(tx.data.tx_type == TxLogEntryType::TxSent ||
|
||||
tx.data.tx_type == TxLogEntryType::TxReceived) {
|
||||
tx.data.tx_type == TxLogEntryType::TxReceived) && !self.response_edit.is_empty() {
|
||||
self.message_ui(ui, tx, wallet, modal, cb);
|
||||
} else if self.show_qr {
|
||||
self.qr_code_content.clear_state();
|
||||
self.show_qr = false;
|
||||
} else if let Some(qr_content) = self.qr_code_content.as_mut() {
|
||||
qr_content.clear_state();
|
||||
}
|
||||
|
||||
if !self.finalizing {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
if self.show_qr {
|
||||
if self.qr_code_content.is_some() {
|
||||
// Show buttons to close modal or come back to text request content.
|
||||
ui.columns(2, |cols| {
|
||||
cols[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
self.qr_code_content.clear_state();
|
||||
self.show_qr = false;
|
||||
self.qr_code_content = None;
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
cols[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("back"), Colors::white_or_black(false), || {
|
||||
self.qr_code_content.clear_state();
|
||||
self.show_qr = false;
|
||||
self.qr_code_content = None;
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if self.show_scanner {
|
||||
} else if self.qr_scan_content.is_some() {
|
||||
ui.add_space(8.0);
|
||||
// Show buttons to close modal or scanner.
|
||||
ui.columns(2, |cols| {
|
||||
cols[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
cb.stop_camera();
|
||||
self.scanner_content.clear_state();
|
||||
self.show_scanner = false;
|
||||
self.qr_scan_content = None;
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
cols[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("back"), Colors::white_or_black(false), || {
|
||||
cb.stop_camera();
|
||||
self.scanner_content.clear_state();
|
||||
self.show_scanner = false;
|
||||
self.qr_scan_content = None;
|
||||
modal.enable_closing();
|
||||
});
|
||||
});
|
||||
|
@ -361,20 +331,19 @@ impl WalletTransactionModal {
|
|||
ui.add_space(6.0);
|
||||
|
||||
// Draw QR code scanner content if requested.
|
||||
if self.show_scanner {
|
||||
if let Some(result) = self.scanner_content.qr_scan_result() {
|
||||
if let Some(qr_scan_content) = self.qr_scan_content.as_mut() {
|
||||
if let Some(result) = qr_scan_content.qr_scan_result() {
|
||||
cb.stop_camera();
|
||||
self.scanner_content.clear_state();
|
||||
qr_scan_content.clear_state();
|
||||
|
||||
// Setup value to finalization input field.
|
||||
self.finalize_edit = result.text();
|
||||
self.on_finalization_input_change(tx, wallet, modal, cb);
|
||||
|
||||
modal.enable_closing();
|
||||
self.scanner_content.clear_state();
|
||||
self.show_scanner = false;
|
||||
self.qr_scan_content = None;
|
||||
} else {
|
||||
self.scanner_content.ui(ui, cb);
|
||||
qr_scan_content.ui(ui, cb);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -427,17 +396,10 @@ impl WalletTransactionModal {
|
|||
let message_before = message_edit.clone();
|
||||
|
||||
// Draw QR code content if requested.
|
||||
if self.show_qr {
|
||||
let text = message_edit.clone();
|
||||
if text.is_empty() {
|
||||
self.qr_code_content.clear_state();
|
||||
self.show_qr = false;
|
||||
} else {
|
||||
// Draw QR code content.
|
||||
self.qr_code_content.ui(ui, text.clone(), cb);
|
||||
if let Some(qr_content) = self.qr_code_content.as_mut() {
|
||||
qr_content.ui(ui, cb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw Slatepack message finalization input or request text.
|
||||
ui.vertical_centered(|ui| {
|
||||
|
@ -498,7 +460,7 @@ impl WalletTransactionModal {
|
|||
cb.hide_keyboard();
|
||||
modal.disable_closing();
|
||||
cb.start_camera();
|
||||
self.show_scanner = true;
|
||||
self.qr_scan_content = Some(CameraContent::default());
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
|
@ -535,9 +497,10 @@ impl WalletTransactionModal {
|
|||
columns[0].vertical_centered_justified(|ui| {
|
||||
// Draw button to show Slatepack message as QR code.
|
||||
let qr_text = format!("{} {}", QR_CODE, t!("qr_code"));
|
||||
View::button(ui, qr_text, Colors::button(), || {
|
||||
View::button(ui, qr_text.clone(), Colors::button(), || {
|
||||
cb.hide_keyboard();
|
||||
self.show_qr = true;
|
||||
let text = self.response_edit.clone();
|
||||
self.qr_code_content = Some(QrCodeContent::new(text, true));
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
|
@ -599,7 +562,7 @@ impl WalletTransactionModal {
|
|||
self.finalizing = true;
|
||||
modal.disable_closing();
|
||||
thread::spawn(move || {
|
||||
let res = wallet.finalize(&message, wallet.can_use_dandelion());
|
||||
let res = wallet.finalize(&message);
|
||||
let mut w_res = final_res.write();
|
||||
*w_res = Some(res);
|
||||
});
|
||||
|
|
|
@ -158,14 +158,12 @@ pub struct WalletTransaction {
|
|||
pub amount: u64,
|
||||
/// Flag to check if transaction is cancelling.
|
||||
pub cancelling: bool,
|
||||
/// Flag to check if transaction is posting after finalization.
|
||||
pub posting: bool,
|
||||
/// Flag to check if transaction can be finalized based on Slatepack message state.
|
||||
pub can_finalize: bool,
|
||||
/// Flag to check if transaction is finalizing.
|
||||
pub finalizing: bool,
|
||||
/// Block height when tx was confirmed.
|
||||
pub conf_height: Option<u64>,
|
||||
/// Block height when tx was reposted.
|
||||
pub repost_height: Option<u64>,
|
||||
/// Flag to check if tx was received after sync from node.
|
||||
pub from_node: bool,
|
||||
}
|
||||
|
@ -173,16 +171,8 @@ pub struct WalletTransaction {
|
|||
impl WalletTransaction {
|
||||
/// Check if transaction can be cancelled.
|
||||
pub fn can_cancel(&self) -> bool {
|
||||
self.from_node && !self.cancelling && !self.posting && !self.data.confirmed &&
|
||||
self.from_node && !self.cancelling && !self.data.confirmed &&
|
||||
self.data.tx_type != TxLogEntryType::TxReceivedCancelled
|
||||
&& self.data.tx_type != TxLogEntryType::TxSentCancelled
|
||||
}
|
||||
|
||||
/// Check if transaction can be reposted.
|
||||
pub fn can_repost(&self, data: &WalletData) -> bool {
|
||||
let last_height = data.info.last_confirmed_height;
|
||||
let min_conf = data.info.minimum_confirmations;
|
||||
self.from_node && self.posting && self.repost_height.is_some() &&
|
||||
last_height - self.repost_height.unwrap() > min_conf
|
||||
}
|
||||
}
|
|
@ -437,8 +437,8 @@ impl Wallet {
|
|||
// Mark wallet as not opened.
|
||||
wallet_close.closing.store(false, Ordering::Relaxed);
|
||||
wallet_close.is_open.store(false, Ordering::Relaxed);
|
||||
// Wake up thread to exit.
|
||||
wallet_close.sync(true);
|
||||
// Start sync to exit from thread.
|
||||
wallet_close.sync();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -464,8 +464,8 @@ impl Wallet {
|
|||
});
|
||||
}
|
||||
|
||||
// Sync wallet data.
|
||||
self.sync(false);
|
||||
// Refresh wallet info.
|
||||
sync_wallet_data(&self, false);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
@ -498,7 +498,7 @@ impl Wallet {
|
|||
self.info_sync_progress.store(0, Ordering::Relaxed);
|
||||
|
||||
// Sync wallet data.
|
||||
self.sync(false);
|
||||
self.sync();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -555,19 +555,12 @@ impl Wallet {
|
|||
r_data.clone()
|
||||
}
|
||||
|
||||
/// Sync wallet data from node or locally.
|
||||
pub fn sync(&self, from_node: bool) {
|
||||
if from_node {
|
||||
/// Sync wallet data from node at sync thread or locally synchronously.
|
||||
pub fn sync(&self) {
|
||||
let thread_r = self.sync_thread.read();
|
||||
if let Some(thread) = thread_r.as_ref() {
|
||||
thread.unpark();
|
||||
}
|
||||
} else {
|
||||
let wallet = self.clone();
|
||||
thread::spawn(move || {
|
||||
sync_wallet_data(&wallet, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if wallet is syncing.
|
||||
|
@ -625,13 +618,7 @@ impl Wallet {
|
|||
let mut slate = None;
|
||||
if let Some(slate_id) = tx.data.tx_slate_id {
|
||||
// Get slate state based on tx state and status.
|
||||
let state = if tx.posting {
|
||||
if tx.data.tx_type == TxLogEntryType::TxSent {
|
||||
Some(SlateState::Standard3)
|
||||
} else {
|
||||
Some(SlateState::Invoice3)
|
||||
}
|
||||
} else if !tx.data.confirmed && (tx.data.tx_type == TxLogEntryType::TxSent ||
|
||||
let state = if !tx.data.confirmed && (tx.data.tx_type == TxLogEntryType::TxSent ||
|
||||
tx.data.tx_type == TxLogEntryType::TxReceived) {
|
||||
if tx.can_finalize {
|
||||
if tx.data.tx_type == TxLogEntryType::TxSent {
|
||||
|
@ -681,7 +668,7 @@ impl Wallet {
|
|||
}
|
||||
|
||||
/// Initialize a transaction to send amount, return request for funds receiver.
|
||||
pub fn send(&self, amount: u64) -> Result<(Slate, String), Error> {
|
||||
pub fn send(&self, amount: u64) -> Result<WalletTransaction, Error> {
|
||||
let config = self.get_config();
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: Some(config.account),
|
||||
|
@ -698,51 +685,35 @@ impl Wallet {
|
|||
api.tx_lock_outputs(None, &slate)?;
|
||||
|
||||
// Create Slatepack message response.
|
||||
let message_resp = self.create_slatepack_message(&slate)?;
|
||||
let _ = self.create_slatepack_message(&slate)?;
|
||||
|
||||
// Sync wallet info.
|
||||
self.sync(false);
|
||||
// Refresh wallet info.
|
||||
sync_wallet_data(&self, false);
|
||||
|
||||
Ok((slate, message_resp))
|
||||
let tx = self.tx_by_slate(&slate).ok_or(Error::GenericError("No tx found".to_string()))?;
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Send amount to provided address with Tor transport.
|
||||
pub async fn send_tor(&mut self, amount: u64, addr: &SlatepackAddress) -> Option<Slate> {
|
||||
pub async fn send_tor(&mut self,
|
||||
amount: u64,
|
||||
addr: &SlatepackAddress) -> Result<WalletTransaction, Error> {
|
||||
// Initialize transaction.
|
||||
let send_res = self.send(amount);
|
||||
|
||||
if send_res.is_err() {
|
||||
return None;
|
||||
let tx = self.send(amount)?;
|
||||
let slate_res = self.read_slate_by_tx(&tx);
|
||||
if slate_res.is_none() {
|
||||
return Err(Error::GenericError("Slate not found".to_string()));
|
||||
}
|
||||
let slate = send_res.unwrap().0;
|
||||
let (slate, _) = slate_res.unwrap();
|
||||
|
||||
// Function to cancel initialized tx in case of error.
|
||||
let cancel_tx = || {
|
||||
let instance = self.instance.clone().unwrap();
|
||||
let id = slate.clone().id;
|
||||
cancel_tx(instance, None, &None, None, Some(id.clone())).unwrap();
|
||||
// Setup posting flag, and ability to finalize.
|
||||
{
|
||||
let mut w_data = self.data.write();
|
||||
let mut data = w_data.clone().unwrap();
|
||||
let txs = data.txs.clone().unwrap().iter_mut().map(|tx| {
|
||||
if tx.data.tx_slate_id == Some(id) {
|
||||
tx.cancelling = false;
|
||||
tx.posting = false;
|
||||
tx.can_finalize = false;
|
||||
tx.data.tx_type = if tx.data.tx_type == TxLogEntryType::TxReceived {
|
||||
TxLogEntryType::TxReceivedCancelled
|
||||
} else {
|
||||
TxLogEntryType::TxSentCancelled
|
||||
};
|
||||
}
|
||||
tx.clone()
|
||||
}).collect::<Vec<WalletTransaction>>();
|
||||
data.txs = Some(txs);
|
||||
*w_data = Some(data);
|
||||
}
|
||||
|
||||
// Refresh wallet info to update statuses.
|
||||
self.sync(false);
|
||||
sync_wallet_data(&self, false);
|
||||
};
|
||||
|
||||
// Initialize parameters.
|
||||
|
@ -764,17 +735,15 @@ impl Wallet {
|
|||
let req_res = Tor::post(body, url).await;
|
||||
if req_res.is_none() {
|
||||
cancel_tx();
|
||||
return None;
|
||||
return Err(Error::GenericError("Tor post error".to_string()));
|
||||
}
|
||||
|
||||
// Parse response and finalize transaction.
|
||||
// Parse response.
|
||||
let res: Value = serde_json::from_str(&req_res.unwrap()).unwrap();
|
||||
if res["error"] != json!(null) {
|
||||
cancel_tx();
|
||||
return None;
|
||||
return Err(Error::GenericError("Tx error".to_string()));
|
||||
}
|
||||
|
||||
// Slatepack message json value.
|
||||
let slate_value = res["result"]["Ok"].clone();
|
||||
|
||||
let mut ret_slate = None;
|
||||
|
@ -788,7 +757,7 @@ impl Wallet {
|
|||
// Save Slatepack message to file.
|
||||
let _ = self.create_slatepack_message(&slate).unwrap_or("".to_string());
|
||||
// Post transaction to blockchain.
|
||||
let result = self.post(&slate, self.can_use_dandelion());
|
||||
let result = self.post(&slate);
|
||||
match result {
|
||||
Ok(_) => {
|
||||
Ok(())
|
||||
|
@ -798,21 +767,25 @@ impl Wallet {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::GenericError("TX finalization error".to_string()))
|
||||
Err(Error::GenericError("Tx finalization error".to_string()))
|
||||
};
|
||||
}).unwrap();
|
||||
})?;
|
||||
}
|
||||
Err(_) => {}
|
||||
};
|
||||
|
||||
// Cancel transaction on error.
|
||||
if ret_slate.is_none() {
|
||||
cancel_tx();
|
||||
return Err(Error::GenericError("Tx error".to_string()));
|
||||
}
|
||||
ret_slate
|
||||
let tx = self.tx_by_slate(ret_slate.as_ref().unwrap())
|
||||
.ok_or(Error::GenericError("No tx found".to_string()))?;
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Initialize an invoice transaction to receive amount, return request for funds sender.
|
||||
pub fn issue_invoice(&self, amount: u64) -> Result<(Slate, String), Error> {
|
||||
pub fn issue_invoice(&self, amount: u64) -> Result<WalletTransaction, Error> {
|
||||
let args = IssueInvoiceTxArgs {
|
||||
dest_acct_name: None,
|
||||
amount,
|
||||
|
@ -822,16 +795,17 @@ impl Wallet {
|
|||
let slate = api.issue_invoice_tx(None, args)?;
|
||||
|
||||
// Create Slatepack message response.
|
||||
let response = self.create_slatepack_message(&slate.clone())?;
|
||||
let _ = self.create_slatepack_message(&slate)?;
|
||||
|
||||
// Sync wallet info.
|
||||
self.sync(false);
|
||||
// Refresh wallet info.
|
||||
sync_wallet_data(&self, false);
|
||||
|
||||
Ok((slate, response))
|
||||
let tx = self.tx_by_slate(&slate).ok_or(Error::GenericError("No tx found".to_string()))?;
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Handle message from the invoice issuer to send founds, return response for funds receiver.
|
||||
pub fn pay(&self, message: &String) -> Result<String, Error> {
|
||||
pub fn pay(&self, message: &String) -> Result<WalletTransaction, Error> {
|
||||
if let Ok(slate) = self.parse_slatepack(message) {
|
||||
let config = self.get_config();
|
||||
let args = InitTxArgs {
|
||||
|
@ -846,19 +820,19 @@ impl Wallet {
|
|||
api.tx_lock_outputs(None, &slate)?;
|
||||
|
||||
// Create Slatepack message response.
|
||||
let response = self.create_slatepack_message(&slate)?;
|
||||
let _ = self.create_slatepack_message(&slate)?;
|
||||
|
||||
// Sync wallet info.
|
||||
self.sync(false);
|
||||
// Refresh wallet info.
|
||||
sync_wallet_data(&self, false);
|
||||
|
||||
Ok(response)
|
||||
Ok(self.tx_by_slate(&slate).ok_or(Error::GenericError("No tx found".to_string()))?)
|
||||
} else {
|
||||
Err(Error::SlatepackDeser("Slatepack parsing error".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle message to receive funds, return response to sender.
|
||||
pub fn receive(&self, message: &String) -> Result<String, Error> {
|
||||
pub fn receive(&self, message: &String) -> Result<WalletTransaction, Error> {
|
||||
if let Ok(mut slate) = self.parse_slatepack(message) {
|
||||
let api = Owner::new(self.instance.clone().unwrap(), None);
|
||||
controller::foreign_single_use(api.wallet_inst.clone(), None, |api| {
|
||||
|
@ -866,61 +840,47 @@ impl Wallet {
|
|||
Ok(())
|
||||
})?;
|
||||
// Create Slatepack message response.
|
||||
let response = self.create_slatepack_message(&slate)?;
|
||||
let _ = self.create_slatepack_message(&slate)?;
|
||||
|
||||
// Sync wallet info.
|
||||
self.sync(false);
|
||||
// Refresh wallet info.
|
||||
sync_wallet_data(&self, false);
|
||||
|
||||
Ok(response)
|
||||
Ok(self.tx_by_slate(&slate).ok_or(Error::GenericError("No tx found".to_string()))?)
|
||||
} else {
|
||||
Err(Error::SlatepackDeser("Slatepack parsing error".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Finalize transaction from provided message as sender or invoice issuer with Dandelion.
|
||||
pub fn finalize(&self, message: &String, dandelion: bool) -> Result<Slate, Error> {
|
||||
pub fn finalize(&self, message: &String) -> Result<WalletTransaction, Error> {
|
||||
if let Ok(mut slate) = self.parse_slatepack(message) {
|
||||
let api = Owner::new(self.instance.clone().unwrap(), None);
|
||||
slate = api.finalize_tx(None, &slate)?;
|
||||
// Save Slatepack message to file.
|
||||
let _ = self.create_slatepack_message(&slate)?;
|
||||
|
||||
// Post transaction to blockchain.
|
||||
let _ = self.post(&slate, dandelion);
|
||||
Ok(slate)
|
||||
let tx = self.post(&slate)?;
|
||||
|
||||
// Refresh wallet info.
|
||||
sync_wallet_data(&self, false);
|
||||
|
||||
Ok(tx)
|
||||
} else {
|
||||
Err(Error::SlatepackDeser("Slatepack parsing error".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Post transaction to blockchain.
|
||||
pub fn post(&self, slate: &Slate, dandelion: bool) -> Result<(), Error> {
|
||||
fn post(&self, slate: &Slate) -> Result<WalletTransaction, Error> {
|
||||
// Post transaction to blockchain.
|
||||
let api = Owner::new(self.instance.clone().unwrap(), None);
|
||||
api.post_tx(None, slate, dandelion)?;
|
||||
// Setup transaction repost height, posting flag and ability to finalize.
|
||||
let mut slate = slate.clone();
|
||||
if slate.state == SlateState::Invoice2 {
|
||||
slate.state = SlateState::Invoice3
|
||||
} else if slate.state == SlateState::Standard2 {
|
||||
slate.state = SlateState::Standard3
|
||||
};
|
||||
if let Some(tx) = self.tx_by_slate(&slate) {
|
||||
let mut w_data = self.data.write();
|
||||
let mut data = w_data.clone().unwrap();
|
||||
let mut data_txs = data.txs.unwrap();
|
||||
for t in &mut data_txs {
|
||||
if t.data.id == tx.data.id {
|
||||
t.repost_height = Some(data.info.last_confirmed_height);
|
||||
t.posting = true;
|
||||
t.can_finalize = false;
|
||||
}
|
||||
}
|
||||
data.txs = Some(data_txs);
|
||||
*w_data = Some(data);
|
||||
}
|
||||
// Sync local wallet info.
|
||||
self.sync(false);
|
||||
Ok(())
|
||||
api.post_tx(None, slate, self.can_use_dandelion())?;
|
||||
|
||||
// Refresh wallet info.
|
||||
sync_wallet_data(&self, false);
|
||||
|
||||
Ok(self.tx_by_slate(&slate).ok_or(Error::GenericError("No tx found".to_string()))?)
|
||||
}
|
||||
|
||||
/// Cancel transaction.
|
||||
|
@ -948,27 +908,7 @@ impl Wallet {
|
|||
}
|
||||
let instance = wallet.instance.clone().unwrap();
|
||||
let _ = cancel_tx(instance, None, &None, Some(id), None);
|
||||
// Setup tx status, cancelling, posting flag, and ability to finalize.
|
||||
{
|
||||
let mut w_data = wallet.data.write();
|
||||
let mut data = w_data.clone().unwrap();
|
||||
let mut data_txs = data.txs.unwrap();
|
||||
let txs = data_txs.iter_mut().map(|tx| {
|
||||
if tx.data.id == id {
|
||||
tx.cancelling = false;
|
||||
tx.posting = false;
|
||||
tx.can_finalize = false;
|
||||
tx.data.tx_type = if tx.data.tx_type == TxLogEntryType::TxReceived {
|
||||
TxLogEntryType::TxReceivedCancelled
|
||||
} else {
|
||||
TxLogEntryType::TxSentCancelled
|
||||
};
|
||||
}
|
||||
tx.clone()
|
||||
}).collect::<Vec<WalletTransaction>>();
|
||||
data.txs = Some(txs);
|
||||
*w_data = Some(data);
|
||||
}
|
||||
|
||||
// Refresh wallet info to update statuses.
|
||||
sync_wallet_data(&wallet, false);
|
||||
});
|
||||
|
@ -985,7 +925,7 @@ impl Wallet {
|
|||
/// Initiate wallet repair by scanning its outputs.
|
||||
pub fn repair(&self) {
|
||||
self.repair_needed.store(true, Ordering::Relaxed);
|
||||
self.sync(true);
|
||||
self.sync();
|
||||
}
|
||||
|
||||
/// Check if wallet is repairing.
|
||||
|
@ -1013,7 +953,7 @@ impl Wallet {
|
|||
// Remove wallet db files.
|
||||
let _ = fs::remove_dir_all(wallet_delete.get_config().get_db_path());
|
||||
// Start sync to close thread.
|
||||
wallet_delete.sync(true);
|
||||
wallet_delete.sync();
|
||||
// Mark wallet to reopen.
|
||||
wallet_delete.set_reopen(reopen);
|
||||
});
|
||||
|
@ -1046,7 +986,7 @@ impl Wallet {
|
|||
// Mark wallet as deleted.
|
||||
wallet_delete.deleted.store(true, Ordering::Relaxed);
|
||||
// Start sync to close thread.
|
||||
wallet_delete.sync(true);
|
||||
wallet_delete.sync();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1268,7 +1208,7 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
|
|||
wallet.reset_sync_attempts();
|
||||
|
||||
// Filter transactions for current account.
|
||||
let filter_txs = txs.1.iter().map(|v| v.clone()).filter(|tx| {
|
||||
let account_txs = txs.1.iter().map(|v| v.clone()).filter(|tx| {
|
||||
match wallet.get_parent_key_id() {
|
||||
Ok(key) => {
|
||||
tx.parent_key_id == key
|
||||
|
@ -1286,7 +1226,7 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
|
|||
|
||||
// Create wallet txs.
|
||||
let mut new_txs: Vec<WalletTransaction> = vec![];
|
||||
for tx in &filter_txs {
|
||||
for tx in &account_txs {
|
||||
// Setup transaction amount.
|
||||
let amount = if tx.amount_debited > tx.amount_credited {
|
||||
tx.amount_debited - tx.amount_credited
|
||||
|
@ -1294,42 +1234,13 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
|
|||
tx.amount_credited - tx.amount_debited
|
||||
};
|
||||
|
||||
// Setup flag for ability to finalize transaction.
|
||||
let unconfirmed_sent_or_received = tx.tx_slate_id.is_some() &&
|
||||
!tx.confirmed && (tx.tx_type == TxLogEntryType::TxSent ||
|
||||
tx.tx_type == TxLogEntryType::TxReceived);
|
||||
|
||||
// Setup transaction posting status based on slate state.
|
||||
let posting = if unconfirmed_sent_or_received {
|
||||
// Create slate to check existing file.
|
||||
let is_invoice = tx.tx_type == TxLogEntryType::TxReceived;
|
||||
let mut slate = Slate::blank(0, is_invoice);
|
||||
slate.id = tx.tx_slate_id.unwrap();
|
||||
slate.state = match is_invoice {
|
||||
true => SlateState::Invoice3,
|
||||
_ => SlateState::Standard3
|
||||
};
|
||||
|
||||
// Setup posting status if we have other tx with same slate id.
|
||||
let mut same_tx_posting = false;
|
||||
for t in &mut new_txs {
|
||||
if t.data.tx_slate_id == tx.tx_slate_id &&
|
||||
tx.tx_type != t.data.tx_type {
|
||||
same_tx_posting = t.posting ||
|
||||
wallet.read_slatepack(&slate).is_some();
|
||||
if same_tx_posting && !t.posting {
|
||||
t.posting = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
same_tx_posting || wallet.read_slatepack(&slate).is_some()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Setup flag for ability to finalize transaction.
|
||||
let can_finalize = if !posting && unconfirmed_sent_or_received {
|
||||
// Check existing file.
|
||||
let mut finalizing = false;
|
||||
let can_finalize = if unconfirmed_sent_or_received {
|
||||
let initial_state = {
|
||||
let mut slate = Slate::blank(1, false);
|
||||
slate.id = tx.tx_slate_id.unwrap();
|
||||
slate.state = match tx.tx_type {
|
||||
|
@ -1337,11 +1248,22 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
|
|||
_ => SlateState::Standard1
|
||||
};
|
||||
wallet.read_slatepack(&slate).is_some()
|
||||
};
|
||||
finalizing = {
|
||||
let mut slate = Slate::blank(1, false);
|
||||
slate.id = tx.tx_slate_id.unwrap();
|
||||
slate.state = match tx.tx_type {
|
||||
TxLogEntryType::TxReceived => SlateState::Invoice3,
|
||||
_ => SlateState::Standard3
|
||||
};
|
||||
wallet.read_slatepack(&slate).is_some()
|
||||
};
|
||||
initial_state && !finalizing
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Setup confirmation, reposting height and cancelling status.
|
||||
// Setup confirmation and cancelling status.
|
||||
let mut conf_height = None;
|
||||
let mut setup_conf_height = |t: &TxLogEntry, current_empty: bool| -> bool {
|
||||
if current_empty && t.kernel_lookup_min_height.is_some() &&
|
||||
|
@ -1376,7 +1298,6 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
|
|||
false
|
||||
};
|
||||
|
||||
let mut repost_height = None;
|
||||
let mut cancelling = false;
|
||||
if data_txs.is_empty() {
|
||||
setup_conf_height(tx, true);
|
||||
|
@ -1387,7 +1308,6 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
|
|||
t.conf_height.unwrap() == 0) {
|
||||
conf_height = t.conf_height;
|
||||
}
|
||||
repost_height = t.repost_height;
|
||||
if t.cancelling &&
|
||||
tx.tx_type != TxLogEntryType::TxReceivedCancelled &&
|
||||
tx.tx_type != TxLogEntryType::TxSentCancelled {
|
||||
|
@ -1403,10 +1323,9 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
|
|||
data: tx.clone(),
|
||||
amount,
|
||||
cancelling,
|
||||
posting,
|
||||
can_finalize,
|
||||
finalizing,
|
||||
conf_height,
|
||||
repost_height,
|
||||
from_node: !fresh_sync || from_node
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue