Open .slatepack file with the app #13
19 changed files with 672 additions and 243 deletions
28
Cargo.lock
generated
28
Cargo.lock
generated
|
@ -2483,6 +2483,12 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
|
||||
|
||||
[[package]]
|
||||
name = "doctest-file"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562"
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.8"
|
||||
|
@ -3833,6 +3839,7 @@ dependencies = [
|
|||
"hyper 0.14.29",
|
||||
"hyper-tls 0.5.0",
|
||||
"image 0.25.1",
|
||||
"interprocess",
|
||||
"jni",
|
||||
"lazy_static",
|
||||
"local-ip-address",
|
||||
|
@ -4976,6 +4983,21 @@ dependencies = [
|
|||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interprocess"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f4e4a06d42fab3e85ab1b419ad32b09eab58b901d40c57935ff92db3287a13"
|
||||
dependencies = [
|
||||
"doctest-file",
|
||||
"futures-core",
|
||||
"libc",
|
||||
"recvmsg",
|
||||
"tokio 1.38.0",
|
||||
"widestring",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "intl-memoizer"
|
||||
version = "0.5.2"
|
||||
|
@ -7432,6 +7454,12 @@ dependencies = [
|
|||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "recvmsg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
|
|
|
@ -119,6 +119,7 @@ eframe = { version = "0.28.1", features = ["wgpu", "glow"] }
|
|||
arboard = "3.2.0"
|
||||
rfd = "0.14.1"
|
||||
dark-light = "1.1.1"
|
||||
interprocess = { version = "2.2.1", features = ["tokio"] }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.13.1"
|
||||
|
|
|
@ -3,4 +3,5 @@ Name=Grim
|
|||
Exec=grim
|
||||
Icon=grim
|
||||
Type=Application
|
||||
Categories=Finance
|
||||
Categories=Finance
|
||||
MimeType=application/x-slatepack;text/plain;
|
|
@ -32,25 +32,38 @@ lazy_static! {
|
|||
/// Implements ui entry point and contains platform-specific callbacks.
|
||||
pub struct App<Platform> {
|
||||
/// Platform specific callbacks handler.
|
||||
pub(crate) platform: Platform,
|
||||
|
||||
/// Main ui content.
|
||||
pub platform: Platform,
|
||||
/// Main content.
|
||||
content: Content,
|
||||
|
||||
/// Last window resize direction.
|
||||
resize_direction: Option<ResizeDirection>
|
||||
resize_direction: Option<ResizeDirection>,
|
||||
/// Flag to check if it's first draw.
|
||||
first_draw: bool,
|
||||
}
|
||||
|
||||
impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
pub fn new(platform: Platform) -> Self {
|
||||
Self { platform, content: Content::default(), resize_direction: None }
|
||||
Self {
|
||||
platform,
|
||||
content: Content::default(),
|
||||
resize_direction: None,
|
||||
first_draw: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw application content.
|
||||
pub fn ui(&mut self, ctx: &Context) {
|
||||
// Set Desktop platform context on first draw.
|
||||
if self.first_draw {
|
||||
if View::is_desktop() {
|
||||
self.platform.set_context(ctx);
|
||||
}
|
||||
self.first_draw = false;
|
||||
}
|
||||
|
||||
// Handle Esc keyboard key event and platform Back button key event.
|
||||
let back_pressed = BACK_BUTTON_PRESSED.load(Ordering::Relaxed);
|
||||
if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, egui::Key::Escape)) || back_pressed {
|
||||
if back_pressed || ctx.input_mut(|i| i.consume_key(Modifiers::NONE, egui::Key::Escape)) {
|
||||
self.content.on_back();
|
||||
if back_pressed {
|
||||
BACK_BUTTON_PRESSED.store(false, Ordering::Relaxed);
|
||||
|
@ -59,8 +72,8 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
|||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
// Handle Close event (on desktop).
|
||||
if ctx.input(|i| i.viewport().close_requested()) {
|
||||
// Handle Close event on desktop.
|
||||
if View::is_desktop() && ctx.input(|i| i.viewport().close_requested()) {
|
||||
if !self.content.exit_allowed {
|
||||
ctx.send_viewport_cmd(ViewportCommand::CancelClose);
|
||||
Content::show_exit_modal();
|
||||
|
@ -92,6 +105,11 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
|||
}
|
||||
self.content.ui(ui, &self.platform);
|
||||
}
|
||||
|
||||
// Provide incoming data to wallets.
|
||||
if let Some(data) = self.platform.consume_data() {
|
||||
self.content.wallets.on_data(ui, Some(data), &self.platform);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -167,6 +167,10 @@ impl PlatformCallbacks for Android {
|
|||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn consume_data(&mut self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
|
|
|
@ -13,12 +13,13 @@
|
|||
// limitations under the License.
|
||||
|
||||
use std::fs::File;
|
||||
use std::io:: Write;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use std::io::Write;
|
||||
use std::thread;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use parking_lot::RwLock;
|
||||
use lazy_static::lazy_static;
|
||||
use egui::{UserAttentionType, ViewportCommand};
|
||||
use rfd::FileDialog;
|
||||
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
|
@ -28,17 +29,16 @@ use crate::gui::platform::PlatformCallbacks;
|
|||
pub struct Desktop {
|
||||
/// Flag to check if camera stop is needed.
|
||||
stop_camera: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Default for Desktop {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stop_camera: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
/// Context to repaint content and handle viewport commands.
|
||||
ctx: Arc<RwLock<Option<egui::Context>>>,
|
||||
}
|
||||
|
||||
impl PlatformCallbacks for Desktop {
|
||||
fn set_context(&mut self, ctx: &egui::Context) {
|
||||
let mut w_ctx = self.ctx.write();
|
||||
*w_ctx = Some(ctx.clone());
|
||||
}
|
||||
|
||||
fn show_keyboard(&self) {}
|
||||
|
||||
fn hide_keyboard(&self) {}
|
||||
|
@ -119,9 +119,61 @@ impl PlatformCallbacks for Desktop {
|
|||
fn picked_file(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn consume_data(&mut self) -> Option<String> {
|
||||
let has_data = {
|
||||
let r_data = PASSED_DATA.read();
|
||||
r_data.is_some()
|
||||
};
|
||||
if has_data {
|
||||
// Reset window state.
|
||||
let r_ctx = self.ctx.read();
|
||||
if r_ctx.is_some() {
|
||||
let ctx = r_ctx.as_ref().unwrap();
|
||||
ctx.send_viewport_cmd(
|
||||
ViewportCommand::RequestUserAttention(UserAttentionType::Reset)
|
||||
);
|
||||
}
|
||||
// Clear data.
|
||||
let mut w_data = PASSED_DATA.write();
|
||||
let data = w_data.clone();
|
||||
*w_data = None;
|
||||
return data;
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Desktop {
|
||||
/// Create new instance with provided extra data from app opening.
|
||||
pub fn new(data: Option<String>) -> Self {
|
||||
let mut w_data = PASSED_DATA.write();
|
||||
*w_data = data;
|
||||
Self {
|
||||
stop_camera: Arc::new(AtomicBool::new(false)),
|
||||
ctx: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle data passed to application.
|
||||
pub fn on_data(&self, data: String) {
|
||||
let mut w_data = PASSED_DATA.write();
|
||||
*w_data = Some(data);
|
||||
|
||||
// Bring focus on window.
|
||||
let r_ctx = self.ctx.read();
|
||||
if r_ctx.is_some() {
|
||||
let ctx = r_ctx.as_ref().unwrap();
|
||||
ctx.send_viewport_cmd(ViewportCommand::Visible(true));
|
||||
ctx.send_viewport_cmd(ViewportCommand::Minimized(false));
|
||||
ctx.send_viewport_cmd(
|
||||
ViewportCommand::RequestUserAttention(UserAttentionType::Informational)
|
||||
);
|
||||
ctx.send_viewport_cmd(ViewportCommand::Focus);
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn start_camera_capture(stop_camera: Arc<AtomicBool>) {
|
||||
|
@ -205,4 +257,7 @@ impl Desktop {
|
|||
lazy_static! {
|
||||
/// Last captured image from started camera.
|
||||
static ref LAST_CAMERA_IMAGE: Arc<RwLock<Option<(Vec<u8>, u32)>>> = Arc::new(RwLock::new(None));
|
||||
|
||||
/// Data passed from deeplink or opened file.
|
||||
static ref PASSED_DATA: Arc<RwLock<Option<String>>> = Arc::new(RwLock::new(None));
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ pub mod platform;
|
|||
pub mod platform;
|
||||
|
||||
pub trait PlatformCallbacks {
|
||||
fn set_context(&mut self, ctx: &egui::Context);
|
||||
fn show_keyboard(&self);
|
||||
fn hide_keyboard(&self);
|
||||
fn copy_string_to_buffer(&self, data: String);
|
||||
|
@ -34,4 +35,5 @@ pub trait PlatformCallbacks {
|
|||
fn share_data(&self, name: String, data: Vec<u8>) -> Result<(), std::io::Error>;
|
||||
fn pick_file(&self) -> Option<String>;
|
||||
fn picked_file(&self) -> Option<String>;
|
||||
fn consume_data(&mut self) -> Option<String>;
|
||||
}
|
|
@ -35,7 +35,7 @@ pub struct Modal {
|
|||
/// Identifier for modal.
|
||||
pub(crate) id: &'static str,
|
||||
/// Position on the screen.
|
||||
position: ModalPosition,
|
||||
pub position: ModalPosition,
|
||||
/// To check if it can be closed.
|
||||
closeable: Arc<AtomicBool>,
|
||||
/// Title text
|
||||
|
@ -64,6 +64,12 @@ impl Modal {
|
|||
self
|
||||
}
|
||||
|
||||
/// Change [`Modal`] position on the screen.
|
||||
pub fn change_position(position: ModalPosition) {
|
||||
let mut w_state = MODAL_STATE.write();
|
||||
w_state.modal.as_mut().unwrap().position = position;
|
||||
}
|
||||
|
||||
/// Mark [`Modal`] closed.
|
||||
pub fn close(&self) {
|
||||
let mut w_nav = MODAL_STATE.write();
|
||||
|
|
|
@ -83,7 +83,7 @@ impl Default for StratumSetup {
|
|||
|
||||
Self {
|
||||
wallets: WalletList::default(),
|
||||
wallets_modal: WalletsModal::new(wallet_id),
|
||||
wallets_modal: WalletsModal::new(wallet_id, None, false),
|
||||
available_ips: NodeConfig::get_ip_addrs(),
|
||||
stratum_port_edit: port,
|
||||
stratum_port_available_edit: is_port_available,
|
||||
|
@ -111,10 +111,12 @@ impl ModalContainer for StratumSetup {
|
|||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
WALLET_SELECTION_MODAL => self.wallets_modal.ui(ui, modal, &self.wallets, |id| {
|
||||
NodeConfig::save_stratum_wallet_id(id);
|
||||
self.wallet_name = WalletConfig::name_by_id(id);
|
||||
}),
|
||||
WALLET_SELECTION_MODAL => {
|
||||
self.wallets_modal.ui(ui, modal, &mut self.wallets, cb, |id, _| {
|
||||
NodeConfig::save_stratum_wallet_id(id);
|
||||
self.wallet_name = WalletConfig::name_by_id(id);
|
||||
})
|
||||
},
|
||||
STRATUM_PORT_MODAL => self.port_modal(ui, modal, cb),
|
||||
ATTEMPT_TIME_MODAL => self.attempt_modal(ui, modal, cb),
|
||||
MIN_SHARE_DIFF_MODAL => self.min_diff_modal(ui, modal, cb),
|
||||
|
@ -240,7 +242,7 @@ impl StratumSetup {
|
|||
|
||||
/// Show wallet selection [`Modal`].
|
||||
fn show_wallets_modal(&mut self) {
|
||||
self.wallets_modal = WalletsModal::new(NodeConfig::get_stratum_wallet_id());
|
||||
self.wallets_modal = WalletsModal::new(NodeConfig::get_stratum_wallet_id(), None, false);
|
||||
// Show modal.
|
||||
Modal::new(WALLET_SELECTION_MODAL)
|
||||
.position(ModalPosition::Center)
|
||||
|
|
|
@ -18,13 +18,14 @@ use egui::scroll_area::ScrollBarVisibility;
|
|||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_LOCK, FOLDER_OPEN, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SPINNER, SUITCASE, WARNING_CIRCLE};
|
||||
use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_OPEN, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SUITCASE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Content, TitlePanel, View};
|
||||
use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions, TitleContentType, TitleType};
|
||||
use crate::gui::views::types::{ModalContainer, ModalPosition, TitleContentType, TitleType};
|
||||
use crate::gui::views::wallets::creation::WalletCreation;
|
||||
use crate::gui::views::wallets::modals::WalletConnectionModal;
|
||||
use crate::gui::views::wallets::modals::{OpenWalletModal, WalletConnectionModal, WalletsModal};
|
||||
use crate::gui::views::wallets::types::WalletTabType;
|
||||
use crate::gui::views::wallets::wallet::types::status_text;
|
||||
use crate::gui::views::wallets::WalletContent;
|
||||
use crate::wallet::{Wallet, WalletList};
|
||||
|
||||
|
@ -33,10 +34,11 @@ pub struct WalletsContent {
|
|||
/// List of wallets.
|
||||
wallets: WalletList,
|
||||
|
||||
/// Password to open wallet for [`Modal`].
|
||||
pass_edit: String,
|
||||
/// Flag to check if wrong password was entered at [`Modal`].
|
||||
wrong_pass: bool,
|
||||
/// Wallet selection [`Modal`] content.
|
||||
wallet_selection_content: Option<WalletsModal>,
|
||||
|
||||
/// Wallet opening [`Modal`] content.
|
||||
open_wallet_content: Option<OpenWalletModal>,
|
||||
|
||||
/// Wallet connection selection content.
|
||||
conn_modal_content: Option<WalletConnectionModal>,
|
||||
|
@ -54,24 +56,29 @@ pub struct WalletsContent {
|
|||
}
|
||||
|
||||
/// Identifier for connection selection [`Modal`].
|
||||
const CONNECTION_SELECTION_MODAL: &'static str = "wallets_connection_selection_modal";
|
||||
const CONNECTION_SELECTION_MODAL: &'static str = "wallets_connection_selection";
|
||||
|
||||
/// Identifier for wallet opening [`Modal`].
|
||||
const OPEN_WALLET_MODAL: &'static str = "open_wallet_modal";
|
||||
const OPEN_WALLET_MODAL: &'static str = "wallets_open_wallet";
|
||||
|
||||
/// Identifier for wallet opening [`Modal`].
|
||||
const SELECT_WALLET_MODAL: &'static str = "wallets_select_wallet";
|
||||
|
||||
impl Default for WalletsContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
wallets: WalletList::default(),
|
||||
pass_edit: "".to_string(),
|
||||
wrong_pass: false,
|
||||
wallet_selection_content: None,
|
||||
open_wallet_content: None,
|
||||
conn_modal_content: None,
|
||||
wallet_content: WalletContent::default(),
|
||||
wallet_content: WalletContent::new(None),
|
||||
creation_content: WalletCreation::default(),
|
||||
show_wallets_at_dual_panel: AppConfig::show_wallets_at_dual_panel(),
|
||||
modal_ids: vec![
|
||||
OPEN_WALLET_MODAL,
|
||||
WalletCreation::NAME_PASS_MODAL,
|
||||
CONNECTION_SELECTION_MODAL,
|
||||
SELECT_WALLET_MODAL
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -87,13 +94,21 @@ impl ModalContainer for WalletsContent {
|
|||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
OPEN_WALLET_MODAL => self.open_wallet_modal_ui(ui, modal, cb),
|
||||
OPEN_WALLET_MODAL => {
|
||||
if let Some(content) = self.open_wallet_content.as_mut() {
|
||||
content.ui(ui, modal, &mut self.wallets, cb, |data| {
|
||||
// Setup wallet content.
|
||||
self.wallet_content = WalletContent::new(data);
|
||||
});
|
||||
}
|
||||
},
|
||||
WalletCreation::NAME_PASS_MODAL => {
|
||||
self.creation_content.name_pass_modal_ui(ui, modal, cb)
|
||||
},
|
||||
CONNECTION_SELECTION_MODAL => {
|
||||
if let Some(content) = self.conn_modal_content.as_mut() {
|
||||
content.ui(ui, modal, cb, |id| {
|
||||
// Update wallet connection on select.
|
||||
let list = self.wallets.list();
|
||||
for w in list {
|
||||
if self.wallets.selected_id == Some(w.get_config().id) {
|
||||
|
@ -103,12 +118,20 @@ impl ModalContainer for WalletsContent {
|
|||
});
|
||||
}
|
||||
}
|
||||
SELECT_WALLET_MODAL => {
|
||||
if let Some(content) = self.wallet_selection_content.as_mut() {
|
||||
content.ui(ui, modal, &mut self.wallets, cb, |_, data| {
|
||||
self.wallet_content = WalletContent::new(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletsContent {
|
||||
/// Draw wallets content.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
// Draw modal content for current ui container.
|
||||
self.current_modal_ui(ui, cb);
|
||||
|
@ -159,7 +182,7 @@ impl WalletsContent {
|
|||
// Add created wallet to list.
|
||||
self.wallets.add(wallet);
|
||||
// Reset wallet content.
|
||||
self.wallet_content = WalletContent::default();
|
||||
self.wallet_content = WalletContent::new(None);
|
||||
});
|
||||
} else {
|
||||
let selected_id = self.wallets.selected_id.clone();
|
||||
|
@ -254,6 +277,56 @@ impl WalletsContent {
|
|||
self.creation_content.can_go_back()
|
||||
}
|
||||
|
||||
/// Handle data from deeplink or opened file.
|
||||
pub 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;
|
||||
}
|
||||
// Close network panel on single panel mode.
|
||||
if !Content::is_dual_panel_mode(ui) && Content::is_network_panel_open() {
|
||||
Content::toggle_network_panel();
|
||||
}
|
||||
// Pass data to opened selected wallet or show wallets selection.
|
||||
if self.wallets.is_selected_open() {
|
||||
if wallets_size == 1 {
|
||||
self.wallet_content = WalletContent::new(data);
|
||||
} else {
|
||||
self.show_wallet_selection_modal(data);
|
||||
}
|
||||
} else {
|
||||
if wallets_size == 1 {
|
||||
self.show_opening_modal(self.wallets.list()[0].get_config().id, data, cb);
|
||||
} else {
|
||||
self.show_wallet_selection_modal(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_wallet_selection_modal(&mut self, data: Option<String>) {
|
||||
self.wallet_selection_content = Some(WalletsModal::new(None, data, true));
|
||||
// Show wallet selection modal.
|
||||
Modal::new(SELECT_WALLET_MODAL)
|
||||
.position(ModalPosition::Center)
|
||||
.title(t!("network_settings.choose_wallet"))
|
||||
.show();
|
||||
}
|
||||
|
||||
/// Handle Back key event returning `false` when event was handled.
|
||||
pub fn on_back(&mut self) -> bool {
|
||||
let can_go_back = self.creation_content.can_go_back();
|
||||
if can_go_back {
|
||||
self.creation_content.back();
|
||||
return false
|
||||
} else {
|
||||
if self.wallets.is_selected_open() {
|
||||
self.wallets.select(None);
|
||||
return false
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Draw [`TitlePanel`] content.
|
||||
fn title_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
|
@ -383,8 +456,7 @@ impl WalletsContent {
|
|||
// Check if wallet reopen is needed.
|
||||
if !wallet.is_open() && wallet.reopen_needed() {
|
||||
wallet.set_reopen(false);
|
||||
self.wallets.select(Some(wallet.get_config().id));
|
||||
self.show_open_wallet_modal(cb);
|
||||
self.show_opening_modal(wallet.get_config().id, None, cb);
|
||||
}
|
||||
// Draw wallet list item.
|
||||
self.wallet_item_ui(ui, wallet, cb);
|
||||
|
@ -420,8 +492,7 @@ 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.wallets.select(Some(id));
|
||||
self.show_open_wallet_modal(cb);
|
||||
self.show_opening_modal(id, None, cb);
|
||||
});
|
||||
// Show button to select connection if not syncing.
|
||||
if !wallet.syncing() {
|
||||
|
@ -435,7 +506,7 @@ impl WalletsContent {
|
|||
// Show button to select opened wallet.
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, None, || {
|
||||
self.wallets.select(Some(id));
|
||||
self.wallet_content = WalletContent::default();
|
||||
self.wallet_content = WalletContent::new(None);
|
||||
});
|
||||
}
|
||||
// Show button to close opened wallet.
|
||||
|
@ -455,7 +526,7 @@ impl WalletsContent {
|
|||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
// Setup wallet name text.
|
||||
// Show wallet name text.
|
||||
let name_color = if is_selected {
|
||||
Colors::white_or_black(true)
|
||||
} else {
|
||||
|
@ -466,42 +537,11 @@ impl WalletsContent {
|
|||
View::ellipsize_text(ui, config.name, 18.0, name_color);
|
||||
});
|
||||
|
||||
// Setup wallet status text.
|
||||
let status_text = if wallet.is_open() {
|
||||
if wallet.sync_error() {
|
||||
format!("{} {}", WARNING_CIRCLE, t!("error"))
|
||||
} else if wallet.is_closing() {
|
||||
format!("{} {}", SPINNER, t!("wallets.closing"))
|
||||
} else if wallet.is_repairing() {
|
||||
let repair_progress = wallet.repairing_progress();
|
||||
if repair_progress == 0 {
|
||||
format!("{} {}", SPINNER, t!("wallets.checking"))
|
||||
} else {
|
||||
format!("{} {}: {}%",
|
||||
SPINNER,
|
||||
t!("wallets.checking"),
|
||||
repair_progress)
|
||||
}
|
||||
} else if wallet.syncing() {
|
||||
let info_progress = wallet.info_sync_progress();
|
||||
if info_progress == 100 || info_progress == 0 {
|
||||
format!("{} {}", SPINNER, t!("wallets.loading"))
|
||||
} else {
|
||||
format!("{} {}: {}%",
|
||||
SPINNER,
|
||||
t!("wallets.loading"),
|
||||
info_progress)
|
||||
}
|
||||
} else {
|
||||
format!("{} {}", FOLDER_OPEN, t!("wallets.unlocked"))
|
||||
}
|
||||
} else {
|
||||
format!("{} {}", FOLDER_LOCK, t!("wallets.locked"))
|
||||
};
|
||||
View::ellipsize_text(ui, status_text, 15.0, Colors::text(false));
|
||||
// Show wallet status text.
|
||||
View::ellipsize_text(ui, status_text(wallet), 15.0, Colors::text(false));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup wallet connection text.
|
||||
// Show wallet connection text.
|
||||
let conn_text = if let Some(conn) = wallet.get_current_ext_conn() {
|
||||
format!("{} {}", GLOBE_SIMPLE, conn.url)
|
||||
} else {
|
||||
|
@ -525,11 +565,10 @@ impl WalletsContent {
|
|||
.show();
|
||||
}
|
||||
|
||||
/// Show [`Modal`] to open selected wallet.
|
||||
fn show_open_wallet_modal(&mut self, cb: &dyn PlatformCallbacks) {
|
||||
// Reset modal values.
|
||||
self.pass_edit = String::from("");
|
||||
self.wrong_pass = false;
|
||||
/// Show [`Modal`] to select and open wallet.
|
||||
fn show_opening_modal(&mut self, id: i64, data: Option<String>, cb: &dyn PlatformCallbacks) {
|
||||
self.wallets.select(Some(id));
|
||||
self.open_wallet_content = Some(OpenWalletModal::new(data));
|
||||
// Show modal.
|
||||
Modal::new(OPEN_WALLET_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
|
@ -537,100 +576,6 @@ impl WalletsContent {
|
|||
.show();
|
||||
cb.show_keyboard();
|
||||
}
|
||||
|
||||
/// Draw wallet opening [`Modal`] content.
|
||||
fn open_wallet_modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show password input.
|
||||
let mut pass_edit_opts = TextEditOptions::new(Id::from(modal.id)).password();
|
||||
View::text_edit(ui, cb, &mut self.pass_edit, &mut pass_edit_opts);
|
||||
|
||||
// Show information when password is empty.
|
||||
if self.pass_edit.is_empty() {
|
||||
self.wrong_pass = false;
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(t!("wallets.pass_empty"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
} else if self.wrong_pass {
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(t!("wallets.wrong_pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
// Close modal.
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
// Callback for button to continue.
|
||||
let mut on_continue = || {
|
||||
if self.pass_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
match self.wallets.open_selected(&self.pass_edit) {
|
||||
Ok(_) => {
|
||||
// Clear values.
|
||||
self.pass_edit = "".to_string();
|
||||
self.wrong_pass = false;
|
||||
// Close modal.
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
// Reset wallet content.
|
||||
self.wallet_content = WalletContent::default();
|
||||
}
|
||||
Err(_) => self.wrong_pass = true
|
||||
}
|
||||
};
|
||||
|
||||
// Continue on Enter key press.
|
||||
View::on_enter_key(ui, || {
|
||||
(on_continue)();
|
||||
});
|
||||
|
||||
View::button(ui, t!("continue"), Colors::white_or_black(false), on_continue);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
|
||||
/// Handle Back key event.
|
||||
/// Return `false` when event was handled.
|
||||
pub fn on_back(&mut self) -> bool {
|
||||
let can_go_back = self.creation_content.can_go_back();
|
||||
if can_go_back {
|
||||
self.creation_content.back();
|
||||
return false
|
||||
} else {
|
||||
if self.wallets.is_selected_open() {
|
||||
self.wallets.select(None);
|
||||
return false
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if it's possible to show [`WalletsContent`] and [`WalletContent`] panels at same time.
|
||||
|
|
|
@ -16,4 +16,7 @@ mod conn;
|
|||
pub use conn::*;
|
||||
|
||||
mod wallets;
|
||||
pub use wallets::*;
|
||||
pub use wallets::*;
|
||||
|
||||
mod open;
|
||||
pub use open::*;
|
121
src/gui/views/wallets/modals/open.rs
Normal file
121
src/gui/views/wallets/modals/open.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
// Copyright 2024 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Id, RichText};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::views::types::TextEditOptions;
|
||||
use crate::wallet::WalletList;
|
||||
|
||||
/// Wallet opening [`Modal`] content.
|
||||
pub struct OpenWalletModal {
|
||||
/// Password to open wallet.
|
||||
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 {
|
||||
Self {
|
||||
pass_edit: "".to_string(),
|
||||
wrong_pass: false,
|
||||
data,
|
||||
}
|
||||
}
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
wallets: &mut WalletList,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
mut on_continue: impl FnMut(Option<String>)) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show password input.
|
||||
let mut pass_edit_opts = TextEditOptions::new(Id::from(modal.id)).password();
|
||||
View::text_edit(ui, cb, &mut self.pass_edit, &mut pass_edit_opts);
|
||||
|
||||
// Show information when password is empty.
|
||||
if self.pass_edit.is_empty() {
|
||||
self.wrong_pass = false;
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(t!("wallets.pass_empty"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
} else if self.wrong_pass {
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(t!("wallets.wrong_pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
// Close modal.
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
// Callback for button to continue.
|
||||
let mut on_continue = || {
|
||||
if self.pass_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
match wallets.open_selected(&self.pass_edit) {
|
||||
Ok(_) => {
|
||||
// Clear values.
|
||||
self.pass_edit = "".to_string();
|
||||
self.wrong_pass = false;
|
||||
// Close modal.
|
||||
cb.hide_keyboard();
|
||||
modal.close();
|
||||
on_continue(self.data.clone());
|
||||
}
|
||||
Err(_) => self.wrong_pass = true
|
||||
}
|
||||
};
|
||||
|
||||
// Continue on Enter key press.
|
||||
View::on_enter_key(ui, || {
|
||||
(on_continue)();
|
||||
});
|
||||
|
||||
View::button(ui, t!("continue"), Colors::white_or_black(false), on_continue);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -16,29 +16,53 @@ use egui::scroll_area::ScrollBarVisibility;
|
|||
use egui::{Align, Layout, RichText, ScrollArea};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK, CHECK_FAT, COMPUTER_TOWER, GLOBE_SIMPLE, PLUGS_CONNECTED};
|
||||
use crate::gui::icons::{CHECK, CHECK_FAT, COMPUTER_TOWER, FOLDER_OPEN, GLOBE_SIMPLE, PLUGS_CONNECTED};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::modals::OpenWalletModal;
|
||||
use crate::gui::views::wallets::wallet::types::status_text;
|
||||
use crate::wallet::{Wallet, WalletList};
|
||||
|
||||
/// Wallet list [`Modal`] content
|
||||
pub struct WalletsModal {
|
||||
/// Selected wallet id.
|
||||
selected: Option<i64>
|
||||
selected: Option<i64>,
|
||||
|
||||
/// Optional data to pass after wallet selection.
|
||||
data: Option<String>,
|
||||
|
||||
/// Flag to check if wallet can be opened from the list.
|
||||
can_open: bool,
|
||||
/// Wallet opening content.
|
||||
open_wallet_content: Option<OpenWalletModal>,
|
||||
}
|
||||
|
||||
impl WalletsModal {
|
||||
pub fn new(selected: Option<i64>) -> Self {
|
||||
Self {
|
||||
selected,
|
||||
}
|
||||
/// Create new content instance.
|
||||
pub fn new(selected: Option<i64>, data: Option<String>, can_open: bool) -> Self {
|
||||
Self { selected, data, can_open, open_wallet_content: None }
|
||||
}
|
||||
|
||||
/// Draw [`Modal`] content.
|
||||
/// Draw content.
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
wallets: &WalletList,
|
||||
mut on_select: impl FnMut(i64)) {
|
||||
wallets: &mut WalletList,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
mut on_select: impl FnMut(i64, Option<String>)) {
|
||||
// Draw wallet opening content if requested.
|
||||
if let Some(open_content) = self.open_wallet_content.as_mut() {
|
||||
open_content.ui(ui, modal, wallets, cb, |data| {
|
||||
modal.close();
|
||||
if let Some(id) = self.selected {
|
||||
on_select(id, data);
|
||||
}
|
||||
self.data = None;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
ui.add_space(4.0);
|
||||
ScrollArea::vertical()
|
||||
.max_height(373.0)
|
||||
|
@ -48,10 +72,12 @@ impl WalletsModal {
|
|||
.show(ui, |ui| {
|
||||
ui.add_space(2.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
for wallet in wallets.list() {
|
||||
let data = self.data.clone();
|
||||
for wallet in wallets.clone().list() {
|
||||
// Draw wallet list item.
|
||||
self.wallet_item_ui(ui, wallet, modal, |id| {
|
||||
on_select(id);
|
||||
self.wallet_item_ui(ui, wallet, wallets, |id| {
|
||||
modal.close();
|
||||
on_select(id, data.clone());
|
||||
});
|
||||
ui.add_space(5.0);
|
||||
}
|
||||
|
@ -65,18 +91,19 @@ impl WalletsModal {
|
|||
// Show button to close modal.
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
self.data = None;
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw wallet list item.
|
||||
/// Draw wallet list item with provided callback on select.
|
||||
fn wallet_item_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
mut on_select: impl FnMut(i64)) {
|
||||
wallets: &mut WalletList,
|
||||
mut select: impl FnMut(i64)) {
|
||||
let config = wallet.get_config();
|
||||
let id = config.id;
|
||||
|
||||
|
@ -87,16 +114,34 @@ impl WalletsModal {
|
|||
ui.painter().rect(rect, rounding, Colors::fill(), View::hover_stroke());
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to select wallet.
|
||||
let current = self.selected.unwrap_or(0) == id;
|
||||
if current {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green()));
|
||||
} else {
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || {
|
||||
on_select(id);
|
||||
modal.close();
|
||||
if self.can_open {
|
||||
// Show button to select or open closed wallet.
|
||||
let icon = if wallet.is_open() {
|
||||
CHECK
|
||||
} else {
|
||||
FOLDER_OPEN
|
||||
};
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), icon, None, || {
|
||||
wallets.select(Some(id));
|
||||
if wallet.is_open() {
|
||||
select(id);
|
||||
} else {
|
||||
self.selected = wallets.selected_id;
|
||||
Modal::change_position(ModalPosition::CenterTop);
|
||||
self.open_wallet_content = Some(OpenWalletModal::new(self.data.clone()));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Draw button to select wallet.
|
||||
let current = self.selected.unwrap_or(0) == id;
|
||||
if current {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green()));
|
||||
} else {
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), CHECK, None, || {
|
||||
select(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
|
@ -104,13 +149,13 @@ impl WalletsModal {
|
|||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(3.0);
|
||||
// Setup wallet name text.
|
||||
// Show wallet name text.
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
View::ellipsize_text(ui, config.name, 18.0, Colors::title(false));
|
||||
});
|
||||
|
||||
// Setup wallet connection text.
|
||||
// Show wallet connection text.
|
||||
let conn = if let Some(conn) = wallet.get_current_ext_conn() {
|
||||
format!("{} {}", GLOBE_SIMPLE, conn.url)
|
||||
} else {
|
||||
|
@ -119,14 +164,20 @@ impl WalletsModal {
|
|||
View::ellipsize_text(ui, conn, 15.0, Colors::text(false));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup wallet API text.
|
||||
let address = if let Some(port) = config.api_port {
|
||||
format!("127.0.0.1:{}", port)
|
||||
// Show wallet API text or open status.
|
||||
if self.can_open {
|
||||
ui.label(RichText::new(status_text(wallet))
|
||||
.size(15.0)
|
||||
.color(Colors::gray()));
|
||||
} else {
|
||||
"-".to_string()
|
||||
};
|
||||
let api_text = format!("{} {}", PLUGS_CONNECTED, address);
|
||||
ui.label(RichText::new(api_text).size(15.0).color(Colors::gray()));
|
||||
let address = if let Some(port) = config.api_port {
|
||||
format!("127.0.0.1:{}", port)
|
||||
} else {
|
||||
"-".to_string()
|
||||
};
|
||||
let api_text = format!("{} {}", PLUGS_CONNECTED, address);
|
||||
ui.label(RichText::new(api_text).size(15.0).color(Colors::gray()));
|
||||
}
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -35,7 +35,6 @@ use crate::wallet::types::{WalletAccount, WalletData};
|
|||
pub struct WalletContent {
|
||||
/// List of wallet accounts for [`Modal`].
|
||||
accounts: Vec<WalletAccount>,
|
||||
|
||||
/// Flag to check if account is creating.
|
||||
account_creating: bool,
|
||||
/// Account label [`Modal`] value.
|
||||
|
@ -49,21 +48,7 @@ pub struct WalletContent {
|
|||
qr_scan_result: Option<QrScanResult>,
|
||||
|
||||
/// Current tab content to show.
|
||||
pub current_tab: Box<dyn WalletTab>
|
||||
}
|
||||
|
||||
impl Default for WalletContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
accounts: vec![],
|
||||
account_creating: false,
|
||||
account_label_edit: "".to_string(),
|
||||
account_creation_error: false,
|
||||
camera_content: CameraContent::default(),
|
||||
qr_scan_result: None,
|
||||
current_tab: Box::new(WalletTransactions::default())
|
||||
}
|
||||
}
|
||||
pub current_tab: Box<dyn WalletTab>,
|
||||
}
|
||||
|
||||
/// Identifier for account list [`Modal`].
|
||||
|
@ -73,6 +58,24 @@ const ACCOUNT_LIST_MODAL: &'static str = "account_list_modal";
|
|||
const QR_CODE_SCAN_MODAL: &'static str = "qr_code_scan_modal";
|
||||
|
||||
impl WalletContent {
|
||||
/// Create new instance with optional data.
|
||||
pub fn new(data: Option<String>) -> Self {
|
||||
let mut content = Self {
|
||||
accounts: vec![],
|
||||
account_creating: false,
|
||||
account_label_edit: "".to_string(),
|
||||
account_creation_error: false,
|
||||
camera_content: CameraContent::default(),
|
||||
qr_scan_result: None,
|
||||
current_tab: Box::new(WalletTransactions::default()),
|
||||
};
|
||||
// Provide data to messages.
|
||||
if data.is_some() {
|
||||
content.current_tab = Box::new(WalletMessages::new(data));
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
/// Draw wallet content.
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
|
|
|
@ -33,13 +33,16 @@ use crate::wallet::Wallet;
|
|||
|
||||
/// Slatepack messages interaction tab content.
|
||||
pub struct WalletMessages {
|
||||
/// Flag to check if it's first content draw.
|
||||
first_draw: bool,
|
||||
|
||||
/// Slatepacks message input text.
|
||||
message_edit: String,
|
||||
/// Flag to check if message request is loading.
|
||||
message_loading: bool,
|
||||
/// Error on finalization, parse or response creation.
|
||||
message_error: String,
|
||||
/// Parsed message result with finalization flag and transaction.
|
||||
/// Parsed message result.
|
||||
message_result: Arc<RwLock<Option<(Slate, Result<WalletTransaction, Error>)>>>,
|
||||
|
||||
/// Wallet transaction [`Modal`] content.
|
||||
|
@ -111,6 +114,7 @@ impl WalletMessages {
|
|||
/// Create new content instance, put message into input if provided.
|
||||
pub fn new(message: Option<String>) -> Self {
|
||||
Self {
|
||||
first_draw: true,
|
||||
message_edit: message.unwrap_or("".to_string()),
|
||||
message_loading: false,
|
||||
message_error: "".to_string(),
|
||||
|
@ -128,6 +132,14 @@ impl WalletMessages {
|
|||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
if self.first_draw {
|
||||
// Parse provided message on first draw.
|
||||
if !self.message_edit.is_empty() {
|
||||
self.parse_message(wallet);
|
||||
}
|
||||
self.first_draw = false;
|
||||
}
|
||||
|
||||
ui.add_space(3.0);
|
||||
|
||||
// Show creation of request to send or receive funds.
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::gui::icons::{FOLDER_LOCK, FOLDER_OPEN, SPINNER, WARNING_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
|
@ -48,4 +49,39 @@ impl WalletTabType {
|
|||
WalletTabType::Settings => t!("wallets.settings")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get wallet status text.
|
||||
pub fn status_text(wallet: &Wallet) -> String {
|
||||
if wallet.is_open() {
|
||||
if wallet.sync_error() {
|
||||
format!("{} {}", WARNING_CIRCLE, t!("error"))
|
||||
} else if wallet.is_closing() {
|
||||
format!("{} {}", SPINNER, t!("wallets.closing"))
|
||||
} else if wallet.is_repairing() {
|
||||
let repair_progress = wallet.repairing_progress();
|
||||
if repair_progress == 0 {
|
||||
format!("{} {}", SPINNER, t!("wallets.checking"))
|
||||
} else {
|
||||
format!("{} {}: {}%",
|
||||
SPINNER,
|
||||
t!("wallets.checking"),
|
||||
repair_progress)
|
||||
}
|
||||
} else if wallet.syncing() {
|
||||
let info_progress = wallet.info_sync_progress();
|
||||
if info_progress == 100 || info_progress == 0 {
|
||||
format!("{} {}", SPINNER, t!("wallets.loading"))
|
||||
} else {
|
||||
format!("{} {}: {}%",
|
||||
SPINNER,
|
||||
t!("wallets.loading"),
|
||||
info_progress)
|
||||
}
|
||||
} else {
|
||||
format!("{} {}", FOLDER_OPEN, t!("wallets.unlocked"))
|
||||
}
|
||||
} else {
|
||||
format!("{} {}", FOLDER_LOCK, t!("wallets.locked"))
|
||||
}
|
||||
}
|
151
src/main.rs
151
src/main.rs
|
@ -29,6 +29,23 @@ fn real_main() {
|
|||
.parse_default_env()
|
||||
.init();
|
||||
|
||||
// Handle file path argument passing.
|
||||
let args: Vec<_> = std::env::args().collect();
|
||||
let mut data = None;
|
||||
if args.len() > 1 {
|
||||
let path = std::path::PathBuf::from(&args[1]);
|
||||
let content = match std::fs::read_to_string(path) {
|
||||
Ok(s) => Some(s),
|
||||
Err(_) => None
|
||||
};
|
||||
data = content
|
||||
}
|
||||
|
||||
// Check if another app instance already running.
|
||||
if is_app_running(data.clone()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup callback on panic crash.
|
||||
std::panic::set_hook(Box::new(|info| {
|
||||
let backtrace = backtrace::Backtrace::new();
|
||||
|
@ -50,22 +67,61 @@ fn real_main() {
|
|||
|
||||
// Start GUI.
|
||||
match std::panic::catch_unwind(|| {
|
||||
start_desktop_gui();
|
||||
start_desktop_gui(data);
|
||||
}) {
|
||||
Ok(_) => {}
|
||||
Err(e) => println!("{:?}", e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Start GUI with Desktop related setup.
|
||||
/// Check if application is already running to pass extra data.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn start_desktop_gui() {
|
||||
fn is_app_running(data: Option<String>) -> bool {
|
||||
use tor_rtcompat::BlockOn;
|
||||
let runtime = tor_rtcompat::tokio::TokioNativeTlsRuntime::create().unwrap();
|
||||
let res: Result<(), Box<dyn std::error::Error>> = runtime
|
||||
.block_on(async {
|
||||
use interprocess::local_socket::{
|
||||
tokio::{prelude::*, Stream},
|
||||
GenericFilePath
|
||||
};
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
try_join,
|
||||
};
|
||||
|
||||
let socket_path = grim::Settings::socket_path();
|
||||
let name = socket_path.to_fs_name::<GenericFilePath>()?;
|
||||
// Connect to running application socket.
|
||||
let conn = Stream::connect(name).await?;
|
||||
|
||||
let (rec, mut sen) = conn.split();
|
||||
let mut rec = BufReader::new(rec);
|
||||
let data = data.unwrap_or("".to_string());
|
||||
let mut buffer = String::with_capacity(data.len());
|
||||
|
||||
// Send extra data to socket.
|
||||
let send = sen.write_all(data.as_bytes());
|
||||
let recv = rec.read_line(&mut buffer);
|
||||
try_join!(send, recv)?;
|
||||
|
||||
drop((rec, sen));
|
||||
Ok(())
|
||||
});
|
||||
return match res {
|
||||
Ok(_) => true,
|
||||
Err(_) => false
|
||||
}
|
||||
}
|
||||
|
||||
/// Start GUI with Desktop related setup passing extra data from opening.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn start_desktop_gui(data: Option<String>) {
|
||||
use grim::AppConfig;
|
||||
use dark_light::Mode;
|
||||
|
||||
let platform = grim::gui::platform::Desktop::default();
|
||||
|
||||
// Setup system theme if not set.
|
||||
if let None = AppConfig::dark_theme() {
|
||||
let dark = match dark_light::detect() {
|
||||
|
@ -76,12 +132,11 @@ fn start_desktop_gui() {
|
|||
AppConfig::set_dark_theme(dark);
|
||||
}
|
||||
|
||||
// Setup window size.
|
||||
let (width, height) = AppConfig::window_size();
|
||||
|
||||
let mut viewport = egui::ViewportBuilder::default()
|
||||
.with_min_inner_size([AppConfig::MIN_WIDTH, AppConfig::MIN_HEIGHT])
|
||||
.with_inner_size([width, height]);
|
||||
|
||||
// Setup an icon.
|
||||
if let Ok(icon) = eframe::icon_data::from_png_bytes(include_bytes!("../img/icon.png")) {
|
||||
viewport = viewport.with_icon(std::sync::Arc::new(icon));
|
||||
|
@ -93,6 +148,7 @@ fn start_desktop_gui() {
|
|||
// Setup window decorations.
|
||||
let is_mac = egui::os::OperatingSystem::from_target_os() == egui::os::OperatingSystem::Mac;
|
||||
viewport = viewport
|
||||
.with_window_level(egui::WindowLevel::Normal)
|
||||
.with_fullsize_content_view(true)
|
||||
.with_title_shown(false)
|
||||
.with_titlebar_buttons_shown(false)
|
||||
|
@ -112,8 +168,18 @@ fn start_desktop_gui() {
|
|||
eframe::Renderer::Wgpu
|
||||
};
|
||||
|
||||
let mut platform = grim::gui::platform::Desktop::new(data);
|
||||
|
||||
// Start app socket at separate thread.
|
||||
let socket_pl = platform.clone();
|
||||
platform = socket_pl.clone();
|
||||
std::thread::spawn(move || {
|
||||
start_app_socket(socket_pl);
|
||||
});
|
||||
|
||||
// Start GUI.
|
||||
match grim::start(options.clone(), grim::app_creator(grim::gui::App::new(platform.clone()))) {
|
||||
let app = grim::gui::App::new(platform.clone());
|
||||
match grim::start(options.clone(), grim::app_creator(app)) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
if win {
|
||||
|
@ -121,7 +187,9 @@ fn start_desktop_gui() {
|
|||
}
|
||||
// Start with another renderer on error.
|
||||
options.renderer = eframe::Renderer::Glow;
|
||||
match grim::start(options, grim::app_creator(grim::gui::App::new(platform))) {
|
||||
|
||||
let app = grim::gui::App::new(platform);
|
||||
match grim::start(options, grim::app_creator(app)) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
panic!("{}", e);
|
||||
|
@ -129,4 +197,69 @@ fn start_desktop_gui() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start socket that handles data for single application instance.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||
use tor_rtcompat::BlockOn;
|
||||
let runtime = tor_rtcompat::tokio::TokioNativeTlsRuntime::create().unwrap();
|
||||
let _: Result<_, _> = runtime
|
||||
.block_on(async {
|
||||
use interprocess::local_socket::{
|
||||
tokio::{prelude::*, Stream},
|
||||
GenericFilePath, Listener, ListenerOptions,
|
||||
};
|
||||
use std::io;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
try_join,
|
||||
};
|
||||
|
||||
// Handle incoming connection.
|
||||
async fn handle_conn(conn: Stream)
|
||||
-> io::Result<String> {
|
||||
let mut rec = BufReader::new(&conn);
|
||||
let mut sen = &conn;
|
||||
|
||||
let mut buffer = String::new();
|
||||
let send = sen.write_all(b"");
|
||||
let recv = rec.read_line(&mut buffer);
|
||||
|
||||
// Read data and send answer.
|
||||
try_join!(recv, send)?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
let socket_path = grim::Settings::socket_path();
|
||||
std::fs::remove_file(socket_path.clone()).unwrap();
|
||||
let name = socket_path.to_fs_name::<GenericFilePath>()?;
|
||||
let opts = ListenerOptions::new().name(name);
|
||||
|
||||
// Create socket listener.
|
||||
let listener = match opts.create_tokio() {
|
||||
Err(e) if e.kind() == io::ErrorKind::AddrInUse => {
|
||||
eprintln!("Socket file is occupied.");
|
||||
return Err::<Listener, io::Error>(e);
|
||||
}
|
||||
x => x?,
|
||||
};
|
||||
|
||||
// Handle connections.
|
||||
loop {
|
||||
let conn = match listener.accept().await {
|
||||
Ok(c) => c,
|
||||
Err(_) => continue
|
||||
};
|
||||
let res = handle_conn(conn).await;
|
||||
match res {
|
||||
Ok(data) => {
|
||||
platform.on_data(data)
|
||||
},
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -141,6 +141,13 @@ impl Settings {
|
|||
path
|
||||
}
|
||||
|
||||
/// Get desktop application socket path.
|
||||
pub fn socket_path() -> String {
|
||||
let mut socket_path = Self::base_path(None);
|
||||
socket_path.push("grim.socket");
|
||||
socket_path.to_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// Get configuration file path from provided name and sub-directory if needed.
|
||||
pub fn config_path(config_name: &str, sub_dir: Option<String>) -> PathBuf {
|
||||
let mut path = Self::base_path(sub_dir);
|
||||
|
|
|
@ -18,7 +18,8 @@ use grin_wallet_libwallet::Error;
|
|||
use crate::AppConfig;
|
||||
use crate::wallet::{Wallet, WalletConfig};
|
||||
|
||||
/// Wrapper for [`Wallet`] list.
|
||||
/// [`Wallet`] list container.
|
||||
#[derive(Clone)]
|
||||
pub struct WalletList {
|
||||
/// List of wallets for [`ChainTypes::Mainnet`].
|
||||
pub main_list: Vec<Wallet>,
|
||||
|
|
Loading…
Reference in a new issue