ui: refactoring of wallet screen, fix colors
This commit is contained in:
+1
-2
@@ -17,11 +17,10 @@ use egui::{Align, Context, CornerRadius, CursorIcon, LayerId, Layout, Modifiers,
|
||||
|
||||
use crate::gui::icons::{ARROWS_IN, ARROWS_OUT, CARET_DOWN, MOON, SUN, X};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::ContentContainer;
|
||||
use crate::gui::views::{Content, KeyboardContent, Modal, TitlePanel, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::ExternalConnection;
|
||||
use crate::AppConfig;
|
||||
use crate::gui::views::types::ContentContainer;
|
||||
|
||||
/// Implements ui entry point and contains platform-specific callbacks.
|
||||
pub struct App<Platform> {
|
||||
|
||||
+5
-5
@@ -34,20 +34,20 @@ const GREEN: Color32 = Color32::from_rgb(0, 0x64, 0);
|
||||
const GREEN_DARK: Color32 = Color32::from_rgb(0, (0x64 as f32 * 1.3 + 0.5) as u8, 0);
|
||||
|
||||
const RED: Color32 = Color32::from_rgb(0x8B, 0, 0);
|
||||
const RED_DARK: Color32 = Color32::from_rgb((0x8B as f32 * 1.3 + 0.5) as u8, 0, 0);
|
||||
const RED_DARK: Color32 = Color32::from_rgb((0x8B as f32 * 1.3 + 0.5) as u8, 50, 30);
|
||||
|
||||
const BLUE: Color32 = Color32::from_rgb(0, 0x66, 0xE4);
|
||||
const BLUE_DARK: Color32 =
|
||||
Color32::from_rgb(0, (0x66 as f32 * 1.3 + 0.5) as u8, (0xE4 as f32 * 1.3 + 0.5) as u8);
|
||||
|
||||
const FILL: Color32 = Color32::from_gray(244);
|
||||
const FILL_DARK: Color32 = Color32::from_gray(24);
|
||||
const FILL_DARK: Color32 = Color32::from_gray(26);
|
||||
|
||||
const FILL_DEEP: Color32 = Color32::from_gray(238);
|
||||
const FILL_DEEP_DARK: Color32 = Color32::from_gray(18);
|
||||
const FILL_DEEP_DARK: Color32 = Color32::from_gray(32);
|
||||
|
||||
const FILL_LITE: Color32 = Color32::from_gray(249);
|
||||
const FILL_LITE_DARK: Color32 = Color32::from_gray(16);
|
||||
const FILL_LITE_DARK: Color32 = Color32::from_gray(21);
|
||||
|
||||
const TEXT: Color32 = Color32::from_gray(80);
|
||||
const TEXT_DARK: Color32 = Color32::from_gray(185);
|
||||
@@ -231,7 +231,7 @@ impl Colors {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn item_button() -> Color32 {
|
||||
pub fn item_button_text() -> Color32 {
|
||||
if use_dark() {
|
||||
ITEM_BUTTON_DARK
|
||||
} else {
|
||||
|
||||
@@ -50,7 +50,6 @@ impl Default for CameraContent {
|
||||
impl CameraContent {
|
||||
/// Draw camera content.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
ui.ctx().request_repaint();
|
||||
let rect = if let Some(img_data) = cb.camera_image() {
|
||||
if let Ok(img) =
|
||||
image::load_from_memory(&*img_data.0) {
|
||||
@@ -85,6 +84,7 @@ impl CameraContent {
|
||||
});
|
||||
});
|
||||
}
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
/// Draw camera image.
|
||||
@@ -311,7 +311,7 @@ impl CameraContent {
|
||||
|
||||
// Check if string contains Slatepack message prefix and postfix.
|
||||
if text.starts_with("BEGINSLATEPACK.") && text.ends_with("ENDSLATEPACK.") {
|
||||
return QrScanResult::Slatepack(ZeroingString::from(text));
|
||||
return QrScanResult::Slatepack(text.to_string());
|
||||
}
|
||||
|
||||
// Check Uniform Resource data.
|
||||
|
||||
+80
-24
@@ -12,42 +12,58 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{fs, thread};
|
||||
use egui::CornerRadius;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::{fs, thread};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::ARCHIVE_BOX;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::View;
|
||||
use crate::gui::Colors;
|
||||
|
||||
/// Type of button.
|
||||
pub enum FilePickContentType {
|
||||
Button, ItemButton(CornerRadius), Tab
|
||||
}
|
||||
|
||||
/// Button to pick file and parse its data into text.
|
||||
pub struct FilePickButton {
|
||||
pub struct FilePickContent {
|
||||
/// Content type.
|
||||
content_type: FilePickContentType,
|
||||
|
||||
/// Flag to check if file is picking.
|
||||
pub file_picking: Arc<AtomicBool>,
|
||||
file_picking: Arc<AtomicBool>,
|
||||
|
||||
/// Flag to parse file content after pick.
|
||||
parse_file: bool,
|
||||
/// Flag to check if file is parsing.
|
||||
pub file_parsing: Arc<AtomicBool>,
|
||||
file_parsing: Arc<AtomicBool>,
|
||||
/// File parsing result.
|
||||
pub file_parsing_result: Arc<RwLock<Option<String>>>
|
||||
file_parsing_result: Arc<RwLock<Option<String>>>,
|
||||
}
|
||||
|
||||
impl Default for FilePickButton {
|
||||
fn default() -> Self {
|
||||
impl FilePickContent {
|
||||
/// Create new content from provided type.
|
||||
pub fn new(content_type: FilePickContentType) -> Self {
|
||||
Self {
|
||||
content_type,
|
||||
file_picking: Arc::new(AtomicBool::new(false)),
|
||||
parse_file: true,
|
||||
file_parsing: Arc::new(AtomicBool::new(false)),
|
||||
file_parsing_result: Arc::new(RwLock::new(None))
|
||||
file_parsing_result: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilePickButton {
|
||||
/// Draw button content.
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_result: impl FnOnce(String)) {
|
||||
/// Do not parse file content.
|
||||
pub fn no_parse(mut self) -> Self {
|
||||
self.parse_file = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Draw content with provided callback to return path of the file.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks, on_pick: impl FnOnce(String)) {
|
||||
if self.file_picking.load(Ordering::Relaxed) {
|
||||
View::small_loading_spinner(ui);
|
||||
// Check file pick result.
|
||||
@@ -70,7 +86,7 @@ impl FilePickButton {
|
||||
r_res.clone().unwrap()
|
||||
};
|
||||
// Callback on result.
|
||||
on_result(text);
|
||||
on_pick(text);
|
||||
// Clear result.
|
||||
let mut w_res = self.file_parsing_result.write();
|
||||
*w_res = None;
|
||||
@@ -78,12 +94,48 @@ impl FilePickButton {
|
||||
}
|
||||
} else {
|
||||
// Draw button to pick file.
|
||||
let text = format!("{} {}", ARCHIVE_BOX, t!("choose_file"));
|
||||
View::colored_text_button(ui, text, Colors::blue(), Colors::white_or_black(false), || {
|
||||
if let Some(path) = cb.pick_file() {
|
||||
self.on_file_pick(path);
|
||||
match self.content_type {
|
||||
FilePickContentType::Button => {
|
||||
let text = format!("{} {}", ARCHIVE_BOX, t!("choose_file"));
|
||||
View::colored_text_button(ui,
|
||||
text,
|
||||
Colors::blue(),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
if let Some(path) = cb.pick_file() {
|
||||
if !self.parse_file {
|
||||
on_pick(path);
|
||||
return;
|
||||
}
|
||||
self.on_file_pick(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
FilePickContentType::ItemButton(r) => {
|
||||
View::item_button(ui, r, ARCHIVE_BOX, Some(Colors::blue()), || {
|
||||
if let Some(path) = cb.pick_file() {
|
||||
if !self.parse_file {
|
||||
on_pick(path);
|
||||
return;
|
||||
}
|
||||
self.on_file_pick(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
FilePickContentType::Tab => {
|
||||
let active = self.file_parsing.load(Ordering::Relaxed) ||
|
||||
self.file_picking.load(Ordering::Relaxed);
|
||||
View::tab_button(ui, ARCHIVE_BOX, Some(Colors::blue()), Some(active), |_| {
|
||||
if let Some(path) = cb.pick_file() {
|
||||
if !self.parse_file {
|
||||
on_pick(path);
|
||||
return;
|
||||
}
|
||||
self.on_file_pick(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +146,10 @@ impl FilePickButton {
|
||||
self.file_picking.store(true, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
// Do not parse result.
|
||||
if !self.parse_file {
|
||||
return;
|
||||
}
|
||||
self.file_parsing.store(true, Ordering::Relaxed);
|
||||
let result = self.file_parsing_result.clone();
|
||||
thread::spawn(move || {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Id, Margin, RichText, ScrollArea};
|
||||
|
||||
use crate::gui::icons::{ARROWS_COUNTER_CLOCKWISE, ARROW_LEFT, BRIEFCASE, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, GEAR, POWER};
|
||||
use crate::gui::icons::{ARROWS_COUNTER_CLOCKWISE, ARROW_LEFT, BRIEFCASE, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, GEAR, GLOBE, POWER};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::types::{NodeTab, NodeTabType};
|
||||
use crate::gui::views::network::{ConnectionsContent, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings};
|
||||
@@ -230,22 +230,26 @@ impl NetworkContent {
|
||||
let current_type = self.node_tab_content.get_type();
|
||||
ui.columns(4, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, DATABASE, current_type == NodeTabType::Info, |_| {
|
||||
let active = Some(current_type == NodeTabType::Info);
|
||||
View::tab_button(ui, DATABASE, None, active, |_| {
|
||||
self.node_tab_content = Box::new(NetworkNode::default());
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, GAUGE, current_type == NodeTabType::Metrics, |_| {
|
||||
let active = Some(current_type == NodeTabType::Metrics);
|
||||
View::tab_button(ui, GAUGE, None, active, |_| {
|
||||
self.node_tab_content = Box::new(NetworkMetrics::default());
|
||||
});
|
||||
});
|
||||
columns[2].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, FACTORY, current_type == NodeTabType::Mining, |_| {
|
||||
let active = Some(current_type == NodeTabType::Mining);
|
||||
View::tab_button(ui, FACTORY, None, active, |_| {
|
||||
self.node_tab_content = Box::new(NetworkMining::default());
|
||||
});
|
||||
});
|
||||
columns[3].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, FADERS, current_type == NodeTabType::Settings, |_| {
|
||||
let active = Some(current_type == NodeTabType::Settings);
|
||||
View::tab_button(ui, FADERS, None, active, |_| {
|
||||
self.node_tab_content = Box::new(NetworkSettings::default());
|
||||
});
|
||||
});
|
||||
@@ -276,7 +280,7 @@ impl NetworkContent {
|
||||
self.settings_content = None;
|
||||
});
|
||||
} else if !show_connections {
|
||||
View::title_button_big(ui, DOTS_THREE_OUTLINE_VERTICAL, |ui| {
|
||||
View::title_button_big(ui, GLOBE, |_| {
|
||||
AppConfig::toggle_show_connections_network_panel();
|
||||
});
|
||||
} else if !dual_panel {
|
||||
|
||||
@@ -764,10 +764,8 @@ fn peer_item_ui(ui: &mut egui::Ui,
|
||||
rect.set_height(42.0);
|
||||
|
||||
// Draw round background.
|
||||
let mut bg_rect = rect.clone();
|
||||
bg_rect.min += egui::emath::vec2(6.0, 0.0);
|
||||
let item_rounding = View::item_rounding(index, len, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
ui.painter().rect(rect,
|
||||
item_rounding,
|
||||
Colors::white_or_black(false),
|
||||
View::item_stroke(),
|
||||
@@ -798,7 +796,7 @@ fn peer_item_ui(ui: &mut egui::Ui,
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.add_space(6.0);
|
||||
// Draw peer address.
|
||||
let peer_text = format!("{} {}", GLOBE_SIMPLE, &peer_addr);
|
||||
ui.label(RichText::new(peer_text)
|
||||
|
||||
+73
-65
@@ -21,15 +21,15 @@ use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, Modal, View};
|
||||
use crate::gui::views::types::QrScanResult;
|
||||
|
||||
/// QR code scan [`Modal`] content.
|
||||
pub struct CameraScanModal {
|
||||
/// Camera content for QR scan [`Modal`].
|
||||
/// QR code scanning content.
|
||||
pub struct CameraScanContent {
|
||||
/// Camera content.
|
||||
camera_content: Option<CameraContent>,
|
||||
/// QR code scan result
|
||||
/// Scan result.
|
||||
qr_scan_result: Option<QrScanResult>,
|
||||
}
|
||||
|
||||
impl Default for CameraScanModal {
|
||||
impl Default for CameraScanContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
camera_content: Some(CameraContent::default()),
|
||||
@@ -38,68 +38,20 @@ impl Default for CameraScanModal {
|
||||
}
|
||||
}
|
||||
|
||||
impl CameraScanModal {
|
||||
impl CameraScanContent {
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
mut on_result: impl FnMut(&QrScanResult)) {
|
||||
pub fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
mut on_result: impl FnMut(&QrScanResult)) {
|
||||
// Show scan result if exists or show camera content while scanning.
|
||||
if let Some(result) = &self.qr_scan_result {
|
||||
let mut result_text = result.text();
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(3.0);
|
||||
ScrollArea::vertical()
|
||||
.id_salt(Id::from("qr_scan_result_input"))
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.max_height(128.0)
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.add_space(7.0);
|
||||
egui::TextEdit::multiline(&mut result_text)
|
||||
.font(egui::TextStyle::Small)
|
||||
.desired_rows(5)
|
||||
.interactive(false)
|
||||
.desired_width(f32::INFINITY)
|
||||
.show(ui);
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Show copy button.
|
||||
ui.vertical_centered(|ui| {
|
||||
let copy_text = format!("{} {}", COPY, t!("copy"));
|
||||
View::button(ui, copy_text, Colors::white_or_black(false), || {
|
||||
cb.copy_string_to_buffer(result_text.to_string());
|
||||
self.qr_scan_result = None;
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.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.qr_scan_result = None;
|
||||
self.camera_content = None;
|
||||
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.qr_scan_result = None;
|
||||
self.camera_content = Some(CameraContent::default());
|
||||
cb.start_camera();
|
||||
});
|
||||
});
|
||||
if let Some(result) = &self.qr_scan_result.clone() {
|
||||
Self::result_ui(ui, result, cb, || {
|
||||
Modal::close();
|
||||
}, || {
|
||||
self.qr_scan_result = None;
|
||||
cb.start_camera();
|
||||
Modal::set_title(t!("scan_qr"));
|
||||
});
|
||||
} else if let Some(camera_content) = self.camera_content.as_mut() {
|
||||
if let Some(result) = camera_content.qr_scan_result() {
|
||||
@@ -126,4 +78,60 @@ impl CameraScanModal {
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw scan result content.
|
||||
pub fn result_ui(ui: &mut egui::Ui,
|
||||
result: &QrScanResult,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_close: impl FnOnce(),
|
||||
on_repeat: impl FnOnce()) {
|
||||
let mut result_text = result.text();
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(3.0);
|
||||
ScrollArea::vertical()
|
||||
.id_salt(Id::from("qr_scan_result_input"))
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.max_height(128.0)
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.add_space(7.0);
|
||||
egui::TextEdit::multiline(&mut result_text)
|
||||
.font(egui::TextStyle::Small)
|
||||
.desired_rows(5)
|
||||
.interactive(false)
|
||||
.desired_width(f32::INFINITY)
|
||||
.show(ui);
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Show copy button.
|
||||
ui.vertical_centered(|ui| {
|
||||
let copy_text = format!("{} {}", COPY, t!("copy"));
|
||||
View::button(ui, copy_text, Colors::white_or_black(false), || {
|
||||
cb.copy_string_to_buffer(result_text.to_string());
|
||||
});
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.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), || {
|
||||
on_close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("repeat"), Colors::white_or_black(false), || {
|
||||
on_repeat();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,7 @@
|
||||
|
||||
use crate::gui::icons::GLOBE_SIMPLE;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::settings::interface::InterfaceSettingsContent;
|
||||
use crate::gui::views::settings::network::NetworkSettingsContent;
|
||||
use crate::gui::views::settings::{InterfaceSettingsContent, NetworkSettingsContent};
|
||||
use crate::gui::views::types::ContentContainer;
|
||||
use crate::gui::views::View;
|
||||
use crate::gui::Colors;
|
||||
@@ -26,6 +25,7 @@ pub struct SettingsContent {
|
||||
interface_settings: InterfaceSettingsContent,
|
||||
/// Network communication settings.
|
||||
network_settings: NetworkSettingsContent,
|
||||
// tor_settings: TorSettingsContent,
|
||||
}
|
||||
|
||||
impl Default for SettingsContent {
|
||||
@@ -33,6 +33,7 @@ impl Default for SettingsContent {
|
||||
Self {
|
||||
interface_settings: InterfaceSettingsContent::default(),
|
||||
network_settings: NetworkSettingsContent::default(),
|
||||
//tor_settings: TorSettingsContent::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +54,22 @@ impl SettingsContent {
|
||||
|
||||
// Show network settings.
|
||||
self.network_settings.ui(ui, cb);
|
||||
ui.add_space(6.0);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Do not show Tor settings on Android.
|
||||
// let os = OperatingSystem::from_target_os();
|
||||
// let show_tor = os != OperatingSystem::Android;
|
||||
// if show_tor {
|
||||
// View::horizontal_line(ui, Colors::stroke());
|
||||
// ui.add_space(6.0);
|
||||
//
|
||||
// View::sub_title(ui, format!("{} {}", CIRCLE_HALF, t!("transport.tor_network")));
|
||||
// View::horizontal_line(ui, Colors::stroke());
|
||||
// ui.add_space(6.0);
|
||||
//
|
||||
// // Show Tor settings.
|
||||
// self.tor_settings.ui(ui, cb);
|
||||
// ui.add_space(8.0);
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,13 @@
|
||||
// limitations under the License.
|
||||
|
||||
mod content;
|
||||
mod interface;
|
||||
mod network;
|
||||
pub use content::*;
|
||||
|
||||
pub use content::*;
|
||||
mod interface;
|
||||
pub use interface::*;
|
||||
|
||||
mod network;
|
||||
pub use network::*;
|
||||
|
||||
mod tor;
|
||||
pub use tor::*;
|
||||
@@ -94,9 +94,6 @@ impl NetworkSettingsContent {
|
||||
/// Draw proxy edit modal content.
|
||||
fn proxy_modal_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let on_save = |c: &mut NetworkSettingsContent| {
|
||||
// Check if URL proxy is correct.
|
||||
let http = "http://";
|
||||
let socks = "socks5://";
|
||||
let proxy = c.proxy_url_edit.trim().to_string();
|
||||
let use_socks = AppConfig::use_socks_proxy();
|
||||
// Clear value if empty.
|
||||
@@ -109,6 +106,9 @@ impl NetworkSettingsContent {
|
||||
Modal::close();
|
||||
return;
|
||||
}
|
||||
// Format URL.
|
||||
let http = "http://";
|
||||
let socks = "socks5://";
|
||||
let url = if use_socks {
|
||||
let p = proxy.replace(http, "");
|
||||
if !p.contains(socks) {
|
||||
@@ -200,7 +200,7 @@ impl NetworkSettingsContent {
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw language selection item content.
|
||||
/// Draw proxy item content.
|
||||
fn proxy_item_ui(&mut self, ui: &mut egui::Ui) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
|
||||
@@ -77,7 +77,7 @@ pub trait ContentContainer {
|
||||
#[derive(Clone)]
|
||||
pub enum QrScanResult {
|
||||
/// Slatepack message.
|
||||
Slatepack(ZeroingString),
|
||||
Slatepack(String),
|
||||
/// Slatepack address.
|
||||
Address(ZeroingString),
|
||||
/// Parsed text.
|
||||
|
||||
+30
-8
@@ -197,17 +197,31 @@ impl View {
|
||||
/// Tab button with white background fill color, contains only icon.
|
||||
pub fn tab_button(ui: &mut egui::Ui,
|
||||
icon: &str,
|
||||
active: bool,
|
||||
color: Option<Color32>,
|
||||
selected: Option<bool>,
|
||||
action: impl FnOnce(&mut egui::Ui)) {
|
||||
ui.scope(|ui| {
|
||||
let text_color = match active {
|
||||
true => Colors::title(false),
|
||||
false => Colors::text(false)
|
||||
let text_color = if let Some(c) = color {
|
||||
if selected.is_none() {
|
||||
Colors::inactive_text().gamma_multiply(1.2)
|
||||
} else {
|
||||
c
|
||||
}
|
||||
} else {
|
||||
if let Some(active) = selected {
|
||||
match active {
|
||||
true => Colors::gray(),
|
||||
false => Colors::item_button_text()
|
||||
}
|
||||
} else {
|
||||
Colors::inactive_text().gamma_multiply(1.2)
|
||||
}
|
||||
};
|
||||
|
||||
let mut button = Button::new(RichText::new(icon).size(22.0).color(text_color));
|
||||
|
||||
if !active {
|
||||
let active_not_selected = selected.is_some() && !selected.unwrap();
|
||||
if active_not_selected {
|
||||
// Disable expansion on click/hover.
|
||||
ui.style_mut().visuals.widgets.hovered.expansion = 0.0;
|
||||
ui.style_mut().visuals.widgets.active.expansion = 0.0;
|
||||
@@ -223,7 +237,13 @@ impl View {
|
||||
button = button.fill(Colors::fill()).stroke(Stroke::NONE);
|
||||
}
|
||||
|
||||
let br = button.ui(ui).on_hover_cursor(CursorIcon::PointingHand);
|
||||
// Setup pointer style.
|
||||
let br = if active_not_selected {
|
||||
button.ui(ui).on_hover_cursor(CursorIcon::PointingHand)
|
||||
} else {
|
||||
button.ui(ui)
|
||||
};
|
||||
|
||||
br.surrender_focus();
|
||||
if br.clicked() {
|
||||
action(ui);
|
||||
@@ -322,7 +342,7 @@ impl View {
|
||||
ui.visuals_mut().widgets.active.bg_stroke = Stroke::NONE;
|
||||
|
||||
// Setup button text color.
|
||||
let text_color = if let Some(c) = color { c } else { Colors::item_button() };
|
||||
let text_color = if let Some(c) = color { c } else { Colors::item_button_text() };
|
||||
|
||||
// Show button.
|
||||
let br = Button::new(RichText::new(text).size(20.0).color(text_color))
|
||||
@@ -592,7 +612,9 @@ impl View {
|
||||
if resp.clicked() || resp.dragged() {
|
||||
on_click();
|
||||
}
|
||||
let shape = RectShape::filled(resp.rect, CornerRadius::ZERO, Colors::semi_transparent());
|
||||
let shape = RectShape::filled(resp.rect,
|
||||
CornerRadius::ZERO,
|
||||
Colors::semi_transparent().gamma_multiply(0.7));
|
||||
ui.painter().add(shape);
|
||||
}
|
||||
|
||||
|
||||
+147
-192
@@ -22,13 +22,12 @@ use crate::gui::views::settings::SettingsContent;
|
||||
use crate::gui::views::types::{ContentContainer, LinePosition, ModalPosition, TitleContentType, TitleType};
|
||||
use crate::gui::views::wallets::creation::WalletCreationContent;
|
||||
use crate::gui::views::wallets::modals::{AddWalletModal, OpenWalletModal, WalletConnectionModal, WalletsModal};
|
||||
use crate::gui::views::wallets::types::WalletTabType;
|
||||
use crate::gui::views::wallets::wallet::types::wallet_status_text;
|
||||
use crate::gui::views::wallets::wallet::types::{wallet_status_text, WalletContentContainer};
|
||||
use crate::gui::views::wallets::WalletContent;
|
||||
use crate::gui::views::{Content, Modal, TitlePanel, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::ConnectionMethod;
|
||||
use crate::wallet::{ExternalConnection, Wallet, WalletList};
|
||||
use crate::wallet::{Wallet, WalletList};
|
||||
use crate::AppConfig;
|
||||
|
||||
/// Wallets content.
|
||||
@@ -46,7 +45,7 @@ pub struct WalletsContent {
|
||||
wallet_selection_content: WalletsModal,
|
||||
|
||||
/// Selected [`Wallet`] content.
|
||||
wallet_content: Option<WalletContent>,
|
||||
wallet_content: WalletContent,
|
||||
/// Wallet creation content.
|
||||
creation_content: Option<WalletCreationContent>,
|
||||
|
||||
@@ -64,10 +63,10 @@ impl Default for WalletsContent {
|
||||
Self {
|
||||
wallets: WalletList::default(),
|
||||
wallet_selection_content: WalletsModal::new(None, None, true),
|
||||
open_wallet_content: OpenWalletModal::new(None),
|
||||
open_wallet_content: OpenWalletModal::new(),
|
||||
add_wallet_modal_content: AddWalletModal::default(),
|
||||
conn_selection_content: WalletConnectionModal::new(ConnectionMethod::Integrated),
|
||||
wallet_content: None,
|
||||
wallet_content: WalletContent::default(),
|
||||
creation_content: None,
|
||||
settings_content: None,
|
||||
}
|
||||
@@ -94,9 +93,9 @@ impl ContentContainer for WalletsContent {
|
||||
});
|
||||
},
|
||||
OPEN_WALLET_MODAL => {
|
||||
self.open_wallet_content.ui(ui, modal, cb, |pass, _| {
|
||||
if let Some(content) = &self.wallet_content {
|
||||
return match content.wallet.open(pass) {
|
||||
self.open_wallet_content.ui(ui, modal, cb, |pass| {
|
||||
if let Some(w) = self.wallets.selected().as_ref() {
|
||||
return match w.open(pass) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false
|
||||
};
|
||||
@@ -106,22 +105,25 @@ impl ContentContainer for WalletsContent {
|
||||
},
|
||||
SELECT_CONNECTION_MODAL => {
|
||||
self.conn_selection_content.ui(ui, modal, cb, |conn| {
|
||||
if let Some(wallet_content) = &self.wallet_content {
|
||||
wallet_content.wallet.update_connection(&conn);
|
||||
if let Some(w) = self.wallets.selected().as_ref() {
|
||||
w.update_connection(&conn);
|
||||
}
|
||||
});
|
||||
}
|
||||
SELECT_WALLET_MODAL => {
|
||||
let mut w: Option<Wallet> = None;
|
||||
let mut d: Option<String> = None;
|
||||
self.wallet_selection_content.ui(ui, &mut self.wallets, |wallet, data| {
|
||||
if !wallet.is_open() {
|
||||
self.open_wallet_content = OpenWalletModal::new(data.clone());
|
||||
Modal::new(OPEN_WALLET_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.open"))
|
||||
.show();
|
||||
}
|
||||
self.wallet_content = Some(WalletContent::new(wallet, data));
|
||||
w = Some(wallet);
|
||||
d = data;
|
||||
});
|
||||
if let Some(wallet) = &w {
|
||||
if !wallet.is_open() {
|
||||
self.show_opening_modal(wallet, d, cb);
|
||||
} else {
|
||||
self.select_wallet(wallet, d, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -130,7 +132,7 @@ impl ContentContainer for WalletsContent {
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
if let Some(data) = crate::consume_incoming_data() {
|
||||
if !data.is_empty() {
|
||||
self.on_data(ui, Some(data));
|
||||
self.on_data(ui, Some(data), cb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,12 +142,10 @@ impl ContentContainer for WalletsContent {
|
||||
let dual_panel = is_dual_panel_mode(ui);
|
||||
let content_width = ui.available_width();
|
||||
let list_hidden = showing_settings || creating_wallet || self.wallets.list().is_empty()
|
||||
|| (showing_wallet && self.wallet_content.as_ref().unwrap().qr_scan_showing())
|
||||
|| (dual_panel && showing_wallet && !AppConfig::show_wallets_at_dual_panel())
|
||||
|| (!dual_panel && showing_wallet);
|
||||
|| (showing_wallet && (!dual_panel || !AppConfig::show_wallets_at_dual_panel()));
|
||||
|
||||
// Show title panel.
|
||||
self.title_ui(ui, dual_panel, showing_wallet, cb);
|
||||
self.title_ui(ui, dual_panel, cb);
|
||||
|
||||
egui::SidePanel::right("wallet_panel")
|
||||
.resizable(false)
|
||||
@@ -155,54 +155,53 @@ impl ContentContainer for WalletsContent {
|
||||
content_width - Content::SIDE_PANEL_WIDTH
|
||||
})
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill_deep(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, showing_wallet, |ui| {
|
||||
// Show opened wallet content.
|
||||
if let Some(content) = self.wallet_content.as_mut() {
|
||||
content.ui(ui, cb);
|
||||
// Show selected wallet content.
|
||||
if let Some(w) = self.wallets.selected().as_ref() {
|
||||
self.wallet_content.ui(ui, w, cb);
|
||||
}
|
||||
});
|
||||
|
||||
if !list_hidden {
|
||||
egui::TopBottomPanel::bottom("wallets_bottom_panel")
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Margin {
|
||||
left: (View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING) as i8,
|
||||
right: (View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING) as i8,
|
||||
top: View::TAB_ITEMS_PADDING as i8,
|
||||
bottom: (View::get_bottom_inset() + View::TAB_ITEMS_PADDING) as i8,
|
||||
},
|
||||
fill: Colors::fill(),
|
||||
..Default::default()
|
||||
})
|
||||
.resizable(false)
|
||||
.show_inside(ui, |ui| {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
egui::TopBottomPanel::bottom("wallets_bottom_panel")
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Margin {
|
||||
left: (View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING) as i8,
|
||||
right: (View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING) as i8,
|
||||
top: View::TAB_ITEMS_PADDING as i8,
|
||||
bottom: (View::get_bottom_inset() + View::TAB_ITEMS_PADDING) as i8,
|
||||
},
|
||||
fill: Colors::fill(),
|
||||
..Default::default()
|
||||
})
|
||||
.resizable(false)
|
||||
.show_animated_inside(ui, !list_hidden, |ui| {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
|
||||
// Setup spacing between tabs.
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0);
|
||||
// Setup vertical padding inside buttons.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(10.0, 4.0);
|
||||
// Setup spacing between tabs.
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0);
|
||||
// Setup vertical padding inside buttons.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(10.0, 4.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
let pressed = Modal::opened() == Some(ADD_WALLET_MODAL);
|
||||
View::tab_button(ui, PLUS, pressed, |_| {
|
||||
self.show_add_wallet_modal();
|
||||
});
|
||||
ui.vertical_centered(|ui| {
|
||||
let pressed = Modal::opened() == Some(ADD_WALLET_MODAL);
|
||||
View::tab_button(ui, PLUS, None, Some(pressed), |_| {
|
||||
self.show_add_wallet_modal();
|
||||
});
|
||||
|
||||
// Draw content divider line.
|
||||
let r = {
|
||||
let mut r = rect.clone();
|
||||
r.min.y -= View::TAB_ITEMS_PADDING;
|
||||
r.min.x -= View::TAB_ITEMS_PADDING;
|
||||
r.max.x += View::TAB_ITEMS_PADDING;
|
||||
r
|
||||
};
|
||||
View::line(ui, LinePosition::TOP, &r, Colors::stroke());
|
||||
});
|
||||
}
|
||||
|
||||
// Draw content divider line.
|
||||
let r = {
|
||||
let mut r = rect.clone();
|
||||
r.min.y -= View::TAB_ITEMS_PADDING;
|
||||
r.min.x -= View::TAB_ITEMS_PADDING;
|
||||
r.max.x += View::TAB_ITEMS_PADDING;
|
||||
r
|
||||
};
|
||||
View::line(ui, LinePosition::TOP, &r, Colors::stroke());
|
||||
});
|
||||
|
||||
egui::SidePanel::left("wallet_list_panel")
|
||||
.exact_width(if dual_panel && showing_wallet {
|
||||
@@ -218,7 +217,7 @@ impl ContentContainer for WalletsContent {
|
||||
top: 3.0 as i8,
|
||||
bottom: 4.0 as i8,
|
||||
},
|
||||
fill: Colors::fill_deep(),
|
||||
fill: Colors::fill(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, !list_hidden, |ui| {
|
||||
@@ -226,13 +225,23 @@ impl ContentContainer for WalletsContent {
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
||||
}
|
||||
// Show wallet list.
|
||||
self.wallet_list_ui(ui);
|
||||
self.wallet_list_ui(ui, cb);
|
||||
});
|
||||
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
fill: if creating_wallet {
|
||||
Colors::TRANSPARENT
|
||||
inner_margin: if self.showing_settings() {
|
||||
Margin {
|
||||
left: (View::far_left_inset_margin(ui) + 4.0) as i8,
|
||||
right: (View::far_right_inset_margin(ui) + 4.0) as i8,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
} else {
|
||||
Margin::default()
|
||||
},
|
||||
fill: if self.showing_settings() {
|
||||
Colors::fill_lite()
|
||||
} else {
|
||||
Colors::fill_deep()
|
||||
},
|
||||
@@ -258,19 +267,19 @@ impl ContentContainer for WalletsContent {
|
||||
});
|
||||
}
|
||||
} else if self.creating_wallet() {
|
||||
// Show wallet creation content.
|
||||
let mut created_wallet: Option<Wallet> = None;
|
||||
let creation = self.creation_content.as_mut().unwrap();
|
||||
let pass = creation.pass.clone();
|
||||
let mut created = false;
|
||||
// Show wallet creation content.
|
||||
creation.content_ui(ui, cb, |wallet| {
|
||||
self.wallets.add(wallet.clone());
|
||||
if let Ok(_) = wallet.open(pass.clone()) {
|
||||
self.wallet_content = Some(WalletContent::new(wallet, None));
|
||||
}
|
||||
created = true;
|
||||
created_wallet = Some(wallet);
|
||||
});
|
||||
if created {
|
||||
if let Some(w) = &created_wallet {
|
||||
self.creation_content = None;
|
||||
self.wallets.add(w.clone());
|
||||
if let Ok(_) = w.open(pass.clone()) {
|
||||
self.select_wallet(w, None, cb);
|
||||
}
|
||||
}
|
||||
} else if self.wallets.list().is_empty() {
|
||||
View::center_content(ui, 350.0 + View::get_bottom_inset(), |ui| {
|
||||
@@ -311,20 +320,12 @@ impl WalletsContent {
|
||||
}
|
||||
return false;
|
||||
} else if self.showing_wallet() {
|
||||
let content = self.wallet_content.as_mut().unwrap();
|
||||
// Close opened QR code scanner.
|
||||
if content.qr_scan_showing() {
|
||||
cb.stop_camera();
|
||||
content.close_qr_scan();
|
||||
return false;
|
||||
// Go back at stack or close wallet.
|
||||
if self.wallet_content.can_back() {
|
||||
self.wallet_content.back(cb);
|
||||
} else {
|
||||
self.wallets.select(None);
|
||||
}
|
||||
// Close account list.
|
||||
if content.account_list_showing() {
|
||||
content.close_qr_scan();
|
||||
return false;
|
||||
}
|
||||
// Close opened wallet.
|
||||
self.wallet_content = None;
|
||||
return false;
|
||||
}
|
||||
true
|
||||
@@ -332,8 +333,7 @@ impl WalletsContent {
|
||||
|
||||
/// Check if opened wallet is showing.
|
||||
pub fn showing_wallet(&self) -> bool {
|
||||
if let Some(wallet_content) = &self.wallet_content {
|
||||
let w = &wallet_content.wallet;
|
||||
if let Some(w) = self.wallets.selected().as_ref() {
|
||||
return w.is_open() && !w.is_deleted() &&
|
||||
w.get_config().chain_type == AppConfig::chain_type();
|
||||
}
|
||||
@@ -351,7 +351,7 @@ impl WalletsContent {
|
||||
}
|
||||
|
||||
/// Handle data from deeplink or opened file.
|
||||
fn on_data(&mut self, ui: &mut egui::Ui, data: Option<String>) {
|
||||
fn on_data(&mut self, ui: &mut egui::Ui, data: Option<String>, cb: &dyn PlatformCallbacks) {
|
||||
let wallets_size = self.wallets.list().len();
|
||||
if wallets_size == 0 {
|
||||
return;
|
||||
@@ -364,9 +364,9 @@ impl WalletsContent {
|
||||
if wallets_size == 1 {
|
||||
let w = self.wallets.list()[0].clone();
|
||||
if w.is_open() {
|
||||
self.wallet_content = Some(WalletContent::new(w, data));
|
||||
self.select_wallet(&w, data, cb);
|
||||
} else {
|
||||
self.show_opening_modal(w, data);
|
||||
self.show_opening_modal(&w, data, cb);
|
||||
}
|
||||
} else {
|
||||
self.wallet_selection_content = WalletsModal::new(None, data, true);
|
||||
@@ -387,69 +387,32 @@ impl WalletsContent {
|
||||
}
|
||||
|
||||
/// Draw [`TitlePanel`] content.
|
||||
fn title_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
dual_panel: bool,
|
||||
show_wallet: bool,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
let show_list = AppConfig::show_wallets_at_dual_panel();
|
||||
fn title_ui(&mut self, ui: &mut egui::Ui, dual_panel: bool, cb: &dyn PlatformCallbacks) {
|
||||
let showing_settings = self.showing_settings();
|
||||
let show_wallet = self.showing_wallet();
|
||||
let show_list = AppConfig::show_wallets_at_dual_panel();
|
||||
let creating_wallet = self.creating_wallet();
|
||||
let account_list_showing = show_wallet && self.wallet_content
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.account_list_showing();
|
||||
let qr_scan = {
|
||||
let mut scan = false;
|
||||
if show_wallet {
|
||||
scan = self.wallet_content.as_mut().unwrap().qr_scan_showing();
|
||||
}
|
||||
scan
|
||||
};
|
||||
|
||||
// Setup title.
|
||||
let title_content = if show_wallet && (!dual_panel
|
||||
|| (dual_panel && !show_list)) && !creating_wallet && !showing_settings {
|
||||
let wallet_content = self.wallet_content.as_ref().unwrap();
|
||||
let wallet_tab_type = wallet_content.current_tab.get_type();
|
||||
let title_text = if account_list_showing {
|
||||
t!("wallets.accounts")
|
||||
} else if qr_scan {
|
||||
t!("scan_qr")
|
||||
} else {
|
||||
wallet_tab_type.name()
|
||||
};
|
||||
if wallet_tab_type == WalletTabType::Settings {
|
||||
TitleType::Single(TitleContentType::Title(title_text))
|
||||
} else {
|
||||
let subtitle_text = wallet_content.wallet.get_config().name;
|
||||
TitleType::Single(TitleContentType::WithSubTitle(title_text, subtitle_text, false))
|
||||
}
|
||||
let title = self.wallet_content.title();
|
||||
let subtitle = self.wallets.selected().unwrap().get_config().name;
|
||||
TitleType::Single(TitleContentType::WithSubTitle(title, subtitle, false))
|
||||
} else {
|
||||
let title_text = if showing_settings {
|
||||
t!("settings")
|
||||
} else if qr_scan {
|
||||
t!("scan_qr")
|
||||
} else if creating_wallet {
|
||||
t!("wallets.add")
|
||||
} else {
|
||||
t!("wallets.title")
|
||||
};
|
||||
let dual_title = !showing_settings && !qr_scan && !creating_wallet &&
|
||||
let dual_title = !showing_settings && !creating_wallet &&
|
||||
show_wallet && dual_panel;
|
||||
if dual_title {
|
||||
let wallet_content = self.wallet_content.as_ref().unwrap();
|
||||
let wallet_tab_type = wallet_content.current_tab.get_type();
|
||||
let wallet_title_text = if account_list_showing {
|
||||
t!("wallets.accounts")
|
||||
} else {
|
||||
wallet_tab_type.name()
|
||||
};
|
||||
let wallet_title_content = if wallet_tab_type == WalletTabType::Settings {
|
||||
TitleContentType::Title(wallet_title_text)
|
||||
} else {
|
||||
let subtitle_text = wallet_content.wallet.get_config().name;
|
||||
TitleContentType::WithSubTitle(wallet_title_text, subtitle_text, false)
|
||||
};
|
||||
let title = self.wallet_content.title();
|
||||
let subtitle = self.wallets.selected().unwrap().get_config().name;
|
||||
let wallet_title_content = TitleContentType::WithSubTitle(title, subtitle, false);
|
||||
TitleType::Dual(TitleContentType::Title(title_text), wallet_title_content)
|
||||
} else {
|
||||
TitleType::Single(TitleContentType::Title(title_text))
|
||||
@@ -466,22 +429,11 @@ impl WalletsContent {
|
||||
});
|
||||
} else if show_wallet && !dual_panel {
|
||||
View::title_button_big(ui, ARROW_LEFT, |_| {
|
||||
// Close QR code scanner.
|
||||
let wallet_qr_scan = self.wallet_content
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.qr_scan_showing();
|
||||
if wallet_qr_scan {
|
||||
cb.stop_camera();
|
||||
self.wallet_content.as_mut().unwrap().close_qr_scan();
|
||||
return;
|
||||
if self.wallet_content.can_back() {
|
||||
self.wallet_content.back(cb);
|
||||
} else {
|
||||
self.wallets.select(None);
|
||||
}
|
||||
// Close account list.
|
||||
if account_list_showing {
|
||||
self.wallet_content.as_mut().unwrap().close_account_list();
|
||||
return;
|
||||
}
|
||||
self.wallet_content = None;
|
||||
});
|
||||
} else if self.creating_wallet() {
|
||||
let mut close = false;
|
||||
@@ -496,21 +448,14 @@ impl WalletsContent {
|
||||
self.creation_content = None;
|
||||
}
|
||||
} else if show_wallet && dual_panel {
|
||||
if qr_scan {
|
||||
View::title_button_big(ui, ARROW_LEFT, |_| {
|
||||
cb.stop_camera();
|
||||
self.wallet_content.as_mut().unwrap().close_qr_scan();
|
||||
});
|
||||
let list_icon = if show_list {
|
||||
SIDEBAR_SIMPLE
|
||||
} else {
|
||||
let list_icon = if show_list {
|
||||
SIDEBAR_SIMPLE
|
||||
} else {
|
||||
SUITCASE
|
||||
};
|
||||
View::title_button_big(ui, list_icon, |_| {
|
||||
AppConfig::toggle_show_wallets_at_dual_panel();
|
||||
});
|
||||
}
|
||||
SUITCASE
|
||||
};
|
||||
View::title_button_big(ui, list_icon, |_| {
|
||||
AppConfig::toggle_show_wallets_at_dual_panel();
|
||||
});
|
||||
} else if !Content::is_dual_panel_mode(ui.ctx()) {
|
||||
View::title_button_big(ui, GLOBE, |_| {
|
||||
Content::toggle_network_panel();
|
||||
@@ -530,7 +475,7 @@ impl WalletsContent {
|
||||
}
|
||||
|
||||
/// Draw list of wallets.
|
||||
fn wallet_list_ui(&mut self, ui: &mut egui::Ui) {
|
||||
fn wallet_list_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
ScrollArea::vertical()
|
||||
.id_salt("wallet_list_scroll")
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
@@ -542,20 +487,27 @@ impl WalletsContent {
|
||||
ui.add_space(15.0);
|
||||
|
||||
let list = self.wallets.list().clone();
|
||||
for w in &list {
|
||||
for w in list.iter() {
|
||||
let id = w.get_config().id;
|
||||
// Remove deleted.
|
||||
if w.is_deleted() {
|
||||
self.wallet_content = None;
|
||||
self.wallets.remove(w.get_config().id);
|
||||
self.wallets.select(None);
|
||||
self.wallets.remove(id);
|
||||
ui.ctx().request_repaint();
|
||||
continue;
|
||||
}
|
||||
// Check if wallet reopen is needed.
|
||||
if w.reopen_needed() && !w.is_open() {
|
||||
w.set_reopen(false);
|
||||
self.show_opening_modal(w.clone(), None);
|
||||
self.show_opening_modal(w, None, cb);
|
||||
}
|
||||
self.wallet_item_ui(ui, w);
|
||||
// Check if wallet is selected.
|
||||
let current = if let Some(selected) = self.wallets.selected().as_ref() {
|
||||
selected.get_config().id == id
|
||||
} else {
|
||||
false
|
||||
};
|
||||
self.wallet_item_ui(ui, w, current, cb);
|
||||
ui.add_space(5.0);
|
||||
}
|
||||
});
|
||||
@@ -563,13 +515,12 @@ impl WalletsContent {
|
||||
}
|
||||
|
||||
/// Draw wallet list item.
|
||||
fn wallet_item_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
fn wallet_item_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
current: bool,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
let config = wallet.get_config();
|
||||
let current = if let Some(content) = &self.wallet_content {
|
||||
content.wallet.get_config().id == config.id && wallet.is_open()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
@@ -586,12 +537,11 @@ impl WalletsContent {
|
||||
if !wallet.is_open() {
|
||||
// Show button to open closed wallet.
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), FOLDER_OPEN, None, || {
|
||||
self.show_opening_modal(wallet.clone(), None);
|
||||
self.show_opening_modal(wallet, None, cb);
|
||||
});
|
||||
if !wallet.syncing() {
|
||||
let mut show_selection = false;
|
||||
View::item_button(ui, CornerRadius::default(), GLOBE, None, || {
|
||||
self.wallet_content = Some(WalletContent::new(wallet.clone(), None));
|
||||
self.select_wallet(wallet, None, cb);
|
||||
self.conn_selection_content =
|
||||
WalletConnectionModal::new(wallet.get_current_connection());
|
||||
// Show connection selection modal.
|
||||
@@ -599,17 +549,13 @@ impl WalletsContent {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.conn_method"))
|
||||
.show();
|
||||
show_selection = true;
|
||||
});
|
||||
if show_selection {
|
||||
ExternalConnection::check(None, ui.ctx());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !current {
|
||||
// Show button to select opened wallet.
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, None, || {
|
||||
self.wallet_content = Some(WalletContent::new(wallet.clone(), None));
|
||||
self.select_wallet(wallet, None, cb);
|
||||
});
|
||||
}
|
||||
// Show button to close opened wallet.
|
||||
@@ -660,14 +606,23 @@ impl WalletsContent {
|
||||
}
|
||||
|
||||
/// Show [`Modal`] to select and open wallet.
|
||||
fn show_opening_modal(&mut self, wallet: Wallet, data: Option<String>) {
|
||||
self.wallet_content = Some(WalletContent::new(wallet.clone(), None));
|
||||
self.open_wallet_content = OpenWalletModal::new(data);
|
||||
fn show_opening_modal(&mut self, wallet: &Wallet, data: Option<String>, cb: &dyn PlatformCallbacks) {
|
||||
self.select_wallet(wallet, data, cb);
|
||||
self.open_wallet_content = OpenWalletModal::new();
|
||||
Modal::new(OPEN_WALLET_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.open"))
|
||||
.show();
|
||||
}
|
||||
|
||||
/// Select wallet to make some actions on it.
|
||||
fn select_wallet(&mut self, wallet: &Wallet, data: Option<String>, cb: &dyn PlatformCallbacks) {
|
||||
self.wallet_content.account_content.close_qr_scan(cb);
|
||||
if let Some(data) = data {
|
||||
wallet.open_slatepack(data);
|
||||
}
|
||||
self.wallets.select(Some(wallet.get_config().id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if it's possible to show [`WalletsContent`] and [`WalletContent`] panels at same time.
|
||||
|
||||
@@ -19,7 +19,7 @@ use grin_util::ZeroingString;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK, CLIPBOARD_TEXT, COPY, SCAN};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Content, View, CameraScanModal};
|
||||
use crate::gui::views::{Modal, Content, View, CameraScanContent};
|
||||
use crate::gui::views::types::{LinePosition, ContentContainer, ModalPosition, QrScanResult};
|
||||
use crate::gui::views::wallets::creation::MnemonicSetup;
|
||||
use crate::gui::views::wallets::creation::types::Step;
|
||||
@@ -39,7 +39,7 @@ pub struct WalletCreationContent {
|
||||
step: Step,
|
||||
|
||||
/// QR code scanning [`Modal`] content.
|
||||
scan_modal_content: Option<CameraScanModal>,
|
||||
scan_modal_content: Option<CameraScanContent>,
|
||||
|
||||
/// Mnemonic phrase setup content.
|
||||
mnemonic_setup: MnemonicSetup,
|
||||
@@ -66,7 +66,7 @@ impl ContentContainer for WalletCreationContent {
|
||||
match modal.id {
|
||||
QR_CODE_PHRASE_SCAN_MODAL => {
|
||||
if let Some(content) = self.scan_modal_content.as_mut() {
|
||||
content.ui(ui, cb, |result| {
|
||||
content.modal_ui(ui, cb, |result| {
|
||||
match result {
|
||||
QrScanResult::Text(text) => {
|
||||
self.mnemonic_setup.mnemonic.import(&text);
|
||||
@@ -118,7 +118,7 @@ impl WalletCreationContent {
|
||||
top: View::TAB_ITEMS_PADDING as i8,
|
||||
bottom: (View::get_bottom_inset() + View::TAB_ITEMS_PADDING) as i8,
|
||||
},
|
||||
fill: Colors::fill_deep(),
|
||||
fill: Colors::fill(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
@@ -146,6 +146,7 @@ impl WalletCreationContent {
|
||||
top: 3.0 as i8,
|
||||
bottom: 4.0 as i8,
|
||||
},
|
||||
fill: Colors::fill_lite(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
@@ -260,7 +261,7 @@ impl WalletCreationContent {
|
||||
SCAN,
|
||||
t!("scan").to_uppercase());
|
||||
View::button(ui, scan_text, Colors::white_or_black(false), || {
|
||||
self.scan_modal_content = Some(CameraScanModal::default());
|
||||
self.scan_modal_content = Some(CameraScanContent::default());
|
||||
// Show QR code scan modal.
|
||||
Modal::new(QR_CODE_PHRASE_SCAN_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
|
||||
@@ -25,18 +25,14 @@ pub struct OpenWalletModal {
|
||||
pass_edit: String,
|
||||
/// Flag to check if wrong password was entered.
|
||||
wrong_pass: bool,
|
||||
|
||||
/// Optional data to pass after wallet opening.
|
||||
data: Option<String>,
|
||||
}
|
||||
|
||||
impl OpenWalletModal {
|
||||
/// Create new content instance.
|
||||
pub fn new(data: Option<String>) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pass_edit: "".to_string(),
|
||||
wrong_pass: false,
|
||||
data,
|
||||
}
|
||||
}
|
||||
/// Draw [`Modal`] content.
|
||||
@@ -44,14 +40,14 @@ impl OpenWalletModal {
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
mut on_continue: impl FnMut(ZeroingString, &Option<String>) -> bool) {
|
||||
mut on_continue: impl FnMut(ZeroingString) -> bool) {
|
||||
// Callback for button to continue.
|
||||
let mut on_continue = |m: &mut OpenWalletModal| {
|
||||
let pass = m.pass_edit.clone();
|
||||
if pass.is_empty() {
|
||||
return;
|
||||
}
|
||||
m.wrong_pass = !on_continue(ZeroingString::from(pass), &m.data);
|
||||
m.wrong_pass = !on_continue(ZeroingString::from(pass));
|
||||
if !m.wrong_pass {
|
||||
m.pass_edit = "".to_string();
|
||||
Modal::close();
|
||||
|
||||
@@ -7,36 +7,35 @@
|
||||
// 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,
|
||||
// distributed under the License is distr1ibuted 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 eframe::emath::Align;
|
||||
use eframe::epaint::StrokeKind;
|
||||
use egui::{Layout, RichText};
|
||||
use egui::{Align, Layout, RichText, StrokeKind};
|
||||
use grin_core::core::amount_to_hr_string;
|
||||
|
||||
use crate::gui::icons::{FOLDER_USER, PACKAGE, SCAN, SPINNER, USERS_THREE};
|
||||
use crate::gui::icons::{FOLDER_USER, PACKAGE, SCAN, SPINNER, USERS_THREE, USER_PLUS};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::{ModalPosition, QrScanResult};
|
||||
use crate::gui::views::wallets::wallet::account::create::CreateAccountContent;
|
||||
use crate::gui::views::wallets::wallet::account::list::WalletAccountsContent;
|
||||
use crate::gui::views::wallets::wallet::types::{WalletContentContainer, GRIN};
|
||||
use crate::gui::views::{CameraContent, Content, Modal, View};
|
||||
use crate::gui::views::{CameraContent, CameraScanContent, Content, Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::wallet::account::create::CreateAccountContent;
|
||||
use crate::wallet::{Wallet, WalletConfig};
|
||||
|
||||
/// Wallet account panel content.
|
||||
pub struct AccountContent {
|
||||
/// Account list content.
|
||||
accounts_content: Option<WalletAccountsContent>,
|
||||
|
||||
pub list_content: Option<WalletAccountsContent>,
|
||||
/// Account creation [`Modal`] content.
|
||||
create_account_content: CreateAccountContent,
|
||||
|
||||
/// QR code scan content.
|
||||
qr_scan_content: Option<CameraContent>,
|
||||
/// QR code scan result
|
||||
qr_scan_result: Option<QrScanResult>,
|
||||
}
|
||||
|
||||
/// Account creation [`Modal`] identifier.
|
||||
@@ -49,7 +48,11 @@ impl WalletContentContainer for AccountContent {
|
||||
]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
CREATE_MODAL_ID => self.create_account_content.ui(ui, wallet, modal, cb),
|
||||
_ => {}
|
||||
@@ -57,13 +60,14 @@ impl WalletContentContainer for AccountContent {
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
if self.qr_scan_content.is_some() {
|
||||
if self.qr_scan_showing() {
|
||||
self.qr_scan_ui(ui, wallet, cb);
|
||||
} else {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
if self.accounts_content.is_some() {
|
||||
if self.list_content.is_some() {
|
||||
self.list_ui(ui, wallet);
|
||||
} else {
|
||||
// Show account content.
|
||||
self.account_ui(ui, wallet, cb);
|
||||
}
|
||||
});
|
||||
@@ -74,14 +78,44 @@ impl WalletContentContainer for AccountContent {
|
||||
impl Default for AccountContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
accounts_content: None,
|
||||
list_content: None,
|
||||
create_account_content: CreateAccountContent::default(),
|
||||
qr_scan_content: None,
|
||||
qr_scan_result: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountContent {
|
||||
/// Check if QR code scanner was opened.
|
||||
pub fn qr_scan_showing(&self) -> bool {
|
||||
self.qr_scan_content.is_some() || self.qr_scan_result.is_some()
|
||||
}
|
||||
|
||||
/// Close QR code scanner.
|
||||
pub fn close_qr_scan(&mut self, cb: &dyn PlatformCallbacks) {
|
||||
if !self.qr_scan_showing() {
|
||||
return;
|
||||
}
|
||||
cb.stop_camera();
|
||||
self.qr_scan_content = None;
|
||||
self.qr_scan_result = None;
|
||||
}
|
||||
|
||||
/// Check if it's possible to go back at navigation stack.
|
||||
pub fn can_back(&self) -> bool {
|
||||
self.qr_scan_showing() || self.list_content.is_some()
|
||||
}
|
||||
|
||||
/// Navigate back on navigation stack.
|
||||
pub fn back(&mut self, cb: &dyn PlatformCallbacks) {
|
||||
if self.qr_scan_showing() {
|
||||
self.close_qr_scan(cb);
|
||||
} else if self.list_content.is_some() {
|
||||
self.list_content = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw wallet account content.
|
||||
fn account_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
@@ -91,17 +125,19 @@ impl AccountContent {
|
||||
if wallet.get_data().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = wallet.get_data().unwrap();
|
||||
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(75.0);
|
||||
|
||||
// Draw round background.
|
||||
let rounding = View::item_rounding(0, 2, false);
|
||||
ui.painter().rect(rect,
|
||||
rounding,
|
||||
Colors::fill_lite(),
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Middle);
|
||||
StrokeKind::Outside);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to show QR code scanner.
|
||||
@@ -111,8 +147,13 @@ impl AccountContent {
|
||||
});
|
||||
|
||||
// Draw button to show list of accounts.
|
||||
View::item_button(ui, View::item_rounding(1, 3, true), USERS_THREE, None, || {
|
||||
let accounts = wallet.accounts();
|
||||
let accounts = wallet.accounts();
|
||||
let accounts_icon = if accounts.len() > 1 {
|
||||
USERS_THREE
|
||||
} else {
|
||||
USER_PLUS
|
||||
};
|
||||
View::item_button(ui, View::item_rounding(1, 3, true), accounts_icon, None, || {
|
||||
if accounts.len() == 1 {
|
||||
self.create_account_content = CreateAccountContent::default();
|
||||
Modal::new(CREATE_MODAL_ID)
|
||||
@@ -120,7 +161,7 @@ impl AccountContent {
|
||||
.title(t!("wallets.accounts"))
|
||||
.show();
|
||||
} else {
|
||||
self.accounts_content = Some(
|
||||
self.list_content = Some(
|
||||
WalletAccountsContent::new(accounts, wallet.get_config().account)
|
||||
);
|
||||
}
|
||||
@@ -154,7 +195,9 @@ impl AccountContent {
|
||||
View::ellipsize_text(ui, acc_text, 15.0, Colors::text(false));
|
||||
|
||||
// Show confirmed height or sync progress.
|
||||
let status_text = if !wallet.syncing() {
|
||||
let status_text = if wallet.message_opening() {
|
||||
format!("{} {}", SPINNER, t!("wallets.loading"))
|
||||
} else if !wallet.syncing() {
|
||||
format!("{} {}", PACKAGE, data.info.last_confirmed_height)
|
||||
} else {
|
||||
let info_progress = wallet.info_sync_progress();
|
||||
@@ -183,7 +226,7 @@ impl AccountContent {
|
||||
status_text,
|
||||
15.0,
|
||||
Colors::gray(),
|
||||
wallet.syncing());
|
||||
wallet.syncing() || wallet.message_opening());
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -191,14 +234,14 @@ impl AccountContent {
|
||||
|
||||
/// Draw account list content.
|
||||
fn list_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
if let Some(accounts) = self.accounts_content.as_mut() {
|
||||
if let Some(accounts) = self.list_content.as_mut() {
|
||||
let mut selected = false;
|
||||
accounts.ui(ui, |acc| {
|
||||
let _ = wallet.set_active_account(&acc.label);
|
||||
selected = true;
|
||||
});
|
||||
if selected {
|
||||
self.accounts_content = None;
|
||||
self.list_content = None;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -216,12 +259,12 @@ impl AccountContent {
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
self.accounts_content = None;
|
||||
self.list_content = None;
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.add"), Colors::white_or_black(false), || {
|
||||
self.accounts_content = None;
|
||||
self.list_content = None;
|
||||
self.create_account_content = CreateAccountContent::default();
|
||||
Modal::new(CREATE_MODAL_ID)
|
||||
.position(ModalPosition::CenterTop)
|
||||
@@ -234,53 +277,42 @@ impl AccountContent {
|
||||
}
|
||||
|
||||
/// Draw QR code scanner content.
|
||||
fn qr_scan_ui(&mut self, ui: &mut egui::Ui, _: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
if let Some(_) = self.qr_scan_content.as_ref().unwrap().qr_scan_result() {
|
||||
// match result {
|
||||
// QrScanResult::Address(address) => {
|
||||
// self.current_tab =
|
||||
// Box::new(WalletTransport::new(Some(address.to_string())));
|
||||
// }
|
||||
// _ => {
|
||||
// self.current_tab =
|
||||
// Box::new(WalletMessages::new(Some(result.text())))
|
||||
// }
|
||||
// }
|
||||
// Stop camera and close scanning.
|
||||
cb.stop_camera();
|
||||
self.qr_scan_content = None;
|
||||
} else {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH, |ui| {
|
||||
self.qr_scan_content.as_mut().unwrap().ui(ui, cb);
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
cb.stop_camera();
|
||||
self.qr_scan_content = None;
|
||||
fn qr_scan_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH, |ui| {
|
||||
if self.qr_scan_content.is_some() {
|
||||
if let Some(result) = self.qr_scan_content.as_ref().unwrap().qr_scan_result() {
|
||||
cb.stop_camera();
|
||||
self.qr_scan_content = None;
|
||||
match result {
|
||||
QrScanResult::Address(_) => {
|
||||
//TODO: send with address
|
||||
}
|
||||
QrScanResult::Slatepack(m) => {
|
||||
wallet.open_slatepack(m.to_string());
|
||||
}
|
||||
_ => {
|
||||
self.qr_scan_result = Some(result);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Draw QR code scan content.
|
||||
self.qr_scan_content.as_mut().unwrap().ui(ui, cb);
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
self.close_qr_scan(cb);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if let Some(res) = &self.qr_scan_result.clone() {
|
||||
CameraScanContent::result_ui(ui, res, cb, || {
|
||||
self.qr_scan_result = None;
|
||||
}, || {
|
||||
self.qr_scan_content = Some(CameraContent::default());
|
||||
cb.start_camera();
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if account list is showing.
|
||||
pub fn account_list_showing(&self) -> bool {
|
||||
self.accounts_content.is_some()
|
||||
}
|
||||
|
||||
/// Close account list.
|
||||
pub fn close_account_list(&mut self) {
|
||||
self.accounts_content = None;
|
||||
}
|
||||
|
||||
/// Check if QR code scanner is showing.
|
||||
pub fn qr_scan_showing(&self) -> bool {
|
||||
self.qr_scan_content.is_some()
|
||||
}
|
||||
|
||||
/// Close QR code scanner.
|
||||
pub fn close_qr_scan(&mut self) {
|
||||
self.qr_scan_content = None;
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -45,14 +45,10 @@ impl WalletAccountsContent {
|
||||
ScrollArea::vertical()
|
||||
.id_salt("account_list_scroll")
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.max_height(266.0)
|
||||
.max_height(411.0)
|
||||
.auto_shrink([true; 2])
|
||||
.show_rows(ui, ACCOUNT_ITEM_HEIGHT, size, |ui, row_range| {
|
||||
for index in row_range {
|
||||
// Add space before the first item.
|
||||
if index == 0 {
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
let acc = self.accounts.get(index).unwrap().clone();
|
||||
self.account_item_ui(ui, &acc, index, size, || {
|
||||
on_select(acc.clone());
|
||||
@@ -100,15 +96,18 @@ impl WalletAccountsContent {
|
||||
|
||||
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.add_space(8.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
ui.add_space(3.0);
|
||||
// Show spendable amount.
|
||||
let amount = amount_to_hr_string(acc.spendable_amount, true);
|
||||
let amount_text = format!("{} {}", amount, GRIN);
|
||||
ui.label(RichText::new(amount_text)
|
||||
.size(18.0)
|
||||
.color(Colors::white_or_black(true)));
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
ui.label(RichText::new(amount_text)
|
||||
.size(18.0)
|
||||
.color(Colors::white_or_black(true)));
|
||||
});
|
||||
ui.add_space(-2.0);
|
||||
|
||||
// Show account name.
|
||||
|
||||
@@ -16,95 +16,101 @@ use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Id, Margin, RichText, ScrollArea};
|
||||
use grin_chain::SyncStatus;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::gui::icons::{ARROWS_CLOCKWISE, BRIDGE, CHAT_CIRCLE_TEXT, GEAR_FINE, GRAPH, POWER};
|
||||
use grin_wallet_libwallet::Error;
|
||||
use crate::gui::icons::{ARROWS_CLOCKWISE, FILE_ARROW_DOWN, FILE_ARROW_UP, GEAR_FINE, POWER, STACK};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::{ContentContainer, LinePosition};
|
||||
use crate::gui::views::types::{LinePosition, ModalPosition};
|
||||
use crate::gui::views::wallets::types::{WalletTab, WalletTabType};
|
||||
use crate::gui::views::wallets::wallet::account::AccountContent;
|
||||
use crate::gui::views::wallets::wallet::request::{InvoiceRequestContent, SendRequestContent};
|
||||
use crate::gui::views::wallets::wallet::transport::WalletTransportContent;
|
||||
use crate::gui::views::wallets::wallet::types::WalletContentContainer;
|
||||
use crate::gui::views::wallets::wallet::WalletSettings;
|
||||
use crate::gui::views::wallets::{WalletMessages, WalletTransactions, WalletTransport};
|
||||
use crate::gui::views::{Content, Modal, View};
|
||||
use crate::gui::views::wallets::WalletTransactions;
|
||||
use crate::gui::views::{Content, FilePickContent, FilePickContentType, Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::node::Node;
|
||||
use crate::wallet::types::ConnectionMethod;
|
||||
use crate::wallet::types::{ConnectionMethod, WalletTransaction};
|
||||
use crate::wallet::{ExternalConnection, Wallet};
|
||||
use crate::AppConfig;
|
||||
use crate::gui::views::wallets::wallet::types::WalletContentContainer;
|
||||
|
||||
/// Wallet content.
|
||||
pub struct WalletContent {
|
||||
/// Selected and opened wallet.
|
||||
pub wallet: Wallet,
|
||||
/// Current tab content to show.
|
||||
pub current_tab: Box<dyn WalletTab>,
|
||||
current_tab: Box<dyn WalletTab>,
|
||||
|
||||
/// Account panel content.
|
||||
account_content: AccountContent,
|
||||
pub account_content: AccountContent,
|
||||
/// Transport panel content.
|
||||
pub transport_content: WalletTransportContent,
|
||||
|
||||
/// Invoice request creation [`Modal`] content.
|
||||
invoice_request_content: Option<InvoiceRequestContent>,
|
||||
/// Send request creation [`Modal`] content.
|
||||
send_request_content: Option<SendRequestContent>,
|
||||
|
||||
/// Tab button to pick file for parsing.
|
||||
file_pick_tab_button: FilePickContent,
|
||||
}
|
||||
|
||||
impl ContentContainer for WalletContent {
|
||||
/// Identifier for invoice creation [`Modal`].
|
||||
const INVOICE_MODAL_ID: &'static str = "invoice_request_modal";
|
||||
/// Identifier for sending request creation [`Modal`].
|
||||
const SEND_MODAL_ID: &'static str = "send_request_modal";
|
||||
|
||||
impl WalletContentContainer for WalletContent {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![]
|
||||
vec![
|
||||
INVOICE_MODAL_ID,
|
||||
SEND_MODAL_ID
|
||||
]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self, _: &mut egui::Ui, _: &Modal, _: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, w: &Wallet, m: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match m.id {
|
||||
INVOICE_MODAL_ID => {
|
||||
if let Some(c) = self.invoice_request_content.as_mut() {
|
||||
c.modal_ui(ui, w, m, cb);
|
||||
}
|
||||
}
|
||||
SEND_MODAL_ID => {
|
||||
if let Some(c) = self.send_request_content.as_mut() {
|
||||
c.modal_ui(ui, w, m, cb);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
if self.account_content.can_back() {
|
||||
ui.ctx().request_repaint();
|
||||
} else {
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
||||
}
|
||||
|
||||
let dual_panel = Content::is_dual_panel_mode(ui.ctx());
|
||||
let show_wallets_dual = AppConfig::show_wallets_at_dual_panel();
|
||||
|
||||
let wallet = &self.wallet;
|
||||
let wallet_id = wallet.identifier();
|
||||
let data = wallet.get_data();
|
||||
let show_qr_scan = self.account_content.qr_scan_showing();
|
||||
let block_nav = Self::block_navigation_on_sync(wallet);
|
||||
let block_nav = self.block_navigation_on_sync(wallet);
|
||||
|
||||
// Show wallet account panel not on settings tab when navigation is not blocked and QR code
|
||||
// scanner is not showing and wallet data is not empty.
|
||||
let mut show_account = self.current_tab.get_type() != WalletTabType::Settings && !block_nav
|
||||
&& !wallet.sync_error() && data.is_some();
|
||||
if wallet.get_current_connection() == ConnectionMethod::Integrated && !Node::is_running() {
|
||||
if wallet.get_current_connection() == ConnectionMethod::Integrated &&
|
||||
!Node::is_running() {
|
||||
show_account = false;
|
||||
}
|
||||
// Close scanner when balance got hidden.
|
||||
if !show_account && show_qr_scan {
|
||||
cb.stop_camera();
|
||||
self.account_content.close_qr_scan();
|
||||
|
||||
// Consume inserted message.
|
||||
if let Some(res) = wallet.consume_message_result() {
|
||||
self.on_transaction(res);
|
||||
}
|
||||
egui::TopBottomPanel::top(Id::from("wallet_account").with(wallet.identifier()))
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Margin {
|
||||
left: (View::far_left_inset_margin(ui) + 4.0) as i8,
|
||||
right: (View::get_right_inset() + 4.0) as i8,
|
||||
top: 4.0 as i8,
|
||||
bottom: 0.0 as i8,
|
||||
},
|
||||
fill: Colors::fill(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, show_account, |ui| {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
self.account_content.ui(ui, wallet, cb);
|
||||
// Draw content divider lines.
|
||||
let r = {
|
||||
let mut r = rect.clone();
|
||||
r.min.x -= 4.0 + View::far_left_inset_margin(ui);
|
||||
r.min.y -= 4.0;
|
||||
r.max.x += 4.0 + View::get_right_inset();
|
||||
r
|
||||
};
|
||||
View::line(ui, LinePosition::BOTTOM, &r, Colors::item_stroke());
|
||||
if dual_panel && show_wallets_dual && !show_qr_scan {
|
||||
View::line(ui, LinePosition::LEFT, &r, Colors::item_stroke());
|
||||
}
|
||||
});
|
||||
|
||||
// Show wallet tabs.
|
||||
let show_tabs = !block_nav && !self.qr_scan_showing() && !self.account_list_showing();
|
||||
egui::TopBottomPanel::bottom("wallet_tabs")
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Margin {
|
||||
@@ -116,55 +122,146 @@ impl ContentContainer for WalletContent {
|
||||
fill: Colors::fill(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, show_tabs, |ui| {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
.show_animated_inside(ui, !block_nav, |ui| {
|
||||
let r = ui.available_rect_before_wrap();
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.tabs_ui(ui);
|
||||
self.tabs_ui(wallet, ui, cb);
|
||||
});
|
||||
let rect = {
|
||||
let mut r = rect.clone();
|
||||
let mut r = r.clone();
|
||||
r.min.x -= View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING;
|
||||
r.min.y -= View::TAB_ITEMS_PADDING;
|
||||
r.max.x += View::get_right_inset() + View::TAB_ITEMS_PADDING;
|
||||
r.max.y += View::get_bottom_inset() + View::TAB_ITEMS_PADDING;
|
||||
r
|
||||
};
|
||||
// Draw content divider line.
|
||||
View::line(ui, LinePosition::TOP, &rect, Colors::stroke());
|
||||
// Draw cover for content below opened panel.
|
||||
if self.can_back() && show_account {
|
||||
View::content_cover_ui(ui, rect, "wallet_tabs_content_cover", || {
|
||||
self.back(cb);
|
||||
});
|
||||
} else {
|
||||
// Draw content divider line.
|
||||
View::line(ui, LinePosition::TOP, &rect, Colors::stroke());
|
||||
}
|
||||
});
|
||||
|
||||
// Close scanner when account panel got hidden.
|
||||
if !show_account && self.account_content.qr_scan_showing() {
|
||||
self.account_content.close_qr_scan(cb);
|
||||
}
|
||||
|
||||
// Flag to check if account panel is opened.
|
||||
let top_panel_expanded = self.account_content.can_back() ||
|
||||
self.transport_content.can_back();
|
||||
|
||||
// Show wallet account content.
|
||||
if !self.transport_content.can_back() && show_account {
|
||||
egui::TopBottomPanel::top(Id::from("wallet_account").with(wallet.identifier()))
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Margin {
|
||||
left: (View::far_left_inset_margin(ui) + 4.0) as i8,
|
||||
right: (View::get_right_inset() + 4.0) as i8,
|
||||
top: 4.0 as i8,
|
||||
bottom: 0.0 as i8,
|
||||
},
|
||||
fill: if top_panel_expanded {
|
||||
Colors::fill_lite()
|
||||
} else {
|
||||
Colors::TRANSPARENT
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
self.account_content.ui(ui, &wallet, cb);
|
||||
// Draw content divider lines.
|
||||
let r = {
|
||||
let mut r = rect.clone();
|
||||
r.min.x -= 4.0 + View::far_left_inset_margin(ui);
|
||||
r.min.y -= 4.0;
|
||||
r.max.x += 4.0 + View::get_right_inset();
|
||||
r
|
||||
};
|
||||
if dual_panel && show_wallets_dual {
|
||||
View::line(ui, LinePosition::LEFT, &r, Colors::item_stroke());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show wallet transport content.
|
||||
if !self.account_content.can_back() && show_account {
|
||||
egui::TopBottomPanel::top(Id::from("wallet_transport").with(wallet.identifier()))
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Margin {
|
||||
left: (View::far_left_inset_margin(ui) + 4.0) as i8,
|
||||
right: (View::get_right_inset() + 4.0) as i8,
|
||||
top: 1.0 as i8,
|
||||
bottom: 1.0 as i8,
|
||||
},
|
||||
fill: if top_panel_expanded {
|
||||
Colors::fill_lite()
|
||||
} else {
|
||||
Colors::TRANSPARENT
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.transport_content.ui(ui, &wallet, cb);
|
||||
});
|
||||
// Draw content divider lines.
|
||||
let r = {
|
||||
let mut r = rect.clone();
|
||||
r.min.x -= 4.0 + View::far_left_inset_margin(ui);
|
||||
r.min.y -= 1.0;
|
||||
r.max.x += 4.0 + View::get_right_inset();
|
||||
r
|
||||
};
|
||||
if dual_panel && show_wallets_dual {
|
||||
View::line(ui, LinePosition::LEFT, &r, Colors::item_stroke());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show tab content.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Margin {
|
||||
inner_margin: Margin {
|
||||
left: (View::far_left_inset_margin(ui) + 4.0) as i8,
|
||||
right: (View::get_right_inset() + 4.0) as i8,
|
||||
top: 0.0 as i8,
|
||||
bottom: 4.0 as i8,
|
||||
},
|
||||
fill: if self.current_tab.get_type() == WalletTabType::Settings {
|
||||
Colors::fill_lite()
|
||||
} else {
|
||||
Colors::TRANSPARENT
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
let tab_type = self.current_tab.get_type();
|
||||
let show_sync = (tab_type != WalletTabType::Settings || block_nav) &&
|
||||
sync_ui(ui, &self.wallet);
|
||||
sync_ui(ui, &wallet);
|
||||
if !show_sync {
|
||||
if tab_type != WalletTabType::Txs {
|
||||
ui.add_space(3.0);
|
||||
ScrollArea::vertical()
|
||||
.id_salt(Id::from("wallet_scroll")
|
||||
.id_salt(Id::from("wallet_tab_content_scroll")
|
||||
.with(tab_type.name())
|
||||
.with(wallet_id))
|
||||
.auto_shrink([false; 2])
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.show(ui, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.current_tab.ui(ui, &self.wallet, cb);
|
||||
self.current_tab.ui(ui, &wallet, cb);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
self.current_tab.ui(ui, &self.wallet, cb);
|
||||
self.current_tab.ui(ui, &wallet, cb);
|
||||
}
|
||||
}
|
||||
let rect = {
|
||||
@@ -174,12 +271,10 @@ impl ContentContainer for WalletContent {
|
||||
r.max.y += 4.0;
|
||||
r
|
||||
};
|
||||
// Draw cover when account list or QR code scanner is active.
|
||||
if !show_sync && (self.account_list_showing() || self.qr_scan_showing()) {
|
||||
View::content_cover_ui(ui, rect, "wallet_content_cover", || {
|
||||
cb.stop_camera();
|
||||
self.account_content.close_qr_scan();
|
||||
self.account_content.close_account_list();
|
||||
// Draw cover for content below opened panel.
|
||||
if !show_sync && self.can_back() {
|
||||
View::content_cover_ui(ui, rect, "wallet_panel_content_cover", || {
|
||||
self.back(cb);
|
||||
});
|
||||
}
|
||||
// Draw content divider line.
|
||||
@@ -190,48 +285,58 @@ impl ContentContainer for WalletContent {
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletContent {
|
||||
/// Create new instance with optional data.
|
||||
pub fn new(wallet: Wallet, data: Option<String>) -> Self {
|
||||
let account_content = AccountContent::default();
|
||||
let mut content = Self {
|
||||
wallet,
|
||||
current_tab: Box::new(WalletTransactions::default()),
|
||||
account_content,
|
||||
};
|
||||
if data.is_some() {
|
||||
content.on_data(data);
|
||||
impl Default for WalletContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
current_tab: Box::new(WalletTransactions::new(None)),
|
||||
account_content: AccountContent::default(),
|
||||
transport_content: WalletTransportContent::default(),
|
||||
invoice_request_content: None,
|
||||
send_request_content: None,
|
||||
file_pick_tab_button: FilePickContent::new(FilePickContentType::Tab),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletContent {
|
||||
/// Get title based on current navigation state.
|
||||
pub fn title(&self) -> String {
|
||||
if self.account_content.qr_scan_showing() {
|
||||
t!("scan_qr")
|
||||
} else if self.account_content.list_content.is_some() {
|
||||
t!("wallets.accounts")
|
||||
} else if self.transport_content.settings_content.is_some() {
|
||||
t!("wallets.transport")
|
||||
} else if self.transport_content.qr_address_content.is_some() {
|
||||
t!("network_mining.address")
|
||||
} else {
|
||||
self.current_tab.get_type().name()
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
/// Check account list is showing.
|
||||
pub fn account_list_showing(&self) -> bool {
|
||||
self.account_content.account_list_showing()
|
||||
/// Callback on incoming transaction for user to take action.
|
||||
fn on_transaction(&mut self, tx_result: Result<WalletTransaction, Error>) {
|
||||
if let Ok(tx) = tx_result {
|
||||
self.current_tab = Box::new(WalletTransactions::new(Some(tx)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Close QR code scanner.
|
||||
pub fn close_account_list(&mut self) {
|
||||
self.account_content.close_account_list();
|
||||
/// Check if it's possible to go back at navigation stack.
|
||||
pub fn can_back(&self) -> bool {
|
||||
self.account_content.can_back() || self.transport_content.can_back()
|
||||
}
|
||||
|
||||
/// Check if QR code scanner is opened.
|
||||
pub fn qr_scan_showing(&self) -> bool {
|
||||
self.account_content.qr_scan_showing()
|
||||
}
|
||||
|
||||
/// Close QR code scanner.
|
||||
pub fn close_qr_scan(&mut self) {
|
||||
self.account_content.close_qr_scan();
|
||||
}
|
||||
|
||||
/// Handle data from deeplink or opened file.
|
||||
pub fn on_data(&mut self, data: Option<String>) {
|
||||
self.current_tab = Box::new(WalletMessages::new(data));
|
||||
/// Navigate back on navigation stack.
|
||||
pub fn back(&mut self, cb: &dyn PlatformCallbacks) {
|
||||
if self.account_content.can_back() {
|
||||
self.account_content.back(cb);
|
||||
} else if self.transport_content.can_back() {
|
||||
self.transport_content.back();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check when to block tabs navigation on sync progress.
|
||||
pub fn block_navigation_on_sync(wallet: &Wallet) -> bool {
|
||||
fn block_navigation_on_sync(&self, wallet: &Wallet) -> bool {
|
||||
let sync_error = wallet.sync_error();
|
||||
let integrated_node = wallet.get_current_connection() == ConnectionMethod::Integrated;
|
||||
let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
|
||||
@@ -244,7 +349,7 @@ impl WalletContent {
|
||||
}
|
||||
|
||||
/// Draw tab buttons at the bottom of the screen.
|
||||
fn tabs_ui(&mut self, ui: &mut egui::Ui) {
|
||||
fn tabs_ui(&mut self, wallet: &Wallet, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between tabs.
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0);
|
||||
@@ -252,28 +357,60 @@ impl WalletContent {
|
||||
// Setup vertical padding inside buttons.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 4.0);
|
||||
|
||||
let has_wallet_data = wallet.get_data().is_some();
|
||||
let can_send = has_wallet_data &&
|
||||
wallet.get_data().unwrap().info.amount_currently_spendable > 0;
|
||||
|
||||
let current_type = self.current_tab.get_type();
|
||||
ui.columns(4, |columns| {
|
||||
let tabs_amount = if can_send { 5 } else { 4 };
|
||||
ui.columns(tabs_amount, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, GRAPH, current_type == WalletTabType::Txs, |_| {
|
||||
self.current_tab = Box::new(WalletTransactions::default());
|
||||
View::tab_button(ui, STACK, None, Some(current_type == WalletTabType::Txs), |_| {
|
||||
self.current_tab = Box::new(WalletTransactions::new(None));
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
let is_messages = current_type == WalletTabType::Messages;
|
||||
View::tab_button(ui, CHAT_CIRCLE_TEXT, is_messages, |_| {
|
||||
self.current_tab = Box::new(
|
||||
WalletMessages::new(None)
|
||||
);
|
||||
let active = if has_wallet_data {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
View::tab_button(ui, FILE_ARROW_DOWN, Some(Colors::green()), active, |_| {
|
||||
self.invoice_request_content = Some(InvoiceRequestContent::default());
|
||||
Modal::new(INVOICE_MODAL_ID)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.receive"))
|
||||
.show();
|
||||
});
|
||||
});
|
||||
columns[2].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, BRIDGE, current_type == WalletTabType::Transport, |_| {
|
||||
self.current_tab = Box::new(WalletTransport::new(None));
|
||||
});
|
||||
if wallet.message_opening() {
|
||||
View::small_loading_spinner(ui);
|
||||
} else {
|
||||
let mut message = "".to_string();
|
||||
self.file_pick_tab_button.ui(ui, cb, |m| {
|
||||
message = m;
|
||||
});
|
||||
if !message.is_empty() {
|
||||
wallet.open_slatepack(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
columns[3].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, GEAR_FINE, current_type == WalletTabType::Settings, |ui| {
|
||||
if can_send {
|
||||
columns[3].vertical_centered_justified(|ui| {
|
||||
View::tab_button(ui, FILE_ARROW_UP, Some(Colors::red()), Some(false), |_| {
|
||||
self.send_request_content = Some(SendRequestContent::new(None));
|
||||
Modal::new(SEND_MODAL_ID)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.send"))
|
||||
.show();
|
||||
});
|
||||
});
|
||||
}
|
||||
let settings_index = if tabs_amount == 5 { 4 } else { 3 };
|
||||
columns[settings_index].vertical_centered_justified(|ui| {
|
||||
let active = Some(current_type == WalletTabType::Settings);
|
||||
View::tab_button(ui, GEAR_FINE, None, active, |ui| {
|
||||
ExternalConnection::check(None, ui.ctx());
|
||||
self.current_tab = Box::new(WalletSettings::default());
|
||||
});
|
||||
|
||||
@@ -23,11 +23,11 @@ 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, View, CameraScanModal};
|
||||
use crate::gui::views::{FilePickContent, Modal, View, CameraScanContent, FilePickContentType};
|
||||
use crate::gui::views::types::{ModalPosition, QrScanResult};
|
||||
use crate::gui::views::wallets::wallet::messages::request::MessageRequestModal;
|
||||
use crate::gui::views::wallets::wallet::messages::request::RequestModalContent;
|
||||
use crate::gui::views::wallets::wallet::types::{SLATEPACK_MESSAGE_HINT, WalletTab, WalletTabType};
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionModal;
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionContent;
|
||||
use crate::wallet::types::WalletTransaction;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
@@ -37,10 +37,7 @@ pub struct WalletMessages {
|
||||
first_draw: bool,
|
||||
|
||||
/// Invoice or sending request creation [`Modal`] content.
|
||||
request_modal_content: MessageRequestModal,
|
||||
|
||||
/// Wallet transaction [`Modal`] content.
|
||||
tx_info_content: WalletTransactionModal,
|
||||
request_modal_content: RequestModalContent,
|
||||
|
||||
/// Slatepacks message input text.
|
||||
message_edit: String,
|
||||
@@ -52,10 +49,10 @@ pub struct WalletMessages {
|
||||
message_result: Arc<RwLock<Option<(Slate, Result<WalletTransaction, Error>)>>>,
|
||||
|
||||
/// QR code scanner [`Modal`] content.
|
||||
scan_modal_content: Option<CameraScanModal>,
|
||||
scan_modal_content: Option<CameraScanContent>,
|
||||
|
||||
/// Button to parse picked file content.
|
||||
file_pick_button: FilePickButton,
|
||||
file_pick_button: FilePickContent,
|
||||
}
|
||||
|
||||
/// Identifier for amount input [`Modal`] to create invoice or sending request.
|
||||
@@ -65,17 +62,6 @@ const TX_INFO_MODAL: &'static str = "messages_tx_info_modal";
|
||||
/// Identifier for [`Modal`] to scan Slatepack message from QR code.
|
||||
const SCAN_QR_MODAL: &'static str = "messages_scan_qr_modal";
|
||||
|
||||
impl WalletTab for WalletMessages {
|
||||
fn get_type(&self) -> WalletTabType {
|
||||
WalletTabType::Messages
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
self.modal_content_ui(ui, wallet, cb);
|
||||
self.messages_ui(ui, wallet, cb);
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletMessages {
|
||||
/// Create new content instance, put message into input if provided.
|
||||
pub fn new(message: Option<String>) -> Self {
|
||||
@@ -85,9 +71,8 @@ impl WalletMessages {
|
||||
message_loading: false,
|
||||
message_error: "".to_string(),
|
||||
message_result: Arc::new(Default::default()),
|
||||
tx_info_content: WalletTransactionModal::new(None, false),
|
||||
request_modal_content: MessageRequestModal::new(false),
|
||||
file_pick_button: FilePickButton::default(),
|
||||
request_modal_content: RequestModalContent::new(false),
|
||||
file_pick_button: FilePickContent::new(FilePickContentType::Button),
|
||||
scan_modal_content: None,
|
||||
}
|
||||
}
|
||||
@@ -133,16 +118,11 @@ impl WalletMessages {
|
||||
self.request_modal_content.ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
TX_INFO_MODAL => {
|
||||
Modal::ui(ui.ctx(), cb, |ui, modal, cb| {
|
||||
self.tx_info_content.ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
SCAN_QR_MODAL => {
|
||||
let mut result = None;
|
||||
if let Some(content) = self.scan_modal_content.as_mut() {
|
||||
Modal::ui(ui.ctx(), cb, |ui, _, cb| {
|
||||
content.ui(ui, cb, |res| {
|
||||
content.modal_ui(ui, cb, |res| {
|
||||
result = Some(res.clone());
|
||||
Modal::close();
|
||||
});
|
||||
@@ -213,7 +193,7 @@ impl WalletMessages {
|
||||
|
||||
/// Show [`Modal`] to create invoice or sending request.
|
||||
fn show_request_modal(&mut self, invoice: bool) {
|
||||
self.request_modal_content = MessageRequestModal::new(invoice);
|
||||
self.request_modal_content = RequestModalContent::new(invoice);
|
||||
let title = if invoice {
|
||||
t!("wallets.receive")
|
||||
} else {
|
||||
@@ -295,7 +275,7 @@ impl WalletMessages {
|
||||
Ok(tx) => {
|
||||
self.message_edit.clear();
|
||||
// Show transaction modal on success.
|
||||
self.tx_info_content = WalletTransactionModal::new(Some(tx.data.id), false);
|
||||
// self.tx_info_content = WalletTransactionContent::new(Some(tx.data.id), false);
|
||||
Modal::new(TX_INFO_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.tx"))
|
||||
@@ -318,8 +298,8 @@ impl WalletMessages {
|
||||
// Show tx modal or show default error message.
|
||||
if let Some(tx) = wallet.tx_by_slate(&slate).as_ref() {
|
||||
self.message_edit.clear();
|
||||
self.tx_info_content =
|
||||
WalletTransactionModal::new(Some(tx.data.id), false);
|
||||
// self.tx_info_content =
|
||||
// WalletTransactionContent::new(Some(tx.data.id), false);
|
||||
Modal::new(TX_INFO_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.tx"))
|
||||
@@ -352,7 +332,7 @@ impl WalletMessages {
|
||||
View::button(ui, scan_text, Colors::white_or_black(false), || {
|
||||
self.message_edit.clear();
|
||||
self.message_error.clear();
|
||||
self.scan_modal_content = Some(CameraScanModal::default());
|
||||
self.scan_modal_content = Some(CameraScanContent::default());
|
||||
// Show QR code scan modal.
|
||||
Modal::new(SCAN_QR_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
@@ -422,7 +402,7 @@ impl WalletMessages {
|
||||
if exists {
|
||||
if let Some(tx) = wallet.tx_by_slate(&slate).as_ref() {
|
||||
self.message_edit.clear();
|
||||
self.tx_info_content = WalletTransactionModal::new(Some(tx.data.id), false);
|
||||
// self.tx_info_content = WalletTransactionContent::new(Some(tx.data.id), false);
|
||||
Modal::new(TX_INFO_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.tx"))
|
||||
|
||||
@@ -22,12 +22,12 @@ use grin_wallet_libwallet::Error;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionModal;
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionContent;
|
||||
use crate::wallet::types::WalletTransaction;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Invoice or sending request creation [`Modal`] content.
|
||||
pub struct MessageRequestModal {
|
||||
pub struct RequestModalContent {
|
||||
/// Flag to check if invoice or sending request was opened.
|
||||
invoice: bool,
|
||||
|
||||
@@ -42,10 +42,10 @@ pub struct MessageRequestModal {
|
||||
request_error: Option<String>,
|
||||
|
||||
/// Request result transaction content.
|
||||
result_tx_content: Option<WalletTransactionModal>,
|
||||
result_tx_content: Option<WalletTransactionContent>,
|
||||
}
|
||||
|
||||
impl MessageRequestModal {
|
||||
impl RequestModalContent {
|
||||
/// Create new content instance.
|
||||
pub fn new(invoice: bool) -> Self {
|
||||
Self {
|
||||
@@ -71,7 +71,7 @@ impl MessageRequestModal {
|
||||
}
|
||||
|
||||
// Setup callback on continue.
|
||||
let on_continue = |m: &mut MessageRequestModal| {
|
||||
let on_continue = |m: &mut RequestModalContent| {
|
||||
if m.amount_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -230,7 +230,7 @@ impl MessageRequestModal {
|
||||
match result {
|
||||
Ok(tx) => {
|
||||
self.result_tx_content =
|
||||
Some(WalletTransactionModal::new(Some(tx.data.id), false));
|
||||
Some(WalletTransactionContent::new(tx, false));
|
||||
}
|
||||
Err(err) => {
|
||||
match err {
|
||||
|
||||
@@ -20,13 +20,9 @@ pub use settings::*;
|
||||
mod txs;
|
||||
pub use txs::*;
|
||||
|
||||
mod messages;
|
||||
pub use messages::WalletMessages;
|
||||
|
||||
mod transport;
|
||||
pub use transport::WalletTransport;
|
||||
|
||||
mod content;
|
||||
pub use content::WalletContent;
|
||||
|
||||
mod account;
|
||||
mod account;
|
||||
mod transport;
|
||||
mod request;
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright 2025 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::{Id, RichText};
|
||||
use grin_core::core::amount_from_hr_string;
|
||||
use grin_wallet_libwallet::Error;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionContent;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::WalletTransaction;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Invoice request creation content.
|
||||
pub struct InvoiceRequestContent {
|
||||
/// Amount to receive.
|
||||
amount_edit: String,
|
||||
|
||||
/// Flag to check if request is loading.
|
||||
request_loading: bool,
|
||||
/// Request result if there is no error.
|
||||
request_result: Arc<RwLock<Option<Result<WalletTransaction, Error>>>>,
|
||||
/// Flag to check if there is an error happened on request creation.
|
||||
request_error: Option<String>,
|
||||
|
||||
/// Request result transaction content.
|
||||
result_tx_content: Option<WalletTransactionContent>,
|
||||
}
|
||||
|
||||
impl Default for InvoiceRequestContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
amount_edit: "".to_string(),
|
||||
request_loading: false,
|
||||
request_result: Arc::new(RwLock::new(None)),
|
||||
request_error: None,
|
||||
result_tx_content: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InvoiceRequestContent {
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Draw transaction information on request result.
|
||||
if let Some(tx) = self.result_tx_content.as_mut() {
|
||||
tx.ui(ui, wallet, modal, cb);
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup callback on continue.
|
||||
let on_continue = |m: &mut InvoiceRequestContent| {
|
||||
if m.amount_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Ok(a) = amount_from_hr_string(m.amount_edit.as_str()) {
|
||||
modal.disable_closing();
|
||||
// Setup data for request.
|
||||
let wallet = wallet.clone();
|
||||
let result = m.request_result.clone();
|
||||
// Send request at another thread.
|
||||
m.request_loading = true;
|
||||
thread::spawn(move || {
|
||||
let res = wallet.issue_invoice(a);
|
||||
let mut w_result = result.write();
|
||||
*w_result = Some(res);
|
||||
});
|
||||
} else {
|
||||
m.request_error = Some(t!("wallets.invoice_slatepack_err"));
|
||||
}
|
||||
};
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw content on request loading.
|
||||
if self.request_loading {
|
||||
self.loading_request_ui(ui, modal);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw amount input content.
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.enter_amount_receive"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw request amount text input.
|
||||
let amount_edit_before = self.amount_edit.clone();
|
||||
let mut amount_edit = TextEdit::new(Id::from(modal.id).with(wallet.get_config().id))
|
||||
.h_center()
|
||||
.numeric();
|
||||
amount_edit.ui(ui, &mut self.amount_edit, cb);
|
||||
if amount_edit.enter_pressed {
|
||||
on_continue(self);
|
||||
}
|
||||
|
||||
// Check value if input was changed.
|
||||
if amount_edit_before != self.amount_edit {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
self.amount_edit = amount_edit_before;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
// Button to create Slatepack message request.
|
||||
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
|
||||
on_continue(self);
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw loading request content.
|
||||
fn loading_request_ui(&mut self, ui: &mut egui::Ui, modal: &Modal) {
|
||||
ui.add_space(34.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
View::big_loading_spinner(ui);
|
||||
});
|
||||
ui.add_space(50.0);
|
||||
|
||||
// Check if there is request result error.
|
||||
if self.request_error.is_some() {
|
||||
modal.enable_closing();
|
||||
self.request_loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update data on request result.
|
||||
let r_request = self.request_result.read();
|
||||
if r_request.is_some() {
|
||||
modal.enable_closing();
|
||||
let result = r_request.as_ref().unwrap();
|
||||
match result {
|
||||
Ok(tx) => {
|
||||
self.result_tx_content =
|
||||
Some(WalletTransactionContent::new(tx, false));
|
||||
}
|
||||
Err(_) => {
|
||||
self.request_error = Some(t!("wallets.invoice_slatepack_err"));
|
||||
self.request_loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2025 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 invoice;
|
||||
pub use invoice::*;
|
||||
|
||||
mod send;
|
||||
pub use send::*;
|
||||
+157
-139
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The Grim Developers
|
||||
// Copyright 2025 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.
|
||||
@@ -12,87 +12,129 @@
|
||||
// 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 std::sync::Arc;
|
||||
use std::thread;
|
||||
use egui::{Id, RichText};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionContent;
|
||||
use crate::gui::views::{CameraContent, Modal, TextEdit, View};
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionModal;
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::wallet::types::WalletTransaction;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Transport sending [`Modal`] content.
|
||||
pub struct TransportSendModal {
|
||||
/// 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.
|
||||
/// Content to create a request to send funds.
|
||||
pub struct SendRequestContent {
|
||||
/// Amount to send or receive.
|
||||
amount_edit: String,
|
||||
/// Entered address value.
|
||||
/// Receiver address.
|
||||
address_edit: String,
|
||||
/// Flag to check if entered address is incorrect.
|
||||
address_error: bool,
|
||||
|
||||
/// Flag to check if request is loading.
|
||||
request_loading: bool,
|
||||
/// Request result if there is no error.
|
||||
request_result: Arc<RwLock<Option<Result<WalletTransaction, Error>>>>,
|
||||
/// Flag to check if there is an error happened on request creation.
|
||||
request_error: Option<String>,
|
||||
|
||||
/// Address QR code scanner content.
|
||||
address_scan_content: Option<CameraContent>,
|
||||
|
||||
/// Transaction information content.
|
||||
tx_info_content: Option<WalletTransactionModal>,
|
||||
/// Request result transaction content.
|
||||
result_tx_content: Option<WalletTransactionContent>,
|
||||
}
|
||||
|
||||
impl TransportSendModal {
|
||||
/// Create new instance from provided address.
|
||||
impl SendRequestContent {
|
||||
/// Create new content instance with optional receiver address.
|
||||
pub fn new(addr: Option<String>) -> Self {
|
||||
Self {
|
||||
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,
|
||||
request_loading: false,
|
||||
request_result: Arc::new(RwLock::new(None)),
|
||||
request_error: None,
|
||||
address_scan_content: None,
|
||||
tx_info_content: None,
|
||||
result_tx_content: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
pub fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Draw transaction information on request result.
|
||||
if let Some(tx) = self.tx_info_content.as_mut() {
|
||||
if let Some(tx) = self.result_tx_content.as_mut() {
|
||||
tx.ui(ui, wallet, modal, cb);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw sending content, progress or an error.
|
||||
if self.sending {
|
||||
self.progress_ui(ui);
|
||||
} else if self.error {
|
||||
self.error_ui(ui, wallet, modal);
|
||||
} else {
|
||||
self.content_ui(ui, wallet, modal, cb);
|
||||
}
|
||||
}
|
||||
// Setup callback on continue.
|
||||
let on_continue = |m: &mut SendRequestContent| {
|
||||
if m.amount_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
// Check address to send over Tor if enabled.
|
||||
let addr_str = m.address_edit.as_str();
|
||||
if let Ok(addr) = SlatepackAddress::try_from(addr_str.trim()) {
|
||||
if let Ok(a) = amount_from_hr_string(m.amount_edit.as_str()) {
|
||||
Modal::change_position(ModalPosition::Center);
|
||||
modal.disable_closing();
|
||||
|
||||
let mut wallet = wallet.clone();
|
||||
let res = m.request_result.clone();
|
||||
|
||||
// Send request at another thread.
|
||||
m.request_loading = true;
|
||||
thread::spawn(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
let result = wallet.send_tor(a, &addr).await;
|
||||
let mut w_res = res.write();
|
||||
*w_res = Some(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if !addr_str.is_empty() {
|
||||
m.address_error = true;
|
||||
} else if let Ok(amount) = amount_from_hr_string(m.amount_edit.as_str()) {
|
||||
Modal::change_position(ModalPosition::Center);
|
||||
modal.disable_closing();
|
||||
|
||||
let wallet = wallet.clone();
|
||||
let result = m.request_result.clone();
|
||||
|
||||
// Send request at another thread.
|
||||
m.request_loading = true;
|
||||
thread::spawn(move || {
|
||||
let res = wallet.send(amount, None);
|
||||
let mut w_result = result.write();
|
||||
*w_result = Some(res);
|
||||
});
|
||||
} else {
|
||||
m.request_error = Some(t!("wallets.send_slatepack_err"));
|
||||
}
|
||||
};
|
||||
|
||||
// Draw content on request loading.
|
||||
if self.request_loading {
|
||||
self.loading_request_ui(ui, modal);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Draw content to send.
|
||||
fn content_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.address_scan_content.as_mut() {
|
||||
let on_stop = || {
|
||||
@@ -105,6 +147,7 @@ impl TransportSendModal {
|
||||
on_stop();
|
||||
self.address_scan_content = None;
|
||||
} else {
|
||||
ui.add_space(6.0);
|
||||
scanner.ui(ui, cb);
|
||||
ui.add_space(6.0);
|
||||
|
||||
@@ -115,9 +158,7 @@ impl TransportSendModal {
|
||||
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();
|
||||
self.close();
|
||||
});
|
||||
});
|
||||
cols[1].vertical_centered_justified(|ui| {
|
||||
@@ -201,7 +242,7 @@ impl TransportSendModal {
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw address text edit.
|
||||
// Show address text edit.
|
||||
let addr_edit_before = self.address_edit.clone();
|
||||
let address_edit_id = Id::from(modal.id).with("_address").with(wallet.get_config().id);
|
||||
let mut address_edit = TextEdit::new(address_edit_id)
|
||||
@@ -224,9 +265,19 @@ impl TransportSendModal {
|
||||
self.address_error = false;
|
||||
}
|
||||
|
||||
// Send on Enter press.
|
||||
// Continue on Enter press.
|
||||
if address_edit.enter_pressed {
|
||||
self.send(wallet, modal);
|
||||
on_continue(self);
|
||||
}
|
||||
|
||||
// Show request creation error.
|
||||
if let Some(err) = &self.request_error {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(err)
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
}
|
||||
|
||||
// Setup spacing between buttons.
|
||||
@@ -239,36 +290,9 @@ impl TransportSendModal {
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
// Button to create Slatepack message request.
|
||||
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
|
||||
self.send(wallet, modal);
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw error content.
|
||||
fn error_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal) {
|
||||
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();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("repeat"), Colors::white_or_black(false), || {
|
||||
self.send(wallet, modal);
|
||||
on_continue(self);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -280,78 +304,72 @@ impl TransportSendModal {
|
||||
self.amount_edit = "".to_string();
|
||||
self.address_edit = "".to_string();
|
||||
|
||||
let mut w_res = self.send_result.write();
|
||||
let mut w_res = self.request_result.write();
|
||||
*w_res = None;
|
||||
|
||||
self.tx_info_content = None;
|
||||
self.result_tx_content = None;
|
||||
self.address_scan_content = None;
|
||||
|
||||
Modal::close();
|
||||
}
|
||||
|
||||
/// Send entered amount to address.
|
||||
fn send(&mut self, wallet: &Wallet, modal: &Modal) {
|
||||
if self.amount_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
let addr_str = self.address_edit.as_str();
|
||||
if let Ok(addr) = SlatepackAddress::try_from(addr_str.trim()) {
|
||||
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
|
||||
modal.disable_closing();
|
||||
// Send amount over Tor.
|
||||
let mut wallet = wallet.clone();
|
||||
let res = self.send_result.clone();
|
||||
self.sending = true;
|
||||
thread::spawn(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
let result = wallet.send_tor(a, &addr).await;
|
||||
let mut w_res = res.write();
|
||||
*w_res = Some(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
self.address_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw sending progress content.
|
||||
fn progress_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.add_space(16.0);
|
||||
/// Draw loading request content.
|
||||
fn loading_request_ui(&mut self, ui: &mut egui::Ui, modal: &Modal) {
|
||||
ui.add_space(40.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
View::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()));
|
||||
View::big_loading_spinner(ui);
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
ui.add_space(40.0);
|
||||
|
||||
// Check sending result.
|
||||
let has_result = {
|
||||
let r_result = self.send_result.read();
|
||||
r_result.is_some()
|
||||
if !self.address_edit.is_empty() {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.tor_sending", "amount" => self.amount_edit))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
}
|
||||
|
||||
// Update data on request result.
|
||||
let has_res = {
|
||||
let r_request = self.request_result.read();
|
||||
r_request.is_some()
|
||||
};
|
||||
if has_result {
|
||||
{
|
||||
let res = self.send_result.read().clone().unwrap();
|
||||
match res {
|
||||
Ok(tx) => {
|
||||
self.tx_info_content =
|
||||
Some(WalletTransactionModal::new(Some(tx.data.id), false));
|
||||
}
|
||||
Err(_) => {
|
||||
self.error = true;
|
||||
}
|
||||
if has_res {
|
||||
self.request_loading = false;
|
||||
modal.enable_closing();
|
||||
let r_request = self.request_result.read();
|
||||
let result = r_request.as_ref().unwrap();
|
||||
match result {
|
||||
Ok(tx) => {
|
||||
self.result_tx_content =
|
||||
Some(WalletTransactionContent::new(tx, false));
|
||||
}
|
||||
Err(err) => {
|
||||
let m = match err {
|
||||
Error::NotEnoughFunds { .. } => {
|
||||
t!("wallets.pay_balance_error", "amount" => self.amount_edit)
|
||||
}
|
||||
_ => {
|
||||
if !self.address_edit.is_empty() {
|
||||
t!("transport.tor_send_error")
|
||||
} else {
|
||||
t!("wallets.send_slatepack_err")
|
||||
}
|
||||
}
|
||||
};
|
||||
self.request_error = Some(m);
|
||||
}
|
||||
}
|
||||
let mut w_res = self.send_result.write();
|
||||
*w_res = None;
|
||||
self.sending = false;
|
||||
}
|
||||
|
||||
// Check if there is request result error.
|
||||
if self.request_error.is_some() {
|
||||
Modal::change_position(ModalPosition::CenterTop);
|
||||
modal.enable_closing();
|
||||
let mut w_request = self.request_result.write();
|
||||
*w_request = None;
|
||||
self.request_loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,12 @@ use egui::{Id, RichText};
|
||||
use grin_chain::SyncStatus;
|
||||
use grin_util::ZeroingString;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{EYE, LIFEBUOY, STETHOSCOPE, TRASH, WRENCH};
|
||||
use crate::gui::icons::{EYE, KEY, LIFEBUOY, STETHOSCOPE, TRASH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::wallet::types::WalletContentContainer;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::node::Node;
|
||||
use crate::wallet::types::ConnectionMethod;
|
||||
use crate::wallet::Wallet;
|
||||
@@ -69,7 +69,7 @@ impl WalletContentContainer for RecoverySettings {
|
||||
ui.add_space(10.0);
|
||||
View::horizontal_line(ui, Colors::stroke());
|
||||
ui.add_space(6.0);
|
||||
View::sub_title(ui, format!("{} {}", WRENCH, t!("wallets.recovery")));
|
||||
View::sub_title(ui, format!("{} {}", KEY, t!("wallets.recovery")));
|
||||
View::horizontal_line(ui, Colors::stroke());
|
||||
ui.add_space(4.0);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 The Grim Developers
|
||||
// Copyright 2025 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.
|
||||
@@ -14,362 +14,197 @@
|
||||
|
||||
use egui::{Align, CornerRadius, Layout, RichText, StrokeKind};
|
||||
|
||||
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::icons::{CIRCLE_HALF, DOTS_THREE_CIRCLE, PLUGS, PLUGS_CONNECTED, POWER, QR_CODE, SHIELD_CHECKERED, SHIELD_SLASH, WARNING_CIRCLE, WRENCH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::wallets::wallet::transport::settings::WalletTransportSettingsContent;
|
||||
use crate::gui::views::wallets::wallet::types::WalletContentContainer;
|
||||
use crate::gui::views::{Modal, QrCodeContent, 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::Colors;
|
||||
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>,
|
||||
/// Wallet transport panel content.
|
||||
pub struct WalletTransportContent {
|
||||
/// QR code address content.
|
||||
pub qr_address_content: Option<QrCodeContent>,
|
||||
|
||||
/// QR code address image [`Modal`] content.
|
||||
qr_address_content: Option<QrCodeContent>,
|
||||
|
||||
/// Tor settings [`Modal`] content.
|
||||
settings_modal_content: Option<TransportSettingsModal>,
|
||||
/// Settings content.
|
||||
pub settings_content: Option<WalletTransportSettingsContent>,
|
||||
}
|
||||
|
||||
impl WalletTab for WalletTransport {
|
||||
fn get_type(&self) -> WalletTabType {
|
||||
WalletTabType::Transport
|
||||
impl WalletContentContainer for WalletTransportContent {
|
||||
fn modal_ids(&self) -> Vec<&'static str> { vec![] }
|
||||
|
||||
fn modal_ui(&mut self, _: &mut egui::Ui, _: &Wallet, _: &Modal, _: &dyn PlatformCallbacks) {
|
||||
}
|
||||
|
||||
fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
self.modal_content_ui(ui, wallet, cb);
|
||||
self.transport_ui(ui, wallet, cb);
|
||||
}
|
||||
}
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
if let Some(content) = self.qr_address_content.as_mut() {
|
||||
ui.add_space(6.0);
|
||||
|
||||
/// 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";
|
||||
// Draw QR code content.
|
||||
content.ui(ui, cb);
|
||||
|
||||
impl WalletTransport {
|
||||
/// Create new transport content instance, opening sending `Modal` if address was provided.
|
||||
pub fn new(address: Option<String>) -> Self {
|
||||
let mut content = Self {
|
||||
send_modal_content: None,
|
||||
qr_address_content: None,
|
||||
settings_modal_content: None,
|
||||
};
|
||||
if address.is_some() {
|
||||
content.show_send_tor_modal(address)
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
/// Draw wallet transport content.
|
||||
fn transport_ui(&mut self, ui: &mut egui::Ui, wallet: &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: &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(), cb, |ui, modal, cb| {
|
||||
content.ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
TOR_SETTINGS_MODAL => {
|
||||
if let Some(content) = self.settings_modal_content.as_mut() {
|
||||
Modal::ui(ui.ctx(), cb, |ui, modal, cb| {
|
||||
content.ui(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
QR_ADDRESS_MODAL => {
|
||||
Modal::ui(ui.ctx(), cb, |ui, _, cb| {
|
||||
self.qr_address_modal_ui(ui, cb);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
self.qr_address_content = None;
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
} else if let Some(content) = self.settings_content.as_mut() {
|
||||
let mut closed = false;
|
||||
content.ui(ui, wallet, cb, || {
|
||||
closed = true;
|
||||
});
|
||||
if closed {
|
||||
self.settings_content = None;
|
||||
}
|
||||
} else {
|
||||
self.tor_header_ui(ui, wallet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw Tor transport content.
|
||||
fn tor_ui(&mut self, ui: &mut egui::Ui, wallet: &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);
|
||||
impl Default for WalletTransportContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
qr_address_content: None,
|
||||
settings_content: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
impl WalletTransportContent {
|
||||
/// Check if it's possible to go back at navigation stack.
|
||||
pub fn can_back(&self) -> bool {
|
||||
self.settings_content.is_some() || self.qr_address_content.is_some()
|
||||
}
|
||||
|
||||
/// Navigate back on navigation stack.
|
||||
pub fn back(&mut self) {
|
||||
if self.settings_content.is_some() {
|
||||
self.settings_content = None;
|
||||
} else if self.qr_address_content.is_some() {
|
||||
self.qr_address_content = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw Tor transport header content.
|
||||
fn tor_header_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
let wallet_data = wallet.get_data();
|
||||
if wallet_data.is_none() {
|
||||
return;
|
||||
}
|
||||
let addr = wallet.slatepack_address().unwrap();
|
||||
|
||||
// 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::fill_lite(),
|
||||
let info = wallet.get_data().unwrap().info;
|
||||
let awaiting_balance = info.amount_awaiting_confirmation > 0 ||
|
||||
info.amount_awaiting_finalization > 0 || info.amount_locked > 0;
|
||||
let rounding = if awaiting_balance {
|
||||
View::item_rounding(1, 3, false)
|
||||
} else {
|
||||
View::item_rounding(1, 2, false)
|
||||
};
|
||||
ui.painter().rect(rect,
|
||||
rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Middle);
|
||||
StrokeKind::Outside);
|
||||
|
||||
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();
|
||||
// Show button to show QR code address.
|
||||
View::item_button(ui, View::item_rounding(1, 2, true), QR_CODE, None, || {
|
||||
self.qr_address_content = Some(QrCodeContent::new(addr.clone(), false)
|
||||
.with_max_size(320.0));
|
||||
});
|
||||
|
||||
// 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, CornerRadius::default(), POWER, Some(Colors::green()), || {
|
||||
let r = CornerRadius::default();
|
||||
View::item_button(ui, r, 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, CornerRadius::default(), POWER, Some(Colors::red()), || {
|
||||
let r = CornerRadius::default();
|
||||
View::item_button(ui, r, POWER, Some(Colors::red()), || {
|
||||
Tor::stop_service(service_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Draw button to show Tor transport settings.
|
||||
let button_rounding = View::item_rounding(1, 3, true);
|
||||
View::item_button(ui, button_rounding, WRENCH, None, || {
|
||||
self.settings_content = Some(WalletTransportSettingsContent::default());
|
||||
});
|
||||
|
||||
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 {
|
||||
let address_color = if is_running {
|
||||
Colors::green()
|
||||
} else if has_error {
|
||||
Colors::red()
|
||||
} else {
|
||||
Colors::inactive_text()
|
||||
};
|
||||
let is_starting = Tor::is_service_starting(service_id);
|
||||
// Show slatepack address text.
|
||||
View::animate_text(ui, addr.clone(), 17.0, address_color, is_starting);
|
||||
ui.add_space(1.0);
|
||||
|
||||
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"))
|
||||
(PLUGS_CONNECTED, t!("transport.connected"))
|
||||
} else if let Some(_) = TorConfig::get_proxy() {
|
||||
(PLUGS_CONNECTED, t!("app_settings.proxy"))
|
||||
} else {
|
||||
(X_CIRCLE, t!("transport.disconnected"))
|
||||
(PLUGS, t!("transport.disconnected"))
|
||||
};
|
||||
let status_text = format!("{} {}", icon, text);
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::text(false)));
|
||||
// Show connection status text.
|
||||
View::ellipsize_text(ui, status_text, 15.0, 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))
|
||||
let bridges_text = if is_starting || has_error {
|
||||
match TorConfig::get_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))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
format!("{} {}", CIRCLE_HALF, t!("transport.tor_network"))
|
||||
};
|
||||
|
||||
// Show bridge info text.
|
||||
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::fill_lite(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Middle);
|
||||
|
||||
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, CornerRadius::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,
|
||||
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) {
|
||||
// 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(),
|
||||
StrokeKind::Middle);
|
||||
|
||||
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(None);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Show [`Modal`] to send over Tor.
|
||||
fn show_send_tor_modal(&mut self, 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();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The Grim Developers
|
||||
// Copyright 2025 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.
|
||||
@@ -15,5 +15,4 @@
|
||||
mod content;
|
||||
pub use content::*;
|
||||
|
||||
mod send;
|
||||
mod settings;
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The Grim Developers
|
||||
// Copyright 2025 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.
|
||||
@@ -13,226 +13,66 @@
|
||||
// limitations under the License.
|
||||
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::{Id, RichText};
|
||||
use egui::RichText;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, Modal, TextEdit, View};
|
||||
use crate::tor::{Tor, TorBridge, TorConfig};
|
||||
use crate::gui::views::settings::TorSettingsContent;
|
||||
use crate::gui::views::types::ContentContainer;
|
||||
use crate::gui::views::View;
|
||||
use crate::tor::Tor;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Transport settings [`Modal`] content.
|
||||
pub struct TransportSettingsModal {
|
||||
/// Flag to check if Tor settings were changed.
|
||||
/// Wallet transport settings content.
|
||||
pub struct WalletTransportSettingsContent {
|
||||
/// Flag to check if settings were changed to restart Tor service.
|
||||
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>,
|
||||
/// Tor transport content settings.
|
||||
tor_settings_content: TorSettingsContent,
|
||||
}
|
||||
|
||||
impl Default for TransportSettingsModal {
|
||||
impl Default for WalletTransportSettingsContent {
|
||||
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,
|
||||
tor_settings_content: TorSettingsContent::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransportSettingsModal {
|
||||
impl WalletTransportSettingsContent {
|
||||
/// Draw transport settings content.
|
||||
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;
|
||||
} 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 = TextEdit::new(bin_edit_id)
|
||||
.no_soft_keyboard()
|
||||
.paste()
|
||||
.focus(false);
|
||||
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);
|
||||
bin_edit.ui(ui, &mut self.bridge_bin_path_edit, cb);
|
||||
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 = TextEdit::new(conn_edit_id)
|
||||
.no_soft_keyboard()
|
||||
.paste()
|
||||
.focus(false)
|
||||
.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);
|
||||
conn_edit.ui(ui, &mut self.bridge_conn_line_edit, cb);
|
||||
// Check if scan button was pressed.
|
||||
if conn_edit.scan_pressed {
|
||||
modal.disable_closing();
|
||||
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);
|
||||
}
|
||||
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_close: impl FnOnce()) {
|
||||
ui.add_space(8.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
// Do not show bridges settings on Android.
|
||||
let os = OperatingSystem::from_target_os();
|
||||
if os != OperatingSystem::Android {
|
||||
// Show Tor settings.
|
||||
self.tor_settings_content.ui(ui, cb);
|
||||
if !self.tor_settings_content.settings_changed {
|
||||
self.settings_changed = true;
|
||||
}
|
||||
ui.add_space(4.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
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);
|
||||
self.settings_changed = true;
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
ui.add_space(8.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
if self.settings_changed {
|
||||
@@ -248,7 +88,7 @@ impl TransportSettingsModal {
|
||||
Tor::rebuild_client();
|
||||
}
|
||||
}
|
||||
Modal::close();
|
||||
on_close();
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
@@ -12,30 +12,30 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::ops::Range;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use egui::{Align, Id, Layout, Rect, RichText, CornerRadius, ScrollArea, StrokeKind};
|
||||
use egui::epaint::RectShape;
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Align, CornerRadius, Id, Layout, Rect, RichText, ScrollArea, StrokeKind};
|
||||
use grin_core::consensus::COINBASE_MATURITY;
|
||||
use grin_core::core::amount_to_hr_string;
|
||||
use grin_wallet_libwallet::TxLogEntryType;
|
||||
use std::ops::Range;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, BRIDGE, CALENDAR_CHECK, CHAT_CIRCLE_TEXT, CHECK, DOTS_THREE_CIRCLE, FILE_TEXT, GEAR_FINE, PROHIBIT, X_CIRCLE};
|
||||
use crate::gui::icons::{ARCHIVE_BOX, ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, CALENDAR_CHECK, CHECK, DOTS_THREE_CIRCLE, FILE_ARROW_DOWN, 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::{LinePosition, ModalPosition};
|
||||
use crate::gui::views::wallets::types::WalletTab;
|
||||
use crate::gui::views::wallets::wallet::types::{GRIN, WalletTabType};
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionModal;
|
||||
use crate::gui::views::wallets::wallet::types::{WalletTabType, GRIN};
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionContent;
|
||||
use crate::gui::views::{Content, Modal, PullToRefresh, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::{WalletData, WalletTransaction};
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Wallet transactions tab content.
|
||||
pub struct WalletTransactions {
|
||||
/// Transaction information [`Modal`] content.
|
||||
tx_info_content: Option<WalletTransactionModal>,
|
||||
tx_info_content: Option<WalletTransactionContent>,
|
||||
|
||||
/// Transaction identifier to use at confirmation [`Modal`].
|
||||
confirm_cancel_tx_id: Option<u32>,
|
||||
@@ -44,16 +44,6 @@ pub struct WalletTransactions {
|
||||
manual_sync: Option<u128>
|
||||
}
|
||||
|
||||
impl Default for WalletTransactions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tx_info_content: None,
|
||||
confirm_cancel_tx_id: None,
|
||||
manual_sync: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletTab for WalletTransactions {
|
||||
fn get_type(&self) -> WalletTabType {
|
||||
WalletTabType::Txs
|
||||
@@ -74,6 +64,19 @@ impl WalletTransactions {
|
||||
/// Height of transaction list item.
|
||||
pub const TX_ITEM_HEIGHT: f32 = 75.0;
|
||||
|
||||
/// Create new content instance with opening tx info.
|
||||
pub fn new(tx: Option<WalletTransaction>) -> Self {
|
||||
let mut content = Self {
|
||||
tx_info_content: None,
|
||||
confirm_cancel_tx_id: None,
|
||||
manual_sync: None,
|
||||
};
|
||||
if let Some(tx) = &tx {
|
||||
content.show_tx_info_modal(tx, false);
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
/// Draw transactions content.
|
||||
fn txs_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
let data = wallet.get_data().unwrap();
|
||||
@@ -84,15 +87,14 @@ impl WalletTransactions {
|
||||
return;
|
||||
}
|
||||
let txs = data.txs.as_ref().unwrap();
|
||||
let mut awaiting_amount = false;
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
// Show message when txs are empty.
|
||||
if txs.is_empty() {
|
||||
View::center_content(ui, 96.0, |ui| {
|
||||
let empty_text = t!(
|
||||
"wallets.txs_empty",
|
||||
"message" => CHAT_CIRCLE_TEXT,
|
||||
"transport" => BRIDGE,
|
||||
"message" => FILE_ARROW_DOWN,
|
||||
"transport" => ARCHIVE_BOX,
|
||||
"settings" => GEAR_FINE
|
||||
);
|
||||
ui.label(RichText::new(empty_text)
|
||||
@@ -102,7 +104,7 @@ impl WalletTransactions {
|
||||
return;
|
||||
}
|
||||
// Draw awaiting amount info if exists.
|
||||
awaiting_amount = self.awaiting_info_ui(ui, &data);
|
||||
self.awaiting_info_ui(ui, &data);
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
|
||||
@@ -120,7 +122,7 @@ impl WalletTransactions {
|
||||
.show_rows(ui, Self::TX_ITEM_HEIGHT, txs.len(), |ui, row_range| {
|
||||
ui.add_space(1.0);
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.tx_list_ui(ui, awaiting_amount, row_range, wallet, txs);
|
||||
self.tx_list_ui(ui, row_range, wallet, txs);
|
||||
});
|
||||
})
|
||||
});
|
||||
@@ -137,25 +139,19 @@ impl WalletTransactions {
|
||||
/// Draw transaction list content.
|
||||
fn tx_list_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
awaiting: bool,
|
||||
row_range: Range<usize>,
|
||||
wallet: &Wallet,
|
||||
txs: &Vec<WalletTransaction>) {
|
||||
for index in row_range {
|
||||
let mut rect = if awaiting {
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.min += egui::emath::vec2(6.0, 0.0);
|
||||
rect.max -= egui::emath::vec2(6.0, 0.0);
|
||||
rect
|
||||
} else {
|
||||
ui.available_rect_before_wrap()
|
||||
};
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.min += egui::emath::vec2(6.0, 0.0);
|
||||
rect.max -= egui::emath::vec2(6.0, 0.0);
|
||||
rect.set_height(Self::TX_ITEM_HEIGHT);
|
||||
|
||||
// Draw tx item background.
|
||||
let mut r = View::item_rounding(index, txs.len(), false);
|
||||
let p = ui.painter();
|
||||
p.rect(rect, r, Colors::fill_lite(), View::item_stroke(), StrokeKind::Middle);
|
||||
p.rect(rect, r, Colors::fill(), View::item_stroke(), StrokeKind::Middle);
|
||||
|
||||
let tx = txs.get(index).unwrap();
|
||||
let data = wallet.get_data().unwrap();
|
||||
@@ -195,14 +191,15 @@ impl WalletTransactions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw information about locked, finalizing or confirming balance, return `true` if exists.
|
||||
fn awaiting_info_ui(&mut self, ui: &mut egui::Ui, data: &WalletData) -> bool {
|
||||
/// Draw information about locked, finalizing or confirming balance.
|
||||
fn awaiting_info_ui(&mut self, ui: &mut egui::Ui, data: &WalletData) {
|
||||
let amount_conf = data.info.amount_awaiting_confirmation;
|
||||
let amount_fin = data.info.amount_awaiting_finalization;
|
||||
let amount_locked = data.info.amount_locked;
|
||||
if amount_conf == 0 && amount_fin == 0 && amount_locked == 0 {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
ui.add_space(-1.0);
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
// Draw background.
|
||||
let mut bg = RectShape::new(rect, CornerRadius {
|
||||
@@ -210,7 +207,7 @@ impl WalletTransactions {
|
||||
ne: 0.0 as u8,
|
||||
sw: 8.0 as u8,
|
||||
se: 8.0 as u8,
|
||||
}, Colors::TRANSPARENT, View::item_stroke(), StrokeKind::Middle);
|
||||
}, Colors::fill(), View::item_stroke(), StrokeKind::Middle);
|
||||
let bg_idx = ui.painter().add(bg.clone());
|
||||
let resp = ui.allocate_ui(rect.size(), |ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
@@ -233,7 +230,6 @@ impl WalletTransactions {
|
||||
// Setup background size.
|
||||
bg.rect = resp.rect;
|
||||
ui.painter().set(bg_idx, bg);
|
||||
true
|
||||
}
|
||||
|
||||
/// Draw [`Modal`] content for this ui container.
|
||||
@@ -454,7 +450,7 @@ impl WalletTransactions {
|
||||
|
||||
/// Show transaction information [`Modal`].
|
||||
fn show_tx_info_modal(&mut self, tx: &WalletTransaction, finalize: bool) {
|
||||
let modal = WalletTransactionModal::new(Some(tx.data.id), finalize);
|
||||
let modal = WalletTransactionContent::new(tx, finalize);
|
||||
self.tx_info_content = Some(modal);
|
||||
Modal::new(TX_INFO_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
@@ -529,5 +525,5 @@ fn awaiting_item_ui(ui: &mut egui::Ui, amount: u64, label: String) {
|
||||
ui.label(RichText::new(label)
|
||||
.color(Colors::gray())
|
||||
.size(15.0));
|
||||
ui.add_space(4.0);
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
@@ -24,14 +24,14 @@ use grin_wallet_libwallet::{Error, Slate, SlateState, TxLogEntryType};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{BROOM, CHECK, CLIPBOARD_TEXT, COPY, CUBE, FILE_ARCHIVE, FILE_TEXT, HASH_STRAIGHT, PROHIBIT, QR_CODE, SCAN};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, FilePickButton, Modal, QrCodeContent, View};
|
||||
use crate::gui::views::{CameraContent, FilePickContent, FilePickContentType, Modal, QrCodeContent, View};
|
||||
use crate::gui::views::wallets::wallet::txs::WalletTransactions;
|
||||
use crate::gui::views::wallets::wallet::types::SLATEPACK_MESSAGE_HINT;
|
||||
use crate::wallet::types::WalletTransaction;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Transaction information [`Modal`] content.
|
||||
pub struct WalletTransactionModal {
|
||||
pub struct WalletTransactionContent {
|
||||
/// Transaction identifier.
|
||||
tx_id: u32,
|
||||
|
||||
@@ -56,14 +56,14 @@ pub struct WalletTransactionModal {
|
||||
scan_qr_content: Option<CameraContent>,
|
||||
|
||||
/// Button to parse picked file content.
|
||||
file_pick_button: FilePickButton,
|
||||
file_pick_button: FilePickContent,
|
||||
}
|
||||
|
||||
impl WalletTransactionModal {
|
||||
impl WalletTransactionContent {
|
||||
/// Create new content instance with [`Wallet`] from provided [`WalletTransaction`].
|
||||
pub fn new(tx_id: Option<u32>, show_finalization: bool) -> Self {
|
||||
pub fn new(tx: &WalletTransaction, show_finalization: bool) -> Self {
|
||||
Self {
|
||||
tx_id: tx_id.unwrap_or(0),
|
||||
tx_id: tx.data.id,
|
||||
response_edit: None,
|
||||
finalize_edit: "".to_string(),
|
||||
finalize_error: false,
|
||||
@@ -72,7 +72,7 @@ impl WalletTransactionModal {
|
||||
final_result: Arc::new(RwLock::new(None)),
|
||||
qr_code_content: None,
|
||||
scan_qr_content: None,
|
||||
file_pick_button: FilePickButton::default(),
|
||||
file_pick_button: FilePickContent::new(FilePickContentType::Button),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,8 +57,6 @@ pub trait WalletTab {
|
||||
#[derive(PartialEq)]
|
||||
pub enum WalletTabType {
|
||||
Txs,
|
||||
Messages,
|
||||
Transport,
|
||||
Settings
|
||||
}
|
||||
|
||||
@@ -67,8 +65,6 @@ impl WalletTabType {
|
||||
pub fn name(&self) -> String {
|
||||
match *self {
|
||||
WalletTabType::Txs => t!("wallets.txs"),
|
||||
WalletTabType::Messages => t!("wallets.messages"),
|
||||
WalletTabType::Transport => t!("wallets.transport"),
|
||||
WalletTabType::Settings => t!("wallets.settings")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -161,7 +161,7 @@ pub fn setup_visuals(ctx: &Context) {
|
||||
} else {
|
||||
Colors::yellow()
|
||||
};
|
||||
visuals.widgets.inactive.fg_stroke.color = Colors::item_button();
|
||||
visuals.widgets.inactive.fg_stroke.color = Colors::item_button_text();
|
||||
// Setup visuals.
|
||||
ctx.set_visuals(visuals);
|
||||
}
|
||||
|
||||
+27
-1
@@ -24,12 +24,19 @@ pub struct WalletList {
|
||||
pub main_list: Vec<Wallet>,
|
||||
/// List of wallets for [`ChainTypes::Testnet`].
|
||||
pub test_list: Vec<Wallet>,
|
||||
|
||||
/// Selected wallet id.
|
||||
selected: Option<i64>,
|
||||
}
|
||||
|
||||
impl Default for WalletList {
|
||||
fn default() -> Self {
|
||||
let (main_list, test_list) = Self::init();
|
||||
Self { main_list, test_list }
|
||||
Self {
|
||||
main_list,
|
||||
test_list,
|
||||
selected: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,4 +103,23 @@ impl WalletList {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Select wallet.
|
||||
pub fn select(&mut self, id: Option<i64>) {
|
||||
self.selected = id;
|
||||
}
|
||||
|
||||
/// Get selected wallet.
|
||||
pub fn selected(&self) -> Box<Option<&Wallet>> {
|
||||
if self.selected.is_none() {
|
||||
return Box::new(None);
|
||||
}
|
||||
let list = self.list();
|
||||
for wallet in list {
|
||||
if wallet.get_config().id == self.selected.unwrap() {
|
||||
return Box::new(Some(wallet));
|
||||
}
|
||||
}
|
||||
Box::new(None)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user