mirror of
https://github.com/jwinterm/grim.git
synced 2026-10-19 23:08:33 +02:00
build + android + wallet + ui: new lib to get IPs, egui, android and grin deps, add input from soft keyboard to text edit, receive txs, filter txs by account, finalize input (in rework), wallet creation copy/paste buttons
This commit is contained in:
+12
-11
@@ -45,12 +45,21 @@ impl<Platform: PlatformCallbacks> eframe::App for PlatformApp<Platform> {
|
||||
// Handle Esc keyboard key event and platform Back button key event.
|
||||
let back_button_pressed = BACK_BUTTON_PRESSED.load(Ordering::Relaxed);
|
||||
if ctx.input_mut(|i| i.consume_key(Modifiers::NONE, egui::Key::Escape)) || back_button_pressed {
|
||||
if back_button_pressed {
|
||||
BACK_BUTTON_PRESSED.store(false, Ordering::Relaxed);
|
||||
}
|
||||
self.root.on_back();
|
||||
// Request repaint to update previous content.
|
||||
ctx.request_repaint();
|
||||
|
||||
if back_button_pressed {
|
||||
BACK_BUTTON_PRESSED.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Close event (on desktop).
|
||||
if ctx.input(|i| i.viewport().close_requested()) {
|
||||
if !self.root.exit_allowed {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::CancelClose);
|
||||
Root::show_exit_modal();
|
||||
}
|
||||
}
|
||||
|
||||
// Show main content.
|
||||
@@ -63,14 +72,6 @@ impl<Platform: PlatformCallbacks> eframe::App for PlatformApp<Platform> {
|
||||
self.root.ui(ui, frame, &self.platform);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_close_event(&mut self) -> bool {
|
||||
let exit = self.root.exit_allowed;
|
||||
if !exit {
|
||||
Root::show_exit_modal();
|
||||
}
|
||||
exit
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -30,11 +30,41 @@ impl Android {
|
||||
|
||||
impl PlatformCallbacks for Android {
|
||||
fn show_keyboard(&self) {
|
||||
self.android_app.show_soft_input(true);
|
||||
// Disable NDK soft input show call before fix for egui.
|
||||
// self.android_app.show_soft_input(false);
|
||||
|
||||
use jni::objects::{JObject};
|
||||
|
||||
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 _ = env.call_method(
|
||||
activity,
|
||||
"showKeyboard",
|
||||
"()V",
|
||||
&[]
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
fn hide_keyboard(&self) {
|
||||
self.android_app.hide_soft_input(true);
|
||||
// Disable NDK soft input hide call before fix for egui.
|
||||
// self.android_app.hide_soft_input(false);
|
||||
|
||||
use jni::objects::{JObject};
|
||||
|
||||
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 _ = env.call_method(
|
||||
activity,
|
||||
"hideKeyboard",
|
||||
"()V",
|
||||
&[]
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
fn copy_string_to_buffer(&self, data: String) {
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{RichText, ScrollArea, Stroke};
|
||||
use egui::style::Margin;
|
||||
use egui::{Margin, RichText, ScrollArea, Stroke};
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
@@ -57,7 +56,7 @@ impl NetworkContent {
|
||||
fill: Colors::FILL,
|
||||
inner_margin: Margin {
|
||||
left: View::get_left_inset() + 4.0,
|
||||
right: View::far_right_inset_margin(ui, frame) + 4.0,
|
||||
right: View::far_right_inset_margin(ui) + 4.0,
|
||||
top: 4.0,
|
||||
bottom: View::get_bottom_inset() + 4.0,
|
||||
},
|
||||
@@ -82,7 +81,7 @@ impl NetworkContent {
|
||||
stroke: View::DEFAULT_STROKE,
|
||||
inner_margin: Margin {
|
||||
left: View::get_left_inset() + 4.0,
|
||||
right: View::far_right_inset_margin(ui, frame) + 4.0,
|
||||
right: View::far_right_inset_margin(ui) + 4.0,
|
||||
top: 3.0,
|
||||
bottom: 4.0,
|
||||
},
|
||||
@@ -108,7 +107,7 @@ impl NetworkContent {
|
||||
0.0
|
||||
},
|
||||
right: if show_connections {
|
||||
View::far_right_inset_margin(ui, frame) + 4.0
|
||||
View::far_right_inset_margin(ui) + 4.0
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
@@ -128,7 +127,7 @@ impl NetworkContent {
|
||||
.show(ui, |ui| {
|
||||
ui.add_space(1.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
let max_width = if !Root::is_dual_panel_mode(frame) {
|
||||
let max_width = if !Root::is_dual_panel_mode(ui) {
|
||||
Root::SIDE_PANEL_WIDTH * 1.3
|
||||
} else {
|
||||
ui.available_width()
|
||||
@@ -212,7 +211,7 @@ impl NetworkContent {
|
||||
});
|
||||
}
|
||||
}, |ui, frame| {
|
||||
if !Root::is_dual_panel_mode(frame) {
|
||||
if !Root::is_dual_panel_mode(ui) {
|
||||
View::title_button(ui, CARDHOLDER, || {
|
||||
Root::toggle_network_panel();
|
||||
});
|
||||
|
||||
+18
-22
@@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::RichText;
|
||||
use lazy_static::lazy_static;
|
||||
@@ -70,11 +69,11 @@ impl ModalContainer for Root {
|
||||
|
||||
fn modal_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
_: &mut eframe::Frame,
|
||||
modal: &Modal,
|
||||
_: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
Self::EXIT_MODAL_ID => self.exit_modal_content(ui, frame, modal),
|
||||
Self::EXIT_MODAL_ID => self.exit_modal_content(ui, modal),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +90,7 @@ impl Root {
|
||||
// Draw modal content for current ui container.
|
||||
self.current_modal_ui(ui, frame, cb);
|
||||
|
||||
let (is_panel_open, panel_width) = Self::network_panel_state_width(frame);
|
||||
let (is_panel_open, panel_width) = Self::network_panel_state_width(ui);
|
||||
// Show network content.
|
||||
egui::SidePanel::left("network_panel")
|
||||
.resizable(false)
|
||||
@@ -103,7 +102,8 @@ impl Root {
|
||||
.show_animated_inside(ui, is_panel_open, |ui| {
|
||||
// Set content height as window height.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(frame.info().window_info.size.y);
|
||||
let window_size = View::window_size(ui);
|
||||
rect.set_height(window_size.1);
|
||||
ui.allocate_ui_at_rect(rect, |ui| {
|
||||
self.network.ui(ui, frame, cb);
|
||||
});
|
||||
@@ -118,7 +118,8 @@ impl Root {
|
||||
.show_inside(ui, |ui| {
|
||||
// Set content height as window height.
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(frame.info().window_info.size.y);
|
||||
let window_size = View::window_size(ui);
|
||||
rect.set_height(window_size.1);
|
||||
ui.allocate_ui_at_rect(rect, |ui| {
|
||||
self.wallets.ui(ui, frame, cb);
|
||||
});
|
||||
@@ -126,21 +127,20 @@ impl Root {
|
||||
}
|
||||
|
||||
/// Get [`NetworkContent`] panel state and width.
|
||||
fn network_panel_state_width(frame: &mut eframe::Frame) -> (bool, f32) {
|
||||
let dual_panel_mode = Self::is_dual_panel_mode(frame);
|
||||
fn network_panel_state_width(ui: &mut egui::Ui) -> (bool, f32) {
|
||||
let dual_panel_mode = Self::is_dual_panel_mode(ui);
|
||||
let is_panel_open = dual_panel_mode || Self::is_network_panel_open();
|
||||
let panel_width = if dual_panel_mode {
|
||||
Self::SIDE_PANEL_WIDTH + View::get_left_inset()
|
||||
} else {
|
||||
frame.info().window_info.size.x
|
||||
View::window_size(ui).0
|
||||
};
|
||||
(is_panel_open, panel_width)
|
||||
}
|
||||
|
||||
/// Check if ui can show [`NetworkContent`] and [`WalletsContent`] at same time.
|
||||
pub fn is_dual_panel_mode(frame: &mut eframe::Frame) -> bool {
|
||||
let w = frame.info().window_info.size.x;
|
||||
let h = frame.info().window_info.size.y;
|
||||
pub fn is_dual_panel_mode(ui: &mut egui::Ui) -> bool {
|
||||
let (w, h) = View::window_size(ui);
|
||||
// Screen is wide if width is greater than height or just 20% smaller.
|
||||
let is_wide_screen = w > h || w + (w * 0.2) >= h;
|
||||
// Dual panel mode is available when window is wide and its width is at least 2 times
|
||||
@@ -168,10 +168,11 @@ impl Root {
|
||||
}
|
||||
|
||||
/// Draw exit confirmation modal content.
|
||||
fn exit_modal_content(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, modal: &Modal) {
|
||||
fn exit_modal_content(&mut self, ui: &mut egui::Ui, modal: &Modal) {
|
||||
if self.show_exit_progress {
|
||||
if !Node::is_running() {
|
||||
self.exit(frame);
|
||||
self.exit_allowed = true;
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
modal.close();
|
||||
}
|
||||
ui.add_space(16.0);
|
||||
@@ -199,9 +200,10 @@ impl Root {
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal_exit.exit"), Colors::WHITE, || {
|
||||
View::button_ui(ui, t!("modal_exit.exit"), Colors::WHITE, |ui| {
|
||||
if !Node::is_running() {
|
||||
self.exit(frame);
|
||||
self.exit_allowed = true;
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
modal.close();
|
||||
} else {
|
||||
Node::stop(true);
|
||||
@@ -221,12 +223,6 @@ impl Root {
|
||||
}
|
||||
}
|
||||
|
||||
/// Exit from the application.
|
||||
fn exit(&mut self, frame: &mut eframe::Frame) {
|
||||
self.exit_allowed = true;
|
||||
frame.close();
|
||||
}
|
||||
|
||||
/// Handle Back key event.
|
||||
pub fn on_back(&mut self) {
|
||||
if Modal::on_back() {
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Color32, Id, lerp, Rgba};
|
||||
use egui::style::Margin;
|
||||
use egui::{Margin, Color32, Id, lerp, Rgba};
|
||||
use egui_extras::{Size, Strip, StripBuilder};
|
||||
|
||||
use crate::gui::Colors;
|
||||
@@ -59,7 +58,7 @@ impl TitlePanel {
|
||||
.resizable(false)
|
||||
.exact_height(Self::DEFAULT_HEIGHT)
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Self::inner_margin(ui, frame),
|
||||
inner_margin: Self::inner_margin(ui),
|
||||
fill: Colors::YELLOW,
|
||||
..Default::default()
|
||||
})
|
||||
@@ -132,10 +131,10 @@ impl TitlePanel {
|
||||
}
|
||||
|
||||
/// Calculate inner margin based on display insets (cutouts).
|
||||
fn inner_margin(ui: &mut egui::Ui, frame: &mut eframe::Frame) -> Margin {
|
||||
fn inner_margin(ui: &mut egui::Ui) -> Margin {
|
||||
Margin {
|
||||
left: View::far_left_inset_margin(ui),
|
||||
right: View::far_right_inset_margin(ui, frame),
|
||||
right: View::far_right_inset_margin(ui),
|
||||
top: View::get_top_inset(),
|
||||
bottom: 0.0,
|
||||
}
|
||||
|
||||
+105
-5
@@ -12,13 +12,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::ops::Add;
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use egui::{Align, Button, CursorIcon, Layout, PointerState, Rect, Response, RichText, Sense, Spinner, TextStyle, Vec2, Widget};
|
||||
use egui::{Align, Button, CursorIcon, Layout, PointerState, Rect, Response, RichText, Sense, Spinner, TextBuffer, TextStyle, Widget};
|
||||
use egui::epaint::{CircleShape, Color32, FontId, RectShape, Rounding, Stroke};
|
||||
use egui::epaint::text::TextWrapping;
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::text::{LayoutJob, TextFormat};
|
||||
use lazy_static::lazy_static;
|
||||
use egui::text_edit::TextEditState;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK_SQUARE, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SQUARE};
|
||||
@@ -54,6 +58,21 @@ impl View {
|
||||
});
|
||||
}
|
||||
|
||||
/// Get width and height of app window.
|
||||
pub fn window_size(ui: &mut egui::Ui) -> (f32, f32) {
|
||||
ui.ctx().input(|i| {
|
||||
return match i.viewport().inner_rect {
|
||||
None => {
|
||||
let size = i.viewport().monitor_size.unwrap();
|
||||
(size.x, size.y)
|
||||
}
|
||||
Some(rect) => {
|
||||
(rect.width(), rect.height())
|
||||
}
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
/// Callback on Enter key press event.
|
||||
pub fn on_enter_key(ui: &mut egui::Ui, cb: impl FnOnce()) {
|
||||
if ui.ctx().input(|i| i.key_pressed(egui::Key::Enter)) {
|
||||
@@ -71,9 +90,10 @@ impl View {
|
||||
}
|
||||
|
||||
/// Calculate margin for far left view based on display insets (cutouts).
|
||||
pub fn far_right_inset_margin(ui: &mut egui::Ui, frame: &mut eframe::Frame) -> f32 {
|
||||
pub fn far_right_inset_margin(ui: &mut egui::Ui) -> f32 {
|
||||
let container_width = ui.available_rect_before_wrap().max.x as i32;
|
||||
let display_width = frame.info().window_info.size.x as i32;
|
||||
let window_size = Self::window_size(ui);
|
||||
let display_width = window_size.0 as i32;
|
||||
// Means end of the screen.
|
||||
if container_width == display_width {
|
||||
Self::get_right_inset()
|
||||
@@ -198,6 +218,19 @@ impl View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw [`Button`] with specified background fill color.
|
||||
pub fn button_ui(ui: &mut egui::Ui, text: String, fill: Color32, action: impl FnOnce(&mut egui::Ui)) {
|
||||
let button_text = Self::ellipsize(text.to_uppercase(), 17.0, Colors::TEXT_BUTTON);
|
||||
let br = Button::new(button_text)
|
||||
.stroke(Self::DEFAULT_STROKE)
|
||||
.fill(fill)
|
||||
.ui(ui)
|
||||
.on_hover_cursor(CursorIcon::PointingHand);
|
||||
if Self::touched(ui, br) {
|
||||
(action)(ui);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw list item [`Button`] with provided rounding.
|
||||
pub fn item_button(ui: &mut egui::Ui,
|
||||
rounding: Rounding,
|
||||
@@ -336,15 +369,17 @@ impl View {
|
||||
// Setup text edit size.
|
||||
let mut edit_rect = ui.available_rect_before_wrap();
|
||||
edit_rect.set_height(Self::TEXT_EDIT_HEIGHT);
|
||||
|
||||
// Show text edit.
|
||||
let text_edit_resp = egui::TextEdit::singleline(value)
|
||||
.id(options.id)
|
||||
.margin(Vec2::new(2.0, 0.0))
|
||||
.margin(egui::Vec2::new(2.0, 0.0))
|
||||
.font(TextStyle::Heading)
|
||||
.min_size(edit_rect.size())
|
||||
.horizontal_align(if options.h_center { Align::Center } else { Align::Min })
|
||||
.vertical_align(Align::Center)
|
||||
.password(show_pass)
|
||||
.cursor_at_end(true)
|
||||
.ui(ui);
|
||||
// Show keyboard on click.
|
||||
if text_edit_resp.clicked() {
|
||||
@@ -355,6 +390,42 @@ impl View {
|
||||
text_edit_resp.request_focus();
|
||||
cb.show_keyboard();
|
||||
}
|
||||
// Apply text from input on Android as temporary fix for egui.
|
||||
let os = OperatingSystem::from_target_os();
|
||||
if os == OperatingSystem::Android && text_edit_resp.has_focus() {
|
||||
let mut w_input = LAST_SOFT_KEYBOARD_INPUT.write().unwrap();
|
||||
|
||||
if !w_input.is_empty() {
|
||||
let mut state = TextEditState::load(ui.ctx(), options.id).unwrap();
|
||||
match state.cursor.char_range() {
|
||||
None => {}
|
||||
Some(range) => {
|
||||
let mut r = range.clone();
|
||||
|
||||
let mut index = r.primary.index;
|
||||
println!("insert_str {} {}", index, w_input.as_str());
|
||||
|
||||
value.insert_text(w_input.as_str(), index);
|
||||
index = index + 1;
|
||||
|
||||
println!("12345 {} {}", value, r.primary.index);
|
||||
|
||||
if index == 0 {
|
||||
r.primary.index = value.len();
|
||||
r.secondary.index = r.primary.index;
|
||||
} else {
|
||||
r.primary.index = index;
|
||||
r.secondary.index = r.primary.index;
|
||||
}
|
||||
state.cursor.set_char_range(Some(r));
|
||||
TextEditState::store(state, ui.ctx(), options.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*w_input = "".to_string();
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -571,4 +642,33 @@ pub extern "C" fn Java_mw_gri_android_MainActivity_onDisplayInsets(
|
||||
RIGHT_DISPLAY_INSET.store(array[1], Ordering::Relaxed);
|
||||
BOTTOM_DISPLAY_INSET.store(array[2], Ordering::Relaxed);
|
||||
LEFT_DISPLAY_INSET.store(array[3], Ordering::Relaxed);
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref LAST_SOFT_KEYBOARD_INPUT: Arc<RwLock<String>> = Arc::new(RwLock::new("".to_string()));
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(target_os = "android")]
|
||||
#[allow(non_snake_case)]
|
||||
#[no_mangle]
|
||||
/// Callback from Java code with last entered character from soft keyboard.
|
||||
pub extern "C" fn Java_mw_gri_android_MainActivity_onInput(
|
||||
_env: jni::JNIEnv,
|
||||
_class: jni::objects::JObject,
|
||||
char: jni::sys::jstring
|
||||
) {
|
||||
use jni::objects::{JString};
|
||||
|
||||
unsafe {
|
||||
let j_obj = JString::from_raw(char);
|
||||
let j_str = _env.get_string_unchecked(j_obj.as_ref()).unwrap();
|
||||
match j_str.to_str() {
|
||||
Ok(str) => {
|
||||
let mut w_input = LAST_SOFT_KEYBOARD_INPUT.write().unwrap();
|
||||
*w_input = w_input.clone().add(str);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ impl WalletsContent {
|
||||
let show_wallet = self.wallets.is_selected_open();
|
||||
|
||||
// Setup panels parameters.
|
||||
let dual_panel = is_dual_panel_mode(ui, frame);
|
||||
let dual_panel = is_dual_panel_mode(ui);
|
||||
let open_wallet_panel = dual_panel || show_wallet || create_wallet || empty_list;
|
||||
let wallet_panel_width = self.wallet_panel_width(ui, empty_list, dual_panel, show_wallet);
|
||||
let content_width = ui.available_width();
|
||||
@@ -131,10 +131,10 @@ impl WalletsContent {
|
||||
if create_wallet || !show_wallet {
|
||||
// Show wallet creation content.
|
||||
self.creation_content.ui(ui, frame, cb, |wallet| {
|
||||
// Reset wallet content.
|
||||
self.wallet_content = WalletContent::default();
|
||||
// Add created wallet to list.
|
||||
self.wallets.add(wallet);
|
||||
// Reset wallet content.
|
||||
self.wallet_content = WalletContent::default();
|
||||
});
|
||||
} else {
|
||||
let selected_id = self.wallets.selected_id.clone();
|
||||
@@ -180,7 +180,7 @@ impl WalletsContent {
|
||||
right: if list_hidden {
|
||||
0.0
|
||||
} else {
|
||||
View::far_right_inset_margin(ui, frame) + 4.0
|
||||
View::far_right_inset_margin(ui) + 4.0
|
||||
},
|
||||
top: 4.0,
|
||||
bottom: View::get_bottom_inset() + 4.0,
|
||||
@@ -274,7 +274,7 @@ impl WalletsContent {
|
||||
self.show_wallets_at_dual_panel = !show_list;
|
||||
AppConfig::toggle_show_wallets_at_dual_panel();
|
||||
});
|
||||
} else if !Root::is_dual_panel_mode(frame) {
|
||||
} else if !Root::is_dual_panel_mode(ui) {
|
||||
View::title_button(ui, GLOBE, || {
|
||||
Root::toggle_network_panel();
|
||||
});
|
||||
@@ -605,8 +605,8 @@ impl WalletsContent {
|
||||
}
|
||||
|
||||
/// Check if it's possible to show [`WalletsContent`] and [`WalletContent`] panels at same time.
|
||||
fn is_dual_panel_mode(ui: &mut egui::Ui, frame: &mut eframe::Frame) -> bool {
|
||||
let dual_panel_root = Root::is_dual_panel_mode(frame);
|
||||
fn is_dual_panel_mode(ui: &mut egui::Ui) -> bool {
|
||||
let dual_panel_root = Root::is_dual_panel_mode(ui);
|
||||
let max_width = ui.available_width();
|
||||
dual_panel_root && max_width >= (Root::SIDE_PANEL_WIDTH * 2.0) + View::get_right_inset()
|
||||
}
|
||||
@@ -17,7 +17,7 @@ use egui_extras::{RetainedImage, Size, StripBuilder};
|
||||
|
||||
use crate::built_info;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{CHECK, EYE, EYE_SLASH, FOLDER_PLUS, SHARE_FAT};
|
||||
use crate::gui::icons::{CHECK, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, FOLDER_PLUS, SHARE_FAT};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, Root, View};
|
||||
use crate::gui::views::types::{ModalPosition, TextEditOptions};
|
||||
@@ -86,7 +86,7 @@ impl WalletCreation {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 2.0, |ui| {
|
||||
self.step_control_ui(ui, on_create);
|
||||
self.step_control_ui(ui, on_create, cb);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -130,17 +130,12 @@ impl WalletCreation {
|
||||
});
|
||||
}
|
||||
|
||||
/// Reset wallet creation to default values.
|
||||
fn reset(&mut self) {
|
||||
self.step = None;
|
||||
self.name_edit = String::from("");
|
||||
self.pass_edit = String::from("");
|
||||
self.mnemonic_setup.reset();
|
||||
}
|
||||
|
||||
/// Draw [`Step`] description and confirmation control.
|
||||
fn step_control_ui(&mut self, ui: &mut egui::Ui, on_create: impl FnOnce(Wallet)) {
|
||||
if let Some(step) = &self.step {
|
||||
fn step_control_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
on_create: impl FnOnce(Wallet),
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
if let Some(step) = self.step.clone() {
|
||||
// Setup step description text and availability.
|
||||
let (step_text, mut step_available) = match step {
|
||||
Step::EnterMnemonic => {
|
||||
@@ -179,27 +174,121 @@ impl WalletCreation {
|
||||
.color(Colors::RED));
|
||||
ui.add_space(2.0);
|
||||
}
|
||||
|
||||
// Show next step button if there are no empty words.
|
||||
if step_available {
|
||||
// Setup button text.
|
||||
let (next_text, color) = if step == &Step::SetupConnection {
|
||||
(format!("{} {}", CHECK, t!("complete")), Colors::GOLD)
|
||||
if step == Step::EnterMnemonic {
|
||||
ui.add_space(4.0);
|
||||
if !step_available {
|
||||
self.copy_or_paste_button_ui(ui, cb);
|
||||
} else {
|
||||
let text = format!("{} {}", SHARE_FAT, t!("continue"));
|
||||
(text, Colors::WHITE)
|
||||
};
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
|
||||
|
||||
ui.add_space(4.0);
|
||||
// Show button.
|
||||
View::button(ui, next_text.to_uppercase(), color, || {
|
||||
self.forward(on_create);
|
||||
ui.columns(2, |columns| {
|
||||
// Show copy or paste button for mnemonic phrase step.
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
self.copy_or_paste_button_ui(ui, cb);
|
||||
});
|
||||
|
||||
// Show next step button if there are no empty words.
|
||||
if step_available {
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
self.next_step_button_ui(ui, step, on_create);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if step_available {
|
||||
ui.add_space(4.0);
|
||||
self.next_step_button_ui(ui, step, on_create);
|
||||
}
|
||||
}
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw copy or paste button at [`Step::EnterMnemonic`].
|
||||
fn copy_or_paste_button_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
match self.mnemonic_setup.mnemonic.mode {
|
||||
PhraseMode::Generate => {
|
||||
// Show copy button.
|
||||
let c_t = format!("{} {}", COPY, t!("copy").to_uppercase());
|
||||
View::button(ui, c_t.to_uppercase(), Colors::WHITE, || {
|
||||
cb.copy_string_to_buffer(self.mnemonic_setup.mnemonic.get_phrase());
|
||||
});
|
||||
}
|
||||
PhraseMode::Import => {
|
||||
// Show paste button.
|
||||
let p_t = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase());
|
||||
View::button(ui, p_t, Colors::WHITE, || {
|
||||
self.mnemonic_setup.mnemonic.import_text(cb.get_string_from_buffer());
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw button to go to next [`Step`].
|
||||
fn next_step_button_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
step: Step,
|
||||
on_create: impl FnOnce(Wallet)) {
|
||||
// Setup button text.
|
||||
let (next_text, color) = if step == Step::SetupConnection {
|
||||
(format!("{} {}", CHECK, t!("complete")), Colors::GOLD)
|
||||
} else {
|
||||
let text = format!("{} {}", SHARE_FAT, t!("continue"));
|
||||
(text, Colors::WHITE)
|
||||
};
|
||||
|
||||
|
||||
// Show next step button.
|
||||
View::button(ui, next_text.to_uppercase(), color, || {
|
||||
self.step = if let Some(step) = &self.step {
|
||||
match step {
|
||||
Step::EnterMnemonic => {
|
||||
if self.mnemonic_setup.mnemonic.mode == PhraseMode::Generate {
|
||||
Some(Step::ConfirmMnemonic)
|
||||
} else {
|
||||
// Check if entered phrase was valid.
|
||||
if self.mnemonic_setup.valid_phrase {
|
||||
Some(Step::SetupConnection)
|
||||
} else {
|
||||
Some(Step::EnterMnemonic)
|
||||
}
|
||||
}
|
||||
}
|
||||
Step::ConfirmMnemonic => {
|
||||
// Check external connections availability on connection setup.
|
||||
ExternalConnection::start_ext_conn_availability_check();
|
||||
Some(Step::SetupConnection)
|
||||
},
|
||||
Step::SetupConnection => {
|
||||
// Create wallet at last step.
|
||||
let name = self.name_edit.clone();
|
||||
let pass = self.pass_edit.clone();
|
||||
let phrase = self.mnemonic_setup.mnemonic.get_phrase();
|
||||
let conn_method = &self.network_setup.method;
|
||||
let mut wallet = Wallet::create(name,
|
||||
pass.clone(),
|
||||
phrase,
|
||||
conn_method).unwrap();
|
||||
// Open created wallet.
|
||||
wallet.open(pass).unwrap();
|
||||
// Pass created wallet to callback.
|
||||
(on_create)(wallet);
|
||||
// Reset input data.
|
||||
self.step = None;
|
||||
self.name_edit = String::from("");
|
||||
self.pass_edit = String::from("");
|
||||
self.mnemonic_setup.reset();
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Some(Step::EnterMnemonic)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw wallet creation [`Step`] content.
|
||||
fn step_content_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
@@ -256,7 +345,12 @@ impl WalletCreation {
|
||||
None => {}
|
||||
Some(step) => {
|
||||
match step {
|
||||
Step::EnterMnemonic => self.reset(),
|
||||
Step::EnterMnemonic => {
|
||||
self.step = None;
|
||||
self.name_edit = String::from("");
|
||||
self.pass_edit = String::from("");
|
||||
self.mnemonic_setup.reset();
|
||||
},
|
||||
Step::ConfirmMnemonic => self.step = Some(Step::EnterMnemonic),
|
||||
Step::SetupConnection => self.step = Some(Step::EnterMnemonic)
|
||||
}
|
||||
@@ -264,49 +358,6 @@ impl WalletCreation {
|
||||
}
|
||||
}
|
||||
|
||||
/// Go to the next wallet creation [`Step`].
|
||||
fn forward(&mut self, on_create: impl FnOnce(Wallet)) {
|
||||
self.step = if let Some(step) = &self.step {
|
||||
match step {
|
||||
Step::EnterMnemonic => {
|
||||
if self.mnemonic_setup.mnemonic.mode == PhraseMode::Generate {
|
||||
Some(Step::ConfirmMnemonic)
|
||||
} else {
|
||||
// Check if entered phrase was valid.
|
||||
if self.mnemonic_setup.valid_phrase {
|
||||
Some(Step::SetupConnection)
|
||||
} else {
|
||||
Some(Step::EnterMnemonic)
|
||||
}
|
||||
}
|
||||
}
|
||||
Step::ConfirmMnemonic => {
|
||||
// Check external connections availability on connection setup.
|
||||
ExternalConnection::start_ext_conn_availability_check();
|
||||
Some(Step::SetupConnection)
|
||||
},
|
||||
Step::SetupConnection => {
|
||||
// Create wallet at last step.
|
||||
let name = self.name_edit.clone();
|
||||
let pass = self.pass_edit.clone();
|
||||
let phrase = self.mnemonic_setup.mnemonic.get_phrase();
|
||||
let conn_method = &self.network_setup.method;
|
||||
let mut wallet
|
||||
= Wallet::create(name, pass.clone(), phrase, conn_method).unwrap();
|
||||
// Open created wallet.
|
||||
wallet.open(pass).unwrap();
|
||||
// Pass created wallet to callback.
|
||||
(on_create)(wallet);
|
||||
// Reset input data.
|
||||
self.reset();
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Some(Step::EnterMnemonic)
|
||||
};
|
||||
}
|
||||
|
||||
/// Start wallet creation from showing [`Modal`] to enter name and password.
|
||||
pub fn show_name_pass_modal(&mut self, cb: &dyn PlatformCallbacks) {
|
||||
// Reset modal values.
|
||||
|
||||
@@ -167,13 +167,13 @@ impl MnemonicSetup {
|
||||
let words = match self.mnemonic.mode {
|
||||
PhraseMode::Generate => {
|
||||
if edit_words {
|
||||
self.mnemonic.confirm_words.clone()
|
||||
&self.mnemonic.confirm_words
|
||||
} else {
|
||||
self.mnemonic.words.clone()
|
||||
&self.mnemonic.words
|
||||
}
|
||||
}
|
||||
PhraseMode::Import => self.mnemonic.words.clone()
|
||||
};
|
||||
PhraseMode::Import => &self.mnemonic.words
|
||||
}.clone();
|
||||
|
||||
let mut word_number = 0;
|
||||
let cols = list_columns_count(ui);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
/// Wallet creation step.
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum Step {
|
||||
/// Mnemonic phrase input.
|
||||
EnterMnemonic,
|
||||
|
||||
@@ -185,22 +185,20 @@ impl RecoverySetup {
|
||||
.size(17.0)
|
||||
.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 pass_edit_opts = TextEditOptions::new(pass_edit_id).password();
|
||||
View::text_edit(ui, cb, &mut self.pass_edit, pass_edit_opts);
|
||||
// Draw current wallet password text edit.
|
||||
let pass_edit_id = Id::from(modal.id).with(wallet.get_config().id);
|
||||
let pass_edit_opts = TextEditOptions::new(pass_edit_id).password();
|
||||
View::text_edit(ui, cb, &mut self.pass_edit, pass_edit_opts);
|
||||
|
||||
// Show information when password is empty.
|
||||
ui.vertical_centered(|ui| {
|
||||
// Show information when password is empty or wrong.
|
||||
if self.pass_edit.is_empty() {
|
||||
ui.add_space(10.0);
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("wallets.pass_empty"))
|
||||
.size(17.0)
|
||||
.color(Colors::INACTIVE_TEXT));
|
||||
} else if self.wrong_pass {
|
||||
ui.add_space(10.0);
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("wallets.wrong_pass"))
|
||||
.size(17.0)
|
||||
.color(Colors::RED));
|
||||
|
||||
@@ -375,7 +375,7 @@ impl WalletContent {
|
||||
}
|
||||
|
||||
/// Draw content when wallet is syncing and not ready to use, returns `true` at this case.
|
||||
pub fn sync_ui(ui: &mut egui::Ui, frame: &mut eframe::Frame, wallet: &Wallet) -> bool {
|
||||
pub fn sync_ui(ui: &mut egui::Ui, wallet: &Wallet) -> bool {
|
||||
if wallet.is_repairing() && !wallet.sync_error() {
|
||||
Self::sync_progress_ui(ui, wallet);
|
||||
return true;
|
||||
@@ -384,7 +384,7 @@ impl WalletContent {
|
||||
return true;
|
||||
} else if wallet.get_current_ext_conn_id().is_none() {
|
||||
if !Node::is_running() || Node::is_stopping() {
|
||||
let dual_panel_root = Root::is_dual_panel_mode(frame);
|
||||
let dual_panel_root = Root::is_dual_panel_mode(ui);
|
||||
View::center_content(ui, 108.0, |ui| {
|
||||
let text = t!("wallets.enable_node", "settings" => GEAR_FINE);
|
||||
ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT));
|
||||
|
||||
@@ -37,10 +37,10 @@ impl WalletTab for WalletInfo {
|
||||
|
||||
fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
_: &mut eframe::Frame,
|
||||
wallet: &mut Wallet,
|
||||
_: &dyn PlatformCallbacks) {
|
||||
if WalletContent::sync_ui(ui, frame, wallet) {
|
||||
if WalletContent::sync_ui(ui, wallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,35 +12,58 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::{Id, Margin, RichText, ScrollArea, Widget};
|
||||
use egui::{Id, Margin, RichText, ScrollArea};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARCHIVE_BOX, BROOM, CLIPBOARD_TEXT, COPY, HAND_COINS};
|
||||
use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, COPY, HAND_COINS, RECEIPT};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Root, View};
|
||||
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
|
||||
use crate::gui::views::{Modal, Root, View};
|
||||
use crate::gui::views::types::{ModalPosition, TextEditOptions};
|
||||
use crate::gui::views::wallets::wallet::types::{SLATEPACK_MESSAGE_HINT, WalletTab, WalletTabType};
|
||||
use crate::gui::views::wallets::wallet::WalletContent;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
/// Receiving tab content.
|
||||
pub struct WalletReceive {
|
||||
/// Slatepack text from sender to create response.
|
||||
/// Flag to check if there is invoice transaction type.
|
||||
is_invoice: bool,
|
||||
|
||||
/// Slatepack message from sender to create response message.
|
||||
message_edit: String,
|
||||
/// Generated Slatepack response.
|
||||
/// Generated Slatepack response message for sender.
|
||||
response_edit: String,
|
||||
/// Flag to check if there is an error happened on receive.
|
||||
receive_error: bool,
|
||||
/// Flag to check if response was copied to the clipboard.
|
||||
response_copied: bool,
|
||||
/// Flag to check if there is an error happened on response creation.
|
||||
response_error: bool,
|
||||
|
||||
/// Amount to receive for invoice transaction type.
|
||||
amount_edit: String,
|
||||
/// Generated Slatepack message for invoice transaction.
|
||||
request_edit: String,
|
||||
/// Flag to check if there is an error happened on invoice creation.
|
||||
request_error: bool,
|
||||
/// Slatepack message from sender to finalize transaction.
|
||||
finalization_edit: String,
|
||||
/// Flag to check if there is an error happened on transaction finalization.
|
||||
finalization_error: bool,
|
||||
}
|
||||
|
||||
/// Identifier for invoice amount [`Modal`].
|
||||
const INVOICE_AMOUNT_MODAL: &'static str = "invoice_amount_modal";
|
||||
|
||||
impl Default for WalletReceive {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_invoice: false,
|
||||
message_edit: "".to_string(),
|
||||
response_edit: "".to_string(),
|
||||
receive_error: false,
|
||||
response_copied: false,
|
||||
response_error: false,
|
||||
amount_edit: "".to_string(),
|
||||
request_edit: "".to_string(),
|
||||
request_error: false,
|
||||
finalization_edit: "".to_string(),
|
||||
finalization_error: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,13 +75,16 @@ impl WalletTab for WalletReceive {
|
||||
|
||||
fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
_: &mut eframe::Frame,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
if WalletContent::sync_ui(ui, frame, wallet) {
|
||||
if WalletContent::sync_ui(ui, wallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show modal content for this ui container.
|
||||
self.modal_content_ui(ui, wallet, cb);
|
||||
|
||||
// Show receiving content panel.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
@@ -73,18 +99,21 @@ impl WalletTab for WalletReceive {
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.receive_ui(ui, wallet, cb);
|
||||
ScrollArea::vertical()
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
|
||||
.id_source(Id::from("wallet_receive").with(wallet.get_config().id))
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.receive_ui(ui, wallet, cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Hint for Slatepack Message input.
|
||||
const RECEIVE_SLATEPACK_HINT: &'static str = "BEGINSLATEPACK.\n...\n...\n...\nENDSLATEPACK.";
|
||||
|
||||
impl WalletReceive {
|
||||
/// Draw receiving content.
|
||||
pub fn receive_ui(&mut self,
|
||||
@@ -95,18 +124,91 @@ impl WalletReceive {
|
||||
View::sub_title(ui, format!("{} {}", HAND_COINS, t!("wallets.manually")));
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(3.0);
|
||||
// Show manual receiving content.
|
||||
self.manual_ui(ui, wallet, cb);
|
||||
}
|
||||
|
||||
// Setup manual sending description.
|
||||
let response_empty = self.response_edit.is_empty();
|
||||
let desc_text = if response_empty {
|
||||
t!("wallets.receive_paste_slatepack")
|
||||
/// Draw [`Modal`] content for this ui container.
|
||||
fn modal_content_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
match Modal::opened() {
|
||||
None => {}
|
||||
Some(id) => {
|
||||
match id {
|
||||
INVOICE_AMOUNT_MODAL => {
|
||||
Modal::ui(ui.ctx(), |ui, modal| {
|
||||
self.invoice_amount_modal(ui, wallet, modal, cb);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw manual receiving content.
|
||||
fn manual_ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(10.0);
|
||||
ui.columns(2, |columns| {
|
||||
let mut is_invoice = self.is_invoice;
|
||||
columns[0].vertical_centered(|ui| {
|
||||
View::radio_value(ui, &mut is_invoice, false, t!("wallets.receive"));
|
||||
});
|
||||
columns[1].vertical_centered(|ui| {
|
||||
View::radio_value(ui, &mut is_invoice, true, t!("wallets.invoice"));
|
||||
});
|
||||
if is_invoice != self.is_invoice {
|
||||
self.is_invoice = is_invoice;
|
||||
// Reset fields to default values on mode change.
|
||||
if is_invoice {
|
||||
self.amount_edit = "".to_string();
|
||||
self.request_edit = "".to_string();
|
||||
self.request_error = false;
|
||||
self.finalization_edit = "".to_string();
|
||||
self.finalization_error = false;
|
||||
} else {
|
||||
self.message_edit = "".to_string();
|
||||
self.response_edit = "".to_string();
|
||||
self.response_error = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
|
||||
if self.is_invoice {
|
||||
// Show invoice creation content.
|
||||
self.manual_invoice_ui(ui, wallet, cb);
|
||||
} else {
|
||||
t!("wallets.receive_send_slatepack")
|
||||
};
|
||||
ui.label(RichText::new(desc_text).size(16.0).color(Colors::INACTIVE_TEXT));
|
||||
ui.add_space(3.0);
|
||||
// Show manual transaction receiving content.
|
||||
self.manual_receive_ui(ui, wallet, cb);
|
||||
}
|
||||
}
|
||||
|
||||
// Show Slatepack text input.
|
||||
/// Draw manual receiving content.
|
||||
fn manual_receive_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Setup description.
|
||||
let response_empty = self.response_edit.is_empty();
|
||||
|
||||
if self.response_error {
|
||||
ui.label(RichText::new(t!("wallets.receive_slatepack_err"))
|
||||
.size(16.0)
|
||||
.color(Colors::RED));
|
||||
} else {
|
||||
let desc_text = if response_empty {
|
||||
t!("wallets.receive_slatepack_desc")
|
||||
} else {
|
||||
t!("wallets.receive_send_slatepack")
|
||||
};
|
||||
ui.label(RichText::new(desc_text).size(16.0).color(Colors::INACTIVE_TEXT));
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Setup Slatepack message text input.
|
||||
let message = if response_empty {
|
||||
&mut self.message_edit
|
||||
} else {
|
||||
@@ -126,12 +228,12 @@ impl WalletReceive {
|
||||
.font(egui::TextStyle::Small)
|
||||
.desired_rows(5)
|
||||
.interactive(response_empty)
|
||||
.hint_text(RECEIVE_SLATEPACK_HINT)
|
||||
.hint_text(SLATEPACK_MESSAGE_HINT)
|
||||
.desired_width(f32::INFINITY)
|
||||
.show(ui);
|
||||
// Clear an error when message changed.
|
||||
if &message_before != message {
|
||||
self.receive_error = false;
|
||||
self.response_error = false;
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
@@ -139,38 +241,27 @@ impl WalletReceive {
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Show receiving input control buttons.
|
||||
self.receive_buttons_ui(ui, wallet, cb);
|
||||
}
|
||||
|
||||
/// Draw manual receiving input control buttons.
|
||||
fn receive_buttons_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
// Draw buttons to clear/copy/paste.
|
||||
let field_is_empty = self.message_edit.is_empty() && self.response_edit.is_empty();
|
||||
let columns_num = if !field_is_empty { 2 } else { 1 };
|
||||
|
||||
// Draw buttons to clear/copy/paste.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
ui.columns(columns_num, |columns| {
|
||||
let first_column_content = |ui: &mut egui::Ui| {
|
||||
if !field_is_empty {
|
||||
if !self.response_edit.is_empty() && !self.response_error {
|
||||
let clear_text = format!("{} {}", BROOM, t!("clear"));
|
||||
View::button(ui, clear_text, Colors::BUTTON, || {
|
||||
self.receive_error = false;
|
||||
self.response_copied = false;
|
||||
self.response_error = false;
|
||||
self.message_edit.clear();
|
||||
self.response_edit.clear();
|
||||
});
|
||||
} else if self.message_edit.is_empty() {
|
||||
} else {
|
||||
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
|
||||
View::button(ui, paste_text, Colors::BUTTON, || {
|
||||
self.message_edit = cb.get_string_from_buffer();
|
||||
self.receive_error = false;
|
||||
self.response_error = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -178,56 +269,253 @@ impl WalletReceive {
|
||||
columns[0].vertical_centered(first_column_content);
|
||||
} else {
|
||||
columns[0].vertical_centered_justified(first_column_content);
|
||||
}
|
||||
if !field_is_empty {
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
if !self.message_edit.is_empty() {
|
||||
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
|
||||
View::button(ui, paste_text, Colors::BUTTON, || {
|
||||
self.message_edit = cb.get_string_from_buffer();
|
||||
self.receive_error = false;
|
||||
if self.response_error {
|
||||
let clear_text = format!("{} {}", BROOM, t!("clear"));
|
||||
View::button(ui, clear_text, Colors::BUTTON, || {
|
||||
self.response_error = false;
|
||||
self.message_edit.clear();
|
||||
self.response_edit.clear();
|
||||
});
|
||||
} else if !self.response_edit.is_empty() {
|
||||
let copy_text = format!("{} {}", COPY, t!("copy"));
|
||||
View::button(ui, copy_text, Colors::BUTTON, || {
|
||||
cb.copy_string_to_buffer(self.response_edit.clone());
|
||||
self.response_copied = true;
|
||||
});
|
||||
} else {
|
||||
View::button(ui, t!("wallets.create_response"), Colors::GOLD, || {
|
||||
match wallet.receive(self.message_edit.clone()) {
|
||||
Ok(response) => {
|
||||
self.response_edit = response.trim().to_string();
|
||||
self.message_edit.clear();
|
||||
cb.copy_string_to_buffer(response);
|
||||
},
|
||||
Err(e) => {
|
||||
wallet.sync();
|
||||
println!("error {}", e);
|
||||
self.response_error = true
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Draw button to create response.
|
||||
if !self.message_edit.is_empty() && !self.receive_error {
|
||||
ui.add_space(8.0);
|
||||
let create_text = format!("{} {}", ARCHIVE_BOX, t!("wallets.create_response"));
|
||||
View::button(ui, create_text, Colors::GOLD, || {
|
||||
match wallet.receive(self.message_edit.clone()) {
|
||||
Ok(response) => {
|
||||
self.response_edit = response.trim().to_string();
|
||||
self.message_edit.clear();
|
||||
// Copy response to clipboard.
|
||||
cb.copy_string_to_buffer(response);
|
||||
self.response_copied = true;
|
||||
},
|
||||
Err(_) => self.receive_error = true
|
||||
/// Draw invoice creation content.
|
||||
fn manual_invoice_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.label(RichText::new(t!("wallets.issue_invoice_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT));
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Draw invoice creation button.
|
||||
let invoice_text = format!("{} {}", RECEIPT, t!("wallets.issue_invoice"));
|
||||
View::button(ui, invoice_text, Colors::BUTTON, || {
|
||||
// Reset modal values.
|
||||
self.amount_edit = "".to_string();
|
||||
self.request_error = false;
|
||||
// Show invoice amount modal.
|
||||
Modal::new(INVOICE_AMOUNT_MODAL)
|
||||
.position(ModalPosition::CenterTop)
|
||||
.title(t!("wallets.issue_invoice"))
|
||||
.show();
|
||||
cb.show_keyboard();
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new(t!("wallets.receive_slatepack_desc"))
|
||||
.size(16.0)
|
||||
.color(Colors::INACTIVE_TEXT));
|
||||
ui.add_space(6.0);
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(3.0);
|
||||
|
||||
// Draw invoice finalization text input.
|
||||
ScrollArea::vertical()
|
||||
.max_height(128.0)
|
||||
.id_source(Id::from("receive_input").with(wallet.get_config().id))
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.add_space(7.0);
|
||||
let finalization_before = self.finalization_edit.clone();
|
||||
egui::TextEdit::multiline(&mut self.finalization_edit)
|
||||
.font(egui::TextStyle::Small)
|
||||
.desired_rows(5)
|
||||
.interactive(true)
|
||||
.hint_text(SLATEPACK_MESSAGE_HINT)
|
||||
.desired_width(f32::INFINITY)
|
||||
.show(ui);
|
||||
// Clear an error when message changed.
|
||||
if finalization_before != self.finalization_edit {
|
||||
self.finalization_error = false;
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Draw buttons to clear/paste.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
|
||||
View::button(ui, paste_text, Colors::BUTTON, || {
|
||||
self.finalization_edit = cb.get_string_from_buffer();
|
||||
self.response_error = false;
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("wallets.finalize"), Colors::GOLD, || {
|
||||
wallet.finalize();
|
||||
//TODO: finalize
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if self.finalization_error {
|
||||
ui.add_space(8.0);
|
||||
} else if self.receive_error {
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new(t!("wallets.receive_slatepack_err"))
|
||||
ui.label(RichText::new(t!("wallets.finalize_slatepack_err"))
|
||||
.size(16.0)
|
||||
.color(Colors::RED));
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
|
||||
/// Draw invoice amount [`Modal`] content.
|
||||
fn invoice_amount_modal(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
wallet: &mut Wallet,
|
||||
modal: &Modal,
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
ui.add_space(6.0);
|
||||
if self.request_edit.is_empty() {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(t!("wallets.enter_amount"))
|
||||
.size(17.0)
|
||||
.color(Colors::GRAY));
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
} else if self.response_copied {
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new(t!("wallets.response_copied"))
|
||||
.size(16.0)
|
||||
.color(Colors::GREEN));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw invoice amount text edit.
|
||||
let amount_edit_id = Id::from(modal.id).with(wallet.get_config().id);
|
||||
let amount_edit_opts = TextEditOptions::new(amount_edit_id).h_center();
|
||||
let mut amount_edit = self.amount_edit.clone();
|
||||
View::text_edit(ui, cb, &mut amount_edit, amount_edit_opts);
|
||||
if amount_edit != self.amount_edit {
|
||||
self.request_error = false;
|
||||
match amount_from_hr_string(amount_edit.as_str()) {
|
||||
Ok(_) => {
|
||||
self.amount_edit = amount_edit;
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Show invoice creation error.
|
||||
if self.request_error {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("wallets.invoice_slatepack_err"))
|
||||
.size(17.0)
|
||||
.color(Colors::RED));
|
||||
}
|
||||
|
||||
// Show modal buttons.
|
||||
ui.add_space(12.0);
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::WHITE, || {
|
||||
self.amount_edit = "".to_string();
|
||||
self.request_error = false;
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("continue"), Colors::WHITE, || {
|
||||
match amount_from_hr_string(self.amount_edit.as_str()) {
|
||||
Ok(amount) => {
|
||||
match wallet.issue_invoice(amount) {
|
||||
Ok(message) => {
|
||||
self.request_edit = message;
|
||||
cb.hide_keyboard();
|
||||
}
|
||||
Err(_) => {
|
||||
self.request_error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
self.request_error = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
} else {
|
||||
ui.vertical_centered(|ui| {
|
||||
let amount = amount_from_hr_string(self.amount_edit.as_str()).unwrap();
|
||||
let amount_format = amount_to_hr_string(amount, true);
|
||||
let desc_text = t!("wallets.invoice_desc","amount" => amount_format);
|
||||
ui.label(RichText::new(desc_text).size(16.0).color(Colors::INACTIVE_TEXT));
|
||||
ui.add_space(6.0);
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(3.0);
|
||||
|
||||
// Draw invoice request text.
|
||||
ScrollArea::vertical()
|
||||
.max_height(128.0)
|
||||
.id_source(Id::from("receive_input").with(wallet.get_config().id))
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.add_space(7.0);
|
||||
egui::TextEdit::multiline(&mut self.request_edit)
|
||||
.font(egui::TextStyle::Small)
|
||||
.desired_rows(5)
|
||||
.interactive(false)
|
||||
.hint_text(SLATEPACK_MESSAGE_HINT)
|
||||
.desired_width(f32::INFINITY)
|
||||
.show(ui);
|
||||
ui.add_space(6.0);
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Draw copy button.
|
||||
let copy_text = format!("{} {}", COPY, t!("copy"));
|
||||
View::button(ui, copy_text, Colors::BUTTON, || {
|
||||
cb.copy_string_to_buffer(self.request_edit.clone());
|
||||
});
|
||||
});
|
||||
|
||||
// Draw button to close modal.
|
||||
ui.add_space(12.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::WHITE, || {
|
||||
self.amount_edit = "".to_string();
|
||||
self.request_edit = "".to_string();
|
||||
modal.close();
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,10 @@ impl WalletTab for WalletSend {
|
||||
|
||||
fn ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut eframe::Frame,
|
||||
_: &mut eframe::Frame,
|
||||
wallet: &mut Wallet,
|
||||
_: &dyn PlatformCallbacks) {
|
||||
if WalletContent::sync_ui(ui, frame, wallet) {
|
||||
if WalletContent::sync_ui(ui, wallet) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user