2023-08-03 00:00:23 +03:00
|
|
|
// 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.
|
2023-08-03 23:49:11 +03:00
|
|
|
|
2024-05-15 14:56:28 +03:00
|
|
|
use std::sync::Arc;
|
2024-04-30 18:15:03 +03:00
|
|
|
use std::thread;
|
|
|
|
use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea};
|
2024-05-15 14:56:28 +03:00
|
|
|
use egui::os::OperatingSystem;
|
2024-04-30 18:15:03 +03:00
|
|
|
use egui::scroll_area::ScrollBarVisibility;
|
2024-05-15 14:56:28 +03:00
|
|
|
use parking_lot::RwLock;
|
2024-05-15 20:27:58 +03:00
|
|
|
use tor_rtcompat::BlockOn;
|
|
|
|
use tor_rtcompat::tokio::TokioNativeTlsRuntime;
|
2024-04-30 18:15:03 +03:00
|
|
|
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
|
|
|
|
use grin_wallet_libwallet::SlatepackAddress;
|
|
|
|
|
2023-08-16 04:42:05 +03:00
|
|
|
use crate::gui::Colors;
|
2024-05-01 03:41:59 +03:00
|
|
|
use crate::gui::icons::{CHECK_CIRCLE, COMPUTER_TOWER, COPY, DOTS_THREE_CIRCLE, EXPORT, GEAR_SIX, GLOBE_SIMPLE, POWER, QR_CODE, WARNING_CIRCLE, X_CIRCLE};
|
2023-08-03 23:49:11 +03:00
|
|
|
use crate::gui::platform::PlatformCallbacks;
|
2024-05-04 12:20:35 +03:00
|
|
|
use crate::gui::views::{Modal, QrCodeContent, Root, View};
|
2024-04-30 18:15:03 +03:00
|
|
|
use crate::gui::views::types::{ModalPosition, TextEditOptions};
|
2023-08-03 23:49:11 +03:00
|
|
|
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
|
2023-08-12 04:24:23 +03:00
|
|
|
use crate::gui::views::wallets::wallet::WalletContent;
|
2024-05-15 14:56:28 +03:00
|
|
|
use crate::tor::{Tor, TorBridge, TorConfig};
|
2024-05-01 04:49:48 +03:00
|
|
|
use crate::wallet::types::WalletData;
|
2023-08-09 02:22:16 +03:00
|
|
|
use crate::wallet::Wallet;
|
2023-08-03 23:49:11 +03:00
|
|
|
|
2024-04-30 18:15:03 +03:00
|
|
|
/// 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 [`Modal`] was just opened to focus on first field.
|
|
|
|
modal_just_opened: bool,
|
|
|
|
|
2024-05-04 12:20:35 +03:00
|
|
|
/// QR code address image [`Modal`] content.
|
|
|
|
qr_code_content: QrCodeContent,
|
2024-05-15 14:56:28 +03:00
|
|
|
|
|
|
|
/// Tor bridge binary path edit text.
|
|
|
|
bridge_bin_path_edit: String,
|
2024-04-30 18:15:03 +03:00
|
|
|
}
|
2023-08-03 23:49:11 +03:00
|
|
|
|
2024-04-18 05:20:49 +03:00
|
|
|
impl WalletTab for WalletTransport {
|
2023-08-03 23:49:11 +03:00
|
|
|
fn get_type(&self) -> WalletTabType {
|
2024-04-18 05:20:49 +03:00
|
|
|
WalletTabType::Transport
|
2023-08-03 23:49:11 +03:00
|
|
|
}
|
|
|
|
|
2023-08-11 17:29:25 +03:00
|
|
|
fn ui(&mut self,
|
|
|
|
ui: &mut egui::Ui,
|
2024-04-16 15:24:22 +03:00
|
|
|
_: &mut eframe::Frame,
|
2023-08-11 17:29:25 +03:00
|
|
|
wallet: &mut Wallet,
|
2024-04-30 18:15:03 +03:00
|
|
|
cb: &dyn PlatformCallbacks) {
|
2024-04-16 15:24:22 +03:00
|
|
|
if WalletContent::sync_ui(ui, wallet) {
|
2023-08-12 04:24:23 +03:00
|
|
|
return;
|
|
|
|
}
|
2023-08-16 04:42:05 +03:00
|
|
|
|
2024-04-30 18:15:03 +03:00
|
|
|
// Show modal content for this ui container.
|
|
|
|
self.modal_content_ui(ui, wallet, cb);
|
|
|
|
|
2024-04-18 05:20:49 +03:00
|
|
|
// Show transport content panel.
|
2023-08-16 04:42:05 +03:00
|
|
|
egui::CentralPanel::default()
|
|
|
|
.frame(egui::Frame {
|
2023-08-16 05:15:35 +03:00
|
|
|
stroke: View::ITEM_STROKE,
|
2023-08-16 04:42:05 +03:00
|
|
|
fill: Colors::WHITE,
|
|
|
|
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| {
|
2024-04-30 18:15:03 +03:00
|
|
|
ScrollArea::vertical()
|
|
|
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
|
|
|
|
.id_source(Id::from("wallet_transport").with(wallet.get_config().id))
|
|
|
|
.auto_shrink([false; 2])
|
|
|
|
.show(ui, |ui| {
|
|
|
|
ui.vertical_centered(|ui| {
|
|
|
|
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
|
|
|
self.ui(ui, wallet, cb);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2023-08-16 04:42:05 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-30 18:15:03 +03:00
|
|
|
/// Identifier for [`Modal`] to send amount over Tor.
|
|
|
|
const SEND_TOR_MODAL: &'static str = "send_tor_modal";
|
|
|
|
|
2024-04-30 23:28:49 +03:00
|
|
|
/// Identifier for [`Modal`] to setup Tor service.
|
|
|
|
const TOR_SETTINGS_MODAL: &'static str = "tor_settings_modal";
|
|
|
|
|
2024-05-04 12:20:35 +03:00
|
|
|
/// Identifier for [`Modal`] to show QR code address image.
|
|
|
|
const QR_ADDRESS_MODAL: &'static str = "qr_address_modal";
|
|
|
|
|
2024-04-18 05:20:49 +03:00
|
|
|
impl WalletTransport {
|
2024-05-04 12:20:35 +03:00
|
|
|
/// Create new content instance from provided Slatepack address text.
|
|
|
|
pub fn new(addr: String) -> Self {
|
2024-05-15 14:56:28 +03:00
|
|
|
// Setup Tor bridge binary path edit text.
|
|
|
|
let bridge = TorConfig::get_bridge();
|
|
|
|
let bridge_bin_path_edit = if let Some(b) = bridge {
|
|
|
|
b.binary_path()
|
|
|
|
} else {
|
|
|
|
"".to_string()
|
|
|
|
};
|
2024-05-04 12:20:35 +03:00
|
|
|
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,
|
|
|
|
modal_just_opened: false,
|
|
|
|
qr_code_content: QrCodeContent::new(addr),
|
2024-05-15 14:56:28 +03:00
|
|
|
bridge_bin_path_edit
|
2024-05-04 12:20:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-30 18:15:03 +03:00
|
|
|
/// 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);
|
|
|
|
});
|
|
|
|
}
|
2024-04-30 23:28:49 +03:00
|
|
|
TOR_SETTINGS_MODAL => {
|
|
|
|
Modal::ui(ui.ctx(), |ui, modal| {
|
2024-05-15 14:56:28 +03:00
|
|
|
self.tor_settings_modal_ui(ui, wallet, modal, cb);
|
2024-04-30 23:28:49 +03:00
|
|
|
});
|
|
|
|
}
|
2024-05-04 12:20:35 +03:00
|
|
|
QR_ADDRESS_MODAL => {
|
|
|
|
Modal::ui(ui.ctx(), |ui, modal| {
|
|
|
|
self.qr_address_modal_ui(ui, modal);
|
|
|
|
});
|
|
|
|
}
|
2024-04-30 18:15:03 +03:00
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Draw Tor transport content.
|
|
|
|
fn tor_ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
|
2024-05-01 04:49:48 +03:00
|
|
|
let data = wallet.get_data().unwrap();
|
|
|
|
|
2024-04-30 18:15:03 +03:00
|
|
|
// Draw header content.
|
2024-04-30 23:28:49 +03:00
|
|
|
self.tor_header_ui(ui, wallet);
|
2024-04-30 18:15:03 +03:00
|
|
|
|
|
|
|
// Draw receive info content.
|
|
|
|
if wallet.slatepack_address().is_some() {
|
2024-05-01 04:49:48 +03:00
|
|
|
self.tor_receive_ui(ui, wallet, &data, cb);
|
2024-04-30 18:15:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Draw send content.
|
2024-05-01 04:49:48 +03:00
|
|
|
if data.info.amount_currently_spendable > 0 {
|
|
|
|
self.tor_send_ui(ui, cb);
|
|
|
|
}
|
2024-04-30 18:15:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Draw Tor transport header content.
|
2024-05-01 04:49:48 +03:00
|
|
|
fn tor_header_ui(&self, ui: &mut egui::Ui, wallet: &Wallet) {
|
2024-04-30 18:15:03 +03:00
|
|
|
// 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, || {
|
2024-05-04 12:20:35 +03:00
|
|
|
// Show Tor settings modal.
|
2024-04-30 23:28:49 +03:00
|
|
|
Modal::new(TOR_SETTINGS_MODAL)
|
|
|
|
.position(ModalPosition::CenterTop)
|
|
|
|
.title(t!("transport.tor_settings"))
|
|
|
|
.show();
|
2024-04-30 18:15:03 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
// Draw button to enable/disable Tor listener for current wallet.
|
|
|
|
let service_id = &wallet.identifier();
|
2024-05-01 04:11:46 +03:00
|
|
|
if !Tor::is_service_starting(service_id) {
|
|
|
|
if !Tor::is_service_running(service_id) &&
|
|
|
|
wallet.foreign_api_port().is_some() {
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
}
|
2024-04-30 18:15:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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.label(RichText::new(t!("transport.tor_network"))
|
|
|
|
.size(18.0)
|
|
|
|
.color(Colors::TITLE));
|
|
|
|
|
|
|
|
// Setup wallet API address text.
|
|
|
|
let port = wallet.foreign_api_port().unwrap();
|
2024-04-30 23:28:49 +03:00
|
|
|
let address_text = format!("{} http://127.0.0.1:{}",
|
|
|
|
COMPUTER_TOWER,
|
|
|
|
port);
|
2024-04-30 18:15:03 +03:00
|
|
|
ui.label(RichText::new(address_text).size(15.0).color(Colors::TEXT));
|
|
|
|
ui.add_space(1.0);
|
|
|
|
|
|
|
|
// 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 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::GRAY));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-04-30 23:28:49 +03:00
|
|
|
/// Draw tor transport settings [`Modal`] content.
|
2024-05-15 14:56:28 +03:00
|
|
|
fn tor_settings_modal_ui(&mut self,
|
|
|
|
ui: &mut egui::Ui,
|
|
|
|
wallet: &Wallet,
|
|
|
|
modal: &Modal,
|
|
|
|
cb: &dyn PlatformCallbacks) {
|
|
|
|
// Do not show bridges setup on Android.
|
|
|
|
let os = OperatingSystem::from_target_os();
|
|
|
|
let show_bridges = os != OperatingSystem::Android;
|
|
|
|
|
2024-04-30 23:28:49 +03:00
|
|
|
ui.add_space(6.0);
|
2024-05-15 14:56:28 +03:00
|
|
|
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"), || {
|
2024-05-15 17:36:09 +03:00
|
|
|
// Save value.
|
2024-05-15 14:56:28 +03:00
|
|
|
let value = if bridge.is_some() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
let b = TorConfig::get_snowflake();
|
|
|
|
self.bridge_bin_path_edit = b.binary_path();
|
|
|
|
Some(b)
|
|
|
|
};
|
|
|
|
TorConfig::save_bridge(value);
|
2024-05-15 17:36:09 +03:00
|
|
|
// Restart service.
|
|
|
|
if let Ok(key) = wallet.secret_key() {
|
|
|
|
let service_id = &wallet.identifier();
|
|
|
|
Tor::stop_service(service_id);
|
2024-05-16 19:37:28 +03:00
|
|
|
Tor::rebuild_client();
|
2024-05-15 17:36:09 +03:00
|
|
|
let api_port = wallet.foreign_api_port().unwrap();
|
|
|
|
Tor::start_service(api_port, key, service_id);
|
|
|
|
}
|
2024-05-15 14:56:28 +03:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// 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 Snowflake bridge selector.
|
|
|
|
let snowflake = TorConfig::get_snowflake();
|
|
|
|
let name = snowflake.protocol_name().to_uppercase();
|
|
|
|
View::radio_value(ui, &mut bridge, snowflake, name);
|
|
|
|
});
|
|
|
|
columns[1].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);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
ui.add_space(12.0);
|
|
|
|
|
|
|
|
// Check if bridge type was changed to save.
|
|
|
|
if current_bridge != bridge {
|
|
|
|
TorConfig::save_bridge(Some(bridge.clone()));
|
|
|
|
self.bridge_bin_path_edit = bridge.binary_path();
|
2024-05-15 17:36:09 +03:00
|
|
|
// Restart service.
|
|
|
|
if let Ok(key) = wallet.secret_key() {
|
|
|
|
let service_id = &wallet.identifier();
|
|
|
|
Tor::stop_service(service_id);
|
2024-05-16 19:37:28 +03:00
|
|
|
Tor::rebuild_client();
|
2024-05-15 17:36:09 +03:00
|
|
|
let api_port = wallet.foreign_api_port().unwrap();
|
|
|
|
Tor::start_service(api_port, key, service_id);
|
|
|
|
}
|
2024-05-15 14:56:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Draw binary path text edit.
|
2024-05-15 17:36:09 +03:00
|
|
|
let bin_edit_id = Id::from(modal.id).with(wallet.get_config().id).with("_bin_edit");
|
2024-05-15 14:56:28 +03:00
|
|
|
let bin_edit_opts = TextEditOptions::new(bin_edit_id).paste();
|
|
|
|
let bin_edit_before = self.bridge_bin_path_edit.clone();
|
|
|
|
ui.vertical_centered(|ui| {
|
|
|
|
View::text_edit(ui, cb, &mut self.bridge_bin_path_edit, bin_edit_opts);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Check if text was changed to save bin path.
|
|
|
|
if bin_edit_before != self.bridge_bin_path_edit {
|
|
|
|
let b = match bridge {
|
|
|
|
TorBridge::Snowflake(_) => {
|
|
|
|
TorBridge::Snowflake(self.bridge_bin_path_edit.clone())
|
|
|
|
},
|
|
|
|
TorBridge::Obfs4(_) => {
|
|
|
|
TorBridge::Obfs4(self.bridge_bin_path_edit.clone())
|
|
|
|
}
|
|
|
|
};
|
|
|
|
TorConfig::save_bridge(Some(b));
|
2024-05-15 17:36:09 +03:00
|
|
|
// Restart service.
|
|
|
|
if let Ok(key) = wallet.secret_key() {
|
|
|
|
let service_id = &wallet.identifier();
|
|
|
|
Tor::stop_service(service_id);
|
|
|
|
let api_port = wallet.foreign_api_port().unwrap();
|
|
|
|
Tor::start_service(api_port, key, service_id);
|
|
|
|
}
|
2024-05-15 14:56:28 +03:00
|
|
|
}
|
|
|
|
ui.add_space(2.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
ui.add_space(6.0);
|
|
|
|
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
|
|
|
ui.add_space(6.0);
|
|
|
|
}
|
|
|
|
|
2024-04-30 23:28:49 +03:00
|
|
|
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, || {
|
|
|
|
modal.close();
|
|
|
|
});
|
|
|
|
});
|
2024-05-15 14:56:28 +03:00
|
|
|
ui.add_space(6.0);
|
2024-04-30 23:28:49 +03:00
|
|
|
}
|
|
|
|
|
2024-05-15 20:27:58 +03:00
|
|
|
/// Draw Tor receive content.
|
2024-05-04 12:20:35 +03:00
|
|
|
fn tor_receive_ui(&mut self,
|
2024-05-01 04:49:48 +03:00
|
|
|
ui: &mut egui::Ui,
|
|
|
|
wallet: &Wallet,
|
|
|
|
data: &WalletData,
|
|
|
|
cb: &dyn PlatformCallbacks) {
|
2024-04-30 18:15:03 +03:00
|
|
|
let slatepack_addr = wallet.slatepack_address().unwrap();
|
|
|
|
let service_id = &wallet.identifier();
|
2024-05-01 04:49:48 +03:00
|
|
|
let can_send = data.info.amount_currently_spendable > 0;
|
2024-04-30 18:15:03 +03:00
|
|
|
|
|
|
|
// Setup layout size.
|
|
|
|
let mut rect = ui.available_rect_before_wrap();
|
|
|
|
rect.set_height(52.0);
|
|
|
|
|
|
|
|
// Draw round background.
|
|
|
|
let bg_rect = rect.clone();
|
2024-05-01 04:49:48 +03:00
|
|
|
let item_rounding = if can_send {
|
|
|
|
View::item_rounding(1, 3, false)
|
|
|
|
} else {
|
|
|
|
View::item_rounding(1, 2, false)
|
|
|
|
};
|
2024-04-30 18:15:03 +03:00
|
|
|
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.
|
2024-05-01 04:49:48 +03:00
|
|
|
let button_rounding = if can_send {
|
|
|
|
View::item_rounding(1, 3, true)
|
|
|
|
} else {
|
|
|
|
View::item_rounding(1, 2, true)
|
|
|
|
};
|
2024-04-30 18:15:03 +03:00
|
|
|
View::item_button(ui, button_rounding, QR_CODE, None, || {
|
2024-05-04 12:20:35 +03:00
|
|
|
// Show QR code image address modal.
|
|
|
|
self.qr_code_content.clear_state();
|
|
|
|
Modal::new(QR_ADDRESS_MODAL)
|
|
|
|
.position(ModalPosition::CenterTop)
|
|
|
|
.title(t!("network_mining.address"))
|
|
|
|
.show();
|
2024-04-30 18:15:03 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
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);
|
2023-08-16 04:42:05 +03:00
|
|
|
|
2024-04-30 18:15:03 +03:00
|
|
|
let address_label = format!("{} {}",
|
2024-05-01 03:41:59 +03:00
|
|
|
GLOBE_SIMPLE,
|
2024-04-30 18:15:03 +03:00
|
|
|
t!("network_mining.address"));
|
|
|
|
ui.label(RichText::new(address_label).size(15.0).color(Colors::GRAY));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-05-04 12:20:35 +03:00
|
|
|
/// Draw QR code image address [`Modal`] content.
|
|
|
|
fn qr_address_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal) {
|
|
|
|
ui.add_space(6.0);
|
|
|
|
|
|
|
|
// Draw QR code content.
|
|
|
|
let text = self.qr_code_content.text.clone();
|
|
|
|
self.qr_code_content.ui(ui, text.clone());
|
|
|
|
ui.add_space(6.0);
|
|
|
|
|
|
|
|
// Show address.
|
2024-05-05 00:04:06 +03:00
|
|
|
View::ellipsize_text(ui, text, 16.0, Colors::GRAY);
|
2024-05-04 12:20:35 +03:00
|
|
|
ui.add_space(6.0);
|
|
|
|
|
|
|
|
ui.vertical_centered_justified(|ui| {
|
|
|
|
View::button(ui, t!("close"), Colors::WHITE, || {
|
|
|
|
self.qr_code_content.clear_state();
|
|
|
|
modal.close();
|
|
|
|
});
|
|
|
|
ui.add_space(6.0);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-05-15 20:27:58 +03:00
|
|
|
/// Draw Tor send content.
|
2024-04-30 23:28:49 +03:00
|
|
|
fn tor_send_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
2024-04-30 18:15:03 +03:00
|
|
|
// 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, || {
|
2024-05-04 12:20:35 +03:00
|
|
|
self.show_send_tor_modal(cb, None);
|
2024-04-30 18:15:03 +03:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Show [`Modal`] to send over Tor.
|
2024-05-04 12:20:35 +03:00
|
|
|
pub fn show_send_tor_modal(&mut self, cb: &dyn PlatformCallbacks, address: Option<String>) {
|
2024-04-30 18:15:03 +03:00
|
|
|
// Setup modal values.
|
2024-05-15 14:56:28 +03:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2024-04-30 18:15:03 +03:00
|
|
|
self.modal_just_opened = true;
|
|
|
|
self.amount_edit = "".to_string();
|
2024-05-04 12:20:35 +03:00
|
|
|
self.address_edit = address.unwrap_or("".to_string());
|
2024-04-30 18:15:03 +03:00
|
|
|
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 {
|
2024-05-15 14:56:28 +03:00
|
|
|
let r_send_err = self.tor_send_error.read();
|
2024-04-30 18:15:03 +03:00
|
|
|
r_send_err.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Check if transaction is sending over Tor to show progress at [`Modal`].
|
|
|
|
fn tor_sending(&self) -> bool {
|
2024-05-15 14:56:28 +03:00
|
|
|
let r_sending = self.tor_sending.read();
|
2024-04-30 18:15:03 +03:00
|
|
|
r_sending.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Check if transaction sent over Tor with success at [`Modal`].
|
|
|
|
fn tor_success(&self) -> bool {
|
2024-05-15 14:56:28 +03:00
|
|
|
let r_success = self.tor_success.read();
|
2024-04-30 18:15:03 +03:00
|
|
|
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 {
|
|
|
|
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, 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() {
|
|
|
|
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(8.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 address_edit_opts = TextEditOptions::new(address_edit_id)
|
|
|
|
.paste()
|
|
|
|
.scan_qr()
|
|
|
|
.no_focus();
|
|
|
|
View::text_edit(ui, cb, &mut self.address_edit, address_edit_opts);
|
|
|
|
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(6.0, 0.0);
|
|
|
|
|
|
|
|
ui.columns(2, |columns| {
|
|
|
|
columns[0].vertical_centered_justified(|ui| {
|
|
|
|
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
|
|
|
|
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, || {
|
|
|
|
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();
|
2024-05-01 03:41:59 +03:00
|
|
|
modal.disable_closing();
|
2024-05-15 14:56:28 +03:00
|
|
|
let mut w_sending = self.tor_sending.write();
|
2024-04-30 18:15:03 +03:00
|
|
|
*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 || {
|
2024-05-15 20:27:58 +03:00
|
|
|
let runtime = TokioNativeTlsRuntime::create().unwrap();
|
|
|
|
runtime
|
2024-04-30 18:15:03 +03:00
|
|
|
.block_on(async {
|
2024-05-16 19:37:28 +03:00
|
|
|
if wallet.send_tor(a, &addr)
|
2024-05-15 20:27:58 +03:00
|
|
|
.await
|
|
|
|
.is_some() {
|
2024-05-15 14:56:28 +03:00
|
|
|
let mut w_send_success = send_success.write();
|
2024-04-30 18:15:03 +03:00
|
|
|
*w_send_success = true;
|
|
|
|
} else {
|
2024-05-15 14:56:28 +03:00
|
|
|
let mut w_send_error = send_error.write();
|
2024-04-30 18:15:03 +03:00
|
|
|
*w_send_error = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.address_error = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
ui.add_space(6.0);
|
|
|
|
} else if has_send_err {
|
|
|
|
ui.add_space(6.0);
|
2024-04-30 23:28:49 +03:00
|
|
|
ui.vertical_centered(|ui| {
|
|
|
|
ui.label(RichText::new(t!("transport.tor_send_error"))
|
|
|
|
.size(17.0)
|
|
|
|
.color(Colors::RED));
|
|
|
|
});
|
2024-04-30 18:15:03 +03:00
|
|
|
ui.add_space(12.0);
|
|
|
|
|
|
|
|
// Setup spacing between buttons.
|
|
|
|
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
|
|
|
|
|
|
|
|
ui.columns(2, |columns| {
|
|
|
|
columns[0].vertical_centered_justified(|ui| {
|
|
|
|
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
|
|
|
|
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, || {
|
|
|
|
// Parse amount and send over Tor.
|
|
|
|
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
|
2024-05-15 14:56:28 +03:00
|
|
|
let mut w_send_error = self.tor_send_error.write();
|
2024-04-30 18:15:03 +03:00
|
|
|
*w_send_error = false;
|
2024-05-15 14:56:28 +03:00
|
|
|
let mut w_sending = self.tor_sending.write();
|
2024-04-30 18:15:03 +03:00
|
|
|
*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 || {
|
2024-05-15 20:27:58 +03:00
|
|
|
let runtime = TokioNativeTlsRuntime::create().unwrap();
|
|
|
|
runtime
|
2024-04-30 18:15:03 +03:00
|
|
|
.block_on(async {
|
|
|
|
let addr_str = addr_text.as_str();
|
|
|
|
let addr = &SlatepackAddress::try_from(addr_str)
|
|
|
|
.unwrap();
|
2024-05-16 19:37:28 +03:00
|
|
|
if wallet.send_tor(a, &addr)
|
2024-05-15 20:27:58 +03:00
|
|
|
.await
|
|
|
|
.is_some() {
|
2024-05-15 14:56:28 +03:00
|
|
|
let mut w_send_success = send_success.write();
|
2024-04-30 18:15:03 +03:00
|
|
|
*w_send_success = true;
|
|
|
|
} else {
|
2024-05-15 14:56:28 +03:00
|
|
|
let mut w_send_error = send_error.write();
|
2024-04-30 18:15:03 +03:00
|
|
|
*w_send_error = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2024-04-30 23:28:49 +03:00
|
|
|
ui.add_space(6.0);
|
2024-04-30 18:15:03 +03:00
|
|
|
} else {
|
|
|
|
ui.add_space(16.0);
|
|
|
|
ui.vertical_centered(|ui| {
|
|
|
|
View::small_loading_spinner(ui);
|
|
|
|
ui.add_space(12.0);
|
2024-05-01 05:19:47 +03:00
|
|
|
ui.label(RichText::new(t!("transport.tor_sending", "amount" => self.amount_edit))
|
2024-04-30 18:15:03 +03:00
|
|
|
.size(17.0)
|
2024-04-30 23:28:49 +03:00
|
|
|
.color(Colors::GRAY));
|
2024-04-30 18:15:03 +03:00
|
|
|
});
|
|
|
|
ui.add_space(10.0);
|
|
|
|
|
|
|
|
// Close modal on success sending.
|
|
|
|
if self.tor_success() {
|
|
|
|
modal.close();
|
|
|
|
}
|
|
|
|
}
|
2023-08-03 23:49:11 +03:00
|
|
|
}
|
|
|
|
}
|