mirror of
https://code.gri.mw/GUI/grim.git
synced 2026-07-04 05:57:29 +00:00
build: v0.3.6, format code
This commit is contained in:
Generated
+12
-1
@@ -3900,7 +3900,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "grim"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"android-activity",
|
||||
"android_logger",
|
||||
@@ -5690,6 +5690,7 @@ version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
@@ -6945,6 +6946,15 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.6.0+3.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.111"
|
||||
@@ -6953,6 +6963,7 @@ checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "grim"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
authors = ["Ardocrat <ardocrat@gri.mw>"]
|
||||
description = "Cross-platform GUI for Grin with focus on usability and availability to be used by anyone, anywhere."
|
||||
license = "Apache-2.0"
|
||||
@@ -92,8 +92,8 @@ uuid = { version = "0.8.2", features = ["v4"] }
|
||||
num-bigint = "0.4.6"
|
||||
|
||||
## tor
|
||||
arti-client = { version = "0.42.0", features = ["pt-client", "onion-service-service", "onion-service-client"] }
|
||||
tor-rtcompat = "0.42.0"
|
||||
arti-client = { version = "0.42.0", features = ["static", "pt-client", "onion-service-service", "onion-service-client"] }
|
||||
tor-rtcompat = { version = "0.42.0", features = ["static"] }
|
||||
tor-config = "0.42.0"
|
||||
fs-mistrust = "0.14.1"
|
||||
tor-hsservice = "0.42.0"
|
||||
|
||||
@@ -12,7 +12,7 @@ android {
|
||||
minSdk 24
|
||||
targetSdk 36
|
||||
versionCode 5
|
||||
versionName "0.3.5"
|
||||
versionName "0.3.6"
|
||||
}
|
||||
|
||||
lint {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.5</string>
|
||||
<string>0.3.6</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
|
||||
+49
-37
@@ -12,17 +12,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use egui::epaint::RectShape;
|
||||
use egui::{Align, Context, CornerRadius, CursorIcon, LayerId, Layout, Modifiers, Order, ResizeDirection, Stroke, StrokeKind, UiBuilder, ViewportCommand};
|
||||
use egui::{
|
||||
Align, Context, CornerRadius, CursorIcon, LayerId, Layout, Modifiers, Order, ResizeDirection,
|
||||
Stroke, StrokeKind, UiBuilder, ViewportCommand,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
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::AppConfig;
|
||||
|
||||
lazy_static! {
|
||||
/// State to check if platform Back button was pressed.
|
||||
@@ -40,7 +43,7 @@ pub struct App<Platform> {
|
||||
/// Last window resize direction.
|
||||
resize_direction: Option<ResizeDirection>,
|
||||
/// Flag to check if it's first draw.
|
||||
first_draw: bool
|
||||
first_draw: bool,
|
||||
}
|
||||
|
||||
impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
@@ -49,7 +52,7 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
platform,
|
||||
content: Content::default(),
|
||||
resize_direction: None,
|
||||
first_draw: true
|
||||
first_draw: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +76,11 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
|
||||
// Handle Esc keyboard key event and platform Back button key event.
|
||||
let back_pressed = BACK_BUTTON_PRESSED.load(Ordering::Relaxed);
|
||||
if back_pressed || ctx.input_mut(|i| i.consume_key(Modifiers::NONE, egui::Key::Escape) ||
|
||||
i.consume_key(Modifiers::NONE, egui::Key::BrowserBack)) {
|
||||
if back_pressed
|
||||
|| ctx.input_mut(|i| {
|
||||
i.consume_key(Modifiers::NONE, egui::Key::Escape)
|
||||
|| i.consume_key(Modifiers::NONE, egui::Key::BrowserBack)
|
||||
}) {
|
||||
// Pass event to content.
|
||||
self.content.on_back(ctx, &self.platform);
|
||||
if back_pressed {
|
||||
@@ -107,9 +113,8 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
})
|
||||
.show(ctx, |ui| {
|
||||
if View::is_desktop() {
|
||||
let is_fullscreen = ui.ctx().input(|i| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
let is_fullscreen =
|
||||
ui.ctx().input(|i| i.viewport().fullscreen.unwrap_or(false));
|
||||
let os = egui::os::OperatingSystem::from_target_os();
|
||||
match os {
|
||||
egui::os::OperatingSystem::Mac => {
|
||||
@@ -133,8 +138,9 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
});
|
||||
|
||||
// Check if desktop window was focused after requested attention.
|
||||
if self.platform.user_attention_required() &&
|
||||
ctx.input(|i| i.viewport().focused.unwrap_or(true)) {
|
||||
if self.platform.user_attention_required()
|
||||
&& ctx.input(|i| i.viewport().focused.unwrap_or(true))
|
||||
{
|
||||
self.platform.clear_user_attention();
|
||||
}
|
||||
|
||||
@@ -147,9 +153,10 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
false
|
||||
};
|
||||
if keyboard_showing {
|
||||
ctx.move_to_top(
|
||||
LayerId::new(Order::Middle, egui::Id::new(KeyboardContent::WINDOW_ID))
|
||||
);
|
||||
ctx.move_to_top(LayerId::new(
|
||||
Order::Middle,
|
||||
egui::Id::new(KeyboardContent::WINDOW_ID),
|
||||
));
|
||||
}
|
||||
}
|
||||
// Reset keyboard state for newly opened modal.
|
||||
@@ -168,11 +175,13 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
r.min.y += Content::WINDOW_TITLE_HEIGHT + TitlePanel::HEIGHT;
|
||||
r
|
||||
};
|
||||
let content_bg = RectShape::new(content_bg_rect,
|
||||
let content_bg = RectShape::new(
|
||||
content_bg_rect,
|
||||
CornerRadius::ZERO,
|
||||
Colors::fill_lite(),
|
||||
View::default_stroke(),
|
||||
StrokeKind::Outside);
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
// Draw content background.
|
||||
ui.painter().add(content_bg);
|
||||
|
||||
@@ -194,9 +203,8 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
rect.min.y += Content::WINDOW_TITLE_HEIGHT;
|
||||
rect
|
||||
};
|
||||
let mut content_ui = ui.new_child(UiBuilder::new()
|
||||
.max_rect(content_rect)
|
||||
.layout(*ui.layout()));
|
||||
let mut content_ui =
|
||||
ui.new_child(UiBuilder::new().max_rect(content_rect).layout(*ui.layout()));
|
||||
// Draw main content.
|
||||
self.content.ui(&mut content_ui, &self.platform);
|
||||
});
|
||||
@@ -242,7 +250,9 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
r
|
||||
};
|
||||
let is_mac = egui::os::OperatingSystem::from_target_os() == egui::os::OperatingSystem::Mac;
|
||||
let window_title_bg = RectShape::new(title_bg_rect, if is_fullscreen || is_mac {
|
||||
let window_title_bg = RectShape::new(
|
||||
title_bg_rect,
|
||||
if is_fullscreen || is_mac {
|
||||
CornerRadius::ZERO
|
||||
} else {
|
||||
CornerRadius {
|
||||
@@ -251,7 +261,11 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
sw: 0.0 as u8,
|
||||
se: 0.0 as u8,
|
||||
}
|
||||
}, Colors::yellow_dark(), Stroke::new(1.0, Colors::STROKE), StrokeKind::Outside);
|
||||
},
|
||||
Colors::yellow_dark(),
|
||||
Stroke::new(1.0, Colors::STROKE),
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
// Draw title background.
|
||||
ui.painter().add(window_title_bg);
|
||||
|
||||
@@ -296,13 +310,10 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
});
|
||||
|
||||
// Draw fullscreen button.
|
||||
let fullscreen_icon = if is_fullscreen {
|
||||
ARROWS_IN
|
||||
} else {
|
||||
ARROWS_OUT
|
||||
};
|
||||
let fullscreen_icon = if is_fullscreen { ARROWS_IN } else { ARROWS_OUT };
|
||||
View::title_button_small(ui, fullscreen_icon, |ui| {
|
||||
ui.ctx().send_viewport_cmd(ViewportCommand::Fullscreen(!is_fullscreen));
|
||||
ui.ctx()
|
||||
.send_viewport_cmd(ViewportCommand::Fullscreen(!is_fullscreen));
|
||||
});
|
||||
|
||||
// Draw button to minimize window.
|
||||
@@ -312,19 +323,19 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
|
||||
// Draw application icon.
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
layout_size,
|
||||
Layout::left_to_right(Align::Center),
|
||||
|ui| {
|
||||
// Draw button to minimize window.
|
||||
let use_dark = AppConfig::dark_theme().unwrap_or(false);
|
||||
let theme_icon = if use_dark {
|
||||
SUN
|
||||
} else {
|
||||
MOON
|
||||
};
|
||||
let theme_icon = if use_dark { SUN } else { MOON };
|
||||
View::title_button_small(ui, theme_icon, |ui| {
|
||||
AppConfig::set_dark_theme(!use_dark);
|
||||
crate::setup_visuals(ui.ctx());
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -388,7 +399,8 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
if area_resp.dragged() {
|
||||
if self.resize_direction.is_none() {
|
||||
self.resize_direction = Some(direction.clone());
|
||||
ui.ctx().send_viewport_cmd(ViewportCommand::BeginResize(direction));
|
||||
ui.ctx()
|
||||
.send_viewport_cmd(ViewportCommand::BeginResize(direction));
|
||||
}
|
||||
}
|
||||
if area_resp.drag_stopped() {
|
||||
|
||||
+13
-42
@@ -38,8 +38,11 @@ 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, 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 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(26);
|
||||
@@ -90,17 +93,9 @@ impl Colors {
|
||||
|
||||
pub fn white_or_black(black_in_white: bool) -> Color32 {
|
||||
if use_dark() {
|
||||
if black_in_white {
|
||||
WHITE
|
||||
if black_in_white { WHITE } else { BLACK }
|
||||
} else {
|
||||
BLACK
|
||||
}
|
||||
} else {
|
||||
if black_in_white {
|
||||
BLACK
|
||||
} else {
|
||||
WHITE
|
||||
}
|
||||
if black_in_white { BLACK } else { WHITE }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,35 +132,19 @@ impl Colors {
|
||||
}
|
||||
|
||||
pub fn green() -> Color32 {
|
||||
if use_dark() {
|
||||
GREEN_DARK
|
||||
} else {
|
||||
GREEN
|
||||
}
|
||||
if use_dark() { GREEN_DARK } else { GREEN }
|
||||
}
|
||||
|
||||
pub fn red() -> Color32 {
|
||||
if use_dark() {
|
||||
RED_DARK
|
||||
} else {
|
||||
RED
|
||||
}
|
||||
if use_dark() { RED_DARK } else { RED }
|
||||
}
|
||||
|
||||
pub fn blue() -> Color32 {
|
||||
if use_dark() {
|
||||
BLUE_DARK
|
||||
} else {
|
||||
BLUE
|
||||
}
|
||||
if use_dark() { BLUE_DARK } else { BLUE }
|
||||
}
|
||||
|
||||
pub fn fill() -> Color32 {
|
||||
if use_dark() {
|
||||
FILL_DARK
|
||||
} else {
|
||||
FILL
|
||||
}
|
||||
if use_dark() { FILL_DARK } else { FILL }
|
||||
}
|
||||
|
||||
pub fn fill_deep() -> Color32 {
|
||||
@@ -185,11 +164,7 @@ impl Colors {
|
||||
}
|
||||
|
||||
pub fn checkbox() -> Color32 {
|
||||
if use_dark() {
|
||||
CHECKBOX_DARK
|
||||
} else {
|
||||
CHECKBOX
|
||||
}
|
||||
if use_dark() { CHECKBOX_DARK } else { CHECKBOX }
|
||||
}
|
||||
|
||||
pub fn text(always_light: bool) -> Color32 {
|
||||
@@ -217,11 +192,7 @@ impl Colors {
|
||||
}
|
||||
|
||||
pub fn gray() -> Color32 {
|
||||
if use_dark() {
|
||||
GRAY_DARK
|
||||
} else {
|
||||
GRAY
|
||||
}
|
||||
if use_dark() { GRAY_DARK } else { GRAY }
|
||||
}
|
||||
|
||||
pub fn stroke() -> Color32 {
|
||||
|
||||
+1
-2
@@ -12,13 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
mod app;
|
||||
pub use app::App;
|
||||
|
||||
mod colors;
|
||||
pub use colors::Colors;
|
||||
|
||||
pub mod icons;
|
||||
pub mod platform;
|
||||
pub mod views;
|
||||
pub mod icons;
|
||||
@@ -12,14 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use jni::JNIEnv;
|
||||
use jni::objects::{JByteArray, JObject, JString, JValue};
|
||||
@@ -50,9 +50,8 @@ impl Android {
|
||||
pub fn call_java_method(&self, name: &str, s: &str, a: &[JValue]) -> Option<jni::sys::jvalue> {
|
||||
let vm = unsafe { jni::JavaVM::from_raw(self.android_app.vm_as_ptr() as _) }.unwrap();
|
||||
let mut env = vm.attach_current_thread().unwrap();
|
||||
let activity = unsafe {
|
||||
JObject::from_raw(self.android_app.activity_as_ptr() as jni::sys::jobject)
|
||||
};
|
||||
let activity =
|
||||
unsafe { JObject::from_raw(self.android_app.activity_as_ptr() as jni::sys::jobject) };
|
||||
if let Ok(result) = env.call_method(activity, name, s, a) {
|
||||
return Some(result.as_jni().clone());
|
||||
}
|
||||
@@ -74,18 +73,24 @@ impl PlatformCallbacks for Android {
|
||||
let vm = unsafe { jni::JavaVM::from_raw(self.android_app.vm_as_ptr() as _) }.unwrap();
|
||||
let env = vm.attach_current_thread().unwrap();
|
||||
let arg_value = env.new_string(data).unwrap();
|
||||
let _ = self.call_java_method("copyText",
|
||||
let _ = self.call_java_method(
|
||||
"copyText",
|
||||
"(Ljava/lang/String;)V",
|
||||
&[JValue::Object(&JObject::from(arg_value))]);
|
||||
&[JValue::Object(&JObject::from(arg_value))],
|
||||
);
|
||||
}
|
||||
|
||||
fn get_string_from_buffer(&self) -> String {
|
||||
let result = self.call_java_method("pasteText", "()Ljava/lang/String;", &[]).unwrap();
|
||||
let result = self
|
||||
.call_java_method("pasteText", "()Ljava/lang/String;", &[])
|
||||
.unwrap();
|
||||
let vm = unsafe { jni::JavaVM::from_raw(self.android_app.vm_as_ptr() as _) }.unwrap();
|
||||
let mut env = vm.attach_current_thread().unwrap();
|
||||
let j_object: jni::sys::jobject = unsafe { result.l };
|
||||
let paste_data: String = unsafe {
|
||||
env.get_string(JString::from(JObject::from_raw(j_object)).as_ref()).unwrap().into()
|
||||
env.get_string(JString::from(JObject::from_raw(j_object)).as_ref())
|
||||
.unwrap()
|
||||
.into()
|
||||
};
|
||||
paste_data
|
||||
}
|
||||
@@ -145,9 +150,11 @@ impl PlatformCallbacks for Android {
|
||||
let vm = unsafe { jni::JavaVM::from_raw(self.android_app.vm_as_ptr() as _) }.unwrap();
|
||||
let env = vm.attach_current_thread().unwrap();
|
||||
let arg_value = env.new_string(file.to_str().unwrap()).unwrap();
|
||||
let _ = self.call_java_method("shareData",
|
||||
let _ = self.call_java_method(
|
||||
"shareData",
|
||||
"(Ljava/lang/String;)V",
|
||||
&[JValue::Object(&JObject::from(arg_value))]);
|
||||
&[JValue::Object(&JObject::from(arg_value))],
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -180,7 +187,7 @@ impl PlatformCallbacks for Android {
|
||||
let mut w_path = PICKED_FILE_PATH.write();
|
||||
let path = Some(w_path.clone().unwrap());
|
||||
*w_path = None;
|
||||
return path
|
||||
return path;
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -222,7 +229,7 @@ pub extern "C" fn Java_mw_gri_android_MainActivity_onCameraImage(
|
||||
pub extern "C" fn Java_mw_gri_android_MainActivity_onFilePick(
|
||||
_env: JNIEnv,
|
||||
_class: JObject,
|
||||
char: jni::sys::jstring
|
||||
char: jni::sys::jstring,
|
||||
) {
|
||||
use std::ops::Add;
|
||||
unsafe {
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{UserAttentionType, ViewportCommand, WindowLevel};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use rfd::FileDialog;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::thread;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
use lazy_static::lazy_static;
|
||||
use egui::{UserAttentionType, ViewportCommand, WindowLevel};
|
||||
use rfd::FileDialog;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::thread;
|
||||
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
|
||||
@@ -54,13 +54,15 @@ impl Desktop {
|
||||
|
||||
// #[allow(dead_code)]
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn start_camera_capture(cameras_amount: Arc<AtomicUsize>,
|
||||
fn start_camera_capture(
|
||||
cameras_amount: Arc<AtomicUsize>,
|
||||
camera_index: Arc<AtomicUsize>,
|
||||
stop_camera: Arc<AtomicBool>) {
|
||||
stop_camera: Arc<AtomicBool>,
|
||||
) {
|
||||
use nokhwa::Camera;
|
||||
use nokhwa::pixel_format::RgbFormat;
|
||||
use nokhwa::utils::{CameraIndex, RequestedFormat, RequestedFormatType};
|
||||
use nokhwa::utils::ApiBackend;
|
||||
use nokhwa::utils::{CameraIndex, RequestedFormat, RequestedFormatType};
|
||||
|
||||
let devices = nokhwa::query(ApiBackend::Auto).unwrap();
|
||||
cameras_amount.store(devices.len(), Ordering::Relaxed);
|
||||
@@ -71,9 +73,8 @@ impl Desktop {
|
||||
|
||||
thread::spawn(move || {
|
||||
let index = CameraIndex::Index(camera_index.load(Ordering::Relaxed) as u32);
|
||||
let requested = RequestedFormat::new::<RgbFormat>(
|
||||
RequestedFormatType::AbsoluteHighestFrameRate
|
||||
);
|
||||
let requested =
|
||||
RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestFrameRate);
|
||||
// Create and open camera.
|
||||
if let Ok(mut camera) = Camera::new(index, requested) {
|
||||
if let Ok(_) = camera.open_stream() {
|
||||
@@ -106,15 +107,17 @@ impl Desktop {
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn start_camera_capture(cameras_amount: Arc<AtomicUsize>,
|
||||
fn start_camera_capture(
|
||||
cameras_amount: Arc<AtomicUsize>,
|
||||
camera_index: Arc<AtomicUsize>,
|
||||
stop_camera: Arc<AtomicBool>) {
|
||||
stop_camera: Arc<AtomicBool>,
|
||||
) {
|
||||
use nokhwa::CallbackCamera;
|
||||
use nokhwa::nokhwa_initialize;
|
||||
use nokhwa::pixel_format::RgbFormat;
|
||||
use nokhwa::utils::{CameraIndex, RequestedFormat, RequestedFormatType};
|
||||
use nokhwa::utils::ApiBackend;
|
||||
use nokhwa::query;
|
||||
use nokhwa::CallbackCamera;
|
||||
use nokhwa::utils::ApiBackend;
|
||||
use nokhwa::utils::{CameraIndex, RequestedFormat, RequestedFormatType};
|
||||
|
||||
// Ask permission to open camera.
|
||||
nokhwa_initialize(|_| {});
|
||||
@@ -131,7 +134,7 @@ impl Desktop {
|
||||
let camera_callback = CallbackCamera::new(
|
||||
camera_index,
|
||||
RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestFrameRate),
|
||||
|_| {}
|
||||
|_| {},
|
||||
);
|
||||
if let Ok(mut cb) = camera_callback {
|
||||
if cb.open_stream().is_ok() {
|
||||
@@ -150,7 +153,9 @@ impl Desktop {
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
let format = image::ImageFormat::Jpeg;
|
||||
// Convert image to Jpeg format.
|
||||
image.write_to(&mut std::io::Cursor::new(&mut bytes), format).unwrap();
|
||||
image
|
||||
.write_to(&mut std::io::Cursor::new(&mut bytes), format)
|
||||
.unwrap();
|
||||
let mut w_image = LAST_CAMERA_IMAGE.write();
|
||||
*w_image = Some((bytes, 0));
|
||||
} else {
|
||||
@@ -200,9 +205,11 @@ impl PlatformCallbacks for Desktop {
|
||||
let stop_camera = self.stop_camera.clone();
|
||||
stop_camera.store(false, Ordering::Relaxed);
|
||||
|
||||
Self::start_camera_capture(self.cameras_amount.clone(),
|
||||
Self::start_camera_capture(
|
||||
self.cameras_amount.clone(),
|
||||
self.camera_index.clone(),
|
||||
stop_camera);
|
||||
stop_camera,
|
||||
);
|
||||
}
|
||||
|
||||
fn stop_camera(&self) {
|
||||
@@ -280,9 +287,9 @@ impl PlatformCallbacks for Desktop {
|
||||
if r_ctx.is_some() {
|
||||
let ctx = r_ctx.as_ref().unwrap();
|
||||
// Request attention on taskbar.
|
||||
ctx.send_viewport_cmd(
|
||||
ViewportCommand::RequestUserAttention(UserAttentionType::Informational)
|
||||
);
|
||||
ctx.send_viewport_cmd(ViewportCommand::RequestUserAttention(
|
||||
UserAttentionType::Informational,
|
||||
));
|
||||
// Un-minimize window.
|
||||
if ctx.input(|i| i.viewport().minimized.unwrap_or(false)) {
|
||||
ctx.send_viewport_cmd(ViewportCommand::Minimized(false));
|
||||
@@ -305,9 +312,9 @@ impl PlatformCallbacks for Desktop {
|
||||
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)
|
||||
);
|
||||
ctx.send_viewport_cmd(ViewportCommand::RequestUserAttention(
|
||||
UserAttentionType::Reset,
|
||||
));
|
||||
ctx.send_viewport_cmd(ViewportCommand::WindowLevel(WindowLevel::Normal));
|
||||
}
|
||||
self.attention_required.store(false, Ordering::Relaxed);
|
||||
|
||||
+31
-27
@@ -22,27 +22,27 @@ use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::CAMERA_ROTATE;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::{QrScanResult, QrScanState};
|
||||
use crate::gui::views::View;
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::PhraseSize;
|
||||
use crate::gui::views::types::{QrScanResult, QrScanState};
|
||||
use crate::wallet::WalletUtils;
|
||||
use crate::wallet::types::PhraseSize;
|
||||
|
||||
/// Camera QR code scanner.
|
||||
pub struct CameraContent {
|
||||
/// QR code scanning progress and result.
|
||||
qr_scan_state: Arc<RwLock<QrScanState>>,
|
||||
/// Uniform Resources URIs collected from QR code scanning.
|
||||
ur_data: Arc<RwLock<Option<(Vec<String>, usize)>>>
|
||||
ur_data: Arc<RwLock<Option<(Vec<String>, usize)>>>,
|
||||
}
|
||||
|
||||
impl Default for CameraContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
qr_scan_state: Arc::new(RwLock::new(QrScanState::default())),
|
||||
ur_data: Arc::new(RwLock::new(None))
|
||||
ur_data: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,33 +95,30 @@ impl CameraContent {
|
||||
90 => img.rotate90(),
|
||||
180 => img.rotate180(),
|
||||
270 => img.rotate270(),
|
||||
_ => img
|
||||
_ => img,
|
||||
};
|
||||
if View::is_desktop() {
|
||||
img = img.fliph();
|
||||
}
|
||||
// Convert to ColorImage.
|
||||
let color_img = match &img {
|
||||
DynamicImage::ImageRgb8(image) => {
|
||||
egui::ColorImage::from_rgb(
|
||||
DynamicImage::ImageRgb8(image) => egui::ColorImage::from_rgb(
|
||||
[image.width() as usize, image.height() as usize],
|
||||
image.as_bytes(),
|
||||
)
|
||||
},
|
||||
),
|
||||
other => {
|
||||
let image = other.to_rgba8();
|
||||
egui::ColorImage::from_rgba_unmultiplied(
|
||||
[image.width() as usize, image.height() as usize],
|
||||
image.as_bytes(),
|
||||
)
|
||||
},
|
||||
}
|
||||
};
|
||||
// Create image texture.
|
||||
let texture = ui.ctx().load_texture("camera_image",
|
||||
color_img.clone(),
|
||||
TextureOptions::default());
|
||||
let img_size = egui::emath::vec2(color_img.width() as f32,
|
||||
color_img.height() as f32);
|
||||
let texture =
|
||||
ui.ctx()
|
||||
.load_texture("camera_image", color_img.clone(), TextureOptions::default());
|
||||
let img_size = egui::emath::vec2(color_img.width() as f32, color_img.height() as f32);
|
||||
let sized_img = SizedTexture::new(texture.id(), img_size);
|
||||
egui::Image::from_texture(sized_img)
|
||||
// Setup to crop image at square.
|
||||
@@ -131,25 +128,26 @@ impl CameraContent {
|
||||
} else {
|
||||
Pos2::new(1.0 - (img_size.y / img_size.x), 0.0)
|
||||
},
|
||||
Pos2::new(1.0, 1.0)
|
||||
Pos2::new(1.0, 1.0),
|
||||
]))
|
||||
.max_height(ui.available_width())
|
||||
.maintain_aspect_ratio(false)
|
||||
.shrink_to_fit()
|
||||
.ui(ui).rect
|
||||
.ui(ui)
|
||||
.rect
|
||||
}
|
||||
|
||||
/// Draw animated QR code scanning progress.
|
||||
fn ur_progress_ui(&self, ui: &mut egui::Ui, rect: &Rect) {
|
||||
let show_ur_progress = {
|
||||
self.ur_data.as_ref().read().is_some()
|
||||
};
|
||||
let show_ur_progress = { self.ur_data.as_ref().read().is_some() };
|
||||
if show_ur_progress {
|
||||
ui.scope_builder(UiBuilder::new().max_rect(rect.clone()), |ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.label(RichText::new(format!("{}%", self.ur_progress()))
|
||||
ui.label(
|
||||
RichText::new(format!("{}%", self.ur_progress()))
|
||||
.size(32.0)
|
||||
.color(Colors::gold_dark()));
|
||||
.color(Colors::gold_dark()),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -162,7 +160,9 @@ impl CameraContent {
|
||||
ui.add_space(space);
|
||||
View::big_loading_spinner(ui);
|
||||
ui.add_space(space);
|
||||
}).response.rect
|
||||
})
|
||||
.response
|
||||
.rect
|
||||
}
|
||||
|
||||
/// Check if image is processing to find QR code.
|
||||
@@ -401,12 +401,16 @@ impl CameraContent {
|
||||
if !text.is_empty() && data.len() <= 96 && data.len() % 4 == 0 && only_numbers() {
|
||||
if let Some(_) = PhraseSize::type_for_value(text.len() / 4) {
|
||||
let chars: Vec<char> = text.trim().chars().collect();
|
||||
let split = &chars.chunks(4)
|
||||
.map(|chunk| chunk.iter().collect::<String>()
|
||||
let split = &chars
|
||||
.chunks(4)
|
||||
.map(|chunk| {
|
||||
chunk
|
||||
.iter()
|
||||
.collect::<String>()
|
||||
.trim()
|
||||
.trim_start_matches("0")
|
||||
.to_string()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut words = "".to_string();
|
||||
for i in split {
|
||||
|
||||
+57
-26
@@ -12,19 +12,19 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::RichText;
|
||||
use egui::os::OperatingSystem;
|
||||
use lazy_static::lazy_static;
|
||||
use std::fs;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::FILE_X;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::NetworkContent;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::wallets::WalletsContent;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::node::Node;
|
||||
use crate::{AppConfig, Settings};
|
||||
|
||||
@@ -75,7 +75,7 @@ impl ContentContainer for Content {
|
||||
vec![
|
||||
Self::EXIT_CONFIRMATION_MODAL,
|
||||
ANDROID_INTEGRATED_NODE_WARNING_MODAL,
|
||||
CRASH_REPORT_MODAL
|
||||
CRASH_REPORT_MODAL,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -123,8 +123,9 @@ impl ContentContainer for Content {
|
||||
.position(ModalPosition::Center)
|
||||
.title(t!("crash_report"))
|
||||
.show();
|
||||
} else if OperatingSystem::from_target_os() == OperatingSystem::Android &&
|
||||
AppConfig::android_integrated_node_warning_needed() {
|
||||
} else if OperatingSystem::from_target_os() == OperatingSystem::Android
|
||||
&& AppConfig::android_integrated_node_warning_needed()
|
||||
{
|
||||
Modal::new(ANDROID_INTEGRATED_NODE_WARNING_MODAL)
|
||||
.title(t!("network.node"))
|
||||
.show();
|
||||
@@ -208,17 +209,21 @@ impl Content {
|
||||
} else {
|
||||
t!("sync_status.shutdown")
|
||||
};
|
||||
ui.label(RichText::new(exit_status_text)
|
||||
ui.label(
|
||||
RichText::new(exit_status_text)
|
||||
.size(17.0)
|
||||
.color(Colors::text(false)));
|
||||
.color(Colors::text(false)),
|
||||
);
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
} else {
|
||||
ui.add_space(8.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("modal_exit.description"))
|
||||
ui.label(
|
||||
RichText::new(t!("modal_exit.description"))
|
||||
.size(17.0)
|
||||
.color(Colors::text(false)));
|
||||
.color(Colors::text(false)),
|
||||
);
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
|
||||
@@ -227,12 +232,21 @@ impl Content {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button_ui(ui, t!("modal_exit.exit"), Colors::white_or_black(false), |_| {
|
||||
View::button_ui(
|
||||
ui,
|
||||
t!("modal_exit.exit"),
|
||||
Colors::white_or_black(false),
|
||||
|_| {
|
||||
if !Node::is_running() && !Node::data_dir_changing() {
|
||||
self.exit_allowed = true;
|
||||
cb.exit();
|
||||
@@ -243,7 +257,8 @@ impl Content {
|
||||
Modal::set_title(t!("modal_exit.exit"));
|
||||
self.show_exit_progress = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
@@ -254,9 +269,11 @@ impl Content {
|
||||
fn android_warning_modal_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network.android_warning"))
|
||||
ui.label(
|
||||
RichText::new(t!("network.android_warning"))
|
||||
.size(16.0)
|
||||
.color(Colors::text(false)));
|
||||
.color(Colors::text(false)),
|
||||
);
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
@@ -272,29 +289,42 @@ impl Content {
|
||||
fn crash_report_modal_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("crash_report_warning"))
|
||||
ui.label(
|
||||
RichText::new(t!("crash_report_warning"))
|
||||
.size(16.0)
|
||||
.color(Colors::text(false)));
|
||||
.color(Colors::text(false)),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
// Draw button to share log file.
|
||||
let text = format!("{} {}", FILE_X, t!("share"));
|
||||
View::colored_text_button(ui, text, Colors::blue(), Colors::white_or_black(false), || {
|
||||
View::colored_text_button(
|
||||
ui,
|
||||
text,
|
||||
Colors::blue(),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
if let Ok(data) = fs::read_to_string(Settings::log_path()) {
|
||||
let name = Settings::LOG_FILE_NAME.to_string();
|
||||
let _ = cb.share_data(name, data.as_bytes().to_vec());
|
||||
}
|
||||
Settings::delete_crash_check();
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(8.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Settings::delete_crash_check();
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
@@ -306,11 +336,12 @@ fn network_panel_state_width(ctx: &egui::Context, dual_panel: bool) -> (bool, f3
|
||||
let panel_width = if dual_panel {
|
||||
Content::SIDE_PANEL_WIDTH + View::get_left_inset()
|
||||
} else {
|
||||
let is_fullscreen = ctx.input(|i| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
View::window_size(ctx).0 - if View::is_desktop() && !is_fullscreen &&
|
||||
OperatingSystem::from_target_os() != OperatingSystem::Mac {
|
||||
let is_fullscreen = ctx.input(|i| i.viewport().fullscreen.unwrap_or(false));
|
||||
View::window_size(ctx).0
|
||||
- if View::is_desktop()
|
||||
&& !is_fullscreen
|
||||
&& OperatingSystem::from_target_os() != OperatingSystem::Mac
|
||||
{
|
||||
Content::WINDOW_FRAME_MARGIN * 2.0
|
||||
} else {
|
||||
0.0
|
||||
|
||||
@@ -14,18 +14,20 @@
|
||||
|
||||
use egui::CornerRadius;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
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(String), ItemButton(CornerRadius), Tab
|
||||
Button(String),
|
||||
ItemButton(CornerRadius),
|
||||
Tab,
|
||||
}
|
||||
|
||||
/// Button to pick file and parse its data into text.
|
||||
@@ -132,9 +134,11 @@ impl FilePickContent {
|
||||
}
|
||||
FilePickContentType::Tab => {
|
||||
let active = match self.active {
|
||||
true => Some(self.file_parsing.load(Ordering::Relaxed) ||
|
||||
self.file_picking.load(Ordering::Relaxed)),
|
||||
false => None
|
||||
true => Some(
|
||||
self.file_parsing.load(Ordering::Relaxed)
|
||||
|| self.file_picking.load(Ordering::Relaxed),
|
||||
),
|
||||
false => None,
|
||||
};
|
||||
View::tab_button(ui, ARCHIVE_BOX, Some(Colors::blue()), active, |_| {
|
||||
self.on_file_pick(pick, cb);
|
||||
@@ -175,8 +179,7 @@ impl FilePickContent {
|
||||
thread::spawn(move || {
|
||||
if path.ends_with(".gif") {
|
||||
//TODO: Detect QR codes on GIF file.
|
||||
} else if path.ends_with(".jpeg") || path.ends_with(".jpg") ||
|
||||
path.ends_with(".png") {
|
||||
} else if path.ends_with(".jpeg") || path.ends_with(".jpg") || path.ends_with(".png") {
|
||||
//TODO: Detect QR codes on image files.
|
||||
} else {
|
||||
// Parse file as plain text.
|
||||
|
||||
+66
-44
@@ -18,11 +18,11 @@ use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SCAN};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::input::keyboard::KeyboardContent;
|
||||
use crate::gui::views::{KeyboardEvent, View};
|
||||
use crate::gui::Colors;
|
||||
|
||||
/// Text input content.
|
||||
pub struct TextEdit {
|
||||
@@ -82,37 +82,47 @@ impl TextEdit {
|
||||
}
|
||||
|
||||
/// Draw text input with additional buttons (right to left order).
|
||||
pub fn custom_buttons_ui(&mut self,
|
||||
pub fn custom_buttons_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
input: &mut String,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
buttons_content: impl FnOnce(&mut egui::Ui)) {
|
||||
buttons_content: impl FnOnce(&mut egui::Ui),
|
||||
) {
|
||||
self.input_ui(ui, input, buttons_content, cb);
|
||||
}
|
||||
|
||||
/// Draw text input content.
|
||||
fn input_ui(&mut self,
|
||||
fn input_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
input: &mut String,
|
||||
buttons_content: impl FnOnce(&mut egui::Ui),
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
let mut layout_rect = ui.available_rect_before_wrap();
|
||||
layout_rect.set_height(Self::TEXT_EDIT_HEIGHT);
|
||||
ui.allocate_ui_with_layout(layout_rect.size(), Layout::right_to_left(Align::Max), |ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
layout_rect.size(),
|
||||
Layout::right_to_left(Align::Max),
|
||||
|ui| {
|
||||
let mut hide_input = false;
|
||||
if self.password {
|
||||
let show_pass_id = egui::Id::new(self.id).with("_show_pass");
|
||||
hide_input = ui.data(|data| {
|
||||
data.get_temp(show_pass_id)
|
||||
}).unwrap_or(true);
|
||||
hide_input = ui.data(|data| data.get_temp(show_pass_id)).unwrap_or(true);
|
||||
// Draw button to show/hide current password.
|
||||
let eye_icon = if hide_input { EYE } else { EYE_SLASH };
|
||||
View::button_ui(ui, eye_icon.to_string(), Colors::white_or_black(false), |ui| {
|
||||
View::button_ui(
|
||||
ui,
|
||||
eye_icon.to_string(),
|
||||
Colors::white_or_black(false),
|
||||
|ui| {
|
||||
hide_input = !hide_input;
|
||||
ui.data_mut(|data| {
|
||||
data.insert_temp(show_pass_id, hide_input);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
|
||||
@@ -155,9 +165,10 @@ impl TextEdit {
|
||||
|
||||
// Setup focused input value to avoid dismiss when click on keyboard.
|
||||
let focused_input_id = egui::Id::new("focused_input_id");
|
||||
let focused = ui.data(|data| {
|
||||
data.get_temp(focused_input_id)
|
||||
}).unwrap_or(egui::Id::new("")) == self.id;
|
||||
let focused = ui
|
||||
.data(|data| data.get_temp(focused_input_id))
|
||||
.unwrap_or(egui::Id::new(""))
|
||||
== self.id;
|
||||
|
||||
// Show text edit.
|
||||
let text_edit_resp = egui::TextEdit::singleline(input)
|
||||
@@ -175,7 +186,11 @@ impl TextEdit {
|
||||
} else {
|
||||
egui::Margin::symmetric(8, 8)
|
||||
})
|
||||
.horizontal_align(if self.h_center { Align::Center } else { Align::Min })
|
||||
.horizontal_align(if self.h_center {
|
||||
Align::Center
|
||||
} else {
|
||||
Align::Min
|
||||
})
|
||||
.vertical_align(Align::Center)
|
||||
.password(hide_input)
|
||||
.cursor_at_end(true)
|
||||
@@ -183,14 +198,16 @@ impl TextEdit {
|
||||
|
||||
// Setup focus state.
|
||||
let clicked = text_edit_resp.clicked();
|
||||
if !text_edit_resp.has_focus() &&
|
||||
(self.focus || self.focus_request || clicked || focused) {
|
||||
if !text_edit_resp.has_focus()
|
||||
&& (self.focus || self.focus_request || clicked || focused)
|
||||
{
|
||||
text_edit_resp.request_focus();
|
||||
}
|
||||
|
||||
// Reset keyboard state for newly focused.
|
||||
if clicked || self.focus_request {
|
||||
ui.ctx().send_viewport_cmd(ViewportCommand::IMEAllowed(true));
|
||||
ui.ctx()
|
||||
.send_viewport_cmd(ViewportCommand::IMEAllowed(true));
|
||||
KeyboardContent::reset_window_state();
|
||||
}
|
||||
|
||||
@@ -202,8 +219,9 @@ impl TextEdit {
|
||||
self.enter_pressed = self.on_soft_input(ui, self.id, false, input);
|
||||
// Check Enter or Tab keys press.
|
||||
if !self.focus_request {
|
||||
if ui.ctx().input(|i| i.key_pressed(egui::Key::Enter) ||
|
||||
i.key_pressed(egui::Key::Tab)) {
|
||||
if ui.ctx().input(|i| {
|
||||
i.key_pressed(egui::Key::Enter) || i.key_pressed(egui::Key::Tab)
|
||||
}) {
|
||||
self.enter_pressed = true;
|
||||
}
|
||||
}
|
||||
@@ -215,14 +233,20 @@ impl TextEdit {
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
// Immediate repaint when input is open.
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
/// Apply soft keyboard input data to provided String, returns `true` if Enter was pressed.
|
||||
fn on_soft_input(&self, ui: &mut egui::Ui, id: egui::Id, multiline: bool, value: &mut String)
|
||||
-> bool {
|
||||
fn on_soft_input(
|
||||
&self,
|
||||
ui: &mut egui::Ui,
|
||||
id: egui::Id,
|
||||
multiline: bool,
|
||||
value: &mut String,
|
||||
) -> bool {
|
||||
let event: Option<KeyboardEvent> = if is_android() {
|
||||
let mut w_input = LAST_SOFT_KEYBOARD_EVENT.write();
|
||||
w_input.take()
|
||||
@@ -241,19 +265,18 @@ impl TextEdit {
|
||||
let mut index = r.primary.index;
|
||||
|
||||
let selected = r.primary.index != r.secondary.index;
|
||||
let start_select = f32::min(r.primary.index as f32,
|
||||
r.secondary.index as f32) as usize;
|
||||
let end_select = f32::max(r.primary.index as f32,
|
||||
r.secondary.index as f32) as usize;
|
||||
let start_select =
|
||||
f32::min(r.primary.index as f32, r.secondary.index as f32) as usize;
|
||||
let end_select =
|
||||
f32::max(r.primary.index as f32, r.secondary.index as f32) as usize;
|
||||
match e {
|
||||
KeyboardEvent::TEXT(text) => {
|
||||
if selected {
|
||||
*value = {
|
||||
let part1: String = value.chars()
|
||||
.skip(0)
|
||||
.take(start_select)
|
||||
.collect();
|
||||
let part2: String = value.chars()
|
||||
let part1: String =
|
||||
value.chars().skip(0).take(start_select).collect();
|
||||
let part2: String = value
|
||||
.chars()
|
||||
.skip(end_select)
|
||||
.take(value.len() - end_select)
|
||||
.collect();
|
||||
@@ -268,11 +291,10 @@ impl TextEdit {
|
||||
KeyboardEvent::CLEAR => {
|
||||
if selected {
|
||||
*value = {
|
||||
let part1: String = value.chars()
|
||||
.skip(0)
|
||||
.take(start_select)
|
||||
.collect();
|
||||
let part2: String = value.chars()
|
||||
let part1: String =
|
||||
value.chars().skip(0).take(start_select).collect();
|
||||
let part2: String = value
|
||||
.chars()
|
||||
.skip(end_select)
|
||||
.take(value.len() - end_select)
|
||||
.collect();
|
||||
@@ -281,11 +303,10 @@ impl TextEdit {
|
||||
index = start_select;
|
||||
} else if index != 0 {
|
||||
*value = {
|
||||
let part1: String = value.chars()
|
||||
.skip(0)
|
||||
.take(index - 1)
|
||||
.collect();
|
||||
let part2: String = value.chars()
|
||||
let part1: String =
|
||||
value.chars().skip(0).take(index - 1).collect();
|
||||
let part2: String = value
|
||||
.chars()
|
||||
.skip(index)
|
||||
.take(value.len() - index)
|
||||
.collect();
|
||||
@@ -398,7 +419,8 @@ fn is_android() -> bool {
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref LAST_SOFT_KEYBOARD_EVENT: Arc<RwLock<Option<KeyboardEvent>>> = Arc::new(RwLock::new(None));
|
||||
static ref LAST_SOFT_KEYBOARD_EVENT: Arc<RwLock<Option<KeyboardEvent>>> =
|
||||
Arc::new(RwLock::new(None));
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -409,7 +431,7 @@ lazy_static! {
|
||||
pub extern "C" fn Java_mw_gri_android_MainActivity_onTextInput(
|
||||
_env: jni::JNIEnv,
|
||||
_class: jni::objects::JObject,
|
||||
char: jni::sys::jstring
|
||||
char: jni::sys::jstring,
|
||||
) {
|
||||
use jni::objects::JString;
|
||||
|
||||
|
||||
+104
-64
@@ -12,17 +12,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::string::ToString;
|
||||
use egui::{Align, Align2, Button, Color32, CursorIcon, Layout, Margin, Rect, Response, RichText, Sense, Shadow, Vec2, Widget};
|
||||
use egui::{
|
||||
Align, Align2, Button, Color32, CursorIcon, Layout, Margin, Rect, Response, RichText, Sense,
|
||||
Shadow, Vec2, Widget,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::string::ToString;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_FAT_UP, BACKSPACE, GLOBE_SIMPLE, KEY_RETURN};
|
||||
use crate::gui::views::{KeyboardEvent, KeyboardLayout, KeyboardState, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::AppConfig;
|
||||
|
||||
lazy_static! {
|
||||
/// Keyboard window state.
|
||||
@@ -90,11 +93,14 @@ impl KeyboardContent {
|
||||
// Calculate content width.
|
||||
let side_insets = View::get_left_inset() + View::get_right_inset();
|
||||
let available_width = width - side_insets;
|
||||
let w = f32::min(available_width, if numeric {
|
||||
let w = f32::min(
|
||||
available_width,
|
||||
if numeric {
|
||||
Self::MAX_WIDTH_NUMBERS
|
||||
} else {
|
||||
Self::MAX_WIDTH
|
||||
});
|
||||
},
|
||||
);
|
||||
// Draw content.
|
||||
View::max_width_ui(ui, w, |ui| {
|
||||
self.ui(numeric, ui);
|
||||
@@ -102,7 +108,10 @@ impl KeyboardContent {
|
||||
// Save state.
|
||||
let mut w_state = WINDOW_STATE.write();
|
||||
*w_state = self.state.clone();
|
||||
}).unwrap().response.layer_id;
|
||||
})
|
||||
.unwrap()
|
||||
.response
|
||||
.layer_id;
|
||||
|
||||
// Always show keyboard above others windows.
|
||||
ctx.move_to_top(layer_id);
|
||||
@@ -120,11 +129,7 @@ impl KeyboardContent {
|
||||
// Setup spacing between buttons.
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(0.0, 0.0);
|
||||
// Setup vertical padding inside buttons.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(0.0, if numeric {
|
||||
12.0
|
||||
} else {
|
||||
10.0
|
||||
});
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(0.0, if numeric { 12.0 } else { 10.0 });
|
||||
|
||||
// Draw input buttons.
|
||||
let button_rect = match *self.state.layout {
|
||||
@@ -139,7 +144,8 @@ impl KeyboardContent {
|
||||
r.set_width(ui.available_width());
|
||||
r.size()
|
||||
};
|
||||
let button_width = ui.available_width() / match *self.state.layout {
|
||||
let button_width = ui.available_width()
|
||||
/ match *self.state.layout {
|
||||
KeyboardLayout::TEXT => 11.0,
|
||||
KeyboardLayout::SYMBOLS => 10.0,
|
||||
KeyboardLayout::NUMBERS => 4.0,
|
||||
@@ -150,14 +156,15 @@ impl KeyboardContent {
|
||||
// Enter key input.
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.set_max_width(button_width * 2.0);
|
||||
self.custom_button_ui(KEY_RETURN.to_string(),
|
||||
self.custom_button_ui(
|
||||
KEY_RETURN.to_string(),
|
||||
Colors::white_or_black(false),
|
||||
Some(Colors::green()),
|
||||
ui,
|
||||
|_, c| {
|
||||
c.state.last_event =
|
||||
Arc::new(Some(KeyboardEvent::ENTER));
|
||||
});
|
||||
c.state.last_event = Arc::new(Some(KeyboardEvent::ENTER));
|
||||
},
|
||||
);
|
||||
});
|
||||
// Custom input key.
|
||||
ui.horizontal_centered(|ui| {
|
||||
@@ -167,47 +174,51 @@ impl KeyboardContent {
|
||||
// Space key input.
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.set_max_width(button_width * 5.0);
|
||||
self.custom_button_ui(" ".to_string(),
|
||||
self.custom_button_ui(
|
||||
" ".to_string(),
|
||||
Colors::inactive_text(),
|
||||
None,
|
||||
ui,
|
||||
|l, c| {
|
||||
c.state.last_event =
|
||||
Arc::new(Some(KeyboardEvent::TEXT(l)));
|
||||
});
|
||||
c.state.last_event = Arc::new(Some(KeyboardEvent::TEXT(l)));
|
||||
},
|
||||
);
|
||||
});
|
||||
// Switch to english and back.
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.set_max_width(button_width);
|
||||
self.custom_button_ui(GLOBE_SIMPLE.to_string(),
|
||||
self.custom_button_ui(
|
||||
GLOBE_SIMPLE.to_string(),
|
||||
Colors::text_button(),
|
||||
Some(Colors::fill_lite()),
|
||||
ui,
|
||||
|_, _| {
|
||||
AppConfig::toggle_english_keyboard()
|
||||
});
|
||||
|_, _| AppConfig::toggle_english_keyboard(),
|
||||
);
|
||||
});
|
||||
// Switch to symbols layout.
|
||||
self.custom_button_ui("!@ツ".to_string(),
|
||||
self.custom_button_ui(
|
||||
"!@ツ".to_string(),
|
||||
Colors::text_button(),
|
||||
Some(Colors::fill_lite()),
|
||||
ui,
|
||||
|_, c| {
|
||||
c.state.layout = Arc::new(KeyboardLayout::SYMBOLS);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
KeyboardLayout::SYMBOLS => {
|
||||
// Enter key input.
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.set_max_width(button_width * 2.0);
|
||||
self.custom_button_ui(KEY_RETURN.to_string(),
|
||||
self.custom_button_ui(
|
||||
KEY_RETURN.to_string(),
|
||||
Colors::white_or_black(false),
|
||||
Some(Colors::green()),
|
||||
ui,
|
||||
|_, c| {
|
||||
c.state.last_event =
|
||||
Arc::new(Some(KeyboardEvent::ENTER));
|
||||
});
|
||||
c.state.last_event = Arc::new(Some(KeyboardEvent::ENTER));
|
||||
},
|
||||
);
|
||||
});
|
||||
// Custom input key.
|
||||
ui.horizontal_centered(|ui| {
|
||||
@@ -217,14 +228,15 @@ impl KeyboardContent {
|
||||
// Space key input.
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.set_max_width(button_width * 4.0);
|
||||
self.custom_button_ui(" ".to_string(),
|
||||
self.custom_button_ui(
|
||||
" ".to_string(),
|
||||
Colors::inactive_text(),
|
||||
None,
|
||||
ui,
|
||||
|l, c| {
|
||||
c.state.last_event =
|
||||
Arc::new(Some(KeyboardEvent::TEXT(l)));
|
||||
});
|
||||
c.state.last_event = Arc::new(Some(KeyboardEvent::TEXT(l)));
|
||||
},
|
||||
);
|
||||
});
|
||||
// Switch to text layout.
|
||||
let label = {
|
||||
@@ -233,25 +245,28 @@ impl KeyboardContent {
|
||||
let e = t!("keyboard.e", locale = Self::input_locale().as_str());
|
||||
format!("{}{}{}", q, w, e).to_uppercase()
|
||||
};
|
||||
self.custom_button_ui(label,
|
||||
self.custom_button_ui(
|
||||
label,
|
||||
Colors::text_button(),
|
||||
Some(Colors::fill_lite()),
|
||||
ui,
|
||||
|_, c| {
|
||||
c.state.layout = Arc::new(KeyboardLayout::TEXT);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
KeyboardLayout::NUMBERS => {
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.set_max_width(button_width * 2.0);
|
||||
self.custom_button_ui(KEY_RETURN.to_string(),
|
||||
self.custom_button_ui(
|
||||
KEY_RETURN.to_string(),
|
||||
Colors::white_or_black(false),
|
||||
Some(Colors::green()),
|
||||
ui,
|
||||
|_, c| {
|
||||
c.state.last_event =
|
||||
Arc::new(Some(KeyboardEvent::ENTER));
|
||||
});
|
||||
c.state.last_event = Arc::new(Some(KeyboardEvent::ENTER));
|
||||
},
|
||||
);
|
||||
});
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.set_max_width(button_width);
|
||||
@@ -289,14 +304,15 @@ impl KeyboardContent {
|
||||
ui.columns(tl_2.len(), |columns| {
|
||||
for (index, s) in tl_2.iter().enumerate() {
|
||||
if index == tl_2.len() - 1 {
|
||||
self.custom_button_ui(BACKSPACE.to_string(),
|
||||
self.custom_button_ui(
|
||||
BACKSPACE.to_string(),
|
||||
Colors::red(),
|
||||
Some(Colors::fill_lite()),
|
||||
&mut columns[index],
|
||||
|_, c| {
|
||||
c.state.last_event =
|
||||
Arc::new(Some(KeyboardEvent::CLEAR));
|
||||
});
|
||||
c.state.last_event = Arc::new(Some(KeyboardEvent::CLEAR));
|
||||
},
|
||||
);
|
||||
} else {
|
||||
self.input_button_ui(s, true, &mut columns[index]);
|
||||
}
|
||||
@@ -330,8 +346,19 @@ impl KeyboardContent {
|
||||
}
|
||||
});
|
||||
|
||||
let tl_3: Vec<&str> =
|
||||
vec![ARROW_FAT_UP, "z", "x", "c", "v", "b", "n", "m", "m1", "m2", BACKSPACE];
|
||||
let tl_3: Vec<&str> = vec![
|
||||
ARROW_FAT_UP,
|
||||
"z",
|
||||
"x",
|
||||
"c",
|
||||
"v",
|
||||
"b",
|
||||
"n",
|
||||
"m",
|
||||
"m1",
|
||||
"m2",
|
||||
BACKSPACE,
|
||||
];
|
||||
ui.columns(tl_3.len(), |columns| {
|
||||
for (index, s) in tl_3.iter().enumerate() {
|
||||
if index == 0 {
|
||||
@@ -341,22 +368,25 @@ impl KeyboardContent {
|
||||
} else {
|
||||
Colors::inactive_text()
|
||||
};
|
||||
self.custom_button_ui(ARROW_FAT_UP.to_string(),
|
||||
self.custom_button_ui(
|
||||
ARROW_FAT_UP.to_string(),
|
||||
color,
|
||||
Some(Colors::fill_lite()),
|
||||
&mut columns[index],
|
||||
|_, c| {
|
||||
c.state.shift.store(!shift, Ordering::Relaxed);
|
||||
});
|
||||
},
|
||||
);
|
||||
} else if index == tl_3.len() - 1 {
|
||||
self.custom_button_ui(BACKSPACE.to_string(),
|
||||
self.custom_button_ui(
|
||||
BACKSPACE.to_string(),
|
||||
Colors::red(),
|
||||
Some(Colors::fill_lite()),
|
||||
&mut columns[index],
|
||||
|_, c| {
|
||||
c.state.last_event =
|
||||
Arc::new(Some(KeyboardEvent::CLEAR));
|
||||
});
|
||||
c.state.last_event = Arc::new(Some(KeyboardEvent::CLEAR));
|
||||
},
|
||||
);
|
||||
} else {
|
||||
self.input_button_ui(s, true, &mut columns[index]);
|
||||
}
|
||||
@@ -394,14 +424,15 @@ impl KeyboardContent {
|
||||
ui.columns(tl_3.len(), |columns| {
|
||||
for (index, s) in tl_3.iter().enumerate() {
|
||||
if index == tl_3.len() - 1 {
|
||||
self.custom_button_ui(BACKSPACE.to_string(),
|
||||
self.custom_button_ui(
|
||||
BACKSPACE.to_string(),
|
||||
Colors::red(),
|
||||
Some(Colors::fill_lite()),
|
||||
&mut columns[index],
|
||||
|_, c| {
|
||||
c.state.last_event =
|
||||
Arc::new(Some(KeyboardEvent::CLEAR));
|
||||
});
|
||||
c.state.last_event = Arc::new(Some(KeyboardEvent::CLEAR));
|
||||
},
|
||||
);
|
||||
} else {
|
||||
self.input_button_ui(s, false, &mut columns[index]);
|
||||
}
|
||||
@@ -412,12 +443,14 @@ impl KeyboardContent {
|
||||
}
|
||||
|
||||
/// Draw custom keyboard button.
|
||||
fn custom_button_ui(&mut self,
|
||||
fn custom_button_ui(
|
||||
&mut self,
|
||||
s: String,
|
||||
color: Color32,
|
||||
bg: Option<Color32>,
|
||||
ui: &mut egui::Ui,
|
||||
cb: impl FnOnce(String, &mut KeyboardContent)) -> Response {
|
||||
cb: impl FnOnce(String, &mut KeyboardContent),
|
||||
) -> Response {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
// Disable expansion on click/hover.
|
||||
ui.style_mut().visuals.widgets.hovered.expansion = 0.0;
|
||||
@@ -452,20 +485,27 @@ impl KeyboardContent {
|
||||
if resp.clicked() || resp.long_touched() || resp.dragged() {
|
||||
cb(label, self);
|
||||
}
|
||||
}).response
|
||||
})
|
||||
.response
|
||||
}
|
||||
|
||||
/// Draw input button.
|
||||
fn input_button_ui(&mut self, s: &str, translate: bool, ui: &mut egui::Ui) -> Rect {
|
||||
let value = if translate {
|
||||
t!(format!("keyboard.{}", s), locale = Self::input_locale().as_str()).into()
|
||||
t!(
|
||||
format!("keyboard.{}", s),
|
||||
locale = Self::input_locale().as_str()
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
s.to_string()
|
||||
};
|
||||
let rect = self.custom_button_ui(value, Colors::text_button(), None, ui, |l, c| {
|
||||
let rect = self
|
||||
.custom_button_ui(value, Colors::text_button(), None, ui, |l, c| {
|
||||
c.state.last_event = Arc::new(Some(KeyboardEvent::TEXT(l)));
|
||||
c.state.shift.store(false, Ordering::Relaxed);
|
||||
}).rect;
|
||||
})
|
||||
.rect;
|
||||
rect
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,17 @@ use std::sync::atomic::AtomicBool;
|
||||
/// Software keyboard input type.
|
||||
#[derive(Clone, PartialOrd, PartialEq)]
|
||||
pub enum KeyboardLayout {
|
||||
TEXT, SYMBOLS, NUMBERS
|
||||
TEXT,
|
||||
SYMBOLS,
|
||||
NUMBERS,
|
||||
}
|
||||
|
||||
/// Software keyboard input event.
|
||||
#[derive(Clone)]
|
||||
pub enum KeyboardEvent {
|
||||
TEXT(String), CLEAR, ENTER
|
||||
TEXT(String),
|
||||
CLEAR,
|
||||
ENTER,
|
||||
}
|
||||
|
||||
/// Software keyboard Window State.
|
||||
|
||||
@@ -27,8 +27,8 @@ mod content;
|
||||
pub use content::*;
|
||||
|
||||
pub mod network;
|
||||
pub mod wallets;
|
||||
pub mod settings;
|
||||
pub mod wallets;
|
||||
|
||||
mod camera;
|
||||
pub use camera::*;
|
||||
|
||||
+46
-30
@@ -17,13 +17,13 @@ use egui::os::OperatingSystem;
|
||||
use egui::{Align2, Color32, CornerRadius, RichText, Stroke, StrokeKind, UiBuilder, Vec2};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::gui::views::types::{ModalPosition, ModalState};
|
||||
use crate::gui::views::{Content, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::{ModalPosition, ModalState};
|
||||
use crate::gui::views::{Content, View};
|
||||
|
||||
lazy_static! {
|
||||
/// Showing [`Modal`] state to be accessible from different ui parts.
|
||||
@@ -179,12 +179,12 @@ impl Modal {
|
||||
modal.first_draw.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn ui(ctx: &egui::Context,
|
||||
pub fn ui(
|
||||
ctx: &egui::Context,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
add_content: impl FnOnce(&mut egui::Ui, &Modal, &dyn PlatformCallbacks)) {
|
||||
let has_modal = {
|
||||
MODAL_STATE.read().modal.is_some()
|
||||
};
|
||||
add_content: impl FnOnce(&mut egui::Ui, &Modal, &dyn PlatformCallbacks),
|
||||
) {
|
||||
let has_modal = { MODAL_STATE.read().modal.is_some() };
|
||||
if has_modal {
|
||||
let modal = {
|
||||
let r_state = MODAL_STATE.read();
|
||||
@@ -195,13 +195,13 @@ impl Modal {
|
||||
}
|
||||
|
||||
/// Draw [`egui::Window`] with provided content.
|
||||
fn window_ui(&self,
|
||||
fn window_ui(
|
||||
&self,
|
||||
ctx: &egui::Context,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
add_content: impl FnOnce(&mut egui::Ui, &Modal, &dyn PlatformCallbacks)) {
|
||||
let is_fullscreen = ctx.input(|i| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
add_content: impl FnOnce(&mut egui::Ui, &Modal, &dyn PlatformCallbacks),
|
||||
) {
|
||||
let is_fullscreen = ctx.input(|i| i.viewport().fullscreen.unwrap_or(false));
|
||||
|
||||
// Setup background rect.
|
||||
let is_win = OperatingSystem::Windows == OperatingSystem::from_target_os();
|
||||
@@ -274,14 +274,15 @@ impl Modal {
|
||||
fn modal_position(&self) -> (Align2, Vec2) {
|
||||
let align = match self.position {
|
||||
ModalPosition::CenterTop => Align2::CENTER_TOP,
|
||||
ModalPosition::Center => Align2::CENTER_CENTER
|
||||
ModalPosition::Center => Align2::CENTER_CENTER,
|
||||
};
|
||||
|
||||
let x_align = View::get_left_inset() - View::get_right_inset();
|
||||
let is_mac = OperatingSystem::Mac == OperatingSystem::from_target_os();
|
||||
let is_win = OperatingSystem::Windows == OperatingSystem::from_target_os();
|
||||
let extra_y = if View::is_desktop() && !is_win {
|
||||
Content::WINDOW_TITLE_HEIGHT + if !is_mac {
|
||||
Content::WINDOW_TITLE_HEIGHT
|
||||
+ if !is_mac {
|
||||
Content::WINDOW_FRAME_MARGIN
|
||||
} else {
|
||||
0.0
|
||||
@@ -293,7 +294,7 @@ impl Modal {
|
||||
|
||||
let offset = match self.position {
|
||||
ModalPosition::CenterTop => Vec2::new(x_align, y_align),
|
||||
ModalPosition::Center => Vec2::new(x_align, 0.0)
|
||||
ModalPosition::Center => Vec2::new(x_align, 0.0),
|
||||
};
|
||||
(align, offset)
|
||||
}
|
||||
@@ -305,14 +306,18 @@ impl Modal {
|
||||
}
|
||||
|
||||
/// Draw provided content.
|
||||
fn content_ui(&self,
|
||||
fn content_ui(
|
||||
&self,
|
||||
ui: &mut egui::Ui,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
add_content: impl FnOnce(&mut egui::Ui, &Modal, &dyn PlatformCallbacks)) {
|
||||
add_content: impl FnOnce(&mut egui::Ui, &Modal, &dyn PlatformCallbacks),
|
||||
) {
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
|
||||
// Create background shape.
|
||||
let mut bg_shape = RectShape::new(rect, if self.title.is_none() {
|
||||
let mut bg_shape = RectShape::new(
|
||||
rect,
|
||||
if self.title.is_none() {
|
||||
CornerRadius::same(8.0 as u8)
|
||||
} else {
|
||||
CornerRadius {
|
||||
@@ -321,14 +326,20 @@ impl Modal {
|
||||
sw: 8.0 as u8,
|
||||
se: 8.0 as u8,
|
||||
}
|
||||
}, self.fill.unwrap_or(Colors::fill_lite()), Stroke::NONE, StrokeKind::Outside);
|
||||
},
|
||||
self.fill.unwrap_or(Colors::fill_lite()),
|
||||
Stroke::NONE,
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
rect.min += egui::emath::vec2(6.0, 0.0);
|
||||
rect.max -= egui::emath::vec2(6.0, 0.0);
|
||||
let resp = ui.scope_builder(UiBuilder::new().max_rect(rect), |ui| {
|
||||
let resp = ui
|
||||
.scope_builder(UiBuilder::new().max_rect(rect), |ui| {
|
||||
(add_content)(ui, self, cb);
|
||||
}).response;
|
||||
})
|
||||
.response;
|
||||
|
||||
// Setup background size.
|
||||
let bg_rect = {
|
||||
@@ -347,25 +358,30 @@ fn title_ui(title: &String, ui: &mut egui::Ui) {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
|
||||
// Create background shape.
|
||||
let mut bg_shape = RectShape::new(rect, CornerRadius {
|
||||
let mut bg_shape = RectShape::new(
|
||||
rect,
|
||||
CornerRadius {
|
||||
nw: 8.0 as u8,
|
||||
ne: 8.0 as u8,
|
||||
sw: 0.0 as u8,
|
||||
se: 0.0 as u8,
|
||||
}, Colors::yellow(), Stroke::NONE, StrokeKind::Outside);
|
||||
},
|
||||
Colors::yellow(),
|
||||
Stroke::NONE,
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
// Draw title content.
|
||||
let resp = ui.vertical_centered(|ui| {
|
||||
let resp = ui
|
||||
.vertical_centered(|ui| {
|
||||
ui.add_space(Modal::DEFAULT_MARGIN + 2.0);
|
||||
ui.label(RichText::new(title)
|
||||
.size(19.0)
|
||||
.color(Colors::title(true))
|
||||
);
|
||||
ui.label(RichText::new(title).size(19.0).color(Colors::title(true)));
|
||||
ui.add_space(Modal::DEFAULT_MARGIN + 1.0);
|
||||
// Draw line below title.
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
}).response;
|
||||
})
|
||||
.response;
|
||||
|
||||
// Setup background size.
|
||||
bg_shape.rect = resp.rect;
|
||||
|
||||
@@ -13,19 +13,24 @@
|
||||
// limitations under the License.
|
||||
|
||||
use eframe::epaint::RectShape;
|
||||
use egui::{Align, Color32, CornerRadius, CursorIcon, Layout, RichText, Sense, StrokeKind, UiBuilder};
|
||||
use egui::{
|
||||
Align, Color32, CornerRadius, CursorIcon, Layout, RichText, Sense, StrokeKind, UiBuilder,
|
||||
};
|
||||
|
||||
use crate::gui::icons::{CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE_SIMPLE, PLUS_CIRCLE, POWER, QR_CODE, TRASH, WARNING_CIRCLE, X_CIRCLE};
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{
|
||||
CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE_SIMPLE, PLUS_CIRCLE, POWER, QR_CODE,
|
||||
TRASH, WARNING_CIRCLE, X_CIRCLE,
|
||||
};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::NodeSetup;
|
||||
use crate::gui::views::network::modals::{ExternalConnectionModal, ShareConnectionContent};
|
||||
use crate::gui::views::network::types::ShareConnection;
|
||||
use crate::gui::views::network::NodeSetup;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::node::{Node, NodeConfig};
|
||||
use crate::wallet::{ConnectionsConfig, ExternalConnection};
|
||||
use crate::AppConfig;
|
||||
|
||||
/// Network connections content.
|
||||
pub struct ConnectionsContent {
|
||||
@@ -36,7 +41,7 @@ pub struct ConnectionsContent {
|
||||
ext_conn_modal_content: ExternalConnectionModal,
|
||||
|
||||
/// [`Modal`] content to share connection with QR code.
|
||||
share_conn_modal_content: Option<ShareConnectionContent>
|
||||
share_conn_modal_content: Option<ShareConnectionContent>,
|
||||
}
|
||||
|
||||
impl Default for ConnectionsContent {
|
||||
@@ -44,7 +49,7 @@ impl Default for ConnectionsContent {
|
||||
Self {
|
||||
first_draw: true,
|
||||
ext_conn_modal_content: ExternalConnectionModal::new(None),
|
||||
share_conn_modal_content: None
|
||||
share_conn_modal_content: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,20 +59,14 @@ const SHARE_CONN_QR_MODAL: &'static str = "share_conn_qr_modal";
|
||||
|
||||
impl ContentContainer for ConnectionsContent {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
ExternalConnectionModal::NETWORK_ID,
|
||||
SHARE_CONN_QR_MODAL
|
||||
]
|
||||
vec![ExternalConnectionModal::NETWORK_ID, SHARE_CONN_QR_MODAL]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
ExternalConnectionModal::NETWORK_ID => {
|
||||
self.ext_conn_modal_content.ui(ui, cb, modal, |_| {});
|
||||
},
|
||||
}
|
||||
SHARE_CONN_QR_MODAL => {
|
||||
if let Some(c) = self.share_conn_modal_content.as_mut() {
|
||||
c.ui(ui, modal, cb);
|
||||
@@ -97,9 +96,13 @@ impl ContentContainer for ConnectionsContent {
|
||||
}
|
||||
|
||||
// Show integrated node info content.
|
||||
Self::integrated_node_item_ui(ui, Colors::fill_lite(), (true, || {
|
||||
Self::integrated_node_item_ui(
|
||||
ui,
|
||||
Colors::fill_lite(),
|
||||
(true, || {
|
||||
AppConfig::toggle_show_connections_network_panel();
|
||||
}), |ui| {
|
||||
}),
|
||||
|ui| {
|
||||
let r = View::item_rounding(0, 1, true);
|
||||
View::item_button(ui, r, QR_CODE, None, || {
|
||||
if let Ok(c) = ShareConnectionContent::new(ShareConnection {
|
||||
@@ -116,11 +119,16 @@ impl ContentContainer for ConnectionsContent {
|
||||
}
|
||||
});
|
||||
true
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Show external connections.
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::gray()));
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.ext_conn"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show button to add new external node connection.
|
||||
@@ -140,9 +148,16 @@ impl ContentContainer for ConnectionsContent {
|
||||
let mut show_qr_content: Option<ShareConnectionContent> = None;
|
||||
// Draw external connection list item.
|
||||
let bg = Colors::fill_lite();
|
||||
Self::ext_conn_item_ui(ui, bg, c, i, len, (true, || {
|
||||
Self::ext_conn_item_ui(
|
||||
ui,
|
||||
bg,
|
||||
c,
|
||||
i,
|
||||
len,
|
||||
(true, || {
|
||||
self.show_add_ext_conn_modal(Some(c.clone()));
|
||||
}), |ui| {
|
||||
}),
|
||||
|ui| {
|
||||
// Draw button to delete connection.
|
||||
let r = View::item_rounding(i, len, true);
|
||||
View::item_button(ui, r, TRASH, Some(Colors::inactive_text()), || {
|
||||
@@ -159,7 +174,8 @@ impl ContentContainer for ConnectionsContent {
|
||||
show_qr_content = Some(c);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
if let Some(c) = show_qr_content {
|
||||
self.share_conn_modal_content = Some(c);
|
||||
// Show QR code to share external connection.
|
||||
@@ -176,10 +192,12 @@ impl ContentContainer for ConnectionsContent {
|
||||
|
||||
impl ConnectionsContent {
|
||||
/// Draw integrated node connection item content.
|
||||
pub fn integrated_node_item_ui(ui: &mut egui::Ui,
|
||||
pub fn integrated_node_item_ui(
|
||||
ui: &mut egui::Ui,
|
||||
bg: Color32,
|
||||
on_click: (bool, impl FnOnce()),
|
||||
custom_button: impl FnOnce(&mut egui::Ui) -> bool) {
|
||||
custom_button: impl FnOnce(&mut egui::Ui) -> bool,
|
||||
) {
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(78.0);
|
||||
@@ -187,11 +205,13 @@ impl ConnectionsContent {
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
let res = ui.scope_builder(
|
||||
let res = ui
|
||||
.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
.max_rect(rect),
|
||||
|ui| {
|
||||
// Draw custom button.
|
||||
let extra_button = custom_button(ui);
|
||||
|
||||
@@ -206,7 +226,10 @@ impl ConnectionsContent {
|
||||
View::item_button(ui, rounding, POWER, Some(Colors::green()), || {
|
||||
Node::start();
|
||||
});
|
||||
} else if !Node::is_starting() && !Node::is_stopping() && !Node::is_restarting() {
|
||||
} else if !Node::is_starting()
|
||||
&& !Node::is_stopping()
|
||||
&& !Node::is_restarting()
|
||||
{
|
||||
View::item_button(ui, rounding, POWER, Some(Colors::red()), || {
|
||||
Node::stop(false);
|
||||
});
|
||||
@@ -214,15 +237,20 @@ impl ConnectionsContent {
|
||||
}
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
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!("network.node"))
|
||||
ui.label(
|
||||
RichText::new(t!("network.node"))
|
||||
.size(18.0)
|
||||
.color(Colors::title(false)));
|
||||
.color(Colors::title(false)),
|
||||
);
|
||||
});
|
||||
|
||||
// Setup node status text.
|
||||
@@ -236,21 +264,31 @@ impl ConnectionsContent {
|
||||
} else {
|
||||
DOTS_THREE_CIRCLE
|
||||
};
|
||||
let status_text = format!("{} {}", status_icon, if has_error {
|
||||
let status_text = format!(
|
||||
"{} {}",
|
||||
status_icon,
|
||||
if has_error {
|
||||
t!("error").into()
|
||||
} else {
|
||||
Node::get_sync_status_text()
|
||||
});
|
||||
}
|
||||
);
|
||||
View::ellipsize_text(ui, status_text, 15.0, Colors::text(false));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup node API address text.
|
||||
let api_address = NodeConfig::get_api_address();
|
||||
let address_text = format!("{} http://{}", COMPUTER_TOWER, api_address);
|
||||
ui.label(RichText::new(address_text).size(15.0).color(Colors::gray()));
|
||||
let address_text =
|
||||
format!("{} http://{}", COMPUTER_TOWER, api_address);
|
||||
ui.label(
|
||||
RichText::new(address_text).size(15.0).color(Colors::gray()),
|
||||
);
|
||||
})
|
||||
});
|
||||
}).response;
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.response;
|
||||
let (clickable, on_click) = on_click;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
@@ -266,13 +304,15 @@ impl ConnectionsContent {
|
||||
}
|
||||
|
||||
/// Draw external connection item content.
|
||||
pub fn ext_conn_item_ui(ui: &mut egui::Ui,
|
||||
pub fn ext_conn_item_ui(
|
||||
ui: &mut egui::Ui,
|
||||
bg: Color32,
|
||||
conn: &ExternalConnection,
|
||||
index: usize,
|
||||
len: usize,
|
||||
on_click: (bool, impl FnOnce()),
|
||||
custom_button: impl FnOnce(&mut egui::Ui)) {
|
||||
custom_button: impl FnOnce(&mut egui::Ui),
|
||||
) {
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(52.0);
|
||||
@@ -280,16 +320,21 @@ impl ConnectionsContent {
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
let res = ui.scope_builder(
|
||||
let res = ui
|
||||
.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
.max_rect(rect),
|
||||
|ui| {
|
||||
// Draw custom button.
|
||||
custom_button(ui);
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
layout_size,
|
||||
Layout::left_to_right(Align::Center),
|
||||
|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical(|ui| {
|
||||
// Draw connections URL.
|
||||
@@ -306,14 +351,22 @@ impl ConnectionsContent {
|
||||
format!("{} {}", X_CIRCLE, t!("network.not_available"))
|
||||
}
|
||||
} else {
|
||||
format!("{} {}", DOTS_THREE_CIRCLE, t!("network.availability_check"))
|
||||
format!(
|
||||
"{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("network.availability_check")
|
||||
)
|
||||
};
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::gray()));
|
||||
ui.label(
|
||||
RichText::new(status_text).size(15.0).color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.response;
|
||||
let (clickable, on_click) = on_click;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
|
||||
@@ -15,16 +15,21 @@
|
||||
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, GLOBE, POWER};
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{
|
||||
ARROW_LEFT, ARROWS_COUNTER_CLOCKWISE, 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};
|
||||
use crate::gui::views::network::{
|
||||
ConnectionsContent, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings,
|
||||
};
|
||||
use crate::gui::views::settings::SettingsContent;
|
||||
use crate::gui::views::types::{ContentContainer, LinePosition, TitleContentType, TitleType};
|
||||
use crate::gui::views::{Content, TitlePanel, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::node::{Node, NodeConfig, NodeError};
|
||||
use crate::AppConfig;
|
||||
|
||||
/// Network content.
|
||||
pub struct NetworkContent {
|
||||
@@ -58,11 +63,7 @@ impl NetworkContent {
|
||||
|
||||
// Show integrated node tabs content.
|
||||
if !show_connections && !show_settings {
|
||||
let side_padding = View::TAB_ITEMS_PADDING + if View::is_desktop() {
|
||||
0.0
|
||||
} else {
|
||||
4.0
|
||||
};
|
||||
let side_padding = View::TAB_ITEMS_PADDING + if View::is_desktop() { 0.0 } else { 4.0 };
|
||||
let tabs_margin = Margin {
|
||||
left: (View::get_left_inset() + side_padding) as i8,
|
||||
right: (View::far_right_inset_margin(ui) + side_padding) as i8,
|
||||
@@ -106,8 +107,8 @@ impl NetworkContent {
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Margin {
|
||||
left: (View::get_left_inset() + View::content_padding()) as i8,
|
||||
right: (View::far_right_inset_margin(ui) +
|
||||
View::content_padding()) as i8,
|
||||
right: (View::far_right_inset_margin(ui) + View::content_padding())
|
||||
as i8,
|
||||
top: 3.0 as i8,
|
||||
bottom: 4.0 as i8,
|
||||
},
|
||||
@@ -124,11 +125,13 @@ impl NetworkContent {
|
||||
ui.add_space(1.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
// Show application settings content.
|
||||
View::max_width_ui(ui,
|
||||
View::max_width_ui(
|
||||
ui,
|
||||
Content::SIDE_PANEL_WIDTH * 1.3,
|
||||
|ui| {
|
||||
c.ui(ui, cb);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
} else if self.node_tab_content.get_type() != NodeTabType::Settings {
|
||||
@@ -138,8 +141,9 @@ impl NetworkContent {
|
||||
node_error_ui(ui, err);
|
||||
} else if !Node::is_running() {
|
||||
disabled_node_ui(ui);
|
||||
} else if Node::get_stats().is_none() || Node::is_restarting() ||
|
||||
Node::is_stopping() {
|
||||
} else if Node::get_stats().is_none()
|
||||
|| Node::is_restarting() || Node::is_stopping()
|
||||
{
|
||||
NetworkContent::loading_ui(ui, None::<String>);
|
||||
} else {
|
||||
self.node_tab_content.tab_ui(ui, cb);
|
||||
@@ -289,7 +293,9 @@ impl NetworkContent {
|
||||
};
|
||||
|
||||
// Draw title panel.
|
||||
TitlePanel::new(Id::from("network_title_panel")).ui(TitleType::Single(title_content), |ui| {
|
||||
TitlePanel::new(Id::from("network_title_panel")).ui(
|
||||
TitleType::Single(title_content),
|
||||
|ui| {
|
||||
if show_settings {
|
||||
View::title_button_big(ui, ARROW_LEFT, |_| {
|
||||
self.settings_content = None;
|
||||
@@ -303,13 +309,16 @@ impl NetworkContent {
|
||||
self.settings_content = Some(SettingsContent::default());
|
||||
});
|
||||
}
|
||||
}, |ui| {
|
||||
},
|
||||
|ui| {
|
||||
if !dual_panel && !show_settings {
|
||||
View::title_button_big(ui, BRIEFCASE, |_| {
|
||||
Content::toggle_network_panel();
|
||||
});
|
||||
}
|
||||
}, ui);
|
||||
},
|
||||
ui,
|
||||
);
|
||||
}
|
||||
|
||||
/// Content to draw on loading.
|
||||
@@ -324,10 +333,7 @@ impl NetworkContent {
|
||||
View::center_content(ui, 162.0, |ui| {
|
||||
View::big_loading_spinner(ui);
|
||||
ui.add_space(18.0);
|
||||
ui.label(RichText::new(t)
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
);
|
||||
ui.label(RichText::new(t).size(16.0).color(Colors::inactive_text()));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -346,14 +352,19 @@ impl NetworkContent {
|
||||
fn disabled_node_ui(ui: &mut egui::Ui) {
|
||||
View::center_content(ui, 156.0, |ui| {
|
||||
let text = t!("network.disabled_server", "dots" => DOTS_THREE_OUTLINE_VERTICAL);
|
||||
ui.label(RichText::new(text)
|
||||
ui.label(
|
||||
RichText::new(text)
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
View::action_button(ui, format!("{} {}", POWER, t!("network.enable_node")), || {
|
||||
View::action_button(
|
||||
ui,
|
||||
format!("{} {}", POWER, t!("network.enable_node")),
|
||||
|| {
|
||||
Node::start();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(2.0);
|
||||
NetworkContent::autorun_node_ui(ui);
|
||||
});
|
||||
@@ -364,14 +375,13 @@ pub fn node_error_ui(ui: &mut egui::Ui, e: NodeError) {
|
||||
match e {
|
||||
NodeError::Storage => {
|
||||
View::center_content(ui, 156.0, |ui| {
|
||||
ui.label(RichText::new(t!("network_node.error_clean"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_node.error_clean"))
|
||||
.size(16.0)
|
||||
.color(Colors::red())
|
||||
.color(Colors::red()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
let btn_txt = format!("{} {}",
|
||||
ARROWS_COUNTER_CLOCKWISE,
|
||||
t!("network_node.resync"));
|
||||
let btn_txt = format!("{} {}", ARROWS_COUNTER_CLOCKWISE, t!("network_node.resync"));
|
||||
View::action_button(ui, btn_txt, || {
|
||||
Node::clean_up_data();
|
||||
Node::start();
|
||||
@@ -383,7 +393,7 @@ pub fn node_error_ui(ui: &mut egui::Ui, e: NodeError) {
|
||||
NodeError::P2P | NodeError::API => {
|
||||
let msg_type = match e {
|
||||
NodeError::API => "API",
|
||||
_ => "P2P"
|
||||
_ => "P2P",
|
||||
};
|
||||
View::center_content(ui, 106.0, |ui| {
|
||||
let text = t!(
|
||||
@@ -391,24 +401,24 @@ pub fn node_error_ui(ui: &mut egui::Ui, e: NodeError) {
|
||||
"p2p_api" => msg_type,
|
||||
"settings" => FADERS
|
||||
);
|
||||
ui.label(RichText::new(text)
|
||||
.size(16.0)
|
||||
.color(Colors::red())
|
||||
);
|
||||
ui.label(RichText::new(text).size(16.0).color(Colors::red()));
|
||||
ui.add_space(2.0);
|
||||
});
|
||||
return;
|
||||
}
|
||||
NodeError::Configuration => {
|
||||
View::center_content(ui, 106.0, |ui| {
|
||||
ui.label(RichText::new(t!("network_node.error_config", "settings" => FADERS))
|
||||
ui.label(
|
||||
RichText::new(t!("network_node.error_config", "settings" => FADERS))
|
||||
.size(16.0)
|
||||
.color(Colors::red())
|
||||
.color(Colors::red()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
let btn_txt = format!("{} {}",
|
||||
let btn_txt = format!(
|
||||
"{} {}",
|
||||
ARROWS_COUNTER_CLOCKWISE,
|
||||
t!("network_settings.reset"));
|
||||
t!("network_settings.reset")
|
||||
);
|
||||
View::action_button(ui, btn_txt, || {
|
||||
NodeConfig::reset_to_default();
|
||||
Node::start();
|
||||
@@ -418,14 +428,13 @@ pub fn node_error_ui(ui: &mut egui::Ui, e: NodeError) {
|
||||
}
|
||||
NodeError::Unknown => {
|
||||
View::center_content(ui, 156.0, |ui| {
|
||||
ui.label(RichText::new(t!("network_node.error_unknown", "settings" => FADERS))
|
||||
ui.label(
|
||||
RichText::new(t!("network_node.error_unknown", "settings" => FADERS))
|
||||
.size(16.0)
|
||||
.color(Colors::red())
|
||||
.color(Colors::red()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
let btn_txt = format!("{} {}",
|
||||
ARROWS_COUNTER_CLOCKWISE,
|
||||
t!("network_node.resync"));
|
||||
let btn_txt = format!("{} {}", ARROWS_COUNTER_CLOCKWISE, t!("network_node.resync"));
|
||||
View::action_button(ui, btn_txt, || {
|
||||
Node::clean_up_data();
|
||||
Node::start();
|
||||
|
||||
@@ -12,17 +12,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{RichText, CornerRadius, ScrollArea, vec2, StrokeKind};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{CornerRadius, RichText, ScrollArea, StrokeKind, vec2};
|
||||
use grin_core::consensus::{DAY_HEIGHT, GRIN_BASE, HOUR_SEC, REWARD};
|
||||
use grin_servers::{DiffBlock, ServerStats};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{AT, COINS, CUBE_TRANSPARENT, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Content, View};
|
||||
use crate::gui::views::network::NetworkContent;
|
||||
use crate::gui::views::network::types::{NodeTab, NodeTabType};
|
||||
use crate::gui::views::{Content, View};
|
||||
use crate::node::Node;
|
||||
|
||||
/// Chain metrics tab content.
|
||||
@@ -64,22 +64,28 @@ fn info_ui(ui: &mut egui::Ui, stats: &ServerStats) {
|
||||
let rate = (YEARLY_SUPPLY * 100) / supply;
|
||||
|
||||
columns[0].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
format!("{}ツ", BLOCK_REWARD),
|
||||
t!("network_metrics.reward"),
|
||||
[true, false, true, false]);
|
||||
[true, false, true, false],
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
format!("{:.2}%", rate),
|
||||
t!("network_metrics.inflation"),
|
||||
[false, false, false, false]);
|
||||
[false, false, false, false],
|
||||
);
|
||||
});
|
||||
columns[2].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
supply.to_string(),
|
||||
t!("network_metrics.supply"),
|
||||
[false, true, false, true]);
|
||||
[false, true, false, true],
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(5.0);
|
||||
@@ -92,22 +98,28 @@ fn info_ui(ui: &mut egui::Ui, stats: &ServerStats) {
|
||||
View::sub_title(ui, format!("{} {}", HOURGLASS_MEDIUM, difficulty_title));
|
||||
ui.columns(3, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stats.diff_stats.height.to_string(),
|
||||
t!("network_node.height"),
|
||||
[true, false, true, false]);
|
||||
[true, false, true, false],
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
format!("{}s", stats.diff_stats.average_block_time),
|
||||
t!("network_metrics.block_time"),
|
||||
[false, false, false, false]);
|
||||
[false, false, false, false],
|
||||
);
|
||||
});
|
||||
columns[2].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stats.diff_stats.average_difficulty.to_string(),
|
||||
t!("network_node.difficulty"),
|
||||
[false, true, false, true]);
|
||||
[false, true, false, true],
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -123,18 +135,13 @@ fn blocks_ui(ui: &mut egui::Ui, stats: &ServerStats) {
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.auto_shrink([false; 2])
|
||||
.stick_to_bottom(true)
|
||||
.show_rows(
|
||||
ui,
|
||||
BLOCK_ITEM_HEIGHT,
|
||||
blocks_size,
|
||||
|ui, row_range| {
|
||||
.show_rows(ui, BLOCK_ITEM_HEIGHT, blocks_size, |ui, row_range| {
|
||||
ui.add_space(4.0);
|
||||
for index in row_range {
|
||||
let db = stats.diff_stats.last_blocks.get(index).unwrap();
|
||||
block_item_ui(ui, db, View::item_rounding(index, blocks_size, false));
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw block difficulty item.
|
||||
@@ -150,42 +157,48 @@ fn block_item_ui(ui: &mut egui::Ui, db: &DiffBlock, rounding: CornerRadius) {
|
||||
// Draw round background.
|
||||
rect.min += vec2(8.0, 0.0);
|
||||
rect.max -= vec2(8.0, 0.0);
|
||||
ui.painter().rect(rect,
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
|
||||
// Draw block hash.
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new(db.block_hash.to_string())
|
||||
ui.label(
|
||||
RichText::new(db.block_hash.to_string())
|
||||
.color(Colors::white_or_black(true))
|
||||
.size(17.0));
|
||||
.size(17.0),
|
||||
);
|
||||
});
|
||||
// Draw block difficulty and height.
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(7.0);
|
||||
let diff_text = format!("{} {} {} {}",
|
||||
CUBE_TRANSPARENT,
|
||||
db.difficulty,
|
||||
AT,
|
||||
db.block_height);
|
||||
ui.label(RichText::new(diff_text)
|
||||
let diff_text = format!(
|
||||
"{} {} {} {}",
|
||||
CUBE_TRANSPARENT, db.difficulty, AT, db.block_height
|
||||
);
|
||||
ui.label(
|
||||
RichText::new(diff_text)
|
||||
.color(Colors::title(false))
|
||||
.size(15.0));
|
||||
.size(15.0),
|
||||
);
|
||||
});
|
||||
// Draw block date.
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(7.0);
|
||||
let block_time = View::format_time(db.time as i64);
|
||||
ui.label(RichText::new(format!("{} {}s {} {}",
|
||||
TIMER,
|
||||
db.duration,
|
||||
HOURGLASS_LOW,
|
||||
block_time))
|
||||
ui.label(
|
||||
RichText::new(format!(
|
||||
"{} {}s {} {}",
|
||||
TIMER, db.duration, HOURGLASS_LOW, block_time
|
||||
))
|
||||
.color(Colors::gray())
|
||||
.size(15.0));
|
||||
.size(15.0),
|
||||
);
|
||||
});
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
|
||||
@@ -12,19 +12,22 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{RichText, CornerRadius, ScrollArea, StrokeKind};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{CornerRadius, RichText, ScrollArea, StrokeKind};
|
||||
use grin_chain::SyncStatus;
|
||||
use grin_servers::WorkerStats;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_SIMPLE_MINUS, FOLDER_SIMPLE_PLUS, HARD_DRIVES, PLUGS, PLUGS_CONNECTED, POLYGON};
|
||||
use crate::gui::icons::{
|
||||
BARBELL, CLOCK_AFTERNOON, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_SIMPLE_MINUS,
|
||||
FOLDER_SIMPLE_PLUS, HARD_DRIVES, PLUGS, PLUGS_CONNECTED, POLYGON,
|
||||
};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Content, View};
|
||||
use crate::gui::views::network::NetworkContent;
|
||||
use crate::gui::views::network::setup::StratumSetup;
|
||||
use crate::gui::views::network::types::{NodeTab, NodeTabType};
|
||||
use crate::gui::views::types::ContentContainer;
|
||||
use crate::gui::views::{Content, View};
|
||||
use crate::node::{Node, NodeConfig};
|
||||
|
||||
/// Mining tab content.
|
||||
@@ -71,23 +74,30 @@ impl NodeTab for NetworkMining {
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Show stratum mining server info.
|
||||
View::sub_title(ui, format!("{} {}", HARD_DRIVES, t!("network_mining.server")));
|
||||
View::sub_title(
|
||||
ui,
|
||||
format!("{} {}", HARD_DRIVES, t!("network_mining.server")),
|
||||
);
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
let (stratum_addr, stratum_port) = NodeConfig::get_stratum_address();
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
format!("{}:{}", stratum_addr, stratum_port),
|
||||
t!("network_mining.address"),
|
||||
[true, false, true, false]);
|
||||
[true, false, true, false],
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
self.stratum_server_setup
|
||||
.wallet_name
|
||||
.clone()
|
||||
.unwrap_or("-".to_string()),
|
||||
t!("network_mining.rewards_wallet"),
|
||||
[false, true, false, true]);
|
||||
[false, true, false, true],
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
@@ -101,10 +111,12 @@ impl NodeTab for NetworkMining {
|
||||
} else {
|
||||
"-".into()
|
||||
};
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
difficulty,
|
||||
t!("network_node.difficulty"),
|
||||
[true, false, true, false]);
|
||||
[true, false, true, false],
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
let block_height = if stratum_stats.block_height > 0 {
|
||||
@@ -112,10 +124,12 @@ impl NodeTab for NetworkMining {
|
||||
} else {
|
||||
"-".into()
|
||||
};
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
block_height,
|
||||
t!("network_node.header"),
|
||||
[false, false, false, false]);
|
||||
[false, false, false, false],
|
||||
);
|
||||
});
|
||||
columns[2].vertical_centered(|ui| {
|
||||
let hashrate = if stratum_stats.network_hashrate > 0.0 {
|
||||
@@ -123,10 +137,12 @@ impl NodeTab for NetworkMining {
|
||||
} else {
|
||||
"-".into()
|
||||
};
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
hashrate,
|
||||
t!("network_mining.hashrate", "bits" => stratum_stats.edge_bits),
|
||||
[false, true, false, true]);
|
||||
[false, true, false, true],
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
@@ -135,17 +151,21 @@ impl NodeTab for NetworkMining {
|
||||
View::sub_title(ui, format!("{} {}", CPU, t!("network_mining.miners")));
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stratum_stats.num_workers.to_string(),
|
||||
t!("network_mining.devices"),
|
||||
[true, false, true, false]);
|
||||
[true, false, true, false],
|
||||
);
|
||||
});
|
||||
|
||||
columns[1].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stratum_stats.blocks_found.to_string(),
|
||||
t!("network_mining.blocks_found"),
|
||||
[false, true, false, true]);
|
||||
[false, true, false, true],
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
@@ -160,11 +180,7 @@ impl NodeTab for NetworkMining {
|
||||
.id_salt("stratum_workers_scroll")
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.auto_shrink([false; 2])
|
||||
.show_rows(
|
||||
ui,
|
||||
WORKER_ITEM_HEIGHT,
|
||||
workers_size,
|
||||
|ui, row_range| {
|
||||
.show_rows(ui, WORKER_ITEM_HEIGHT, workers_size, |ui, row_range| {
|
||||
for index in row_range {
|
||||
// Add space before the first item.
|
||||
if index == 0 {
|
||||
@@ -174,13 +190,13 @@ impl NodeTab for NetworkMining {
|
||||
let item_rounding = View::item_rounding(index, workers_size, false);
|
||||
worker_item_ui(ui, worker, item_rounding);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
} else if ui.available_height() > 142.0 {
|
||||
View::center_content(ui, 142.0, |ui| {
|
||||
ui.label(RichText::new(t!("network_mining.info", "settings" => FADERS))
|
||||
ui.label(
|
||||
RichText::new(t!("network_mining.info", "settings" => FADERS))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -197,11 +213,13 @@ fn worker_item_ui(ui: &mut egui::Ui, ws: &WorkerStats, rounding: CornerRadius) {
|
||||
// Draw round background.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(WORKER_ITEM_HEIGHT);
|
||||
ui.painter().rect(rect,
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
rounding,
|
||||
Colors::white_or_black(false),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
|
||||
ui.add_space(2.0);
|
||||
ui.horizontal(|ui| {
|
||||
@@ -212,14 +230,20 @@ fn worker_item_ui(ui: &mut egui::Ui, ws: &WorkerStats, rounding: CornerRadius) {
|
||||
true => (
|
||||
t!("network_mining.connected"),
|
||||
PLUGS_CONNECTED,
|
||||
Colors::white_or_black(true)
|
||||
Colors::white_or_black(true),
|
||||
),
|
||||
false => (
|
||||
t!("network_mining.disconnected"),
|
||||
PLUGS,
|
||||
Colors::inactive_text(),
|
||||
),
|
||||
false => (t!("network_mining.disconnected"), PLUGS, Colors::inactive_text())
|
||||
};
|
||||
let status_line_text = format!("{} {} {}", status_icon, ws.id, status_text);
|
||||
ui.heading(RichText::new(status_line_text)
|
||||
ui.heading(
|
||||
RichText::new(status_line_text)
|
||||
.color(status_color)
|
||||
.size(17.0));
|
||||
.size(17.0),
|
||||
);
|
||||
ui.add_space(2.0);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
@@ -227,48 +251,52 @@ fn worker_item_ui(ui: &mut egui::Ui, ws: &WorkerStats, rounding: CornerRadius) {
|
||||
|
||||
// Draw difficulty.
|
||||
let diff_text = format!("{} {}", BARBELL, ws.pow_difficulty);
|
||||
ui.heading(RichText::new(diff_text)
|
||||
ui.heading(
|
||||
RichText::new(diff_text)
|
||||
.color(Colors::title(false))
|
||||
.size(16.0));
|
||||
.size(16.0),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw accepted shares.
|
||||
let accepted_text = format!("{} {}", FOLDER_SIMPLE_PLUS, ws.num_accepted);
|
||||
ui.heading(RichText::new(accepted_text)
|
||||
ui.heading(
|
||||
RichText::new(accepted_text)
|
||||
.color(Colors::green())
|
||||
.size(16.0));
|
||||
.size(16.0),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw rejected shares.
|
||||
let rejected_text = format!("{} {}", FOLDER_SIMPLE_MINUS, ws.num_rejected);
|
||||
ui.heading(RichText::new(rejected_text)
|
||||
.color(Colors::red())
|
||||
.size(16.0));
|
||||
ui.heading(RichText::new(rejected_text).color(Colors::red()).size(16.0));
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw stale shares.
|
||||
let stale_text = format!("{} {}", FOLDER_DASHED, ws.num_stale);
|
||||
ui.heading(RichText::new(stale_text)
|
||||
.color(Colors::gray())
|
||||
.size(16.0));
|
||||
ui.heading(RichText::new(stale_text).color(Colors::gray()).size(16.0));
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw blocks found.
|
||||
let blocks_found_text = format!("{} {}", CUBE, ws.num_blocks_found);
|
||||
ui.heading(RichText::new(blocks_found_text)
|
||||
ui.heading(
|
||||
RichText::new(blocks_found_text)
|
||||
.color(Colors::title(false))
|
||||
.size(16.0));
|
||||
.size(16.0),
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw block time
|
||||
let seen_ts = ws.last_seen.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
|
||||
let seen_ts = ws
|
||||
.last_seen
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
let seen_time = View::format_time(seen_ts as i64);
|
||||
let seen_text = format!("{} {}", CLOCK_AFTERNOON, seen_time);
|
||||
ui.heading(RichText::new(seen_text)
|
||||
.color(Colors::gray())
|
||||
.size(16.0));
|
||||
ui.heading(RichText::new(seen_text).color(Colors::gray()).size(16.0));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,5 +33,5 @@ pub use content::*;
|
||||
mod connections;
|
||||
pub use connections::*;
|
||||
|
||||
pub mod types;
|
||||
pub mod modals;
|
||||
pub mod types;
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
use egui::{Id, RichText};
|
||||
use url::Url;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::SCAN;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::types::ShareConnection;
|
||||
use crate::gui::views::{CameraContent, Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::{ConnectionsConfig, ExternalConnection};
|
||||
|
||||
/// Content to create or update external wallet connection.
|
||||
@@ -65,16 +65,18 @@ impl ExternalConnectionModal {
|
||||
url_error: false,
|
||||
secret_edit,
|
||||
id,
|
||||
scan_qr_content: None
|
||||
scan_qr_content: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw external connection [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
modal: &Modal,
|
||||
on_save: impl Fn(ExternalConnection)) {
|
||||
on_save: impl Fn(ExternalConnection),
|
||||
) {
|
||||
// Show QR code scanner content.
|
||||
if let Some(scan_content) = self.scan_qr_content.as_mut() {
|
||||
if let Some(result) = scan_content.qr_scan_result() {
|
||||
@@ -155,9 +157,11 @@ impl ExternalConnectionModal {
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.node_url"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.node_url"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw node URL text edit.
|
||||
@@ -181,9 +185,11 @@ impl ExternalConnectionModal {
|
||||
// username_edit.ui(ui, &mut self.username_edit, cb);
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new(t!("wallets.node_secret"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.node_secret"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw node API secret text edit.
|
||||
@@ -204,9 +210,11 @@ impl ExternalConnectionModal {
|
||||
// Show error when specified URL is not valid.
|
||||
if self.url_error {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("wallets.invalid_url"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.invalid_url"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
|
||||
@@ -229,22 +237,32 @@ impl ExternalConnectionModal {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
self.url_edit = "".to_string();
|
||||
self.secret_edit = "".to_string();
|
||||
self.url_error = false;
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button_ui(ui, if self.id.is_some() {
|
||||
View::button_ui(
|
||||
ui,
|
||||
if self.id.is_some() {
|
||||
t!("modal.save")
|
||||
} else {
|
||||
t!("modal.add")
|
||||
}, Colors::white_or_black(false), |ui| {
|
||||
},
|
||||
Colors::white_or_black(false),
|
||||
|ui| {
|
||||
(on_add)(ui, self);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{RichText, CornerRadius, ScrollArea, StrokeKind};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{CornerRadius, RichText, ScrollArea, StrokeKind};
|
||||
use grin_servers::PeerStats;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, SHARE_NETWORK};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Content, View};
|
||||
use crate::gui::views::network::types::{NodeTab, NodeTabType};
|
||||
use crate::gui::views::{Content, View};
|
||||
use crate::node::{Node, NodeConfig};
|
||||
|
||||
/// Integrated node tab content.
|
||||
@@ -56,32 +56,40 @@ fn node_stats_ui(ui: &mut egui::Ui) {
|
||||
View::sub_title(ui, format!("{} {}", FLOW_ARROW, t!("network_node.header")));
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stats.header_stats.last_block_h.to_string(),
|
||||
t!("network_node.hash"),
|
||||
[true, false, false, false]);
|
||||
[true, false, false, false],
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stats.header_stats.height.to_string(),
|
||||
t!("network_node.height"),
|
||||
[false, true, false, false]);
|
||||
[false, true, false, false],
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stats.header_stats.total_difficulty.to_string(),
|
||||
t!("network_node.difficulty"),
|
||||
[false, false, true, false]);
|
||||
[false, false, true, false],
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
let h_ts = stats.header_stats.latest_timestamp.timestamp();
|
||||
let h_time = View::format_time(h_ts);
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
h_time,
|
||||
t!("network_node.time"),
|
||||
[false, false, false, true]);
|
||||
[false, false, false, true],
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(5.0);
|
||||
@@ -90,32 +98,40 @@ fn node_stats_ui(ui: &mut egui::Ui) {
|
||||
View::sub_title(ui, format!("{} {}", CUBE, t!("network_node.block")));
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stats.chain_stats.last_block_h.to_string(),
|
||||
t!("network_node.hash"),
|
||||
[true, false, false, false]);
|
||||
[true, false, false, false],
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stats.chain_stats.height.to_string(),
|
||||
t!("network_node.height"),
|
||||
[false, true, false, false]);
|
||||
[false, true, false, false],
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stats.chain_stats.total_difficulty.to_string(),
|
||||
t!("network_node.difficulty"),
|
||||
[false, false, true, false]);
|
||||
[false, false, true, false],
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
let b_ts = stats.chain_stats.latest_timestamp.timestamp();
|
||||
let b_time = View::format_time(b_ts);
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
b_time,
|
||||
t!("network_node.time"),
|
||||
[false, false, false, true]);
|
||||
[false, false, false, true],
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(5.0);
|
||||
@@ -126,38 +142,49 @@ fn node_stats_ui(ui: &mut egui::Ui) {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
let tx_stat = match &stats.tx_stats {
|
||||
None => "0 (0)".to_string(),
|
||||
Some(tx) => format!("{} ({})", tx.tx_pool_size, tx.tx_pool_kernels)
|
||||
Some(tx) => format!("{} ({})", tx.tx_pool_size, tx.tx_pool_kernels),
|
||||
};
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
tx_stat,
|
||||
t!("network_node.main_pool"),
|
||||
[true, false, false, false]);
|
||||
[true, false, false, false],
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
let stem_tx_stat = match &stats.tx_stats {
|
||||
None => "0 (0)".to_string(),
|
||||
Some(stx) => format!("{} ({})",
|
||||
stx.stem_pool_size,
|
||||
stx.stem_pool_kernels)
|
||||
Some(stx) => format!("{} ({})", stx.stem_pool_size, stx.stem_pool_kernels),
|
||||
};
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stem_tx_stat,
|
||||
t!("network_node.stem_pool"),
|
||||
[false, true, false, false]);
|
||||
[false, true, false, false],
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
View::label_box(ui,
|
||||
View::label_box(
|
||||
ui,
|
||||
stats.disk_usage_gb.to_string(),
|
||||
t!("network_node.size"),
|
||||
[false, false, true, false]);
|
||||
[false, false, true, false],
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
let peers_txt = format!("{} ({})",
|
||||
let peers_txt = format!(
|
||||
"{} ({})",
|
||||
stats.peer_count,
|
||||
NodeConfig::get_max_outbound_peers());
|
||||
View::label_box(ui, peers_txt, t!("network_node.peers"), [false, false, false, true]);
|
||||
NodeConfig::get_max_outbound_peers()
|
||||
);
|
||||
View::label_box(
|
||||
ui,
|
||||
peers_txt,
|
||||
t!("network_node.peers"),
|
||||
[false, false, false, true],
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(5.0);
|
||||
@@ -184,34 +211,41 @@ fn peer_item_ui(ui: &mut egui::Ui, peer: &PeerStats, r: CornerRadius) {
|
||||
ui.add_space(4.0);
|
||||
|
||||
// Draw round background.
|
||||
ui.painter().rect(rect, r, Colors::fill(), View::item_stroke(), StrokeKind::Outside);
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
r,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
|
||||
// Draw IP address.
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(7.0);
|
||||
ui.label(RichText::new(&peer.addr)
|
||||
ui.label(
|
||||
RichText::new(&peer.addr)
|
||||
.color(Colors::white_or_black(true))
|
||||
.size(17.0));
|
||||
.size(17.0),
|
||||
);
|
||||
});
|
||||
// Draw difficulty and height.
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(6.0);
|
||||
let diff_text = format!("{} {} {} {}",
|
||||
PACKAGE,
|
||||
peer.total_difficulty,
|
||||
AT,
|
||||
peer.height);
|
||||
ui.label(RichText::new(diff_text)
|
||||
let diff_text = format!(
|
||||
"{} {} {} {}",
|
||||
PACKAGE, peer.total_difficulty, AT, peer.height
|
||||
);
|
||||
ui.label(
|
||||
RichText::new(diff_text)
|
||||
.color(Colors::title(false))
|
||||
.size(15.0));
|
||||
.size(15.0),
|
||||
);
|
||||
});
|
||||
// Draw user-agent.
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(6.0);
|
||||
let agent_text = format!("{} {}", DEVICES, &peer.user_agent);
|
||||
ui.label(RichText::new(agent_text)
|
||||
.color(Colors::gray())
|
||||
.size(15.0));
|
||||
ui.label(RichText::new(agent_text).color(Colors::gray()).size(15.0));
|
||||
});
|
||||
|
||||
ui.add_space(3.0);
|
||||
|
||||
@@ -12,16 +12,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{RichText, ScrollArea};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{RichText, ScrollArea};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_COUNTER_CLOCKWISE, TRASH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Content, View};
|
||||
use crate::gui::views::network::setup::{DandelionSetup, NodeSetup, P2PSetup, PoolSetup, StratumSetup};
|
||||
use crate::gui::views::network::setup::{
|
||||
DandelionSetup, NodeSetup, P2PSetup, PoolSetup, StratumSetup,
|
||||
};
|
||||
use crate::gui::views::network::types::{NodeTab, NodeTabType};
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Content, Modal, View};
|
||||
use crate::node::{Node, NodeConfig};
|
||||
|
||||
/// Integrated node settings tab content.
|
||||
@@ -59,15 +61,10 @@ impl Default for NetworkSettings {
|
||||
|
||||
impl ContentContainer for NetworkSettings {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
RESET_SETTINGS_CONFIRMATION_MODAL
|
||||
]
|
||||
vec![RESET_SETTINGS_CONFIRMATION_MODAL]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
_: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, _: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
RESET_SETTINGS_CONFIRMATION_MODAL => reset_settings_confirmation_modal(ui),
|
||||
_ => {}
|
||||
@@ -150,18 +147,21 @@ impl NetworkSettings {
|
||||
pub fn node_restart_required_ui(ui: &mut egui::Ui) {
|
||||
if Node::is_running() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.restart_node_required"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.restart_node_required"))
|
||||
.size(16.0)
|
||||
.color(Colors::green())
|
||||
.color(Colors::green()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw IP addresses as radio buttons.
|
||||
pub fn ip_addrs_ui(ui: &mut egui::Ui,
|
||||
pub fn ip_addrs_ui(
|
||||
ui: &mut egui::Ui,
|
||||
saved_ip: &String,
|
||||
ips: &Vec<String>,
|
||||
on_change: impl FnOnce(&String)) {
|
||||
on_change: impl FnOnce(&String),
|
||||
) {
|
||||
let mut selected_ip = saved_ip;
|
||||
|
||||
// Set first IP address as current if saved is not present at system.
|
||||
@@ -172,7 +172,9 @@ impl NetworkSettings {
|
||||
ui.add_space(2.0);
|
||||
|
||||
// Show available IP addresses on the system.
|
||||
let _ = ips.chunks(2).map(|x| {
|
||||
let _ = ips
|
||||
.chunks(2)
|
||||
.map(|x| {
|
||||
if x.len() == 2 {
|
||||
ui.columns(2, |columns| {
|
||||
let ip_left = x.get(0).unwrap();
|
||||
@@ -189,7 +191,8 @@ impl NetworkSettings {
|
||||
View::radio_value(ui, &mut selected_ip, ip, ip.to_string());
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
}).collect::<Vec<_>>();
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if saved_ip != selected_ip {
|
||||
on_change(&selected_ip.to_string());
|
||||
@@ -199,9 +202,10 @@ impl NetworkSettings {
|
||||
/// Show message when IP addresses are not available at system.
|
||||
pub fn no_ip_address_ui(ui: &mut egui::Ui) {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network.no_ips"))
|
||||
ui.label(
|
||||
RichText::new(t!("network.no_ips"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
@@ -210,32 +214,39 @@ impl NetworkSettings {
|
||||
/// Draw content to reset data.
|
||||
fn reset_data_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.add_space(4.0);
|
||||
View::colored_text_button(ui,
|
||||
View::colored_text_button(
|
||||
ui,
|
||||
format!("{} {}", TRASH, t!("network_settings.reset_data")),
|
||||
Colors::red(),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Node::reset_data(false);
|
||||
self.data_reset = true;
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("network_settings.reset_data_desc"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.reset_data_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Draw button to reset integrated node settings to default values.
|
||||
fn reset_settings_ui(ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.reset_settings_desc"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.reset_settings_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::text(false)));
|
||||
.color(Colors::text(false)),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
let button_text = format!("{} {}",
|
||||
let button_text = format!(
|
||||
"{} {}",
|
||||
ARROW_COUNTER_CLOCKWISE,
|
||||
t!("network_settings.reset_settings"));
|
||||
t!("network_settings.reset_settings")
|
||||
);
|
||||
View::action_button(ui, button_text, || {
|
||||
// Show modal to confirm settings reset.
|
||||
Modal::new(RESET_SETTINGS_CONFIRMATION_MODAL)
|
||||
@@ -247,9 +258,10 @@ fn reset_settings_ui(ui: &mut egui::Ui) {
|
||||
// Show reminder to restart enabled node.
|
||||
if Node::is_running() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.restart_node_required"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.restart_node_required"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
@@ -260,9 +272,11 @@ fn reset_settings_confirmation_modal(ui: &mut egui::Ui) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
let reset_text = format!("{}?", t!("network_settings.reset_settings_desc"));
|
||||
ui.label(RichText::new(reset_text)
|
||||
ui.label(
|
||||
RichText::new(reset_text)
|
||||
.size(17.0)
|
||||
.color(Colors::text(false)));
|
||||
.color(Colors::text(false)),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
|
||||
@@ -273,15 +287,25 @@ fn reset_settings_confirmation_modal(ui: &mut egui::Ui) {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("network_settings.reset"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("network_settings.reset"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
NodeConfig::reset_to_default();
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
use egui::{Id, RichText};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CLOCK_COUNTDOWN, GRAPH, TIMER, WATCH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::NetworkSettings;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::views::network::NetworkSettings;
|
||||
use crate::node::NodeConfig;
|
||||
|
||||
/// Dandelion server setup section content.
|
||||
@@ -63,14 +63,11 @@ impl ContentContainer for DandelionSetup {
|
||||
EPOCH_MODAL,
|
||||
EMBARGO_MODAL,
|
||||
AGGREGATION_MODAL,
|
||||
STEM_PROBABILITY_MODAL
|
||||
STEM_PROBABILITY_MODAL,
|
||||
]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
EPOCH_MODAL => self.epoch_modal(ui, modal, cb),
|
||||
EMBARGO_MODAL => self.embargo_modal(ui, modal, cb),
|
||||
@@ -127,14 +124,19 @@ impl ContentContainer for DandelionSetup {
|
||||
impl DandelionSetup {
|
||||
/// Draw epoch duration setup content.
|
||||
fn epoch_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.epoch_duration"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.epoch_duration"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let epoch = NodeConfig::get_dandelion_epoch();
|
||||
View::button(ui, format!("{} {}", WATCH, &epoch), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", WATCH, &epoch),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.epoch_edit = epoch;
|
||||
// Show epoch setup modal.
|
||||
@@ -142,7 +144,8 @@ impl DandelionSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -158,9 +161,11 @@ impl DandelionSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.epoch_duration"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.epoch_duration"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw epoch text edit.
|
||||
@@ -173,9 +178,11 @@ impl DandelionSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.epoch_edit.parse::<u16>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -189,10 +196,15 @@ impl DandelionSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -206,21 +218,27 @@ impl DandelionSetup {
|
||||
|
||||
/// Draw embargo expiration time setup content.
|
||||
fn embargo_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.embargo_timer"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.embargo_timer"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let embargo = NodeConfig::get_dandelion_embargo();
|
||||
View::button(ui, format!("{} {}", TIMER, &embargo), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", TIMER, &embargo),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
self.embargo_edit = embargo;
|
||||
// Show embargo setup modal.
|
||||
Modal::new(EMBARGO_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -236,9 +254,11 @@ impl DandelionSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.embargo_timer"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.embargo_timer"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw embargo text edit.
|
||||
@@ -251,9 +271,11 @@ impl DandelionSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.embargo_edit.parse::<u16>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -267,10 +289,15 @@ impl DandelionSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -284,14 +311,19 @@ impl DandelionSetup {
|
||||
|
||||
/// Draw aggregation period setup content.
|
||||
fn aggregation_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.aggregation_period"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.aggregation_period"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let ag = NodeConfig::get_dandelion_aggregation();
|
||||
View::button(ui, format!("{} {}", CLOCK_COUNTDOWN, &ag), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", CLOCK_COUNTDOWN, &ag),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.aggregation_edit = ag;
|
||||
// Show aggregation setup modal.
|
||||
@@ -299,7 +331,8 @@ impl DandelionSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -315,9 +348,11 @@ impl DandelionSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.aggregation_period"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.aggregation_period"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw aggregation period text edit.
|
||||
@@ -330,9 +365,11 @@ impl DandelionSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.aggregation_edit.parse::<u16>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -346,10 +383,15 @@ impl DandelionSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -363,14 +405,19 @@ impl DandelionSetup {
|
||||
|
||||
/// Draw stem phase probability setup content.
|
||||
fn stem_prob_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.stem_probability"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.stem_probability"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let stem_prob = NodeConfig::get_stem_probability();
|
||||
View::button(ui, format!("{}%", &stem_prob), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
format!("{}%", &stem_prob),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.stem_prob_edit = stem_prob;
|
||||
// Show stem probability setup modal.
|
||||
@@ -378,7 +425,8 @@ impl DandelionSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -394,9 +442,11 @@ impl DandelionSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.stem_probability"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.stem_probability"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw stem phase probability text edit.
|
||||
@@ -409,9 +459,11 @@ impl DandelionSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.stem_prob_edit.parse::<u8>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -425,10 +477,15 @@ impl DandelionSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
|
||||
@@ -17,15 +17,17 @@ use eframe::epaint::{RectShape, StrokeKind};
|
||||
use egui::{CursorIcon, Id, Layout, RichText, Sense, UiBuilder};
|
||||
use grin_core::global::ChainTypes;
|
||||
|
||||
use crate::gui::icons::{CLOCK_CLOCKWISE, COMPUTER_TOWER, FOLDERS, PLUG, POWER, SHIELD, SHIELD_SLASH};
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{
|
||||
CLOCK_CLOCKWISE, COMPUTER_TOWER, FOLDERS, PLUG, POWER, SHIELD, SHIELD_SLASH,
|
||||
};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::settings::NetworkSettings;
|
||||
use crate::gui::views::network::NetworkContent;
|
||||
use crate::gui::views::network::settings::NetworkSettings;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{FilePickContent, FilePickContentType, Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::node::{Node, NodeConfig};
|
||||
use crate::AppConfig;
|
||||
|
||||
/// Integrated node general setup section content.
|
||||
pub struct NodeSetup {
|
||||
@@ -69,9 +71,11 @@ impl Default for NodeSetup {
|
||||
let is_api_port_available = NodeConfig::is_api_port_available(&api_ip, &api_port);
|
||||
Self {
|
||||
data_path_edit: NodeConfig::get_chain_data_path(),
|
||||
pick_data_dir: FilePickContent::new(
|
||||
FilePickContentType::ItemButton(View::item_rounding(0, 1, true))
|
||||
).no_parse().pick_folder(),
|
||||
pick_data_dir: FilePickContent::new(FilePickContentType::ItemButton(
|
||||
View::item_rounding(0, 1, true),
|
||||
))
|
||||
.no_parse()
|
||||
.pick_folder(),
|
||||
available_ips: NodeConfig::get_ip_addrs(),
|
||||
api_port_edit: api_port,
|
||||
api_port_available_edit: is_api_port_available,
|
||||
@@ -89,14 +93,11 @@ impl ContentContainer for NodeSetup {
|
||||
API_PORT_MODAL,
|
||||
API_SECRET_MODAL,
|
||||
FOREIGN_API_SECRET_MODAL,
|
||||
FTL_MODAL
|
||||
FTL_MODAL,
|
||||
]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
DATA_PATH_MODAL => self.data_path_edit_modal_ui(ui, cb),
|
||||
API_PORT_MODAL => self.api_port_modal(ui, modal, cb),
|
||||
@@ -108,7 +109,10 @@ impl ContentContainer for NodeSetup {
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
View::sub_title(ui, format!("{} {}", COMPUTER_TOWER, t!("network_settings.server")));
|
||||
View::sub_title(
|
||||
ui,
|
||||
format!("{} {}", COMPUTER_TOWER, t!("network_settings.server")),
|
||||
);
|
||||
View::horizontal_line(ui, Colors::stroke());
|
||||
ui.add_space(6.0);
|
||||
|
||||
@@ -117,8 +121,11 @@ impl ContentContainer for NodeSetup {
|
||||
ui.add_space(2.0);
|
||||
|
||||
// Show loading indicator or controls to stop/start/restart node.
|
||||
if Node::is_stopping() || Node::is_restarting() || Node::is_starting()
|
||||
|| Node::data_dir_changing() {
|
||||
if Node::is_stopping()
|
||||
|| Node::is_restarting()
|
||||
|| Node::is_starting()
|
||||
|| Node::data_dir_changing()
|
||||
{
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(8.0);
|
||||
View::small_loading_spinner(ui);
|
||||
@@ -161,9 +168,10 @@ impl ContentContainer for NodeSetup {
|
||||
NetworkContent::autorun_node_ui(ui);
|
||||
if Node::is_running() {
|
||||
ui.add_space(2.0);
|
||||
ui.label(RichText::new(t!("network_settings.restart_node_required"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.restart_node_required"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
@@ -171,8 +179,11 @@ impl ContentContainer for NodeSetup {
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show data location selection for Desktop when it already started or turned off.
|
||||
if !Node::is_restarting() && !Node::is_stopping() && !Node::is_starting() &&
|
||||
View::is_desktop() {
|
||||
if !Node::is_restarting()
|
||||
&& !Node::is_stopping()
|
||||
&& !Node::is_starting()
|
||||
&& View::is_desktop()
|
||||
{
|
||||
self.data_dir_ui(ui, cb);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
@@ -185,9 +196,10 @@ impl ContentContainer for NodeSetup {
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.api_ip"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.api_ip"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
@@ -245,17 +257,25 @@ impl NodeSetup {
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
let res = ui.scope_builder(
|
||||
let res = ui
|
||||
.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
.max_rect(rect),
|
||||
|ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
rect.size(),
|
||||
Layout::right_to_left(Align::Center),
|
||||
|ui| {
|
||||
self.pick_data_dir.ui(ui, cb, |path| {
|
||||
Node::change_data_dir(path);
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
layout_size,
|
||||
Layout::left_to_right(Align::Center),
|
||||
|ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
@@ -263,13 +283,18 @@ impl NodeSetup {
|
||||
View::ellipsize_text(ui, path, 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let desc = format!("{} {}", FOLDERS, t!("files_location"));
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.label(
|
||||
RichText::new(desc).size(15.0).color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
@@ -296,9 +321,11 @@ impl NodeSetup {
|
||||
Node::change_data_dir(path.clone());
|
||||
Modal::close();
|
||||
};
|
||||
ui.label(RichText::new(format!("{}:", t!("files_location")))
|
||||
ui.label(
|
||||
RichText::new(format!("{}:", t!("files_location")))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw chain data path text edit.
|
||||
@@ -316,9 +343,14 @@ impl NodeSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -334,7 +366,11 @@ impl NodeSetup {
|
||||
/// Draw [`ChainTypes`] setup content.
|
||||
pub fn chain_type_ui(ui: &mut egui::Ui) {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network.type")).size(16.0).color(Colors::gray()));
|
||||
ui.label(
|
||||
RichText::new(t!("network.type"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
});
|
||||
|
||||
let saved_chain_type = AppConfig::chain_type();
|
||||
@@ -344,11 +380,21 @@ impl NodeSetup {
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
let main_type = ChainTypes::Mainnet;
|
||||
View::radio_value(ui, &mut selected_chain_type, main_type, t!("network.mainnet"));
|
||||
View::radio_value(
|
||||
ui,
|
||||
&mut selected_chain_type,
|
||||
main_type,
|
||||
t!("network.mainnet"),
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
let test_type = ChainTypes::Testnet;
|
||||
View::radio_value(ui, &mut selected_chain_type, test_type, t!("network.testnet"));
|
||||
View::radio_value(
|
||||
ui,
|
||||
&mut selected_chain_type,
|
||||
test_type,
|
||||
t!("network.testnet"),
|
||||
);
|
||||
})
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
@@ -363,11 +409,19 @@ impl NodeSetup {
|
||||
|
||||
/// Draw API port setup content.
|
||||
fn api_port_setup_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.api_port")).size(16.0).color(Colors::gray()));
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.api_port"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let (_, port) = NodeConfig::get_api_ip_port();
|
||||
View::button(ui, format!("{} {}", PLUG, &port), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", PLUG, &port),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.api_port_edit = port;
|
||||
self.api_port_available_edit = self.is_api_port_available;
|
||||
@@ -377,14 +431,17 @@ impl NodeSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
if !self.is_api_port_available {
|
||||
// Show error when API server port is unavailable.
|
||||
ui.label(RichText::new(t!("network_settings.port_unavailable"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.port_unavailable"))
|
||||
.size(16.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
@@ -410,9 +467,11 @@ impl NodeSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.api_port"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.api_port"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw API port text edit.
|
||||
@@ -425,9 +484,11 @@ impl NodeSetup {
|
||||
// Show error when specified port is unavailable or reminder to restart enabled node.
|
||||
if !self.api_port_available_edit {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.port_unavailable"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.port_unavailable"))
|
||||
.size(16.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -441,9 +502,14 @@ impl NodeSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -459,17 +525,14 @@ impl NodeSetup {
|
||||
fn secret_ui(&mut self, modal_id: &'static str, ui: &mut egui::Ui) {
|
||||
let secret_title = match modal_id {
|
||||
API_SECRET_MODAL => t!("network_settings.api_secret"),
|
||||
_ => t!("network_settings.foreign_api_secret")
|
||||
_ => t!("network_settings.foreign_api_secret"),
|
||||
};
|
||||
ui.label(RichText::new(secret_title)
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
);
|
||||
ui.label(RichText::new(secret_title).size(16.0).color(Colors::gray()));
|
||||
ui.add_space(6.0);
|
||||
|
||||
let secret_value = match modal_id {
|
||||
API_SECRET_MODAL => NodeConfig::get_api_secret(false),
|
||||
_ => NodeConfig::get_api_secret(true)
|
||||
_ => NodeConfig::get_api_secret(true),
|
||||
};
|
||||
|
||||
let secret_text = if secret_value.is_some() {
|
||||
@@ -508,15 +571,13 @@ impl NodeSetup {
|
||||
ui.vertical_centered(|ui| {
|
||||
let description = match modal.id {
|
||||
API_SECRET_MODAL => t!("network_settings.api_secret"),
|
||||
_ => t!("network_settings.foreign_api_secret")
|
||||
_ => t!("network_settings.foreign_api_secret"),
|
||||
};
|
||||
ui.label(RichText::new(description).size(17.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw API secret token value text edit.
|
||||
let mut secret_edit = TextEdit::new(Id::from(modal.id))
|
||||
.copy()
|
||||
.paste();
|
||||
let mut secret_edit = TextEdit::new(Id::from(modal.id)).copy().paste();
|
||||
secret_edit.ui(ui, &mut self.secret_edit, cb);
|
||||
if secret_edit.enter_pressed {
|
||||
on_save(self);
|
||||
@@ -526,9 +587,10 @@ impl NodeSetup {
|
||||
|
||||
// Show reminder to restart enabled node.
|
||||
if Node::is_running() {
|
||||
ui.label(RichText::new(t!("network_settings.restart_node_required"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.restart_node_required"))
|
||||
.size(16.0)
|
||||
.color(Colors::green())
|
||||
.color(Colors::green()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
@@ -542,9 +604,14 @@ impl NodeSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -558,16 +625,19 @@ impl NodeSetup {
|
||||
|
||||
/// Draw FTL setup content.
|
||||
fn ftl_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.ftl"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.ftl"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let ftl = NodeConfig::get_ftl();
|
||||
View::button(ui,
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", CLOCK_CLOCKWISE, &ftl),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.ftl_edit = ftl;
|
||||
// Show ftl value setup modal.
|
||||
@@ -575,11 +645,13 @@ impl NodeSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("network_settings.ftl_description"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.ftl_description"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -595,9 +667,11 @@ impl NodeSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.ftl"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.ftl"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw ftl value text edit.
|
||||
@@ -610,9 +684,11 @@ impl NodeSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.ftl_edit.parse::<u64>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -626,10 +702,15 @@ impl NodeSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -648,22 +729,29 @@ impl NodeSetup {
|
||||
NodeConfig::toggle_full_chain_validation();
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
ui.label(RichText::new(t!("network_settings.full_validation_description"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.full_validation_description"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
}
|
||||
|
||||
/// Draw archive mode setup content.
|
||||
fn archive_mode_ui(&mut self, ui: &mut egui::Ui) {
|
||||
let archive_mode = NodeConfig::is_archive_mode();
|
||||
View::checkbox(ui, archive_mode, t!("network_settings.archive_mode"), || {
|
||||
View::checkbox(
|
||||
ui,
|
||||
archive_mode,
|
||||
t!("network_settings.archive_mode"),
|
||||
|| {
|
||||
NodeConfig::toggle_archive_mode();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
ui.label(RichText::new(t!("network_settings.archive_mode_desc"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.archive_mode_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,14 @@ use grin_core::global::ChainTypes;
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_FAT_LINES_DOWN, ARROW_FAT_LINES_UP, GLOBE_SIMPLE, HANDSHAKE, PLUG, PLUS_CIRCLE, PROHIBIT_INSET, TRASH};
|
||||
use crate::gui::icons::{
|
||||
ARROW_FAT_LINES_DOWN, ARROW_FAT_LINES_UP, GLOBE_SIMPLE, HANDSHAKE, PLUG, PLUS_CIRCLE,
|
||||
PROHIBIT_INSET, TRASH,
|
||||
};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::views::network::settings::NetworkSettings;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::node::{Node, NodeConfig, PeersConfig};
|
||||
|
||||
/// Type of peer.
|
||||
@@ -32,7 +35,7 @@ enum PeerType {
|
||||
CustomSeed,
|
||||
Allowed,
|
||||
Denied,
|
||||
Preferred
|
||||
Preferred,
|
||||
}
|
||||
|
||||
/// P2P server setup section content.
|
||||
@@ -122,14 +125,11 @@ impl ContentContainer for P2PSetup {
|
||||
PREFER_PEER_MODAL,
|
||||
BAN_WINDOW_MODAL,
|
||||
MAX_INBOUND_MODAL,
|
||||
MAX_OUTBOUND_MODAL
|
||||
MAX_OUTBOUND_MODAL,
|
||||
]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
PORT_MODAL => self.port_modal(ui, modal, cb),
|
||||
CUSTOM_SEED_MODAL => self.peer_modal(ui, modal, cb),
|
||||
@@ -144,7 +144,10 @@ impl ContentContainer for P2PSetup {
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, _: &dyn PlatformCallbacks) {
|
||||
View::sub_title(ui, format!("{} {}", HANDSHAKE, t!("network_settings.p2p_server")));
|
||||
View::sub_title(
|
||||
ui,
|
||||
format!("{} {}", HANDSHAKE, t!("network_settings.p2p_server")),
|
||||
);
|
||||
View::horizontal_line(ui, Colors::stroke());
|
||||
ui.add_space(6.0);
|
||||
|
||||
@@ -163,9 +166,11 @@ impl ContentContainer for P2PSetup {
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.label(RichText::new(t!("network_settings.allow_list"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.allow_list"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
// Show allowed peers setup.
|
||||
self.peer_list_ui(ui, &PeerType::Allowed);
|
||||
@@ -174,9 +179,11 @@ impl ContentContainer for P2PSetup {
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.label(RichText::new(t!("network_settings.deny_list"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.deny_list"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
// Show denied peers setup.
|
||||
self.peer_list_ui(ui, &PeerType::Denied);
|
||||
@@ -185,9 +192,11 @@ impl ContentContainer for P2PSetup {
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.label(RichText::new(t!("network_settings.favourites"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.favourites"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
// Show preferred peers setup.
|
||||
self.peer_list_ui(ui, &PeerType::Preferred);
|
||||
@@ -222,16 +231,19 @@ const DNS_SEEDS_TITLE: &'static str = "DNS Seeds";
|
||||
impl P2PSetup {
|
||||
/// Draw p2p port setup content.
|
||||
fn port_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.p2p_port"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.p2p_port"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let port = NodeConfig::get_p2p_port();
|
||||
View::button(ui,
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", PLUG, &port),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.port_edit = port;
|
||||
self.port_available_edit = self.is_port_available;
|
||||
@@ -240,15 +252,18 @@ impl P2PSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show error when p2p port is unavailable.
|
||||
if !self.is_port_available {
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("network_settings.port_unavailable"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.port_unavailable"))
|
||||
.size(16.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
ui.add_space(12.0);
|
||||
}
|
||||
}
|
||||
@@ -273,9 +288,11 @@ impl P2PSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.p2p_port"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.p2p_port"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw p2p port text edit.
|
||||
@@ -288,9 +305,11 @@ impl P2PSetup {
|
||||
// Show error when specified port is unavailable.
|
||||
if !self.port_available_edit {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.port_unavailable"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.port_unavailable"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
}
|
||||
|
||||
ui.add_space(12.0);
|
||||
@@ -302,10 +321,15 @@ impl P2PSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -331,7 +355,7 @@ impl P2PSetup {
|
||||
PeerType::CustomSeed => NodeConfig::get_custom_seeds(),
|
||||
PeerType::Allowed => NodeConfig::get_allowed_peers(),
|
||||
PeerType::Denied => NodeConfig::get_denied_peers(),
|
||||
PeerType::Preferred => NodeConfig::get_preferred_peers()
|
||||
PeerType::Preferred => NodeConfig::get_preferred_peers(),
|
||||
};
|
||||
for (index, peer) in peers.iter().enumerate() {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
@@ -351,9 +375,11 @@ impl P2PSetup {
|
||||
PeerType::Denied => t!("network_settings.deny_list_desc"),
|
||||
&_ => t!("network_settings.favourites_desc"),
|
||||
};
|
||||
ui.label(RichText::new(desc)
|
||||
ui.label(
|
||||
RichText::new(desc)
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()));
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(12.0);
|
||||
} else if !peers.is_empty() {
|
||||
ui.add_space(12.0);
|
||||
@@ -363,7 +389,6 @@ impl P2PSetup {
|
||||
format!("{} {}", PLUS_CIRCLE, t!("network_settings.add_seed"))
|
||||
} else {
|
||||
format!("{} {}", PLUS_CIRCLE, t!("network_settings.add_peer"))
|
||||
|
||||
};
|
||||
View::button(ui, add_text, Colors::white_or_black(false), || {
|
||||
// Setup values for modal.
|
||||
@@ -375,14 +400,14 @@ impl P2PSetup {
|
||||
PeerType::Allowed => ALLOW_PEER_MODAL,
|
||||
PeerType::Denied => DENY_PEER_MODAL,
|
||||
PeerType::Preferred => PREFER_PEER_MODAL,
|
||||
_ => CUSTOM_SEED_MODAL
|
||||
_ => CUSTOM_SEED_MODAL,
|
||||
};
|
||||
// Select modal title.
|
||||
let modal_title = match peer_type {
|
||||
PeerType::Allowed => t!("network_settings.allow_list").into(),
|
||||
PeerType::Denied => t!("network_settings.deny_list").into(),
|
||||
PeerType::Preferred => t!("network_settings.favourites").into(),
|
||||
_ => DNS_SEEDS_TITLE.to_string()
|
||||
_ => DNS_SEEDS_TITLE.to_string(),
|
||||
};
|
||||
// Show modal to add peer.
|
||||
Modal::new(modal_id)
|
||||
@@ -429,7 +454,7 @@ impl P2PSetup {
|
||||
ui.vertical_centered(|ui| {
|
||||
let label_text = match modal.id {
|
||||
CUSTOM_SEED_MODAL => t!("network_settings.seed_address"),
|
||||
&_ => t!("network_settings.peer_address")
|
||||
&_ => t!("network_settings.peer_address"),
|
||||
};
|
||||
ui.label(RichText::new(label_text).size(17.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
@@ -448,9 +473,11 @@ impl P2PSetup {
|
||||
if let Some(available) = self.address_available {
|
||||
if !available {
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(t!("network_settings.peer_address_error"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.peer_address_error"))
|
||||
.size(16.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
}
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
@@ -462,10 +489,15 @@ impl P2PSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -482,7 +514,11 @@ impl P2PSetup {
|
||||
|
||||
/// Draw seeding type setup content.
|
||||
fn seeding_type_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(DNS_SEEDS_TITLE).size(16.0).color(Colors::gray()));
|
||||
ui.label(
|
||||
RichText::new(DNS_SEEDS_TITLE)
|
||||
.size(16.0)
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(2.0);
|
||||
|
||||
let default_seeding = NodeConfig::is_default_seeding_type();
|
||||
@@ -501,16 +537,19 @@ impl P2PSetup {
|
||||
|
||||
/// Draw ban window setup content.
|
||||
fn ban_window_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.ban_window"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.ban_window"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let ban_window = NodeConfig::get_p2p_ban_window();
|
||||
View::button(ui,
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", PROHIBIT_INSET, &ban_window),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.ban_window_edit = ban_window;
|
||||
// Show ban window period setup modal.
|
||||
@@ -518,11 +557,13 @@ impl P2PSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("network_settings.ban_window_desc"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.ban_window_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(2.0);
|
||||
}
|
||||
@@ -538,9 +579,11 @@ impl P2PSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.ban_window"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.ban_window"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw ban window text edit.
|
||||
@@ -553,9 +596,11 @@ impl P2PSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.ban_window_edit.parse::<i64>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -569,9 +614,14 @@ impl P2PSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -585,16 +635,19 @@ impl P2PSetup {
|
||||
|
||||
/// Draw maximum number of inbound peers setup content.
|
||||
fn max_inbound_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.max_inbound_count"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.max_inbound_count"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let max_inbound = NodeConfig::get_max_inbound_peers();
|
||||
View::button(ui,
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", ARROW_FAT_LINES_DOWN, &max_inbound),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.max_inbound_count = max_inbound;
|
||||
// Show maximum number of inbound peers setup modal.
|
||||
@@ -602,7 +655,8 @@ impl P2PSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -617,9 +671,11 @@ impl P2PSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.max_inbound_count"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.max_inbound_count"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw maximum number of inbound peers text edit.
|
||||
@@ -632,9 +688,11 @@ impl P2PSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.max_inbound_count.parse::<u32>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -648,9 +706,14 @@ impl P2PSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -664,15 +727,18 @@ impl P2PSetup {
|
||||
|
||||
/// Draw maximum number of outbound peers setup content.
|
||||
fn max_outbound_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.max_outbound_count"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.max_outbound_count"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
let max_outbound = NodeConfig::get_max_outbound_peers();
|
||||
View::button(ui,
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", ARROW_FAT_LINES_UP, &max_outbound),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.max_outbound_count = max_outbound;
|
||||
// Show maximum number of outbound peers setup modal.
|
||||
@@ -680,7 +746,8 @@ impl P2PSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -695,9 +762,11 @@ impl P2PSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.max_outbound_count"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.max_outbound_count"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw maximum number of outbound peers text edit.
|
||||
@@ -710,9 +779,11 @@ impl P2PSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.max_outbound_count.parse::<u32>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -726,10 +797,15 @@ impl P2PSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -743,30 +819,38 @@ impl P2PSetup {
|
||||
}
|
||||
|
||||
/// Draw peer list item.
|
||||
fn peer_item_ui(ui: &mut egui::Ui,
|
||||
fn peer_item_ui(
|
||||
ui: &mut egui::Ui,
|
||||
peer_addr: &String,
|
||||
peer_type: &PeerType,
|
||||
index: usize,
|
||||
len: usize,) {
|
||||
len: usize,
|
||||
) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(42.0);
|
||||
|
||||
// Draw round background.
|
||||
let item_rounding = View::item_rounding(index, len, false);
|
||||
ui.painter().rect(rect,
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw delete button for non-default seed peers.
|
||||
if peer_type != &PeerType::DefaultSeed {
|
||||
let r = View::item_rounding(index, len, true);
|
||||
View::item_button(ui, r, TRASH, Some(Colors::inactive_text()), || {
|
||||
match peer_type {
|
||||
View::item_button(
|
||||
ui,
|
||||
r,
|
||||
TRASH,
|
||||
Some(Colors::inactive_text()),
|
||||
|| match peer_type {
|
||||
PeerType::CustomSeed => {
|
||||
NodeConfig::remove_custom_seed(peer_addr);
|
||||
}
|
||||
@@ -780,8 +864,8 @@ fn peer_item_ui(ui: &mut egui::Ui,
|
||||
NodeConfig::remove_preferred_peer(peer_addr);
|
||||
}
|
||||
PeerType::DefaultSeed => {}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
@@ -789,9 +873,11 @@ fn peer_item_ui(ui: &mut egui::Ui,
|
||||
ui.add_space(6.0);
|
||||
// Draw peer address.
|
||||
let peer_text = format!("{} {}", GLOBE_SIMPLE, &peer_addr);
|
||||
ui.label(RichText::new(peer_text)
|
||||
ui.label(
|
||||
RichText::new(peer_text)
|
||||
.color(Colors::text_button())
|
||||
.size(16.0));
|
||||
.size(16.0),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
use egui::{Id, RichText};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{BEZIER_CURVE, BOUNDING_BOX, CHART_SCATTER, CIRCLES_THREE, CLOCK_COUNTDOWN, HAND_COINS};
|
||||
use crate::gui::icons::{
|
||||
BEZIER_CURVE, BOUNDING_BOX, CHART_SCATTER, CIRCLES_THREE, CLOCK_COUNTDOWN, HAND_COINS,
|
||||
};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::views::network::settings::NetworkSettings;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::node::NodeConfig;
|
||||
|
||||
/// Memory pool setup section content.
|
||||
@@ -66,14 +68,11 @@ impl ContentContainer for PoolSetup {
|
||||
REORG_PERIOD_MODAL,
|
||||
POOL_SIZE_MODAL,
|
||||
STEMPOOL_SIZE_MODAL,
|
||||
MAX_WEIGHT_MODAL
|
||||
MAX_WEIGHT_MODAL,
|
||||
]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
FEE_BASE_MODAL => self.fee_base_modal(ui, modal, cb),
|
||||
REORG_PERIOD_MODAL => self.reorg_period_modal(ui, modal, cb),
|
||||
@@ -85,7 +84,10 @@ impl ContentContainer for PoolSetup {
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, _: &dyn PlatformCallbacks) {
|
||||
View::sub_title(ui, format!("{} {}", CHART_SCATTER, t!("network_settings.tx_pool")));
|
||||
View::sub_title(
|
||||
ui,
|
||||
format!("{} {}", CHART_SCATTER, t!("network_settings.tx_pool")),
|
||||
);
|
||||
View::horizontal_line(ui, Colors::stroke());
|
||||
ui.add_space(6.0);
|
||||
|
||||
@@ -127,14 +129,19 @@ impl ContentContainer for PoolSetup {
|
||||
impl PoolSetup {
|
||||
/// Draw fee base setup content.
|
||||
fn fee_base_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.pool_fee"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.pool_fee"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let fee = NodeConfig::get_base_fee();
|
||||
View::button(ui, format!("{} {}", HAND_COINS, &fee), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", HAND_COINS, &fee),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.fee_base_edit = fee;
|
||||
// Show fee setup modal.
|
||||
@@ -142,7 +149,8 @@ impl PoolSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -157,9 +165,11 @@ impl PoolSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.pool_fee"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.pool_fee"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw fee base text edit.
|
||||
@@ -172,9 +182,11 @@ impl PoolSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.fee_base_edit.parse::<u64>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -187,10 +199,15 @@ impl PoolSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -205,15 +222,18 @@ impl PoolSetup {
|
||||
|
||||
/// Draw reorg cache retention period setup content.
|
||||
fn reorg_period_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.reorg_period"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.reorg_period"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
let period = NodeConfig::get_reorg_cache_period();
|
||||
View::button(ui,
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", CLOCK_COUNTDOWN, &period),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.reorg_period_edit = period;
|
||||
// Show reorg period setup modal.
|
||||
@@ -221,7 +241,8 @@ impl PoolSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -236,9 +257,11 @@ impl PoolSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.reorg_period"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.reorg_period"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw reorg period text edit.
|
||||
@@ -251,9 +274,11 @@ impl PoolSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.reorg_period_edit.parse::<u32>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -266,10 +291,15 @@ impl PoolSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -284,14 +314,19 @@ impl PoolSetup {
|
||||
|
||||
/// Draw maximum number of transactions in the pool setup content.
|
||||
fn pool_size_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.max_tx_pool"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.max_tx_pool"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let size = NodeConfig::get_max_pool_size();
|
||||
View::button(ui, format!("{} {}", CIRCLES_THREE, size), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", CIRCLES_THREE, size),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.pool_size_edit = size;
|
||||
// Show pool size setup modal.
|
||||
@@ -299,7 +334,8 @@ impl PoolSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -314,9 +350,11 @@ impl PoolSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.max_tx_pool"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.max_tx_pool"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw pool size text edit.
|
||||
@@ -329,9 +367,11 @@ impl PoolSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.pool_size_edit.parse::<usize>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -344,10 +384,15 @@ impl PoolSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -362,16 +407,19 @@ impl PoolSetup {
|
||||
|
||||
/// Draw maximum number of transactions in the stempool setup content.
|
||||
fn stem_size_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.max_tx_stempool"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.max_tx_stempool"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let size = NodeConfig::get_max_stempool_size();
|
||||
View::button(ui,
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", BEZIER_CURVE, &size),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.stempool_size_edit = size;
|
||||
// Show stempool size setup modal.
|
||||
@@ -379,7 +427,8 @@ impl PoolSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -394,9 +443,11 @@ impl PoolSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.max_tx_stempool"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.max_tx_stempool"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw stempool size text edit.
|
||||
@@ -409,9 +460,11 @@ impl PoolSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.stempool_size_edit.parse::<usize>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -424,10 +477,15 @@ impl PoolSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -442,16 +500,19 @@ impl PoolSetup {
|
||||
|
||||
/// Draw maximum total weight of transactions setup content.
|
||||
fn max_weight_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.max_tx_weight"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.max_tx_weight"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let weight = NodeConfig::get_mineable_max_weight();
|
||||
View::button(ui,
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", BOUNDING_BOX, &weight),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.max_weight_edit = weight;
|
||||
// Show total tx weight setup modal.
|
||||
@@ -459,7 +520,8 @@ impl PoolSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -474,9 +536,11 @@ impl PoolSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.max_tx_weight"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.max_tx_weight"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw tx weight text edit.
|
||||
@@ -489,9 +553,11 @@ impl PoolSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.max_weight_edit.parse::<u64>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
NetworkSettings::node_restart_required_ui(ui);
|
||||
}
|
||||
@@ -504,10 +570,15 @@ impl PoolSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
|
||||
@@ -18,10 +18,10 @@ use grin_chain::SyncStatus;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{BARBELL, HARD_DRIVES, PLUG, POWER, TIMER};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::views::network::settings::NetworkSettings;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::wallets::modals::WalletListModal;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::node::{Node, NodeConfig};
|
||||
use crate::wallet::{WalletConfig, WalletList};
|
||||
|
||||
@@ -98,22 +98,17 @@ impl ContentContainer for StratumSetup {
|
||||
WALLET_SELECTION_MODAL,
|
||||
STRATUM_PORT_MODAL,
|
||||
ATTEMPT_TIME_MODAL,
|
||||
MIN_SHARE_DIFF_MODAL
|
||||
MIN_SHARE_DIFF_MODAL,
|
||||
]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
WALLET_SELECTION_MODAL => {
|
||||
self.wallets_modal.ui(ui, &mut self.wallets, |wallet, _| {
|
||||
WALLET_SELECTION_MODAL => self.wallets_modal.ui(ui, &mut self.wallets, |wallet, _| {
|
||||
let id = wallet.get_config().id;
|
||||
NodeConfig::save_stratum_wallet_id(id);
|
||||
self.wallet_name = WalletConfig::read_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),
|
||||
@@ -122,14 +117,19 @@ impl ContentContainer for StratumSetup {
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, _: &dyn PlatformCallbacks) {
|
||||
View::sub_title(ui, format!("{} {}", HARD_DRIVES, t!("network_mining.server")));
|
||||
View::sub_title(
|
||||
ui,
|
||||
format!("{} {}", HARD_DRIVES, t!("network_mining.server")),
|
||||
);
|
||||
View::horizontal_line(ui, Colors::stroke());
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
// Show loading indicator or controls to start/stop stratum server.
|
||||
if Node::get_sync_status().unwrap_or(SyncStatus::Initial) == SyncStatus::NoSync &&
|
||||
self.is_port_available && self.wallet_name.is_some() {
|
||||
if Node::get_sync_status().unwrap_or(SyncStatus::Initial) == SyncStatus::NoSync
|
||||
&& self.is_port_available
|
||||
&& self.wallet_name.is_some()
|
||||
{
|
||||
if Node::is_stratum_starting() || Node::is_stratum_stopping() {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(8.0);
|
||||
@@ -164,9 +164,10 @@ impl ContentContainer for StratumSetup {
|
||||
// Show reminder to restart running server.
|
||||
if Node::get_stratum_stats().is_running {
|
||||
ui.add_space(2.0);
|
||||
ui.label(RichText::new(t!("network_mining.restart_server_required"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_mining.restart_server_required"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
@@ -174,23 +175,29 @@ impl ContentContainer for StratumSetup {
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show wallet name.
|
||||
ui.label(RichText::new(self.wallet_name.as_ref().unwrap_or(&"-".to_string()))
|
||||
ui.label(
|
||||
RichText::new(self.wallet_name.as_ref().unwrap_or(&"-".to_string()))
|
||||
.size(16.0)
|
||||
.color(Colors::white_or_black(true)));
|
||||
.color(Colors::white_or_black(true)),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show button to select wallet.
|
||||
View::button(ui,
|
||||
View::button(
|
||||
ui,
|
||||
t!("network_settings.choose_wallet"),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
self.show_wallets_modal();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(12.0);
|
||||
|
||||
if self.wallet_name.is_some() {
|
||||
ui.label(RichText::new(t!("network_settings.stratum_wallet_warning"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.stratum_wallet_warning"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(12.0);
|
||||
}
|
||||
@@ -205,9 +212,10 @@ impl ContentContainer for StratumSetup {
|
||||
}
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.stratum_ip"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.stratum_ip"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
// Show stratum IP addresses to select.
|
||||
@@ -215,7 +223,6 @@ impl ContentContainer for StratumSetup {
|
||||
NetworkSettings::ip_addrs_ui(ui, &ip, &self.available_ips, |selected_ip| {
|
||||
NodeConfig::save_stratum_address(selected_ip, &port);
|
||||
self.is_port_available = NodeConfig::is_stratum_port_available(selected_ip, &port);
|
||||
|
||||
});
|
||||
// Show stratum port setup.
|
||||
self.port_setup_ui(ui);
|
||||
@@ -248,14 +255,19 @@ impl StratumSetup {
|
||||
|
||||
/// Draw stratum port value setup content.
|
||||
fn port_setup_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.stratum_port"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.stratum_port"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let (_, port) = NodeConfig::get_stratum_address();
|
||||
View::button(ui, format!("{} {}", PLUG, &port), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", PLUG, &port),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.stratum_port_edit = port;
|
||||
self.stratum_port_available_edit = self.is_port_available;
|
||||
@@ -264,15 +276,18 @@ impl StratumSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Show error when stratum server port is unavailable.
|
||||
if !self.is_port_available {
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("network_settings.port_unavailable"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.port_unavailable"))
|
||||
.size(16.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
ui.add_space(12.0);
|
||||
}
|
||||
}
|
||||
@@ -282,10 +297,8 @@ impl StratumSetup {
|
||||
let on_save = |c: &mut StratumSetup| {
|
||||
// Check if port is available.
|
||||
let (stratum_ip, _) = NodeConfig::get_stratum_address();
|
||||
let available = NodeConfig::is_stratum_port_available(
|
||||
&stratum_ip,
|
||||
&c.stratum_port_edit
|
||||
);
|
||||
let available =
|
||||
NodeConfig::is_stratum_port_available(&stratum_ip, &c.stratum_port_edit);
|
||||
c.stratum_port_available_edit = available;
|
||||
|
||||
// Save port at config if it's available.
|
||||
@@ -299,9 +312,11 @@ impl StratumSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.stratum_port"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.stratum_port"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw stratum port text edit.
|
||||
@@ -314,9 +329,11 @@ impl StratumSetup {
|
||||
// Show error when specified port is unavailable.
|
||||
if !self.stratum_port_available_edit {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.port_unavailable"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.port_unavailable"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
server_restart_required_ui(ui);
|
||||
}
|
||||
@@ -330,10 +347,15 @@ impl StratumSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -348,14 +370,19 @@ impl StratumSetup {
|
||||
|
||||
/// Draw attempt time value setup content.
|
||||
fn attempt_time_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.attempt_time"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.attempt_time"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let time = NodeConfig::get_stratum_attempt_time();
|
||||
View::button(ui, format!("{} {}", TIMER, &time), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", TIMER, &time),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.attempt_time_edit = time;
|
||||
|
||||
@@ -364,11 +391,13 @@ impl StratumSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("network_settings.attempt_time_desc"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.attempt_time_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
@@ -384,9 +413,11 @@ impl StratumSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.attempt_time"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.attempt_time"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw attempt time text edit.
|
||||
@@ -399,9 +430,11 @@ impl StratumSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.attempt_time_edit.parse::<u32>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
server_restart_required_ui(ui);
|
||||
}
|
||||
@@ -415,10 +448,15 @@ impl StratumSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -432,14 +470,19 @@ impl StratumSetup {
|
||||
|
||||
/// Draw minimum share difficulty value setup content.
|
||||
fn min_diff_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new(t!("network_settings.min_share_diff"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.min_share_diff"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let diff = NodeConfig::get_stratum_min_share_diff();
|
||||
View::button(ui, format!("{} {}", BARBELL, &diff), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
format!("{} {}", BARBELL, &diff),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Setup values for modal.
|
||||
self.min_share_diff_edit = diff;
|
||||
|
||||
@@ -448,7 +491,8 @@ impl StratumSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("network_settings.change_value"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -463,9 +507,11 @@ impl StratumSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("network_settings.min_share_diff"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.min_share_diff"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw share difficulty text edit.
|
||||
@@ -478,9 +524,11 @@ impl StratumSetup {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.min_share_diff_edit.parse::<u64>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
server_restart_required_ui(ui);
|
||||
}
|
||||
@@ -494,10 +542,15 @@ impl StratumSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -514,9 +567,10 @@ impl StratumSetup {
|
||||
pub fn server_restart_required_ui(ui: &mut egui::Ui) {
|
||||
if Node::get_stratum_stats().is_running {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_mining.restart_server_required"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_mining.restart_server_required"))
|
||||
.size(16.0)
|
||||
.color(Colors::green())
|
||||
.color(Colors::green()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
/// Integrated node tab content interface.
|
||||
pub trait NodeTab {
|
||||
@@ -27,7 +27,7 @@ pub enum NodeTabType {
|
||||
Info,
|
||||
Metrics,
|
||||
Mining,
|
||||
Settings
|
||||
Settings,
|
||||
}
|
||||
|
||||
impl NodeTabType {
|
||||
@@ -36,7 +36,7 @@ impl NodeTabType {
|
||||
NodeTabType::Info => t!("network.node").into(),
|
||||
NodeTabType::Metrics => t!("network.metrics").into(),
|
||||
NodeTabType::Mining => t!("network.mining").into(),
|
||||
NodeTabType::Settings => t!("network.settings").into()
|
||||
NodeTabType::Settings => t!("network.settings").into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,5 +47,5 @@ pub struct ShareConnection {
|
||||
#[serde(rename(serialize = "ipPort", deserialize = "ipPort"))]
|
||||
pub url: String,
|
||||
pub username: String,
|
||||
pub secret: String
|
||||
pub secret: String,
|
||||
}
|
||||
@@ -12,9 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::epaint::{Pos2, Shape, Stroke, emath::lerp, vec2};
|
||||
use egui::scroll_area::ScrollAreaOutput;
|
||||
use egui::{Sense, Align2, Area, Color32, Id, Rect, Response, Widget, Vec2, UiBuilder};
|
||||
use egui::epaint::{emath::lerp, vec2, Pos2, Shape, Stroke};
|
||||
use egui::{Align2, Area, Color32, Id, Rect, Response, Sense, UiBuilder, Vec2, Widget};
|
||||
|
||||
/// A spinner widget used to indicate loading.
|
||||
/// This was taken from egui and modified slightly to allow passing a progress value
|
||||
@@ -195,9 +195,11 @@ impl PullToRefresh {
|
||||
ui: &mut egui::Ui,
|
||||
content: impl FnOnce(&mut egui::Ui) -> T,
|
||||
) -> PullToRefreshResponse<T> {
|
||||
let mut child = ui.new_child(UiBuilder::new()
|
||||
let mut child = ui.new_child(
|
||||
UiBuilder::new()
|
||||
.max_rect(ui.available_rect_before_wrap())
|
||||
.layout(*ui.layout()));
|
||||
.layout(*ui.layout()),
|
||||
);
|
||||
|
||||
let output = content(&mut child);
|
||||
|
||||
@@ -300,7 +302,8 @@ impl PullToRefresh {
|
||||
}
|
||||
} else if let PullToRefreshState::Dragging {
|
||||
far_enough: enough, ..
|
||||
} = state.clone() {
|
||||
} = state.clone()
|
||||
{
|
||||
if enough {
|
||||
state = PullToRefreshState::DoRefresh;
|
||||
}
|
||||
|
||||
+47
-24
@@ -22,11 +22,11 @@ use std::mem::size_of;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{COPY, IMAGES_SQUARE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::QrImageState;
|
||||
use crate::gui::views::View;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::views::types::QrImageState;
|
||||
|
||||
/// QR code image from text.
|
||||
pub struct QrCodeContent {
|
||||
@@ -154,17 +154,20 @@ impl QrCodeContent {
|
||||
ui.vertical_centered(|ui| {
|
||||
// Show button to share QR.
|
||||
let share_text = format!("{} {}", IMAGES_SQUARE, t!("share"));
|
||||
View::colored_text_button(ui,
|
||||
View::colored_text_button(
|
||||
ui,
|
||||
share_text,
|
||||
Colors::blue(),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
{
|
||||
let mut w_state = self.qr_image_state.write();
|
||||
w_state.exporting = true;
|
||||
}
|
||||
// Create GIF to export.
|
||||
self.create_qr_gif();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
} else {
|
||||
ui.vertical_centered(|ui| {
|
||||
@@ -255,12 +258,15 @@ impl QrCodeContent {
|
||||
/// Draw button to share static QR code.
|
||||
fn share_static_button_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let share_text = format!("{} {}", IMAGES_SQUARE, t!("share"));
|
||||
View::colored_text_button(ui,
|
||||
View::colored_text_button(
|
||||
ui,
|
||||
share_text,
|
||||
Colors::blue(),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
self.share_static(cb);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Share static QR code image.
|
||||
@@ -270,13 +276,17 @@ impl QrCodeContent {
|
||||
let size = DEFAULT_QR_SIZE as usize;
|
||||
if let Some(data) = Self::qr_to_image_data(qr, size) {
|
||||
let mut png = vec![];
|
||||
let png_enc = PngEncoder::new_with_quality(&mut png,
|
||||
let png_enc = PngEncoder::new_with_quality(
|
||||
&mut png,
|
||||
CompressionType::Best,
|
||||
FilterType::NoFilter);
|
||||
if let Ok(()) = png_enc.write_image(data.as_slice(),
|
||||
FilterType::NoFilter,
|
||||
);
|
||||
if let Ok(()) = png_enc.write_image(
|
||||
data.as_slice(),
|
||||
DEFAULT_QR_SIZE,
|
||||
DEFAULT_QR_SIZE,
|
||||
ExtendedColorType::L8) {
|
||||
ExtendedColorType::L8,
|
||||
) {
|
||||
let name = format!("{}.png", chrono::Utc::now().timestamp());
|
||||
cb.share_data(name, png).unwrap_or_default();
|
||||
}
|
||||
@@ -297,12 +307,13 @@ impl QrCodeContent {
|
||||
egui::CornerRadius::default(),
|
||||
egui::Color32::WHITE,
|
||||
egui::Stroke::NONE,
|
||||
egui::StrokeKind::Outside
|
||||
egui::StrokeKind::Outside,
|
||||
);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
// Draw QR code image.
|
||||
let mut content_rect = ui.scope_builder(UiBuilder::new().max_rect(rect), |ui| {
|
||||
let mut content_rect = ui
|
||||
.scope_builder(UiBuilder::new().max_rect(rect), |ui| {
|
||||
ui.add_space(10.0);
|
||||
let size = SizeHint::Size {
|
||||
width: ui.available_width() as u32,
|
||||
@@ -311,7 +322,9 @@ impl QrCodeContent {
|
||||
};
|
||||
self.texture_handle = Some(View::svg_image(ui, "qr", svg.as_slice(), size));
|
||||
ui.add_space(10.0);
|
||||
}).response.rect;
|
||||
})
|
||||
.response
|
||||
.rect;
|
||||
|
||||
// Setup background size.
|
||||
content_rect.min -= egui::emath::vec2(10.0, 0.0);
|
||||
@@ -379,11 +392,16 @@ impl QrCodeContent {
|
||||
/// Convert QR code to SVG string.
|
||||
fn qr_to_svg(qr: QrCode, border: i32) -> String {
|
||||
let mut result = String::new();
|
||||
let dimension = qr.size().checked_add(border.checked_mul(2).unwrap()).unwrap();
|
||||
let dimension = qr
|
||||
.size()
|
||||
.checked_add(border.checked_mul(2).unwrap())
|
||||
.unwrap();
|
||||
result += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||
result += "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n";
|
||||
result += &format!(
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 {0} {0}\" stroke=\"none\">\n", dimension);
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 {0} {0}\" stroke=\"none\">\n",
|
||||
dimension
|
||||
);
|
||||
result += "\t<rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\"/>\n";
|
||||
result += "\t<path d=\"";
|
||||
for y in 0..qr.size() {
|
||||
@@ -406,7 +424,6 @@ impl QrCodeContent {
|
||||
{
|
||||
let mut w_state = self.qr_image_state.write();
|
||||
w_state.gif_creating = true;
|
||||
|
||||
}
|
||||
let qr_state = self.qr_image_state.clone();
|
||||
let text = self.text.clone();
|
||||
@@ -421,10 +438,11 @@ impl QrCodeContent {
|
||||
let ur = ur_enc.next_part().unwrap();
|
||||
if let Ok(qr) = qrcode::QrCode::with_error_correction_level(
|
||||
ur.as_bytes(),
|
||||
qrcode::EcLevel::L
|
||||
qrcode::EcLevel::L,
|
||||
) {
|
||||
// Create an image from QR data.
|
||||
let image = qr.render()
|
||||
let image = qr
|
||||
.render()
|
||||
.max_dimensions(DEFAULT_QR_SIZE, DEFAULT_QR_SIZE)
|
||||
.dark_color(image::Rgb([0, 0, 0]))
|
||||
.light_color(image::Rgb([255, 255, 255]))
|
||||
@@ -436,15 +454,20 @@ impl QrCodeContent {
|
||||
if !qrs.is_empty() {
|
||||
// Generate GIF data.
|
||||
let color_map = &[0, 0, 0, 0xFF, 0xFF, 0xFF];
|
||||
let mut gif_enc = gif::Encoder::new(&mut gif,
|
||||
let mut gif_enc = gif::Encoder::new(
|
||||
&mut gif,
|
||||
qrs[0].width() as u16,
|
||||
qrs[0].height() as u16,
|
||||
color_map).unwrap();
|
||||
color_map,
|
||||
)
|
||||
.unwrap();
|
||||
gif_enc.set_repeat(gif::Repeat::Infinite).unwrap();
|
||||
for qr in qrs {
|
||||
let mut frame = gif::Frame::from_rgb(qr.width() as u16,
|
||||
let mut frame = gif::Frame::from_rgb(
|
||||
qr.width() as u16,
|
||||
qr.height() as u16,
|
||||
qr.as_raw().as_slice());
|
||||
qr.as_raw().as_slice(),
|
||||
);
|
||||
frame.delay = 10;
|
||||
// Write an image to GIF encoder.
|
||||
if let Ok(_) = gif_enc.write_frame(&frame) {
|
||||
|
||||
+25
-10
@@ -18,8 +18,8 @@ use egui::{Id, ScrollArea};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::COPY;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, Modal, View};
|
||||
use crate::gui::views::types::QrScanResult;
|
||||
use crate::gui::views::{CameraContent, Modal, View};
|
||||
|
||||
/// QR code scanning content.
|
||||
pub struct CameraScanContent {
|
||||
@@ -40,19 +40,27 @@ impl Default for CameraScanContent {
|
||||
|
||||
impl CameraScanContent {
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn modal_ui(&mut self,
|
||||
pub fn modal_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
mut on_result: impl FnMut(&QrScanResult)) {
|
||||
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.clone() {
|
||||
Self::result_ui(ui, result, cb, || {
|
||||
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() {
|
||||
cb.stop_camera();
|
||||
@@ -68,11 +76,16 @@ impl CameraScanContent {
|
||||
self.camera_content.as_mut().unwrap().ui(ui, cb);
|
||||
ui.add_space(12.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
cb.stop_camera();
|
||||
self.camera_content = None;
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -80,11 +93,13 @@ impl CameraScanContent {
|
||||
}
|
||||
|
||||
/// Draw scan result content.
|
||||
pub fn result_ui(ui: &mut egui::Ui,
|
||||
pub fn result_ui(
|
||||
ui: &mut egui::Ui,
|
||||
result: &QrScanResult,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_close: impl FnOnce(),
|
||||
on_repeat: impl FnOnce()) {
|
||||
on_repeat: impl FnOnce(),
|
||||
) {
|
||||
let mut result_text = result.text();
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(3.0);
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::GLOBE_SIMPLE;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::View;
|
||||
use crate::gui::views::settings::{InterfaceSettingsContent, NetworkSettingsContent};
|
||||
use crate::gui::views::types::ContentContainer;
|
||||
use crate::gui::views::View;
|
||||
use crate::gui::Colors;
|
||||
|
||||
/// Application settings content.
|
||||
pub struct SettingsContent {
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
use eframe::epaint::RectShape;
|
||||
use egui::{Align, CursorIcon, Layout, RichText, Sense, StrokeKind, UiBuilder};
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::ContentContainer;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::AppConfig;
|
||||
|
||||
/// User interface settings content.
|
||||
pub struct InterfaceSettingsContent {
|
||||
@@ -28,7 +28,9 @@ pub struct InterfaceSettingsContent {
|
||||
}
|
||||
|
||||
impl ContentContainer for InterfaceSettingsContent {
|
||||
fn modal_ids(&self) -> Vec<&'static str> { vec![] }
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self, _: &mut egui::Ui, _: &Modal, _: &dyn PlatformCallbacks) {}
|
||||
|
||||
@@ -74,9 +76,7 @@ impl Default for InterfaceSettingsContent {
|
||||
} else {
|
||||
rust_i18n::locale().to_string()
|
||||
};
|
||||
Self {
|
||||
locale,
|
||||
}
|
||||
Self { locale }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,16 +96,21 @@ impl InterfaceSettingsContent {
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
let res = ui.scope_builder(
|
||||
let res = ui
|
||||
.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
.max_rect(rect),
|
||||
|ui| {
|
||||
if is_current {
|
||||
View::selected_item_check(ui);
|
||||
}
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
layout_size,
|
||||
Layout::left_to_right(Align::Center),
|
||||
|ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
// Draw language name.
|
||||
@@ -115,14 +120,18 @@ impl InterfaceSettingsContent {
|
||||
} else {
|
||||
Colors::gray()
|
||||
};
|
||||
ui.label(RichText::new(t!("lang_name", locale = locale))
|
||||
ui.label(
|
||||
RichText::new(t!("lang_name", locale = locale))
|
||||
.size(17.0)
|
||||
.color(color));
|
||||
.color(color),
|
||||
);
|
||||
ui.add_space(14.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() && !is_current {
|
||||
|
||||
@@ -16,12 +16,12 @@ use eframe::epaint::RectShape;
|
||||
use egui::{Align, CursorIcon, Id, Layout, RichText, Sense, StrokeKind, UiBuilder};
|
||||
use url::Url;
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CLOUD_CHECK, CLOUD_SLASH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::AppConfig;
|
||||
|
||||
/// Network communication settings content.
|
||||
pub struct NetworkSettingsContent {
|
||||
@@ -36,9 +36,7 @@ const PROXY_URL_EDIT_MODAL: &'static str = "settings_proxy_edit_modal";
|
||||
|
||||
impl ContentContainer for NetworkSettingsContent {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
PROXY_URL_EDIT_MODAL
|
||||
]
|
||||
vec![PROXY_URL_EDIT_MODAL]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
@@ -52,8 +50,10 @@ impl ContentContainer for NetworkSettingsContent {
|
||||
let use_proxy = AppConfig::use_proxy();
|
||||
View::checkbox(ui, use_proxy, t!("app_settings.proxy"), || {
|
||||
// Show edit modal when both URLs are empty.
|
||||
if AppConfig::http_proxy_url().is_none() && AppConfig::socks_proxy_url().is_none() &&
|
||||
!use_proxy {
|
||||
if AppConfig::http_proxy_url().is_none()
|
||||
&& AppConfig::socks_proxy_url().is_none()
|
||||
&& !use_proxy
|
||||
{
|
||||
Modal::new(PROXY_URL_EDIT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("app_settings.proxy"))
|
||||
@@ -64,9 +64,10 @@ impl ContentContainer for NetworkSettingsContent {
|
||||
});
|
||||
if !use_proxy {
|
||||
ui.add_space(4.0);
|
||||
ui.label(RichText::new(t!("app_settings.proxy_desc"))
|
||||
ui.label(
|
||||
RichText::new(t!("app_settings.proxy_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text())
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
} else {
|
||||
@@ -147,15 +148,15 @@ impl NetworkSettingsContent {
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw proxy URL text edit.
|
||||
let mut edit = TextEdit::new(
|
||||
Id::from("proxy_url_edit")
|
||||
.with(PROXY_URL_EDIT_MODAL)
|
||||
.with(if AppConfig::use_proxy() {
|
||||
let mut edit =
|
||||
TextEdit::new(Id::from("proxy_url_edit").with(PROXY_URL_EDIT_MODAL).with(
|
||||
if AppConfig::use_proxy() {
|
||||
"socks5"
|
||||
} else {
|
||||
"http"
|
||||
})
|
||||
).paste();
|
||||
},
|
||||
))
|
||||
.paste();
|
||||
edit.ui(ui, &mut self.proxy_url_edit, cb);
|
||||
if edit.enter_pressed {
|
||||
on_save(self);
|
||||
@@ -164,9 +165,11 @@ impl NetworkSettingsContent {
|
||||
// Show error when specified address is incorrect.
|
||||
if self.proxy_url_error {
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(t!("wallets.invalid_url"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.invalid_url"))
|
||||
.size(16.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
|
||||
@@ -186,9 +189,14 @@ impl NetworkSettingsContent {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -211,13 +219,18 @@ impl NetworkSettingsContent {
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
let res = ui.scope_builder(
|
||||
let res = ui
|
||||
.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
.max_rect(rect),
|
||||
|ui| {
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
layout_size,
|
||||
Layout::left_to_right(Align::Center),
|
||||
|ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
@@ -228,13 +241,18 @@ impl NetworkSettingsContent {
|
||||
AppConfig::http_proxy_url()
|
||||
};
|
||||
let (url, color, icon, text) = if let Some(url) = proxy_url {
|
||||
(url, Colors::title(false), CLOUD_CHECK, t!("network_settings.enabled"))
|
||||
(
|
||||
url,
|
||||
Colors::title(false),
|
||||
CLOUD_CHECK,
|
||||
t!("network_settings.enabled"),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
t!("enter_url").into(),
|
||||
Colors::inactive_text(),
|
||||
CLOUD_SLASH,
|
||||
t!("network_settings.disabled")
|
||||
t!("network_settings.disabled"),
|
||||
)
|
||||
};
|
||||
View::ellipsize_text(ui, url, 18.0, color);
|
||||
@@ -244,9 +262,11 @@ impl NetworkSettingsContent {
|
||||
ui.label(RichText::new(value).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
|
||||
@@ -18,11 +18,13 @@ use egui::{Align, CursorIcon, Id, Layout, RichText, ScrollArea, Sense, StrokeKin
|
||||
use std::fs;
|
||||
use url::Url;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CLIPBOARD_TEXT, CLOUD_CHECK, NOTCHES, PENCIL, SCAN, TERMINAL};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{CameraScanContent, FilePickContent, FilePickContentType, Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::views::{
|
||||
CameraScanContent, FilePickContent, FilePickContentType, Modal, TextEdit, View,
|
||||
};
|
||||
use crate::tor::{TorBridge, TorConfig, TorProxy};
|
||||
|
||||
/// Transport settings content.
|
||||
@@ -69,9 +71,10 @@ impl Default for TorSettingsContent {
|
||||
proxy_url_edit: "".to_string(),
|
||||
proxy_url_error: false,
|
||||
bridge_bin_path_edit: bin_path,
|
||||
bridge_bin_pick_file: FilePickContent::new(
|
||||
FilePickContentType::ItemButton(View::item_rounding(0, 1, true))
|
||||
).no_parse(),
|
||||
bridge_bin_pick_file: FilePickContent::new(FilePickContentType::ItemButton(
|
||||
View::item_rounding(0, 1, true),
|
||||
))
|
||||
.no_parse(),
|
||||
bridge_conn_line_edit: conn_line,
|
||||
bridge_qr_scan_content: None,
|
||||
}
|
||||
@@ -84,7 +87,7 @@ impl ContentContainer for TorSettingsContent {
|
||||
PROXY_URL_EDIT_MODAL,
|
||||
BRIDGE_BIN_EDIT_MODAL,
|
||||
BRIDGE_CONN_LINE_EDIT_MODAL,
|
||||
SCAN_BRIDGE_CONN_LINE_MODAL
|
||||
SCAN_BRIDGE_CONN_LINE_MODAL,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -118,9 +121,11 @@ impl ContentContainer for TorSettingsContent {
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
ui.label(RichText::new(format!("{}:", t!("wallets.conn_method")))
|
||||
ui.label(
|
||||
RichText::new(format!("{}:", t!("wallets.conn_method")))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(10.0);
|
||||
|
||||
let mut proxy = TorConfig::get_proxy();
|
||||
@@ -132,7 +137,8 @@ impl ContentContainer for TorSettingsContent {
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
let name = t!("app_settings.proxy");
|
||||
let val = current_proxy.clone()
|
||||
let val = current_proxy
|
||||
.clone()
|
||||
.unwrap_or(TorProxy::SOCKS5(TorProxy::DEFAULT_SOCKS5_URL.to_string()));
|
||||
View::radio_value(ui, &mut proxy, Some(val), name);
|
||||
});
|
||||
@@ -142,9 +148,11 @@ impl ContentContainer for TorSettingsContent {
|
||||
ui.add_space(6.0);
|
||||
|
||||
if let Some(p) = proxy.as_mut() {
|
||||
ui.label(RichText::new(format!("{}:", t!("app_settings.proxy")))
|
||||
ui.label(
|
||||
RichText::new(format!("{}:", t!("app_settings.proxy")))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(10.0);
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered(|ui| {
|
||||
@@ -173,9 +181,11 @@ impl ContentContainer for TorSettingsContent {
|
||||
|
||||
let bridge = TorConfig::get_bridge();
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.bridges_desc"))
|
||||
ui.label(
|
||||
RichText::new(t!("transport.bridges_desc"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
|
||||
// Draw checkbox to enable/disable bridges.
|
||||
View::checkbox(ui, bridge.is_some(), t!("transport.bridges"), || {
|
||||
@@ -205,7 +215,6 @@ impl ContentContainer for TorSettingsContent {
|
||||
let webtunnel = TorConfig::get_webtunnel();
|
||||
let name = webtunnel.protocol_name().to_uppercase();
|
||||
View::radio_value(ui, &mut bridge, webtunnel, name);
|
||||
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
// Show Obfs4 bridge selector.
|
||||
@@ -281,8 +290,8 @@ impl TorSettingsContent {
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw proxy URL text edit.
|
||||
let mut edit = TextEdit::new(Id::from("proxy_url_edit").with(PROXY_URL_EDIT_MODAL))
|
||||
.paste();
|
||||
let mut edit =
|
||||
TextEdit::new(Id::from("proxy_url_edit").with(PROXY_URL_EDIT_MODAL)).paste();
|
||||
edit.ui(ui, &mut self.proxy_url_edit, cb);
|
||||
if edit.enter_pressed {
|
||||
on_save(self);
|
||||
@@ -291,9 +300,11 @@ impl TorSettingsContent {
|
||||
// Show error when specified address is incorrect.
|
||||
if self.proxy_url_error {
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(t!("wallets.invalid_url"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.invalid_url"))
|
||||
.size(16.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
|
||||
@@ -304,9 +315,14 @@ impl TorSettingsContent {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -328,11 +344,13 @@ impl TorSettingsContent {
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(0, 1, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
ui.painter().rect(
|
||||
bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), PENCIL, None, || {
|
||||
@@ -373,16 +391,21 @@ impl TorSettingsContent {
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
let res = ui.scope_builder(
|
||||
let res = ui
|
||||
.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
.max_rect(rect),
|
||||
|ui| {
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), SCAN, None, || {
|
||||
self.show_qr_scan_bridge_modal(cb);
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
layout_size,
|
||||
Layout::left_to_right(Align::Center),
|
||||
|ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
@@ -394,9 +417,11 @@ impl TorSettingsContent {
|
||||
ui.label(RichText::new(value).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
@@ -441,9 +466,11 @@ impl TorSettingsContent {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.conn_line"))
|
||||
ui.label(
|
||||
RichText::new(t!("transport.conn_line"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw connection line text edit.
|
||||
@@ -505,10 +532,15 @@ impl TorSettingsContent {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -531,11 +563,13 @@ impl TorSettingsContent {
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
let res = ui.scope_builder(
|
||||
let res = ui
|
||||
.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
.max_rect(rect),
|
||||
|ui| {
|
||||
self.bridge_bin_pick_file.ui(ui, cb, |path| {
|
||||
if bridge.binary_path() != path {
|
||||
TorBridge::save_bridge_bin_path(bridge, path);
|
||||
@@ -543,7 +577,10 @@ impl TorSettingsContent {
|
||||
}
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
layout_size,
|
||||
Layout::left_to_right(Align::Center),
|
||||
|ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
@@ -555,9 +592,11 @@ impl TorSettingsContent {
|
||||
ui.label(RichText::new(value).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
@@ -594,9 +633,11 @@ impl TorSettingsContent {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("transport.bin_file"))
|
||||
ui.label(
|
||||
RichText::new(t!("transport.bin_file"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw bridge text edit.
|
||||
@@ -614,10 +655,15 @@ impl TorSettingsContent {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
|
||||
@@ -12,16 +12,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Margin, Id, Layout, Align, UiBuilder};
|
||||
use egui::{Align, Id, Layout, Margin, UiBuilder};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::views::{Content, View};
|
||||
use crate::gui::views::types::{TitleContentType, TitleType};
|
||||
use crate::gui::views::{Content, View};
|
||||
|
||||
/// Title panel with left/right action buttons and text in the middle.
|
||||
pub struct TitlePanel {
|
||||
/// Widget identifier.
|
||||
id: Id
|
||||
id: Id,
|
||||
}
|
||||
|
||||
impl TitlePanel {
|
||||
@@ -30,16 +30,16 @@ impl TitlePanel {
|
||||
|
||||
/// Create new title panel with provided identifier.
|
||||
pub fn new(id: Id) -> Self {
|
||||
Self {
|
||||
id,
|
||||
}
|
||||
Self { id }
|
||||
}
|
||||
|
||||
pub fn ui(&self,
|
||||
pub fn ui(
|
||||
&self,
|
||||
title: TitleType,
|
||||
mut left_content: impl FnMut(&mut egui::Ui),
|
||||
mut right_content: impl FnMut(&mut egui::Ui),
|
||||
ui: &mut egui::Ui) {
|
||||
ui: &mut egui::Ui,
|
||||
) {
|
||||
// Draw title panel.
|
||||
egui::TopBottomPanel::top(self.id)
|
||||
.resizable(false)
|
||||
@@ -106,14 +106,9 @@ impl TitlePanel {
|
||||
|
||||
/// Setup title text content.
|
||||
fn title_text_content(ui: &mut egui::Ui, content: TitleContentType) {
|
||||
ui.vertical_centered(|ui| {
|
||||
match content {
|
||||
ui.vertical_centered(|ui| match content {
|
||||
TitleContentType::Title(text) => {
|
||||
ui.add_space(13.0 + if !View::is_desktop() {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
});
|
||||
ui.add_space(13.0 + if !View::is_desktop() { 1.0 } else { 0.0 });
|
||||
View::ellipsize_text(ui, text.to_uppercase(), 19.0, Colors::title(true));
|
||||
}
|
||||
TitleContentType::WithSubTitle(text, subtitle, animate) => {
|
||||
@@ -122,7 +117,6 @@ impl TitlePanel {
|
||||
ui.add_space(-2.0);
|
||||
View::animate_text(ui, subtitle, 15.0, Colors::text(true), animate)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use grin_util::ZeroingString;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::Modal;
|
||||
use grin_util::ZeroingString;
|
||||
|
||||
/// Title type, can be single or dual title in the row.
|
||||
pub enum TitleType {
|
||||
@@ -29,19 +29,22 @@ pub enum TitleContentType {
|
||||
/// Single text.
|
||||
Title(String),
|
||||
/// With optionally animated subtitle text.
|
||||
WithSubTitle(String, String, bool)
|
||||
WithSubTitle(String, String, bool),
|
||||
}
|
||||
|
||||
/// Stroke position against content.
|
||||
pub enum LinePosition {
|
||||
TOP, LEFT, RIGHT, BOTTOM
|
||||
TOP,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
BOTTOM,
|
||||
}
|
||||
|
||||
/// Position of [`Modal`] on the screen.
|
||||
#[derive(Clone)]
|
||||
pub enum ModalPosition {
|
||||
CenterTop,
|
||||
Center
|
||||
Center,
|
||||
}
|
||||
|
||||
/// Global [`Modal`] state.
|
||||
@@ -108,7 +111,7 @@ pub struct QrScanState {
|
||||
/// Flag to check if image is processing to find QR code.
|
||||
pub image_processing: bool,
|
||||
/// Processed QR code result.
|
||||
pub qr_scan_result: Option<QrScanResult>
|
||||
pub qr_scan_result: Option<QrScanResult>,
|
||||
}
|
||||
|
||||
impl Default for QrScanState {
|
||||
@@ -135,7 +138,7 @@ pub struct QrImageState {
|
||||
/// Vector image data.
|
||||
pub svg: Option<Vec<u8>>,
|
||||
/// Multiple vector image data for animated QR code.
|
||||
pub svg_list: Option<Vec<Vec<u8>>>
|
||||
pub svg_list: Option<Vec<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Default for QrImageState {
|
||||
|
||||
+147
-86
@@ -12,21 +12,24 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
use egui::emath::GuiRounding;
|
||||
use egui::epaint::text::TextWrapping;
|
||||
use egui::epaint::{Color32, FontId, PathShape, PathStroke, RectShape, Stroke};
|
||||
use egui::load::SizedTexture;
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::text::{LayoutJob, TextFormat};
|
||||
use egui::{lerp, Button, CornerRadius, CursorIcon, Rect, Response, Rgba, RichText, Sense, SizeHint, Spinner, StrokeKind, TextureHandle, TextureOptions, UiBuilder, Widget};
|
||||
use egui::{
|
||||
Button, CornerRadius, CursorIcon, Rect, Response, Rgba, RichText, Sense, SizeHint, Spinner,
|
||||
StrokeKind, TextureHandle, TextureOptions, UiBuilder, Widget, lerp,
|
||||
};
|
||||
use egui_extras::image::load_svg_bytes_with_size;
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK_FAT, CHECK_SQUARE, SQUARE};
|
||||
use crate::gui::views::types::LinePosition;
|
||||
use crate::gui::Colors;
|
||||
use crate::AppConfig;
|
||||
|
||||
pub struct View;
|
||||
|
||||
@@ -47,23 +50,34 @@ impl View {
|
||||
|
||||
/// Get default stroke around views.
|
||||
pub fn default_stroke() -> Stroke {
|
||||
Stroke { width: 1.0, color: Colors::stroke() }
|
||||
Stroke {
|
||||
width: 1.0,
|
||||
color: Colors::stroke(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get default stroke around item buttons.
|
||||
pub fn item_stroke() -> Stroke {
|
||||
Stroke { width: 1.0, color: Colors::item_stroke() }
|
||||
Stroke {
|
||||
width: 1.0,
|
||||
color: Colors::item_stroke(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get stroke for hovered items and buttons.
|
||||
pub fn hover_stroke() -> Stroke {
|
||||
Stroke { width: 1.0, color: Colors::item_hover() }
|
||||
Stroke {
|
||||
width: 1.0,
|
||||
color: Colors::item_hover(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw content with maximum width value.
|
||||
pub fn max_width_ui(ui: &mut egui::Ui,
|
||||
pub fn max_width_ui(
|
||||
ui: &mut egui::Ui,
|
||||
max_width: f32,
|
||||
add_content: impl FnOnce(&mut egui::Ui)) {
|
||||
add_content: impl FnOnce(&mut egui::Ui),
|
||||
) {
|
||||
// Setup content width.
|
||||
let mut width = ui.available_width();
|
||||
if width == 0.0 {
|
||||
@@ -111,18 +125,19 @@ impl View {
|
||||
|
||||
/// Content padding for current platform.
|
||||
pub fn content_padding() -> f32 {
|
||||
if View::is_desktop() {
|
||||
4.0
|
||||
} else {
|
||||
8.0
|
||||
}
|
||||
if View::is_desktop() { 4.0 } else { 8.0 }
|
||||
}
|
||||
|
||||
/// Cut long text with ﹍ character.
|
||||
fn ellipsize(text: impl Into<String>, size: f32, color: Color32) -> LayoutJob {
|
||||
let mut job = LayoutJob::single_section(text.into(), TextFormat {
|
||||
font_id: FontId::proportional(size), color, ..Default::default()
|
||||
});
|
||||
let mut job = LayoutJob::single_section(
|
||||
text.into(),
|
||||
TextFormat {
|
||||
font_id: FontId::proportional(size),
|
||||
color,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
job.wrap = TextWrapping {
|
||||
max_rows: 1,
|
||||
break_anywhere: true,
|
||||
@@ -138,11 +153,13 @@ impl View {
|
||||
}
|
||||
|
||||
/// Draw animated ellipsized text.
|
||||
pub fn animate_text(ui: &mut egui::Ui,
|
||||
pub fn animate_text(
|
||||
ui: &mut egui::Ui,
|
||||
text: impl Into<String>,
|
||||
size: f32,
|
||||
color: Color32,
|
||||
animate: bool) {
|
||||
animate: bool,
|
||||
) {
|
||||
// Setup text color animation if needed.
|
||||
let (dark, bright) = (0.3, 1.0);
|
||||
let color_factor = if animate {
|
||||
@@ -165,7 +182,11 @@ impl View {
|
||||
/// Draw horizontally centered subtitle with space below.
|
||||
pub fn sub_title(ui: &mut egui::Ui, text: String) {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.label(RichText::new(text.to_uppercase()).size(16.0).color(Colors::text(false)));
|
||||
ui.label(
|
||||
RichText::new(text.to_uppercase())
|
||||
.size(16.0)
|
||||
.color(Colors::text(false)),
|
||||
);
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
@@ -197,7 +218,9 @@ impl View {
|
||||
ui.style_mut().visuals.widgets.active.expansion = 0.0;
|
||||
|
||||
// Setup text.
|
||||
let wt = RichText::new(icon.to_string()).size(size).color(Colors::title(true));
|
||||
let wt = RichText::new(icon.to_string())
|
||||
.size(size)
|
||||
.color(Colors::title(true));
|
||||
// Draw button.
|
||||
let br = Button::new(wt)
|
||||
.sense(if View::is_desktop() {
|
||||
@@ -219,11 +242,13 @@ impl View {
|
||||
pub const TAB_ITEMS_PADDING: f32 = 5.0;
|
||||
|
||||
/// Tab button with white background fill color, contains only icon.
|
||||
pub fn tab_button(ui: &mut egui::Ui,
|
||||
pub fn tab_button(
|
||||
ui: &mut egui::Ui,
|
||||
icon: &str,
|
||||
color: Option<Color32>,
|
||||
selected: Option<bool>,
|
||||
action: impl FnOnce(&mut egui::Ui)) {
|
||||
action: impl FnOnce(&mut egui::Ui),
|
||||
) {
|
||||
ui.scope(|ui| {
|
||||
let text_color = if let Some(c) = color {
|
||||
if selected.is_none() {
|
||||
@@ -235,7 +260,7 @@ impl View {
|
||||
if let Some(active) = selected {
|
||||
match active {
|
||||
true => Colors::gray(),
|
||||
false => Colors::item_button_text()
|
||||
false => Colors::item_button_text(),
|
||||
}
|
||||
} else {
|
||||
Colors::inactive_text()
|
||||
@@ -301,11 +326,13 @@ impl View {
|
||||
}
|
||||
|
||||
/// Draw [`Button`] with specified background fill color and text color.
|
||||
pub fn colored_text_button(ui: &mut egui::Ui,
|
||||
pub fn colored_text_button(
|
||||
ui: &mut egui::Ui,
|
||||
text: String,
|
||||
text_color: Color32,
|
||||
fill: Color32,
|
||||
action: impl FnOnce()) {
|
||||
action: impl FnOnce(),
|
||||
) {
|
||||
let br = Self::button_resp(ui, text, text_color, fill);
|
||||
if br.clicked() {
|
||||
action();
|
||||
@@ -313,11 +340,13 @@ impl View {
|
||||
}
|
||||
|
||||
/// Draw [`Button`] with specified background fill color and text color.
|
||||
pub fn colored_text_button_ui(ui: &mut egui::Ui,
|
||||
pub fn colored_text_button_ui(
|
||||
ui: &mut egui::Ui,
|
||||
text: String,
|
||||
text_color: Color32,
|
||||
fill: Color32,
|
||||
action: impl FnOnce(&mut egui::Ui)) {
|
||||
action: impl FnOnce(&mut egui::Ui),
|
||||
) {
|
||||
let br = Self::button_resp(ui, text, text_color, fill);
|
||||
if br.clicked() {
|
||||
action(ui);
|
||||
@@ -325,16 +354,17 @@ impl View {
|
||||
}
|
||||
|
||||
/// Draw gold action [`Button`].
|
||||
pub fn action_button(ui: &mut egui::Ui,
|
||||
text: impl Into<String>, action: impl FnOnce()) {
|
||||
pub fn action_button(ui: &mut egui::Ui, text: impl Into<String>, action: impl FnOnce()) {
|
||||
Self::colored_text_button(ui, text.into(), Colors::title(true), Colors::gold(), action);
|
||||
}
|
||||
|
||||
/// Draw [`Button`] with specified background fill color and ui at callback.
|
||||
pub fn button_ui(ui: &mut egui::Ui,
|
||||
pub fn button_ui(
|
||||
ui: &mut egui::Ui,
|
||||
text: impl Into<String>,
|
||||
fill: Color32,
|
||||
action: impl FnOnce(&mut egui::Ui)) {
|
||||
action: impl FnOnce(&mut egui::Ui),
|
||||
) {
|
||||
let button_text = Self::ellipsize(text.into().to_uppercase(), 17.0, Colors::text_button());
|
||||
let br = Button::new(button_text)
|
||||
.stroke(Self::default_stroke())
|
||||
@@ -347,11 +377,13 @@ impl View {
|
||||
}
|
||||
|
||||
/// Draw list item [`Button`] with provided rounding.
|
||||
pub fn item_button(ui: &mut egui::Ui,
|
||||
pub fn item_button(
|
||||
ui: &mut egui::Ui,
|
||||
rounding: CornerRadius,
|
||||
text: &'static str,
|
||||
color: Option<Color32>,
|
||||
action: impl FnOnce()) {
|
||||
action: impl FnOnce(),
|
||||
) {
|
||||
// Setup button size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_width(42.0);
|
||||
@@ -359,11 +391,7 @@ impl View {
|
||||
|
||||
ui.scope(|ui| {
|
||||
// Setup padding for item buttons.
|
||||
let padding = if Self::is_desktop() {
|
||||
15.0
|
||||
} else {
|
||||
18.0
|
||||
};
|
||||
let padding = if Self::is_desktop() { 15.0 } else { 18.0 };
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(padding, 0.0);
|
||||
// Disable expansion on click/hover.
|
||||
ui.style_mut().visuals.widgets.hovered.expansion = 0.0;
|
||||
@@ -378,7 +406,11 @@ 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_text() };
|
||||
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))
|
||||
@@ -435,11 +467,7 @@ impl View {
|
||||
|
||||
/// Draw selected item check.
|
||||
pub fn selected_item_check(ui: &mut egui::Ui) {
|
||||
let padding = if View::is_desktop() {
|
||||
14.0
|
||||
} else {
|
||||
18.0
|
||||
};
|
||||
let padding = if View::is_desktop() { 14.0 } else { 18.0 };
|
||||
ui.add_space(padding);
|
||||
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green()));
|
||||
ui.add_space(padding);
|
||||
@@ -453,16 +481,23 @@ impl View {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
|
||||
// Create background shape.
|
||||
let mut bg_shape = RectShape::new(rect, CornerRadius {
|
||||
let mut bg_shape = RectShape::new(
|
||||
rect,
|
||||
CornerRadius {
|
||||
nw: if r[0] { 8.0 as u8 } else { 0.0 as u8 },
|
||||
ne: if r[1] { 8.0 as u8 } else { 0.0 as u8 },
|
||||
sw: if r[2] { 8.0 as u8 } else { 0.0 as u8 },
|
||||
se: if r[3] { 8.0 as u8 } else { 0.0 as u8 },
|
||||
}, Colors::fill(), Self::item_stroke(), StrokeKind::Outside);
|
||||
},
|
||||
Colors::fill(),
|
||||
Self::item_stroke(),
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
// Draw box content.
|
||||
let content_resp = ui.scope_builder(UiBuilder::new().max_rect(rect), |ui| {
|
||||
let content_resp = ui
|
||||
.scope_builder(UiBuilder::new().max_rect(rect), |ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.add_space(4.0);
|
||||
ui.scope(|ui| {
|
||||
@@ -470,11 +505,14 @@ impl View {
|
||||
ui.style_mut().spacing.item_spacing.y = -3.0;
|
||||
|
||||
// Draw box value.
|
||||
let mut job = LayoutJob::single_section(v.into(), TextFormat {
|
||||
let mut job = LayoutJob::single_section(
|
||||
v.into(),
|
||||
TextFormat {
|
||||
font_id: FontId::proportional(17.0),
|
||||
color: Colors::white_or_black(true),
|
||||
..Default::default()
|
||||
});
|
||||
},
|
||||
);
|
||||
job.wrap = TextWrapping {
|
||||
max_rows: 1,
|
||||
break_anywhere: true,
|
||||
@@ -488,7 +526,8 @@ impl View {
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
});
|
||||
}).response;
|
||||
})
|
||||
.response;
|
||||
|
||||
// Setup background shape size.
|
||||
bg_shape.rect = content_resp.rect;
|
||||
@@ -532,8 +571,11 @@ impl View {
|
||||
/// Draw the button that looks like checkbox with callback on check.
|
||||
pub fn checkbox(ui: &mut egui::Ui, checked: bool, text: impl Into<String>, cb: impl FnOnce()) {
|
||||
let (text_value, color) = match checked {
|
||||
true => (format!("{} {}", CHECK_SQUARE, text.into()), Colors::text_button()),
|
||||
false => (format!("{} {}", SQUARE, text.into()), Colors::checkbox())
|
||||
true => (
|
||||
format!("{} {}", CHECK_SQUARE, text.into()),
|
||||
Colors::text_button(),
|
||||
),
|
||||
false => (format!("{} {}", SQUARE, text.into()), Colors::checkbox()),
|
||||
};
|
||||
|
||||
let br = Button::new(RichText::new(text_value).size(17.0).color(color))
|
||||
@@ -549,15 +591,18 @@ impl View {
|
||||
|
||||
/// Show a [`RadioButton`]. It is selected if `*current_value == selected_value`.
|
||||
/// If clicked, `selected_value` is assigned to `*current_value`.
|
||||
pub fn radio_value<T: PartialEq>(ui: &mut egui::Ui,
|
||||
pub fn radio_value<T: PartialEq>(
|
||||
ui: &mut egui::Ui,
|
||||
current: &mut T,
|
||||
value: T,
|
||||
text: impl Into<String>) {
|
||||
text: impl Into<String>,
|
||||
) {
|
||||
ui.scope(|ui| {
|
||||
// Setup background color.
|
||||
ui.visuals_mut().widgets.inactive.bg_fill = Colors::fill_deep();
|
||||
// Draw radio button.
|
||||
let mut response = ui.radio(*current == value, text.into())
|
||||
let mut response = ui
|
||||
.radio(*current == value, text.into())
|
||||
.on_hover_cursor(CursorIcon::PointingHand);
|
||||
if response.clicked() && *current != value {
|
||||
*current = value;
|
||||
@@ -571,27 +616,38 @@ impl View {
|
||||
let line_size = egui::Vec2::new(ui.available_width(), 1.0);
|
||||
let (line_rect, _) = ui.allocate_exact_size(line_size, Sense::hover());
|
||||
let painter = ui.painter();
|
||||
painter.hline(line_rect.x_range(),
|
||||
line_rect.center().y.round_to_pixels(painter.pixels_per_point()),
|
||||
Stroke { width: 1.0, color });
|
||||
painter.hline(
|
||||
line_rect.x_range(),
|
||||
line_rect
|
||||
.center()
|
||||
.y
|
||||
.round_to_pixels(painter.pixels_per_point()),
|
||||
Stroke { width: 1.0, color },
|
||||
);
|
||||
}
|
||||
|
||||
/// Draw line for panel content.
|
||||
pub fn line(ui: &mut egui::Ui, pos: LinePosition, rect: &Rect, color: Color32) {
|
||||
let points = match pos {
|
||||
LinePosition::RIGHT => {
|
||||
vec![{
|
||||
vec![
|
||||
{
|
||||
let mut r = rect.clone();
|
||||
r.min.x = r.max.x;
|
||||
r.min
|
||||
}, rect.max]
|
||||
},
|
||||
rect.max,
|
||||
]
|
||||
}
|
||||
LinePosition::BOTTOM => {
|
||||
vec![{
|
||||
vec![
|
||||
{
|
||||
let mut r = rect.clone();
|
||||
r.min.y = r.max.y;
|
||||
r.min
|
||||
}, rect.max]
|
||||
},
|
||||
rect.max,
|
||||
]
|
||||
}
|
||||
LinePosition::LEFT => {
|
||||
vec![rect.min, {
|
||||
@@ -618,22 +674,20 @@ impl View {
|
||||
}
|
||||
|
||||
/// Draw SVG image from provided data with optional provided size.
|
||||
pub fn svg_image(ui: &mut egui::Ui,
|
||||
name: &str,
|
||||
svg: &[u8],
|
||||
size: SizeHint) -> TextureHandle {
|
||||
pub fn svg_image(ui: &mut egui::Ui, name: &str, svg: &[u8], size: SizeHint) -> TextureHandle {
|
||||
let color_img = load_svg_bytes_with_size(svg, size, &usvg::Options::default()).unwrap();
|
||||
// Create image texture.
|
||||
let texture_handle = ui.ctx().load_texture(name,
|
||||
color_img.clone(),
|
||||
TextureOptions::default());
|
||||
let img_size = egui::emath::vec2(color_img.width() as f32,
|
||||
color_img.height() as f32);
|
||||
let texture_handle =
|
||||
ui.ctx()
|
||||
.load_texture(name, color_img.clone(), TextureOptions::default());
|
||||
let img_size = egui::emath::vec2(color_img.width() as f32, color_img.height() as f32);
|
||||
let sized_img = SizedTexture::new(texture_handle.id(), img_size);
|
||||
// Add image to content.
|
||||
ui.add(egui::Image::from_texture(sized_img)
|
||||
ui.add(
|
||||
egui::Image::from_texture(sized_img)
|
||||
.max_height(ui.available_width())
|
||||
.fit_to_original_size(1.0));
|
||||
.fit_to_original_size(1.0),
|
||||
);
|
||||
texture_handle
|
||||
}
|
||||
|
||||
@@ -648,32 +702,40 @@ impl View {
|
||||
// Show application logo and name.
|
||||
ui.scope(|ui| {
|
||||
ui.set_opacity(0.9);
|
||||
egui::Image::new(logo).fit_to_exact_size(egui::vec2(182.0, 182.0)).ui(ui);
|
||||
egui::Image::new(logo)
|
||||
.fit_to_exact_size(egui::vec2(182.0, 182.0))
|
||||
.ui(ui);
|
||||
});
|
||||
ui.add_space(-11.0);
|
||||
ui.label(RichText::new("GRIM")
|
||||
ui.label(
|
||||
RichText::new("GRIM")
|
||||
.size(24.0)
|
||||
.color(Colors::white_or_black(true))
|
||||
.color(Colors::white_or_black(true)),
|
||||
);
|
||||
ui.add_space(-2.0);
|
||||
ui.label(RichText::new(crate::VERSION)
|
||||
ui.label(
|
||||
RichText::new(crate::VERSION)
|
||||
.size(16.0)
|
||||
.color(Colors::title(false))
|
||||
.color(Colors::title(false)),
|
||||
);
|
||||
}
|
||||
|
||||
/// Draw semi-transparent cover at specified area.
|
||||
pub fn content_cover_ui(ui: &mut egui::Ui,
|
||||
pub fn content_cover_ui(
|
||||
ui: &mut egui::Ui,
|
||||
rect: Rect,
|
||||
id: impl std::hash::Hash,
|
||||
mut on_click: impl FnMut()) {
|
||||
mut on_click: impl FnMut(),
|
||||
) {
|
||||
let resp = ui.interact(rect, egui::Id::new(id), Sense::click_and_drag());
|
||||
if resp.clicked() || resp.dragged() {
|
||||
on_click();
|
||||
}
|
||||
let shape = RectShape::filled(resp.rect,
|
||||
let shape = RectShape::filled(
|
||||
resp.rect,
|
||||
CornerRadius::ZERO,
|
||||
Colors::semi_transparent().gamma_multiply(0.7));
|
||||
Colors::semi_transparent().gamma_multiply(0.7),
|
||||
);
|
||||
ui.painter().add(shape);
|
||||
}
|
||||
|
||||
@@ -696,7 +758,6 @@ impl View {
|
||||
pub fn get_left_inset() -> f32 {
|
||||
LEFT_DISPLAY_INSET.load(Ordering::Relaxed) as f32
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
@@ -714,7 +775,7 @@ lazy_static! {
|
||||
pub extern "C" fn Java_mw_gri_android_MainActivity_onDisplayInsets(
|
||||
_env: jni::JNIEnv,
|
||||
_class: jni::objects::JObject,
|
||||
cutouts: jni::sys::jarray
|
||||
cutouts: jni::sys::jarray,
|
||||
) {
|
||||
use jni::objects::{JObject, JPrimitiveArray};
|
||||
|
||||
|
||||
@@ -12,29 +12,39 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use eframe::epaint::RectShape;
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Align, CornerRadius, CursorIcon, Id, Layout, Margin, OpenUrl, RichText, ScrollArea, Sense, StrokeKind, UiBuilder};
|
||||
use egui::{
|
||||
Align, CornerRadius, CursorIcon, Id, Layout, Margin, OpenUrl, RichText, ScrollArea, Sense,
|
||||
StrokeKind, UiBuilder,
|
||||
};
|
||||
use egui_async::Bind;
|
||||
use std::time::Duration;
|
||||
use eframe::epaint::RectShape;
|
||||
|
||||
use crate::gui::icons::{ARROW_LEFT, BOOKMARKS, CALENDAR_CHECK, CLOUD_ARROW_DOWN, COMPUTER_TOWER, FOLDER_PLUS, GEAR, GEAR_FINE, GLOBE, GLOBE_SIMPLE, LOCK_KEY, NOTEPAD, PLUS, SIDEBAR_SIMPLE, SUITCASE};
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{
|
||||
ARROW_LEFT, BOOKMARKS, CALENDAR_CHECK, CLOUD_ARROW_DOWN, COMPUTER_TOWER, FOLDER_PLUS, GEAR,
|
||||
GEAR_FINE, GLOBE, GLOBE_SIMPLE, LOCK_KEY, NOTEPAD, PLUS, SIDEBAR_SIMPLE, SUITCASE,
|
||||
};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
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, ChangelogContent, OpenWalletModal, WalletListModal, WalletSettingsModal};
|
||||
use crate::gui::views::wallets::wallet::types::{wallet_status_text, WalletContentContainer};
|
||||
use crate::gui::views::wallets::wallet::RecoverySettings;
|
||||
use crate::gui::views::types::{
|
||||
ContentContainer, LinePosition, ModalPosition, TitleContentType, TitleType,
|
||||
};
|
||||
use crate::gui::views::wallets::WalletContent;
|
||||
use crate::gui::views::wallets::creation::WalletCreationContent;
|
||||
use crate::gui::views::wallets::modals::{
|
||||
AddWalletModal, ChangelogContent, OpenWalletModal, WalletListModal, WalletSettingsModal,
|
||||
};
|
||||
use crate::gui::views::wallets::wallet::RecoverySettings;
|
||||
use crate::gui::views::wallets::wallet::types::{WalletContentContainer, wallet_status_text};
|
||||
use crate::gui::views::{Content, Modal, TitlePanel, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::http::{retrieve_release, ReleaseInfo};
|
||||
use crate::http::{ReleaseInfo, retrieve_release};
|
||||
use crate::settings::AppUpdate;
|
||||
use crate::wallet::types::{ConnectionMethod, WalletTask};
|
||||
use crate::wallet::{Wallet, WalletList};
|
||||
use crate::AppConfig;
|
||||
|
||||
/// Wallets content.
|
||||
pub struct WalletsContent {
|
||||
@@ -63,7 +73,7 @@ pub struct WalletsContent {
|
||||
/// Application update information.
|
||||
update_info: (bool, Option<AppUpdate>),
|
||||
/// Update changelog [`Modal`] content.
|
||||
changelog_content: Option<ChangelogContent>
|
||||
changelog_content: Option<ChangelogContent>,
|
||||
}
|
||||
|
||||
/// Identifier for [`Modal`] to add the wallet.
|
||||
@@ -101,30 +111,30 @@ impl ContentContainer for WalletsContent {
|
||||
WALLET_SETTINGS_MODAL,
|
||||
SELECT_WALLET_MODAL,
|
||||
Self::DELETE_CONFIRMATION_MODAL,
|
||||
ChangelogContent::MODAL_ID
|
||||
ChangelogContent::MODAL_ID,
|
||||
]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
ADD_WALLET_MODAL => {
|
||||
self.add_wallet_modal_content.ui(ui, modal, cb, |name, pass| {
|
||||
self.creation_content = Some(
|
||||
WalletCreationContent::new(name.clone(), pass.clone())
|
||||
);
|
||||
self.add_wallet_modal_content
|
||||
.ui(ui, modal, cb, |name, pass| {
|
||||
self.creation_content =
|
||||
Some(WalletCreationContent::new(name.clone(), pass.clone()));
|
||||
});
|
||||
},
|
||||
}
|
||||
OPEN_WALLET_MODAL => {
|
||||
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
|
||||
Err(_) => false,
|
||||
};
|
||||
}
|
||||
true
|
||||
});
|
||||
},
|
||||
}
|
||||
WALLET_SETTINGS_MODAL => {
|
||||
self.wallet_settings_content.ui(ui, modal, cb, |conn| {
|
||||
if let Some(w) = self.wallets.selected().as_ref() {
|
||||
@@ -135,7 +145,8 @@ impl ContentContainer for WalletsContent {
|
||||
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| {
|
||||
self.wallet_selection_content
|
||||
.ui(ui, &mut self.wallets, |wallet, data| {
|
||||
w = Some(wallet);
|
||||
d = data;
|
||||
});
|
||||
@@ -165,7 +176,8 @@ impl ContentContainer for WalletsContent {
|
||||
// Small repaint delay is needed for Android back navigation and account list opening.
|
||||
let is_android = OperatingSystem::from_target_os() == OperatingSystem::Android;
|
||||
let account_list_showing = self.wallet_content.account_content.show_list;
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(if account_list_showing {
|
||||
ui.ctx()
|
||||
.request_repaint_after(Duration::from_millis(if account_list_showing {
|
||||
10
|
||||
} else if is_android {
|
||||
100
|
||||
@@ -184,7 +196,9 @@ impl ContentContainer for WalletsContent {
|
||||
let showing_wallet = self.showing_wallet() && !creating_wallet && !showing_settings;
|
||||
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()
|
||||
let list_hidden = showing_settings
|
||||
|| creating_wallet
|
||||
|| self.wallets.list().is_empty()
|
||||
|| (showing_wallet && (!dual_panel || !AppConfig::show_wallets_at_dual_panel()));
|
||||
|
||||
// Show title panel.
|
||||
@@ -209,11 +223,7 @@ impl ContentContainer for WalletsContent {
|
||||
});
|
||||
|
||||
// Show wallet list tabs.
|
||||
let side_padding = View::TAB_ITEMS_PADDING + if View::is_desktop() {
|
||||
0.0
|
||||
} else {
|
||||
4.0
|
||||
};
|
||||
let side_padding = View::TAB_ITEMS_PADDING + if View::is_desktop() { 0.0 } else { 4.0 };
|
||||
let tabs_margin = Margin {
|
||||
left: (View::far_left_inset_margin(ui) + side_padding) as i8,
|
||||
right: (View::far_right_inset_margin(ui) + side_padding) as i8,
|
||||
@@ -303,9 +313,7 @@ impl ContentContainer for WalletsContent {
|
||||
ui.add_space(1.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
// Show application settings content.
|
||||
View::max_width_ui(ui,
|
||||
Content::SIDE_PANEL_WIDTH * 1.3,
|
||||
|ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
c.ui(ui, cb);
|
||||
});
|
||||
});
|
||||
@@ -332,10 +340,7 @@ impl ContentContainer for WalletsContent {
|
||||
ui.add_space(4.0);
|
||||
|
||||
let text = t!("wallets.create_desc");
|
||||
ui.label(RichText::new(text)
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
);
|
||||
ui.label(RichText::new(text).size(16.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
// Show wallet creation button.
|
||||
let add_text = format!("{} {}", FOLDER_PLUS, t!("wallets.add"));
|
||||
@@ -382,8 +387,9 @@ impl WalletsContent {
|
||||
/// Check if opened wallet is showing.
|
||||
pub fn showing_wallet(&self) -> bool {
|
||||
if let Some(w) = self.wallets.selected().as_ref() {
|
||||
return w.is_open() && !w.is_deleted() &&
|
||||
w.get_config().chain_type == AppConfig::chain_type();
|
||||
return w.is_open()
|
||||
&& !w.is_deleted()
|
||||
&& w.get_config().chain_type == AppConfig::chain_type();
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -442,8 +448,11 @@ impl WalletsContent {
|
||||
let creating_wallet = self.creating_wallet();
|
||||
|
||||
// Setup title.
|
||||
let title_content = if show_wallet && (!dual_panel
|
||||
|| (dual_panel && !show_list)) && !creating_wallet && !showing_settings {
|
||||
let title_content = if show_wallet
|
||||
&& (!dual_panel || (dual_panel && !show_list))
|
||||
&& !creating_wallet
|
||||
&& !showing_settings
|
||||
{
|
||||
let title = self.wallet_content.title().into();
|
||||
let subtitle = self.wallets.selected().unwrap().get_config().name;
|
||||
let wallet_title_content = if self.wallet_content.settings_content.is_some() {
|
||||
@@ -459,9 +468,9 @@ impl WalletsContent {
|
||||
t!("wallets.add")
|
||||
} else {
|
||||
t!("wallets.title")
|
||||
}.into();
|
||||
let dual_title = !showing_settings && !creating_wallet &&
|
||||
show_wallet && dual_panel;
|
||||
}
|
||||
.into();
|
||||
let dual_title = !showing_settings && !creating_wallet && show_wallet && dual_panel;
|
||||
if dual_title {
|
||||
let title = self.wallet_content.title().into();
|
||||
let subtitle = self.wallets.selected().unwrap().get_config().name;
|
||||
@@ -479,7 +488,9 @@ impl WalletsContent {
|
||||
// Draw title panel.
|
||||
let mut show_settings = false;
|
||||
let showing_settings = self.showing_settings();
|
||||
TitlePanel::new(Id::new("wallets_title_panel")).ui(title_content, |ui| {
|
||||
TitlePanel::new(Id::new("wallets_title_panel")).ui(
|
||||
title_content,
|
||||
|ui| {
|
||||
if self.showing_settings() {
|
||||
View::title_button_big(ui, ARROW_LEFT, |_| {
|
||||
self.settings_content = None;
|
||||
@@ -505,11 +516,7 @@ impl WalletsContent {
|
||||
self.creation_content = None;
|
||||
}
|
||||
} else if show_wallet && dual_panel {
|
||||
let list_icon = if show_list {
|
||||
SIDEBAR_SIMPLE
|
||||
} else {
|
||||
SUITCASE
|
||||
};
|
||||
let list_icon = if show_list { SIDEBAR_SIMPLE } else { SUITCASE };
|
||||
View::title_button_big(ui, list_icon, |_| {
|
||||
AppConfig::toggle_show_wallets_at_dual_panel();
|
||||
});
|
||||
@@ -518,14 +525,17 @@ impl WalletsContent {
|
||||
Content::toggle_network_panel();
|
||||
});
|
||||
}
|
||||
}, |ui| {
|
||||
},
|
||||
|ui| {
|
||||
if !showing_settings {
|
||||
View::title_button_big(ui, GEAR, |_| {
|
||||
// Show application settings.
|
||||
show_settings = true;
|
||||
});
|
||||
}
|
||||
}, ui);
|
||||
},
|
||||
ui,
|
||||
);
|
||||
if show_settings {
|
||||
self.wallet_content.back(cb);
|
||||
self.settings_content = Some(SettingsContent::default());
|
||||
@@ -546,9 +556,10 @@ impl WalletsContent {
|
||||
|
||||
// Show result of update check.
|
||||
if AppConfig::check_updates() {
|
||||
if let Some(res) = self.check_update.read_or_request(|| async {
|
||||
retrieve_release().await
|
||||
}) {
|
||||
if let Some(res) = self
|
||||
.check_update
|
||||
.read_or_request(|| async { retrieve_release().await })
|
||||
{
|
||||
let checked = self.update_info.0;
|
||||
if !checked {
|
||||
self.update_info.0 = true;
|
||||
@@ -602,11 +613,13 @@ impl WalletsContent {
|
||||
}
|
||||
|
||||
/// Draw wallet list item.
|
||||
fn wallet_item_ui(&mut self,
|
||||
fn wallet_item_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
current: bool,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
let config = wallet.get_config();
|
||||
let can_open = !wallet.is_open() && !wallet.files_moving();
|
||||
|
||||
@@ -622,14 +635,21 @@ impl WalletsContent {
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
let res = ui.scope_builder(
|
||||
let res = ui
|
||||
.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
.max_rect(rect),
|
||||
|ui| {
|
||||
if can_open {
|
||||
if !wallet.is_repairing() {
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), GEAR_FINE, None, || {
|
||||
View::item_button(
|
||||
ui,
|
||||
View::item_rounding(0, 1, true),
|
||||
GEAR_FINE,
|
||||
None,
|
||||
|| {
|
||||
self.select_wallet(wallet, None, cb);
|
||||
let conn = wallet.get_current_connection();
|
||||
self.wallet_settings_content = WalletSettingsModal::new(conn);
|
||||
@@ -638,17 +658,27 @@ impl WalletsContent {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.settings"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
} else if !wallet.is_closing() {
|
||||
// Show button to close opened wallet.
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), LOCK_KEY, None, || {
|
||||
View::item_button(
|
||||
ui,
|
||||
View::item_rounding(0, 1, true),
|
||||
LOCK_KEY,
|
||||
None,
|
||||
|| {
|
||||
wallet.close();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
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);
|
||||
@@ -681,9 +711,11 @@ impl WalletsContent {
|
||||
View::ellipsize_text(ui, conn_text, 15.0, Colors::gray());
|
||||
ui.add_space(3.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() && (can_open || !current) {
|
||||
@@ -713,13 +745,25 @@ impl WalletsContent {
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(78.0);
|
||||
let r = View::item_rounding(0, 1, false);
|
||||
ui.painter().rect(rect, r, Colors::fill(), View::item_stroke(), StrokeKind::Outside);
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
r,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Show button to download the update.
|
||||
let mut link_clicked = false;
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), CLOUD_ARROW_DOWN, None, || {
|
||||
View::item_button(
|
||||
ui,
|
||||
View::item_rounding(0, 1, true),
|
||||
CLOUD_ARROW_DOWN,
|
||||
None,
|
||||
|| {
|
||||
link_clicked = true;
|
||||
});
|
||||
},
|
||||
);
|
||||
if link_clicked {
|
||||
ui.ctx().open_url(OpenUrl {
|
||||
url: update.url.clone(),
|
||||
|
||||
@@ -12,21 +12,21 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Id, Margin, RichText, ScrollArea};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Id, Margin, RichText, ScrollArea};
|
||||
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, CameraScanContent};
|
||||
use crate::gui::views::types::{LinePosition, ContentContainer, ModalPosition, QrScanResult};
|
||||
use crate::gui::views::types::{ContentContainer, LinePosition, ModalPosition, QrScanResult};
|
||||
use crate::gui::views::wallets::ConnectionSettings;
|
||||
use crate::gui::views::wallets::creation::MnemonicSetup;
|
||||
use crate::gui::views::wallets::creation::types::Step;
|
||||
use crate::gui::views::wallets::ConnectionSettings;
|
||||
use crate::gui::views::{CameraScanContent, Content, Modal, View};
|
||||
use crate::node::Node;
|
||||
use crate::wallet::{ExternalConnection, Wallet};
|
||||
use crate::wallet::types::PhraseMode;
|
||||
use crate::wallet::{ExternalConnection, Wallet};
|
||||
|
||||
/// Wallet creation content.
|
||||
pub struct WalletCreationContent {
|
||||
@@ -54,20 +54,14 @@ const QR_CODE_PHRASE_SCAN_MODAL: &'static str = "qr_code_rec_phrase_scan_modal";
|
||||
|
||||
impl ContentContainer for WalletCreationContent {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
QR_CODE_PHRASE_SCAN_MODAL
|
||||
]
|
||||
vec![QR_CODE_PHRASE_SCAN_MODAL]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
QR_CODE_PHRASE_SCAN_MODAL => {
|
||||
if let Some(content) = self.scan_modal_content.as_mut() {
|
||||
content.modal_ui(ui, cb, |result| {
|
||||
match result {
|
||||
content.modal_ui(ui, cb, |result| match result {
|
||||
QrScanResult::Text(text) => {
|
||||
self.mnemonic_setup.mnemonic.import(&text);
|
||||
Modal::close();
|
||||
@@ -77,17 +71,15 @@ impl ContentContainer for WalletCreationContent {
|
||||
Modal::close();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, _: &mut egui::Ui, _: &dyn PlatformCallbacks) {
|
||||
}
|
||||
fn container_ui(&mut self, _: &mut egui::Ui, _: &dyn PlatformCallbacks) {}
|
||||
}
|
||||
|
||||
impl WalletCreationContent {
|
||||
@@ -105,10 +97,12 @@ impl WalletCreationContent {
|
||||
}
|
||||
|
||||
/// Draw wallet creation content.
|
||||
pub fn content_ui(&mut self,
|
||||
pub fn content_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_create: impl FnMut(Wallet)) {
|
||||
on_create: impl FnMut(Wallet),
|
||||
) {
|
||||
self.ui(ui, cb);
|
||||
egui::TopBottomPanel::bottom("wallet_creation_step_panel")
|
||||
.frame(egui::Frame {
|
||||
@@ -151,7 +145,10 @@ impl WalletCreationContent {
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
ScrollArea::vertical()
|
||||
.id_salt(Id::from(format!("creation_step_scroll_{}", self.step.name())))
|
||||
.id_salt(Id::from(format!(
|
||||
"creation_step_scroll_{}",
|
||||
self.step.name()
|
||||
)))
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
@@ -168,10 +165,12 @@ impl WalletCreationContent {
|
||||
}
|
||||
|
||||
/// Draw [`Step`] description and confirmation control.
|
||||
fn step_control_ui(&mut self,
|
||||
fn step_control_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
on_create: impl FnOnce(Wallet),
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
let step = &self.step;
|
||||
// Setup description and next step availability.
|
||||
let (step_text, mut next) = match step {
|
||||
@@ -191,16 +190,14 @@ impl WalletCreationContent {
|
||||
let available = !self.mnemonic_setup.mnemonic.has_empty_or_invalid();
|
||||
(text, available)
|
||||
}
|
||||
Step::SetupConnection => {
|
||||
(t!("wallets.setup_conn_desc"), self.creation_error.is_none())
|
||||
}
|
||||
Step::SetupConnection => (t!("wallets.setup_conn_desc"), self.creation_error.is_none()),
|
||||
};
|
||||
|
||||
// Show step description or error.
|
||||
let generate_step = step == &Step::EnterMnemonic &&
|
||||
self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate;
|
||||
if (self.mnemonic_setup.mnemonic.valid() && self.creation_error.is_none()) ||
|
||||
generate_step {
|
||||
let generate_step = step == &Step::EnterMnemonic
|
||||
&& self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate;
|
||||
if (self.mnemonic_setup.mnemonic.valid() && self.creation_error.is_none()) || generate_step
|
||||
{
|
||||
ui.label(RichText::new(step_text).size(16.0).color(Colors::gray()));
|
||||
ui.add_space(6.0);
|
||||
} else {
|
||||
@@ -208,14 +205,14 @@ impl WalletCreationContent {
|
||||
// Show error text.
|
||||
if let Some(err) = &self.creation_error {
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(err)
|
||||
.size(16.0)
|
||||
.color(Colors::red()));
|
||||
ui.label(RichText::new(err).size(16.0).color(Colors::red()));
|
||||
ui.add_space(10.0);
|
||||
} else {
|
||||
ui.label(RichText::new(t!("wallets.not_valid_phrase"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.not_valid_phrase"))
|
||||
.size(16.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
};
|
||||
}
|
||||
@@ -234,9 +231,9 @@ impl WalletCreationContent {
|
||||
PhraseMode::Generate => {
|
||||
let c_t = format!("{} {}", COPY, t!("copy"));
|
||||
View::button(ui, c_t, Colors::white_or_black(false), || {
|
||||
cb.copy_string_to_buffer(self.mnemonic_setup
|
||||
.mnemonic
|
||||
.get_phrase());
|
||||
cb.copy_string_to_buffer(
|
||||
self.mnemonic_setup.mnemonic.get_phrase(),
|
||||
);
|
||||
});
|
||||
}
|
||||
PhraseMode::Import => {
|
||||
@@ -290,14 +287,20 @@ impl WalletCreationContent {
|
||||
}
|
||||
|
||||
/// Draw button to go to next [`Step`].
|
||||
fn next_step_button_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
on_create: impl FnOnce(Wallet)) {
|
||||
fn next_step_button_ui(&mut self, ui: &mut egui::Ui, on_create: impl FnOnce(Wallet)) {
|
||||
// Setup button text.
|
||||
let (next_text, text_color, bg_color) = if self.step == Step::SetupConnection {
|
||||
(format!("{} {}", CHECK, t!("complete")), Colors::title(true), Colors::gold())
|
||||
(
|
||||
format!("{} {}", CHECK, t!("complete")),
|
||||
Colors::title(true),
|
||||
Colors::gold(),
|
||||
)
|
||||
} else {
|
||||
(t!("continue").into(), Colors::green(), Colors::white_or_black(false))
|
||||
(
|
||||
t!("continue").into(),
|
||||
Colors::green(),
|
||||
Colors::white_or_black(false),
|
||||
)
|
||||
};
|
||||
|
||||
// Show next step button.
|
||||
@@ -310,15 +313,15 @@ impl WalletCreationContent {
|
||||
Step::SetupConnection
|
||||
}
|
||||
}
|
||||
Step::ConfirmMnemonic => {
|
||||
Step::SetupConnection
|
||||
},
|
||||
Step::ConfirmMnemonic => Step::SetupConnection,
|
||||
Step::SetupConnection => {
|
||||
// Create wallet at last step.
|
||||
match Wallet::create(&self.name,
|
||||
match Wallet::create(
|
||||
&self.name,
|
||||
&self.pass,
|
||||
&self.mnemonic_setup.mnemonic,
|
||||
&self.network_setup.method) {
|
||||
&self.network_setup.method,
|
||||
) {
|
||||
Ok(w) => {
|
||||
self.mnemonic_setup.reset();
|
||||
// Pass created wallet to callback.
|
||||
@@ -361,13 +364,13 @@ impl WalletCreationContent {
|
||||
Step::ConfirmMnemonic => {
|
||||
self.step = Step::EnterMnemonic;
|
||||
false
|
||||
},
|
||||
}
|
||||
Step::SetupConnection => {
|
||||
self.creation_error = None;
|
||||
self.step = Step::EnterMnemonic;
|
||||
false
|
||||
}
|
||||
_ => true
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ use egui::{Id, RichText};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::PENCIL;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Content, View, TextEdit};
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Content, Modal, TextEdit, View};
|
||||
use crate::wallet::Mnemonic;
|
||||
use crate::wallet::types::{PhraseMode, PhraseSize, PhraseWord};
|
||||
|
||||
@@ -51,23 +51,17 @@ impl Default for MnemonicSetup {
|
||||
|
||||
impl ContentContainer for MnemonicSetup {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
WORD_INPUT_MODAL
|
||||
]
|
||||
vec![WORD_INPUT_MODAL]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
WORD_INPUT_MODAL => self.word_modal_ui(ui, modal, cb),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn container_ui(&mut self, _: &mut egui::Ui, _: &dyn PlatformCallbacks) {
|
||||
}
|
||||
fn container_ui(&mut self, _: &mut egui::Ui, _: &dyn PlatformCallbacks) {}
|
||||
}
|
||||
|
||||
impl MnemonicSetup {
|
||||
@@ -96,9 +90,10 @@ impl MnemonicSetup {
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.words_count"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.words_count"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray())
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
@@ -149,7 +144,9 @@ impl MnemonicSetup {
|
||||
|
||||
let mut word_number = 0;
|
||||
let cols = list_columns_count(ui);
|
||||
let _ = words.chunks(cols).map(|chunk| {
|
||||
let _ = words
|
||||
.chunks(cols)
|
||||
.map(|chunk| {
|
||||
let size = chunk.len();
|
||||
word_number += 1;
|
||||
if size > 1 {
|
||||
@@ -186,17 +183,14 @@ impl MnemonicSetup {
|
||||
});
|
||||
});
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
/// Draw word grid item.
|
||||
fn word_item_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
num: usize,
|
||||
word: &PhraseWord,
|
||||
edit: bool) {
|
||||
fn word_item_ui(&mut self, ui: &mut egui::Ui, num: usize, word: &PhraseWord, edit: bool) {
|
||||
let color = if !word.valid || (word.text.is_empty() && !self.mnemonic.valid()) {
|
||||
Colors::red()
|
||||
} else {
|
||||
@@ -204,7 +198,11 @@ impl MnemonicSetup {
|
||||
};
|
||||
if edit {
|
||||
ui.add_space(6.0);
|
||||
View::button(ui, PENCIL.to_string(), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
PENCIL.to_string(),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
self.word_index_edit = num - 1;
|
||||
self.word_edit = word.text.clone();
|
||||
self.valid_word_edit = word.valid;
|
||||
@@ -213,10 +211,13 @@ impl MnemonicSetup {
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.recovery_phrase"))
|
||||
.show();
|
||||
});
|
||||
ui.label(RichText::new(format!("#{} {}", num, word.text))
|
||||
},
|
||||
);
|
||||
ui.label(
|
||||
RichText::new(format!("#{} {}", num, word.text))
|
||||
.size(17.0)
|
||||
.color(color));
|
||||
.color(color),
|
||||
);
|
||||
} else {
|
||||
ui.add_space(12.0);
|
||||
let text = format!("#{} {}", num, word.text);
|
||||
@@ -240,9 +241,8 @@ impl MnemonicSetup {
|
||||
}
|
||||
// Close modal or go to next word to edit.
|
||||
let next_word = c.mnemonic.get(c.word_index_edit + 1);
|
||||
let close_modal = next_word.is_none() ||
|
||||
(!next_word.as_ref().unwrap().text.is_empty() &&
|
||||
next_word.unwrap().valid);
|
||||
let close_modal = next_word.is_none()
|
||||
|| (!next_word.as_ref().unwrap().text.is_empty() && next_word.unwrap().valid);
|
||||
if close_modal {
|
||||
Modal::close();
|
||||
} else {
|
||||
@@ -253,9 +253,11 @@ impl MnemonicSetup {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.enter_word", "number" => self.word_index_edit + 1))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.enter_word", "number" => self.word_index_edit + 1))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw word value text edit.
|
||||
@@ -268,9 +270,11 @@ impl MnemonicSetup {
|
||||
// Show error when specified word is not valid.
|
||||
if !self.valid_word_edit {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("wallets.not_valid_word"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.not_valid_word"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
@@ -282,10 +286,15 @@ impl MnemonicSetup {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
// Show save button.
|
||||
|
||||
@@ -20,7 +20,7 @@ pub enum Step {
|
||||
/// Mnemonic phrase confirmation.
|
||||
ConfirmMnemonic,
|
||||
/// Wallet connection setup.
|
||||
SetupConnection
|
||||
SetupConnection,
|
||||
}
|
||||
|
||||
impl Step {
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod modals;
|
||||
mod creation;
|
||||
pub mod modals;
|
||||
|
||||
mod content;
|
||||
pub use content::*;
|
||||
|
||||
@@ -38,11 +38,13 @@ impl Default for AddWalletModal {
|
||||
|
||||
impl AddWalletModal {
|
||||
/// Draw creating wallet name/password input [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
mut on_input: impl FnMut(String, ZeroingString)) {
|
||||
mut on_input: impl FnMut(String, ZeroingString),
|
||||
) {
|
||||
let mut on_next = |m: &mut AddWalletModal| {
|
||||
let name = m.name_edit.clone();
|
||||
let pass = m.pass_edit.clone();
|
||||
@@ -54,20 +56,23 @@ impl AddWalletModal {
|
||||
};
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.name"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.name"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show wallet name text edit.
|
||||
let mut name_input = TextEdit::new(Id::from(modal.id).with("name"))
|
||||
.focus(false);
|
||||
let mut name_input = TextEdit::new(Id::from(modal.id).with("name")).focus(false);
|
||||
name_input.ui(ui, &mut self.name_edit, cb);
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new(t!("wallets.pass"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show wallet password text edit.
|
||||
@@ -91,10 +96,15 @@ impl AddWalletModal {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Id, OpenUrl, RichText, ScrollArea};
|
||||
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{BRACKETS_CURLY, GITHUB_LOGO, TELEGRAM_LOGO};
|
||||
use crate::gui::views::{Modal, View};
|
||||
|
||||
/// Application release changelog content.
|
||||
pub struct ChangelogContent {
|
||||
@@ -45,7 +45,11 @@ impl ChangelogContent {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("changelog")).size(16.0).color(Colors::gray()));
|
||||
ui.label(
|
||||
RichText::new(t!("changelog"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Align, Layout, RichText, ScrollArea, StrokeKind};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK, COMPUTER_TOWER, FOLDER_OPEN, GLOBE_SIMPLE, PLUGS_CONNECTED};
|
||||
use crate::gui::views::wallets::wallet::types::wallet_status_text;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::ConnectionMethod;
|
||||
use crate::wallet::{Wallet, WalletList};
|
||||
|
||||
@@ -37,14 +37,20 @@ pub struct WalletListModal {
|
||||
impl WalletListModal {
|
||||
/// Create new content instance.
|
||||
pub fn new(selected_id: Option<i64>, data: Option<String>, can_open: bool) -> Self {
|
||||
Self { selected_id, data, can_open }
|
||||
Self {
|
||||
selected_id,
|
||||
data,
|
||||
can_open,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw content.
|
||||
pub fn ui(&mut self,
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallets: &WalletList,
|
||||
mut on_select: impl FnMut(Wallet, Option<String>)) {
|
||||
mut on_select: impl FnMut(Wallet, Option<String>),
|
||||
) {
|
||||
ui.add_space(4.0);
|
||||
ScrollArea::vertical()
|
||||
.max_height(373.0)
|
||||
@@ -72,19 +78,21 @@ impl WalletListModal {
|
||||
|
||||
// Show button to close modal.
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
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 with provided callback on select.
|
||||
fn wallet_item_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
on_select: impl FnOnce()) {
|
||||
fn wallet_item_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, on_select: impl FnOnce()) {
|
||||
let config = wallet.get_config();
|
||||
let id = config.id;
|
||||
|
||||
@@ -92,20 +100,18 @@ impl WalletListModal {
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(78.0);
|
||||
let rounding = View::item_rounding(0, 1, false);
|
||||
ui.painter().rect(rect,
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
rounding,
|
||||
Colors::fill(),
|
||||
View::hover_stroke(),
|
||||
StrokeKind::Outside);
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
if self.can_open {
|
||||
// Show button to select or open closed wallet.
|
||||
let icon = if wallet.is_open() {
|
||||
CHECK
|
||||
} else {
|
||||
FOLDER_OPEN
|
||||
};
|
||||
let icon = if wallet.is_open() { CHECK } else { FOLDER_OPEN };
|
||||
View::item_button(ui, View::item_rounding(0, 1, true), icon, None, || {
|
||||
on_select();
|
||||
});
|
||||
@@ -137,16 +143,22 @@ impl WalletListModal {
|
||||
ConnectionMethod::Integrated => {
|
||||
format!("{} {}", COMPUTER_TOWER, t!("network.node"))
|
||||
}
|
||||
ConnectionMethod::External(_, url) => format!("{} {}", GLOBE_SIMPLE, url)
|
||||
ConnectionMethod::External(_, url) => format!("{} {}", GLOBE_SIMPLE, url),
|
||||
};
|
||||
ui.label(RichText::new(conn_text).size(15.0).color(Colors::text(false)));
|
||||
ui.label(
|
||||
RichText::new(conn_text)
|
||||
.size(15.0)
|
||||
.color(Colors::text(false)),
|
||||
);
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Show wallet API text or open status.
|
||||
if self.can_open {
|
||||
ui.label(RichText::new(wallet_status_text(wallet))
|
||||
ui.label(
|
||||
RichText::new(wallet_status_text(wallet))
|
||||
.size(15.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
} else {
|
||||
let address = if let Some(port) = config.api_port {
|
||||
format!("127.0.0.1:{}", port)
|
||||
|
||||
@@ -36,11 +36,13 @@ impl OpenWalletModal {
|
||||
}
|
||||
}
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
mut on_continue: impl FnMut(ZeroingString) -> 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();
|
||||
@@ -56,9 +58,11 @@ impl OpenWalletModal {
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.pass"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show password input.
|
||||
@@ -72,14 +76,18 @@ impl OpenWalletModal {
|
||||
if self.pass_edit.is_empty() {
|
||||
self.wrong_pass = false;
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(t!("wallets.pass_empty"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.pass_empty"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
} else if self.wrong_pass {
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(t!("wallets.wrong_pass"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.wrong_pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
@@ -91,10 +99,15 @@ impl OpenWalletModal {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{RichText, ScrollArea};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{PLUS_CIRCLE, TRASH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::modals::ExternalConnectionModal;
|
||||
use crate::gui::views::network::ConnectionsContent;
|
||||
use crate::gui::views::network::modals::ExternalConnectionModal;
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::WalletsContent;
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::ConnectionMethod;
|
||||
use crate::wallet::{ConnectionsConfig, ExternalConnection};
|
||||
|
||||
@@ -32,7 +32,7 @@ pub struct WalletSettingsModal {
|
||||
pub conn: ConnectionMethod,
|
||||
|
||||
/// External connection creation content.
|
||||
new_ext_conn_content: Option<ExternalConnectionModal>
|
||||
new_ext_conn_content: Option<ExternalConnectionModal>,
|
||||
}
|
||||
|
||||
impl WalletSettingsModal {
|
||||
@@ -45,11 +45,13 @@ impl WalletSettingsModal {
|
||||
}
|
||||
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_select: impl Fn(ConnectionMethod)) {
|
||||
on_select: impl Fn(ConnectionMethod),
|
||||
) {
|
||||
// Draw external connection creation content.
|
||||
if let Some(ext_content) = self.new_ext_conn_content.as_mut() {
|
||||
ext_content.ui(ui, cb, modal, |conn| {
|
||||
@@ -85,21 +87,28 @@ impl WalletSettingsModal {
|
||||
} else {
|
||||
Colors::fill_lite()
|
||||
};
|
||||
ConnectionsContent::integrated_node_item_ui(ui, bg, (!cur_integrated, || {
|
||||
ConnectionsContent::integrated_node_item_ui(
|
||||
ui,
|
||||
bg,
|
||||
(!cur_integrated, || {
|
||||
on_select(ConnectionMethod::Integrated);
|
||||
Modal::close();
|
||||
}), |ui| {
|
||||
}),
|
||||
|ui| {
|
||||
if cur_integrated {
|
||||
View::selected_item_check(ui);
|
||||
}
|
||||
cur_integrated
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.ext_conn"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.ext_conn"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
// Show button to add new external node connection.
|
||||
let add_node_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add_node"));
|
||||
@@ -117,23 +126,29 @@ impl WalletSettingsModal {
|
||||
let len = ext_conn_list.len();
|
||||
let is_current = match self.conn {
|
||||
ConnectionMethod::External(id, _) => id == c.id,
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
let bg = if is_current {
|
||||
Colors::fill()
|
||||
} else {
|
||||
Colors::fill_lite()
|
||||
};
|
||||
ConnectionsContent::ext_conn_item_ui(ui, bg, c, i, len, (!is_current, || {
|
||||
on_select(
|
||||
ConnectionMethod::External(c.id, c.url.clone())
|
||||
);
|
||||
ConnectionsContent::ext_conn_item_ui(
|
||||
ui,
|
||||
bg,
|
||||
c,
|
||||
i,
|
||||
len,
|
||||
(!is_current, || {
|
||||
on_select(ConnectionMethod::External(c.id, c.url.clone()));
|
||||
Modal::close();
|
||||
}), |ui| {
|
||||
}),
|
||||
|ui| {
|
||||
if is_current {
|
||||
View::selected_item_check(ui);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
ui.add_space(4.0);
|
||||
@@ -144,15 +159,18 @@ impl WalletSettingsModal {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
// Draw button to delete the wallet.
|
||||
View::colored_text_button(ui,
|
||||
View::colored_text_button(
|
||||
ui,
|
||||
format!("{} {}", TRASH, t!("wallets.delete")),
|
||||
Colors::red(),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::new(WalletsContent::DELETE_CONFIRMATION_MODAL)
|
||||
.position(ModalPosition::Center)
|
||||
.title(t!("confirmation"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
@@ -160,9 +178,14 @@ impl WalletSettingsModal {
|
||||
|
||||
// Show button to close modal.
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
|
||||
@@ -12,20 +12,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Align, Layout, RichText, ScrollArea, StrokeKind};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Align, Layout, RichText, ScrollArea, StrokeKind};
|
||||
use grin_core::core::amount_to_hr_string;
|
||||
|
||||
use crate::gui::icons::{CHECK, FOLDER_USER, PACKAGE, PATH, SCAN, SPINNER, USERS_THREE, USER_PLUS};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK, FOLDER_USER, PACKAGE, PATH, SCAN, SPINNER, USER_PLUS, USERS_THREE};
|
||||
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::types::{WalletContentContainer, GRIN};
|
||||
use crate::gui::views::{CameraContent, CameraScanContent, Content, Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::views::wallets::wallet::request::SendRequestContent;
|
||||
use crate::wallet::{Wallet, WalletConfig};
|
||||
use crate::gui::views::wallets::wallet::types::{GRIN, WalletContentContainer};
|
||||
use crate::gui::views::{CameraContent, CameraScanContent, Content, Modal, View};
|
||||
use crate::wallet::types::{WalletAccount, WalletTask};
|
||||
use crate::wallet::{Wallet, WalletConfig};
|
||||
|
||||
/// Wallet account panel content.
|
||||
pub struct WalletAccountContent {
|
||||
@@ -49,17 +49,16 @@ const SEND_MODAL_ID: &'static str = "account_send_request_modal";
|
||||
|
||||
impl WalletContentContainer for WalletAccountContent {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
CREATE_MODAL_ID,
|
||||
SEND_MODAL_ID
|
||||
]
|
||||
vec![CREATE_MODAL_ID, SEND_MODAL_ID]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
fn modal_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
match modal.id {
|
||||
CREATE_MODAL_ID => self.create_account_content.ui(ui, wallet, modal, cb),
|
||||
SEND_MODAL_ID => {
|
||||
@@ -132,10 +131,7 @@ impl WalletAccountContent {
|
||||
}
|
||||
|
||||
/// Draw wallet account content.
|
||||
fn account_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn account_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
// Check wallet data.
|
||||
if wallet.get_data().is_none() {
|
||||
return;
|
||||
@@ -148,11 +144,13 @@ impl WalletAccountContent {
|
||||
|
||||
// Draw round background.
|
||||
let rounding = View::item_rounding(0, 2, false);
|
||||
ui.painter().rect(rect,
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to show QR code scanner.
|
||||
@@ -191,9 +189,11 @@ impl WalletAccountContent {
|
||||
let amount_text = format!("{} {}", amount, GRIN);
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
ui.label(RichText::new(amount_text)
|
||||
ui.label(
|
||||
RichText::new(amount_text)
|
||||
.size(18.0)
|
||||
.color(Colors::white_or_black(true)));
|
||||
.color(Colors::white_or_black(true)),
|
||||
);
|
||||
});
|
||||
ui.add_space(-2.0);
|
||||
|
||||
@@ -223,16 +223,20 @@ impl WalletAccountContent {
|
||||
if rep_progress == 0 {
|
||||
format!("{} {}", SPINNER, t!("wallets.wallet_checking"))
|
||||
} else {
|
||||
format!("{} {}: {}%",
|
||||
format!(
|
||||
"{} {}: {}%",
|
||||
SPINNER,
|
||||
t!("wallets.wallet_checking"),
|
||||
rep_progress)
|
||||
rep_progress
|
||||
)
|
||||
}
|
||||
} else {
|
||||
format!("{} {}: {}%",
|
||||
format!(
|
||||
"{} {}: {}%",
|
||||
SPINNER,
|
||||
t!("wallets.wallet_loading"),
|
||||
info_progress)
|
||||
info_progress
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -276,9 +280,14 @@ impl WalletAccountContent {
|
||||
// Show modal buttons.
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
self.show_list = false;
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.add"), Colors::white_or_black(false), || {
|
||||
@@ -332,12 +341,18 @@ impl WalletAccountContent {
|
||||
});
|
||||
}
|
||||
} else if let Some(res) = &self.qr_scan_result.clone() {
|
||||
CameraScanContent::result_ui(ui, res, cb, || {
|
||||
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);
|
||||
});
|
||||
@@ -345,12 +360,14 @@ impl WalletAccountContent {
|
||||
}
|
||||
|
||||
/// Draw account item.
|
||||
fn account_item_ui(ui: &mut egui::Ui,
|
||||
fn account_item_ui(
|
||||
ui: &mut egui::Ui,
|
||||
acc: &WalletAccount,
|
||||
current: bool,
|
||||
index: usize,
|
||||
size: usize,
|
||||
mut on_select: impl FnMut()) {
|
||||
mut on_select: impl FnMut(),
|
||||
) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(ACCOUNT_ITEM_HEIGHT);
|
||||
@@ -358,11 +375,13 @@ fn account_item_ui(ui: &mut egui::Ui,
|
||||
// Draw round background.
|
||||
let bg_rect = rect.clone();
|
||||
let item_rounding = View::item_rounding(index, size, false);
|
||||
ui.painter().rect(bg_rect,
|
||||
ui.painter().rect(
|
||||
bg_rect,
|
||||
item_rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
@@ -386,9 +405,11 @@ fn account_item_ui(ui: &mut egui::Ui,
|
||||
let amount_text = format!("{} {}", amount, GRIN);
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
ui.label(RichText::new(amount_text)
|
||||
ui.label(
|
||||
RichText::new(amount_text)
|
||||
.size(18.0)
|
||||
.color(Colors::white_or_black(true)));
|
||||
.color(Colors::white_or_black(true)),
|
||||
);
|
||||
});
|
||||
ui.add_space(-2.0);
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
use egui::{Id, RichText};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Account creation [`Modal`] content.
|
||||
@@ -38,11 +38,13 @@ impl Default for CreateAccountContent {
|
||||
|
||||
impl CreateAccountContent {
|
||||
/// Draw account creation [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
let on_create = |m: &mut CreateAccountContent| {
|
||||
if m.account_label_edit.is_empty() {
|
||||
return;
|
||||
@@ -52,16 +54,18 @@ impl CreateAccountContent {
|
||||
Ok(_) => {
|
||||
let _ = wallet.set_active_account(label);
|
||||
Modal::close();
|
||||
},
|
||||
Err(_) => m.account_creation_error = true
|
||||
}
|
||||
Err(_) => m.account_creation_error = true,
|
||||
};
|
||||
};
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.new_account_desc"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.new_account_desc"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw account name edit.
|
||||
@@ -74,9 +78,7 @@ impl CreateAccountContent {
|
||||
// Show error occurred during account creation.
|
||||
if self.account_creation_error {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("error"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
ui.label(RichText::new(t!("error")).size(17.0).color(Colors::red()));
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
@@ -87,10 +89,15 @@ impl CreateAccountContent {
|
||||
// Show modal buttons.
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("create"), Colors::white_or_black(false), || {
|
||||
|
||||
@@ -16,22 +16,24 @@ use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Id, Margin, RichText, ScrollArea};
|
||||
use grin_chain::SyncStatus;
|
||||
|
||||
use crate::gui::icons::{ARROWS_CLOCKWISE, FILE_ARROW_DOWN, FILE_ARROW_UP, FILE_TEXT, GEAR_FINE, POWER, STACK};
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{
|
||||
ARROWS_CLOCKWISE, FILE_ARROW_DOWN, FILE_ARROW_UP, FILE_TEXT, GEAR_FINE, POWER, STACK,
|
||||
};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::{LinePosition, ModalPosition};
|
||||
use crate::gui::views::wallets::wallet::account::WalletAccountContent;
|
||||
use crate::gui::views::wallets::wallet::message::MessageInputContent;
|
||||
use crate::gui::views::wallets::wallet::proof::PaymentProofContent;
|
||||
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::{WalletSettingsContent, WalletTransactionsContent};
|
||||
use crate::gui::views::{Content, Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::node::Node;
|
||||
use crate::wallet::types::{ConnectionMethod, WalletTask};
|
||||
use crate::wallet::{ExternalConnection, Wallet};
|
||||
use crate::AppConfig;
|
||||
use crate::gui::views::wallets::wallet::proof::PaymentProofContent;
|
||||
|
||||
/// Wallet content.
|
||||
pub struct WalletContent {
|
||||
@@ -51,7 +53,7 @@ pub struct WalletContent {
|
||||
/// Send request creation [`Modal`] content.
|
||||
send_content: Option<SendRequestContent>,
|
||||
/// Slatepack message input [`Modal`] content.
|
||||
message_content: Option<MessageInputContent>
|
||||
message_content: Option<MessageInputContent>,
|
||||
}
|
||||
|
||||
/// Identifier for invoice creation [`Modal`].
|
||||
@@ -64,7 +66,7 @@ impl WalletContentContainer for WalletContent {
|
||||
vec![
|
||||
INVOICE_MODAL_ID,
|
||||
SEND_MODAL_ID,
|
||||
MessageInputContent::MODAL_ID
|
||||
MessageInputContent::MODAL_ID,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -106,19 +108,14 @@ impl WalletContentContainer for WalletContent {
|
||||
|
||||
// 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.settings_content.is_none() && !block_nav
|
||||
&& !wallet.sync_error() && data.is_some();
|
||||
if wallet.get_current_connection() == ConnectionMethod::Integrated &&
|
||||
!Node::is_running() {
|
||||
let mut show_account =
|
||||
self.settings_content.is_none() && !block_nav && !wallet.sync_error() && data.is_some();
|
||||
if wallet.get_current_connection() == ConnectionMethod::Integrated && !Node::is_running() {
|
||||
show_account = false;
|
||||
}
|
||||
|
||||
// Show wallet tabs.
|
||||
let side_padding = View::TAB_ITEMS_PADDING + if View::is_desktop() {
|
||||
0.0
|
||||
} else {
|
||||
4.0
|
||||
};
|
||||
let side_padding = View::TAB_ITEMS_PADDING + if View::is_desktop() { 0.0 } else { 4.0 };
|
||||
let tabs_margin = Margin {
|
||||
left: (View::far_left_inset_margin(ui) + side_padding) as i8,
|
||||
right: (View::get_right_inset() + side_padding) as i8,
|
||||
@@ -165,8 +162,8 @@ impl WalletContentContainer for WalletContent {
|
||||
}
|
||||
|
||||
// Flag to check if account panel is opened.
|
||||
let top_panel_expanded = self.account_content.can_back() ||
|
||||
self.transport_content.can_back();
|
||||
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 {
|
||||
@@ -262,8 +259,7 @@ impl WalletContentContainer for WalletContent {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
let show_settings = self.settings_content.is_some();
|
||||
let show_txs = self.txs_content.is_some() && !top_panel_expanded;
|
||||
let show_sync = (!show_settings || block_nav) &&
|
||||
sync_ui(ui, &wallet);
|
||||
let show_sync = (!show_settings || block_nav) && sync_ui(ui, &wallet);
|
||||
if !show_sync {
|
||||
if show_settings {
|
||||
ui.add_space(3.0);
|
||||
@@ -273,17 +269,11 @@ impl WalletContentContainer for WalletContent {
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
.show(ui, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.settings_content
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.ui(ui, &wallet, cb);
|
||||
self.settings_content.as_mut().unwrap().ui(ui, &wallet, cb);
|
||||
});
|
||||
});
|
||||
} else if show_txs {
|
||||
self.txs_content
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.ui(ui, &wallet, cb);
|
||||
self.txs_content.as_mut().unwrap().ui(ui, &wallet, cb);
|
||||
}
|
||||
|
||||
// Handle wallet task result.
|
||||
@@ -365,8 +355,8 @@ impl WalletContent {
|
||||
// Block navigation if wallet is repairing and integrated node is not launching
|
||||
// and if wallet is closing or syncing after opening when there is no data to show.
|
||||
(wallet.is_repairing() && (integrated_node_ready || !integrated_node) && !sync_error)
|
||||
|| wallet.is_closing() || (sync_after_opening &&
|
||||
(!integrated_node || integrated_node_ready))
|
||||
|| wallet.is_closing()
|
||||
|| (sync_after_opening && (!integrated_node || integrated_node_ready))
|
||||
}
|
||||
|
||||
/// Draw tab buttons at the bottom of the screen.
|
||||
@@ -493,11 +483,7 @@ impl WalletContent {
|
||||
if m.max_calculating {
|
||||
let data = wallet.get_data().unwrap();
|
||||
let a = data.info.amount_currently_spendable;
|
||||
let max = if f > a {
|
||||
0
|
||||
} else {
|
||||
a - f
|
||||
};
|
||||
let max = if f > a { 0 } else { a - f };
|
||||
m.on_max_amount_calculated(max, f);
|
||||
} else {
|
||||
m.on_fee_calculated(f);
|
||||
@@ -513,8 +499,9 @@ impl WalletContent {
|
||||
if let Some(res) = res {
|
||||
// Update message content on validation error
|
||||
// or when tx not belongs to current wallet.
|
||||
if res.is_err() || (!res.as_ref().unwrap().1 &&
|
||||
!res.as_ref().unwrap().2) {
|
||||
if res.is_err()
|
||||
|| (!res.as_ref().unwrap().1 && !res.as_ref().unwrap().2)
|
||||
{
|
||||
if let Some(m) = self.message_content.as_mut() {
|
||||
if let Ok(p) = serde_json::to_string_pretty(&proof) {
|
||||
let mut c = PaymentProofContent::new(Some(p));
|
||||
@@ -562,13 +549,18 @@ fn sync_ui(ui: &mut egui::Ui, wallet: &Wallet) -> bool {
|
||||
View::center_content(ui, 108.0, |ui| {
|
||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
let text = t!("wallets.enable_node", "settings" => GEAR_FINE);
|
||||
ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text()));
|
||||
ui.label(
|
||||
RichText::new(text)
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
// Show button to enable integrated node at non-dual root panel mode
|
||||
// or when network connections are not showing and node is not stopping
|
||||
let dual_panel = Content::is_dual_panel_mode(ui.ctx());
|
||||
if (!dual_panel || AppConfig::show_connections_network_panel())
|
||||
&& !Node::is_stopping() {
|
||||
&& !Node::is_stopping()
|
||||
{
|
||||
let enable_text = format!("{} {}", POWER, t!("network.enable_node"));
|
||||
View::action_button(ui, enable_text, || {
|
||||
Node::start();
|
||||
@@ -576,9 +568,8 @@ fn sync_ui(ui: &mut egui::Ui, wallet: &Wallet) -> bool {
|
||||
}
|
||||
});
|
||||
});
|
||||
return true
|
||||
} else if wallet.sync_error()
|
||||
&& Node::get_sync_status() == Some(SyncStatus::NoSync) {
|
||||
return true;
|
||||
} else if wallet.sync_error() && Node::get_sync_status() == Some(SyncStatus::NoSync) {
|
||||
sync_error_ui(ui, wallet);
|
||||
return true;
|
||||
} else if wallet.get_data().is_none() {
|
||||
@@ -599,7 +590,11 @@ fn sync_ui(ui: &mut egui::Ui, wallet: &Wallet) -> bool {
|
||||
fn sync_error_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
View::center_content(ui, 108.0, |ui| {
|
||||
let text = t!("wallets.wallet_loading_err", "settings" => GEAR_FINE);
|
||||
ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text()));
|
||||
ui.label(
|
||||
RichText::new(text)
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
let retry_text = format!("{} {}", ARROWS_CLOCKWISE, t!("retry"));
|
||||
View::action_button(ui, retry_text, || {
|
||||
@@ -643,7 +638,11 @@ fn sync_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
t!("wallets.tx_loading").into()
|
||||
}
|
||||
};
|
||||
ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text()));
|
||||
ui.label(
|
||||
RichText::new(text)
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -15,13 +15,13 @@
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Id, RichText, ScrollArea};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, SCAN, SEAL_CHECK};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, FilePickContent, FilePickContentType, Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::views::wallets::wallet::proof::PaymentProofContent;
|
||||
use crate::wallet::types::WalletTask;
|
||||
use crate::gui::views::{CameraContent, FilePickContent, FilePickContentType, Modal, View};
|
||||
use crate::wallet::Wallet;
|
||||
use crate::wallet::types::WalletTask;
|
||||
|
||||
pub struct MessageInputContent {
|
||||
/// Slatepack input text.
|
||||
@@ -46,9 +46,9 @@ impl Default for MessageInputContent {
|
||||
Self {
|
||||
message_edit: "".to_string(),
|
||||
parse_error: false,
|
||||
file_pick_button: FilePickContent::new(
|
||||
FilePickContentType::Button(t!("choose_file").into())
|
||||
),
|
||||
file_pick_button: FilePickContent::new(FilePickContentType::Button(
|
||||
t!("choose_file").into(),
|
||||
)),
|
||||
scan_qr_content: None,
|
||||
proof_content: None,
|
||||
}
|
||||
@@ -60,11 +60,13 @@ impl MessageInputContent {
|
||||
pub const MODAL_ID: &'static str = "input_message_modal";
|
||||
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
if let Some(scan_content) = self.scan_qr_content.as_mut() {
|
||||
if let Some(result) = scan_content.qr_scan_result() {
|
||||
cb.stop_camera();
|
||||
@@ -209,13 +211,16 @@ impl MessageInputContent {
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
let proof_label = format!("{} {}", SEAL_CHECK, t!("wallets.payment_proof"));
|
||||
View::colored_text_button(ui,
|
||||
View::colored_text_button(
|
||||
ui,
|
||||
proof_label,
|
||||
Colors::gold_dark(),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::set_title(t!("wallets.payment_proof"));
|
||||
self.proof_content = Some(PaymentProofContent::new(None));
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
@@ -239,8 +244,9 @@ impl MessageInputContent {
|
||||
if self.message_edit.is_empty() {
|
||||
return;
|
||||
}
|
||||
if self.message_edit.starts_with("BEGINSLATEPACK.") &&
|
||||
self.message_edit.ends_with("ENDSLATEPACK.") {
|
||||
if self.message_edit.starts_with("BEGINSLATEPACK.")
|
||||
&& self.message_edit.ends_with("ENDSLATEPACK.")
|
||||
{
|
||||
wallet.task(WalletTask::OpenMessage(self.message_edit.to_string()));
|
||||
self.message_edit = "".to_string();
|
||||
Modal::close();
|
||||
|
||||
@@ -24,7 +24,7 @@ mod content;
|
||||
pub use content::WalletContent;
|
||||
|
||||
mod account;
|
||||
mod transport;
|
||||
mod request;
|
||||
mod message;
|
||||
mod proof;
|
||||
mod request;
|
||||
mod transport;
|
||||
|
||||
@@ -3,12 +3,12 @@ use egui::{Id, RichText, ScrollArea};
|
||||
use grin_util::ToHex;
|
||||
use grin_wallet_libwallet::{Error, PaymentProof, TxLogEntryType};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, COPY, FILE_TEXT, SEAL_CHECK};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{FilePickContent, FilePickContentType, Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::{WalletTask, WalletTx};
|
||||
use crate::wallet::Wallet;
|
||||
use crate::wallet::types::{WalletTask, WalletTx};
|
||||
|
||||
pub struct PaymentProofContent {
|
||||
/// Payment proof text.
|
||||
@@ -53,7 +53,11 @@ impl PaymentProofContent {
|
||||
}
|
||||
} else {
|
||||
let desc_label = t!("wallets.payment_proof_desc");
|
||||
ui.label(RichText::new(desc_label).size(16.0).color(Colors::inactive_text()));
|
||||
ui.label(
|
||||
RichText::new(desc_label)
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
}
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
@@ -103,8 +107,10 @@ impl PaymentProofContent {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
if self.parse_error || (self.validation_result.is_some() &&
|
||||
self.validation_result.as_ref().unwrap().is_err()) {
|
||||
if self.parse_error
|
||||
|| (self.validation_result.is_some()
|
||||
&& self.validation_result.as_ref().unwrap().is_err())
|
||||
{
|
||||
// Draw button to clear message input.
|
||||
let clear_text = format!("{} {}", BROOM, t!("clear"));
|
||||
View::button(ui, clear_text, Colors::white_or_black(false), || {
|
||||
@@ -152,16 +158,16 @@ impl PaymentProofContent {
|
||||
}
|
||||
|
||||
/// Draw transaction payment proof content to share.
|
||||
pub fn share_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
tx: &WalletTx,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
pub fn share_ui(&mut self, ui: &mut egui::Ui, tx: &WalletTx, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
let (desc_text, color) = if tx.data.tx_type == TxLogEntryType::TxReceived {
|
||||
(t!("wallets.payment_proof_valid").into(), Colors::green())
|
||||
} else {
|
||||
(format!("{}:", t!("wallets.payment_proof")), Colors::inactive_text())
|
||||
(
|
||||
format!("{}:", t!("wallets.payment_proof")),
|
||||
Colors::inactive_text(),
|
||||
)
|
||||
};
|
||||
let desc = format!("{} {}", SEAL_CHECK, desc_text);
|
||||
ui.label(RichText::new(desc).size(16.0).color(color));
|
||||
@@ -208,15 +214,18 @@ impl PaymentProofContent {
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
let share_text = format!("{} {}", FILE_TEXT, t!("share"));
|
||||
View::colored_text_button(ui,
|
||||
View::colored_text_button(
|
||||
ui,
|
||||
share_text,
|
||||
Colors::blue(),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
let file_name = format!("{}.txt", tx.data.kernel_excess.unwrap().to_hex());
|
||||
let data = self.input_edit.as_bytes().to_vec();
|
||||
cb.share_data(file_name, data).unwrap_or_default();
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
use egui::{Id, RichText};
|
||||
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::WalletTask;
|
||||
use crate::wallet::Wallet;
|
||||
use crate::wallet::types::WalletTask;
|
||||
|
||||
/// Invoice request creation content.
|
||||
pub struct InvoiceRequestContent {
|
||||
@@ -37,11 +37,13 @@ impl Default for InvoiceRequestContent {
|
||||
|
||||
impl InvoiceRequestContent {
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
// Setup callback on continue.
|
||||
let on_continue = |m: &mut InvoiceRequestContent| {
|
||||
if m.amount_edit.is_empty() {
|
||||
@@ -58,9 +60,11 @@ impl InvoiceRequestContent {
|
||||
|
||||
// Draw amount input content.
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.enter_amount_receive"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.enter_amount_receive"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
|
||||
@@ -91,8 +95,9 @@ impl InvoiceRequestContent {
|
||||
} else {
|
||||
// Check input after `.`.
|
||||
let parts = self.amount_edit.split(".").collect::<Vec<&str>>();
|
||||
if parts.len() == 2 && (parts[1].len() > 9 ||
|
||||
(amount == 0 && parts[1].len() > 8)) {
|
||||
if parts.len() == 2
|
||||
&& (parts[1].len() > 9 || (amount == 0 && parts[1].len() > 8))
|
||||
{
|
||||
self.amount_edit = amount_edit_before;
|
||||
}
|
||||
}
|
||||
@@ -111,10 +116,15 @@ impl InvoiceRequestContent {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
self.amount_edit = "".to_string();
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
// Button to create Slatepack message request.
|
||||
|
||||
@@ -17,11 +17,11 @@ use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
|
||||
use grin_core::global::get_accept_fee_base;
|
||||
use grin_wallet_libwallet::SlatepackAddress;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::WalletTask;
|
||||
use crate::wallet::Wallet;
|
||||
use crate::wallet::types::WalletTask;
|
||||
|
||||
/// Content to create a request to send funds.
|
||||
pub struct SendRequestContent {
|
||||
@@ -73,11 +73,13 @@ impl SendRequestContent {
|
||||
}
|
||||
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw QR code scanner content if requested.
|
||||
@@ -123,14 +125,14 @@ impl SendRequestContent {
|
||||
let data = wallet.get_data().unwrap();
|
||||
let amount = amount_to_hr_string(data.info.amount_currently_spendable, true);
|
||||
let enter_text = t!("wallets.enter_amount_send","amount" => amount);
|
||||
ui.label(RichText::new(enter_text)
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
ui.label(RichText::new(enter_text).size(17.0).color(Colors::gray()));
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw amount text edit.
|
||||
let amount_edit_id = Id::from(modal.id).with("amount").with(wallet.get_config().id);
|
||||
let amount_edit_id = Id::from(modal.id)
|
||||
.with("amount")
|
||||
.with(wallet.get_config().id);
|
||||
let mut amount_edit = TextEdit::new(amount_edit_id)
|
||||
.h_center()
|
||||
.numeric()
|
||||
@@ -185,8 +187,9 @@ impl SendRequestContent {
|
||||
} else {
|
||||
// Check input after `.`.
|
||||
let parts = self.amount_edit.split(".").collect::<Vec<&str>>();
|
||||
if parts.len() == 2 && (parts[1].len() > 9 ||
|
||||
(amount == 0 && parts[1].len() > 8)) {
|
||||
if parts.len() == 2
|
||||
&& (parts[1].len() > 9 || (amount == 0 && parts[1].len() > 8))
|
||||
{
|
||||
self.amount_edit = amount_edit_before.clone();
|
||||
}
|
||||
}
|
||||
@@ -217,42 +220,47 @@ impl SendRequestContent {
|
||||
"wallets.fee_base_desc",
|
||||
"value" => format!(": {}", get_accept_fee_base())
|
||||
);
|
||||
ui.label(RichText::new(fee_label)
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
ui.label(RichText::new(fee_label).size(17.0).color(Colors::gray()));
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
let fee_edit_id = Id::from(modal.id).with("_fee").with(wallet.get_config().id);
|
||||
let mut fee_edit = TextEdit::new(fee_edit_id)
|
||||
.focus(false)
|
||||
.h_center()
|
||||
.disable();
|
||||
let mut fee_edit = TextEdit::new(fee_edit_id).focus(false).h_center().disable();
|
||||
let mut loading_label = format!("{}...", t!("wallets.loading"));
|
||||
fee_edit.ui(ui, if wallet.fee_calculating() {
|
||||
fee_edit.ui(
|
||||
ui,
|
||||
if wallet.fee_calculating() {
|
||||
&mut loading_label
|
||||
} else {
|
||||
&mut self.fee_edit
|
||||
}, cb);
|
||||
},
|
||||
cb,
|
||||
);
|
||||
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show address error or input description.
|
||||
ui.vertical_centered(|ui| {
|
||||
if self.address_error {
|
||||
ui.label(RichText::new(t!("transport.incorrect_addr_err"))
|
||||
ui.label(
|
||||
RichText::new(t!("transport.incorrect_addr_err"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
} else {
|
||||
ui.label(RichText::new(t!("transport.receiver_address"))
|
||||
ui.label(
|
||||
RichText::new(t!("transport.receiver_address"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
}
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show address text edit.
|
||||
let addr_edit_before = self.address_edit.clone();
|
||||
let address_edit_id = Id::from(modal.id).with("_address").with(wallet.get_config().id);
|
||||
let address_edit_id = Id::from(modal.id)
|
||||
.with("_address")
|
||||
.with(wallet.get_config().id);
|
||||
let mut address_edit = TextEdit::new(address_edit_id)
|
||||
.paste()
|
||||
.focus(false)
|
||||
@@ -281,9 +289,14 @@ impl SendRequestContent {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
self.close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
// Button to create Slatepack message request.
|
||||
|
||||
@@ -16,12 +16,12 @@ use eframe::emath::Align;
|
||||
use eframe::epaint::{RectShape, StrokeKind};
|
||||
use egui::{CursorIcon, Id, Layout, RichText, Sense, UiBuilder};
|
||||
|
||||
use crate::gui::icons::{CLOCK_COUNTDOWN, FOLDERS, FOLDER_USER, PASSWORD};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CLOCK_COUNTDOWN, FOLDER_USER, FOLDERS, PASSWORD};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::wallet::types::WalletContentContainer;
|
||||
use crate::gui::views::{FilePickContent, FilePickContentType, Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Common wallet settings content.
|
||||
@@ -60,15 +60,17 @@ impl WalletContentContainer for CommonSettings {
|
||||
NAME_EDIT_MODAL,
|
||||
PASS_EDIT_MODAL,
|
||||
DATA_PATH_MODAL,
|
||||
MIN_CONFIRMATIONS_EDIT_MODAL
|
||||
MIN_CONFIRMATIONS_EDIT_MODAL,
|
||||
]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
fn modal_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
match modal.id {
|
||||
NAME_EDIT_MODAL => self.name_modal_ui(ui, wallet, modal, cb),
|
||||
PASS_EDIT_MODAL => self.pass_modal_ui(ui, wallet, modal, cb),
|
||||
@@ -94,7 +96,11 @@ impl WalletContentContainer for CommonSettings {
|
||||
self.data_dir_ui(ui, wallet, cb);
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.min_tx_conf_count")).size(16.0).color(Colors::gray()));
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.min_tx_conf_count"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show minimum amount of confirmations value setup.
|
||||
@@ -111,9 +117,14 @@ impl WalletContentContainer for CommonSettings {
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Ability to post wallet transactions with Dandelion.
|
||||
View::checkbox(ui, wallet.can_use_dandelion(), t!("wallets.use_dandelion"), || {
|
||||
View::checkbox(
|
||||
ui,
|
||||
wallet.can_use_dandelion(),
|
||||
t!("wallets.use_dandelion"),
|
||||
|| {
|
||||
wallet.update_use_dandelion(!wallet.can_use_dandelion());
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
ui.add_space(6.0);
|
||||
View::horizontal_line(ui, Colors::stroke());
|
||||
@@ -130,9 +141,11 @@ impl Default for CommonSettings {
|
||||
old_pass_edit: "".to_string(),
|
||||
new_pass_edit: "".to_string(),
|
||||
data_path_edit: "".to_string(),
|
||||
pick_data_dir: FilePickContent::new(
|
||||
FilePickContentType::ItemButton(View::item_rounding(1, 2, true))
|
||||
).no_parse().pick_folder(),
|
||||
pick_data_dir: FilePickContent::new(FilePickContentType::ItemButton(
|
||||
View::item_rounding(1, 2, true),
|
||||
))
|
||||
.no_parse()
|
||||
.pick_folder(),
|
||||
min_confirmations_edit: "".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -153,11 +166,13 @@ impl CommonSettings {
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
let res = ui.scope_builder(
|
||||
let res = ui
|
||||
.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
.max_rect(rect),
|
||||
|ui| {
|
||||
let r = if View::is_desktop() {
|
||||
View::item_rounding(0, 2, true)
|
||||
} else {
|
||||
@@ -174,19 +189,28 @@ impl CommonSettings {
|
||||
.show();
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
layout_size,
|
||||
Layout::left_to_right(Align::Center),
|
||||
|ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
View::ellipsize_text(ui, name.clone(), 18.0, Colors::title(false));
|
||||
ui.add_space(1.0);
|
||||
let desc = format!("{} {}", FOLDER_USER, t!("wallets.name").replace(":", ""));
|
||||
let desc = format!(
|
||||
"{} {}",
|
||||
FOLDER_USER,
|
||||
t!("wallets.name").replace(":", "")
|
||||
);
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
@@ -206,11 +230,13 @@ impl CommonSettings {
|
||||
}
|
||||
|
||||
/// Draw wallet name [`Modal`] content.
|
||||
fn name_modal_ui(&mut self,
|
||||
fn name_modal_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
let on_save = |c: &mut CommonSettings| {
|
||||
if !c.name_edit.is_empty() {
|
||||
wallet.change_name(c.name_edit.clone());
|
||||
@@ -220,9 +246,11 @@ impl CommonSettings {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.name"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.name"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
// Show wallet name text edit.
|
||||
let mut name_edit = TextEdit::new(Id::from(modal.id).with(wallet.get_config().id));
|
||||
@@ -240,10 +268,15 @@ impl CommonSettings {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -256,11 +289,13 @@ impl CommonSettings {
|
||||
}
|
||||
|
||||
/// Draw wallet pass [`Modal`] content.
|
||||
fn pass_modal_ui(&mut self,
|
||||
fn pass_modal_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
let wallet_id = wallet.get_config().id;
|
||||
let on_continue = |c: &mut CommonSettings| {
|
||||
if c.new_pass_edit.is_empty() {
|
||||
@@ -276,15 +311,17 @@ impl CommonSettings {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
}
|
||||
Err(_) => c.wrong_pass = true
|
||||
Err(_) => c.wrong_pass = true,
|
||||
}
|
||||
};
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.current_pass"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.current_pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw old password text edit.
|
||||
@@ -295,16 +332,16 @@ impl CommonSettings {
|
||||
pass_edit.ui(ui, &mut self.old_pass_edit, cb);
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.label(RichText::new(t!("wallets.new_pass"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.new_pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw new password text edit.
|
||||
let new_pass_edit_id = Id::from(modal.id).with(wallet_id).with("new_pass");
|
||||
let mut new_pass_edit = TextEdit::new(new_pass_edit_id)
|
||||
.password()
|
||||
.focus(false);
|
||||
let mut new_pass_edit = TextEdit::new(new_pass_edit_id).password().focus(false);
|
||||
if pass_edit.enter_pressed {
|
||||
new_pass_edit.focus_request();
|
||||
}
|
||||
@@ -316,14 +353,18 @@ impl CommonSettings {
|
||||
// Show information when password is empty.
|
||||
if self.old_pass_edit.is_empty() || self.new_pass_edit.is_empty() {
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(t!("wallets.pass_empty"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.pass_empty"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
} else if self.wrong_pass {
|
||||
ui.add_space(10.0);
|
||||
ui.label(RichText::new(t!("wallets.wrong_pass"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.wrong_pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
@@ -335,10 +376,15 @@ impl CommonSettings {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("change"), Colors::white_or_black(false), || {
|
||||
@@ -360,16 +406,21 @@ impl CommonSettings {
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
let res = ui.scope_builder(
|
||||
let res = ui
|
||||
.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
.max_rect(rect),
|
||||
|ui| {
|
||||
self.pick_data_dir.ui(ui, cb, |path| {
|
||||
wallet.change_data_path(path);
|
||||
});
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.allocate_ui_with_layout(
|
||||
layout_size,
|
||||
Layout::left_to_right(Align::Center),
|
||||
|ui| {
|
||||
ui.add_space(12.0);
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
@@ -380,9 +431,11 @@ impl CommonSettings {
|
||||
ui.label(RichText::new(desc).size(15.0).color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.response;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
if res.hovered() {
|
||||
@@ -402,19 +455,23 @@ impl CommonSettings {
|
||||
}
|
||||
|
||||
/// Draw data path input [`Modal`] content.
|
||||
fn data_path_modal_ui(&mut self,
|
||||
fn data_path_modal_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
let on_save = |path: &String| {
|
||||
wallet.change_data_path(path.clone());
|
||||
Modal::close();
|
||||
};
|
||||
ui.label(RichText::new(format!("{}:", t!("files_location")))
|
||||
ui.label(
|
||||
RichText::new(format!("{}:", t!("files_location")))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw chain data path text edit.
|
||||
@@ -432,9 +489,14 @@ impl CommonSettings {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
@@ -448,11 +510,13 @@ impl CommonSettings {
|
||||
}
|
||||
|
||||
/// Draw wallet name [`Modal`] content.
|
||||
fn min_conf_modal_ui(&mut self,
|
||||
fn min_conf_modal_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
let on_save = |c: &mut CommonSettings| {
|
||||
if let Ok(min_conf) = c.min_confirmations_edit.parse::<u64>() {
|
||||
wallet.update_min_confirmations(min_conf);
|
||||
@@ -462,9 +526,11 @@ impl CommonSettings {
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.min_tx_conf_count"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.min_tx_conf_count"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Minimum amount of confirmations text edit.
|
||||
@@ -477,9 +543,11 @@ impl CommonSettings {
|
||||
// Show error when specified value is not valid or reminder to restart enabled node.
|
||||
if self.min_confirmations_edit.parse::<u64>().is_err() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("network_settings.not_valid_value"))
|
||||
ui.label(
|
||||
RichText::new(t!("network_settings.not_valid_value"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
});
|
||||
@@ -491,10 +559,15 @@ impl CommonSettings {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
// Close modal.
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.save"), Colors::white_or_black(false), || {
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
|
||||
use egui::RichText;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{GLOBE, PLUS_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::modals::ExternalConnectionModal;
|
||||
use crate::gui::views::network::ConnectionsContent;
|
||||
use crate::gui::views::network::modals::ExternalConnectionModal;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Modal, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::node::Node;
|
||||
use crate::wallet::types::ConnectionMethod;
|
||||
use crate::wallet::{ConnectionsConfig, ExternalConnection};
|
||||
@@ -54,19 +54,14 @@ impl Default for ConnectionSettings {
|
||||
|
||||
impl ContentContainer for ConnectionSettings {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
ExternalConnectionModal::WALLET_ID
|
||||
]
|
||||
vec![ExternalConnectionModal::WALLET_ID]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
ExternalConnectionModal::WALLET_ID => {
|
||||
self.ext_conn_modal.ui(ui, cb, modal, |_| {});
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -86,17 +81,26 @@ impl ContentContainer for ConnectionSettings {
|
||||
} else {
|
||||
Colors::fill_lite()
|
||||
};
|
||||
ConnectionsContent::integrated_node_item_ui(ui, bg, (!cur_integrated, || {
|
||||
ConnectionsContent::integrated_node_item_ui(
|
||||
ui,
|
||||
bg,
|
||||
(!cur_integrated, || {
|
||||
self.method = ConnectionMethod::Integrated;
|
||||
}), |ui| {
|
||||
}),
|
||||
|ui| {
|
||||
if cur_integrated {
|
||||
View::selected_item_check(ui);
|
||||
}
|
||||
cur_integrated
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::gray()));
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.ext_conn"))
|
||||
.size(16.0)
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Show button to add new external node connection.
|
||||
@@ -113,23 +117,23 @@ impl ContentContainer for ConnectionSettings {
|
||||
// Check for removed active connection.
|
||||
let cur_method = &self.method.clone();
|
||||
let mut ext_conn_list = ConnectionsConfig::ext_conn_list();
|
||||
let has_method = !ext_conn_list.iter().filter(|c| {
|
||||
match cur_method {
|
||||
let has_method = !ext_conn_list
|
||||
.iter()
|
||||
.filter(|c| match cur_method {
|
||||
ConnectionMethod::Integrated => true,
|
||||
ConnectionMethod::External(id, url) => id == &c.id || url == &c.url
|
||||
}
|
||||
}).collect::<Vec<&ExternalConnection>>().is_empty();
|
||||
ConnectionMethod::External(id, url) => id == &c.id || url == &c.url,
|
||||
})
|
||||
.collect::<Vec<&ExternalConnection>>()
|
||||
.is_empty();
|
||||
if !has_method {
|
||||
match cur_method {
|
||||
ConnectionMethod::External(id, url) => {
|
||||
ext_conn_list.push(ExternalConnection {
|
||||
ConnectionMethod::External(id, url) => ext_conn_list.push(ExternalConnection {
|
||||
id: *id,
|
||||
url: url.clone(),
|
||||
username: Some("grin".to_string()),
|
||||
secret: None,
|
||||
available: Some(true),
|
||||
})
|
||||
}
|
||||
}),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -142,20 +146,28 @@ impl ContentContainer for ConnectionSettings {
|
||||
// Draw external connection item.
|
||||
let is_current = match cur_method {
|
||||
ConnectionMethod::External(id, url) => id == &c.id || url == &c.url,
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
let bg = if is_current {
|
||||
Colors::fill()
|
||||
} else {
|
||||
Colors::fill_lite()
|
||||
};
|
||||
ConnectionsContent::ext_conn_item_ui(ui, bg, c, i, len, (!is_current, || {
|
||||
ConnectionsContent::ext_conn_item_ui(
|
||||
ui,
|
||||
bg,
|
||||
c,
|
||||
i,
|
||||
len,
|
||||
(!is_current, || {
|
||||
self.method = ConnectionMethod::External(c.id, c.url.clone());
|
||||
}), |ui| {
|
||||
}),
|
||||
|ui| {
|
||||
if is_current {
|
||||
View::selected_item_check(ui);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::ContentContainer;
|
||||
use crate::gui::views::wallets::{CommonSettings, ConnectionSettings, RecoverySettings};
|
||||
use crate::gui::views::wallets::wallet::types::WalletContentContainer;
|
||||
use crate::gui::views::wallets::{CommonSettings, ConnectionSettings, RecoverySettings};
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Wallet settings tab content.
|
||||
@@ -25,7 +25,7 @@ pub struct WalletSettingsContent {
|
||||
/// Connection setup content.
|
||||
conn_setup: ConnectionSettings,
|
||||
/// Recovery setup content.
|
||||
recovery_setup: RecoverySettings
|
||||
recovery_setup: RecoverySettings,
|
||||
}
|
||||
|
||||
impl Default for WalletSettingsContent {
|
||||
@@ -33,16 +33,13 @@ impl Default for WalletSettingsContent {
|
||||
Self {
|
||||
common_setup: CommonSettings::default(),
|
||||
conn_setup: ConnectionSettings::default(),
|
||||
recovery_setup: RecoverySettings::default()
|
||||
recovery_setup: RecoverySettings::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletSettingsContent {
|
||||
pub fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
// Show common wallet setup.
|
||||
self.common_setup.ui(ui, wallet, cb);
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@ use egui::{Id, RichText};
|
||||
use grin_chain::SyncStatus;
|
||||
use grin_util::ZeroingString;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{EYE, KEY, LIFEBUOY, STETHOSCOPE, TRASH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
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;
|
||||
use crate::wallet::types::ConnectionMethod;
|
||||
|
||||
/// Wallet recovery settings content.
|
||||
pub struct RecoverySettings {
|
||||
@@ -44,16 +44,16 @@ const DELETE_CONFIRMATION_MODAL: &'static str = "delete_wallet_confirmation_moda
|
||||
|
||||
impl WalletContentContainer for RecoverySettings {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
RECOVERY_PHRASE_MODAL,
|
||||
DELETE_CONFIRMATION_MODAL
|
||||
]
|
||||
vec![RECOVERY_PHRASE_MODAL, DELETE_CONFIRMATION_MODAL]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
fn modal_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
match modal.id {
|
||||
RECOVERY_PHRASE_MODAL => {
|
||||
self.recovery_phrase_modal_ui(ui, wallet, modal, cb);
|
||||
@@ -78,9 +78,11 @@ impl WalletContentContainer for RecoverySettings {
|
||||
let integrated_node_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
|
||||
if wallet.sync_error() || (integrated_node && !integrated_node_ready) {
|
||||
ui.add_space(2.0);
|
||||
ui.label(RichText::new(t!("wallets.repair_unavailable"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.repair_unavailable"))
|
||||
.size(16.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
ui.add_space(2.0);
|
||||
} else if !wallet.is_repairing() {
|
||||
ui.add_space(6.0);
|
||||
@@ -92,9 +94,11 @@ impl WalletContentContainer for RecoverySettings {
|
||||
});
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.repair_desc"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.repair_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()));
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
}
|
||||
|
||||
ui.add_space(6.0);
|
||||
@@ -103,23 +107,32 @@ impl WalletContentContainer for RecoverySettings {
|
||||
|
||||
// Draw button to restore the wallet.
|
||||
ui.add_space(4.0);
|
||||
View::colored_text_button(ui,
|
||||
View::colored_text_button(
|
||||
ui,
|
||||
format!("{} {}", LIFEBUOY, t!("wallets.recover")),
|
||||
Colors::green(),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
wallet.delete_db();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.restore_wallet_desc"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.restore_wallet_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()));
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
|
||||
ui.add_space(6.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.0);
|
||||
|
||||
let recovery_text = format!("{}:", t!("wallets.recovery_phrase"));
|
||||
ui.label(RichText::new(recovery_text).size(16.0).color(Colors::gray()));
|
||||
ui.label(
|
||||
RichText::new(recovery_text)
|
||||
.size(16.0)
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw button to show recovery phrase.
|
||||
@@ -131,19 +144,26 @@ impl WalletContentContainer for RecoverySettings {
|
||||
ui.add_space(12.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.delete_desc")).size(16.0).color(Colors::red()));
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.delete_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::red()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw button to delete the wallet.
|
||||
View::colored_text_button(ui,
|
||||
View::colored_text_button(
|
||||
ui,
|
||||
format!("{} {}", TRASH, t!("wallets.delete")),
|
||||
Colors::red(),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::new(DELETE_CONFIRMATION_MODAL)
|
||||
.position(ModalPosition::Center)
|
||||
.title(t!("confirmation"))
|
||||
.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
}
|
||||
@@ -174,13 +194,14 @@ impl RecoverySettings {
|
||||
}
|
||||
|
||||
/// Draw recovery phrase [`Modal`] content.
|
||||
fn recovery_phrase_modal_ui(&mut self,
|
||||
fn recovery_phrase_modal_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
let on_next = |c: &mut RecoverySettings| {
|
||||
match wallet.get_recovery(c.pass_edit.clone()) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
let on_next = |c: &mut RecoverySettings| match wallet.get_recovery(c.pass_edit.clone()) {
|
||||
Ok(phrase) => {
|
||||
c.wrong_pass = false;
|
||||
c.recovery_phrase = Some(phrase);
|
||||
@@ -188,15 +209,16 @@ impl RecoverySettings {
|
||||
Err(_) => {
|
||||
c.wrong_pass = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ui.add_space(6.0);
|
||||
if self.recovery_phrase.is_some() {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(self.recovery_phrase.clone().unwrap().to_string())
|
||||
ui.label(
|
||||
RichText::new(self.recovery_phrase.clone().unwrap().to_string())
|
||||
.size(17.0)
|
||||
.color(Colors::white_or_black(true)));
|
||||
.color(Colors::white_or_black(true)),
|
||||
);
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
@@ -207,15 +229,16 @@ impl RecoverySettings {
|
||||
});
|
||||
} else {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.pass"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
.color(Colors::gray()),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw current wallet password text edit.
|
||||
let pass_edit_id = Id::from(modal.id).with(wallet.get_config().id);
|
||||
let mut pass_edit = TextEdit::new(pass_edit_id)
|
||||
.password();
|
||||
let mut pass_edit = TextEdit::new(pass_edit_id).password();
|
||||
pass_edit.ui(ui, &mut self.pass_edit, cb);
|
||||
if pass_edit.enter_pressed {
|
||||
on_next(self);
|
||||
@@ -224,14 +247,18 @@ impl RecoverySettings {
|
||||
// Show information when password is empty or wrong.
|
||||
if self.pass_edit.is_empty() {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("wallets.pass_empty"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.pass_empty"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
} else if self.wrong_pass {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("wallets.wrong_pass"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.wrong_pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
.color(Colors::red()),
|
||||
);
|
||||
}
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
@@ -243,10 +270,15 @@ impl RecoverySettings {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
self.recovery_phrase = None;
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, "OK".to_owned(), Colors::white_or_black(false), || {
|
||||
@@ -263,9 +295,11 @@ impl RecoverySettings {
|
||||
pub fn deletion_modal_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
ui.add_space(8.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.delete_conf"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.delete_conf"))
|
||||
.size(17.0)
|
||||
.color(Colors::text(false)));
|
||||
.color(Colors::text(false)),
|
||||
);
|
||||
});
|
||||
ui.add_space(12.0);
|
||||
|
||||
@@ -276,9 +310,14 @@ impl RecoverySettings {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("delete"), Colors::white_or_black(false), || {
|
||||
|
||||
@@ -15,12 +15,15 @@
|
||||
use egui::{Align, CornerRadius, Layout, RichText, StrokeKind};
|
||||
|
||||
use crate::AppConfig;
|
||||
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::Colors;
|
||||
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::Colors;
|
||||
use crate::tor::{Tor, TorConfig};
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
@@ -34,11 +37,12 @@ pub struct WalletTransportContent {
|
||||
}
|
||||
|
||||
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 modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self, _: &mut egui::Ui, _: &Wallet, _: &Modal, _: &dyn PlatformCallbacks) {}
|
||||
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||
if let Some(content) = self.qr_address_content.as_mut() {
|
||||
let dark_theme = AppConfig::dark_theme().unwrap_or(false);
|
||||
@@ -112,18 +116,21 @@ impl WalletTransportContent {
|
||||
|
||||
// Draw round background.
|
||||
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 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,
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
@@ -134,8 +141,8 @@ impl WalletTransportContent {
|
||||
View::item_rounding(1, 2, true)
|
||||
};
|
||||
View::item_button(ui, r, QR_CODE, None, || {
|
||||
self.qr_address_content = Some(QrCodeContent::new(addr.clone(), false)
|
||||
.with_max_size(320.0));
|
||||
self.qr_address_content =
|
||||
Some(QrCodeContent::new(addr.clone(), false).with_max_size(320.0));
|
||||
});
|
||||
|
||||
let service_id = &wallet.identifier();
|
||||
@@ -164,7 +171,10 @@ impl WalletTransportContent {
|
||||
});
|
||||
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
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);
|
||||
@@ -202,13 +212,19 @@ impl WalletTransportContent {
|
||||
let bridges_text = if is_starting || has_error {
|
||||
match TorConfig::get_bridge() {
|
||||
None => {
|
||||
format!("{} {}", SHIELD_SLASH, t!("transport.bridges_disabled"))
|
||||
format!(
|
||||
"{} {}",
|
||||
SHIELD_SLASH,
|
||||
t!("transport.bridges_disabled")
|
||||
)
|
||||
}
|
||||
Some(b) => {
|
||||
let name = b.protocol_name().to_uppercase();
|
||||
format!("{} {}",
|
||||
format!(
|
||||
"{} {}",
|
||||
SHIELD_CHECKERED,
|
||||
t!("transport.bridge_name", "b" = name))
|
||||
t!("transport.bridge_name", "b" = name)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -217,7 +233,8 @@ impl WalletTransportContent {
|
||||
// Show bridge info text.
|
||||
ui.label(RichText::new(bridges_text).size(15.0).color(Colors::gray()));
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
|
||||
use egui::RichText;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::View;
|
||||
use crate::gui::views::settings::TorSettingsContent;
|
||||
use crate::gui::views::types::ContentContainer;
|
||||
use crate::gui::views::View;
|
||||
use crate::gui::Colors;
|
||||
use crate::tor::Tor;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
@@ -31,18 +31,20 @@ pub struct WalletTransportSettingsContent {
|
||||
impl Default for WalletTransportSettingsContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tor_settings_content: TorSettingsContent::default()
|
||||
tor_settings_content: TorSettingsContent::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletTransportSettingsContent {
|
||||
/// Draw transport settings content.
|
||||
pub fn ui(&mut self,
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_close: impl FnOnce()) {
|
||||
on_close: impl FnOnce(),
|
||||
) {
|
||||
ui.add_space(8.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
// Show Tor settings.
|
||||
@@ -50,9 +52,11 @@ impl WalletTransportSettingsContent {
|
||||
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"))
|
||||
ui.label(
|
||||
RichText::new(t!("transport.tor_autorun_desc"))
|
||||
.size(17.0)
|
||||
.color(Colors::inactive_text()));
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
// Show Tor service autorun checkbox.
|
||||
let autorun = wallet.auto_start_tor_listener();
|
||||
View::checkbox(ui, autorun, t!("network.autorun"), || {
|
||||
|
||||
@@ -14,22 +14,28 @@
|
||||
|
||||
use egui::epaint::RectShape;
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui::{Align, Color32, CornerRadius, CursorIcon, Id, Layout, Rect, RichText, ScrollArea, Sense, StrokeKind, UiBuilder};
|
||||
use egui::{
|
||||
Align, Color32, CornerRadius, CursorIcon, Id, Layout, Rect, RichText, ScrollArea, Sense,
|
||||
StrokeKind, UiBuilder,
|
||||
};
|
||||
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::icons::{ARROWS_CLOCKWISE, ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, CALENDAR_CHECK, DOTS_THREE_CIRCLE, FILE_ARROW_DOWN, FILE_TEXT, FILE_X, GEAR_FINE, PROHIBIT, WARNING, X_CIRCLE};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{
|
||||
ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, ARROWS_CLOCKWISE, CALENDAR_CHECK, DOTS_THREE_CIRCLE,
|
||||
FILE_ARROW_DOWN, FILE_TEXT, FILE_X, GEAR_FINE, PROHIBIT, WARNING, X_CIRCLE,
|
||||
};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::{LinePosition, ModalPosition};
|
||||
use crate::gui::views::wallets::wallet::types::{WalletContentContainer, GRIN};
|
||||
use crate::gui::views::wallets::wallet::WalletTransactionContent;
|
||||
use crate::gui::views::wallets::wallet::types::{GRIN, WalletContentContainer};
|
||||
use crate::gui::views::{Content, Modal, PullToRefresh, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::{WalletData, WalletTask, WalletTx, WalletTxAction};
|
||||
use crate::wallet::Wallet;
|
||||
use crate::wallet::types::{WalletData, WalletTask, WalletTx, WalletTxAction};
|
||||
|
||||
/// Wallet transactions tab content.
|
||||
pub struct WalletTransactionsContent {
|
||||
@@ -42,7 +48,7 @@ pub struct WalletTransactionsContent {
|
||||
confirm_delete_tx_id: Option<u32>,
|
||||
|
||||
/// Flag to check if sync of wallet was initiated manually at time.
|
||||
manual_sync: Option<u128>
|
||||
manual_sync: Option<u128>,
|
||||
}
|
||||
|
||||
/// Identifier for transaction information [`Modal`].
|
||||
@@ -54,7 +60,11 @@ const DELETE_TX_CONFIRMATION_MODAL: &'static str = "delete_tx_conf_modal";
|
||||
|
||||
impl WalletContentContainer for WalletTransactionsContent {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![TX_INFO_MODAL, CANCEL_TX_CONFIRMATION_MODAL, DELETE_TX_CONFIRMATION_MODAL]
|
||||
vec![
|
||||
TX_INFO_MODAL,
|
||||
CANCEL_TX_CONFIRMATION_MODAL,
|
||||
DELETE_TX_CONFIRMATION_MODAL,
|
||||
]
|
||||
}
|
||||
|
||||
fn modal_ui(&mut self, ui: &mut egui::Ui, w: &Wallet, m: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
@@ -113,7 +123,10 @@ impl WalletTransactionsContent {
|
||||
});
|
||||
return;
|
||||
}
|
||||
let txs = data.txs.as_ref().unwrap()
|
||||
let txs = data
|
||||
.txs
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|tx| !tx.deleting())
|
||||
.collect::<Vec<&WalletTx>>();
|
||||
@@ -127,9 +140,11 @@ impl WalletTransactionsContent {
|
||||
"transport" => FILE_TEXT,
|
||||
"settings" => GEAR_FINE
|
||||
);
|
||||
ui.label(RichText::new(empty_text)
|
||||
ui.label(
|
||||
RichText::new(empty_text)
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()));
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -139,7 +154,10 @@ impl WalletTransactionsContent {
|
||||
ui.add_space(4.0);
|
||||
|
||||
// Show list of transactions.
|
||||
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis();
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
let refresh = self.manual_sync.unwrap_or(0) + 1600 > now;
|
||||
let refresh_resp = PullToRefresh::new(refresh)
|
||||
.id(Id::from("refresh_tx_list").with(config.id))
|
||||
@@ -174,11 +192,13 @@ impl WalletTransactionsContent {
|
||||
}
|
||||
|
||||
/// Draw transaction list content.
|
||||
fn tx_list_ui(&mut self,
|
||||
fn tx_list_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
row_range: Range<usize>,
|
||||
wallet: &Wallet,
|
||||
txs: Vec<&WalletTx>) {
|
||||
txs: Vec<&WalletTx>,
|
||||
) {
|
||||
let data = wallet.get_data().unwrap();
|
||||
for index in row_range {
|
||||
if index == txs.len() && ui.is_visible() {
|
||||
@@ -212,9 +232,17 @@ impl WalletTransactionsContent {
|
||||
let tx = txs.get(index).unwrap();
|
||||
let mut show_tx_info = false;
|
||||
// Draw transaction list item.
|
||||
Self::tx_item_ui(ui, tx, rect, bg, rounding, &data, (true, || {
|
||||
Self::tx_item_ui(
|
||||
ui,
|
||||
tx,
|
||||
rect,
|
||||
bg,
|
||||
rounding,
|
||||
&data,
|
||||
(true, || {
|
||||
show_tx_info = true;
|
||||
}), |ui| {
|
||||
}),
|
||||
|ui| {
|
||||
let btn_rounding = {
|
||||
let mut r = rounding.clone();
|
||||
r.nw = 0.0 as u8;
|
||||
@@ -223,11 +251,19 @@ impl WalletTransactionsContent {
|
||||
};
|
||||
// Draw button to delete transaction.
|
||||
if tx.data.confirmed || tx.cancelled() {
|
||||
View::item_button(ui, btn_rounding, FILE_X, Some(Colors::inactive_text()), || {
|
||||
View::item_button(
|
||||
ui,
|
||||
btn_rounding,
|
||||
FILE_X,
|
||||
Some(Colors::inactive_text()),
|
||||
|| {
|
||||
self.show_delete_confirmation_modal(tx.data.id);
|
||||
});
|
||||
} else if !tx.cancelled() && !tx.cancelling() && !tx.posting() &&
|
||||
wallet.synced_from_node() {
|
||||
},
|
||||
);
|
||||
} else if !tx.cancelled()
|
||||
&& !tx.cancelling()
|
||||
&& !tx.posting() && wallet.synced_from_node()
|
||||
{
|
||||
let repeat = tx.broadcasting_timed_out(wallet);
|
||||
// Draw button to cancel transaction.
|
||||
if tx.can_cancel() || repeat {
|
||||
@@ -243,10 +279,17 @@ impl WalletTransactionsContent {
|
||||
}
|
||||
// Draw button to repeat transaction action.
|
||||
if tx.can_repeat_action(wallet) || repeat {
|
||||
Self::tx_repeat_button_ui(ui, CornerRadius::default(), tx, wallet, repeat);
|
||||
Self::tx_repeat_button_ui(
|
||||
ui,
|
||||
CornerRadius::default(),
|
||||
tx,
|
||||
wallet,
|
||||
repeat,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
// Show transaction info on click.
|
||||
if show_tx_info {
|
||||
self.show_tx_info_modal(tx.data.id);
|
||||
@@ -265,14 +308,21 @@ impl WalletTransactionsContent {
|
||||
ui.add_space(-1.0);
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
// Draw background.
|
||||
let mut bg = RectShape::new(rect, CornerRadius {
|
||||
let mut bg = RectShape::new(
|
||||
rect,
|
||||
CornerRadius {
|
||||
nw: 0.0 as u8,
|
||||
ne: 0.0 as u8,
|
||||
sw: 8.0 as u8,
|
||||
se: 8.0 as u8,
|
||||
}, Colors::fill(), View::item_stroke(), StrokeKind::Outside);
|
||||
},
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
let bg_idx = ui.painter().add(bg.clone());
|
||||
let resp = ui.allocate_ui(rect.size(), |ui| {
|
||||
let resp = ui
|
||||
.allocate_ui(rect.size(), |ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
// Correct vertical spacing between items.
|
||||
ui.style_mut().spacing.item_spacing.y = -3.0;
|
||||
@@ -289,29 +339,34 @@ impl WalletTransactionsContent {
|
||||
awaiting_item_ui(ui, amount_locked, t!("wallets.locked_amount"));
|
||||
}
|
||||
});
|
||||
}).response;
|
||||
})
|
||||
.response;
|
||||
// Setup background size.
|
||||
bg.rect = resp.rect;
|
||||
ui.painter().set(bg_idx, bg);
|
||||
}
|
||||
|
||||
/// Draw transaction item.
|
||||
pub fn tx_item_ui(ui: &mut egui::Ui,
|
||||
pub fn tx_item_ui(
|
||||
ui: &mut egui::Ui,
|
||||
tx: &WalletTx,
|
||||
rect: Rect,
|
||||
bg: Color32,
|
||||
r: CornerRadius,
|
||||
data: &WalletData,
|
||||
on_click: (bool, impl FnOnce()),
|
||||
buttons_ui: impl FnOnce(&mut egui::Ui)) {
|
||||
buttons_ui: impl FnOnce(&mut egui::Ui),
|
||||
) {
|
||||
// Draw background.
|
||||
let mut bg_shape = RectShape::new(rect, r, bg, View::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
let res = ui.scope_builder(
|
||||
let res = ui
|
||||
.scope_builder(
|
||||
UiBuilder::new()
|
||||
.sense(Sense::click())
|
||||
.layout(Layout::right_to_left(Align::Center))
|
||||
.max_rect(rect), |ui| {
|
||||
.max_rect(rect),
|
||||
|ui| {
|
||||
ui.horizontal_centered(|ui| {
|
||||
// Draw buttons.
|
||||
buttons_ui(ui);
|
||||
@@ -323,19 +378,24 @@ impl WalletTransactionsContent {
|
||||
ui.add_space(3.0);
|
||||
|
||||
// Setup transaction amount.
|
||||
let mut amount_text = if tx.data.tx_type == TxLogEntryType::TxSent ||
|
||||
tx.data.tx_type == TxLogEntryType::TxSentCancelled {
|
||||
let mut amount_text = if tx.data.tx_type == TxLogEntryType::TxSent
|
||||
|| tx.data.tx_type == TxLogEntryType::TxSentCancelled
|
||||
{
|
||||
"-"
|
||||
} else if tx.data.tx_type == TxLogEntryType::TxReceived ||
|
||||
tx.data.tx_type == TxLogEntryType::TxReceivedCancelled {
|
||||
} else if tx.data.tx_type == TxLogEntryType::TxReceived
|
||||
|| tx.data.tx_type == TxLogEntryType::TxReceivedCancelled
|
||||
{
|
||||
"+"
|
||||
} else {
|
||||
""
|
||||
}.to_string();
|
||||
amount_text = format!("{}{} {}",
|
||||
}
|
||||
.to_string();
|
||||
amount_text = format!(
|
||||
"{}{} {}",
|
||||
amount_text,
|
||||
amount_to_hr_string(tx.amount, true),
|
||||
GRIN);
|
||||
GRIN
|
||||
);
|
||||
|
||||
// Setup amount color.
|
||||
let amount_color = match tx.data.tx_type {
|
||||
@@ -344,7 +404,7 @@ impl WalletTransactionsContent {
|
||||
TxLogEntryType::TxSent => Colors::white_or_black(true),
|
||||
TxLogEntryType::TxReceivedCancelled => Colors::text(false),
|
||||
TxLogEntryType::TxSentCancelled => Colors::text(false),
|
||||
TxLogEntryType::TxReverted => Colors::text(false)
|
||||
TxLogEntryType::TxReverted => Colors::text(false),
|
||||
};
|
||||
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
|
||||
ui.add_space(1.0);
|
||||
@@ -355,7 +415,8 @@ impl WalletTransactionsContent {
|
||||
// Setup transaction status text.
|
||||
let height = data.info.last_confirmed_height;
|
||||
let status_text = if !tx.data.confirmed {
|
||||
let is_canceled = tx.data.tx_type == TxLogEntryType::TxSentCancelled
|
||||
let is_canceled = tx.data.tx_type
|
||||
== TxLogEntryType::TxSentCancelled
|
||||
|| tx.data.tx_type == TxLogEntryType::TxReceivedCancelled;
|
||||
if is_canceled {
|
||||
format!("{} {}", X_CIRCLE, t!("wallets.tx_canceled"))
|
||||
@@ -369,7 +430,7 @@ impl WalletTransactionsContent {
|
||||
WalletTxAction::Finalizing => t!("wallets.tx_finalizing"),
|
||||
WalletTxAction::Posting => t!("wallets.tx_posting"),
|
||||
WalletTxAction::SendingTor => t!("transport.tor_sending"),
|
||||
_ => t!("wallets.tx_cancelling")
|
||||
_ => t!("wallets.tx_cancelling"),
|
||||
};
|
||||
let icon = if error.is_empty() {
|
||||
DOTS_THREE_CIRCLE
|
||||
@@ -382,45 +443,53 @@ impl WalletTransactionsContent {
|
||||
TxLogEntryType::TxReceived => {
|
||||
let text = match tx.finalized() {
|
||||
true => t!("wallets.await_fin_amount"),
|
||||
false => t!("wallets.tx_receiving")
|
||||
false => t!("wallets.tx_receiving"),
|
||||
};
|
||||
format!("{} {}", DOTS_THREE_CIRCLE, text)
|
||||
},
|
||||
}
|
||||
TxLogEntryType::TxSent => {
|
||||
let text = match tx.finalized() {
|
||||
true => t!("wallets.await_fin_amount"),
|
||||
false => t!("wallets.tx_sending")
|
||||
false => t!("wallets.tx_sending"),
|
||||
};
|
||||
format!("{} {}", DOTS_THREE_CIRCLE, text)
|
||||
},
|
||||
}
|
||||
TxLogEntryType::ConfirmedCoinbase => {
|
||||
let tx_h = tx.height.unwrap_or(1) - 1;
|
||||
if tx_h != 0 {
|
||||
let left_conf = height - tx_h;
|
||||
if height >= tx_h && left_conf < COINBASE_MATURITY {
|
||||
let conf_info = format!("{}/{}",
|
||||
left_conf,
|
||||
COINBASE_MATURITY);
|
||||
format!("{} {} {}",
|
||||
let conf_info = format!(
|
||||
"{}/{}",
|
||||
left_conf, COINBASE_MATURITY
|
||||
);
|
||||
format!(
|
||||
"{} {} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"),
|
||||
conf_info
|
||||
)
|
||||
} else {
|
||||
format!("{} {}",
|
||||
format!(
|
||||
"{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"))
|
||||
t!("wallets.tx_confirming")
|
||||
)
|
||||
}
|
||||
} else {
|
||||
format!("{} {}",
|
||||
format!(
|
||||
"{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"))
|
||||
t!("wallets.tx_confirming")
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
format!("{} {}",
|
||||
format!(
|
||||
"{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"))
|
||||
t!("wallets.tx_confirming")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -431,31 +500,37 @@ impl WalletTransactionsContent {
|
||||
if tx_h != 0 {
|
||||
let left_conf = height - tx_h;
|
||||
if height >= tx_h && left_conf < COINBASE_MATURITY {
|
||||
let conf_info = format!("{}/{}",
|
||||
left_conf,
|
||||
COINBASE_MATURITY);
|
||||
format!("{} {} {}",
|
||||
let conf_info =
|
||||
format!("{}/{}", left_conf, COINBASE_MATURITY);
|
||||
format!(
|
||||
"{} {} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"),
|
||||
conf_info
|
||||
)
|
||||
} else {
|
||||
format!("{} {}",
|
||||
format!(
|
||||
"{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirmed"))
|
||||
t!("wallets.tx_confirmed")
|
||||
)
|
||||
}
|
||||
} else {
|
||||
format!("{} {}",
|
||||
format!(
|
||||
"{} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirmed"))
|
||||
t!("wallets.tx_confirmed")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
TxLogEntryType::TxSent | TxLogEntryType::TxReceived => {
|
||||
let min_conf = data.info.minimum_confirmations;
|
||||
if tx.height.is_none() || (tx.height.unwrap() != 0 &&
|
||||
height - tx.height.unwrap() >= min_conf - 1) {
|
||||
let (i, t) = if tx.data.tx_type == TxLogEntryType::TxSent {
|
||||
if tx.height.is_none()
|
||||
|| (tx.height.unwrap() != 0
|
||||
&& height - tx.height.unwrap() >= min_conf - 1)
|
||||
{
|
||||
let (i, t) =
|
||||
if tx.data.tx_type == TxLogEntryType::TxSent {
|
||||
(ARROW_CIRCLE_UP, t!("wallets.tx_sent"))
|
||||
} else {
|
||||
(ARROW_CIRCLE_DOWN, t!("wallets.tx_received"))
|
||||
@@ -464,36 +539,43 @@ impl WalletTransactionsContent {
|
||||
} else {
|
||||
let tx_height = tx.height.unwrap() - 1;
|
||||
let left_conf = height - tx_height;
|
||||
let conf_info = if tx_height != 0 && height >= tx_height &&
|
||||
left_conf < min_conf {
|
||||
let conf_info = if tx_height != 0
|
||||
&& height >= tx_height && left_conf
|
||||
< min_conf
|
||||
{
|
||||
format!("{}/{}", left_conf, min_conf)
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
format!("{} {} {}",
|
||||
format!(
|
||||
"{} {} {}",
|
||||
DOTS_THREE_CIRCLE,
|
||||
t!("wallets.tx_confirming"),
|
||||
conf_info
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => format!("{} {}", X_CIRCLE, t!("wallets.canceled"))
|
||||
}
|
||||
_ => format!("{} {}", X_CIRCLE, t!("wallets.canceled")),
|
||||
}
|
||||
};
|
||||
|
||||
// Setup status text color.
|
||||
let status_color = match tx.data.tx_type {
|
||||
TxLogEntryType::ConfirmedCoinbase => Colors::text(false),
|
||||
TxLogEntryType::TxReceived => if tx.data.confirmed {
|
||||
TxLogEntryType::TxReceived => {
|
||||
if tx.data.confirmed {
|
||||
Colors::green()
|
||||
} else {
|
||||
Colors::text(false)
|
||||
},
|
||||
TxLogEntryType::TxSent => if tx.data.confirmed {
|
||||
}
|
||||
}
|
||||
TxLogEntryType::TxSent => {
|
||||
if tx.data.confirmed {
|
||||
Colors::red()
|
||||
} else {
|
||||
Colors::text(false)
|
||||
},
|
||||
}
|
||||
}
|
||||
TxLogEntryType::TxReceivedCancelled => Colors::inactive_text(),
|
||||
TxLogEntryType::TxSentCancelled => Colors::inactive_text(),
|
||||
TxLogEntryType::TxReverted => Colors::inactive_text(),
|
||||
@@ -507,8 +589,9 @@ impl WalletTransactionsContent {
|
||||
ui.add_space(4.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
).response;
|
||||
},
|
||||
)
|
||||
.response;
|
||||
let (clickable, on_click) = on_click;
|
||||
let clicked = res.clicked() || res.long_touched();
|
||||
// Setup background and cursor.
|
||||
@@ -524,11 +607,13 @@ impl WalletTransactionsContent {
|
||||
}
|
||||
|
||||
/// Draw button to repeat transaction action on error or repost.
|
||||
pub fn tx_repeat_button_ui(ui: &mut egui::Ui,
|
||||
pub fn tx_repeat_button_ui(
|
||||
ui: &mut egui::Ui,
|
||||
rounding: CornerRadius,
|
||||
tx: &WalletTx,
|
||||
wallet: &Wallet,
|
||||
repost: bool) {
|
||||
repost: bool,
|
||||
) {
|
||||
let (icon, color) = (ARROWS_CLOCKWISE, Some(Colors::green()));
|
||||
View::item_button(ui, rounding, icon, color, || {
|
||||
if repost {
|
||||
@@ -569,7 +654,8 @@ impl WalletTransactionsContent {
|
||||
fn cancel_confirmation_modal(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
let data = wallet.get_data().unwrap();
|
||||
let data_txs = data.txs.unwrap();
|
||||
let txs = data_txs.into_iter()
|
||||
let txs = data_txs
|
||||
.into_iter()
|
||||
.filter(|tx| tx.data.id == self.confirm_cancel_tx_id.unwrap_or_default())
|
||||
.collect::<Vec<WalletTx>>();
|
||||
if txs.is_empty() {
|
||||
@@ -581,7 +667,7 @@ impl WalletTransactionsContent {
|
||||
let text = match tx.data.tx_type {
|
||||
TxLogEntryType::TxReceived => {
|
||||
t!("wallets.tx_receive_cancel_conf", "amount" => amount)
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
t!("wallets.tx_send_cancel_conf", "amount" => amount)
|
||||
}
|
||||
@@ -590,9 +676,7 @@ impl WalletTransactionsContent {
|
||||
// Show confirmation text.
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(text)
|
||||
.size(17.0)
|
||||
.color(Colors::text(false)));
|
||||
ui.label(RichText::new(text).size(17.0).color(Colors::text(false)));
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
|
||||
@@ -603,10 +687,15 @@ impl WalletTransactionsContent {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
self.confirm_cancel_tx_id = None;
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, "OK".to_string(), Colors::white_or_black(false), || {
|
||||
@@ -634,7 +723,8 @@ impl WalletTransactionsContent {
|
||||
fn delete_confirmation_modal(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
let data = wallet.get_data().unwrap();
|
||||
let data_txs = data.txs.unwrap();
|
||||
let txs = data_txs.into_iter()
|
||||
let txs = data_txs
|
||||
.into_iter()
|
||||
.filter(|tx| tx.data.id == self.confirm_delete_tx_id.unwrap_or_default())
|
||||
.collect::<Vec<WalletTx>>();
|
||||
if txs.is_empty() {
|
||||
@@ -646,9 +736,11 @@ impl WalletTransactionsContent {
|
||||
// Show confirmation text.
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.tx_delete_confirmation"))
|
||||
ui.label(
|
||||
RichText::new(t!("wallets.tx_delete_confirmation"))
|
||||
.size(17.0)
|
||||
.color(Colors::text(false)));
|
||||
.color(Colors::text(false)),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
});
|
||||
|
||||
@@ -659,10 +751,15 @@ impl WalletTransactionsContent {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
View::button(
|
||||
ui,
|
||||
t!("modal.cancel"),
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
self.confirm_delete_tx_id = None;
|
||||
Modal::close();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, "OK".to_string(), Colors::white_or_black(false), || {
|
||||
@@ -683,11 +780,11 @@ fn awaiting_item_ui(ui: &mut egui::Ui, amount: u64, label: impl Into<String>) {
|
||||
View::line(ui, LinePosition::TOP, &rect, Colors::item_stroke());
|
||||
ui.add_space(4.0);
|
||||
let amount_format = amount_to_hr_string(amount, true);
|
||||
ui.label(RichText::new(format!("{} ツ", amount_format))
|
||||
ui.label(
|
||||
RichText::new(format!("{} ツ", amount_format))
|
||||
.color(Colors::white_or_black(true))
|
||||
.size(17.0));
|
||||
ui.label(RichText::new(label)
|
||||
.color(Colors::gray())
|
||||
.size(15.0));
|
||||
.size(17.0),
|
||||
);
|
||||
ui.label(RichText::new(label).color(Colors::gray()).size(15.0));
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
@@ -19,17 +19,20 @@ use grin_util::ToHex;
|
||||
use grin_wallet_libwallet::TxLogEntryType;
|
||||
use std::fs;
|
||||
|
||||
use crate::gui::icons::{CIRCLE_HALF, COPY, CUBE, FILE_ARCHIVE, FILE_TEXT, FILE_X, HASH_STRAIGHT, PROHIBIT, QR_CODE, SEAL_CHECK};
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{
|
||||
CIRCLE_HALF, COPY, CUBE, FILE_ARCHIVE, FILE_TEXT, FILE_X, HASH_STRAIGHT, PROHIBIT, QR_CODE,
|
||||
SEAL_CHECK,
|
||||
};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::wallet::message::MessageInputContent;
|
||||
use crate::gui::views::wallets::wallet::proof::PaymentProofContent;
|
||||
use crate::gui::views::wallets::wallet::txs::WalletTransactionsContent;
|
||||
use crate::gui::views::{Modal, QrCodeContent, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::types::{WalletTask, WalletTx};
|
||||
use crate::wallet::Wallet;
|
||||
use crate::AppConfig;
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::wallet::message::MessageInputContent;
|
||||
use crate::wallet::types::{WalletTask, WalletTx};
|
||||
|
||||
/// Transaction information [`Modal`] content.
|
||||
pub struct WalletTransactionContent {
|
||||
@@ -57,12 +60,14 @@ impl WalletTransactionContent {
|
||||
}
|
||||
|
||||
/// Draw [`Modal`] content.
|
||||
pub fn ui(&mut self,
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
modal: &Modal,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_delete: impl FnOnce(u32)) {
|
||||
on_delete: impl FnOnce(u32),
|
||||
) {
|
||||
// Check values and setup transaction data.
|
||||
let wallet_data = wallet.get_data();
|
||||
if wallet_data.is_none() {
|
||||
@@ -71,7 +76,8 @@ impl WalletTransactionContent {
|
||||
}
|
||||
let data = wallet_data.unwrap();
|
||||
let data_txs = data.txs.clone().unwrap();
|
||||
let txs = data_txs.into_iter()
|
||||
let txs = data_txs
|
||||
.into_iter()
|
||||
.filter(|tx| tx.data.id == self.tx_id)
|
||||
.collect::<Vec<WalletTx>>();
|
||||
if txs.is_empty() {
|
||||
@@ -122,8 +128,7 @@ impl WalletTransactionContent {
|
||||
if let Some(proof_content) = self.proof_content.as_mut() {
|
||||
// Draw payment proof sharing content.
|
||||
proof_content.share_ui(ui, tx, cb);
|
||||
} else if tx.proof.is_some() && !tx.sending_tor() &&
|
||||
tx.action_error.is_none() {
|
||||
} else if tx.proof.is_some() && !tx.sending_tor() && tx.action_error.is_none() {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(8.0);
|
||||
let label = format!("{} {}", SEAL_CHECK, t!("wallets.payment_proof"));
|
||||
@@ -155,11 +160,13 @@ impl WalletTransactionContent {
|
||||
}
|
||||
|
||||
/// Draw transaction sharing content.
|
||||
fn share_ui(&mut self,
|
||||
fn share_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &Wallet,
|
||||
tx: &WalletTx,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
if self.message.is_none() {
|
||||
let slatepack_path = wallet.get_config().get_tx_slate_path(tx);
|
||||
self.message = Some(fs::read_to_string(slatepack_path).unwrap_or("".to_string()));
|
||||
@@ -185,7 +192,11 @@ impl WalletTransactionContent {
|
||||
};
|
||||
ui.add_space(6.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(desc_text).size(16.0).color(Colors::inactive_text()));
|
||||
ui.label(
|
||||
RichText::new(desc_text)
|
||||
.size(16.0)
|
||||
.color(Colors::inactive_text()),
|
||||
);
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
@@ -250,10 +261,12 @@ impl WalletTransactionContent {
|
||||
ui.add_space(8.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
let share_text = format!("{} {}", FILE_TEXT, t!("share"));
|
||||
View::colored_text_button(ui,
|
||||
View::colored_text_button(
|
||||
ui,
|
||||
share_text,
|
||||
Colors::blue(),
|
||||
Colors::white_or_black(false), || {
|
||||
Colors::white_or_black(false),
|
||||
|| {
|
||||
if let Some(slate_id) = tx.data.tx_slate_id {
|
||||
let name = format!("{}.{}.slatepack", slate_id, tx.state);
|
||||
let data = m.as_bytes().to_vec();
|
||||
@@ -265,7 +278,8 @@ impl WalletTransactionContent {
|
||||
Modal::close();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if finalization_needed {
|
||||
@@ -278,12 +292,14 @@ impl WalletTransactionContent {
|
||||
}
|
||||
|
||||
/// Draw transaction information content.
|
||||
fn info_ui(&mut self,
|
||||
fn info_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
tx: &WalletTx,
|
||||
wallet: &Wallet,
|
||||
cb: &dyn PlatformCallbacks,
|
||||
on_delete: impl FnOnce(u32)) {
|
||||
on_delete: impl FnOnce(u32),
|
||||
) {
|
||||
ui.add_space(6.0);
|
||||
// Transaction item background setup.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
@@ -308,9 +324,7 @@ impl WalletTransactionContent {
|
||||
let height = format!("{} {}", CUBE, h.to_string());
|
||||
ui.with_layout(Layout::bottom_up(Align::Max), |ui| {
|
||||
ui.add_space(3.0);
|
||||
ui.label(RichText::new(height)
|
||||
.size(15.0)
|
||||
.color(Colors::text(false)));
|
||||
ui.label(RichText::new(height).size(15.0).color(Colors::text(false)));
|
||||
});
|
||||
}
|
||||
return;
|
||||
@@ -361,11 +375,13 @@ impl WalletTransactionContent {
|
||||
}
|
||||
|
||||
/// Draw transaction information item content.
|
||||
fn info_item_ui(ui: &mut egui::Ui,
|
||||
fn info_item_ui(
|
||||
ui: &mut egui::Ui,
|
||||
value: String,
|
||||
label: String,
|
||||
copy: bool,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
) {
|
||||
// Setup layout size.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(50.0);
|
||||
@@ -374,7 +390,13 @@ fn info_item_ui(ui: &mut egui::Ui,
|
||||
let bg_rect = rect.clone();
|
||||
let mut rounding = View::item_rounding(1, 3, false);
|
||||
|
||||
ui.painter().rect(bg_rect, rounding, Colors::fill(), View::item_stroke(), StrokeKind::Outside);
|
||||
ui.painter().rect(
|
||||
bg_rect,
|
||||
rounding,
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside,
|
||||
);
|
||||
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to copy transaction info value.
|
||||
|
||||
@@ -25,7 +25,13 @@ pub trait WalletContentContainer {
|
||||
/// List of allowed [`Modal`] identifiers.
|
||||
fn modal_ids(&self) -> Vec<&'static str>;
|
||||
/// Draw modal content.
|
||||
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,
|
||||
);
|
||||
/// Draw container content.
|
||||
fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks);
|
||||
/// Draw content, to call by parent container.
|
||||
@@ -53,20 +59,19 @@ pub fn wallet_status_text(wallet: &Wallet) -> String {
|
||||
if repair_progress == 0 {
|
||||
format!("{} {}", SPINNER, t!("wallets.checking"))
|
||||
} else {
|
||||
format!("{} {}: {}%",
|
||||
format!(
|
||||
"{} {}: {}%",
|
||||
SPINNER,
|
||||
t!("wallets.checking"),
|
||||
repair_progress)
|
||||
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)
|
||||
format!("{} {}: {}%", SPINNER, t!("wallets.loading"), info_progress)
|
||||
}
|
||||
} else if wallet.is_open() {
|
||||
format!("{} {}", FOLDER_OPEN, t!("wallets.unlocked"))
|
||||
|
||||
+24
-16
@@ -23,13 +23,14 @@ use serde::de::StdError;
|
||||
use crate::AppConfig;
|
||||
|
||||
/// Handles http requests.
|
||||
pub struct HttpClient {
|
||||
}
|
||||
pub struct HttpClient {}
|
||||
|
||||
impl HttpClient {
|
||||
/// Send request.
|
||||
pub async fn send<B>(req: Request<B>) -> Result<Response<Incoming>, Error>
|
||||
where B: Body + Send + 'static + Unpin, <B as Body>::Data: Send,
|
||||
where
|
||||
B: Body + Send + 'static + Unpin,
|
||||
<B as Body>::Data: Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
@@ -40,16 +41,19 @@ impl HttpClient {
|
||||
Self::send_http_proxy(AppConfig::http_proxy_url().unwrap(), req).await
|
||||
}
|
||||
} else {
|
||||
let client = Client::builder(TokioExecutor::new())
|
||||
.build::<_, B>(HttpsConnector::new());
|
||||
let client = Client::builder(TokioExecutor::new()).build::<_, B>(HttpsConnector::new());
|
||||
client.request(req).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Create socks proxy client.
|
||||
pub async fn send_socks_proxy<B>(proxy_url: String, req: Request<B>)
|
||||
-> Result<Response<Incoming>, Error>
|
||||
where B: Body + Send + 'static + Unpin, <B as Body>::Data: Send,
|
||||
pub async fn send_socks_proxy<B>(
|
||||
proxy_url: String,
|
||||
req: Request<B>,
|
||||
) -> Result<Response<Incoming>, Error>
|
||||
where
|
||||
B: Body + Send + 'static + Unpin,
|
||||
<B as Body>::Data: Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
@@ -59,16 +63,21 @@ impl HttpClient {
|
||||
proxy_addr: uri,
|
||||
auth: None,
|
||||
connector,
|
||||
}.with_tls().unwrap();
|
||||
let client = Client::builder(TokioExecutor::new())
|
||||
.build::<_, B>(proxy);
|
||||
}
|
||||
.with_tls()
|
||||
.unwrap();
|
||||
let client = Client::builder(TokioExecutor::new()).build::<_, B>(proxy);
|
||||
client.request(req).await
|
||||
}
|
||||
|
||||
/// Create http proxy client.
|
||||
pub async fn send_http_proxy<B>(proxy_url: String, req: Request<B>)
|
||||
-> Result<Response<Incoming>, Error>
|
||||
where B: Body + Send + 'static + Unpin, <B as Body>::Data: Send,
|
||||
pub async fn send_http_proxy<B>(
|
||||
proxy_url: String,
|
||||
req: Request<B>,
|
||||
) -> Result<Response<Incoming>, Error>
|
||||
where
|
||||
B: Body + Send + 'static + Unpin,
|
||||
<B as Body>::Data: Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
@@ -76,8 +85,7 @@ impl HttpClient {
|
||||
let proxy = Proxy::new(Intercept::All, uri);
|
||||
let connector = HttpsConnector::new();
|
||||
let proxy_connector = ProxyConnector::from_proxy(connector, proxy).unwrap();
|
||||
let client = Client::builder(TokioExecutor::new())
|
||||
.build::<_, B>(proxy_connector);
|
||||
let client = Client::builder(TokioExecutor::new()).build::<_, B>(proxy_connector);
|
||||
client.request(req).await
|
||||
}
|
||||
}
|
||||
+4
-6
@@ -12,13 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::gui::views::View;
|
||||
use crate::http::HttpClient;
|
||||
use bytes::Bytes;
|
||||
use chrono::NaiveDateTime;
|
||||
use egui::os::OperatingSystem;
|
||||
use http_body_util::{BodyExt, Empty};
|
||||
use serde_derive::Deserialize;
|
||||
use crate::gui::views::View;
|
||||
use crate::http::HttpClient;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ReleaseAsset {
|
||||
@@ -67,7 +67,7 @@ impl ReleaseInfo {
|
||||
format!("grim-{}-android-x86_64.apk", self.tag_name)
|
||||
};
|
||||
Some(name)
|
||||
},
|
||||
}
|
||||
OperatingSystem::IOS => None,
|
||||
OperatingSystem::Nix => {
|
||||
let name = if ARCH == ARM_ARCH {
|
||||
@@ -77,9 +77,7 @@ impl ReleaseInfo {
|
||||
};
|
||||
Some(name)
|
||||
}
|
||||
OperatingSystem::Mac => {
|
||||
Some(format!("grim-{}-macos-universal.zip", self.tag_name))
|
||||
},
|
||||
OperatingSystem::Mac => Some(format!("grim-{}-macos-universal.zip", self.tag_name)),
|
||||
OperatingSystem::Windows => {
|
||||
if ARCH == ARM_ARCH {
|
||||
None
|
||||
|
||||
+35
-19
@@ -33,13 +33,13 @@ use crate::gui::views::View;
|
||||
use crate::gui::{App, Colors};
|
||||
use crate::node::Node;
|
||||
|
||||
mod node;
|
||||
mod wallet;
|
||||
mod tor;
|
||||
mod settings;
|
||||
mod http;
|
||||
pub mod gui;
|
||||
mod http;
|
||||
pub mod logger;
|
||||
mod node;
|
||||
mod settings;
|
||||
mod tor;
|
||||
mod wallet;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -81,13 +81,18 @@ fn android_main(app: AndroidApp) {
|
||||
#[allow(dead_code)]
|
||||
#[cfg(target_os = "android")]
|
||||
fn use_dark_theme(platform: &gui::platform::Android) -> bool {
|
||||
let res = platform.call_java_method("useDarkTheme", "()Z", &[]).unwrap();
|
||||
let res = platform
|
||||
.call_java_method("useDarkTheme", "()Z", &[])
|
||||
.unwrap();
|
||||
unsafe { res.z != 0 }
|
||||
}
|
||||
|
||||
/// [`App`] setup for [`eframe`].
|
||||
pub fn app_creator<T: 'static>(app: App<T>) -> eframe::AppCreator<'static>
|
||||
where App<T>: eframe::App, T: PlatformCallbacks {
|
||||
where
|
||||
App<T>: eframe::App,
|
||||
T: PlatformCallbacks,
|
||||
{
|
||||
Box::new(|cc| {
|
||||
// Setup images support.
|
||||
egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||
@@ -146,7 +151,10 @@ pub fn setup_visuals(ctx: &Context) {
|
||||
egui::Visuals::light()
|
||||
};
|
||||
// Setup selection color.
|
||||
visuals.selection.stroke = Stroke { width: 1.0, color: Colors::text(false) };
|
||||
visuals.selection.stroke = Stroke {
|
||||
width: 1.0,
|
||||
color: Colors::text(false),
|
||||
};
|
||||
visuals.selection.bg_fill = Colors::gold();
|
||||
// Disable stroke around panels by default.
|
||||
visuals.widgets.noninteractive.bg_stroke = Stroke::NONE;
|
||||
@@ -171,13 +179,15 @@ pub fn setup_fonts(ctx: &Context) {
|
||||
|
||||
fonts.font_data.insert(
|
||||
"phosphor".to_owned(),
|
||||
Arc::new(egui::FontData::from_static(include_bytes!(
|
||||
"../fonts/phosphor.ttf"
|
||||
)).tweak(egui::FontTweak {
|
||||
Arc::new(
|
||||
egui::FontData::from_static(include_bytes!("../fonts/phosphor.ttf")).tweak(
|
||||
egui::FontTweak {
|
||||
scale: 1.0,
|
||||
y_offset_factor: -0.04,
|
||||
y_offset: 0.0,
|
||||
}))
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
fonts
|
||||
.families
|
||||
@@ -187,13 +197,15 @@ pub fn setup_fonts(ctx: &Context) {
|
||||
|
||||
fonts.font_data.insert(
|
||||
"noto".to_owned(),
|
||||
Arc::new(egui::FontData::from_static(include_bytes!(
|
||||
"../fonts/noto_sc_reg.otf"
|
||||
)).tweak(egui::FontTweak {
|
||||
Arc::new(
|
||||
egui::FontData::from_static(include_bytes!("../fonts/noto_sc_reg.otf")).tweak(
|
||||
egui::FontTweak {
|
||||
scale: 1.0,
|
||||
y_offset_factor: -0.08,
|
||||
y_offset: 0.0,
|
||||
}))
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
fonts
|
||||
.families
|
||||
@@ -213,7 +225,8 @@ pub fn setup_fonts(ctx: &Context) {
|
||||
(Button, FontId::new(17.0, Proportional)),
|
||||
(Small, FontId::new(15.0, Proportional)),
|
||||
(Monospace, FontId::new(16.0, Proportional)),
|
||||
].into();
|
||||
]
|
||||
.into();
|
||||
|
||||
ctx.set_style(style);
|
||||
}
|
||||
@@ -228,7 +241,10 @@ fn setup_i18n() {
|
||||
} else {
|
||||
let locale = sys_locale::get_locale().unwrap_or(String::from(AppConfig::DEFAULT_LOCALE));
|
||||
let locale_str = if locale.contains("-") {
|
||||
locale.split("-").next().unwrap_or(AppConfig::DEFAULT_LOCALE)
|
||||
locale
|
||||
.split("-")
|
||||
.next()
|
||||
.unwrap_or(AppConfig::DEFAULT_LOCALE)
|
||||
} else {
|
||||
locale.as_str()
|
||||
};
|
||||
@@ -277,7 +293,7 @@ lazy_static! {
|
||||
pub extern "C" fn Java_mw_gri_android_MainActivity_onData(
|
||||
_env: jni::JNIEnv,
|
||||
_class: jni::objects::JObject,
|
||||
char: jni::sys::jstring
|
||||
char: jni::sys::jstring,
|
||||
) {
|
||||
unsafe {
|
||||
let j_obj = jni::objects::JString::from_raw(char);
|
||||
|
||||
+12
-8
@@ -12,21 +12,21 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{panic, thread};
|
||||
use std::fs::File;
|
||||
use backtrace::Backtrace;
|
||||
use log::{LevelFilter, Record, error};
|
||||
use log4rs::Config;
|
||||
use log4rs::append::Append;
|
||||
use log4rs::append::console::ConsoleAppender;
|
||||
use log4rs::append::rolling_file::RollingFileAppender;
|
||||
use log4rs::append::rolling_file::policy::compound::CompoundPolicy;
|
||||
use log4rs::append::rolling_file::policy::compound::roll::fixed_window::FixedWindowRoller;
|
||||
use log4rs::append::rolling_file::policy::compound::trigger::size::SizeTrigger;
|
||||
use log4rs::append::rolling_file::RollingFileAppender;
|
||||
use log4rs::Config;
|
||||
use log4rs::config::{Appender, Root};
|
||||
use log4rs::encode::pattern::PatternEncoder;
|
||||
use log4rs::filter::threshold::ThresholdFilter;
|
||||
use log4rs::filter::{Filter, Response};
|
||||
use log::{error, LevelFilter, Record};
|
||||
use std::fs::File;
|
||||
use std::{panic, thread};
|
||||
|
||||
use crate::Settings;
|
||||
|
||||
@@ -49,8 +49,10 @@ struct AppFilter;
|
||||
impl Filter for AppFilter {
|
||||
fn filter(&self, record: &Record<'_>) -> Response {
|
||||
if let Some(module_path) = record.module_path() {
|
||||
if module_path.starts_with("grin") || module_path.starts_with("grim")
|
||||
|| module_path.starts_with("arti") {
|
||||
if module_path.starts_with("grin")
|
||||
|| module_path.starts_with("grim")
|
||||
|| module_path.starts_with("arti")
|
||||
{
|
||||
return Response::Neutral;
|
||||
}
|
||||
}
|
||||
@@ -155,7 +157,9 @@ fn send_panic_to_log() {
|
||||
// Also print to stderr.
|
||||
eprintln!(
|
||||
"Thread '{}' panicked with message:\n\"{}\"\nSee {} for further details.",
|
||||
thread, msg, Settings::log_path()
|
||||
thread,
|
||||
msg,
|
||||
Settings::log_path()
|
||||
);
|
||||
// Create file to show report send on launch.
|
||||
let log = Settings::crash_check_path();
|
||||
|
||||
+15
-20
@@ -32,7 +32,7 @@ fn real_main() {
|
||||
let path = std::path::PathBuf::from(&args[1]);
|
||||
let content = match std::fs::read_to_string(path) {
|
||||
Ok(s) => Some(s),
|
||||
Err(_) => Some(args[1].clone())
|
||||
Err(_) => Some(args[1].clone()),
|
||||
};
|
||||
data = content
|
||||
}
|
||||
@@ -129,12 +129,8 @@ fn is_app_running(data: &Option<String>) -> bool {
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
use interprocess::local_socket::{
|
||||
tokio::{prelude::*, Stream}
|
||||
};
|
||||
use tokio::{
|
||||
io::AsyncWriteExt,
|
||||
};
|
||||
use interprocess::local_socket::tokio::{Stream, prelude::*};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
let socket_path = grim::Settings::socket_path();
|
||||
let name = socket_name(&socket_path)?;
|
||||
@@ -155,7 +151,7 @@ fn is_app_running(data: &Option<String>) -> bool {
|
||||
});
|
||||
match res {
|
||||
Ok(_) => true,
|
||||
Err(_) => false
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,19 +165,16 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
use grim::gui::platform::PlatformCallbacks;
|
||||
use interprocess::local_socket::{
|
||||
tokio::{prelude::*, Stream},
|
||||
Listener, ListenerOptions,
|
||||
tokio::{Stream, prelude::*},
|
||||
};
|
||||
use std::io;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, BufReader},
|
||||
};
|
||||
use grim::gui::platform::PlatformCallbacks;
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
|
||||
// Handle incoming connection.
|
||||
async fn handle_conn(conn: Stream)
|
||||
-> io::Result<String> {
|
||||
async fn handle_conn(conn: Stream) -> io::Result<String> {
|
||||
let mut read = BufReader::new(&conn);
|
||||
let mut buffer = String::new();
|
||||
// Read data.
|
||||
@@ -211,7 +204,7 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
};
|
||||
// Handle connection.
|
||||
@@ -220,7 +213,7 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||
Ok(data) => {
|
||||
grim::on_data(data);
|
||||
platform.request_user_attention();
|
||||
},
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
@@ -233,11 +226,13 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn socket_name(path: &std::path::PathBuf) -> std::io::Result<interprocess::local_socket::Name<'_>> {
|
||||
use interprocess::local_socket::{NameType, ToFsName, ToNsName};
|
||||
let name = if egui::os::OperatingSystem::Mac != egui::os::OperatingSystem::from_target_os() &&
|
||||
interprocess::local_socket::GenericNamespaced::is_supported() {
|
||||
let name = if egui::os::OperatingSystem::Mac != egui::os::OperatingSystem::from_target_os()
|
||||
&& interprocess::local_socket::GenericNamespaced::is_supported()
|
||||
{
|
||||
grim::Settings::SOCKET_NAME.to_ns_name::<interprocess::local_socket::GenericNamespaced>()?
|
||||
} else {
|
||||
path.clone().to_fs_name::<interprocess::local_socket::GenericFilePath>()?
|
||||
path.clone()
|
||||
.to_fs_name::<interprocess::local_socket::GenericFilePath>()?
|
||||
};
|
||||
Ok(name)
|
||||
}
|
||||
+152
-60
@@ -12,24 +12,26 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use local_ip_address::list_afinet_netifas;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, ToSocketAddrs};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use local_ip_address::list_afinet_netifas;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use grin_config::{config, ConfigError, ConfigMembers, GlobalConfig};
|
||||
use grin_config::config::{API_SECRET_FILE_NAME, FOREIGN_API_SECRET_FILE_NAME, SERVER_CONFIG_FILE_NAME};
|
||||
use grin_config::config::{
|
||||
API_SECRET_FILE_NAME, FOREIGN_API_SECRET_FILE_NAME, SERVER_CONFIG_FILE_NAME,
|
||||
};
|
||||
use grin_config::{ConfigError, ConfigMembers, GlobalConfig, config};
|
||||
use grin_core::global::ChainTypes;
|
||||
use grin_p2p::{PeerAddr, Seeding};
|
||||
use grin_p2p::msg::PeerAddrs;
|
||||
use grin_p2p::{PeerAddr, Seeding};
|
||||
use grin_servers::common::types::ChainValidationMode;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::{AppConfig, Settings};
|
||||
use crate::node::Node;
|
||||
use crate::{AppConfig, Settings};
|
||||
|
||||
/// Peers config to save peers DNS names into the file.
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
@@ -37,7 +39,7 @@ pub struct PeersConfig {
|
||||
seeds: Vec<String>,
|
||||
allowed: Vec<String>,
|
||||
denied: Vec<String>,
|
||||
preferred: Vec<String>
|
||||
preferred: Vec<String>,
|
||||
}
|
||||
|
||||
impl PeersConfig {
|
||||
@@ -130,7 +132,7 @@ impl PeersConfig {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct NodeConfig {
|
||||
pub(crate) node: ConfigMembers,
|
||||
pub(crate) peers: PeersConfig
|
||||
pub(crate) peers: PeersConfig,
|
||||
}
|
||||
|
||||
impl NodeConfig {
|
||||
@@ -164,7 +166,10 @@ impl NodeConfig {
|
||||
}
|
||||
};
|
||||
|
||||
Self { node: node_config, peers: peers_config }
|
||||
Self {
|
||||
node: node_config,
|
||||
peers: peers_config,
|
||||
}
|
||||
}
|
||||
|
||||
/// Save default node config for specified [`ChainTypes`].
|
||||
@@ -180,7 +185,12 @@ impl NodeConfig {
|
||||
Self::setup_default_ports(&mut config);
|
||||
|
||||
// Clear wallet listener url (actually it will be wallet id).
|
||||
config.server.stratum_mining_config.clone().unwrap().wallet_listener_url = "".to_string();
|
||||
config
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.clone()
|
||||
.unwrap()
|
||||
.wallet_listener_url = "".to_string();
|
||||
|
||||
Settings::write_to_file(&config, path);
|
||||
config
|
||||
@@ -193,7 +203,7 @@ impl NodeConfig {
|
||||
let api = rand::rng().random_range(30000..33000);
|
||||
let p2p = rand::rng().random_range(33000..37000);
|
||||
(api, p2p)
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
let api = rand::rng().random_range(40000..43000);
|
||||
let p2p = rand::rng().random_range(43000..47000);
|
||||
@@ -376,7 +386,13 @@ impl NodeConfig {
|
||||
/// Get stratum mining server wallet address to get rewards.
|
||||
pub fn get_stratum_wallet_id() -> Option<i64> {
|
||||
let r_config = Settings::node_config_to_read();
|
||||
let id = r_config.node.clone().server.stratum_mining_config.unwrap().wallet_listener_url;
|
||||
let id = r_config
|
||||
.node
|
||||
.clone()
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.unwrap()
|
||||
.wallet_listener_url;
|
||||
return if id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -385,13 +401,14 @@ impl NodeConfig {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Save stratum mining server wallet address to get rewards.
|
||||
pub fn save_stratum_wallet_id(id: i64) {
|
||||
let mut w_config = Settings::node_config_to_update();
|
||||
w_config.node
|
||||
w_config
|
||||
.node
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.as_mut()
|
||||
@@ -403,7 +420,8 @@ impl NodeConfig {
|
||||
/// Get the amount of time in seconds to attempt to mine on a particular header.
|
||||
pub fn get_stratum_attempt_time() -> String {
|
||||
let r_config = Settings::node_config_to_read();
|
||||
r_config.node
|
||||
r_config
|
||||
.node
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.as_ref()
|
||||
@@ -415,7 +433,8 @@ impl NodeConfig {
|
||||
/// Save stratum attempt time value in seconds.
|
||||
pub fn save_stratum_attempt_time(time: u32) {
|
||||
let mut w_config = Settings::node_config_to_update();
|
||||
w_config.node
|
||||
w_config
|
||||
.node
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.as_mut()
|
||||
@@ -427,7 +446,8 @@ impl NodeConfig {
|
||||
/// Get minimum acceptable share difficulty to request from miners.
|
||||
pub fn get_stratum_min_share_diff() -> String {
|
||||
let r_config = Settings::node_config_to_read();
|
||||
r_config.node
|
||||
r_config
|
||||
.node
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.as_ref()
|
||||
@@ -439,7 +459,8 @@ impl NodeConfig {
|
||||
/// Save minimum acceptable share difficulty.
|
||||
pub fn save_stratum_min_share_diff(diff: u64) {
|
||||
let mut w_config = Settings::node_config_to_update();
|
||||
w_config.node
|
||||
w_config
|
||||
.node
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.as_mut()
|
||||
@@ -451,12 +472,7 @@ impl NodeConfig {
|
||||
/// Check if stratum mining server autorun is enabled.
|
||||
pub fn is_stratum_autorun_enabled() -> bool {
|
||||
let r_config = Settings::node_config_to_read();
|
||||
let stratum_config = r_config
|
||||
.node
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let stratum_config = r_config.node.server.stratum_mining_config.as_ref().unwrap();
|
||||
if let Some(enable) = stratum_config.enable_stratum_server {
|
||||
return enable;
|
||||
}
|
||||
@@ -467,7 +483,8 @@ impl NodeConfig {
|
||||
pub fn toggle_stratum_autorun() {
|
||||
let autorun = Self::is_stratum_autorun_enabled();
|
||||
let mut w_config = Settings::node_config_to_update();
|
||||
w_config.node
|
||||
w_config
|
||||
.node
|
||||
.server
|
||||
.stratum_mining_config
|
||||
.as_mut()
|
||||
@@ -516,16 +533,11 @@ impl NodeConfig {
|
||||
pub fn get_api_secret(foreign: bool) -> Option<String> {
|
||||
let r_config = Settings::node_config_to_read();
|
||||
let api_secret_path = if foreign {
|
||||
&r_config
|
||||
.node
|
||||
.server
|
||||
.foreign_api_secret_path
|
||||
&r_config.node.server.foreign_api_secret_path
|
||||
} else {
|
||||
&r_config
|
||||
.node
|
||||
.server
|
||||
.api_secret_path
|
||||
}.clone();
|
||||
&r_config.node.server.api_secret_path
|
||||
}
|
||||
.clone();
|
||||
if let Some(secret_path) = api_secret_path {
|
||||
if let Ok(file) = File::open(secret_path) {
|
||||
let buf_reader = BufReader::new(file);
|
||||
@@ -555,7 +567,7 @@ impl NodeConfig {
|
||||
let mut w_config = Settings::node_config_to_update();
|
||||
match file_name {
|
||||
API_SECRET_FILE_NAME => w_config.node.server.api_secret_path = None,
|
||||
_ => w_config.node.server.foreign_api_secret_path = None
|
||||
_ => w_config.node.server.foreign_api_secret_path = None,
|
||||
}
|
||||
w_config.save();
|
||||
return;
|
||||
@@ -567,7 +579,7 @@ impl NodeConfig {
|
||||
let r_config = Settings::node_config_to_read();
|
||||
let path = match file_name {
|
||||
API_SECRET_FILE_NAME => r_config.node.server.api_secret_path.clone(),
|
||||
_ => r_config.node.server.foreign_api_secret_path.clone()
|
||||
_ => r_config.node.server.foreign_api_secret_path.clone(),
|
||||
};
|
||||
path.unwrap_or_else(|| {
|
||||
secret_enabled = false;
|
||||
@@ -580,11 +592,10 @@ impl NodeConfig {
|
||||
if !secret_enabled {
|
||||
let mut w_config = Settings::node_config_to_update();
|
||||
match file_name {
|
||||
API_SECRET_FILE_NAME => w_config
|
||||
.node
|
||||
.server
|
||||
.api_secret_path = Some(secret_path.clone()),
|
||||
_ => w_config.node.server.foreign_api_secret_path = Some(secret_path.clone())
|
||||
API_SECRET_FILE_NAME => {
|
||||
w_config.node.server.api_secret_path = Some(secret_path.clone())
|
||||
}
|
||||
_ => w_config.node.server.foreign_api_secret_path = Some(secret_path.clone()),
|
||||
};
|
||||
|
||||
w_config.save();
|
||||
@@ -596,7 +607,11 @@ impl NodeConfig {
|
||||
|
||||
/// Get Future Time Limit.
|
||||
pub fn get_ftl() -> String {
|
||||
Settings::node_config_to_read().node.server.future_time_limit.to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.future_time_limit
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save Future Time Limit.
|
||||
@@ -608,7 +623,11 @@ impl NodeConfig {
|
||||
|
||||
/// Check if full chain validation mode is enabled.
|
||||
pub fn is_full_chain_validation() -> bool {
|
||||
let mode = Settings::node_config_to_read().node.clone().server.chain_validation_mode;
|
||||
let mode = Settings::node_config_to_read()
|
||||
.node
|
||||
.clone()
|
||||
.server
|
||||
.chain_validation_mode;
|
||||
mode == ChainValidationMode::EveryBlock
|
||||
}
|
||||
|
||||
@@ -627,7 +646,11 @@ impl NodeConfig {
|
||||
|
||||
/// Check if node is running in archive mode.
|
||||
pub fn is_archive_mode() -> bool {
|
||||
let archive_mode = Settings::node_config_to_read().node.clone().server.archive_mode;
|
||||
let archive_mode = Settings::node_config_to_read()
|
||||
.node
|
||||
.clone()
|
||||
.server
|
||||
.archive_mode;
|
||||
archive_mode.is_some() && archive_mode.unwrap()
|
||||
}
|
||||
|
||||
@@ -641,7 +664,12 @@ impl NodeConfig {
|
||||
|
||||
/// Get P2P server port.
|
||||
pub fn get_p2p_port() -> String {
|
||||
Settings::node_config_to_read().node.server.p2p_config.port.to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.p2p_config
|
||||
.port
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Check if P2P server port is available across the system and config.
|
||||
@@ -672,14 +700,19 @@ impl NodeConfig {
|
||||
|
||||
/// Check if default seed list is used.
|
||||
pub fn is_default_seeding_type() -> bool {
|
||||
Settings::node_config_to_read().node.server.p2p_config.seeding_type == Seeding::DNSSeed
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.p2p_config
|
||||
.seeding_type
|
||||
== Seeding::DNSSeed
|
||||
}
|
||||
|
||||
/// Toggle seeding type to use default or custom seed list.
|
||||
pub fn toggle_seeding_type() {
|
||||
let seeding_type = match Self::is_default_seeding_type() {
|
||||
true => Seeding::List,
|
||||
false => Seeding::DNSSeed
|
||||
false => Seeding::DNSSeed,
|
||||
};
|
||||
let mut w_config = Settings::node_config_to_update();
|
||||
w_config.node.server.p2p_config.seeding_type = seeding_type;
|
||||
@@ -784,7 +817,12 @@ impl NodeConfig {
|
||||
|
||||
/// How long a banned peer should stay banned in ms.
|
||||
pub fn get_p2p_ban_window() -> String {
|
||||
Settings::node_config_to_read().node.server.p2p_config.ban_window().to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.p2p_config
|
||||
.ban_window()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save for how long a banned peer should stay banned in ms.
|
||||
@@ -797,7 +835,8 @@ impl NodeConfig {
|
||||
/// Maximum number of inbound peer connections.
|
||||
pub fn get_max_inbound_peers() -> String {
|
||||
Settings::node_config_to_read()
|
||||
.node.server
|
||||
.node
|
||||
.server
|
||||
.p2p_config
|
||||
.peer_max_inbound_count()
|
||||
.to_string()
|
||||
@@ -825,13 +864,22 @@ impl NodeConfig {
|
||||
let mut w_config = Settings::node_config_to_update();
|
||||
w_config.node.server.p2p_config.peer_max_outbound_count = Some(count);
|
||||
// Same value for preferred.
|
||||
w_config.node.server.p2p_config.peer_min_preferred_outbound_count = Some(count);
|
||||
w_config
|
||||
.node
|
||||
.server
|
||||
.p2p_config
|
||||
.peer_min_preferred_outbound_count = Some(count);
|
||||
w_config.save();
|
||||
}
|
||||
|
||||
/// Base fee that's accepted into the pool.
|
||||
pub fn get_base_fee() -> String {
|
||||
Settings::node_config_to_read().node.server.pool_config.accept_fee_base.to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.pool_config
|
||||
.accept_fee_base
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save base fee that's accepted into the pool.
|
||||
@@ -843,7 +891,12 @@ impl NodeConfig {
|
||||
|
||||
/// Reorg cache retention period in minutes.
|
||||
pub fn get_reorg_cache_period() -> String {
|
||||
Settings::node_config_to_read().node.server.pool_config.reorg_cache_period.to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.pool_config
|
||||
.reorg_cache_period
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save reorg cache retention period in minutes.
|
||||
@@ -855,7 +908,12 @@ impl NodeConfig {
|
||||
|
||||
/// Max amount of transactions at pool.
|
||||
pub fn get_max_pool_size() -> String {
|
||||
Settings::node_config_to_read().node.server.pool_config.max_pool_size.to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.pool_config
|
||||
.max_pool_size
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save max amount of transactions at pool.
|
||||
@@ -867,7 +925,12 @@ impl NodeConfig {
|
||||
|
||||
/// Max amount of transactions at stem pool.
|
||||
pub fn get_max_stempool_size() -> String {
|
||||
Settings::node_config_to_read().node.server.pool_config.max_stempool_size.to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.pool_config
|
||||
.max_stempool_size
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save max amount of transactions at stem pool.
|
||||
@@ -879,7 +942,12 @@ impl NodeConfig {
|
||||
|
||||
/// Max total weight of transactions that can get selected to build a block.
|
||||
pub fn get_mineable_max_weight() -> String {
|
||||
Settings::node_config_to_read().node.server.pool_config.mineable_max_weight.to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.pool_config
|
||||
.mineable_max_weight
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Set max total weight of transactions that can get selected to build a block.
|
||||
@@ -893,7 +961,12 @@ impl NodeConfig {
|
||||
|
||||
/// Dandelion epoch duration in seconds.
|
||||
pub fn get_dandelion_epoch() -> String {
|
||||
Settings::node_config_to_read().node.server.dandelion_config.epoch_secs.to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.dandelion_config
|
||||
.epoch_secs
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save Dandelion epoch duration in seconds.
|
||||
@@ -906,7 +979,12 @@ impl NodeConfig {
|
||||
/// Dandelion embargo timer in seconds.
|
||||
/// Fluff and broadcast after embargo expires if tx not seen on network.
|
||||
pub fn get_dandelion_embargo() -> String {
|
||||
Settings::node_config_to_read().node.server.dandelion_config.embargo_secs.to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.dandelion_config
|
||||
.embargo_secs
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save Dandelion embargo timer in seconds.
|
||||
@@ -918,7 +996,12 @@ impl NodeConfig {
|
||||
|
||||
/// Dandelion aggregation period in seconds.
|
||||
pub fn get_dandelion_aggregation() -> String {
|
||||
Settings::node_config_to_read().node.server.dandelion_config.aggregation_secs.to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.dandelion_config
|
||||
.aggregation_secs
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save Dandelion aggregation period in seconds.
|
||||
@@ -930,7 +1013,12 @@ impl NodeConfig {
|
||||
|
||||
/// Dandelion stem probability (default: stem 90% of the time, fluff 10% of the time).
|
||||
pub fn get_stem_probability() -> String {
|
||||
Settings::node_config_to_read().node.server.dandelion_config.stem_probability.to_string()
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.dandelion_config
|
||||
.stem_probability
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Save Dandelion stem probability.
|
||||
@@ -942,7 +1030,11 @@ impl NodeConfig {
|
||||
|
||||
/// Default to always stem our txs as described in Dandelion++ paper.
|
||||
pub fn always_stem_our_txs() -> bool {
|
||||
Settings::node_config_to_read().node.server.dandelion_config.always_stem_our_txs
|
||||
Settings::node_config_to_read()
|
||||
.node
|
||||
.server
|
||||
.dandelion_config
|
||||
.always_stem_our_txs
|
||||
}
|
||||
|
||||
/// Toggle stem of our txs.
|
||||
|
||||
@@ -16,24 +16,24 @@
|
||||
//! them into a block and returns it.
|
||||
|
||||
use chrono::prelude::{DateTime, Utc};
|
||||
use rand::{rng, Rng};
|
||||
use serde_json::{json, Value};
|
||||
use rand::{Rng, rng};
|
||||
use serde_json::{Value, json};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::node::stratum::StratumStopState;
|
||||
use grin_api;
|
||||
use grin_chain;
|
||||
use grin_servers::common::types::Error;
|
||||
use grin_core::core::{Output, TxKernel};
|
||||
use grin_core::libtx::secp_ser;
|
||||
use grin_core::libtx::ProofBuilder;
|
||||
use grin_core::libtx::secp_ser;
|
||||
use grin_core::{consensus, core, global};
|
||||
use grin_keychain::{ExtKeychain, Identifier, Keychain};
|
||||
use grin_servers::ServerTxPool;
|
||||
use grin_servers::common::types::Error;
|
||||
use log::{debug, error, trace, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use crate::node::stratum::StratumStopState;
|
||||
|
||||
/// Fees in block to use for coinbase amount calculation
|
||||
/// (Duplicated from Grin wallet project)
|
||||
@@ -75,7 +75,7 @@ pub fn get_block(
|
||||
tx_pool: &ServerTxPool,
|
||||
key_id: Option<Identifier>,
|
||||
wallet_listener_url: Option<String>,
|
||||
stop_state: &Arc<StratumStopState>
|
||||
stop_state: &Arc<StratumStopState>,
|
||||
) -> Option<(core::Block, BlockFees)> {
|
||||
let wallet_retry_interval = 5;
|
||||
// get the latest chain state and build a block on top of it
|
||||
@@ -250,7 +250,7 @@ fn get_coinbase(
|
||||
debug!("get_coinbase: {:?}", block_fees);
|
||||
Ok((output, kernel, block_fees))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Call the wallet API to create a coinbase output for the given block_fees.
|
||||
|
||||
+1
-1
@@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod stratum;
|
||||
mod mine_block;
|
||||
mod stratum;
|
||||
|
||||
mod node;
|
||||
pub use node::Node;
|
||||
|
||||
+39
-30
@@ -12,25 +12,25 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{fs, thread};
|
||||
use futures::channel::oneshot;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use futures::channel::oneshot;
|
||||
use std::{fs, thread};
|
||||
|
||||
use crate::node::stratum::{StratumServer, StratumStopState};
|
||||
use crate::node::{NodeConfig, NodeError, PeersConfig};
|
||||
use grin_chain::SyncStatus;
|
||||
use grin_core::global;
|
||||
use grin_core::global::ChainTypes;
|
||||
use grin_p2p::msg::PeerAddrs;
|
||||
use grin_p2p::Seeding;
|
||||
use grin_servers::{Server, ServerStats, StratumServerConfig, StratumStats};
|
||||
use grin_p2p::msg::PeerAddrs;
|
||||
use grin_servers::common::types::Error;
|
||||
use grin_servers::{Server, ServerStats, StratumServerConfig, StratumStats};
|
||||
use log::error;
|
||||
use crate::node::{NodeConfig, NodeError, PeersConfig};
|
||||
use crate::node::stratum::{StratumStopState, StratumServer};
|
||||
|
||||
lazy_static! {
|
||||
/// Static thread-aware state of [`Node`] to be updated from separate thread.
|
||||
@@ -63,7 +63,7 @@ pub struct Node {
|
||||
change_data_dir: AtomicBool,
|
||||
|
||||
/// An error occurred on [`Server`] start.
|
||||
error: Arc<RwLock<Option<Error>>>
|
||||
error: Arc<RwLock<Option<Error>>>,
|
||||
}
|
||||
|
||||
impl Default for Node {
|
||||
@@ -108,7 +108,9 @@ impl Node {
|
||||
/// Stop the [`Server`] and setup exit flag after if needed.
|
||||
pub fn stop(exit_after_stop: bool) {
|
||||
NODE_STATE.stop_needed.store(true, Ordering::Relaxed);
|
||||
NODE_STATE.exit_after_stop.store(exit_after_stop, Ordering::Relaxed);
|
||||
NODE_STATE
|
||||
.exit_after_stop
|
||||
.store(exit_after_stop, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Request to start the [`Node`].
|
||||
@@ -129,7 +131,9 @@ impl Node {
|
||||
|
||||
/// Request to start [`StratumServer`].
|
||||
pub fn start_stratum() {
|
||||
NODE_STATE.start_stratum_needed.store(true, Ordering::Relaxed);
|
||||
NODE_STATE
|
||||
.start_stratum_needed
|
||||
.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Check if [`StratumServer`] is starting.
|
||||
@@ -186,7 +190,7 @@ impl Node {
|
||||
pub fn not_syncing() -> bool {
|
||||
match Node::get_sync_status() {
|
||||
None => true,
|
||||
Some(ss) => ss == SyncStatus::NoSync
|
||||
Some(ss) => ss == SyncStatus::NoSync,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +223,7 @@ impl Node {
|
||||
let store_err = match e {
|
||||
Error::Store(_) => true,
|
||||
Error::Chain(_) => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
if store_err {
|
||||
return Some(NodeError::Storage);
|
||||
@@ -229,7 +233,7 @@ impl Node {
|
||||
let p2p_api_err = match e {
|
||||
Error::P2P(_) => Some(NodeError::P2P),
|
||||
Error::API(_) => Some(NodeError::API),
|
||||
_ => None
|
||||
_ => None,
|
||||
};
|
||||
if p2p_api_err.is_some() {
|
||||
return p2p_api_err;
|
||||
@@ -238,13 +242,13 @@ impl Node {
|
||||
// Flag setup to show configuration error.
|
||||
let config_err = match e {
|
||||
Error::Configuration(_) => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
};
|
||||
return if config_err {
|
||||
Some(NodeError::Configuration)
|
||||
} else {
|
||||
Some(NodeError::Unknown)
|
||||
}
|
||||
};
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -302,11 +306,8 @@ impl Node {
|
||||
if stratum_start_requested {
|
||||
let (s_ip, s_port) = NodeConfig::get_stratum_address();
|
||||
if NodeConfig::is_stratum_port_available(&s_ip, &s_port) {
|
||||
let stratum_config = server
|
||||
.config
|
||||
.stratum_mining_config
|
||||
.clone()
|
||||
.unwrap();
|
||||
let stratum_config =
|
||||
server.config.stratum_mining_config.clone().unwrap();
|
||||
start_stratum_mining_server(&server, stratum_config);
|
||||
}
|
||||
}
|
||||
@@ -326,7 +327,9 @@ impl Node {
|
||||
|
||||
// Reset stratum server start flag.
|
||||
if stratum_start_requested && NODE_STATE.stratum_stats.read().is_running {
|
||||
NODE_STATE.start_stratum_needed.store(false, Ordering::Relaxed);
|
||||
NODE_STATE
|
||||
.start_stratum_needed
|
||||
.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
thread::sleep(Self::STATS_UPDATE_DELAY);
|
||||
@@ -349,7 +352,9 @@ impl Node {
|
||||
fn reset_server_state(has_error: bool) {
|
||||
NODE_STATE.starting.store(false, Ordering::Relaxed);
|
||||
NODE_STATE.restart_needed.store(false, Ordering::Relaxed);
|
||||
NODE_STATE.start_stratum_needed.store(false, Ordering::Relaxed);
|
||||
NODE_STATE
|
||||
.start_stratum_needed
|
||||
.store(false, Ordering::Relaxed);
|
||||
NODE_STATE.stop_needed.store(false, Ordering::Relaxed);
|
||||
|
||||
// Reset stratum stats.
|
||||
@@ -585,15 +590,17 @@ fn start_node_server() -> Result<Server, Error> {
|
||||
} else {
|
||||
Node::TESTNET_DNS_SEEDS
|
||||
};
|
||||
let seed_port = if is_mainnet {
|
||||
3414
|
||||
} else {
|
||||
13414
|
||||
};
|
||||
let seed_port = if is_mainnet { 3414 } else { 13414 };
|
||||
for seed_addr in seed_list {
|
||||
let addr = format!("{}:{}", seed_addr, seed_port);
|
||||
if let Some(p) = PeersConfig::peer_to_addr(addr) {
|
||||
server_config.p2p_config.seeds.as_mut().unwrap().peers.push(p)
|
||||
server_config
|
||||
.p2p_config
|
||||
.seeds
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.peers
|
||||
.push(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -661,7 +668,9 @@ fn start_node_server() -> Result<Server, Error> {
|
||||
|
||||
// Put flag to start stratum server if autorun is available.
|
||||
if NodeConfig::is_stratum_autorun_enabled() {
|
||||
NODE_STATE.start_stratum_needed.store(true, Ordering::Relaxed);
|
||||
NODE_STATE
|
||||
.start_stratum_needed
|
||||
.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
// Reset an error.
|
||||
|
||||
+43
-22
@@ -21,8 +21,9 @@ use tokio_old::net::TcpListener;
|
||||
use tokio_old::runtime::Runtime;
|
||||
use tokio_util_old::codec::{Framed, LinesCodec};
|
||||
|
||||
use grin_util::RwLock;
|
||||
use chrono::prelude::Utc;
|
||||
use futures::future::{AbortHandle, abortable};
|
||||
use grin_util::RwLock;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
@@ -30,23 +31,22 @@ use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use futures::future::{abortable, AbortHandle};
|
||||
|
||||
use grin_chain::{self, SyncState};
|
||||
use grin_servers::common::stats::{StratumStats, WorkerStats};
|
||||
use grin_servers::common::types::StratumServerConfig;
|
||||
use grin_core::consensus::graph_weight;
|
||||
use grin_core::core::hash::Hashed;
|
||||
use grin_core::core::Block;
|
||||
use grin_core::core::hash::Hashed;
|
||||
use grin_core::global::min_edge_bits;
|
||||
use grin_core::{pow, ser};
|
||||
use grin_util::ToHex;
|
||||
use grin_servers::ServerTxPool;
|
||||
use grin_servers::common::stats::{StratumStats, WorkerStats};
|
||||
use grin_servers::common::types::StratumServerConfig;
|
||||
use grin_util::ToHex;
|
||||
|
||||
use log::{debug, error};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use crate::node::mine_block::get_block;
|
||||
use crate::wallet::WalletConfig;
|
||||
use log::{debug, error};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
type Tx = mpsc::UnboundedSender<String>;
|
||||
|
||||
@@ -212,7 +212,7 @@ pub struct StratumStopState {
|
||||
impl Default for StratumStopState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stopping: AtomicBool::new(false)
|
||||
stopping: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,7 +428,12 @@ impl Handler {
|
||||
// Return error status
|
||||
println!(
|
||||
"(Server ID: {}) Failed to validate solution at height {}, hash {}, edge_bits {}, nonce {}, job_id {}: cuckoo size too small",
|
||||
self.id, params.height, b.hash(), params.edge_bits, params.nonce, params.job_id,
|
||||
self.id,
|
||||
params.height,
|
||||
b.hash(),
|
||||
params.edge_bits,
|
||||
params.nonce,
|
||||
params.job_id,
|
||||
);
|
||||
self.workers
|
||||
.update_stats(worker_id, |worker_stats| worker_stats.num_rejected += 1);
|
||||
@@ -445,7 +450,14 @@ impl Handler {
|
||||
// Return error status
|
||||
println!(
|
||||
"(Server ID: {}) Share at height {}, hash {}, edge_bits {}, nonce {}, job_id {} rejected due to low difficulty: {}/{}",
|
||||
self.id, params.height, b.hash(), params.edge_bits, params.nonce, params.job_id, unscaled_share_difficulty, state.minimum_share_difficulty,
|
||||
self.id,
|
||||
params.height,
|
||||
b.hash(),
|
||||
params.edge_bits,
|
||||
params.nonce,
|
||||
params.job_id,
|
||||
unscaled_share_difficulty,
|
||||
state.minimum_share_difficulty,
|
||||
);
|
||||
self.workers
|
||||
.update_stats(worker_id, |worker_stats| worker_stats.num_rejected += 1);
|
||||
@@ -455,7 +467,9 @@ impl Handler {
|
||||
// If the difficulty is high enough, submit it (which also validates it)
|
||||
if scaled_share_difficulty >= state.current_difficulty {
|
||||
// This is a full solution, submit it to the network
|
||||
let res = self.chain.process_block(b.clone(), grin_chain::Options::MINE);
|
||||
let res = self
|
||||
.chain
|
||||
.process_block(b.clone(), grin_chain::Options::MINE);
|
||||
if let Err(e) = res {
|
||||
// Return error status
|
||||
println!(
|
||||
@@ -480,7 +494,8 @@ impl Handler {
|
||||
let stats = self.workers.get_stats(worker_id)?;
|
||||
println!(
|
||||
"(Server ID: {}) Solution Found for block {}, hash {} - Yay!!! Worker ID: {}, blocks found: {}, shares: {}",
|
||||
self.id, params.height,
|
||||
self.id,
|
||||
params.height,
|
||||
b.hash(),
|
||||
stats.id,
|
||||
stats.num_blocks_found,
|
||||
@@ -560,10 +575,12 @@ impl Handler {
|
||||
self.workers.broadcast(job_request_json);
|
||||
}
|
||||
|
||||
pub fn run(&self,
|
||||
pub fn run(
|
||||
&self,
|
||||
config: &StratumServerConfig,
|
||||
tx_pool: &ServerTxPool,
|
||||
stop_state: Arc<StratumStopState>) {
|
||||
stop_state: Arc<StratumStopState>,
|
||||
) {
|
||||
debug!("Run main loop");
|
||||
let mut deadline: i64 = 0;
|
||||
let mut head = self.chain.head().unwrap();
|
||||
@@ -609,7 +626,7 @@ impl Handler {
|
||||
tx_pool,
|
||||
state.current_key_id.clone(),
|
||||
wallet_listener_url,
|
||||
&stop_state
|
||||
&stop_state,
|
||||
) {
|
||||
// scaled difficulty
|
||||
state.current_difficulty =
|
||||
@@ -631,7 +648,8 @@ impl Handler {
|
||||
|
||||
// Update the mining stats
|
||||
self.workers.update_block_height(new_block.header.height);
|
||||
let difficulty = new_block.header.total_difficulty() - head.total_difficulty;
|
||||
let difficulty =
|
||||
new_block.header.total_difficulty() - head.total_difficulty;
|
||||
self.workers.update_network_difficulty(difficulty.to_num());
|
||||
self.workers.update_network_hashrate();
|
||||
|
||||
@@ -654,9 +672,11 @@ impl Handler {
|
||||
|
||||
// ----------------------------------------
|
||||
// Worker Factory Thread Function
|
||||
fn accept_connections(listen_addr: SocketAddr,
|
||||
fn accept_connections(
|
||||
listen_addr: SocketAddr,
|
||||
handler: Arc<Handler>,
|
||||
stop_state: Arc<StratumStopState>) {
|
||||
stop_state: Arc<StratumStopState>,
|
||||
) {
|
||||
debug!("Start tokio stratum server");
|
||||
let task = async move {
|
||||
let mut listener = TcpListener::bind(&listen_addr).await.unwrap_or_else(|_| {
|
||||
@@ -733,7 +753,6 @@ async fn check_stop_state(stop_state: Arc<StratumStopState>, handle: AbortHandle
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// Worker Object - a connected stratum client - a miner, pool, proxy, etc...
|
||||
|
||||
@@ -922,10 +941,12 @@ impl StratumServer {
|
||||
/// existing chain anytime required and sending that to the connected
|
||||
/// stratum miner, proxy, or pool, and accepts full solutions to
|
||||
/// be submitted.
|
||||
pub fn run_loop(&mut self,
|
||||
pub fn run_loop(
|
||||
&mut self,
|
||||
proof_size: usize,
|
||||
sync_state: Arc<SyncState>,
|
||||
stop_state: Arc<StratumStopState>) {
|
||||
stop_state: Arc<StratumStopState>,
|
||||
) {
|
||||
debug!(
|
||||
"(Server ID: {}) Starting stratum server with proof_size = {}",
|
||||
self.id, proof_size
|
||||
|
||||
+1
-1
@@ -24,5 +24,5 @@ pub enum NodeError {
|
||||
/// Configuration issue.
|
||||
Configuration,
|
||||
/// Unknown error.
|
||||
Unknown
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ use grin_core::global;
|
||||
use grin_core::global::ChainTypes;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::Settings;
|
||||
use crate::gui::views::Content;
|
||||
use crate::http::ReleaseInfo;
|
||||
use crate::node::NodeConfig;
|
||||
use crate::wallet::ConnectionsConfig;
|
||||
use crate::Settings;
|
||||
|
||||
/// Application update information.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
@@ -59,7 +59,8 @@ pub struct AppConfig {
|
||||
height: f32,
|
||||
|
||||
/// Position of the desktop window.
|
||||
x: Option<f32>, y: Option<f32>,
|
||||
x: Option<f32>,
|
||||
y: Option<f32>,
|
||||
|
||||
/// Locale code for i18n.
|
||||
lang: Option<String>,
|
||||
@@ -237,7 +238,7 @@ impl AppConfig {
|
||||
pub fn window_pos() -> Option<(f32, f32)> {
|
||||
let r_config = Settings::app_config_to_read();
|
||||
if r_config.x.is_some() && r_config.y.is_some() {
|
||||
return Some((r_config.x.unwrap(), r_config.y.unwrap()))
|
||||
return Some((r_config.x.unwrap(), r_config.y.unwrap()));
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -253,7 +254,7 @@ impl AppConfig {
|
||||
pub fn locale() -> Option<String> {
|
||||
let r_config = Settings::app_config_to_read();
|
||||
if r_config.lang.is_some() {
|
||||
return Some(r_config.lang.clone().unwrap())
|
||||
return Some(r_config.lang.clone().unwrap());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ use grin_config::ConfigError;
|
||||
use grin_core::global;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
@@ -42,7 +42,7 @@ pub struct Settings {
|
||||
/// Wallet connections configuration.
|
||||
conn_config: Arc<RwLock<ConnectionsConfig>>,
|
||||
/// Tor server configuration.
|
||||
tor_config: Arc<RwLock<TorConfig>>
|
||||
tor_config: Arc<RwLock<TorConfig>>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
@@ -190,12 +190,10 @@ impl Settings {
|
||||
let parsed = toml::from_str::<T>(file_content.as_str());
|
||||
match parsed {
|
||||
Ok(cfg) => Ok(cfg),
|
||||
Err(e) => {
|
||||
Err(ConfigError::ParseError(
|
||||
Err(e) => Err(ConfigError::ParseError(
|
||||
config_path.to_str().unwrap().to_string(),
|
||||
format!("{}", e),
|
||||
))
|
||||
}
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-20
@@ -12,8 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::Settings;
|
||||
use crate::tor::{TorBridge, TorProxy};
|
||||
@@ -40,7 +40,7 @@ pub struct TorConfig {
|
||||
snowflake: TorBridge,
|
||||
|
||||
/// Config version.
|
||||
ver: Option<i32>
|
||||
ver: Option<i32>,
|
||||
}
|
||||
|
||||
impl Default for TorConfig {
|
||||
@@ -54,11 +54,11 @@ impl Default for TorConfig {
|
||||
webtunnel,
|
||||
obfs4: TorBridge::Obfs4(
|
||||
TorBridge::DEFAULT_OBFS4_BIN_PATH.to_string(),
|
||||
TorBridge::DEFAULT_OBFS4_CONN_LINE.to_string()
|
||||
TorBridge::DEFAULT_OBFS4_CONN_LINE.to_string(),
|
||||
),
|
||||
snowflake: TorBridge::Snowflake(
|
||||
TorBridge::DEFAULT_SNOWFLAKE_BIN_PATH.to_string(),
|
||||
TorBridge::DEFAULT_SNOWFLAKE_CONN_LINE.to_string()
|
||||
TorBridge::DEFAULT_SNOWFLAKE_CONN_LINE.to_string(),
|
||||
),
|
||||
ver: Some(TOR_CONFIG_VERSION),
|
||||
}
|
||||
@@ -127,7 +127,7 @@ impl TorConfig {
|
||||
TorConfig::webtunnel_path()
|
||||
},
|
||||
serde_json::to_string(&TorBridge::DEFAULT_WEBTUNNEL_CONN_LINES)
|
||||
.unwrap_or(TorBridge::DEFAULT_WEBTUNNEL_CONN_LINE.to_string())
|
||||
.unwrap_or(TorBridge::DEFAULT_WEBTUNNEL_CONN_LINE.to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -151,15 +151,9 @@ impl TorConfig {
|
||||
if bridge.is_some() {
|
||||
let bridge = bridge.unwrap();
|
||||
match &bridge {
|
||||
TorBridge::Webtunnel(_, _) => {
|
||||
w_tor_config.webtunnel = bridge
|
||||
}
|
||||
TorBridge::Obfs4(_, _) => {
|
||||
w_tor_config.obfs4 = bridge
|
||||
}
|
||||
TorBridge::Snowflake(_, _) => {
|
||||
w_tor_config.snowflake = bridge
|
||||
}
|
||||
TorBridge::Webtunnel(_, _) => w_tor_config.webtunnel = bridge,
|
||||
TorBridge::Obfs4(_, _) => w_tor_config.obfs4 = bridge,
|
||||
TorBridge::Snowflake(_, _) => w_tor_config.snowflake = bridge,
|
||||
}
|
||||
}
|
||||
w_tor_config.save();
|
||||
@@ -195,12 +189,8 @@ impl TorConfig {
|
||||
w_config.proxy = proxy.clone();
|
||||
if let Some(p) = proxy {
|
||||
match p {
|
||||
TorProxy::SOCKS5(_) => {
|
||||
w_config.proxy_socks5 = p
|
||||
}
|
||||
TorProxy::HTTP(_) => {
|
||||
w_config.proxy_http = p
|
||||
}
|
||||
TorProxy::SOCKS5(_) => w_config.proxy_socks5 = p,
|
||||
TorProxy::HTTP(_) => w_config.proxy_http = p,
|
||||
}
|
||||
}
|
||||
w_config.save();
|
||||
|
||||
+1
-1
@@ -20,8 +20,8 @@ use std::task::{Context, Poll};
|
||||
|
||||
use arti_client::{DataStream, IntoTorAddr, TorClient};
|
||||
use hyper_tor::client::connect::{Connected, Connection};
|
||||
use hyper_tor::http::uri::Scheme;
|
||||
use hyper_tor::http::Uri;
|
||||
use hyper_tor::http::uri::Scheme;
|
||||
use hyper_tor::service::Service;
|
||||
use pin_project::pin_project;
|
||||
use thiserror::Error;
|
||||
|
||||
+69
-53
@@ -15,31 +15,31 @@
|
||||
use arti_client::config::pt::TransportConfigBuilder;
|
||||
use arti_client::config::{CfgPath, TorClientConfigBuilder};
|
||||
use arti_client::{TorClient, TorClientConfig};
|
||||
use bytes::Bytes;
|
||||
use curve25519_dalek::digest::Digest;
|
||||
use ed25519_dalek::hazmat::ExpandedSecretKey;
|
||||
use fs_mistrust::Mistrust;
|
||||
use grin_util::secp::SecretKey;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use lazy_static::lazy_static;
|
||||
use log::error;
|
||||
use parking_lot::RwLock;
|
||||
use safelog::DisplayRedacted;
|
||||
use sha2::Sha512;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
use std::{fs, thread};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use bytes::Bytes;
|
||||
use log::error;
|
||||
use safelog::DisplayRedacted;
|
||||
use tls_api::{TlsConnector as TlsConnectorTrait, TlsConnectorBuilder};
|
||||
use tls_api_native_tls::TlsConnector;
|
||||
use tor_hscrypto::pk::{HsIdKey, HsIdKeypair};
|
||||
use tor_hsrproxy::OnionServiceReverseProxy;
|
||||
use tor_hsrproxy::config::{
|
||||
Encapsulation, ProxyAction, ProxyConfigBuilder, ProxyPattern, ProxyRule, TargetAddr,
|
||||
};
|
||||
use tor_hsrproxy::OnionServiceReverseProxy;
|
||||
use tor_hsservice::config::OnionServiceConfigBuilder;
|
||||
use tor_hsservice::{
|
||||
HsIdKeypairSpecifier, HsIdPublicKeySpecifier, HsNickname, RunningOnionService,
|
||||
@@ -66,13 +66,21 @@ pub struct Tor {
|
||||
client_launching: Arc<AtomicBool>,
|
||||
|
||||
/// Mapping of running Onion services identifiers to proxy.
|
||||
run: Arc<RwLock<
|
||||
BTreeMap<String, (u16, SecretKey, Arc<RunningOnionService>, Arc<OnionServiceReverseProxy>)>
|
||||
>>,
|
||||
run: Arc<
|
||||
RwLock<
|
||||
BTreeMap<
|
||||
String,
|
||||
(
|
||||
u16,
|
||||
SecretKey,
|
||||
Arc<RunningOnionService>,
|
||||
Arc<OnionServiceReverseProxy>,
|
||||
),
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
/// Mapping of starting Onion services identifiers.
|
||||
start: Arc<RwLock<
|
||||
BTreeMap<String, (u16, SecretKey)>
|
||||
>>,
|
||||
start: Arc<RwLock<BTreeMap<String, (u16, SecretKey)>>>,
|
||||
/// Failed Onion services identifiers.
|
||||
fail: Arc<RwLock<BTreeSet<String>>>,
|
||||
/// Checking Onion services identifiers.
|
||||
@@ -101,8 +109,9 @@ impl Default for Tor {
|
||||
|
||||
impl Tor {
|
||||
/// Create Tor configuration returning unused bridges (exclude more than 2 to avoid stuck).
|
||||
fn build_config(bridges: Option<Vec<TorBridge>>)
|
||||
-> (TorClientConfig, Vec<TorBridge>, Vec<TorBridge>) {
|
||||
fn build_config(
|
||||
bridges: Option<Vec<TorBridge>>,
|
||||
) -> (TorClientConfig, Vec<TorBridge>, Vec<TorBridge>) {
|
||||
let mut builder = TorClientConfigBuilder::from_directories(
|
||||
TorConfig::state_path(),
|
||||
TorConfig::cache_path(),
|
||||
@@ -110,10 +119,10 @@ impl Tor {
|
||||
// Build bridges.
|
||||
let mut bridges = bridges.unwrap_or(vec![]);
|
||||
let max_two_bridges = if bridges.len() > 2 {
|
||||
let two_bridges = bridges.iter().take(2)
|
||||
.cloned()
|
||||
.collect::<Vec<TorBridge>>();
|
||||
bridges = bridges.iter().filter(|b| !two_bridges.contains(b))
|
||||
let two_bridges = bridges.iter().take(2).cloned().collect::<Vec<TorBridge>>();
|
||||
bridges = bridges
|
||||
.iter()
|
||||
.filter(|b| !two_bridges.contains(b))
|
||||
.cloned()
|
||||
.collect::<Vec<TorBridge>>();
|
||||
two_bridges
|
||||
@@ -144,14 +153,20 @@ impl Tor {
|
||||
let bootstrapping_t = bootstrapping.clone();
|
||||
let bootstrap_success_t = bootstrap_success.clone();
|
||||
let c = client.clone();
|
||||
client.runtime().spawn(async move {
|
||||
client
|
||||
.runtime()
|
||||
.spawn(async move {
|
||||
let task = c.bootstrap();
|
||||
// Bootstrap client with 60s timeout.
|
||||
if tokio::time::timeout(Duration::from_millis(60000), task).await.is_ok() {
|
||||
if tokio::time::timeout(Duration::from_millis(60000), task)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
bootstrap_success_t.store(true, Ordering::Relaxed);
|
||||
}
|
||||
bootstrapping_t.store(false, Ordering::Relaxed);
|
||||
}).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
// Wait client to finish bootstrap.
|
||||
while bootstrapping.load(Ordering::Relaxed) {
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
@@ -182,14 +197,15 @@ impl Tor {
|
||||
// Get initial bridges.
|
||||
let initial_bridges = if let Some(b) = TorConfig::get_bridge() {
|
||||
let lines_parse = serde_json::from_str::<Vec<String>>(&b.connection_line());
|
||||
let bridges = lines_parse.unwrap_or_else(|_| b.connection_line()
|
||||
.lines()
|
||||
.map(|l| l.to_string()).collect()
|
||||
).iter().map(|l| {
|
||||
let bridges = lines_parse
|
||||
.unwrap_or_else(|_| b.connection_line().lines().map(|l| l.to_string()).collect())
|
||||
.iter()
|
||||
.map(|l| {
|
||||
let mut bridge = b.clone();
|
||||
bridge.update_conn_line(l.clone());
|
||||
bridge
|
||||
}).collect::<Vec<TorBridge>>();
|
||||
})
|
||||
.collect::<Vec<TorBridge>>();
|
||||
Some(bridges)
|
||||
} else {
|
||||
None
|
||||
@@ -211,13 +227,12 @@ impl Tor {
|
||||
bridge.update_conn_line(l.clone());
|
||||
!used_bridges.contains(&bridge)
|
||||
});
|
||||
let lines_str = serde_json::to_string(&lines)
|
||||
.unwrap_or_else(|_| {
|
||||
let lines_str = serde_json::to_string(&lines).unwrap_or_else(|_| {
|
||||
TorConfig::default_webtunnel_bridge().connection_line()
|
||||
});
|
||||
TorBridge::save_bridge_conn_line(&b, lines_str);
|
||||
}
|
||||
Err(_) => {},
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
TOR_STATE.client_config.write().replace((c, config.clone()));
|
||||
@@ -234,7 +249,8 @@ impl Tor {
|
||||
if !default_attempt {
|
||||
default_attempt = true;
|
||||
// Launch client with default Webtunnel bridges if failed.
|
||||
let add_bridges = TorBridge::DEFAULT_WEBTUNNEL_CONN_LINES.iter()
|
||||
let add_bridges = TorBridge::DEFAULT_WEBTUNNEL_CONN_LINES
|
||||
.iter()
|
||||
.map(|b| TorBridge::Webtunnel(TorConfig::webtunnel_path(), b.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
config_bridges = Self::build_config(Some(add_bridges));
|
||||
@@ -249,7 +265,7 @@ impl Tor {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
TOR_STATE.client_launching.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
@@ -262,16 +278,11 @@ impl Tor {
|
||||
.body(Full::from(body))
|
||||
.unwrap();
|
||||
let res = match proxy {
|
||||
TorProxy::SOCKS5(url) => {
|
||||
HttpClient::send_socks_proxy(url, req).await
|
||||
}
|
||||
TorProxy::HTTP(url) => {
|
||||
HttpClient::send_http_proxy(url, req).await
|
||||
}
|
||||
TorProxy::SOCKS5(url) => HttpClient::send_socks_proxy(url, req).await,
|
||||
TorProxy::HTTP(url) => HttpClient::send_http_proxy(url, req).await,
|
||||
};
|
||||
match res {
|
||||
Ok(res) => {
|
||||
match res.into_body().collect().await {
|
||||
Ok(res) => match res.into_body().collect().await {
|
||||
Ok(r) => {
|
||||
let body = r.to_bytes().into();
|
||||
match String::from_utf8(body) {
|
||||
@@ -286,8 +297,7 @@ impl Tor {
|
||||
error!("Tor: POST with proxy, response parse error: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Tor: POST failed with proxy: {}", e);
|
||||
None
|
||||
@@ -377,7 +387,10 @@ impl Tor {
|
||||
// Stop all services saving keys to relaunch.
|
||||
let service_ids = {
|
||||
let r_services = TOR_STATE.run.read().clone();
|
||||
r_services.keys().map(|s| s.to_string()).collect::<Vec<String>>()
|
||||
r_services
|
||||
.keys()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
};
|
||||
let mut services: BTreeMap<String, (u16, SecretKey)> = TOR_STATE.start.read().clone();
|
||||
for id in service_ids.clone() {
|
||||
@@ -486,9 +499,7 @@ impl Tor {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(bridge.binary_path())
|
||||
.unwrap()
|
||||
.permissions();
|
||||
let mut perms = fs::metadata(bridge.binary_path()).unwrap().permissions();
|
||||
let mode = perms.mode() | 0o100;
|
||||
perms.set_mode(mode);
|
||||
fs::set_permissions(bridge.binary_path(), perms).unwrap_or_default();
|
||||
@@ -595,8 +606,8 @@ impl Tor {
|
||||
}
|
||||
let client = client_config.unwrap().0.isolated_client();
|
||||
let conn = ArtiHttpConnector::new(client, tls_conn);
|
||||
let http = hyper_tor::Client::builder()
|
||||
.build::<_, hyper_tor::Body>(conn);
|
||||
let http =
|
||||
hyper_tor::Client::builder().build::<_, hyper_tor::Body>(conn);
|
||||
let uri = hyper_tor::Uri::from_str(url.clone().as_str()).unwrap();
|
||||
let check = http.get(uri.clone());
|
||||
// Setup error callback.
|
||||
@@ -634,8 +645,10 @@ impl Tor {
|
||||
if on_error(&service_id) {
|
||||
break;
|
||||
}
|
||||
error!("Tor check failed: {} for {}, errors: {}/{}",
|
||||
e, service_id, errors_count, MAX_ERRORS);
|
||||
error!(
|
||||
"Tor check failed: {} for {}, errors: {}/{}",
|
||||
e, service_id, errors_count, MAX_ERRORS
|
||||
);
|
||||
// Check again after 5s.
|
||||
Duration::from_millis(5000)
|
||||
}
|
||||
@@ -645,8 +658,10 @@ impl Tor {
|
||||
if on_error(&service_id) {
|
||||
break;
|
||||
}
|
||||
error!("Tor check for {} timeout, errors: {}/{}",
|
||||
&service_id, errors_count, MAX_ERRORS);
|
||||
error!(
|
||||
"Tor check for {} timeout, errors: {}/{}",
|
||||
&service_id, errors_count, MAX_ERRORS
|
||||
);
|
||||
// Check again after 5s.
|
||||
Duration::from_millis(5000)
|
||||
}
|
||||
@@ -665,7 +680,8 @@ impl Tor {
|
||||
addr: SocketAddr,
|
||||
request: S,
|
||||
nickname: HsNickname,
|
||||
) -> Arc<OnionServiceReverseProxy> where
|
||||
) -> Arc<OnionServiceReverseProxy>
|
||||
where
|
||||
S: futures::Stream<Item = tor_hsservice::RendRequest> + Unpin + Send + 'static,
|
||||
{
|
||||
let id = nickname.to_string();
|
||||
@@ -737,14 +753,14 @@ impl Tor {
|
||||
HsIdKey::from(expanded_kp.public().clone()),
|
||||
&HsIdPublicKeySpecifier::new(hs_nickname.clone()),
|
||||
KeystoreSelector::Primary,
|
||||
true
|
||||
true,
|
||||
)?;
|
||||
|
||||
key_manager.insert(
|
||||
HsIdKeypair::from(expanded_kp),
|
||||
&HsIdKeypairSpecifier::new(hs_nickname.clone()),
|
||||
KeystoreSelector::Primary,
|
||||
true
|
||||
true,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+16
-15
@@ -12,9 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::tor::TorConfig;
|
||||
use egui::os;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use crate::tor::TorConfig;
|
||||
|
||||
/// Tor connection proxy type.
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
@@ -22,7 +22,7 @@ pub enum TorProxy {
|
||||
/// SOCKS5 proxy URL.
|
||||
SOCKS5(String),
|
||||
/// HTTP proxy URL.
|
||||
HTTP(String)
|
||||
HTTP(String),
|
||||
}
|
||||
|
||||
impl TorProxy {
|
||||
@@ -35,7 +35,7 @@ impl TorProxy {
|
||||
pub fn url(&self) -> String {
|
||||
match self {
|
||||
TorProxy::SOCKS5(url) => url.into(),
|
||||
TorProxy::HTTP(url) => url.into()
|
||||
TorProxy::HTTP(url) => url.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ pub enum TorBridge {
|
||||
/// Obfs4 bridge with binary path and connection line.
|
||||
Obfs4(String, String),
|
||||
/// Snowflake bridge with binary path and connection line.
|
||||
Snowflake(String, String)
|
||||
Snowflake(String, String),
|
||||
}
|
||||
|
||||
impl TorBridge {
|
||||
@@ -96,20 +96,25 @@ impl TorBridge {
|
||||
pub fn binary_path(&self) -> String {
|
||||
let is_android = os::OperatingSystem::from_target_os() == os::OperatingSystem::Android;
|
||||
match self {
|
||||
TorBridge::Webtunnel(path, _) => if is_android {
|
||||
TorBridge::Webtunnel(path, _) => {
|
||||
if is_android {
|
||||
TorConfig::webtunnel_path()
|
||||
} else {
|
||||
path.clone()
|
||||
},
|
||||
}
|
||||
}
|
||||
TorBridge::Obfs4(path, _) => path.clone(),
|
||||
TorBridge::Snowflake(path, _) => path.clone()
|
||||
TorBridge::Snowflake(path, _) => path.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get bridge client binary name.
|
||||
pub fn binary_name(&self) -> String {
|
||||
let path = self.binary_path();
|
||||
path.split(std::path::MAIN_SEPARATOR_STR).last().unwrap().to_string()
|
||||
path.split(std::path::MAIN_SEPARATOR_STR)
|
||||
.last()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Get bridge client connection line.
|
||||
@@ -117,7 +122,7 @@ impl TorBridge {
|
||||
match self {
|
||||
TorBridge::Webtunnel(_, line) => line.clone(),
|
||||
TorBridge::Obfs4(_, line) => line.clone(),
|
||||
TorBridge::Snowflake(_, line) => line.clone()
|
||||
TorBridge::Snowflake(_, line) => line.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,14 +157,10 @@ impl TorBridge {
|
||||
TorConfig::save_bridge(Some(TorBridge::Webtunnel(path.into(), line)));
|
||||
}
|
||||
TorBridge::Obfs4(path, _) => {
|
||||
TorConfig::save_bridge(
|
||||
Some(TorBridge::Obfs4(path.into(), line))
|
||||
);
|
||||
TorConfig::save_bridge(Some(TorBridge::Obfs4(path.into(), line)));
|
||||
}
|
||||
TorBridge::Snowflake(path, _) => {
|
||||
TorConfig::save_bridge(
|
||||
Some(TorBridge::Snowflake(path.into(), line))
|
||||
);
|
||||
TorConfig::save_bridge(Some(TorBridge::Snowflake(path.into(), line)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ use std::path::PathBuf;
|
||||
use std::string::ToString;
|
||||
|
||||
use grin_core::global::ChainTypes;
|
||||
use grin_wallet_libwallet::{Slate};
|
||||
use grin_wallet_libwallet::Slate;
|
||||
use rand::Rng;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::{AppConfig, Settings};
|
||||
use crate::wallet::ConnectionsConfig;
|
||||
use crate::wallet::types::{ConnectionMethod, WalletTx};
|
||||
use crate::{AppConfig, Settings};
|
||||
|
||||
/// Wallet configuration.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
@@ -53,7 +53,7 @@ pub struct WalletConfig {
|
||||
pub data_path: Option<String>,
|
||||
|
||||
/// Config version.
|
||||
ver: Option<i32>
|
||||
ver: Option<i32>,
|
||||
}
|
||||
|
||||
/// Base wallets directory name.
|
||||
@@ -96,7 +96,7 @@ impl WalletConfig {
|
||||
name,
|
||||
ext_conn_id: match conn_method {
|
||||
ConnectionMethod::Integrated => None,
|
||||
ConnectionMethod::External(id, _) => Some(*id)
|
||||
ConnectionMethod::External(id, _) => Some(*id),
|
||||
},
|
||||
min_confirmations: MIN_CONFIRMATIONS_DEFAULT,
|
||||
use_dandelion: Some(true),
|
||||
@@ -116,7 +116,7 @@ impl WalletConfig {
|
||||
config_path.push(CONFIG_FILE_NAME);
|
||||
if let Ok(mut config) = Settings::read_from_file::<WalletConfig>(config_path) {
|
||||
config.migrate();
|
||||
return Some(config)
|
||||
return Some(config);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
use grin_core::global::ChainTypes;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::{AppConfig, Settings};
|
||||
use crate::wallet::ExternalConnection;
|
||||
use crate::{AppConfig, Settings};
|
||||
|
||||
/// Wallet connections configuration.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
@@ -24,7 +24,7 @@ pub struct ConnectionsConfig {
|
||||
/// Network type for connections.
|
||||
chain_type: ChainTypes,
|
||||
/// URLs of external connections for wallets.
|
||||
external: Vec<ExternalConnection>
|
||||
external: Vec<ExternalConnection>,
|
||||
}
|
||||
|
||||
impl ConnectionsConfig {
|
||||
@@ -63,9 +63,11 @@ impl ConnectionsConfig {
|
||||
/// Save [`ExternalConnection`] in configuration.
|
||||
pub fn add_ext_conn(conn: ExternalConnection) {
|
||||
let mut w_config = Settings::conn_config_to_update();
|
||||
if let Some(pos) = w_config.external.iter().position(|c| {
|
||||
c.id == conn.id || c.url == conn.url
|
||||
}) {
|
||||
if let Some(pos) = w_config
|
||||
.external
|
||||
.iter()
|
||||
.position(|c| c.id == conn.id || c.url == conn.url)
|
||||
{
|
||||
w_config.external.remove(pos);
|
||||
w_config.external.insert(pos, conn);
|
||||
} else {
|
||||
@@ -100,7 +102,12 @@ impl ConnectionsConfig {
|
||||
/// Remove [`ExternalConnection`] with provided identifier.
|
||||
pub fn remove_ext_conn(id: i64) {
|
||||
let mut w_config = Settings::conn_config_to_update();
|
||||
w_config.external = w_config.external.iter().filter(|c| c.id != id).cloned().collect();
|
||||
w_config.external = w_config
|
||||
.external
|
||||
.iter()
|
||||
.filter(|c| c.id != id)
|
||||
.cloned()
|
||||
.collect();
|
||||
w_config.save();
|
||||
}
|
||||
}
|
||||
@@ -42,14 +42,14 @@ pub struct ExternalConnection {
|
||||
const DEFAULT_MAIN_URLS: [&'static str; 3] = [
|
||||
"https://main.gri.mw",
|
||||
"https://grincoin.org",
|
||||
"https://mainnet.grinffindor.org"
|
||||
"https://mainnet.grinffindor.org",
|
||||
];
|
||||
|
||||
/// Default external node URL for main network.
|
||||
const DEFAULT_TEST_URLS: [&'static str; 3] = [
|
||||
"https://test.gri.mw",
|
||||
"https://testnet.grincoin.org",
|
||||
"https://testnet.grinffindor.org"
|
||||
"https://testnet.grinffindor.org",
|
||||
];
|
||||
|
||||
impl ExternalConnection {
|
||||
@@ -57,17 +57,18 @@ impl ExternalConnection {
|
||||
pub fn default(chain_type: &ChainTypes) -> Vec<ExternalConnection> {
|
||||
let urls = match chain_type {
|
||||
ChainTypes::Mainnet => DEFAULT_MAIN_URLS.to_vec(),
|
||||
_ => DEFAULT_TEST_URLS.to_vec()
|
||||
_ => DEFAULT_TEST_URLS.to_vec(),
|
||||
};
|
||||
urls.iter().enumerate().map(|(index, url)| {
|
||||
ExternalConnection {
|
||||
urls.iter()
|
||||
.enumerate()
|
||||
.map(|(index, url)| ExternalConnection {
|
||||
id: index as i64,
|
||||
url: url.to_string(),
|
||||
username: Some("grin".to_string()),
|
||||
secret: None,
|
||||
available: None,
|
||||
}
|
||||
}).collect::<Vec<ExternalConnection>>()
|
||||
})
|
||||
.collect::<Vec<ExternalConnection>>()
|
||||
}
|
||||
|
||||
/// Create new external connection.
|
||||
@@ -140,16 +141,16 @@ fn check_ext_conn(conn: &ExternalConnection, ui_ctx: &egui::Context) {
|
||||
// Setup secret key auth.
|
||||
if let Some(key) = conn.secret {
|
||||
let username = conn.username.unwrap_or("grin".to_string());
|
||||
let basic_auth = format!(
|
||||
"Basic {}",
|
||||
to_base64(&format!("{}:{}", username, key))
|
||||
);
|
||||
req_setup = req_setup
|
||||
.header(hyper::header::AUTHORIZATION, basic_auth.clone());
|
||||
let basic_auth =
|
||||
format!("Basic {}", to_base64(&format!("{}:{}", username, key)));
|
||||
req_setup =
|
||||
req_setup.header(hyper::header::AUTHORIZATION, basic_auth.clone());
|
||||
}
|
||||
let req: hyper::Request<Full<Bytes>> = req_setup.body(Full::from(
|
||||
r#"{"id":1,"jsonrpc":"2.0","method":"get_version","params":{} }"#)
|
||||
).unwrap();
|
||||
let req: hyper::Request<Full<Bytes>> = req_setup
|
||||
.body(Full::from(
|
||||
r#"{"id":1,"jsonrpc":"2.0","method":"get_version","params":{} }"#,
|
||||
))
|
||||
.unwrap();
|
||||
// Send request.
|
||||
match HttpClient::send(req).await {
|
||||
Ok(res) => {
|
||||
@@ -157,7 +158,7 @@ fn check_ext_conn(conn: &ExternalConnection, ui_ctx: &egui::Context) {
|
||||
// Available on 200 HTTP status code.
|
||||
ConnectionsConfig::update_ext_conn_status(conn.id, Some(status == 200));
|
||||
}
|
||||
Err(_) => ConnectionsConfig::update_ext_conn_status(conn.id, Some(false))
|
||||
Err(_) => ConnectionsConfig::update_ext_conn_status(conn.id, Some(false)),
|
||||
}
|
||||
} else {
|
||||
ConnectionsConfig::update_ext_conn_status(conn.id, Some(false));
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ impl Default for WalletList {
|
||||
Self {
|
||||
main_list,
|
||||
test_list,
|
||||
selected: None
|
||||
selected: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
-20
@@ -38,7 +38,13 @@ impl Default for Mnemonic {
|
||||
let mode = PhraseMode::Generate;
|
||||
let words = Self::generate_words(&mode, &size);
|
||||
let confirmation = Self::empty_words(&size);
|
||||
Self { mode, size, words, confirmation, valid: true }
|
||||
Self {
|
||||
mode,
|
||||
size,
|
||||
words,
|
||||
confirmation,
|
||||
valid: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +85,9 @@ impl Mnemonic {
|
||||
&self.words
|
||||
}
|
||||
}
|
||||
PhraseMode::Import => &self.words
|
||||
}.clone()
|
||||
PhraseMode::Import => &self.words,
|
||||
}
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Check if current phrase is valid.
|
||||
@@ -90,7 +97,8 @@ impl Mnemonic {
|
||||
|
||||
/// Get phrase from words.
|
||||
pub fn get_phrase(&self) -> String {
|
||||
self.words.iter()
|
||||
self.words
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| if i == 0 { "" } else { " " }.to_owned() + &x.text)
|
||||
.collect::<String>()
|
||||
@@ -105,20 +113,16 @@ impl Mnemonic {
|
||||
for _ in 0..size.entropy_size() {
|
||||
entropy.push(rng.random());
|
||||
}
|
||||
from_entropy(&entropy).unwrap()
|
||||
from_entropy(&entropy)
|
||||
.unwrap()
|
||||
.split(" ")
|
||||
.map(|s| {
|
||||
let text = s.to_string();
|
||||
PhraseWord {
|
||||
text,
|
||||
valid: true,
|
||||
}
|
||||
PhraseWord { text, valid: true }
|
||||
})
|
||||
.collect::<Vec<PhraseWord>>()
|
||||
},
|
||||
PhraseMode::Import => {
|
||||
Self::empty_words(size)
|
||||
}
|
||||
PhraseMode::Import => Self::empty_words(size),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,15 +160,24 @@ impl Mnemonic {
|
||||
&mut self.words
|
||||
};
|
||||
words.remove(index);
|
||||
words.insert(index, PhraseWord { text: word.to_owned(), valid: true });
|
||||
words.insert(
|
||||
index,
|
||||
PhraseWord {
|
||||
text: word.to_owned(),
|
||||
valid: true,
|
||||
},
|
||||
);
|
||||
|
||||
// Validate phrase when all words are entered.
|
||||
let mut has_empty = false;
|
||||
let _: Vec<_> = words.iter().map(|w| {
|
||||
let _: Vec<_> = words
|
||||
.iter()
|
||||
.map(|w| {
|
||||
if w.text.is_empty() {
|
||||
has_empty = true;
|
||||
}
|
||||
}).collect();
|
||||
})
|
||||
.collect();
|
||||
if !has_empty {
|
||||
self.valid = to_entropy(self.get_phrase().as_str()).is_ok();
|
||||
}
|
||||
@@ -175,13 +188,13 @@ impl Mnemonic {
|
||||
pub fn get(&self, index: usize) -> Option<PhraseWord> {
|
||||
let words = match self.mode {
|
||||
PhraseMode::Generate => &self.confirmation,
|
||||
PhraseMode::Import => &self.words
|
||||
PhraseMode::Import => &self.words,
|
||||
};
|
||||
let word = words.get(index);
|
||||
if let Some(w) = word {
|
||||
return Some(PhraseWord {
|
||||
text: w.text.clone(),
|
||||
valid: w.valid
|
||||
valid: w.valid,
|
||||
});
|
||||
}
|
||||
None
|
||||
@@ -223,18 +236,21 @@ impl Mnemonic {
|
||||
pub fn has_empty_or_invalid(&self) -> bool {
|
||||
let words = match self.mode {
|
||||
PhraseMode::Generate => &self.confirmation,
|
||||
PhraseMode::Import => &self.words
|
||||
PhraseMode::Import => &self.words,
|
||||
};
|
||||
let mut has_empty = false;
|
||||
let mut has_invalid = false;
|
||||
let _: Vec<_> = words.iter().map(|w| {
|
||||
let _: Vec<_> = words
|
||||
.iter()
|
||||
.map(|w| {
|
||||
if w.text.is_empty() {
|
||||
has_empty = true;
|
||||
}
|
||||
if !w.valid {
|
||||
has_invalid = true;
|
||||
}
|
||||
}).collect();
|
||||
})
|
||||
.collect();
|
||||
has_empty || has_invalid
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -32,5 +32,5 @@ pub use list::*;
|
||||
mod utils;
|
||||
pub use utils::WalletUtils;
|
||||
|
||||
pub mod store;
|
||||
mod seed;
|
||||
pub mod store;
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
use core::num::NonZeroU32;
|
||||
use grin_util::{ToHex, ZeroingString};
|
||||
use grin_wallet_impls::Error;
|
||||
use rand::{rng, Rng};
|
||||
use rand::{Rng, rng};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::fs::File;
|
||||
|
||||
+21
-11
@@ -12,10 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use rkv::backend::{SafeMode, SafeModeDatabase, SafeModeEnvironment};
|
||||
use rkv::{Manager, Rkv, SingleStore, StoreOptions, Value};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
/// Transaction height storage.
|
||||
pub struct TxHeightStore {
|
||||
@@ -23,23 +23,27 @@ pub struct TxHeightStore {
|
||||
/// Confirmed heights.
|
||||
confirmed: SingleStore<SafeModeDatabase>,
|
||||
/// Broadcasting heights.
|
||||
broadcasting: SingleStore<SafeModeDatabase>
|
||||
broadcasting: SingleStore<SafeModeDatabase>,
|
||||
}
|
||||
|
||||
impl TxHeightStore {
|
||||
/// Create new transaction height storage from provided directory.
|
||||
pub fn new(dir: PathBuf) -> Self {
|
||||
let mut manager = Manager::<SafeModeEnvironment>::singleton().write().unwrap();
|
||||
let created_arc = manager.get_or_create(dir.as_path(), Rkv::new::<SafeMode>).unwrap();
|
||||
let created_arc = manager
|
||||
.get_or_create(dir.as_path(), Rkv::new::<SafeMode>)
|
||||
.unwrap();
|
||||
let env = created_arc.clone();
|
||||
let k = created_arc.read().unwrap();
|
||||
|
||||
let confirmed = k.open_single("tx_height", StoreOptions::create()).unwrap();
|
||||
let broadcasting = k.open_single("broadcast_tx_height", StoreOptions::create()).unwrap();
|
||||
let broadcasting = k
|
||||
.open_single("broadcast_tx_height", StoreOptions::create())
|
||||
.unwrap();
|
||||
Self {
|
||||
env,
|
||||
confirmed,
|
||||
broadcasting
|
||||
broadcasting,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +55,7 @@ impl TxHeightStore {
|
||||
if let Some(height) = value {
|
||||
return match height {
|
||||
Value::U64(v) => Some(v),
|
||||
_ => None
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
return None;
|
||||
@@ -63,7 +67,9 @@ impl TxHeightStore {
|
||||
pub fn write_tx_height(&self, slate_id: &String, height: u64) {
|
||||
let env = self.env.read().unwrap();
|
||||
let mut writer = env.write().unwrap();
|
||||
self.confirmed.put(&mut writer, slate_id, &Value::U64(height)).unwrap();
|
||||
self.confirmed
|
||||
.put(&mut writer, slate_id, &Value::U64(height))
|
||||
.unwrap();
|
||||
writer.commit().unwrap();
|
||||
}
|
||||
|
||||
@@ -75,7 +81,7 @@ impl TxHeightStore {
|
||||
if let Some(height) = value {
|
||||
return match height {
|
||||
Value::U64(v) => Some(v),
|
||||
_ => None
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
return None;
|
||||
@@ -87,7 +93,9 @@ impl TxHeightStore {
|
||||
pub fn write_broadcasting_height(&self, slate_id: &String, height: u64) {
|
||||
let env = self.env.read().unwrap();
|
||||
let mut writer = env.write().unwrap();
|
||||
self.broadcasting.put(&mut writer, slate_id, &Value::U64(height)).unwrap();
|
||||
self.broadcasting
|
||||
.put(&mut writer, slate_id, &Value::U64(height))
|
||||
.unwrap();
|
||||
writer.commit().unwrap();
|
||||
}
|
||||
|
||||
@@ -95,7 +103,9 @@ impl TxHeightStore {
|
||||
pub fn delete_broadcasting_height(&self, slate_id: &String) {
|
||||
let env = self.env.read().unwrap();
|
||||
let mut writer = env.write().unwrap();
|
||||
self.broadcasting.delete(&mut writer, slate_id).unwrap_or_default();
|
||||
self.broadcasting
|
||||
.delete(&mut writer, slate_id)
|
||||
.unwrap_or_default();
|
||||
writer.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
+60
-51
@@ -15,7 +15,10 @@
|
||||
use grin_keychain::ExtKeychain;
|
||||
use grin_util::Mutex;
|
||||
use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient};
|
||||
use grin_wallet_libwallet::{Error, PaymentProof, Slate, SlateState, SlatepackAddress, TxLogEntry, TxLogEntryType, WalletInfo, WalletInst};
|
||||
use grin_wallet_libwallet::{
|
||||
Error, PaymentProof, Slate, SlateState, SlatepackAddress, TxLogEntry, TxLogEntryType,
|
||||
WalletInfo, WalletInst,
|
||||
};
|
||||
use grin_wallet_util::OnionV3Address;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
@@ -38,12 +41,18 @@ pub enum PhraseMode {
|
||||
/// Generate new mnemonic phrase.
|
||||
Generate,
|
||||
/// Import existing mnemonic phrase.
|
||||
Import
|
||||
Import,
|
||||
}
|
||||
|
||||
/// Mnemonic phrase size based on entropy.
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum PhraseSize { Words12, Words15, Words18, Words21, Words24 }
|
||||
pub enum PhraseSize {
|
||||
Words12,
|
||||
Words15,
|
||||
Words18,
|
||||
Words21,
|
||||
Words24,
|
||||
}
|
||||
|
||||
impl PhraseSize {
|
||||
pub const VALUES: [PhraseSize; 5] = [
|
||||
@@ -51,7 +60,7 @@ impl PhraseSize {
|
||||
PhraseSize::Words15,
|
||||
PhraseSize::Words18,
|
||||
PhraseSize::Words21,
|
||||
PhraseSize::Words24
|
||||
PhraseSize::Words24,
|
||||
];
|
||||
|
||||
/// Get entropy value.
|
||||
@@ -61,7 +70,7 @@ impl PhraseSize {
|
||||
PhraseSize::Words15 => 15,
|
||||
PhraseSize::Words18 => 18,
|
||||
PhraseSize::Words21 => 21,
|
||||
PhraseSize::Words24 => 24
|
||||
PhraseSize::Words24 => 24,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +81,7 @@ impl PhraseSize {
|
||||
PhraseSize::Words15 => 20,
|
||||
PhraseSize::Words18 => 24,
|
||||
PhraseSize::Words21 => 28,
|
||||
PhraseSize::Words24 => 32
|
||||
PhraseSize::Words24 => 32,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,24 +89,12 @@ impl PhraseSize {
|
||||
pub fn type_for_value(count: usize) -> Option<PhraseSize> {
|
||||
if Self::is_correct_count(count) {
|
||||
match count {
|
||||
12 => {
|
||||
Some(PhraseSize::Words12)
|
||||
}
|
||||
15 => {
|
||||
Some(PhraseSize::Words15)
|
||||
}
|
||||
18 => {
|
||||
Some(PhraseSize::Words18)
|
||||
}
|
||||
21 => {
|
||||
Some(PhraseSize::Words21)
|
||||
}
|
||||
24 => {
|
||||
Some(PhraseSize::Words24)
|
||||
}
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
12 => Some(PhraseSize::Words12),
|
||||
15 => Some(PhraseSize::Words15),
|
||||
18 => Some(PhraseSize::Words18),
|
||||
21 => Some(PhraseSize::Words21),
|
||||
24 => Some(PhraseSize::Words24),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@@ -116,7 +113,7 @@ pub enum ConnectionMethod {
|
||||
/// Integrated node.
|
||||
Integrated,
|
||||
/// External node, contains connection identifier and URL.
|
||||
External(i64, String)
|
||||
External(i64, String),
|
||||
}
|
||||
|
||||
/// Wallet instance type.
|
||||
@@ -141,7 +138,7 @@ pub struct WalletAccount {
|
||||
/// Account label.
|
||||
pub label: String,
|
||||
/// Account BIP32 derivation path.
|
||||
pub path: String
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
/// Wallet balance and transactions data.
|
||||
@@ -204,7 +201,11 @@ impl WalletData {
|
||||
/// Wallet transaction action.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum WalletTxAction {
|
||||
Cancelling, Finalizing, Posting, SendingTor, Deleting
|
||||
Cancelling,
|
||||
Finalizing,
|
||||
Posting,
|
||||
SendingTor,
|
||||
Deleting,
|
||||
}
|
||||
|
||||
/// Wallet transaction data.
|
||||
@@ -231,18 +232,20 @@ pub struct WalletTx {
|
||||
/// Action on transaction.
|
||||
pub action: Option<WalletTxAction>,
|
||||
/// Action result error.
|
||||
pub action_error: Option<Error>
|
||||
pub action_error: Option<Error>,
|
||||
}
|
||||
|
||||
impl WalletTx {
|
||||
/// Create new wallet transaction.
|
||||
pub fn new(tx: TxLogEntry,
|
||||
pub fn new(
|
||||
tx: TxLogEntry,
|
||||
proof: Option<PaymentProof>,
|
||||
wallet: &Wallet,
|
||||
height: Option<u64>,
|
||||
broadcasting_height: Option<u64>,
|
||||
action: Option<WalletTxAction>,
|
||||
action_error: Option<Error>) -> Self {
|
||||
action_error: Option<Error>,
|
||||
) -> Self {
|
||||
let amount = if tx.amount_debited > tx.amount_credited {
|
||||
tx.amount_debited - tx.amount_credited
|
||||
} else {
|
||||
@@ -286,7 +289,7 @@ impl WalletTx {
|
||||
slate.id = tx.tx_slate_id.unwrap();
|
||||
slate.state = match tx.tx_type {
|
||||
TxLogEntryType::TxReceived => SlateState::Invoice3,
|
||||
_ => SlateState::Standard3
|
||||
_ => SlateState::Standard3,
|
||||
};
|
||||
// Transaction was finalized.
|
||||
if wallet.slatepack_exists(&slate) {
|
||||
@@ -295,7 +298,7 @@ impl WalletTx {
|
||||
slate.id = tx.tx_slate_id.unwrap();
|
||||
slate.state = match tx.tx_type {
|
||||
TxLogEntryType::TxReceived => SlateState::Standard2,
|
||||
_ => SlateState::Invoice2
|
||||
_ => SlateState::Invoice2,
|
||||
};
|
||||
// Transaction signed to be finalized.
|
||||
if wallet.slatepack_exists(&slate) {
|
||||
@@ -304,7 +307,7 @@ impl WalletTx {
|
||||
// Transaction just was created.
|
||||
slate.state = match tx.tx_type {
|
||||
TxLogEntryType::TxReceived => SlateState::Invoice1,
|
||||
_ => SlateState::Standard1
|
||||
_ => SlateState::Standard1,
|
||||
};
|
||||
if wallet.slatepack_exists(&slate) {
|
||||
self.state = slate.state;
|
||||
@@ -317,18 +320,20 @@ impl WalletTx {
|
||||
|
||||
/// Check if transactions can be finalized after receiving response.
|
||||
pub fn can_finalize(&self) -> bool {
|
||||
!self.cancelling() && !self.data.confirmed &&
|
||||
(!self.sending_tor() || self.action_error.is_some()) &&
|
||||
(self.data.tx_type == TxLogEntryType::TxSent ||
|
||||
self.data.tx_type == TxLogEntryType::TxReceived) &&
|
||||
(self.state == SlateState::Invoice1 || self.state == SlateState::Standard1)
|
||||
!self.cancelling()
|
||||
&& !self.data.confirmed
|
||||
&& (!self.sending_tor() || self.action_error.is_some())
|
||||
&& (self.data.tx_type == TxLogEntryType::TxSent
|
||||
|| self.data.tx_type == TxLogEntryType::TxReceived)
|
||||
&& (self.state == SlateState::Invoice1 || self.state == SlateState::Standard1)
|
||||
}
|
||||
|
||||
/// Check if transaction was finalized.
|
||||
pub fn finalized(&self) -> bool {
|
||||
(self.data.tx_type == TxLogEntryType::TxSent ||
|
||||
self.data.tx_type == TxLogEntryType::TxReceived) &&
|
||||
self.state == SlateState::Invoice3 || self.state == SlateState::Standard3
|
||||
(self.data.tx_type == TxLogEntryType::TxSent
|
||||
|| self.data.tx_type == TxLogEntryType::TxReceived)
|
||||
&& self.state == SlateState::Invoice3
|
||||
|| self.state == SlateState::Standard3
|
||||
}
|
||||
|
||||
/// Check if transaction is sending over Tor.
|
||||
@@ -357,16 +362,18 @@ impl WalletTx {
|
||||
|
||||
/// Check if transaction can be cancelled.
|
||||
pub fn can_cancel(&self) -> bool {
|
||||
!self.cancelling() && !self.data.confirmed && !self.broadcasting() &&
|
||||
(!self.sending_tor() || self.action_error.is_some()) &&
|
||||
self.data.tx_type != TxLogEntryType::TxReceivedCancelled &&
|
||||
self.data.tx_type != TxLogEntryType::TxSentCancelled
|
||||
!self.cancelling()
|
||||
&& !self.data.confirmed
|
||||
&& !self.broadcasting()
|
||||
&& (!self.sending_tor() || self.action_error.is_some())
|
||||
&& self.data.tx_type != TxLogEntryType::TxReceivedCancelled
|
||||
&& self.data.tx_type != TxLogEntryType::TxSentCancelled
|
||||
}
|
||||
|
||||
/// Check if transaction was canceled.
|
||||
pub fn cancelled(&self) -> bool {
|
||||
self.data.tx_type == TxLogEntryType::TxReceivedCancelled ||
|
||||
self.data.tx_type == TxLogEntryType::TxSentCancelled
|
||||
self.data.tx_type == TxLogEntryType::TxReceivedCancelled
|
||||
|| self.data.tx_type == TxLogEntryType::TxSentCancelled
|
||||
}
|
||||
|
||||
/// Check if transaction is finalizing.
|
||||
@@ -383,9 +390,11 @@ impl WalletTx {
|
||||
self.action_error.is_some() && a != &WalletTxAction::Cancelling
|
||||
} else {
|
||||
// Can resend over Tor.
|
||||
!self.data.confirmed && !self.sending_tor() &&
|
||||
Tor::is_service_running(&wallet.identifier()) && !self.broadcasting() &&
|
||||
self.receiver.is_some()
|
||||
!self.data.confirmed
|
||||
&& !self.sending_tor()
|
||||
&& Tor::is_service_running(&wallet.identifier())
|
||||
&& !self.broadcasting()
|
||||
&& self.receiver.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,5 +463,5 @@ pub enum WalletTask {
|
||||
Cancel(u32),
|
||||
/// Delete transaction.
|
||||
/// * tx id
|
||||
Delete(u32)
|
||||
Delete(u32),
|
||||
}
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use sha2::{Sha256, Digest};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
/// Wallet utilities functions.
|
||||
pub struct WalletUtils {}
|
||||
|
||||
+230
-150
@@ -12,14 +12,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::node::{Node, NodeConfig};
|
||||
use crate::tor::Tor;
|
||||
use crate::wallet::seed::WalletSeed;
|
||||
use crate::wallet::store::TxHeightStore;
|
||||
use crate::wallet::types::{ConnectionMethod, PhraseMode, WalletAccount, WalletData, WalletInstance, WalletTask, WalletTx, WalletTxAction};
|
||||
use crate::wallet::types::{
|
||||
ConnectionMethod, PhraseMode, WalletAccount, WalletData, WalletInstance, WalletTask, WalletTx,
|
||||
WalletTxAction,
|
||||
};
|
||||
use crate::wallet::{ConnectionsConfig, Mnemonic, WalletConfig};
|
||||
use crate::AppConfig;
|
||||
|
||||
use chrono::Utc;
|
||||
use futures::channel::oneshot;
|
||||
use grin_api::{ApiServer, Router};
|
||||
use grin_chain::SyncStatus;
|
||||
@@ -32,12 +36,20 @@ use grin_wallet_controller::command::parse_slatepack;
|
||||
use grin_wallet_controller::controller;
|
||||
use grin_wallet_controller::controller::ForeignAPIHandlerV2;
|
||||
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient, LMDBBackend};
|
||||
use grin_wallet_libwallet::api_impl::owner::{cancel_tx, init_send_tx, retrieve_summary_info, retrieve_txs, verify_payment_proof};
|
||||
use grin_wallet_libwallet::{address, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState, SlateVersion, SlatepackAddress, StatusMessage, StoredProofInfo, TxLogEntry, TxLogEntryType, VersionedSlate, WalletBackend, WalletInitStatus, WalletInst, WalletLCProvider};
|
||||
use grin_wallet_libwallet::api_impl::owner::{
|
||||
cancel_tx, init_send_tx, retrieve_summary_info, retrieve_txs, verify_payment_proof,
|
||||
};
|
||||
use grin_wallet_libwallet::{
|
||||
Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState,
|
||||
SlateVersion, SlatepackAddress, StatusMessage, StoredProofInfo, TxLogEntry, TxLogEntryType,
|
||||
VersionedSlate, WalletBackend, WalletInitStatus, WalletInst, WalletLCProvider, address,
|
||||
};
|
||||
use grin_wallet_util::OnionV3Address;
|
||||
use log::error;
|
||||
use num_bigint::BigInt;
|
||||
use parking_lot::RwLock;
|
||||
use rand::Rng;
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::{Value, json};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::net::{SocketAddr, TcpListener, ToSocketAddrs};
|
||||
@@ -45,13 +57,10 @@ use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU8, Ordering};
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::sync::{Arc, mpsc};
|
||||
use std::thread::Thread;
|
||||
use std::time::Duration;
|
||||
use std::{fs, path, thread};
|
||||
use chrono::Utc;
|
||||
use log::error;
|
||||
use num_bigint::BigInt;
|
||||
use tor_config::deps::Itertools;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -179,7 +188,7 @@ impl Wallet {
|
||||
name: &String,
|
||||
password: &ZeroingString,
|
||||
mnemonic: &Mnemonic,
|
||||
conn_method: &ConnectionMethod
|
||||
conn_method: &ConnectionMethod,
|
||||
) -> Result<Wallet, Error> {
|
||||
let config = WalletConfig::create(name.clone(), conn_method);
|
||||
let w = Wallet::new(config.clone());
|
||||
@@ -190,9 +199,11 @@ impl Wallet {
|
||||
fs::create_dir_all(&path)
|
||||
.map_err(|_| Error::IO("Directory creation error".to_string()))?;
|
||||
// Create seed file.
|
||||
let _ = WalletSeed::init_file(config.seed_path().as_str(),
|
||||
let _ = WalletSeed::init_file(
|
||||
config.seed_path().as_str(),
|
||||
ZeroingString::from(mnemonic.get_phrase()),
|
||||
password.clone())
|
||||
password.clone(),
|
||||
)
|
||||
.map_err(|_| Error::IO("Seed file creation error".to_string()))?;
|
||||
let node_client = Self::create_node_client(&config)?;
|
||||
let mut wallet: LMDBBackend<'static, HTTPNodeClient, ExtKeychain> =
|
||||
@@ -206,7 +217,9 @@ impl Wallet {
|
||||
let mut batch = wallet.batch_no_mask()?;
|
||||
match mnemonic.mode() {
|
||||
PhraseMode::Generate => batch.save_init_status(WalletInitStatus::InitNoScanning)?,
|
||||
PhraseMode::Import => batch.save_init_status(WalletInitStatus::InitNeedsScanning)?,
|
||||
PhraseMode::Import => {
|
||||
batch.save_init_status(WalletInitStatus::InitNeedsScanning)?
|
||||
}
|
||||
}
|
||||
batch.commit()?;
|
||||
}
|
||||
@@ -244,7 +257,10 @@ impl Wallet {
|
||||
AppConfig::socks_proxy_url()
|
||||
} else {
|
||||
AppConfig::http_proxy_url()
|
||||
}.unwrap_or("".to_string()).replace("http://", "").replace("socks5://", "");
|
||||
}
|
||||
.unwrap_or("".to_string())
|
||||
.replace("http://", "")
|
||||
.replace("socks5://", "");
|
||||
|
||||
// Convert URL to SocketAddr.
|
||||
let addr_res = match SocketAddr::from_str(url.as_str()) {
|
||||
@@ -263,15 +279,9 @@ impl Wallet {
|
||||
};
|
||||
|
||||
match addr_res {
|
||||
None => {
|
||||
HTTPNodeClient::new(&node_api_url, node_secret)?
|
||||
}
|
||||
None => HTTPNodeClient::new(&node_api_url, node_secret)?,
|
||||
Some(addr) => {
|
||||
let scheme = if socks {
|
||||
"socks5://"
|
||||
} else {
|
||||
"http://"
|
||||
};
|
||||
let scheme = if socks { "socks5://" } else { "http://" };
|
||||
HTTPNodeClient::new_proxy(&node_api_url, node_secret, Some((addr, scheme)))?
|
||||
}
|
||||
}
|
||||
@@ -358,7 +368,8 @@ impl Wallet {
|
||||
let wallet_inst = lc.wallet_inst()?;
|
||||
let label = self.get_config().account.to_owned();
|
||||
wallet_inst.set_parent_key_id_by_name(label.as_str())?;
|
||||
self.account_time.store(Utc::now().timestamp(), Ordering::Relaxed);
|
||||
self.account_time
|
||||
.store(Utc::now().timestamp(), Ordering::Relaxed);
|
||||
|
||||
// Start new synchronization thread or wake up existing one.
|
||||
let mut thread_w = self.sync_thread.write();
|
||||
@@ -375,7 +386,7 @@ impl Wallet {
|
||||
let mut w_inst = self.instance.write();
|
||||
*w_inst = None;
|
||||
}
|
||||
return Err(e)
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -429,7 +440,7 @@ impl Wallet {
|
||||
let r_address = self.slatepack_address.read();
|
||||
if r_address.is_some() {
|
||||
let addr = r_address.clone();
|
||||
return addr
|
||||
return addr;
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -482,7 +493,9 @@ impl Wallet {
|
||||
/// Get transaction broadcasting delay in blocks.
|
||||
pub fn broadcasting_delay(&self) -> u64 {
|
||||
let r_config = self.config.read();
|
||||
r_config.tx_broadcast_timeout.unwrap_or(WalletConfig::BROADCASTING_TIMEOUT_DEFAULT)
|
||||
r_config
|
||||
.tx_broadcast_timeout
|
||||
.unwrap_or(WalletConfig::BROADCASTING_TIMEOUT_DEFAULT)
|
||||
}
|
||||
|
||||
/// Update transaction broadcasting delay in blocks.
|
||||
@@ -497,7 +510,7 @@ impl Wallet {
|
||||
let mut w_config = self.config.write();
|
||||
w_config.ext_conn_id = match conn {
|
||||
ConnectionMethod::Integrated => None,
|
||||
ConnectionMethod::External(id, _) => Some(id.clone())
|
||||
ConnectionMethod::External(id, _) => Some(id.clone()),
|
||||
};
|
||||
w_config.save();
|
||||
}
|
||||
@@ -544,14 +557,14 @@ impl Wallet {
|
||||
thread::spawn(move || {
|
||||
wallet_close.closing.store(true, Ordering::Relaxed);
|
||||
// Wait common operations to finish.
|
||||
while wallet_close.message_opening() || wallet_close.send_creating() ||
|
||||
wallet_close.invoice_creating() {
|
||||
while wallet_close.message_opening()
|
||||
|| wallet_close.send_creating()
|
||||
|| wallet_close.invoice_creating()
|
||||
{
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
}
|
||||
// Stop running API server.
|
||||
let api_server_exists = {
|
||||
wallet_close.foreign_api_server.read().is_some()
|
||||
};
|
||||
let api_server_exists = { wallet_close.foreign_api_server.read().is_some() };
|
||||
if api_server_exists {
|
||||
let mut w_api_server = wallet_close.foreign_api_server.write();
|
||||
w_api_server.as_mut().unwrap().0.stop();
|
||||
@@ -637,7 +650,7 @@ impl Wallet {
|
||||
let mask = self.keychain_mask();
|
||||
if let Ok((_, txs)) = retrieve_txs(inst, mask.as_ref(), &None, false, id, slate_id, None) {
|
||||
if !txs.is_empty() {
|
||||
return Some(txs.get(0).unwrap().clone())
|
||||
return Some(txs.get(0).unwrap().clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -652,7 +665,8 @@ impl Wallet {
|
||||
let w = lc.wallet_inst()?;
|
||||
let parent_key_id = w.parent_key_id();
|
||||
// Retrieve txs from database.
|
||||
let txs: Vec<TxLogEntry> = w.tx_log_iter()
|
||||
let txs: Vec<TxLogEntry> = w
|
||||
.tx_log_iter()
|
||||
.filter(|tx_entry| tx_entry.parent_key_id == parent_key_id)
|
||||
// Filter transactions to not show txs without slate (usually unspent outputs).
|
||||
.filter(|tx| {
|
||||
@@ -660,13 +674,12 @@ impl Wallet {
|
||||
})
|
||||
.filter(|tx_entry| {
|
||||
if tx_entry.tx_type == TxLogEntryType::TxSent
|
||||
|| tx_entry.tx_type == TxLogEntryType::TxSentCancelled {
|
||||
BigInt::from(tx_entry.amount_debited)
|
||||
- BigInt::from(tx_entry.amount_credited)
|
||||
|| tx_entry.tx_type == TxLogEntryType::TxSentCancelled
|
||||
{
|
||||
BigInt::from(tx_entry.amount_debited) - BigInt::from(tx_entry.amount_credited)
|
||||
>= BigInt::from(1)
|
||||
} else {
|
||||
BigInt::from(tx_entry.amount_credited)
|
||||
- BigInt::from(tx_entry.amount_debited)
|
||||
BigInt::from(tx_entry.amount_credited) - BigInt::from(tx_entry.amount_debited)
|
||||
>= BigInt::from(1)
|
||||
}
|
||||
})
|
||||
@@ -674,9 +687,10 @@ impl Wallet {
|
||||
.sorted_by_key(|tx| -tx.creation_ts.timestamp())
|
||||
// Sort to show unconfirmed at top.
|
||||
.sorted_by_key(|tx| {
|
||||
tx.confirmed || tx.tx_type == TxLogEntryType::TxReceivedCancelled ||
|
||||
tx.tx_type == TxLogEntryType::TxSentCancelled ||
|
||||
tx.tx_type == TxLogEntryType::TxReverted
|
||||
tx.confirmed
|
||||
|| tx.tx_type == TxLogEntryType::TxReceivedCancelled
|
||||
|| tx.tx_type == TxLogEntryType::TxSentCancelled
|
||||
|| tx.tx_type == TxLogEntryType::TxReverted
|
||||
})
|
||||
// Apply limit.
|
||||
.take(limit as usize)
|
||||
@@ -693,7 +707,8 @@ impl Wallet {
|
||||
match task {
|
||||
WalletTask::CalculateFee(_, _) => {
|
||||
let calculating = self.fee_calculating.load(Ordering::Relaxed);
|
||||
self.fee_calculating.store(calculating + 1, Ordering::Relaxed);
|
||||
self.fee_calculating
|
||||
.store(calculating + 1, Ordering::Relaxed);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -706,7 +721,11 @@ impl Wallet {
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
let mut api = Owner::new(instance, None);
|
||||
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
|
||||
controller::owner_single_use(
|
||||
None,
|
||||
self.keychain_mask().as_ref(),
|
||||
Some(&mut api),
|
||||
|api, m| {
|
||||
let id = api.create_account_path(m, label)?;
|
||||
if self.get_data().is_none() {
|
||||
return Err(Error::GenericError("No wallet data".to_string()));
|
||||
@@ -722,7 +741,8 @@ impl Wallet {
|
||||
w_data.sort_by_key(|w| w.label != label.clone());
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Set active account from provided label.
|
||||
@@ -741,11 +761,17 @@ impl Wallet {
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
let mut api = Owner::new(instance.clone(), None);
|
||||
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
|
||||
controller::owner_single_use(
|
||||
None,
|
||||
self.keychain_mask().as_ref(),
|
||||
Some(&mut api),
|
||||
|api, m| {
|
||||
api.set_active_account(m, label)?;
|
||||
self.account_time.store(Utc::now().timestamp(), Ordering::Relaxed);
|
||||
self.account_time
|
||||
.store(Utc::now().timestamp(), Ordering::Relaxed);
|
||||
Ok(())
|
||||
})?;
|
||||
},
|
||||
)?;
|
||||
|
||||
// Update Slatepack address and secret key.
|
||||
self.update_secret_key_addr()?;
|
||||
@@ -772,16 +798,18 @@ impl Wallet {
|
||||
&self,
|
||||
current_height: u64,
|
||||
o: &mut Owner<DefaultLCProvider<HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>,
|
||||
m: Option<&SecretKey>)
|
||||
-> Option<u64> {
|
||||
m: Option<&SecretKey>,
|
||||
) -> Option<u64> {
|
||||
if let Ok(outputs) = o.retrieve_outputs(m, false, false, None) {
|
||||
let mut spendable = 0;
|
||||
let min_confirmations = self.get_config().min_confirmations;
|
||||
for out_mapping in outputs.1 {
|
||||
let out = out_mapping.output;
|
||||
if out.status == grin_wallet_libwallet::OutputStatus::Unspent {
|
||||
if !out.is_coinbase || out.lock_height <= current_height
|
||||
|| out.num_confirmations(current_height) >= min_confirmations {
|
||||
if !out.is_coinbase
|
||||
|| out.lock_height <= current_height
|
||||
|| out.num_confirmations(current_height) >= min_confirmations
|
||||
{
|
||||
spendable += out.value;
|
||||
}
|
||||
}
|
||||
@@ -857,32 +885,47 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Parse Slatepack message into [`Slate`].
|
||||
pub fn parse_slatepack(&self, text: &String)
|
||||
-> Result<(Slate, Option<SlatepackAddress>), grin_wallet_controller::Error> {
|
||||
pub fn parse_slatepack(
|
||||
&self,
|
||||
text: &String,
|
||||
) -> Result<(Slate, Option<SlatepackAddress>), grin_wallet_controller::Error> {
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
let mut api = Owner::new(instance, None);
|
||||
match parse_slatepack(&mut api, self.keychain_mask().as_ref(), None, Some(text.clone())) {
|
||||
match parse_slatepack(
|
||||
&mut api,
|
||||
self.keychain_mask().as_ref(),
|
||||
None,
|
||||
Some(text.clone()),
|
||||
) {
|
||||
Ok(s) => Ok(s),
|
||||
Err(e) => Err(e)
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create Slatepack message from provided slate.
|
||||
fn create_slatepack_message(&self, slate: &Slate, dest: Option<SlatepackAddress>)
|
||||
-> Result<String, Error> {
|
||||
fn create_slatepack_message(
|
||||
&self,
|
||||
slate: &Slate,
|
||||
dest: Option<SlatepackAddress>,
|
||||
) -> Result<String, Error> {
|
||||
let mut message = "".to_string();
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
let mut api = Owner::new(instance, None);
|
||||
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
|
||||
controller::owner_single_use(
|
||||
None,
|
||||
self.keychain_mask().as_ref(),
|
||||
Some(&mut api),
|
||||
|api, m| {
|
||||
let recipients = match dest {
|
||||
Some(a) => vec![a],
|
||||
None => vec![],
|
||||
};
|
||||
message = api.create_slatepack_message(m, &slate, Some(0), recipients)?;
|
||||
Ok(())
|
||||
})?;
|
||||
},
|
||||
)?;
|
||||
|
||||
// Write Slatepack message to file.
|
||||
let slatepack_dir = self.get_config().get_slate_path(&slate);
|
||||
@@ -916,19 +959,13 @@ impl Wallet {
|
||||
};
|
||||
let res = init_send_tx(&mut **w, self.keychain_mask().as_ref(), args, false);
|
||||
match res {
|
||||
Ok(slate) => {
|
||||
Ok(slate.fee_fields.fee())
|
||||
}
|
||||
Err(e) => {
|
||||
match e {
|
||||
Error::NotEnoughFunds { available, needed, .. } => {
|
||||
Ok(needed - available)
|
||||
Ok(slate) => Ok(slate.fee_fields.fee()),
|
||||
Err(e) => match e {
|
||||
Error::NotEnoughFunds {
|
||||
available, needed, ..
|
||||
} => Ok(needed - available),
|
||||
e => Err(e),
|
||||
},
|
||||
e => {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,7 +1023,8 @@ impl Wallet {
|
||||
null,
|
||||
null
|
||||
]
|
||||
}).to_string();
|
||||
})
|
||||
.to_string();
|
||||
// Wait Tor service to launch.
|
||||
while Tor::is_service_starting(&self.identifier()) {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
@@ -1109,10 +1147,15 @@ impl Wallet {
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
let mut api = Owner::new(instance, None);
|
||||
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
|
||||
controller::owner_single_use(
|
||||
None,
|
||||
self.keychain_mask().as_ref(),
|
||||
Some(&mut api),
|
||||
|api, m| {
|
||||
api.post_tx(m, &slate, self.can_use_dandelion())?;
|
||||
Ok(())
|
||||
})?;
|
||||
},
|
||||
)?;
|
||||
|
||||
// Clear tx action.
|
||||
if let Some(id) = id {
|
||||
@@ -1127,7 +1170,13 @@ impl Wallet {
|
||||
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
cancel_tx(instance, self.keychain_mask().as_ref(), &None, Some(id), None)?;
|
||||
cancel_tx(
|
||||
instance,
|
||||
self.keychain_mask().as_ref(),
|
||||
&None,
|
||||
Some(id),
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Clear tx action.
|
||||
self.on_tx_action(id, None);
|
||||
@@ -1154,11 +1203,7 @@ impl Wallet {
|
||||
/// Save task result to consume later.
|
||||
fn on_task_result(&self, tx: Option<TxLogEntry>, task: &WalletTask) {
|
||||
let mut w_res = self.task_result.write();
|
||||
let id = if let Some(t) = tx {
|
||||
Some(t.id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let id = if let Some(t) = tx { Some(t.id) } else { None };
|
||||
*w_res = Some((id, task.clone()));
|
||||
}
|
||||
|
||||
@@ -1185,17 +1230,17 @@ impl Wallet {
|
||||
if let Ok(res) = w.w2n_client().get_kernel(
|
||||
tx.data.kernel_excess.as_ref().unwrap(),
|
||||
tx.data.kernel_lookup_min_height,
|
||||
None
|
||||
None,
|
||||
) {
|
||||
tx_height = Some(match res {
|
||||
None => 0,
|
||||
Some((_, h, _)) => h
|
||||
Some((_, h, _)) => h,
|
||||
});
|
||||
}
|
||||
} else if tx.broadcasting() {
|
||||
tx_height = match self.get_data() {
|
||||
None => None,
|
||||
Some(data) => Some(data.info.last_confirmed_height)
|
||||
Some(data) => Some(data.info.last_confirmed_height),
|
||||
};
|
||||
}
|
||||
Ok(tx_height)
|
||||
@@ -1333,8 +1378,11 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Retrieve payment proof.
|
||||
pub fn get_payment_proof(&self, tx_id: Option<u32>, slate_id: Option<Uuid>)
|
||||
-> Result<Option<PaymentProof>, Error> {
|
||||
pub fn get_payment_proof(
|
||||
&self,
|
||||
tx_id: Option<u32>,
|
||||
slate_id: Option<Uuid>,
|
||||
) -> Result<Option<PaymentProof>, Error> {
|
||||
let r_inst = self.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
let key_mask = self.keychain_mask();
|
||||
@@ -1368,12 +1416,15 @@ impl Wallet {
|
||||
let lc = wallet_lock.lc_provider()?;
|
||||
let w = lc.wallet_inst()?;
|
||||
// Find wallet transaction to update or create.
|
||||
let txs = w.tx_log_iter().filter(|entry| {
|
||||
let txs = w
|
||||
.tx_log_iter()
|
||||
.filter(|entry| {
|
||||
if let Some(excess) = entry.kernel_excess {
|
||||
return excess == proof.excess;
|
||||
}
|
||||
false
|
||||
}).collect::<Vec<TxLogEntry>>();
|
||||
})
|
||||
.collect::<Vec<TxLogEntry>>();
|
||||
if let Some(tx) = txs.get(0) {
|
||||
let mut tx = tx.clone();
|
||||
let mut batch = w.batch(keychain_mask.as_ref())?;
|
||||
@@ -1413,7 +1464,7 @@ impl Wallet {
|
||||
Ok((0, send, rec))
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
// Sync wallet data on success.
|
||||
if res.is_ok() {
|
||||
@@ -1481,7 +1532,8 @@ fn start_sync(wallet: Wallet) -> Thread {
|
||||
*w_tasks = Some(tx);
|
||||
}
|
||||
let wallet_thread = wallet.clone();
|
||||
thread::spawn(move || loop {
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
let wallet_task = wallet_thread.clone();
|
||||
if let Ok(task) = rx.recv() {
|
||||
thread::spawn(move || {
|
||||
@@ -1497,6 +1549,7 @@ fn start_sync(wallet: Wallet) -> Thread {
|
||||
if wallet_thread.is_closing() || !wallet_thread.is_open() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Reset progress values.
|
||||
@@ -1517,7 +1570,8 @@ fn start_sync(wallet: Wallet) -> Thread {
|
||||
wallet.syncing.store(false, Ordering::Relaxed);
|
||||
};
|
||||
|
||||
thread::spawn(move || loop {
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
// Set syncing status.
|
||||
wallet.syncing.store(true, Ordering::Relaxed);
|
||||
|
||||
@@ -1565,9 +1619,7 @@ fn start_sync(wallet: Wallet) -> Thread {
|
||||
|
||||
if wallet.is_open() && !wallet.is_closing() {
|
||||
// Start Foreign API listener if not running.
|
||||
let mut api_server_running = {
|
||||
wallet.foreign_api_server.read().is_some()
|
||||
};
|
||||
let mut api_server_running = { wallet.foreign_api_server.read().is_some() };
|
||||
if !api_server_running {
|
||||
match start_api_server(&wallet) {
|
||||
Ok(api_server) => {
|
||||
@@ -1581,8 +1633,10 @@ fn start_sync(wallet: Wallet) -> Thread {
|
||||
|
||||
// Start unfailed Tor service if API server is running.
|
||||
let service_id = wallet.identifier();
|
||||
if wallet.auto_start_tor_listener() && api_server_running &&
|
||||
!Tor::is_service_failed(&service_id) {
|
||||
if wallet.auto_start_tor_listener()
|
||||
&& api_server_running
|
||||
&& !Tor::is_service_failed(&service_id)
|
||||
{
|
||||
let r_foreign_api = wallet.foreign_api_server.read();
|
||||
let api = r_foreign_api.as_ref().unwrap();
|
||||
if let Some(key) = wallet.secret_key() {
|
||||
@@ -1616,17 +1670,20 @@ fn start_sync(wallet: Wallet) -> Thread {
|
||||
SYNC_DELAY
|
||||
};
|
||||
thread::park_timeout(delay);
|
||||
}).thread().clone()
|
||||
}
|
||||
})
|
||||
.thread()
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Handle wallet task.
|
||||
async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
let send_tor = async |tx: TxLogEntry, s: &Slate, r: &SlatepackAddress| {
|
||||
match w.send_tor(tx.id, &s, r).await {
|
||||
Ok(s) => {
|
||||
match w.finalize(&s, tx.id) {
|
||||
Ok(s) => {
|
||||
match w.post(&s, Some(tx.id)) {
|
||||
let send_tor = async |tx: TxLogEntry, s: &Slate, r: &SlatepackAddress| match w
|
||||
.send_tor(tx.id, &s, r)
|
||||
.await
|
||||
{
|
||||
Ok(s) => match w.finalize(&s, tx.id) {
|
||||
Ok(s) => match w.post(&s, Some(tx.id)) {
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
w.on_task_result(Some(tx), &t);
|
||||
@@ -1635,20 +1692,17 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
error!("send tor post error: {:?}", e);
|
||||
w.on_tx_error(tx.id, Some(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("send tor finalize error: {:?}", e);
|
||||
w.task(WalletTask::Cancel(tx.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("send tor error: {:?}", e);
|
||||
w.on_tx_error(tx.id, Some(e));
|
||||
w.on_task_result(Some(tx), &t);
|
||||
}
|
||||
}
|
||||
};
|
||||
match &t {
|
||||
WalletTask::OpenMessage(m) => {
|
||||
@@ -1663,8 +1717,9 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
// Check if message already exists.
|
||||
let exists = {
|
||||
let mut exists = w.slatepack_exists(&s);
|
||||
if !exists && (s.state == SlateState::Invoice2 ||
|
||||
s.state == SlateState::Standard2) {
|
||||
if !exists
|
||||
&& (s.state == SlateState::Invoice2 || s.state == SlateState::Standard2)
|
||||
{
|
||||
let mut slate = s.clone();
|
||||
slate.state = if s.state == SlateState::Standard2 {
|
||||
SlateState::Standard3
|
||||
@@ -1700,8 +1755,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
SlateState::Standard2 | SlateState::Invoice2 => {
|
||||
if let Some(tx) = tx {
|
||||
match w.finalize(&s, tx.id) {
|
||||
Ok(s) => {
|
||||
match w.post(&s, Some(tx.id)) {
|
||||
Ok(s) => match w.post(&s, Some(tx.id)) {
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
}
|
||||
@@ -1709,8 +1763,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
error!("message tx post error: {:?}", e);
|
||||
w.on_tx_error(tx.id, Some(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("message tx finalize error: {:?}", e);
|
||||
w.task(WalletTask::Cancel(tx.id));
|
||||
@@ -1781,13 +1834,12 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
}
|
||||
}
|
||||
w.invoice_creating.store(false, Ordering::Relaxed);
|
||||
},
|
||||
}
|
||||
WalletTask::Finalize(id) => {
|
||||
if let Some(s) = w.get_tx_slate(Some(*id), None) {
|
||||
w.on_tx_error(*id, None);
|
||||
match w.finalize(&s, *id) {
|
||||
Ok(s) => {
|
||||
match w.post(&s, Some(*id)) {
|
||||
Ok(s) => match w.post(&s, Some(*id)) {
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
}
|
||||
@@ -1795,8 +1847,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
error!("tx finalize post error: {:?}", e);
|
||||
w.on_tx_error(*id, Some(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("tx finalize error: {:?}", e);
|
||||
w.task(WalletTask::Cancel(*id));
|
||||
@@ -1841,8 +1892,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
w.task(WalletTask::Cancel(*id));
|
||||
}
|
||||
}
|
||||
WalletTask::Cancel(id) => {
|
||||
match w.cancel(*id) {
|
||||
WalletTask::Cancel(id) => match w.cancel(*id) {
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
}
|
||||
@@ -1850,23 +1900,20 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
error!("tx cancel error: {:?}", e);
|
||||
w.on_tx_error(*id, Some(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
WalletTask::VerifyProof(p, _) => {
|
||||
w.proof_verifying.store(true, Ordering::Relaxed);
|
||||
let res = w.verify_payment_proof(p);
|
||||
w.proof_verifying.store(false, Ordering::Relaxed);
|
||||
w.on_task_result(None, &WalletTask::VerifyProof(p.clone(), Some(res)));
|
||||
}
|
||||
WalletTask::Delete(id) => {
|
||||
match w.delete_tx(*id) {
|
||||
WalletTask::Delete(id) => match w.delete_tx(*id) {
|
||||
Ok(_) => sync_wallet_data(&w, false),
|
||||
Err(e) => {
|
||||
error!("tx delete error: {:?}", e);
|
||||
w.on_tx_error(*id, Some(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1882,7 +1929,9 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
|
||||
StatusMessage::UpdatingTransactions(_) => {}
|
||||
StatusMessage::FullScanWarn(_) => {}
|
||||
StatusMessage::Scanning(_, progress) => {
|
||||
wallet_info.info_sync_progress.store(progress, Ordering::Relaxed);
|
||||
wallet_info
|
||||
.info_sync_progress
|
||||
.store(progress, Ordering::Relaxed);
|
||||
}
|
||||
StatusMessage::ScanningComplete(_) => {
|
||||
wallet_info.info_sync_progress.store(100, Ordering::Relaxed);
|
||||
@@ -1903,11 +1952,13 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
|
||||
wallet.keychain_mask().as_ref(),
|
||||
&Some(info_tx),
|
||||
from_node,
|
||||
config.min_confirmations
|
||||
config.min_confirmations,
|
||||
) {
|
||||
// Do not retrieve txs if wallet was closed or its first sync.
|
||||
if !wallet.is_open() || wallet.is_closing() ||
|
||||
(!from_node && info.last_confirmed_height == 0) {
|
||||
if !wallet.is_open()
|
||||
|| wallet.is_closing()
|
||||
|| (!from_node && info.last_confirmed_height == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1937,7 +1988,11 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
|
||||
if w_data.is_some() {
|
||||
w_data.as_mut().unwrap().info = info;
|
||||
} else {
|
||||
*w_data = Some(WalletData { info, txs: None, txs_limit });
|
||||
*w_data = Some(WalletData {
|
||||
info,
|
||||
txs: None,
|
||||
txs_limit,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Update wallet transactions.
|
||||
@@ -2012,24 +2067,38 @@ fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut new = WalletTx::new(tx.clone(),
|
||||
let mut new = WalletTx::new(
|
||||
tx.clone(),
|
||||
proof.clone(),
|
||||
wallet,
|
||||
height,
|
||||
broadcasting_height,
|
||||
action,
|
||||
action_error);
|
||||
action_error,
|
||||
);
|
||||
// Update Slate state for unconfirmed.
|
||||
let unconfirmed = !tx.confirmed && (tx.tx_type == TxLogEntryType::TxSent ||
|
||||
tx.tx_type == TxLogEntryType::TxReceived);
|
||||
let unconfirmed = !tx.confirmed
|
||||
&& (tx.tx_type == TxLogEntryType::TxSent || tx.tx_type == TxLogEntryType::TxReceived);
|
||||
if unconfirmed {
|
||||
new.update_slate_state(wallet);
|
||||
}
|
||||
// Payment proof setup.
|
||||
if proof.is_none() && tx.payment_proof.is_some() &&
|
||||
tx.payment_proof.as_ref().unwrap().receiver_signature.is_some() &&
|
||||
tx.payment_proof.as_ref().unwrap().sender_signature.is_some() &&
|
||||
tx.kernel_excess.is_some() {
|
||||
if proof.is_none()
|
||||
&& tx.payment_proof.is_some()
|
||||
&& tx
|
||||
.payment_proof
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.receiver_signature
|
||||
.is_some()
|
||||
&& tx
|
||||
.payment_proof
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.sender_signature
|
||||
.is_some()
|
||||
&& tx.kernel_excess.is_some()
|
||||
{
|
||||
if let Ok(p) = wallet.get_payment_proof(Some(tx.id), tx.tx_slate_id) {
|
||||
proof = p.clone();
|
||||
new.proof = proof;
|
||||
@@ -2078,23 +2147,29 @@ fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> {
|
||||
/// Start Foreign API server to receive txs over transport and mining rewards.
|
||||
fn start_api_server(wallet: &Wallet) -> Result<(ApiServer, u16), Error> {
|
||||
let host = "127.0.0.1";
|
||||
let port = wallet.get_config().api_port.unwrap_or(rand::rng().random_range(10000..30000));
|
||||
let free_port = (port..).find(|port| {
|
||||
let port = wallet
|
||||
.get_config()
|
||||
.api_port
|
||||
.unwrap_or(rand::rng().random_range(10000..30000));
|
||||
let free_port = (port..)
|
||||
.find(|port| {
|
||||
return match TcpListener::bind((host, port.to_owned())) {
|
||||
Ok(_) => {
|
||||
let node_p2p_port = NodeConfig::get_p2p_port();
|
||||
let node_api_port = NodeConfig::get_api_ip_port().1;
|
||||
let free = port.to_string() != node_p2p_port && port.to_string() != node_api_port;
|
||||
let free =
|
||||
port.to_string() != node_p2p_port && port.to_string() != node_api_port;
|
||||
if free {
|
||||
let mut config = wallet.config.write();
|
||||
config.api_port = Some(*port);
|
||||
config.save();
|
||||
}
|
||||
free
|
||||
},
|
||||
Err(_) => false
|
||||
}
|
||||
}).unwrap();
|
||||
Err(_) => false,
|
||||
};
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Setup API server address.
|
||||
let api_addr = format!("{}:{}", host, free_port);
|
||||
@@ -2103,10 +2178,12 @@ fn start_api_server(wallet: &Wallet) -> Result<(ApiServer, u16), Error> {
|
||||
let r_inst = wallet.instance.as_ref().read();
|
||||
let instance = r_inst.clone().unwrap();
|
||||
let keychain_mask = wallet.keychain_mask();
|
||||
let api_handler_v2 = ForeignAPIHandlerV2::new(instance,
|
||||
let api_handler_v2 = ForeignAPIHandlerV2::new(
|
||||
instance,
|
||||
Arc::new(Mutex::new(keychain_mask)),
|
||||
false,
|
||||
Mutex::new(None));
|
||||
Mutex::new(None),
|
||||
);
|
||||
let mut router = Router::new();
|
||||
router
|
||||
.add_route("/v2/foreign", Arc::new(api_handler_v2))
|
||||
@@ -2117,7 +2194,8 @@ fn start_api_server(wallet: &Wallet) -> Result<(ApiServer, u16), Error> {
|
||||
|
||||
let mut apis = ApiServer::new();
|
||||
let socket_addr: SocketAddr = api_addr.parse().unwrap();
|
||||
let _ = apis.start(socket_addr, router, None, api_chan)
|
||||
let _ = apis
|
||||
.start(socket_addr, router, None, api_chan)
|
||||
.map_err(|_| Error::GenericError("API thread failed to start".to_string()))?;
|
||||
Ok((apis, free_port))
|
||||
}
|
||||
@@ -2179,7 +2257,9 @@ fn repair_wallet(wallet: &Wallet) {
|
||||
StatusMessage::UpdatingTransactions(_) => {}
|
||||
StatusMessage::FullScanWarn(_) => {}
|
||||
StatusMessage::Scanning(_, progress) => {
|
||||
wallet_scan.repair_progress.store(progress, Ordering::Relaxed);
|
||||
wallet_scan
|
||||
.repair_progress
|
||||
.store(progress, Ordering::Relaxed);
|
||||
}
|
||||
StatusMessage::ScanningComplete(_) => {
|
||||
wallet_scan.repair_progress.store(100, Ordering::Relaxed);
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@
|
||||
<?endif ?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product Id="*" Version="0.3.5" UpgradeCode="C19F9B41-CD13-4F0E-B27D-E0EF8CF1CE91" Language="1033" Name="Grim" Manufacturer="Ardocrat">
|
||||
<Package Id="E15A3211-C334-4B2D-8774-4834657A00B6" InstallerVersion="300" Compressed="yes"/>
|
||||
<Product Id="*" Version="0.3.6" UpgradeCode="C19F9B41-CD13-4F0E-B27D-E0EF8CF1CE91" Language="1033" Name="Grim" Manufacturer="Ardocrat">
|
||||
<Package Id="F4D20D15-2788-4199-9220-3905799817F6" InstallerVersion="300" Compressed="yes"/>
|
||||
<Media Id="1" Cabinet="grim.cab" EmbedCab="yes" />
|
||||
|
||||
<MajorUpgrade AllowDowngrades = "yes"/>
|
||||
|
||||
Reference in New Issue
Block a user