build: v0.3.6, format code

This commit is contained in:
ardocrat
2026-05-21 00:56:28 +03:00
parent a4eadebef2
commit 4aeda9c9dc
114 changed files with 24876 additions and 23319 deletions
Generated
+12 -1
View File
@@ -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
View File
@@ -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"
+1 -1
View File
@@ -12,7 +12,7 @@ android {
minSdk 24
targetSdk 36
versionCode 5
versionName "0.3.5"
versionName "0.3.6"
}
lint {
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+20 -13
View File
@@ -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 {
+34 -27
View File
@@ -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
View File
@@ -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
View File
@@ -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
+11 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+6 -2
View File
@@ -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.
+1 -1
View File
@@ -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
View File
@@ -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;
+100 -47
View File
@@ -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.
+54 -45
View File
@@ -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();
+51 -38
View File
@@ -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);
});
+77 -49
View File
@@ -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));
});
});
});
+1 -1
View File
@@ -33,5 +33,5 @@ pub use content::*;
mod connections;
pub use connections::*;
pub mod types;
pub mod modals;
pub mod types;
+33 -15
View File
@@ -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);
+78 -44
View File
@@ -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);
+60 -36
View File
@@ -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);
+104 -47
View File
@@ -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), || {
+169 -81
View File
@@ -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()),
);
}
}
+171 -85
View File
@@ -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),
);
});
});
});
+132 -61
View File
@@ -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), || {
+119 -65
View File
@@ -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()),
);
}
}
+4 -4
View File
@@ -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,
}
+8 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+2 -2
View File
@@ -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 {
+23 -14
View File
@@ -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 {
+47 -27
View File
@@ -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() {
+88 -42
View File
@@ -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), || {
+10 -16
View File
@@ -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)
}
}
});
}
}
+9 -6
View File
@@ -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
View File
@@ -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};
+112 -68
View File
@@ -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(),
+53 -50
View File
@@ -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,
}
}
}
+41 -32
View File
@@ -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.
+1 -1
View File
@@ -20,7 +20,7 @@ pub enum Step {
/// Mnemonic phrase confirmation.
ConfirmMnemonic,
/// Wallet connection setup.
SetupConnection
SetupConnection,
}
impl Step {
+1 -1
View File
@@ -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::*;
+20 -10
View File
@@ -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), || {
+6 -2
View File
@@ -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);
+33 -21
View File
@@ -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)
+23 -10
View File
@@ -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), || {
+45 -22
View File
@@ -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);
}
+56 -35
View File
@@ -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);
+19 -12
View File
@@ -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), || {
+42 -43
View File
@@ -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()),
);
});
});
}
+19 -13
View File
@@ -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();
+2 -2
View File
@@ -24,7 +24,7 @@ mod content;
pub use content::WalletContent;
mod account;
mod transport;
mod request;
mod message;
mod proof;
mod request;
mod transport;
+22 -13
View File
@@ -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();
});
},
);
});
});
}
+20 -10
View File
@@ -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.
+39 -26
View File
@@ -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.
+133 -60
View File
@@ -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"), || {
+194 -97
View File
@@ -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);
}
+47 -25
View File
@@ -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.
+12 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+7 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -24,5 +24,5 @@ pub enum NodeError {
/// Configuration issue.
Configuration,
/// Unknown error.
Unknown
Unknown,
}
+5 -4
View File
@@ -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
}
+4 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)));
}
}
}
+5 -5
View File
@@ -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
}
+13 -6
View File
@@ -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();
}
}
+18 -17
View File
@@ -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
View File
@@ -35,7 +35,7 @@ impl Default for WalletList {
Self {
main_list,
test_list,
selected: None
selected: None,
}
}
}
+36 -20
View File
@@ -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
View File
@@ -32,5 +32,5 @@ pub use list::*;
mod utils;
pub use utils::WalletUtils;
pub mod store;
mod seed;
pub mod store;
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"/>