diff --git a/locale/de.json b/locale/de.json index 0ac2d52..c94cd65 100644 --- a/locale/de.json +++ b/locale/de.json @@ -122,7 +122,7 @@ "weakaura-updates-queued": "Aktualisierung in Warteschlange. Aktualisierung im Spiel erforderlich.", "weakauras-loaded": "{number} geladen", "website": "Website", - "website-http": "https://getgrin.com", + "website-http": "https://grin.mw", "welcome-to-grin-gui-description": "Bitte wähle dein Verzeichnis", "woops": "Ups!", "wtf": "WTF", @@ -153,7 +153,7 @@ "auto-update": "Neue Updates automatisch anwenden, wenn sie verfügbar sind", "type": "Typ", "donate": "Unterstützen", - "donate-http": "https://www.getajour.com/donate", + "donate-http": "https://grin.mw/fund", "close-to-tray": "Schließen in der Systemablage", "toggle-autostart": "Grin automatisch nach der Anmeldung öffnen", "start-closed-to-tray": "Start Grin geschlossen in System Tray", diff --git a/locale/en.json b/locale/en.json index 0654ed9..a68e0cd 100644 --- a/locale/en.json +++ b/locale/en.json @@ -122,7 +122,7 @@ "weakaura-updates-queued": "Updates can now be applied in-game", "weakauras-loaded": "{number} loaded", "website": "Website", - "website-http": "https://getgrin.com", + "website-http": "https://grin.mw", "welcome-to-grin-gui-description": "Please select your directory", "woops": "Woops!", "wtf": "WTF", @@ -153,7 +153,7 @@ "auto-update": "Automatically apply new updates when available", "type": "Type", "donate": "Donate", - "donate-http": "https://www.getajour.com/donate", + "donate-http": "https://grin.mw/fund", "close-to-tray": "Close to System Tray", "toggle-autostart": "Open Grin automatically after you log in", "start-closed-to-tray": "Start Grin closed to System Tray", diff --git a/resources/linux/ajour.desktop b/resources/linux/grin-gui.desktop similarity index 63% rename from resources/linux/ajour.desktop rename to resources/linux/grin-gui.desktop index 0650a9b..6648f4d 100644 --- a/resources/linux/ajour.desktop +++ b/resources/linux/grin-gui.desktop @@ -1,7 +1,7 @@ [Desktop Entry] Type=Application Terminal=false -Exec=ajour -Name=ajour -Icon=ajour +Exec=grin-gui +Name=grin-gui +Icon=grin Categories=Game; diff --git a/resources/logo/1024x1024/ajour.png b/resources/logo/1024x1024/grin.png similarity index 100% rename from resources/logo/1024x1024/ajour.png rename to resources/logo/1024x1024/grin.png diff --git a/resources/logo/1024x1024/ajour_macos.png b/resources/logo/1024x1024/grin_macos.png similarity index 100% rename from resources/logo/1024x1024/ajour_macos.png rename to resources/logo/1024x1024/grin_macos.png diff --git a/resources/logo/128x128/ajour.png b/resources/logo/128x128/grin.png similarity index 100% rename from resources/logo/128x128/ajour.png rename to resources/logo/128x128/grin.png diff --git a/resources/logo/16x16/ajour.png b/resources/logo/16x16/grin.png similarity index 100% rename from resources/logo/16x16/ajour.png rename to resources/logo/16x16/grin.png diff --git a/resources/logo/24x24/ajour.png b/resources/logo/24x24/grin.png similarity index 100% rename from resources/logo/24x24/ajour.png rename to resources/logo/24x24/grin.png diff --git a/resources/logo/256x256/ajour.png b/resources/logo/256x256/grin.png similarity index 100% rename from resources/logo/256x256/ajour.png rename to resources/logo/256x256/grin.png diff --git a/resources/logo/32x32/ajour.png b/resources/logo/32x32/grin.png similarity index 100% rename from resources/logo/32x32/ajour.png rename to resources/logo/32x32/grin.png diff --git a/resources/logo/48x48/ajour.png b/resources/logo/48x48/grin.png similarity index 100% rename from resources/logo/48x48/ajour.png rename to resources/logo/48x48/grin.png diff --git a/resources/logo/512x512/ajour.png b/resources/logo/512x512/grin.png similarity index 100% rename from resources/logo/512x512/ajour.png rename to resources/logo/512x512/grin.png diff --git a/resources/logo/64x64/ajour.png b/resources/logo/64x64/grin.png similarity index 100% rename from resources/logo/64x64/ajour.png rename to resources/logo/64x64/grin.png diff --git a/resources/windows/ajour.ico b/resources/windows/grin.ico similarity index 100% rename from resources/windows/ajour.ico rename to resources/windows/grin.ico diff --git a/resources/windows/res.rc b/resources/windows/res.rc index 184cc42..63865be 100644 --- a/resources/windows/res.rc +++ b/resources/windows/res.rc @@ -1,3 +1,3 @@ #define IDI_ICON 0x101 -IDI_ICON ICON "ajour.ico" \ No newline at end of file +IDI_ICON ICON "grin.ico" \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index 96312a7..7b99557 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,63 +2,63 @@ use crate::VERSION; use isahc::http::Uri; use structopt::{ - clap::{self, AppSettings}, - StructOpt, + clap::{self, AppSettings}, + StructOpt, }; use std::env; use std::path::PathBuf; pub fn get_opts() -> Result { - let args = env::args_os(); + let args = env::args_os(); - Opts::from_iter_safe(args) + Opts::from_iter_safe(args) } #[allow(unused_variables)] pub fn validate_opts_or_exit( - opts_result: Result, - is_cli: bool, - is_debug: bool, + opts_result: Result, + is_cli: bool, + is_debug: bool, ) -> Opts { - // If an error, we need to setup the AttachConsole fix for Windows release - // so we can exit and display the error message to the user. - let is_opts_error = opts_result.is_err(); + // If an error, we need to setup the AttachConsole fix for Windows release + // so we can exit and display the error message to the user. + let is_opts_error = opts_result.is_err(); - // Workaround to output to console even though we compile with windows_subsystem = "windows" - // in release mode - #[cfg(target_os = "windows")] - { - if (is_cli || is_opts_error) && !is_debug { - use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS}; + // Workaround to output to console even though we compile with windows_subsystem = "windows" + // in release mode + #[cfg(target_os = "windows")] + { + if (is_cli || is_opts_error) && !is_debug { + use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS}; - unsafe { - AttachConsole(ATTACH_PARENT_PROCESS); - } - } - } + unsafe { + AttachConsole(ATTACH_PARENT_PROCESS); + } + } + } - // Now that the windows fix is successfully setup, we can safely exit on the error - // and it will display properly to the user, or carry forward with the program - // and properly display our logging to the user. Since `e.exit()` returns a `!`, - // we can return the Ok(Opts) and carry forward with our program. - match opts_result { - Ok(opts) => opts, - Err(e) => { - // Apparently on `--version`, there is no "error message" that gets displayed - // like with `--help` and actual clap errors. It gets printed before we - // ever hit our console fix for windows, so let's manually print it - // before exiting - #[cfg(target_os = "windows")] - { - if !is_debug && e.kind == clap::ErrorKind::VersionDisplayed { - print!("Grin GUI {}", VERSION); - } - } + // Now that the windows fix is successfully setup, we can safely exit on the error + // and it will display properly to the user, or carry forward with the program + // and properly display our logging to the user. Since `e.exit()` returns a `!`, + // we can return the Ok(Opts) and carry forward with our program. + match opts_result { + Ok(opts) => opts, + Err(e) => { + // Apparently on `--version`, there is no "error message" that gets displayed + // like with `--help` and actual clap errors. It gets printed before we + // ever hit our console fix for windows, so let's manually print it + // before exiting + #[cfg(target_os = "windows")] + { + if !is_debug && e.kind == clap::ErrorKind::VersionDisplayed { + print!("Grin GUI {}", VERSION); + } + } - e.exit(); - } - } + e.exit(); + } + } } #[derive(Debug, StructOpt)] @@ -68,64 +68,15 @@ pub fn validate_opts_or_exit( author = env!("CARGO_PKG_AUTHORS"), setting = AppSettings::DisableHelpSubcommand)] pub struct Opts { - #[structopt(long = "data", help = "Path to a custom data directory for the app")] - pub data_directory: Option, - #[structopt(long = "aa", help = "Enable / Disable Anti-aliasing (true / false)")] - pub antialiasing: Option, - #[structopt(subcommand)] - pub command: Option, - #[structopt(long, hidden = true)] - pub self_update_temp: Option, + #[structopt(long = "data", help = "Path to a custom data directory for the app")] + pub data_directory: Option, + #[structopt(long = "aa", help = "Enable / Disable Anti-aliasing (true / false)")] + pub antialiasing: Option, + #[structopt(subcommand)] + pub command: Option, + #[structopt(long, hidden = true)] + pub self_update_temp: Option, } #[derive(Debug, StructOpt)] -pub enum Command { - /// Update all addons, WeakAura and Plater auras - Update, - /// Update all addons - UpdateAddons, - /// Update all WeakAura and Plater auras - UpdateAuras, - /// Install an addon - Install { - #[structopt()] - /// source url [Github & Gitlab currently supported] - url: Uri, - }, - /// Backup your WTF and/or AddOns folders - Backup { - #[structopt(short, long, default_value = "all", parse(try_from_str = str_to_backup_folder), possible_values = &["all","wtf","addons","config", "screenshots", "fonts"])] - /// folder to backup - backup_folder: BackupFolder, - #[structopt()] - /// folder to save backups to - destination: PathBuf, - }, - /// Add a World of Warcraft path - PathAdd { - /// path to the World of Warcraft directory - path: PathBuf, - }, -} - -#[derive(Debug, Clone, Copy)] -pub enum BackupFolder { - All, - AddOns, - Wtf, - Config, - Screenshots, - Fonts, -} - -fn str_to_backup_folder(s: &str) -> Result { - match s { - "all" => Ok(BackupFolder::All), - "wtf" => Ok(BackupFolder::Wtf), - "addons" => Ok(BackupFolder::AddOns), - "config" => Ok(BackupFolder::Config), - "screenshots" => Ok(BackupFolder::Screenshots), - "fonts" => Ok(BackupFolder::Fonts), - _ => Err("valid values are ['all','wtf','addons','config','screenshots','fonts']"), - } -} +pub enum Command {} diff --git a/src/gui/element/about.rs b/src/gui/element/about.rs index d5f8dee..e72e605 100644 --- a/src/gui/element/about.rs +++ b/src/gui/element/about.rs @@ -1,95 +1,104 @@ use { - super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING}, - crate::gui::{Interaction, Message}, - crate::localization::localized_string, - grin_gui_core::theme::{Button, Column, Container, Element, PickList, Row, Scrollable, Text}, - grin_gui_core::{theme::ColorPalette, utility::Release}, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space, TextInput}, - iced::{alignment, Alignment, Command, Length}, - std::collections::HashMap, - strfmt::strfmt, + super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING}, + crate::gui::{Interaction, Message}, + crate::localization::localized_string, + grin_gui_core::theme::{Button, Column, Container, Element, PickList, Row, Scrollable, Text}, + grin_gui_core::{theme::ColorPalette, utility::Release}, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space, TextInput}, + iced::{alignment, Alignment, Command, Length}, + std::collections::HashMap, + strfmt::strfmt, }; pub struct StateContainer {} impl Default for StateContainer { - fn default() -> Self { - Self {} - } + fn default() -> Self { + Self {} + } } pub fn data_container<'a>( - release: &Option, - state: &'a StateContainer, + release: &Option, + state: &'a StateContainer, ) -> Container<'a, Message> { - let grin_gui_title = Text::new(localized_string("grin")).size(DEFAULT_HEADER_FONT_SIZE); - let grin_gui_title_container = Container::new(grin_gui_title) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let grin_gui_title = Text::new(localized_string("grin")).size(DEFAULT_HEADER_FONT_SIZE); + let grin_gui_title_container = Container::new(grin_gui_title) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let changelog_title_text = Text::new(if let Some(release) = release { - let mut vars = HashMap::new(); - // TODO (casperstorm): change "addon" to "tag" or "version". - vars.insert("addon".to_string(), &release.tag_name); - let fmt = localized_string("changelog-for"); - strfmt(&fmt, &vars).unwrap() - } else { - localized_string("changelog") - }) - .size(DEFAULT_FONT_SIZE); + let changelog_title_text = Text::new(if let Some(release) = release { + let mut vars = HashMap::new(); + // TODO (casperstorm): change "addon" to "tag" or "version". + vars.insert("addon".to_string(), &release.tag_name); + let fmt = localized_string("changelog-for"); + strfmt(&fmt, &vars).unwrap() + } else { + localized_string("changelog") + }) + .size(DEFAULT_FONT_SIZE); - let changelog_text = Text::new(if let Some(release) = release { - release.body.clone() - } else { - localized_string("no-changelog") - }) - .size(DEFAULT_FONT_SIZE); + let changelog_text = Text::new(if let Some(release) = release { + release.body.clone() + } else { + localized_string("no-changelog") + }) + .size(DEFAULT_FONT_SIZE); - let website_button: Element = - Button::new(Text::new(localized_string("website")).size(DEFAULT_FONT_SIZE)) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::OpenLink(localized_string("website-http"))) - .into(); + let website_button: Element = + Button::new(Text::new(localized_string("website")).size(DEFAULT_FONT_SIZE)) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::OpenLink(localized_string("website-http"))) + .into(); - let donation_button: Element = - Button::new(Text::new(localized_string("donate")).size(DEFAULT_FONT_SIZE)) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::OpenLink(localized_string("donate-http"))) - .into(); + let donation_button: Element = + Button::new(Text::new(localized_string("donate")).size(DEFAULT_FONT_SIZE)) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::OpenLink(localized_string("donate-http"))) + .into(); - let button_row = Row::new() - .spacing(DEFAULT_PADDING) - .push(website_button.map(Message::Interaction)) - .push(donation_button.map(Message::Interaction)); + let button_row = Row::new() + .spacing(DEFAULT_PADDING) + .push(website_button.map(Message::Interaction)) + .push(donation_button.map(Message::Interaction)); - let changelog_text_container = Container::new(changelog_text) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let changelog_title_container = Container::new(changelog_title_text) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let changelog_text_container = Container::new(changelog_text) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let changelog_title_container = Container::new(changelog_title_text) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let column = Column::new() - .spacing(1) - .push(grin_gui_title_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(DEFAULT_PADDING))) - .push(button_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(DEFAULT_PADDING))) - .push(changelog_title_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(changelog_text_container); + let column = Column::new() + .spacing(1) + .push(grin_gui_title_container) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(DEFAULT_PADDING), + )) + .push(button_row) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(DEFAULT_PADDING), + )) + .push(changelog_title_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(changelog_text_container); - let mut scrollable = Scrollable::new(column) - .height(Length::FillPortion(1)) - .style(grin_gui_core::theme::ScrollableStyle::Primary); + let mut scrollable = Scrollable::new(column) + .height(Length::FillPortion(1)) + .style(grin_gui_core::theme::ScrollableStyle::Primary); - let col = Column::new().push(scrollable); - let row = Row::new() - .push(Space::new(Length::Fixed(DEFAULT_PADDING), Length::Fixed(0.0))) - .push(col); + let col = Column::new().push(scrollable); + let row = Row::new() + .push(Space::new( + Length::Fixed(DEFAULT_PADDING), + Length::Fixed(0.0), + )) + .push(col); - // Returns the final container. - Container::new(row) - .center_x() - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) - .padding(20) + // Returns the final container. + Container::new(row) + .center_x() + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + .padding(20) } diff --git a/src/gui/element/mod.rs b/src/gui/element/mod.rs index 845d289..6069014 100644 --- a/src/gui/element/mod.rs +++ b/src/gui/element/mod.rs @@ -1,9 +1,9 @@ -pub mod menu; pub mod about; +pub mod menu; +pub mod modal; +pub mod node; pub mod settings; pub mod wallet; -pub mod node; -pub mod modal; // Default values used on multiple elements. pub static SMALLER_FONT_SIZE: u16 = 12; @@ -13,4 +13,4 @@ pub static DEFAULT_SUB_HEADER_FONT_SIZE: u16 = 18; pub static DEFAULT_PADDING: f32 = 10.0; pub static BUTTON_WIDTH: f32 = 84.0; -pub static BUTTON_HEIGHT: f32 = 16.0; \ No newline at end of file +pub static BUTTON_HEIGHT: f32 = 16.0; diff --git a/src/gui/element/modal.rs b/src/gui/element/modal.rs index a29ddc5..4a0ca1b 100644 --- a/src/gui/element/modal.rs +++ b/src/gui/element/modal.rs @@ -3,130 +3,125 @@ use iced::Renderer; use super::{BUTTON_HEIGHT, BUTTON_WIDTH}; use { - super::super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, SMALLER_FONT_SIZE}, - crate::gui::{Interaction, Message}, - crate::localization::localized_string, - grin_gui_core::theme::ColorPalette, - grin_gui_core::theme::{Card, Container, Column, Button, Element, Scrollable, Text, PickList, Row}, - iced::{alignment, Alignment, Command, Length}, - iced::widget::{ - button, pick_list, scrollable, text_input, Checkbox, Space, TextInput, - }, - iced_aw::{modal, Modal}, + super::super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, SMALLER_FONT_SIZE}, + crate::gui::{Interaction, Message}, + crate::localization::localized_string, + grin_gui_core::theme::ColorPalette, + grin_gui_core::theme::{ + Button, Card, Column, Container, Element, PickList, Row, Scrollable, Text, + }, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space, TextInput}, + iced::{alignment, Alignment, Command, Length}, + iced_aw::{modal, Modal}, }; -pub struct StateContainer { -} +pub struct StateContainer {} impl Default for StateContainer { - fn default() -> Self { - Self { - } - } + fn default() -> Self { + Self {} + } } pub fn exit_card() -> Card<'static, Message> { - let button_height = Length::Fixed(BUTTON_HEIGHT); - let button_width = Length::Fixed(BUTTON_WIDTH); + let button_height = Length::Fixed(BUTTON_HEIGHT); + let button_width = Length::Fixed(BUTTON_WIDTH); - let yes_button_label = - Container::new(Text::new(localized_string("yes")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); + let yes_button_label = + Container::new(Text::new(localized_string("yes")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); - let cancel_button_label = - Container::new(Text::new(localized_string("no")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); + let cancel_button_label = + Container::new(Text::new(localized_string("no")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); - let yes_button: Element = Button::new( yes_button_label) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::Exit) - .into(); + let yes_button: Element = Button::new(yes_button_label) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::Exit) + .into(); - let cancel_button: Element = - Button::new( cancel_button_label) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::ExitCancel) - .into(); + let cancel_button: Element = Button::new(cancel_button_label) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::ExitCancel) + .into(); - let unit_spacing = 15.0; + let unit_spacing = 15.0; - // button lipstick - let yes_container = Container::new(yes_button.map(Message::Interaction)).padding(1); - let yes_container = Container::new(yes_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); - let cancel_container = Container::new(cancel_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); + // button lipstick + let yes_container = Container::new(yes_button.map(Message::Interaction)).padding(1); + let yes_container = Container::new(yes_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); + let cancel_container = Container::new(cancel_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); - let button_row = Row::new() - .push(yes_container) - .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) - .push(cancel_container); + let button_row = Row::new() + .push(yes_container) + .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) + .push(cancel_container); - Card::new( - Text::new(localized_string("exit-confirm-title")) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center), - Text::new(localized_string("exit-confirm-msg")).size(DEFAULT_FONT_SIZE), - ) - .foot( - Column::new() - .spacing(10) - .padding(5) - .width(Length::Fill) - .align_items(Alignment::Center) - .push(button_row), - ) - .max_width(500.0) - .on_close(Message::Interaction(Interaction::ExitCancel)) - .style(grin_gui_core::theme::CardStyle::Normal) + Card::new( + Text::new(localized_string("exit-confirm-title")) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center), + Text::new(localized_string("exit-confirm-msg")).size(DEFAULT_FONT_SIZE), + ) + .foot( + Column::new() + .spacing(10) + .padding(5) + .width(Length::Fill) + .align_items(Alignment::Center) + .push(button_row), + ) + .max_width(500.0) + .on_close(Message::Interaction(Interaction::ExitCancel)) + .style(grin_gui_core::theme::CardStyle::Normal) } -pub fn error_card( - error_cause: String, -) -> Card<'static, Message> { - Card::new( - Text::new(localized_string("error-detail")).size(DEFAULT_HEADER_FONT_SIZE), - Text::new(error_cause.clone()).size(DEFAULT_FONT_SIZE), - ) - .foot( - Column::new() - .spacing(10) - .padding(5) - .width(Length::Fill) - .align_items(Alignment::Center) - .push( - Button::new( - Text::new(localized_string("ok-caps")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center), - ) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Message::Interaction(Interaction::CloseErrorModal)), - ) - .push( - Button::new( - Text::new(localized_string("copy-to-clipboard")) - .size(SMALLER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center), - ) - .style(grin_gui_core::theme::ButtonStyle::NormalText) - .on_press(Message::Interaction(Interaction::WriteToClipboard( - error_cause, - ))), - ), - ) - .max_width(500.0) - .on_close(Message::Interaction(Interaction::CloseErrorModal)) - .style(grin_gui_core::theme::CardStyle::Normal) +pub fn error_card(error_cause: String) -> Card<'static, Message> { + Card::new( + Text::new(localized_string("error-detail")).size(DEFAULT_HEADER_FONT_SIZE), + Text::new(error_cause.clone()).size(DEFAULT_FONT_SIZE), + ) + .foot( + Column::new() + .spacing(10) + .padding(5) + .width(Length::Fill) + .align_items(Alignment::Center) + .push( + Button::new( + Text::new(localized_string("ok-caps")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center), + ) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Message::Interaction(Interaction::CloseErrorModal)), + ) + .push( + Button::new( + Text::new(localized_string("copy-to-clipboard")) + .size(SMALLER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center), + ) + .style(grin_gui_core::theme::ButtonStyle::NormalText) + .on_press(Message::Interaction(Interaction::WriteToClipboard( + error_cause, + ))), + ), + ) + .max_width(500.0) + .on_close(Message::Interaction(Interaction::CloseErrorModal)) + .style(grin_gui_core::theme::CardStyle::Normal) } diff --git a/src/gui/element/node/embedded/mod.rs b/src/gui/element/node/embedded/mod.rs index e423d95..151b76f 100644 --- a/src/gui/element/node/embedded/mod.rs +++ b/src/gui/element/node/embedded/mod.rs @@ -3,73 +3,73 @@ use crate::gui::element::DEFAULT_PADDING; pub mod summary; use { - crate::gui::{GrinGui, Message}, - crate::Result, - grin_gui_core::node::ChainTypes, - grin_gui_core::node::ServerStats, - grin_gui_core::theme::ColorPalette, - grin_gui_core::theme::{Column, Container}, - iced::widget::container, - iced::{Command, Length}, + crate::gui::{GrinGui, Message}, + crate::Result, + grin_gui_core::node::ChainTypes, + grin_gui_core::node::ServerStats, + grin_gui_core::theme::ColorPalette, + grin_gui_core::theme::{Column, Container}, + iced::widget::container, + iced::{Command, Length}, }; pub struct StateContainer { - pub mode: Mode, - pub server_stats: Option, - pub summary_state: summary::StateContainer, + pub mode: Mode, + pub server_stats: Option, + pub summary_state: summary::StateContainer, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Mode { - Summary, + Summary, - Peers, - // etc as in TUI + Peers, + // etc as in TUI } impl Default for StateContainer { - fn default() -> Self { - Self { - mode: Mode::Summary, - server_stats: None, - summary_state: Default::default(), - } - } + fn default() -> Self { + Self { + mode: Mode::Summary, + server_stats: None, + summary_state: Default::default(), + } + } } #[derive(Debug, Clone)] pub enum LocalViewInteraction {} pub fn handle_message( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - Ok(Command::none()) + Ok(Command::none()) } pub fn data_container<'a>( - state: &'a StateContainer, - chain_type: ChainTypes, + state: &'a StateContainer, + chain_type: ChainTypes, ) -> Container<'a, Message> { - let content = match state.mode { - Mode::Summary => { - summary::data_container(&state.summary_state, &state.server_stats, chain_type) - } - _ => Container::new(Column::new()), - }; + let content = match state.mode { + Mode::Summary => { + summary::data_container(&state.summary_state, &state.server_stats, chain_type) + } + _ => Container::new(Column::new()), + }; - let column = Column::new().push(content); + let column = Column::new().push(content); - Container::new(column) - .center_y() - .center_x() - .width(Length::Fill) - .height(Length::Fill) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) - .padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + Container::new(column) + .center_y() + .center_x() + .width(Length::Fill) + .height(Length::Fill) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + .padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/node/mod.rs b/src/gui/element/node/mod.rs index 73434ff..f12bf18 100644 --- a/src/gui/element/node/mod.rs +++ b/src/gui/element/node/mod.rs @@ -1,52 +1,51 @@ pub mod embedded; use { - crate::gui::{Message}, - grin_gui_core::{theme::ColorPalette, node::ChainTypes}, - iced::Length, - grin_gui_core::theme::{Container, Column}, + crate::gui::Message, + grin_gui_core::theme::{Column, Container}, + grin_gui_core::{node::ChainTypes, theme::ColorPalette}, + iced::Length, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Mode { - Embedded, - // etc, as in TUI for now + Embedded, + // etc, as in TUI for now } pub struct StateContainer { - pub mode: Mode, - //pub external_state: external::StateContainer, - pub embedded_state: embedded::StateContainer, + pub mode: Mode, + //pub external_state: external::StateContainer, + pub embedded_state: embedded::StateContainer, } impl Default for StateContainer { - fn default() -> Self { - Self { - mode: Mode::Embedded, - embedded_state: Default::default(), - } - } + fn default() -> Self { + Self { + mode: Mode::Embedded, + embedded_state: Default::default(), + } + } } -impl StateContainer { -} +impl StateContainer {} pub fn data_container<'a>( - state: &'a StateContainer, - chain_type: ChainTypes, + state: &'a StateContainer, + chain_type: ChainTypes, ) -> Container<'a, Message> { - let content = match state.mode { - Mode::Embedded => embedded::data_container(&state.embedded_state, chain_type), - //_ => Container::new(Column::new()), - }; - - let column = Column::new() - //.push(Space::new(Length::Fixed(0.0), Length::Fixed(20))) - .push(content); - - Container::new(column) - .center_y() - .center_x() - .width(Length::Fill) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + let content = match state.mode { + Mode::Embedded => embedded::data_container(&state.embedded_state, chain_type), + //_ => Container::new(Column::new()), + }; + + let column = Column::new() + //.push(Space::new(Length::Fixed(0.0), Length::Fixed(20))) + .push(content); + + Container::new(column) + .center_y() + .center_x() + .width(Length::Fill) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) } diff --git a/src/gui/element/settings/general.rs b/src/gui/element/settings/general.rs index 0decc44..00d4d24 100644 --- a/src/gui/element/settings/general.rs +++ b/src/gui/element/settings/general.rs @@ -2,555 +2,555 @@ use futures::future; use grin_gui_core::config::Currency; use { - super::{DEFAULT_FONT_SIZE, DEFAULT_PADDING}, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::{localized_string, LANG}, - crate::{log_error, Result}, - anyhow::Context, - grin_gui_core::{ - config::{Config, Language}, - error::ThemeError, - fs::{import_theme, PersistentData}, - theme::{ - Button, ColorPalette, Column, Container, Element, PickList, Row, Scrollable, Text, - TextInput, Theme, - }, - }, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::{Alignment, Command, Length}, - std::sync::{Arc, RwLock}, + super::{DEFAULT_FONT_SIZE, DEFAULT_PADDING}, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::{localized_string, LANG}, + crate::{log_error, Result}, + anyhow::Context, + grin_gui_core::{ + config::{Config, Language}, + error::ThemeError, + fs::{import_theme, PersistentData}, + theme::{ + Button, ColorPalette, Column, Container, Element, PickList, Row, Scrollable, Text, + TextInput, Theme, + }, + }, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::{Alignment, Command, Length}, + std::sync::{Arc, RwLock}, }; #[derive(Debug, Clone)] pub struct StateContainer { - pub theme_state: ThemeState, - pub scale_state: ScaleState, - //scrollable_state: scrollable::State, - //localization_picklist_state: pick_list::State, + pub theme_state: ThemeState, + pub scale_state: ScaleState, + //scrollable_state: scrollable::State, + //localization_picklist_state: pick_list::State, } impl Default for StateContainer { - fn default() -> Self { - Self { - theme_state: Default::default(), - //scrollable_state: Default::default(), - scale_state: Default::default(), - //localization_picklist_state: Default::default(), - } - } + fn default() -> Self { + Self { + theme_state: Default::default(), + //scrollable_state: Default::default(), + scale_state: Default::default(), + //localization_picklist_state: Default::default(), + } + } } #[derive(Debug, Clone)] pub struct ThemeState { - pub themes: Vec<(String, Theme)>, - pub current_theme_name: String, - //pick_list_state: pick_list::State, - //input_state: text_input::State, - input_url: String, - //import_button_state: button::State, - //open_builder_button_state: button::State, + pub themes: Vec<(String, Theme)>, + pub current_theme_name: String, + //pick_list_state: pick_list::State, + //input_state: text_input::State, + input_url: String, + //import_button_state: button::State, + //open_builder_button_state: button::State, } impl Default for ThemeState { - fn default() -> Self { - let themes = Theme::all(); - - ThemeState { - themes, - current_theme_name: "Dark".to_string(), - //pick_list_state: Default::default(), - //input_state: Default::default(), - input_url: Default::default(), - //import_button_state: Default::default(), - //open_builder_button_state: Default::default(), - } - } + fn default() -> Self { + let themes = Theme::all(); + + ThemeState { + themes, + current_theme_name: "Dark".to_string(), + //pick_list_state: Default::default(), + //input_state: Default::default(), + input_url: Default::default(), + //import_button_state: Default::default(), + //open_builder_button_state: Default::default(), + } + } } #[derive(Debug, Clone)] pub struct ScaleState { - pub scale: f64, - //up_btn_state: button::State, - //down_btn_state: button::State, + pub scale: f64, + //up_btn_state: button::State, + //down_btn_state: button::State, } impl Default for ScaleState { - fn default() -> Self { - ScaleState { - scale: 1.0, - //up_btn_state: Default::default(), - //down_btn_state: Default::default(), - } - } + fn default() -> Self { + ScaleState { + scale: 1.0, + //up_btn_state: Default::default(), + //down_btn_state: Default::default(), + } + } } #[derive(Debug, Clone)] pub enum LocalViewInteraction { - ThemeSelected(String), - CurrencySelected(Currency), - LanguageSelected(Language), - ScaleUp, - ScaleDown, - ThemeUrlInput(String), - ImportTheme, - ThemeImportedOk((String, Vec)), - ThemeImportedError(Arc>>), + ThemeSelected(String), + CurrencySelected(Currency), + LanguageSelected(Language), + ScaleUp, + ScaleDown, + ThemeUrlInput(String), + ImportTheme, + ThemeImportedOk((String, Vec)), + ThemeImportedError(Arc>>), } #[derive(Debug, Clone)] pub enum Mode { - Wallet, - Node, - General, + Wallet, + Node, + General, } pub fn handle_message( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.general_settings_state; - match message { - LocalViewInteraction::CurrencySelected(currency) => { - log::debug!( - "settings::general::LocalViewInteraction::CurrencySelected({:?})", - ¤cy - ); - - grin_gui.config.currency = currency; - let _ = grin_gui.config.save(); - - return Ok(Command::perform(future::ready(()), |r| { - // update the prices - Message::Interaction(Interaction::WalletOperationHomeViewInteraction( + let state = &mut grin_gui.general_settings_state; + match message { + LocalViewInteraction::CurrencySelected(currency) => { + log::debug!( + "settings::general::LocalViewInteraction::CurrencySelected({:?})", + ¤cy + ); + + grin_gui.config.currency = currency; + let _ = grin_gui.config.save(); + + return Ok(Command::perform(future::ready(()), |r| { + // update the prices + Message::Interaction(Interaction::WalletOperationHomeViewInteraction( crate::gui::element::wallet::operation::home::LocalViewInteraction::UpdatePrices, )) - })); - } - LocalViewInteraction::ThemeSelected(theme_name) => { - log::debug!( - "settings::general::LocalViewInteraction::ThemeSelected({:?})", - &theme_name - ); - - // set theme for gui - let theme = &state - .theme_state - .themes - .iter() - .find(|x| theme_name == x.0) - .unwrap() - .1; - grin_gui.theme = theme.clone(); - - state.theme_state.current_theme_name = theme_name.clone(); - grin_gui.config.theme = Some(theme_name); - - let _ = grin_gui.config.save(); - } - LocalViewInteraction::LanguageSelected(lang) => { - log::debug!( - "settings::general::LocalViewInteraction::LanguageSelected({:?})", - &lang - ); - // Update config. - grin_gui.config.language = lang; - let _ = grin_gui.config.save(); - - // Update global LANG refcell. - *LANG.get().expect("LANG not set").write().unwrap() = lang.language_code(); - } - LocalViewInteraction::ScaleUp => { - let prev_scale = state.scale_state.scale; - - state.scale_state.scale = ((prev_scale + 0.1).min(2.0) * 10.0).round() / 10.0; - - grin_gui.config.scale = Some(state.scale_state.scale); - let _ = grin_gui.config.save(); - - log::debug!( - "settings::general::LocalViewInteraction::ScaleUp({} -> {})", - prev_scale, - state.scale_state.scale - ); - } - LocalViewInteraction::ScaleDown => { - let prev_scale = state.scale_state.scale; - - state.scale_state.scale = ((prev_scale - 0.1).max(0.5) * 10.0).round() / 10.0; - - grin_gui.config.scale = Some(state.scale_state.scale); - let _ = grin_gui.config.save(); - - log::debug!( - "settings::general::LocalViewInteraction::ScaleDown({} -> {})", - prev_scale, - state.scale_state.scale - ); - } - LocalViewInteraction::ThemeUrlInput(url) => { - state.theme_state.input_url = url; - } - LocalViewInteraction::ImportTheme => { - // Reset error - grin_gui.error.take(); - - let url = state.theme_state.input_url.clone(); - - log::debug!("Interaction::ImportTheme({})", &url); - - return Ok(Command::perform(import_theme(url), |r| { - match r.context("Failed to Import Theme") { - Ok(result) => { - Message::Interaction(Interaction::GeneralSettingsViewInteraction( - LocalViewInteraction::ThemeImportedOk(result), - )) - } - Err(mut e) => { - for cause in e.chain() { - if let Some(theme_error) = cause.downcast_ref::() { - if matches!(theme_error, ThemeError::NameCollision { .. }) { - e = e.context(localized_string( - "import-theme-error-name-collision", - )); - break; - } - } - } - Message::Interaction(Interaction::GeneralSettingsViewInteraction( - LocalViewInteraction::ThemeImportedError(Arc::new(RwLock::new(Some( - e, - )))), - )) - } - } - })); - } - LocalViewInteraction::ThemeImportedOk((new_theme_name, mut new_themes)) => { - log::debug!("Message::ThemeImported({})", &new_theme_name); - - state.theme_state = Default::default(); - - new_themes.sort(); - - for theme in new_themes { - state.theme_state.themes.push((theme.name.clone(), theme)); - } - - state.theme_state.current_theme_name = new_theme_name.clone(); - grin_gui.config.theme = Some(new_theme_name); - let _ = grin_gui.config.save(); - } - LocalViewInteraction::ThemeImportedError(err) => { - grin_gui.error = err.write().unwrap().take(); - if let Some(e) = grin_gui.error.as_ref() { - log_error(e); - } - // Reset text input - state.theme_state.input_url = Default::default(); - //state.theme_state.input_state = Default::default(); - } - } - Ok(Command::none()) + })); + } + LocalViewInteraction::ThemeSelected(theme_name) => { + log::debug!( + "settings::general::LocalViewInteraction::ThemeSelected({:?})", + &theme_name + ); + + // set theme for gui + let theme = &state + .theme_state + .themes + .iter() + .find(|x| theme_name == x.0) + .unwrap() + .1; + grin_gui.theme = theme.clone(); + + state.theme_state.current_theme_name = theme_name.clone(); + grin_gui.config.theme = Some(theme_name); + + let _ = grin_gui.config.save(); + } + LocalViewInteraction::LanguageSelected(lang) => { + log::debug!( + "settings::general::LocalViewInteraction::LanguageSelected({:?})", + &lang + ); + // Update config. + grin_gui.config.language = lang; + let _ = grin_gui.config.save(); + + // Update global LANG refcell. + *LANG.get().expect("LANG not set").write().unwrap() = lang.language_code(); + } + LocalViewInteraction::ScaleUp => { + let prev_scale = state.scale_state.scale; + + state.scale_state.scale = ((prev_scale + 0.1).min(2.0) * 10.0).round() / 10.0; + + grin_gui.config.scale = Some(state.scale_state.scale); + let _ = grin_gui.config.save(); + + log::debug!( + "settings::general::LocalViewInteraction::ScaleUp({} -> {})", + prev_scale, + state.scale_state.scale + ); + } + LocalViewInteraction::ScaleDown => { + let prev_scale = state.scale_state.scale; + + state.scale_state.scale = ((prev_scale - 0.1).max(0.5) * 10.0).round() / 10.0; + + grin_gui.config.scale = Some(state.scale_state.scale); + let _ = grin_gui.config.save(); + + log::debug!( + "settings::general::LocalViewInteraction::ScaleDown({} -> {})", + prev_scale, + state.scale_state.scale + ); + } + LocalViewInteraction::ThemeUrlInput(url) => { + state.theme_state.input_url = url; + } + LocalViewInteraction::ImportTheme => { + // Reset error + grin_gui.error.take(); + + let url = state.theme_state.input_url.clone(); + + log::debug!("Interaction::ImportTheme({})", &url); + + return Ok(Command::perform(import_theme(url), |r| { + match r.context("Failed to Import Theme") { + Ok(result) => { + Message::Interaction(Interaction::GeneralSettingsViewInteraction( + LocalViewInteraction::ThemeImportedOk(result), + )) + } + Err(mut e) => { + for cause in e.chain() { + if let Some(theme_error) = cause.downcast_ref::() { + if matches!(theme_error, ThemeError::NameCollision { .. }) { + e = e.context(localized_string( + "import-theme-error-name-collision", + )); + break; + } + } + } + Message::Interaction(Interaction::GeneralSettingsViewInteraction( + LocalViewInteraction::ThemeImportedError(Arc::new(RwLock::new(Some( + e, + )))), + )) + } + } + })); + } + LocalViewInteraction::ThemeImportedOk((new_theme_name, mut new_themes)) => { + log::debug!("Message::ThemeImported({})", &new_theme_name); + + state.theme_state = Default::default(); + + new_themes.sort(); + + for theme in new_themes { + state.theme_state.themes.push((theme.name.clone(), theme)); + } + + state.theme_state.current_theme_name = new_theme_name.clone(); + grin_gui.config.theme = Some(new_theme_name); + let _ = grin_gui.config.save(); + } + LocalViewInteraction::ThemeImportedError(err) => { + grin_gui.error = err.write().unwrap().take(); + if let Some(e) = grin_gui.error.as_ref() { + log_error(e); + } + // Reset text input + state.theme_state.input_url = Default::default(); + //state.theme_state.input_state = Default::default(); + } + } + Ok(Command::none()) } pub fn data_container<'a>(state: &'a StateContainer, config: &Config) -> Container<'a, Message> { - let language_container = { - let title = Container::new(Text::new(localized_string("language")).size(DEFAULT_FONT_SIZE)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let pick_list = PickList::new(&Language::ALL[..], Some(config.language), |l| { - Message::Interaction(Interaction::GeneralSettingsViewInteraction( - LocalViewInteraction::LanguageSelected(l), - )) - }) - .text_size(14) - .width(Length::Fixed(120.0)) - .style(grin_gui_core::theme::PickListStyle::Primary); - - let container = Container::new(pick_list) - .center_y() - .width(Length::Fixed(120.0)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - Column::new() - .push(title) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(container) - }; - - let currency_container = { - let title = Container::new(Text::new(localized_string("currency")).size(DEFAULT_FONT_SIZE)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let pick_list = PickList::new(&Currency::ALL[..], Some(config.currency), |c| { - Message::Interaction(Interaction::GeneralSettingsViewInteraction( - LocalViewInteraction::CurrencySelected(c), - )) - }) - .text_size(14) - .width(Length::Fixed(120.0)) - .style(grin_gui_core::theme::PickListStyle::Primary); - - let container = Container::new(pick_list) - .center_y() - .width(Length::Fixed(120.0)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - Column::new() - .push(title) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(container) - }; - - let theme_column = { - let title_container = - Container::new(Text::new(localized_string("theme")).size(DEFAULT_FONT_SIZE)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let theme_names = state - .theme_state - .themes - .iter() - .cloned() - .map(|(name, _)| name) - .collect::>(); - - let theme_pick_list = PickList::new( - theme_names, - Some(state.theme_state.current_theme_name.clone()), - |t| { - Message::Interaction(Interaction::GeneralSettingsViewInteraction( - LocalViewInteraction::ThemeSelected(t), - )) - }, - ) - .text_size(DEFAULT_FONT_SIZE) - .width(Length::Fixed(120.0)) - .style(grin_gui_core::theme::PickListStyle::Primary); - - // Data row for theme picker list. - let theme_data_row = Row::new() - .push(theme_pick_list) - .align_items(Alignment::Center) - .height(Length::Fixed(26.0)); - - Column::new() - .push(title_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(theme_data_row) - }; - - // Scale buttons for application scale factoring. - let scale_column = { - let title_container = - Container::new(Text::new(localized_string("scale")).size(DEFAULT_FONT_SIZE)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let scale_title_row = Row::new().push(title_container); - - let scale_down_button: Element = Button::new( - // &mut state.scale_state.down_btn_state, - Text::new(" - ").size(DEFAULT_FONT_SIZE), - ) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::GeneralSettingsViewInteraction( - LocalViewInteraction::ScaleDown, - )) - .into(); - - let scale_up_button: Element = Button::new( - // &mut state.scale_state.up_btn_state, - Text::new(" + ").size(DEFAULT_FONT_SIZE), - ) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::GeneralSettingsViewInteraction( - LocalViewInteraction::ScaleUp, - )) - .into(); - - let current_scale_text = Text::new(format!(" {:.2} ", state.scale_state.scale)) - .size(DEFAULT_FONT_SIZE) - .vertical_alignment(iced_core::alignment::Vertical::Center); - let current_scale_container = Container::new(current_scale_text) - .height(Length::Fill) - .center_y() - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let scale_buttons_row = Row::new() - .push(scale_down_button.map(Message::Interaction)) - .push(current_scale_container) - .push(scale_up_button.map(Message::Interaction)) - .align_items(Alignment::Center) - .height(Length::Fixed(26.0)); - - Column::new() - .push(scale_title_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(scale_buttons_row) - }; - - let import_theme_column = { - let title_container = - Container::new(Text::new(localized_string("import-theme")).size(DEFAULT_FONT_SIZE)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let theme_input = TextInput::new( - &localized_string("paste-url")[..], - &state.theme_state.input_url, - //|s| Interaction::GeneralSettingsViewInteraction(LocalViewInteraction::ThemeUrlInput(s)), - ) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(185.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); - - let theme_input: Element = theme_input.into(); - - let mut import_button = Button::new( - // &mut state.theme_state.import_button_state, - Text::new(localized_string("import-theme-button")).size(DEFAULT_FONT_SIZE), - ) - .style(grin_gui_core::theme::ButtonStyle::Bordered); - - if !state.theme_state.input_url.is_empty() { - import_button = import_button.on_press(Interaction::GeneralSettingsViewInteraction( - LocalViewInteraction::ImportTheme, - )); - } - - let import_button: Element = import_button.into(); - - let theme_input_row = Row::new() - .push(theme_input.map(Message::Interaction)) - .push(import_button.map(Message::Interaction)) - .spacing(DEFAULT_PADDING) - .align_items(Alignment::Center) - .height(Length::Fixed(26.0)); - - Column::new() - .push(title_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(theme_input_row) - }; - - let open_theme_row = { - let open_button = Button::new( - // &mut state.theme_state.open_builder_button_state, - Text::new(localized_string("open-theme-builder")).size(DEFAULT_FONT_SIZE), - ) - .on_press(Interaction::OpenLink(String::from( - "https://theme.getajour.com", - ))) - .style(grin_gui_core::theme::ButtonStyle::Bordered); - - let open_button: Element = open_button.into(); - - Row::new() - .push(open_button.map(Message::Interaction)) - .align_items(Alignment::Center) - .height(Length::Fixed(26.0)) - }; - - let theme_scale_row = Row::new() - .push(theme_column) - .push(scale_column) - .push(import_theme_column) - .spacing(DEFAULT_PADDING); - - #[cfg(target_os = "windows")] - let close_to_tray_column = { - let checkbox = Checkbox::new( - localized_string("close-to-tray"), - config.close_to_tray, - Interaction::ToggleCloseToTray, - ) - .style(grin_gui_core::theme::CheckboxStyle::Normal) - .text_size(DEFAULT_FONT_SIZE) - .spacing(5); - - let checkbox: Element = checkbox.into(); - - let checkbox_container = Container::new(checkbox.map(Message::Interaction)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - Column::new().push(checkbox_container) - }; - - #[cfg(target_os = "windows")] - let toggle_autostart_column = { - let checkbox = Checkbox::new( - localized_string("toggle-autostart"), - config.autostart, - Interaction::ToggleAutoStart, - ) - .style(grin_gui_core::theme::CheckboxStyle::Normal) - .text_size(DEFAULT_FONT_SIZE) - .spacing(5); - - let checkbox: Element = checkbox.into(); - - let checkbox_container = Container::new(checkbox.map(Message::Interaction)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - Column::new().push(checkbox_container) - }; - - #[cfg(target_os = "windows")] - let start_closed_to_tray_column = { - let checkbox = Checkbox::new( - localized_string("start-closed-to-tray"), - config.start_closed_to_tray, - Interaction::ToggleStartClosedToTray, - ) - .style(grin_gui_core::theme::CheckboxStyle::Normal) - .text_size(DEFAULT_FONT_SIZE) - .spacing(5); - - let checkbox: Element = checkbox.into(); - - let checkbox_container = Container::new(checkbox.map(Message::Interaction)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - Column::new().push(checkbox_container) - }; - - let mut column = Column::new() - .spacing(1) - .push(language_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) - .push(currency_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) - .push(theme_scale_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) - .push(open_theme_row) - .spacing(1); - - // Systray settings - #[cfg(target_os = "windows")] - { - column = column - .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) - .push(close_to_tray_column) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) - .push(start_closed_to_tray_column) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) - .push(toggle_autostart_column); - } - - let scrollable = Scrollable::new(column) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); - - // Colum wrapping all the settings content. - //scrollable = scrollable.height(Length::Fill); - - let col = Column::new() - .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) - .push(scrollable) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(20.0))); - let row = Row::new() - .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) - .push(col); - - // Returns the final container. - Container::new(row) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + let language_container = { + let title = Container::new(Text::new(localized_string("language")).size(DEFAULT_FONT_SIZE)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let pick_list = PickList::new(&Language::ALL[..], Some(config.language), |l| { + Message::Interaction(Interaction::GeneralSettingsViewInteraction( + LocalViewInteraction::LanguageSelected(l), + )) + }) + .text_size(14) + .width(Length::Fixed(120.0)) + .style(grin_gui_core::theme::PickListStyle::Primary); + + let container = Container::new(pick_list) + .center_y() + .width(Length::Fixed(120.0)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + Column::new() + .push(title) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(container) + }; + + let currency_container = { + let title = Container::new(Text::new(localized_string("currency")).size(DEFAULT_FONT_SIZE)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let pick_list = PickList::new(&Currency::ALL[..], Some(config.currency), |c| { + Message::Interaction(Interaction::GeneralSettingsViewInteraction( + LocalViewInteraction::CurrencySelected(c), + )) + }) + .text_size(14) + .width(Length::Fixed(120.0)) + .style(grin_gui_core::theme::PickListStyle::Primary); + + let container = Container::new(pick_list) + .center_y() + .width(Length::Fixed(120.0)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + Column::new() + .push(title) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(container) + }; + + let theme_column = { + let title_container = + Container::new(Text::new(localized_string("theme")).size(DEFAULT_FONT_SIZE)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let theme_names = state + .theme_state + .themes + .iter() + .cloned() + .map(|(name, _)| name) + .collect::>(); + + let theme_pick_list = PickList::new( + theme_names, + Some(state.theme_state.current_theme_name.clone()), + |t| { + Message::Interaction(Interaction::GeneralSettingsViewInteraction( + LocalViewInteraction::ThemeSelected(t), + )) + }, + ) + .text_size(DEFAULT_FONT_SIZE) + .width(Length::Fixed(120.0)) + .style(grin_gui_core::theme::PickListStyle::Primary); + + // Data row for theme picker list. + let theme_data_row = Row::new() + .push(theme_pick_list) + .align_items(Alignment::Center) + .height(Length::Fixed(26.0)); + + Column::new() + .push(title_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(theme_data_row) + }; + + // Scale buttons for application scale factoring. + let scale_column = { + let title_container = + Container::new(Text::new(localized_string("scale")).size(DEFAULT_FONT_SIZE)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let scale_title_row = Row::new().push(title_container); + + let scale_down_button: Element = Button::new( + // &mut state.scale_state.down_btn_state, + Text::new(" - ").size(DEFAULT_FONT_SIZE), + ) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::GeneralSettingsViewInteraction( + LocalViewInteraction::ScaleDown, + )) + .into(); + + let scale_up_button: Element = Button::new( + // &mut state.scale_state.up_btn_state, + Text::new(" + ").size(DEFAULT_FONT_SIZE), + ) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::GeneralSettingsViewInteraction( + LocalViewInteraction::ScaleUp, + )) + .into(); + + let current_scale_text = Text::new(format!(" {:.2} ", state.scale_state.scale)) + .size(DEFAULT_FONT_SIZE) + .vertical_alignment(iced_core::alignment::Vertical::Center); + let current_scale_container = Container::new(current_scale_text) + .height(Length::Fill) + .center_y() + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let scale_buttons_row = Row::new() + .push(scale_down_button.map(Message::Interaction)) + .push(current_scale_container) + .push(scale_up_button.map(Message::Interaction)) + .align_items(Alignment::Center) + .height(Length::Fixed(26.0)); + + Column::new() + .push(scale_title_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(scale_buttons_row) + }; + + let import_theme_column = { + let title_container = + Container::new(Text::new(localized_string("import-theme")).size(DEFAULT_FONT_SIZE)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let theme_input = TextInput::new( + &localized_string("paste-url")[..], + &state.theme_state.input_url, + //|s| Interaction::GeneralSettingsViewInteraction(LocalViewInteraction::ThemeUrlInput(s)), + ) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(185.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); + + let theme_input: Element = theme_input.into(); + + let mut import_button = Button::new( + // &mut state.theme_state.import_button_state, + Text::new(localized_string("import-theme-button")).size(DEFAULT_FONT_SIZE), + ) + .style(grin_gui_core::theme::ButtonStyle::Bordered); + + if !state.theme_state.input_url.is_empty() { + import_button = import_button.on_press(Interaction::GeneralSettingsViewInteraction( + LocalViewInteraction::ImportTheme, + )); + } + + let import_button: Element = import_button.into(); + + let theme_input_row = Row::new() + .push(theme_input.map(Message::Interaction)) + .push(import_button.map(Message::Interaction)) + .spacing(DEFAULT_PADDING) + .align_items(Alignment::Center) + .height(Length::Fixed(26.0)); + + Column::new() + .push(title_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(theme_input_row) + }; + + let open_theme_row = { + let open_button = Button::new( + // &mut state.theme_state.open_builder_button_state, + Text::new(localized_string("open-theme-builder")).size(DEFAULT_FONT_SIZE), + ) + .on_press(Interaction::OpenLink(String::from( + "https://theme.getajour.com", + ))) + .style(grin_gui_core::theme::ButtonStyle::Bordered); + + let open_button: Element = open_button.into(); + + Row::new() + .push(open_button.map(Message::Interaction)) + .align_items(Alignment::Center) + .height(Length::Fixed(26.0)) + }; + + let theme_scale_row = Row::new() + .push(theme_column) + .push(scale_column) + .push(import_theme_column) + .spacing(DEFAULT_PADDING); + + #[cfg(target_os = "windows")] + let close_to_tray_column = { + let checkbox = Checkbox::new( + localized_string("close-to-tray"), + config.close_to_tray, + Interaction::ToggleCloseToTray, + ) + .style(grin_gui_core::theme::CheckboxStyle::Normal) + .text_size(DEFAULT_FONT_SIZE) + .spacing(5); + + let checkbox: Element = checkbox.into(); + + let checkbox_container = Container::new(checkbox.map(Message::Interaction)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + Column::new().push(checkbox_container) + }; + + #[cfg(target_os = "windows")] + let toggle_autostart_column = { + let checkbox = Checkbox::new( + localized_string("toggle-autostart"), + config.autostart, + Interaction::ToggleAutoStart, + ) + .style(grin_gui_core::theme::CheckboxStyle::Normal) + .text_size(DEFAULT_FONT_SIZE) + .spacing(5); + + let checkbox: Element = checkbox.into(); + + let checkbox_container = Container::new(checkbox.map(Message::Interaction)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + Column::new().push(checkbox_container) + }; + + #[cfg(target_os = "windows")] + let start_closed_to_tray_column = { + let checkbox = Checkbox::new( + localized_string("start-closed-to-tray"), + config.start_closed_to_tray, + Interaction::ToggleStartClosedToTray, + ) + .style(grin_gui_core::theme::CheckboxStyle::Normal) + .text_size(DEFAULT_FONT_SIZE) + .spacing(5); + + let checkbox: Element = checkbox.into(); + + let checkbox_container = Container::new(checkbox.map(Message::Interaction)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + Column::new().push(checkbox_container) + }; + + let mut column = Column::new() + .spacing(1) + .push(language_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) + .push(currency_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) + .push(theme_scale_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) + .push(open_theme_row) + .spacing(1); + + // Systray settings + #[cfg(target_os = "windows")] + { + column = column + .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) + .push(close_to_tray_column) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) + .push(start_closed_to_tray_column) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) + .push(toggle_autostart_column); + } + + let scrollable = Scrollable::new(column) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); + + // Colum wrapping all the settings content. + //scrollable = scrollable.height(Length::Fill); + + let col = Column::new() + .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) + .push(scrollable) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(20.0))); + let row = Row::new() + .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) + .push(col); + + // Returns the final container. + Container::new(row) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) } diff --git a/src/gui/element/settings/mod.rs b/src/gui/element/settings/mod.rs index efffb2e..6d529dc 100644 --- a/src/gui/element/settings/mod.rs +++ b/src/gui/element/settings/mod.rs @@ -5,183 +5,173 @@ pub mod node; pub mod wallet; use { - super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING}, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - grin_gui_core::theme::{ - Button, Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, - }, - grin_gui_core::{config::Config, theme::ColorPalette}, - iced::widget::{button, Space}, - iced::{Alignment, Length}, - serde::{Deserialize, Serialize}, + super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING}, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + grin_gui_core::theme::{ + Button, Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, + }, + grin_gui_core::{config::Config, theme::ColorPalette}, + iced::widget::{button, Space}, + iced::{Alignment, Length}, + serde::{Deserialize, Serialize}, }; #[derive(Debug, Clone)] pub struct StateContainer { - pub mode: Mode, - // wallet_btn: button::State, - // node_btn: button::State, - // general_btn: button::State, + pub mode: Mode, + // wallet_btn: button::State, + // node_btn: button::State, + // general_btn: button::State, } impl Default for StateContainer { - fn default() -> Self { - Self { - mode: Mode::Wallet, - // wallet_btn: Default::default(), - // node_btn: Default::default(), - // general_btn: Default::default(), - } - } + fn default() -> Self { + Self { + mode: Mode::Wallet, + // wallet_btn: Default::default(), + // node_btn: Default::default(), + // general_btn: Default::default(), + } + } } #[derive(Serialize, Deserialize, Debug, Clone)] pub enum LocalViewInteraction { - SelectMode(Mode), + SelectMode(Mode), } #[derive(Serialize, Deserialize, Debug, Clone)] pub enum Mode { - Wallet, - Node, - General, + Wallet, + Node, + General, } pub fn handle_message(grin_gui: &mut GrinGui, message: LocalViewInteraction) { - match message { - LocalViewInteraction::SelectMode(mode) => { - log::debug!("Interaction::ModeSelectedSettings({:?})", mode); - // Set Mode - grin_gui.settings_state.mode = mode; - } - } + match message { + LocalViewInteraction::SelectMode(mode) => { + log::debug!("Interaction::ModeSelectedSettings({:?})", mode); + // Set Mode + grin_gui.settings_state.mode = mode; + } + } } pub fn data_container<'a>( - state: &'a StateContainer, - config: &'a Config, - wallet_settings_state: &'a wallet::StateContainer, - node_settings_state: &'a node::StateContainer, - general_settings_state: &'a general::StateContainer, + state: &'a StateContainer, + config: &'a Config, + wallet_settings_state: &'a wallet::StateContainer, + node_settings_state: &'a node::StateContainer, + general_settings_state: &'a general::StateContainer, ) -> Container<'a, Message> { - let title_string = match state.mode { - Mode::Wallet => localized_string("settings-wallet"), - Mode::Node => localized_string("settings-node"), - Mode::General => localized_string("settings-general"), - }; - - // Submenu title to appear of left side of panel - let general_settings_title = Text::new(title_string).size(DEFAULT_HEADER_FONT_SIZE); - let general_settings_title_container = Container::new(general_settings_title) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - let mut wallet_button: Button = Button::new( - // &mut state.wallet_btn, - Text::new(localized_string("wallet")).size(DEFAULT_FONT_SIZE), - ) - .on_press(Interaction::SettingsViewInteraction( - LocalViewInteraction::SelectMode(Mode::Wallet), - )); - - let mut node_button: Button = Button::new( - // &mut state.node_btn, - Text::new(localized_string("node")).size(DEFAULT_FONT_SIZE), - ) - .on_press(Interaction::SettingsViewInteraction( - LocalViewInteraction::SelectMode(Mode::Node), - )); - - let mut general_button: Button = Button::new( - // &mut state.general_btn, - Text::new(localized_string("general")).size(DEFAULT_FONT_SIZE), - ) - .on_press(Interaction::SettingsViewInteraction( - LocalViewInteraction::SelectMode(Mode::General), - )); - - match state.mode { - Mode::Wallet => { - wallet_button = wallet_button.style(grin_gui_core::theme::ButtonStyle::Selected); - node_button = - node_button.style(grin_gui_core::theme::ButtonStyle::Primary); - general_button = - general_button.style(grin_gui_core::theme::ButtonStyle::Primary); - } - Mode::Node => { - wallet_button = - wallet_button.style(grin_gui_core::theme::ButtonStyle::Primary); - node_button = node_button.style(grin_gui_core::theme::ButtonStyle::Selected); - general_button = - general_button.style(grin_gui_core::theme::ButtonStyle::Primary); - } - Mode::General => { - wallet_button = - wallet_button.style(grin_gui_core::theme::ButtonStyle::Primary); - node_button = - node_button.style(grin_gui_core::theme::ButtonStyle::Primary); - general_button = general_button.style(grin_gui_core::theme::ButtonStyle::Selected); - } - } - - let wallet_button: Element = wallet_button.into(); - let node_button: Element = node_button.into(); - let general_button: Element = general_button.into(); - - let segmented_mode_row = Row::new() - .push(wallet_button.map(Message::Interaction)) - .push(node_button.map(Message::Interaction)) - .push(general_button.map(Message::Interaction)) - .spacing(1); - - let segmented_mode_container = Container::new(segmented_mode_row).padding(1); - - let segmented_mode_control_container = - Container::new(segmented_mode_container).padding(1).style( - grin_gui_core::theme::ContainerStyle::Segmented, - ); - - let header_row = Row::new() - .push(general_settings_title_container) - .push(Space::with_width(Length::Fill)) - .push(segmented_mode_control_container) - .align_items(Alignment::Center); - - let header_container = Container::new(header_row); - - // Wrapper for submenu + actual content - let mut wrapper_column = - Column::with_children(vec![header_container.into()]).height(Length::Fill); - // Submenu Area + actual content - match state.mode { - Mode::Wallet => { - wrapper_column = - wrapper_column.push(wallet::data_container(wallet_settings_state, config)) - } - Mode::Node => { - wrapper_column = - wrapper_column.push(node::data_container(node_settings_state)) - } - Mode::General => { - wrapper_column = wrapper_column.push(general::data_container( - general_settings_state, - config, - )) - } - } - - Container::new(wrapper_column) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) - .padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + let title_string = match state.mode { + Mode::Wallet => localized_string("settings-wallet"), + Mode::Node => localized_string("settings-node"), + Mode::General => localized_string("settings-general"), + }; + + // Submenu title to appear of left side of panel + let general_settings_title = Text::new(title_string).size(DEFAULT_HEADER_FONT_SIZE); + let general_settings_title_container = Container::new(general_settings_title) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + let mut wallet_button: Button = Button::new( + // &mut state.wallet_btn, + Text::new(localized_string("wallet")).size(DEFAULT_FONT_SIZE), + ) + .on_press(Interaction::SettingsViewInteraction( + LocalViewInteraction::SelectMode(Mode::Wallet), + )); + + let mut node_button: Button = Button::new( + // &mut state.node_btn, + Text::new(localized_string("node")).size(DEFAULT_FONT_SIZE), + ) + .on_press(Interaction::SettingsViewInteraction( + LocalViewInteraction::SelectMode(Mode::Node), + )); + + let mut general_button: Button = Button::new( + // &mut state.general_btn, + Text::new(localized_string("general")).size(DEFAULT_FONT_SIZE), + ) + .on_press(Interaction::SettingsViewInteraction( + LocalViewInteraction::SelectMode(Mode::General), + )); + + match state.mode { + Mode::Wallet => { + wallet_button = wallet_button.style(grin_gui_core::theme::ButtonStyle::Selected); + node_button = node_button.style(grin_gui_core::theme::ButtonStyle::Primary); + general_button = general_button.style(grin_gui_core::theme::ButtonStyle::Primary); + } + Mode::Node => { + wallet_button = wallet_button.style(grin_gui_core::theme::ButtonStyle::Primary); + node_button = node_button.style(grin_gui_core::theme::ButtonStyle::Selected); + general_button = general_button.style(grin_gui_core::theme::ButtonStyle::Primary); + } + Mode::General => { + wallet_button = wallet_button.style(grin_gui_core::theme::ButtonStyle::Primary); + node_button = node_button.style(grin_gui_core::theme::ButtonStyle::Primary); + general_button = general_button.style(grin_gui_core::theme::ButtonStyle::Selected); + } + } + + let wallet_button: Element = wallet_button.into(); + let node_button: Element = node_button.into(); + let general_button: Element = general_button.into(); + + let segmented_mode_row = Row::new() + .push(wallet_button.map(Message::Interaction)) + .push(node_button.map(Message::Interaction)) + .push(general_button.map(Message::Interaction)) + .spacing(1); + + let segmented_mode_container = Container::new(segmented_mode_row).padding(1); + + let segmented_mode_control_container = Container::new(segmented_mode_container) + .padding(1) + .style(grin_gui_core::theme::ContainerStyle::Segmented); + + let header_row = Row::new() + .push(general_settings_title_container) + .push(Space::with_width(Length::Fill)) + .push(segmented_mode_control_container) + .align_items(Alignment::Center); + + let header_container = Container::new(header_row); + + // Wrapper for submenu + actual content + let mut wrapper_column = + Column::with_children(vec![header_container.into()]).height(Length::Fill); + // Submenu Area + actual content + match state.mode { + Mode::Wallet => { + wrapper_column = + wrapper_column.push(wallet::data_container(wallet_settings_state, config)) + } + Mode::Node => { + wrapper_column = wrapper_column.push(node::data_container(node_settings_state)) + } + Mode::General => { + wrapper_column = + wrapper_column.push(general::data_container(general_settings_state, config)) + } + } + + Container::new(wrapper_column) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + .padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/settings/node.rs b/src/gui/element/settings/node.rs index f79edc1..2900264 100644 --- a/src/gui/element/settings/node.rs +++ b/src/gui/element/settings/node.rs @@ -1,79 +1,77 @@ use { - super::DEFAULT_FONT_SIZE, - crate::gui::{GrinGui, Message}, - crate::localization::localized_string, - grin_gui_core::theme::ColorPalette, - grin_gui_core::theme::{Button, Column, Container, PickList, Row, Scrollable, Text, TextInput}, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::Length, - serde::{Deserialize, Serialize}, + super::DEFAULT_FONT_SIZE, + crate::gui::{GrinGui, Message}, + crate::localization::localized_string, + grin_gui_core::theme::ColorPalette, + grin_gui_core::theme::{Button, Column, Container, PickList, Row, Scrollable, Text, TextInput}, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::Length, + serde::{Deserialize, Serialize}, }; #[derive(Debug, Clone)] pub struct StateContainer { - pub mode: Mode, - // scrollable_state: scrollable::State, + pub mode: Mode, + // scrollable_state: scrollable::State, } impl Default for StateContainer { - fn default() -> Self { - Self { - mode: Mode::Wallet, - // scrollable_state: Default::default(), - } - } + fn default() -> Self { + Self { + mode: Mode::Wallet, + // scrollable_state: Default::default(), + } + } } #[derive(Serialize, Deserialize, Debug, Clone)] pub enum LocalViewInteraction { - SelectMode(Mode), + SelectMode(Mode), } #[derive(Serialize, Deserialize, Debug, Clone)] pub enum Mode { - Wallet, - Node, - General, + Wallet, + Node, + General, } pub fn handle_message(grin_gui: &mut GrinGui, message: LocalViewInteraction) { - match message { - LocalViewInteraction::SelectMode(mode) => { - log::debug!("Interaction::ModeSelectedSettings({:?})", mode); - // Set Mode - grin_gui.node_settings_state.mode = mode - } - } + match message { + LocalViewInteraction::SelectMode(mode) => { + log::debug!("Interaction::ModeSelectedSettings({:?})", mode); + // Set Mode + grin_gui.node_settings_state.mode = mode + } + } } -pub fn data_container<'a>( - state: &'a StateContainer, -) -> Container<'a, Message> { - let language_container = { - let title = Container::new(Text::new(localized_string("todo")).size(DEFAULT_FONT_SIZE)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); +pub fn data_container<'a>(state: &'a StateContainer) -> Container<'a, Message> { + let language_container = { + let title = Container::new(Text::new(localized_string("todo")).size(DEFAULT_FONT_SIZE)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - Column::new() - .push(title) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - }; + Column::new() + .push(title) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + }; - // Colum wrapping all the settings content. - let scrollable = Scrollable::new(language_container) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); + // Colum wrapping all the settings content. + let scrollable = Scrollable::new(language_container) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); - let col = Column::new() - .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) - .push(scrollable) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(20.0))); - let row = Row::new() - .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) - .push(col); + let col = Column::new() + .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) + .push(scrollable) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(20.0))); + let row = Row::new() + .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) + .push(col); - // Returns the final container. - Container::new(row) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + // Returns the final container. + Container::new(row) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) } diff --git a/src/gui/element/settings/wallet.rs b/src/gui/element/settings/wallet.rs index 4da08b2..59281d5 100644 --- a/src/gui/element/settings/wallet.rs +++ b/src/gui/element/settings/wallet.rs @@ -1,243 +1,248 @@ use { - super::{DEFAULT_FONT_SIZE}, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - grin_gui_core::config::{Config, TxMethod}, - grin_gui_core::theme::{Button, Column, Container, Element, PickList, Row, Scrollable, Text, TextInput}, - grin_gui_core::fs::PersistentData, - iced::Length, - iced::widget::{ - button, pick_list, scrollable, text_input, Checkbox, Space, - }, - iced::{alignment, Alignment}, - serde::{Deserialize, Serialize}, + super::DEFAULT_FONT_SIZE, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + grin_gui_core::config::{Config, TxMethod}, + grin_gui_core::fs::PersistentData, + grin_gui_core::theme::{ + Button, Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, + }, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::Length, + iced::{alignment, Alignment}, + serde::{Deserialize, Serialize}, }; #[derive(Debug, Clone)] pub struct StateContainer { - // scrollable_state: scrollable::State, - mw_mixnet_address_1: String, - mw_mixnet_address_2: String, - mw_mixnet_address_3: String, + // scrollable_state: scrollable::State, + mw_mixnet_address_1: String, + mw_mixnet_address_2: String, + mw_mixnet_address_3: String, } impl Default for StateContainer { - fn default() -> Self { - Self { - mw_mixnet_address_1: "".to_string(), - mw_mixnet_address_2: "".to_string(), - mw_mixnet_address_3: "".to_string(), - } - } + fn default() -> Self { + Self { + mw_mixnet_address_1: "".to_string(), + mw_mixnet_address_2: "".to_string(), + mw_mixnet_address_3: "".to_string(), + } + } } #[derive(Serialize, Deserialize, Debug, Clone)] pub enum LocalViewInteraction { - TxMethodSelected(TxMethod), - MwMixnetAddress1Changed(String), - MwMixnetAddress2Changed(String), - MwMixnetAddress3Changed(String), + TxMethodSelected(TxMethod), + MwMixnetAddress1Changed(String), + MwMixnetAddress2Changed(String), + MwMixnetAddress3Changed(String), } #[derive(Serialize, Deserialize, Debug, Clone)] pub enum Mode { - Wallet, - Node, - General, + Wallet, + Node, + General, } -pub fn handle_message( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, -) { - let state = &mut grin_gui.wallet_settings_state; - let mut check_mixnet_config = || { - if grin_gui.config.mixnet_keys.is_none() { - grin_gui.config.mixnet_keys = Some(vec![]); - grin_gui.config.mixnet_keys.as_mut().unwrap().push(String::new()); - grin_gui.config.mixnet_keys.as_mut().unwrap().push(String::new()); - grin_gui.config.mixnet_keys.as_mut().unwrap().push(String::new()); - } - }; - match message { - LocalViewInteraction::TxMethodSelected(method) => { - log::debug!("Interaction::TxMethodSelectedSettings({:?})", method); - // Set Mode - grin_gui.config.tx_method = method; - let _ = grin_gui.config.save(); - } - LocalViewInteraction::MwMixnetAddress1Changed(value) => { - check_mixnet_config(); - grin_gui.config.mixnet_keys.as_mut().unwrap()[0] = value.clone(); - state.mw_mixnet_address_1 = value; - let _ = grin_gui.config.save(); - } - LocalViewInteraction::MwMixnetAddress2Changed(value) => { - check_mixnet_config(); - grin_gui.config.mixnet_keys.as_mut().unwrap()[1] = value.clone(); - state.mw_mixnet_address_2 = value; - let _ = grin_gui.config.save(); - } - LocalViewInteraction::MwMixnetAddress3Changed(value) => { - check_mixnet_config(); - grin_gui.config.mixnet_keys.as_mut().unwrap()[2] = value.clone(); - state.mw_mixnet_address_3 = value; - let _ = grin_gui.config.save(); - } - } +pub fn handle_message(grin_gui: &mut GrinGui, message: LocalViewInteraction) { + let state = &mut grin_gui.wallet_settings_state; + let mut check_mixnet_config = || { + if grin_gui.config.mixnet_keys.is_none() { + grin_gui.config.mixnet_keys = Some(vec![]); + grin_gui + .config + .mixnet_keys + .as_mut() + .unwrap() + .push(String::new()); + grin_gui + .config + .mixnet_keys + .as_mut() + .unwrap() + .push(String::new()); + grin_gui + .config + .mixnet_keys + .as_mut() + .unwrap() + .push(String::new()); + } + }; + match message { + LocalViewInteraction::TxMethodSelected(method) => { + log::debug!("Interaction::TxMethodSelectedSettings({:?})", method); + // Set Mode + grin_gui.config.tx_method = method; + let _ = grin_gui.config.save(); + } + LocalViewInteraction::MwMixnetAddress1Changed(value) => { + check_mixnet_config(); + grin_gui.config.mixnet_keys.as_mut().unwrap()[0] = value.clone(); + state.mw_mixnet_address_1 = value; + let _ = grin_gui.config.save(); + } + LocalViewInteraction::MwMixnetAddress2Changed(value) => { + check_mixnet_config(); + grin_gui.config.mixnet_keys.as_mut().unwrap()[1] = value.clone(); + state.mw_mixnet_address_2 = value; + let _ = grin_gui.config.save(); + } + LocalViewInteraction::MwMixnetAddress3Changed(value) => { + check_mixnet_config(); + grin_gui.config.mixnet_keys.as_mut().unwrap()[2] = value.clone(); + state.mw_mixnet_address_3 = value; + let _ = grin_gui.config.save(); + } + } } -pub fn data_container<'a>( - state: &'a StateContainer, - config: &Config, -) -> Container<'a, Message> { - - let (config_addr_1, config_addr_2, config_addr_3) = if let Some(a) = config.mixnet_keys.as_ref() { - (a[0].clone(), a[1].clone(), a[2].clone()) - } else { - (String::new(), String::new(), String::new()) - }; - - let tx_method_column = { - let tx_method_container = - Container::new(Text::new(localized_string("tx-method")).size(DEFAULT_FONT_SIZE)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let tx_method_pick_list = PickList::new( - &TxMethod::ALL[..], - Some(config.tx_method), - |t| { - Message::Interaction(Interaction::WalletSettingsViewInteraction( - LocalViewInteraction::TxMethodSelected(t), - )) - }, - ) - .text_size(DEFAULT_FONT_SIZE) - .width(Length::Fixed(120.0)) - .style(grin_gui_core::theme::PickListStyle::Primary); - - // Data row for theme picker list. - let tx_method_data_row = Row::new() - .push(tx_method_pick_list) - .align_items(Alignment::Center) - .height(Length::Fixed(26.0)); - - Column::new() - .push(tx_method_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(tx_method_data_row) - }; - - let mw_mixnet_address_column = { - let mw_mixnet_address_container = - Container::new(Text::new(localized_string("mw-mixnet-addresses")).size(DEFAULT_FONT_SIZE)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let mw_mixnet_address_1 = Text::new(localized_string("mw-mixnet-address-1")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let mw_mixnet_address_1_container = Container::new(mw_mixnet_address_1) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let mw_mixnet_address_1_input = TextInput::new("", &config_addr_1) - .on_input(|s| { - Interaction::WalletSettingsViewInteraction( - LocalViewInteraction::MwMixnetAddress1Changed(s), - ) - }) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(400.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); - - let mw_mixnet_address_1_input: Element = mw_mixnet_address_1_input.into(); - - let mw_mixnet_address_2 = Text::new(localized_string("mw-mixnet-address-2")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let mw_mixnet_address_2_container = Container::new(mw_mixnet_address_2) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let mw_mixnet_address_2_input = TextInput::new("", &config_addr_2) - .on_input(|s| { - Interaction::WalletSettingsViewInteraction( - LocalViewInteraction::MwMixnetAddress2Changed(s), - ) - }) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(400.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); - - let mw_mixnet_address_2_input: Element = mw_mixnet_address_2_input.into(); - - let mw_mixnet_address_3 = Text::new(localized_string("mw-mixnet-address-3")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let mw_mixnet_address_3_container = Container::new(mw_mixnet_address_3) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let mw_mixnet_address_3_input = TextInput::new("", &config_addr_3) - .on_input(|s| { - Interaction::WalletSettingsViewInteraction( - LocalViewInteraction::MwMixnetAddress3Changed(s), - ) - }) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(400.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); - - let mw_mixnet_address_3_input: Element = mw_mixnet_address_3_input.into(); - - // Data row for theme picker list. - /*let tx_method_data_row = Row::new() - .push(tx_method_pick_list) - .align_items(Alignment::Center) - .height(Length::Fixed(26.0));*/ - - Column::new() - .push(mw_mixnet_address_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(mw_mixnet_address_1_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(mw_mixnet_address_1_input.map(Message::Interaction)) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(mw_mixnet_address_2_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(mw_mixnet_address_2_input.map(Message::Interaction)) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(mw_mixnet_address_3_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(mw_mixnet_address_3_input.map(Message::Interaction)) - }; - - - let wrap = { - Column::new() - .push(tx_method_column) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) - .push(mw_mixnet_address_column) - }; - - let scrollable = Scrollable::new(wrap) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); - - let col = Column::new() - .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) - .push(scrollable) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(20.0))); - let row = Row::new() - .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) - .push(col); - - // Returns the final container. - Container::new(row) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) +pub fn data_container<'a>(state: &'a StateContainer, config: &Config) -> Container<'a, Message> { + let (config_addr_1, config_addr_2, config_addr_3) = if let Some(a) = config.mixnet_keys.as_ref() + { + (a[0].clone(), a[1].clone(), a[2].clone()) + } else { + (String::new(), String::new(), String::new()) + }; + + let tx_method_column = { + let tx_method_container = + Container::new(Text::new(localized_string("tx-method")).size(DEFAULT_FONT_SIZE)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let tx_method_pick_list = PickList::new(&TxMethod::ALL[..], Some(config.tx_method), |t| { + Message::Interaction(Interaction::WalletSettingsViewInteraction( + LocalViewInteraction::TxMethodSelected(t), + )) + }) + .text_size(DEFAULT_FONT_SIZE) + .width(Length::Fixed(120.0)) + .style(grin_gui_core::theme::PickListStyle::Primary); + + // Data row for theme picker list. + let tx_method_data_row = Row::new() + .push(tx_method_pick_list) + .align_items(Alignment::Center) + .height(Length::Fixed(26.0)); + + Column::new() + .push(tx_method_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(tx_method_data_row) + }; + + let mw_mixnet_address_column = { + let mw_mixnet_address_container = Container::new( + Text::new(localized_string("mw-mixnet-addresses")).size(DEFAULT_FONT_SIZE), + ) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let mw_mixnet_address_1 = Text::new(localized_string("mw-mixnet-address-1")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let mw_mixnet_address_1_container = Container::new(mw_mixnet_address_1) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let mw_mixnet_address_1_input = TextInput::new("", &config_addr_1) + .on_input(|s| { + Interaction::WalletSettingsViewInteraction( + LocalViewInteraction::MwMixnetAddress1Changed(s), + ) + }) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(400.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); + + let mw_mixnet_address_1_input: Element = mw_mixnet_address_1_input.into(); + + let mw_mixnet_address_2 = Text::new(localized_string("mw-mixnet-address-2")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let mw_mixnet_address_2_container = Container::new(mw_mixnet_address_2) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let mw_mixnet_address_2_input = TextInput::new("", &config_addr_2) + .on_input(|s| { + Interaction::WalletSettingsViewInteraction( + LocalViewInteraction::MwMixnetAddress2Changed(s), + ) + }) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(400.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); + + let mw_mixnet_address_2_input: Element = mw_mixnet_address_2_input.into(); + + let mw_mixnet_address_3 = Text::new(localized_string("mw-mixnet-address-3")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let mw_mixnet_address_3_container = Container::new(mw_mixnet_address_3) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let mw_mixnet_address_3_input = TextInput::new("", &config_addr_3) + .on_input(|s| { + Interaction::WalletSettingsViewInteraction( + LocalViewInteraction::MwMixnetAddress3Changed(s), + ) + }) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(400.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); + + let mw_mixnet_address_3_input: Element = mw_mixnet_address_3_input.into(); + + // Data row for theme picker list. + /*let tx_method_data_row = Row::new() + .push(tx_method_pick_list) + .align_items(Alignment::Center) + .height(Length::Fixed(26.0));*/ + + Column::new() + .push(mw_mixnet_address_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(mw_mixnet_address_1_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(mw_mixnet_address_1_input.map(Message::Interaction)) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(mw_mixnet_address_2_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(mw_mixnet_address_2_input.map(Message::Interaction)) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(mw_mixnet_address_3_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(mw_mixnet_address_3_input.map(Message::Interaction)) + }; + + let wrap = { + Column::new() + .push(tx_method_column) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) + .push(mw_mixnet_address_column) + }; + + let scrollable = Scrollable::new(wrap) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); + + let col = Column::new() + .push(Space::new(Length::Fixed(0.0), Length::Fixed(10.0))) + .push(scrollable) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(20.0))); + let row = Row::new() + .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) + .push(col); + + // Returns the final container. + Container::new(row) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) } diff --git a/src/gui/element/wallet/mod.rs b/src/gui/element/wallet/mod.rs index 7f910c5..f61420a 100644 --- a/src/gui/element/wallet/mod.rs +++ b/src/gui/element/wallet/mod.rs @@ -2,76 +2,72 @@ pub mod operation; pub mod setup; use { - crate::gui::{Message}, - grin_gui_core::config::Config, - grin_gui_core::theme::ColorPalette, - grin_gui_core::theme::{Container, Column}, - iced::Length, + crate::gui::Message, + grin_gui_core::config::Config, + grin_gui_core::theme::ColorPalette, + grin_gui_core::theme::{Column, Container}, + iced::Length, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Mode { - Init, - CreateWallet(String), - Operation, + Init, + CreateWallet(String), + Operation, } pub struct StateContainer { - pub mode: Mode, - pub setup_state: setup::StateContainer, - pub operation_state: operation::StateContainer, - // When changed to true, this should stay false until a config exists - has_config_check_failed_one_time: bool, + pub mode: Mode, + pub setup_state: setup::StateContainer, + pub operation_state: operation::StateContainer, + // When changed to true, this should stay false until a config exists + has_config_check_failed_one_time: bool, } impl Default for StateContainer { - fn default() -> Self { - Self { - mode: Mode::Operation, - setup_state: Default::default(), - operation_state: Default::default(), - has_config_check_failed_one_time: false, - } - } + fn default() -> Self { + Self { + mode: Mode::Operation, + setup_state: Default::default(), + operation_state: Default::default(), + has_config_check_failed_one_time: false, + } + } } impl StateContainer { - pub fn config_missing(&self) -> bool { - self.has_config_check_failed_one_time - } + pub fn config_missing(&self) -> bool { + self.has_config_check_failed_one_time + } - pub fn set_config_missing(&mut self) { - self.has_config_check_failed_one_time = true; - self.mode = Mode::Init; - self.setup_state.mode = crate::gui::element::wallet::setup::Mode::Init; - } + pub fn set_config_missing(&mut self) { + self.has_config_check_failed_one_time = true; + self.mode = Mode::Init; + self.setup_state.mode = crate::gui::element::wallet::setup::Mode::Init; + } - pub fn clear_config_missing(&mut self) { - self.has_config_check_failed_one_time = false; - } + pub fn clear_config_missing(&mut self) { + self.has_config_check_failed_one_time = false; + } } -pub fn data_container<'a>( - state: &'a StateContainer, - config: &'a Config, -) -> Container<'a, Message> { - let content = match &state.mode { - Mode::Init => setup::data_container(&state.setup_state, config), - Mode::Operation => { - operation::data_container(&state.operation_state, config) - } - Mode::CreateWallet(default_display_name) => { - setup::wallet_setup::data_container(&state.setup_state.setup_wallet_state, default_display_name) - } - }; +pub fn data_container<'a>(state: &'a StateContainer, config: &'a Config) -> Container<'a, Message> { + let content = match &state.mode { + Mode::Init => setup::data_container(&state.setup_state, config), + Mode::Operation => operation::data_container(&state.operation_state, config), + Mode::CreateWallet(default_display_name) => setup::wallet_setup::data_container( + &state.setup_state.setup_wallet_state, + default_display_name, + ), + }; - let column = Column::new() - //.push(Space::new(Length::Fixed(0.0), Length::Fixed(20))) - .push(content); + let column = Column::new() + //.push(Space::new(Length::Fixed(0.0), Length::Fixed(20))) + .push(content); - Container::new(column) - .center_y() - .center_x() - .width(Length::Fill) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + Container::new(column) + .center_y() + .center_x() + .width(Length::Fill) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) } diff --git a/src/gui/element/wallet/operation/action_menu.rs b/src/gui/element/wallet/operation/action_menu.rs index 9f0eea9..29132ae 100644 --- a/src/gui/element/wallet/operation/action_menu.rs +++ b/src/gui/element/wallet/operation/action_menu.rs @@ -2,8 +2,8 @@ use super::tx_list::{self, ExpandType}; use crate::log_error; use async_std::prelude::FutureExt; use grin_gui_core::{ - config::Config, - wallet::{TxLogEntry, TxLogEntryType}, + config::Config, + wallet::{TxLogEntry, TxLogEntryType}, }; //use grin_gui_widgets::{header}; //use grin_gui_core::widgets::widget::header; @@ -15,157 +15,157 @@ use std::path::PathBuf; use super::tx_list::{HeaderState, TxList}; use { - super::super::super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING}, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - anyhow::Context, - grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, - TextInput, - }, - grin_gui_core::wallet::{StatusMessage, WalletInfo, WalletInterface}, - grin_gui_core::{node::amount_to_hr_string, theme::ColorPalette}, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::{Alignment, Command, Length}, - serde::{Deserialize, Serialize}, - std::sync::{Arc, RwLock}, + super::super::super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING}, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + anyhow::Context, + grin_gui_core::theme::{ + Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, + TextInput, + }, + grin_gui_core::wallet::{StatusMessage, WalletInfo, WalletInterface}, + grin_gui_core::{node::amount_to_hr_string, theme::ColorPalette}, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::{Alignment, Command, Length}, + serde::{Deserialize, Serialize}, + std::sync::{Arc, RwLock}, }; pub struct StateContainer { - // pub create_tx_button_state: button::State, - // pub apply_tx_button_state: button::State, + // pub create_tx_button_state: button::State, + // pub apply_tx_button_state: button::State, } impl Default for StateContainer { - fn default() -> Self { - Self { + fn default() -> Self { + Self { // create_tx_button_state: Default::default(), // apply_tx_button_state: Default::default(), } - } + } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Action { - CreateTx, - ApplyTx, + CreateTx, + ApplyTx, } #[derive(Debug, Clone)] pub enum LocalViewInteraction { - SelectAction(Action), + SelectAction(Action), } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui - .wallet_state - .operation_state - .home_state - .action_menu_state; - match message { - LocalViewInteraction::SelectAction(action) => { - log::debug!( - "Interaction::WalletOperationHomeActionMenuViewInteraction({:?})", - action - ); - match action { - Action::CreateTx => { - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::CreateTx - } - Action::ApplyTx => { - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::ApplyTx - } - } - } - } - Ok(Command::none()) + let state = &mut grin_gui + .wallet_state + .operation_state + .home_state + .action_menu_state; + match message { + LocalViewInteraction::SelectAction(action) => { + log::debug!( + "Interaction::WalletOperationHomeActionMenuViewInteraction({:?})", + action + ); + match action { + Action::CreateTx => { + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::CreateTx + } + Action::ApplyTx => { + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::ApplyTx + } + } + } + } + Ok(Command::none()) } pub fn data_container<'a>( - config: &'a Config, - state: &'a StateContainer, - home_state: &'a super::home::StateContainer, + config: &'a Config, + state: &'a StateContainer, + home_state: &'a super::home::StateContainer, ) -> Container<'a, Message> { - let button_width = Length::Fixed(70.0); - - let description = Text::new(localized_string("tx-transact")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center) - .vertical_alignment(alignment::Vertical::Center); - let description_container = Container::new(description).padding(iced::Padding::from([ - 7, // top - 5, // right - 5, // bottom - 5, // left - ])); - - // Buttons to perform wallet operations - let create_tx_container = - Container::new(Text::new(localized_string("wallet-create-tx")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .align_y(alignment::Vertical::Center) - .align_x(alignment::Horizontal::Center); - - let mut create_tx_button = Button::new(create_tx_container) - .width(button_width) - .style(grin_gui_core::theme::ButtonStyle::Primary); - - if home_state.node_synched { - create_tx_button = create_tx_button - .on_press(Interaction::WalletOperationHomeActionMenuViewInteraction( - LocalViewInteraction::SelectAction(Action::CreateTx), - )) - } - - let create_tx_button: Element = create_tx_button.into(); - - let apply_tx_container = - Container::new(Text::new(localized_string("wallet-apply-tx")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .align_y(alignment::Vertical::Center) - .align_x(alignment::Horizontal::Center); - - let mut apply_tx_button = Button::new(apply_tx_container) - .width(button_width) - .style(grin_gui_core::theme::ButtonStyle::Primary); - - if home_state.node_synched { - apply_tx_button = apply_tx_button - .on_press(Interaction::WalletOperationHomeActionMenuViewInteraction( - LocalViewInteraction::SelectAction(Action::ApplyTx), - )) - } - - let apply_tx_button: Element = apply_tx_button.into(); - - // TODO refactor since many of the buttons around the UI repeat this theme - let create_container = Container::new(create_tx_button.map(Message::Interaction)).padding(1); - let create_container = Container::new(create_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let apply_container = Container::new(apply_tx_button.map(Message::Interaction)).padding(1); - let apply_container = Container::new(apply_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let menu_column = Row::new() - .push(description_container) - .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))) - .push(create_container) - .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))) - .push(apply_container); - - Container::new(menu_column).padding(iced::Padding::from([ - 5, // top - 5, // right - 5, // bottom - 5, // left - ])) + let button_width = Length::Fixed(70.0); + + let description = Text::new(localized_string("tx-transact")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center) + .vertical_alignment(alignment::Vertical::Center); + let description_container = Container::new(description).padding(iced::Padding::from([ + 7, // top + 5, // right + 5, // bottom + 5, // left + ])); + + // Buttons to perform wallet operations + let create_tx_container = + Container::new(Text::new(localized_string("wallet-create-tx")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .align_y(alignment::Vertical::Center) + .align_x(alignment::Horizontal::Center); + + let mut create_tx_button = Button::new(create_tx_container) + .width(button_width) + .style(grin_gui_core::theme::ButtonStyle::Primary); + + if home_state.node_synched { + create_tx_button = + create_tx_button.on_press(Interaction::WalletOperationHomeActionMenuViewInteraction( + LocalViewInteraction::SelectAction(Action::CreateTx), + )) + } + + let create_tx_button: Element = create_tx_button.into(); + + let apply_tx_container = + Container::new(Text::new(localized_string("wallet-apply-tx")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .align_y(alignment::Vertical::Center) + .align_x(alignment::Horizontal::Center); + + let mut apply_tx_button = Button::new(apply_tx_container) + .width(button_width) + .style(grin_gui_core::theme::ButtonStyle::Primary); + + if home_state.node_synched { + apply_tx_button = + apply_tx_button.on_press(Interaction::WalletOperationHomeActionMenuViewInteraction( + LocalViewInteraction::SelectAction(Action::ApplyTx), + )) + } + + let apply_tx_button: Element = apply_tx_button.into(); + + // TODO refactor since many of the buttons around the UI repeat this theme + let create_container = Container::new(create_tx_button.map(Message::Interaction)).padding(1); + let create_container = Container::new(create_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let apply_container = Container::new(apply_tx_button.map(Message::Interaction)).padding(1); + let apply_container = Container::new(apply_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let menu_column = Row::new() + .push(description_container) + .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))) + .push(create_container) + .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))) + .push(apply_container); + + Container::new(menu_column).padding(iced::Padding::from([ + 5, // top + 5, // right + 5, // bottom + 5, // left + ])) } diff --git a/src/gui/element/wallet/operation/apply_tx.rs b/src/gui/element/wallet/operation/apply_tx.rs index af31d34..74c3fa0 100644 --- a/src/gui/element/wallet/operation/apply_tx.rs +++ b/src/gui/element/wallet/operation/apply_tx.rs @@ -2,8 +2,8 @@ use super::tx_list::{self, ExpandType}; use crate::log_error; use async_std::prelude::FutureExt; use grin_gui_core::{ - config::Config, - wallet::{Slate, Slatepack, TxLogEntry, TxLogEntryType}, + config::Config, + wallet::{Slate, Slatepack, TxLogEntry, TxLogEntryType}, }; use grin_gui_widgets::widget::header; use iced_aw::Card; @@ -13,54 +13,55 @@ use std::path::PathBuf; use super::tx_list::{HeaderState, TxList}; use { - super::super::super::{ - BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, - DEFAULT_SUB_HEADER_FONT_SIZE, SMALLER_FONT_SIZE, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - anyhow::Context, - grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, - TextInput, - }, - grin_gui_core::wallet::{StatusMessage, WalletInfo, WalletInterface}, - grin_gui_core::{node::amount_to_hr_string, theme::ColorPalette}, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - serde::{Deserialize, Serialize}, - std::sync::{Arc, RwLock}, + super::super::super::{ + BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, + DEFAULT_SUB_HEADER_FONT_SIZE, SMALLER_FONT_SIZE, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + anyhow::Context, + grin_gui_core::theme::{ + Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, + TextInput, + }, + grin_gui_core::wallet::{StatusMessage, WalletInfo, WalletInterface}, + grin_gui_core::{node::amount_to_hr_string, theme::ColorPalette}, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + serde::{Deserialize, Serialize}, + std::sync::{Arc, RwLock}, }; pub struct StateContainer { - // Slatepack data itself as read, or instructions - pub slatepack_read_data: String, - // Entire slatepack data - pub slatepack_read_data_full: String, - // whether we can continue - pub can_continue: bool, - // confirmation state, in separate panel - pub confirm_state: super::apply_tx_confirm::StateContainer, + // Slatepack data itself as read, or instructions + pub slatepack_read_data: String, + // Entire slatepack data + pub slatepack_read_data_full: String, + // whether we can continue + pub can_continue: bool, + // confirmation state, in separate panel + pub confirm_state: super::apply_tx_confirm::StateContainer, } impl Default for StateContainer { - fn default() -> Self { - Self { - slatepack_read_data: localized_string("tx-slatepack-read-result-default"), - slatepack_read_data_full: Default::default(), - can_continue: false, - confirm_state: Default::default(), - } - } + fn default() -> Self { + Self { + slatepack_read_data: localized_string("tx-slatepack-read-result-default"), + slatepack_read_data_full: Default::default(), + can_continue: false, + confirm_state: Default::default(), + } + } } impl StateContainer { - pub fn set_slate_direct(&mut self, slate: Slate, tx_log_entry: TxLogEntry) { - self.confirm_state.slatepack_parsed = Some((Slatepack::default(), slate, Some(tx_log_entry))); - self.confirm_state.is_self_send = true; - self.can_continue = true; - } + pub fn set_slate_direct(&mut self, slate: Slate, tx_log_entry: TxLogEntry) { + self.confirm_state.slatepack_parsed = + Some((Slatepack::default(), slate, Some(tx_log_entry))); + self.confirm_state.is_self_send = true; + self.can_continue = true; + } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -68,407 +69,404 @@ pub enum Action {} #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Back, - BackCleanup, - Continue, - Address(String), - ApplyTransaction(String), - ReadFromClipboardSuccess(String), - ReadFromClipboardFailure, - ShowSlate, + Back, + BackCleanup, + Continue, + Address(String), + ApplyTransaction(String), + ReadFromClipboardSuccess(String), + ReadFromClipboardFailure, + ShowSlate, } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.wallet_state.operation_state.apply_tx_state; - match message { - LocalViewInteraction::Back => { - log::debug!("Interaction::WalletOperationApplyTxViewInteraction(Back)"); - // If this was a self-send, cancel the transaction and remove from log - if state.confirm_state.is_self_send { - // Unwrap tx id - let tx_id = match state.confirm_state.slatepack_parsed.as_ref() { - Some(p) => { - if let Some (t) = p.2.as_ref() { - Some(t.id) - } else { - None - } - }, - None => None, - }; - let w = grin_gui.wallet_interface.clone(); - let fut = move || { - WalletInterface::cancel_tx(w, tx_id.unwrap()) - }; - - return Ok(Command::perform(fut(), |_| { - return Message::Interaction( + let state = &mut grin_gui.wallet_state.operation_state.apply_tx_state; + match message { + LocalViewInteraction::Back => { + log::debug!("Interaction::WalletOperationApplyTxViewInteraction(Back)"); + // If this was a self-send, cancel the transaction and remove from log + if state.confirm_state.is_self_send { + // Unwrap tx id + let tx_id = match state.confirm_state.slatepack_parsed.as_ref() { + Some(p) => { + if let Some(t) = p.2.as_ref() { + Some(t.id) + } else { + None + } + } + None => None, + }; + let w = grin_gui.wallet_interface.clone(); + let fut = move || WalletInterface::cancel_tx(w, tx_id.unwrap()); + + return Ok(Command::perform(fut(), |_| { + return Message::Interaction( Interaction::WalletOperationApplyTxViewInteraction( crate::gui::element::wallet::operation::apply_tx::LocalViewInteraction::BackCleanup, ), ); - })); - } else { - let fut = move || async {}; - return Ok(Command::perform(fut(), |_| { - return Message::Interaction( + })); + } else { + let fut = move || async {}; + return Ok(Command::perform(fut(), |_| { + return Message::Interaction( Interaction::WalletOperationApplyTxViewInteraction( crate::gui::element::wallet::operation::apply_tx::LocalViewInteraction::BackCleanup, ), ); - })); - } - }, - LocalViewInteraction::BackCleanup => { - state.slatepack_read_data = localized_string("tx-slatepack-read-result-default"); - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::Home; - state.confirm_state.slatepack_parsed = None; - state.slatepack_read_data_full = Default::default(); - state.confirm_state.is_self_send = false; - state.can_continue = false; - - }, - LocalViewInteraction::ReadFromClipboardSuccess(value) => { - debug!("Read from clipboard: {}", value); - let w = grin_gui.wallet_interface.clone(); - let decode_res = WalletInterface::decrypt_slatepack(w, value.clone()); - match decode_res { - Err(e) => { - state.slatepack_read_data = localized_string("tx-slatepack-read-failure"); - state.confirm_state.slatepack_parsed = None; - state.slatepack_read_data_full = Default::default(); - state.can_continue = false; - } - Ok(s) => { - debug!("{}", s.1); - // Truncate a bit for compact display purposes - let mut s1 = value.clone(); - s1.truncate(27); - let s2 = value - .clone() - .split_off(usize::saturating_sub(value.len(), 23)); - let short_display = format!("{}...{}", s1, s2); - - state.slatepack_read_data_full = value.clone(); - state.slatepack_read_data = short_display; - state.confirm_state.slatepack_parsed = Some(s); - state.can_continue = true; - } - } - } - LocalViewInteraction::Continue => { - state.slatepack_read_data = localized_string("tx-slatepack-read-result-default"); - state.can_continue = false; - let fut = move || async {}; - return Ok(Command::perform(fut(), |_| { - return Message::Interaction( + })); + } + } + LocalViewInteraction::BackCleanup => { + state.slatepack_read_data = localized_string("tx-slatepack-read-result-default"); + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::Home; + state.confirm_state.slatepack_parsed = None; + state.slatepack_read_data_full = Default::default(); + state.confirm_state.is_self_send = false; + state.can_continue = false; + } + LocalViewInteraction::ReadFromClipboardSuccess(value) => { + debug!("Read from clipboard: {}", value); + let w = grin_gui.wallet_interface.clone(); + let decode_res = WalletInterface::decrypt_slatepack(w, value.clone()); + match decode_res { + Err(e) => { + state.slatepack_read_data = localized_string("tx-slatepack-read-failure"); + state.confirm_state.slatepack_parsed = None; + state.slatepack_read_data_full = Default::default(); + state.can_continue = false; + } + Ok(s) => { + debug!("{}", s.1); + // Truncate a bit for compact display purposes + let mut s1 = value.clone(); + s1.truncate(27); + let s2 = value + .clone() + .split_off(usize::saturating_sub(value.len(), 23)); + let short_display = format!("{}...{}", s1, s2); + + state.slatepack_read_data_full = value.clone(); + state.slatepack_read_data = short_display; + state.confirm_state.slatepack_parsed = Some(s); + state.can_continue = true; + } + } + } + LocalViewInteraction::Continue => { + state.slatepack_read_data = localized_string("tx-slatepack-read-result-default"); + state.can_continue = false; + let fut = move || async {}; + return Ok(Command::perform(fut(), |_| { + return Message::Interaction( Interaction::WalletOperationApplyTxConfirmViewInteraction( crate::gui::element::wallet::operation::apply_tx_confirm::LocalViewInteraction::Accept ), ); - })); - } - LocalViewInteraction::ReadFromClipboardFailure => { - error!("Failed to read from clipboard"); - } - LocalViewInteraction::ShowSlate => { - // ensure back button on showing slate screen comes back here - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .submit_mode = Some(crate::gui::element::wallet::operation::Mode::ApplyTx); - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .encrypted_slate = Some(state.slatepack_read_data_full.clone()); - - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::ShowSlatepack; - } - LocalViewInteraction::Address(_) => {} - LocalViewInteraction::ApplyTransaction(_) => {} - } - Ok(Command::none()) + })); + } + LocalViewInteraction::ReadFromClipboardFailure => { + error!("Failed to read from clipboard"); + } + LocalViewInteraction::ShowSlate => { + // ensure back button on showing slate screen comes back here + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .submit_mode = Some(crate::gui::element::wallet::operation::Mode::ApplyTx); + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .encrypted_slate = Some(state.slatepack_read_data_full.clone()); + + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::ShowSlatepack; + } + LocalViewInteraction::Address(_) => {} + LocalViewInteraction::ApplyTransaction(_) => {} + } + Ok(Command::none()) } pub fn data_container<'a>(config: &'a Config, state: &'a StateContainer) -> Container<'a, Message> { - let unit_spacing = 15.0; - let mut title_key = localized_string("apply-tx"); - - // Just display Signing... and return while signing futures are being called - if state.confirm_state.is_signing { - title_key = localized_string("signing-tx"); - } - - // Title row - let title = Text::new(title_key) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - - let title_container = Container::new(title) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 2, // top - 0, // right - 2, // bottom - 0, // left - ])); - - // push more items on to header here: e.g. other buttons, things that belong on the header - let header_row = Row::new().push(title_container); - - let header_container = Container::new(header_row).padding(iced::Padding::from([ - 0, // top - 0, // right - DEFAULT_PADDING as u16, // bottom - 0, // left - ])); - - if state.confirm_state.is_signing { - let column = Column::new().push(header_container); - - let form_container = - Container::new(column) - .width(Length::Fill) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - // form container should be scrollable in tiny windows - let scrollable = Scrollable::new(form_container) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); - - let content = Container::new(scrollable) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let wrapper_column = Column::new().height(Length::Fill).push(content); - - // Returns the final container. - return Container::new(wrapper_column).padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])); - } - - let paste_instruction = Text::new(state.slatepack_read_data.clone()) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let paste_instruction_container = Container::new(paste_instruction) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let mut instruction_row = Row::new().push(paste_instruction_container); - - if state.can_continue { - let show_slate_label_container = Container::new( - Text::new(localized_string("tx-show-full-slatepack")).size(SMALLER_FONT_SIZE), - ) - .height(Length::Fixed(14.0)) - .width(Length::Fixed(60.0)) - .center_y() - .center_x(); - - let show_slate_button: Element = Button::new(show_slate_label_container) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::WalletOperationApplyTxViewInteraction( - LocalViewInteraction::ShowSlate, - )) - .padding(2) - .into(); - - let paste_again_label_container = - Container::new(Text::new(localized_string("tx-paste-again")).size(SMALLER_FONT_SIZE)) - .height(Length::Fixed(14.0)) - .width(Length::Fixed(60.0)) - .center_y() - .center_x(); - - let paste_again_button: Element = Button::new(paste_again_label_container) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::ReadSlatepackFromClipboard) - .padding(2) - .into(); - - instruction_row = instruction_row - .push(Space::with_width(Length::Fixed(2.0))) - .push(show_slate_button.map(Message::Interaction)) - .push(Space::with_width(Length::Fixed(2.0))) - .push(paste_again_button.map(Message::Interaction)); - } - - let mut instruction_col = Column::new(); - - if state.can_continue { - let pasted_tx_label = Text::new(localized_string("pasted-slatepack")) - .size(DEFAULT_SUB_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - let pasted_tx_label_container = Container::new(pasted_tx_label) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground); - instruction_col = instruction_col - .push(pasted_tx_label_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - } - - instruction_col = instruction_col.push(instruction_row); - - /*if state.can_continue { - let decrypted_tx_label = Text::new(localized_string("pasted-slatepack-details")) - .size(DEFAULT_SUB_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let decrypted_tx_label_container = Container::new(decrypted_tx_label) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground); - instruction_col = instruction_col - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(decrypted_tx_label_container) - }*/ - - let mut slatepack_area = Column::new(); - if state.can_continue { - // Add parsed slatepack contents area here - let parsed_slate_content = - super::apply_tx_confirm::data_container(config, &state.confirm_state); - - slatepack_area = slatepack_area.push(parsed_slate_content); - } - - let slatepack_area_container = Container::new(slatepack_area); - - let button_height = Length::Fixed(BUTTON_HEIGHT); - let button_width = Length::Fixed(BUTTON_WIDTH); - - let submit_button_label_container = - Container::new(Text::new(localized_string("tx-paste")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let mut submit_button = Button::new(submit_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::ReadSlatepackFromClipboard); - - let submit_button: Element = submit_button.into(); - - let continue_button_label_container = - Container::new(Text::new(localized_string("tx-continue")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let continue_button: Element = Button::new(continue_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletOperationApplyTxViewInteraction( - LocalViewInteraction::Continue, - )) - .into(); - - let cancel_button_label_container = - Container::new(Text::new(localized_string("cancel")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let cancel_button: Element = Button::new(cancel_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletOperationApplyTxViewInteraction( - LocalViewInteraction::Back, - )) - .into(); - - let submit_container = Container::new(submit_button.map(Message::Interaction)).padding(1); - let submit_container = Container::new(submit_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let continue_container = Container::new(continue_button.map(Message::Interaction)).padding(1); - let continue_container = Container::new(continue_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); - let cancel_container = Container::new(cancel_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let mut button_row = Row::new(); - if !state.can_continue { - button_row = button_row - .push(submit_container) - .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))); - } else { - button_row = button_row - .push(continue_container) - .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) - } - - button_row = button_row.push(cancel_container); - - let mut column = Column::new() - .push(header_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(slatepack_area_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); - - if !state.confirm_state.is_self_send { - column = column - .push(instruction_col) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); - } - - column = column - .push(button_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 10.0), - )); - - let form_container = Container::new(column) - .width(Length::Fill) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - // form container should be scrollable in tiny windows - let scrollable = Scrollable::new(form_container) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); - - let content = Container::new(scrollable) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let wrapper_column = Column::new().height(Length::Fill).push(content); - - // Returns the final container. - Container::new(wrapper_column).padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + let unit_spacing = 15.0; + let mut title_key = localized_string("apply-tx"); + + // Just display Signing... and return while signing futures are being called + if state.confirm_state.is_signing { + title_key = localized_string("signing-tx"); + } + + // Title row + let title = Text::new(title_key) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + + let title_container = Container::new(title) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 2, // top + 0, // right + 2, // bottom + 0, // left + ])); + + // push more items on to header here: e.g. other buttons, things that belong on the header + let header_row = Row::new().push(title_container); + + let header_container = Container::new(header_row).padding(iced::Padding::from([ + 0, // top + 0, // right + DEFAULT_PADDING as u16, // bottom + 0, // left + ])); + + if state.confirm_state.is_signing { + let column = Column::new().push(header_container); + + let form_container = + Container::new(column) + .width(Length::Fill) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + // form container should be scrollable in tiny windows + let scrollable = Scrollable::new(form_container) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); + + let content = Container::new(scrollable) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let wrapper_column = Column::new().height(Length::Fill).push(content); + + // Returns the final container. + return Container::new(wrapper_column).padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])); + } + + let paste_instruction = Text::new(state.slatepack_read_data.clone()) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let paste_instruction_container = Container::new(paste_instruction) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let mut instruction_row = Row::new().push(paste_instruction_container); + + if state.can_continue { + let show_slate_label_container = Container::new( + Text::new(localized_string("tx-show-full-slatepack")).size(SMALLER_FONT_SIZE), + ) + .height(Length::Fixed(14.0)) + .width(Length::Fixed(60.0)) + .center_y() + .center_x(); + + let show_slate_button: Element = Button::new(show_slate_label_container) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::WalletOperationApplyTxViewInteraction( + LocalViewInteraction::ShowSlate, + )) + .padding(2) + .into(); + + let paste_again_label_container = + Container::new(Text::new(localized_string("tx-paste-again")).size(SMALLER_FONT_SIZE)) + .height(Length::Fixed(14.0)) + .width(Length::Fixed(60.0)) + .center_y() + .center_x(); + + let paste_again_button: Element = Button::new(paste_again_label_container) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::ReadSlatepackFromClipboard) + .padding(2) + .into(); + + instruction_row = instruction_row + .push(Space::with_width(Length::Fixed(2.0))) + .push(show_slate_button.map(Message::Interaction)) + .push(Space::with_width(Length::Fixed(2.0))) + .push(paste_again_button.map(Message::Interaction)); + } + + let mut instruction_col = Column::new(); + + if state.can_continue { + let pasted_tx_label = Text::new(localized_string("pasted-slatepack")) + .size(DEFAULT_SUB_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + let pasted_tx_label_container = Container::new(pasted_tx_label) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground); + instruction_col = instruction_col + .push(pasted_tx_label_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + } + + instruction_col = instruction_col.push(instruction_row); + + /*if state.can_continue { + let decrypted_tx_label = Text::new(localized_string("pasted-slatepack-details")) + .size(DEFAULT_SUB_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let decrypted_tx_label_container = Container::new(decrypted_tx_label) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground); + instruction_col = instruction_col + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(decrypted_tx_label_container) + }*/ + + let mut slatepack_area = Column::new(); + if state.can_continue { + // Add parsed slatepack contents area here + let parsed_slate_content = + super::apply_tx_confirm::data_container(config, &state.confirm_state); + + slatepack_area = slatepack_area.push(parsed_slate_content); + } + + let slatepack_area_container = Container::new(slatepack_area); + + let button_height = Length::Fixed(BUTTON_HEIGHT); + let button_width = Length::Fixed(BUTTON_WIDTH); + + let submit_button_label_container = + Container::new(Text::new(localized_string("tx-paste")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let mut submit_button = Button::new(submit_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::ReadSlatepackFromClipboard); + + let submit_button: Element = submit_button.into(); + + let continue_button_label_container = + Container::new(Text::new(localized_string("tx-continue")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let continue_button: Element = Button::new(continue_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletOperationApplyTxViewInteraction( + LocalViewInteraction::Continue, + )) + .into(); + + let cancel_button_label_container = + Container::new(Text::new(localized_string("cancel")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let cancel_button: Element = Button::new(cancel_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletOperationApplyTxViewInteraction( + LocalViewInteraction::Back, + )) + .into(); + + let submit_container = Container::new(submit_button.map(Message::Interaction)).padding(1); + let submit_container = Container::new(submit_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let continue_container = Container::new(continue_button.map(Message::Interaction)).padding(1); + let continue_container = Container::new(continue_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); + let cancel_container = Container::new(cancel_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let mut button_row = Row::new(); + if !state.can_continue { + button_row = button_row + .push(submit_container) + .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))); + } else { + button_row = button_row + .push(continue_container) + .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) + } + + button_row = button_row.push(cancel_container); + + let mut column = Column::new() + .push(header_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(slatepack_area_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); + + if !state.confirm_state.is_self_send { + column = column + .push(instruction_col) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); + } + + column = column + .push(button_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 10.0), + )); + + let form_container = Container::new(column) + .width(Length::Fill) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + // form container should be scrollable in tiny windows + let scrollable = Scrollable::new(form_container) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); + + let content = Container::new(scrollable) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let wrapper_column = Column::new().height(Length::Fill).push(content); + + // Returns the final container. + Container::new(wrapper_column).padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/wallet/operation/apply_tx_confirm.rs b/src/gui/element/wallet/operation/apply_tx_confirm.rs index 3c91509..bc0fad3 100644 --- a/src/gui/element/wallet/operation/apply_tx_confirm.rs +++ b/src/gui/element/wallet/operation/apply_tx_confirm.rs @@ -2,11 +2,11 @@ use super::tx_list::{self, ExpandType}; use crate::log_error; use async_std::prelude::FutureExt; use grin_gui_core::{ - config::{Config, TxMethod}, - wallet::{ - ContractNewArgsAPI, ContractSetupArgsAPI, ProofArgs, Slate, SlateState, Slatepack, - TxLogEntry, TxLogEntryType, - }, + config::{Config, TxMethod}, + wallet::{ + ContractNewArgsAPI, ContractSetupArgsAPI, ProofArgs, Slate, SlateState, Slatepack, + TxLogEntry, TxLogEntryType, + }, }; use grin_gui_widgets::widget::header; use iced_aw::Card; @@ -18,54 +18,54 @@ use std::path::PathBuf; use super::tx_list::{HeaderState, TxList}; use { - super::super::super::{ - BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, - SMALLER_FONT_SIZE, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - anyhow::Context, - grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, - TextInput, - }, - grin_gui_core::wallet::{parse_abs_tx_amount_fee, StatusMessage, WalletInfo, WalletInterface}, - grin_gui_core::{node::amount_to_hr_string, theme::ColorPalette}, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - serde::{Deserialize, Serialize}, - std::sync::{Arc, RwLock}, + super::super::super::{ + BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, + SMALLER_FONT_SIZE, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + anyhow::Context, + grin_gui_core::theme::{ + Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, + TextInput, + }, + grin_gui_core::wallet::{parse_abs_tx_amount_fee, StatusMessage, WalletInfo, WalletInterface}, + grin_gui_core::{node::amount_to_hr_string, theme::ColorPalette}, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + serde::{Deserialize, Serialize}, + std::sync::{Arc, RwLock}, }; pub struct StateContainer { - // pub back_button_state: button::State, - // pub copy_address_button_state: button::State, - // pub address_state: text_input::State, - pub address_value: String, - // Slatepack read result - pub slatepack_read_result: String, - // Actual read slatepack - pub slatepack_parsed: Option<(Slatepack, Slate, Option)>, - // In the state of applying slatepack - pub is_signing: bool, - // Is a self send - pub is_self_send: bool, + // pub back_button_state: button::State, + // pub copy_address_button_state: button::State, + // pub address_state: text_input::State, + pub address_value: String, + // Slatepack read result + pub slatepack_read_result: String, + // Actual read slatepack + pub slatepack_parsed: Option<(Slatepack, Slate, Option)>, + // In the state of applying slatepack + pub is_signing: bool, + // Is a self send + pub is_self_send: bool, } impl Default for StateContainer { - fn default() -> Self { - Self { - // back_button_state: Default::default(), - // copy_address_button_state: Default::default(), - // address_state: Default::default(), - address_value: Default::default(), - slatepack_read_result: localized_string("tx-slatepack-read-result-default"), - slatepack_parsed: None, - is_signing: false, - is_self_send: false, - } - } + fn default() -> Self { + Self { + // back_button_state: Default::default(), + // copy_address_button_state: Default::default(), + // address_state: Default::default(), + address_value: Default::default(), + slatepack_read_result: localized_string("tx-slatepack-read-result-default"), + slatepack_parsed: None, + is_signing: false, + is_self_send: false, + } + } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -73,411 +73,411 @@ pub enum Action {} #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Back, - Accept, - TxAcceptSuccess(Slate, Option, bool), - TxAcceptFailure(Arc>>), + Back, + Accept, + TxAcceptSuccess(Slate, Option, bool), + TxAcceptFailure(Arc>>), } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui - .wallet_state - .operation_state - .apply_tx_state - .confirm_state; - match message { - LocalViewInteraction::Back => { - log::debug!("Interaction::Back"); - state.is_signing = false; - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::Home; - } - LocalViewInteraction::Accept => { - grin_gui.error.take(); - - log::debug!("Interaction::WalletOperationApplyTxConfirmViewInteraction(Accept)"); - if state.slatepack_parsed.is_none() { - log::debug!("you should never see this - dev make sure slatepack is not None"); - return Ok(Command::none()); - } - - let (slatepack, slate, tx_log_entry) = state.slatepack_parsed.as_ref().unwrap(); - - let sp_sending_address = match &slatepack.sender { - None => "None".to_string(), - Some(s) => s.to_string(), - }; - - let w = grin_gui.wallet_interface.clone(); - let out_slate = slate.clone(); - if grin_gui.config.tx_method == TxMethod::Legacy { - match slate.state { - SlateState::Standard1 => { - state.is_signing = true; - let fut = move || { - WalletInterface::receive_tx_from_s1(w, out_slate, sp_sending_address) - }; - - return Ok(Command::perform(fut(), |r| { - match r.context("Failed to Progress Transaction") { - Ok((slate, enc_slate)) => Message::Interaction( - Interaction::WalletOperationApplyTxConfirmViewInteraction( - LocalViewInteraction::TxAcceptSuccess( - slate, enc_slate, false, - ), - ), - ), - Err(e) => Message::Interaction( - Interaction::WalletOperationApplyTxConfirmViewInteraction( - LocalViewInteraction::TxAcceptFailure(Arc::new( - RwLock::new(Some(e)), - )), - ), - ), - } - })); - } - SlateState::Standard2 => { - state.is_signing = true; - let fut = move || WalletInterface::finalize_from_s2(w, out_slate, true); - - return Ok(Command::perform(fut(), |r| { - match r.context("Failed to Progress Transaction") { - Ok((slate, enc_slate)) => Message::Interaction( - Interaction::WalletOperationApplyTxConfirmViewInteraction( - LocalViewInteraction::TxAcceptSuccess( - slate, enc_slate, true, - ), - ), - ), - Err(e) => Message::Interaction( - Interaction::WalletOperationApplyTxConfirmViewInteraction( - LocalViewInteraction::TxAcceptFailure(Arc::new( - RwLock::new(Some(e)), - )), - ), - ), - } - })); - } - _ => { - log::error!("Slate state not yet supported"); - return Ok(Command::none()); - } - } - } else { - let (sp_sending_address, sender_pub_key) = match &slatepack.sender { - None => ("None".to_string(), None), - Some(s) => (s.to_string(), Some(s.pub_key)), - }; - - let net_change = match slate.state { - SlateState::Standard1 => Some(slate.amount as i64), - SlateState::Standard2 => None, - SlateState::Invoice1 => Some(-(slate.amount as i64)), - SlateState::Invoice2 => None, - _ => { - log::error!("Slate state not yet supported"); - return Ok(Command::none()); - } - }; - - // Should be a simplified context flow here, where we can be recipient or sender! - let mut args = ContractSetupArgsAPI { - net_change, - proof_args: ProofArgs { - sender_address: sender_pub_key, - ..Default::default() - }, - ..Default::default() - }; - state.is_signing = true; - - if slate.state == SlateState::Invoice1 { - args = ContractSetupArgsAPI { - net_change, - ..Default::default() - }; - } - - if slate.state == SlateState::Invoice2 { - args = ContractSetupArgsAPI { - ..Default::default() - }; - }; - - if state.is_self_send { - debug!("SLATE STATE SELF_SEND: {}", slate.state); - let fut = move || WalletInterface::post_tx(w, out_slate); - return Ok(Command::perform(fut(), |r| { - match r.context("Failed to Progress Transaction") { - Ok((slate, enc_slate)) => { - let finished = slate.state == SlateState::Standard3 - || slate.state == SlateState::Invoice3; - Message::Interaction( - Interaction::WalletOperationApplyTxConfirmViewInteraction( - LocalViewInteraction::TxAcceptSuccess( - slate, enc_slate, true, - ), - ), - ) - } - Err(e) => Message::Interaction( - Interaction::WalletOperationApplyTxConfirmViewInteraction( - LocalViewInteraction::TxAcceptFailure(Arc::new(RwLock::new( - Some(e), - ))), - ), - ), - } - })); - } else { - let fut = move || { - debug!("SIGN ARGS: {:?}", args); - WalletInterface::contract_sign(w, out_slate, args, sp_sending_address, true) - }; - - return Ok(Command::perform(fut(), |r| { - match r.context("Failed to Progress Transaction") { - Ok((slate, enc_slate)) => { - debug!("SLATE STATE: {}", slate.state); - let finished = slate.state == SlateState::Standard3 - || slate.state == SlateState::Invoice3; - Message::Interaction( - Interaction::WalletOperationApplyTxConfirmViewInteraction( - LocalViewInteraction::TxAcceptSuccess( - slate, enc_slate, finished, - ), - ), - ) - } - Err(e) => Message::Interaction( - Interaction::WalletOperationApplyTxConfirmViewInteraction( - LocalViewInteraction::TxAcceptFailure(Arc::new(RwLock::new( - Some(e), - ))), - ), - ), - } - })); - } - } - } - LocalViewInteraction::TxAcceptSuccess(slate, encrypted_slate, finished) => { - // Output the latest slatepack, overriding any previous - if let Some(ref s) = encrypted_slate { - if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { - let out_file_name = format!("{}/{}.slatepack", dir, slate.id); - let mut output = File::create(out_file_name.clone())?; - output.write_all(&s.as_bytes())?; - output.sync_all()?; - } - } else { - // If no encrypted slate, tx was posted so remove file - if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { - let out_file_name = format!("{}/{}.slatepack", dir, slate.id); - let _ = fs::remove_file(out_file_name); - } - } - - state.is_signing = false; - - if finished { - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::TxDone; - } else { - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .encrypted_slate = encrypted_slate; - - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .title_label = localized_string("tx-continue-success-title"); - - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .desc = localized_string("tx-continue-success-desc"); - - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::ShowSlatepack; - } - } - LocalViewInteraction::TxAcceptFailure(err) => { - state.is_signing = false; - grin_gui.error = err.write().unwrap().take(); - if let Some(e) = grin_gui.error.as_ref() { - log_error(e); - } - } - } - Ok(Command::none()) + let state = &mut grin_gui + .wallet_state + .operation_state + .apply_tx_state + .confirm_state; + match message { + LocalViewInteraction::Back => { + log::debug!("Interaction::Back"); + state.is_signing = false; + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::Home; + } + LocalViewInteraction::Accept => { + grin_gui.error.take(); + + log::debug!("Interaction::WalletOperationApplyTxConfirmViewInteraction(Accept)"); + if state.slatepack_parsed.is_none() { + log::debug!("you should never see this - dev make sure slatepack is not None"); + return Ok(Command::none()); + } + + let (slatepack, slate, tx_log_entry) = state.slatepack_parsed.as_ref().unwrap(); + + let sp_sending_address = match &slatepack.sender { + None => "None".to_string(), + Some(s) => s.to_string(), + }; + + let w = grin_gui.wallet_interface.clone(); + let out_slate = slate.clone(); + if grin_gui.config.tx_method == TxMethod::Legacy { + match slate.state { + SlateState::Standard1 => { + state.is_signing = true; + let fut = move || { + WalletInterface::receive_tx_from_s1(w, out_slate, sp_sending_address) + }; + + return Ok(Command::perform(fut(), |r| { + match r.context("Failed to Progress Transaction") { + Ok((slate, enc_slate)) => Message::Interaction( + Interaction::WalletOperationApplyTxConfirmViewInteraction( + LocalViewInteraction::TxAcceptSuccess( + slate, enc_slate, false, + ), + ), + ), + Err(e) => Message::Interaction( + Interaction::WalletOperationApplyTxConfirmViewInteraction( + LocalViewInteraction::TxAcceptFailure(Arc::new( + RwLock::new(Some(e)), + )), + ), + ), + } + })); + } + SlateState::Standard2 => { + state.is_signing = true; + let fut = move || WalletInterface::finalize_from_s2(w, out_slate, true); + + return Ok(Command::perform(fut(), |r| { + match r.context("Failed to Progress Transaction") { + Ok((slate, enc_slate)) => Message::Interaction( + Interaction::WalletOperationApplyTxConfirmViewInteraction( + LocalViewInteraction::TxAcceptSuccess( + slate, enc_slate, true, + ), + ), + ), + Err(e) => Message::Interaction( + Interaction::WalletOperationApplyTxConfirmViewInteraction( + LocalViewInteraction::TxAcceptFailure(Arc::new( + RwLock::new(Some(e)), + )), + ), + ), + } + })); + } + _ => { + log::error!("Slate state not yet supported"); + return Ok(Command::none()); + } + } + } else { + let (sp_sending_address, sender_pub_key) = match &slatepack.sender { + None => ("None".to_string(), None), + Some(s) => (s.to_string(), Some(s.pub_key)), + }; + + let net_change = match slate.state { + SlateState::Standard1 => Some(slate.amount as i64), + SlateState::Standard2 => None, + SlateState::Invoice1 => Some(-(slate.amount as i64)), + SlateState::Invoice2 => None, + _ => { + log::error!("Slate state not yet supported"); + return Ok(Command::none()); + } + }; + + // Should be a simplified context flow here, where we can be recipient or sender! + let mut args = ContractSetupArgsAPI { + net_change, + proof_args: ProofArgs { + sender_address: sender_pub_key, + ..Default::default() + }, + ..Default::default() + }; + state.is_signing = true; + + if slate.state == SlateState::Invoice1 { + args = ContractSetupArgsAPI { + net_change, + ..Default::default() + }; + } + + if slate.state == SlateState::Invoice2 { + args = ContractSetupArgsAPI { + ..Default::default() + }; + }; + + if state.is_self_send { + debug!("SLATE STATE SELF_SEND: {}", slate.state); + let fut = move || WalletInterface::post_tx(w, out_slate); + return Ok(Command::perform(fut(), |r| { + match r.context("Failed to Progress Transaction") { + Ok((slate, enc_slate)) => { + let finished = slate.state == SlateState::Standard3 + || slate.state == SlateState::Invoice3; + Message::Interaction( + Interaction::WalletOperationApplyTxConfirmViewInteraction( + LocalViewInteraction::TxAcceptSuccess( + slate, enc_slate, true, + ), + ), + ) + } + Err(e) => Message::Interaction( + Interaction::WalletOperationApplyTxConfirmViewInteraction( + LocalViewInteraction::TxAcceptFailure(Arc::new(RwLock::new( + Some(e), + ))), + ), + ), + } + })); + } else { + let fut = move || { + debug!("SIGN ARGS: {:?}", args); + WalletInterface::contract_sign(w, out_slate, args, sp_sending_address, true) + }; + + return Ok(Command::perform(fut(), |r| { + match r.context("Failed to Progress Transaction") { + Ok((slate, enc_slate)) => { + debug!("SLATE STATE: {}", slate.state); + let finished = slate.state == SlateState::Standard3 + || slate.state == SlateState::Invoice3; + Message::Interaction( + Interaction::WalletOperationApplyTxConfirmViewInteraction( + LocalViewInteraction::TxAcceptSuccess( + slate, enc_slate, finished, + ), + ), + ) + } + Err(e) => Message::Interaction( + Interaction::WalletOperationApplyTxConfirmViewInteraction( + LocalViewInteraction::TxAcceptFailure(Arc::new(RwLock::new( + Some(e), + ))), + ), + ), + } + })); + } + } + } + LocalViewInteraction::TxAcceptSuccess(slate, encrypted_slate, finished) => { + // Output the latest slatepack, overriding any previous + if let Some(ref s) = encrypted_slate { + if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { + let out_file_name = format!("{}/{}.slatepack", dir, slate.id); + let mut output = File::create(out_file_name.clone())?; + output.write_all(&s.as_bytes())?; + output.sync_all()?; + } + } else { + // If no encrypted slate, tx was posted so remove file + if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { + let out_file_name = format!("{}/{}.slatepack", dir, slate.id); + let _ = fs::remove_file(out_file_name); + } + } + + state.is_signing = false; + + if finished { + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::TxDone; + } else { + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .encrypted_slate = encrypted_slate; + + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .title_label = localized_string("tx-continue-success-title"); + + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .desc = localized_string("tx-continue-success-desc"); + + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::ShowSlatepack; + } + } + LocalViewInteraction::TxAcceptFailure(err) => { + state.is_signing = false; + grin_gui.error = err.write().unwrap().take(); + if let Some(e) = grin_gui.error.as_ref() { + log_error(e); + } + } + } + Ok(Command::none()) } // Very hacky, but these amount string will require different placements of // words + amount in different languages fn parse_info_strings(in_str: &str, amount: &str) -> String { - let amount_split: Vec<&str> = in_str.split("[AMOUNT]").collect(); - let mut amount_included = format!("{}{}", amount_split[0], amount); - if amount_split.len() > 1 { - amount_included = format!("{}{}", amount_included, amount_split[1]); - } - amount_included + let amount_split: Vec<&str> = in_str.split("[AMOUNT]").collect(); + let mut amount_included = format!("{}{}", amount_split[0], amount); + if amount_split.len() > 1 { + amount_included = format!("{}{}", amount_included, amount_split[1]); + } + amount_included } pub fn data_container<'a>(config: &'a Config, state: &'a StateContainer) -> Container<'a, Message> { - let unit_spacing = 15.0; - - if state.slatepack_parsed.is_none() { - return Container::new(Text::new( - "you should never see this - dev make sure slatepack is not None", - )); - } - - // Decode/parse/etc fields for display here - let (slatepack, slate, tx_log_entry) = state.slatepack_parsed.as_ref().unwrap(); - - let sp_sending_address = match &slatepack.sender { - None => "None".to_string(), - Some(s) => s.to_string(), - }; - - let mut amount = amount_to_hr_string(slate.amount, true); - let mut other_wallet_label = localized_string("tx-sender-name"); - let mut reception_instruction_1 = localized_string("tx-reception-instruction"); - let mut reception_instruction_2 = localized_string("tx-reception-instruction-2"); - - // TODO: What's displayed here should change based on the slate state - let mut state_text = match slate.state { - SlateState::Standard1 => parse_info_strings(&localized_string("tx-reception"), &amount), - SlateState::Standard2 => { - let mut fee = String::default(); - other_wallet_label = localized_string("tx-recipient-name"); - if let Some(tx) = tx_log_entry { - (amount, fee) = parse_abs_tx_amount_fee(tx, !state.is_self_send); - } - reception_instruction_2 = - parse_info_strings(&localized_string("tx-s1-finalization-3"), &fee); - reception_instruction_1 = - parse_info_strings(&localized_string("tx-s1-finalization-2"), &fee); - let amt_stmt = parse_info_strings(&localized_string("tx-s1-finalization-1"), &amount); - amt_stmt - } - SlateState::Standard3 => "This transaction is finalised - Standard workflow".to_owned(), - _ => "Support still in development".to_owned(), - }; - - if config.tx_method == TxMethod::Contracts { - state_text = match slate.state { - SlateState::Standard1 => { - other_wallet_label = localized_string("tx-sender-name"); - parse_info_strings(&localized_string("tx-reception"), &amount) - } - SlateState::Standard2 => { - let mut fee = String::default(); - other_wallet_label = localized_string("tx-sender-name"); - if let Some(tx) = tx_log_entry { - (amount, fee) = parse_abs_tx_amount_fee(tx, !state.is_self_send); - } - reception_instruction_2 = - parse_info_strings(&localized_string("tx-s1-finalization-3"), &fee); - reception_instruction_1 = - parse_info_strings(&localized_string("tx-s1-finalization-2"), &fee); - let amt_stmt = match state.is_self_send { - true => parse_info_strings( - &localized_string("tx-s1-finalization-self-send"), - &amount, - ), - false => parse_info_strings(&localized_string("tx-s1-finalization-1"), &amount), - }; - - amt_stmt - } - SlateState::Standard3 => "This transaction is finalised - Standard Workflow".to_owned(), - SlateState::Invoice1 => { - other_wallet_label = localized_string("tx-recipient-name"); - parse_info_strings(&localized_string("tx-sending"), &amount) - } - SlateState::Invoice2 => { - let mut fee = String::default(); - other_wallet_label = localized_string("tx-sender-name"); - if let Some(tx) = tx_log_entry { - (amount, fee) = parse_abs_tx_amount_fee(tx, false); - } - reception_instruction_2 = localized_string("tx-s1-finalization-3"); - reception_instruction_1 = "".to_owned(); - //parse_info_strings(&localized_string("tx-s1-finalization-2"), &fee); - - parse_info_strings(&localized_string("tx-i1-finalization-1"), &amount) - } - _ => state_text, - }; - - if state.is_self_send { - other_wallet_label = localized_string("wallet-self-send-instruction"); - } - } - - // TX State (i.e. Stage) - let state = Text::new(state_text).size(DEFAULT_FONT_SIZE); - - let state_container = - Container::new(state).style(grin_gui_core::theme::ContainerStyle::BrightBackground); - - let state_row = Row::new().push(state_container); - - // Sender address - let sender_address_label = Text::new(format!("{} ", other_wallet_label)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let sender_address_label_container = Container::new(sender_address_label) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let sender_address = Text::new(sp_sending_address).size(DEFAULT_FONT_SIZE); - //.width(Length::Fixed(400)) - //.style(grin_gui_core::theme::TextInputStyle::AddonsQuery); - - let sender_address_container = Container::new(sender_address) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground); - - let sender_address_row = Row::new() - .push(sender_address_label_container) - .push(sender_address_container); - - let instruction_label = Text::new(format!("{} ", reception_instruction_1)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let instruction_label_container = Container::new(instruction_label) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let instruction_label_2 = Text::new(format!("{} ", localized_string(&reception_instruction_2))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let instruction_label_container_2 = Container::new(instruction_label_2) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let column = Column::new() - .push(state_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(sender_address_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(instruction_label_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(instruction_label_container_2); - - let wrapper_column = Column::new().height(Length::Fill).push(column); - - // Returns the final container. - Container::new(wrapper_column) + let unit_spacing = 15.0; + + if state.slatepack_parsed.is_none() { + return Container::new(Text::new( + "you should never see this - dev make sure slatepack is not None", + )); + } + + // Decode/parse/etc fields for display here + let (slatepack, slate, tx_log_entry) = state.slatepack_parsed.as_ref().unwrap(); + + let sp_sending_address = match &slatepack.sender { + None => "None".to_string(), + Some(s) => s.to_string(), + }; + + let mut amount = amount_to_hr_string(slate.amount, true); + let mut other_wallet_label = localized_string("tx-sender-name"); + let mut reception_instruction_1 = localized_string("tx-reception-instruction"); + let mut reception_instruction_2 = localized_string("tx-reception-instruction-2"); + + // TODO: What's displayed here should change based on the slate state + let mut state_text = match slate.state { + SlateState::Standard1 => parse_info_strings(&localized_string("tx-reception"), &amount), + SlateState::Standard2 => { + let mut fee = String::default(); + other_wallet_label = localized_string("tx-recipient-name"); + if let Some(tx) = tx_log_entry { + (amount, fee) = parse_abs_tx_amount_fee(tx, !state.is_self_send); + } + reception_instruction_2 = + parse_info_strings(&localized_string("tx-s1-finalization-3"), &fee); + reception_instruction_1 = + parse_info_strings(&localized_string("tx-s1-finalization-2"), &fee); + let amt_stmt = parse_info_strings(&localized_string("tx-s1-finalization-1"), &amount); + amt_stmt + } + SlateState::Standard3 => "This transaction is finalised - Standard workflow".to_owned(), + _ => "Support still in development".to_owned(), + }; + + if config.tx_method == TxMethod::Contracts { + state_text = match slate.state { + SlateState::Standard1 => { + other_wallet_label = localized_string("tx-sender-name"); + parse_info_strings(&localized_string("tx-reception"), &amount) + } + SlateState::Standard2 => { + let mut fee = String::default(); + other_wallet_label = localized_string("tx-sender-name"); + if let Some(tx) = tx_log_entry { + (amount, fee) = parse_abs_tx_amount_fee(tx, !state.is_self_send); + } + reception_instruction_2 = + parse_info_strings(&localized_string("tx-s1-finalization-3"), &fee); + reception_instruction_1 = + parse_info_strings(&localized_string("tx-s1-finalization-2"), &fee); + let amt_stmt = match state.is_self_send { + true => parse_info_strings( + &localized_string("tx-s1-finalization-self-send"), + &amount, + ), + false => parse_info_strings(&localized_string("tx-s1-finalization-1"), &amount), + }; + + amt_stmt + } + SlateState::Standard3 => "This transaction is finalised - Standard Workflow".to_owned(), + SlateState::Invoice1 => { + other_wallet_label = localized_string("tx-recipient-name"); + parse_info_strings(&localized_string("tx-sending"), &amount) + } + SlateState::Invoice2 => { + let mut fee = String::default(); + other_wallet_label = localized_string("tx-sender-name"); + if let Some(tx) = tx_log_entry { + (amount, fee) = parse_abs_tx_amount_fee(tx, false); + } + reception_instruction_2 = localized_string("tx-s1-finalization-3"); + reception_instruction_1 = "".to_owned(); + //parse_info_strings(&localized_string("tx-s1-finalization-2"), &fee); + + parse_info_strings(&localized_string("tx-i1-finalization-1"), &amount) + } + _ => state_text, + }; + + if state.is_self_send { + other_wallet_label = localized_string("wallet-self-send-instruction"); + } + } + + // TX State (i.e. Stage) + let state = Text::new(state_text).size(DEFAULT_FONT_SIZE); + + let state_container = + Container::new(state).style(grin_gui_core::theme::ContainerStyle::BrightBackground); + + let state_row = Row::new().push(state_container); + + // Sender address + let sender_address_label = Text::new(format!("{} ", other_wallet_label)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let sender_address_label_container = Container::new(sender_address_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let sender_address = Text::new(sp_sending_address).size(DEFAULT_FONT_SIZE); + //.width(Length::Fixed(400)) + //.style(grin_gui_core::theme::TextInputStyle::AddonsQuery); + + let sender_address_container = Container::new(sender_address) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground); + + let sender_address_row = Row::new() + .push(sender_address_label_container) + .push(sender_address_container); + + let instruction_label = Text::new(format!("{} ", reception_instruction_1)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let instruction_label_container = Container::new(instruction_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let instruction_label_2 = Text::new(format!("{} ", localized_string(&reception_instruction_2))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let instruction_label_container_2 = Container::new(instruction_label_2) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let column = Column::new() + .push(state_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(sender_address_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(instruction_label_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(instruction_label_container_2); + + let wrapper_column = Column::new().height(Length::Fill).push(column); + + // Returns the final container. + Container::new(wrapper_column) } diff --git a/src/gui/element/wallet/operation/chart.rs b/src/gui/element/wallet/operation/chart.rs index 84ff9fa..70451e8 100644 --- a/src/gui/element/wallet/operation/chart.rs +++ b/src/gui/element/wallet/operation/chart.rs @@ -4,23 +4,23 @@ extern crate plotters; use crate::gui::{element::DEFAULT_PADDING, Message}; use chrono::{DateTime, Utc}; use grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, - TextInput, Theme, + Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, + TextInput, Theme, }; use iced::{ - alignment::{Horizontal, Vertical}, - executor, - widget::{ - canvas::{self, event, Cache, Frame, Geometry}, - Space, - }, - mouse::Cursor, - Alignment, Command, Font, Length, Point, Settings, Size, Subscription, + alignment::{Horizontal, Vertical}, + executor, + mouse::Cursor, + widget::{ + canvas::{self, event, Cache, Frame, Geometry}, + Space, + }, + Alignment, Command, Font, Length, Point, Settings, Size, Subscription, }; use plotters::{ - coord::{types::RangedCoordf32, ReverseCoordTranslate}, - prelude::*, + coord::{types::RangedCoordf32, ReverseCoordTranslate}, + prelude::*, }; use plotters_backend::DrawingBackend; use plotters_iced::{Chart, ChartWidget}; @@ -35,244 +35,246 @@ const FONT_BOLD: Font = Font::with_name("notosans-bold.ttf"); #[derive(Default)] pub struct BalanceChart { - data_points: VecDeque<(DateTime, f64)>, - cursor_index: Option, - caption_index: Option, - theme: Theme, + data_points: VecDeque<(DateTime, f64)>, + cursor_index: Option, + caption_index: Option, + theme: Theme, } impl BalanceChart { - /// Create a new chart widget - /// `data` is an iterator of `(DateTime, f64)` tuples in descending order - newest datetime first - pub fn new( - theme: Theme, - data: impl Iterator, f64)>, - cursor_index: Option, - caption_index: Option, - ) -> Element<'static, Message> { - let data_points: VecDeque<_> = data.collect(); - let chart = BalanceChart { - data_points, - theme, - cursor_index, - caption_index, - }; - - - Container::new( - Column::new() - .width(Length::Fill) - .height(Length::Fill) - .spacing(5) - .push( - ChartWidget::new(chart).height(Length::Fill)/* .resolve_font( - |_, style| match style { - plotters_backend::FontStyle::Bold => FONT_BOLD, - _ => FONT_REGULAR, - },*/ - ), - ) - .width(Length::Fill) - .height(Length::Fill) - .align_x(Horizontal::Center) - .align_y(Vertical::Center) - .into() - } - - pub fn push_data(&mut self, time: DateTime, value: f64) { - self.data_points.push_front((time, value)); - } + /// Create a new chart widget + /// `data` is an iterator of `(DateTime, f64)` tuples in descending order - newest datetime first + pub fn new( + theme: Theme, + data: impl Iterator, f64)>, + cursor_index: Option, + caption_index: Option, + ) -> Element<'static, Message> { + let data_points: VecDeque<_> = data.collect(); + let chart = BalanceChart { + data_points, + theme, + cursor_index, + caption_index, + }; + + Container::new( + Column::new() + .width(Length::Fill) + .height(Length::Fill) + .spacing(5) + .push( + ChartWidget::new(chart).height(Length::Fill), /* .resolve_font( + |_, style| match style { + plotters_backend::FontStyle::Bold => FONT_BOLD, + _ => FONT_REGULAR, + },*/ + ), + ) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center) + .into() + } + + pub fn push_data(&mut self, time: DateTime, value: f64) { + self.data_points.push_front((time, value)); + } } impl Chart for BalanceChart { - type State = (); - - fn update( - &self, - _state: &mut Self::State, - event: canvas::Event, - bounds: iced::Rectangle, - cursor: Cursor, - ) -> (iced_core::event::Status, Option) { - if let Cursor::Available(point) = cursor { - match event { - canvas::Event::Mouse(_evt) if bounds.contains(point) => { - let p_origin = bounds.position(); - let p = point - p_origin; - let percent = p.x / bounds.width; - - let len = self.data_points.len() - 1; - - let approx_index = len as f32 * percent; - let cursor_index = len.saturating_sub(approx_index.floor() as usize); - let mut caption_index = cursor_index; - - // TODO the caption width 55 here should be dynamic based on the width of the caption text - // USD value is 55px wide - // BTC value is ??px wide - // Grin value is ??px wide - let caption_width = 55.0; - - // caption index is cursor index until the caption reaches the edge of the chart - if p.x / (bounds.width - caption_width) >= 1.0 { - let tail = caption_width / bounds.width; - let approx_tail = len as f32 * tail; - caption_index = approx_tail.floor() as usize; - } - - return ( - iced_core::event::Status::Captured, - Some(Message::Interaction( - crate::gui::Interaction::WalletOperationHomeViewInteraction( - super::home::LocalViewInteraction::MouseIndex(cursor_index, caption_index), - ), - )), - ); - } - _ => { - return ( - iced_core::event::Status::Captured, - Some(Message::Interaction( - crate::gui::Interaction::WalletOperationHomeViewInteraction( - super::home::LocalViewInteraction::MouseExit, - ), - )), - ); - } - } - } - (event::Status::Ignored, None) - } - - fn build_chart(&self, _state: &Self::State, mut chart: ChartBuilder) { - use plotters::{prelude::*, style::Color}; - - const PLOT_LINE_COLOR: RGBColor = RGBColor(0, 175, 255); - - // Acquire time range - let newest_time = self - .data_points - .front() - .unwrap_or(&(chrono::Utc::now(), 0.0)) - .0; - - let mut oldest_time = self - .data_points - .back() - .unwrap_or(&(chrono::Utc::now() - chrono::Duration::days(7), 0.0)) - .0; - - if newest_time == oldest_time { - oldest_time = chrono::Utc::now() - chrono::Duration::days(7); - } - - // get largest amount from data points - let mut max_value = 0.0; - for (_, amount) in self.data_points.iter() { - if *amount > max_value { - max_value = *amount; - } - } - // we add 10% to the max value to make sure the chart is not cut off - max_value = max_value * 1.1; - - let mut chart = chart - .x_label_area_size(6) - .y_label_area_size(0) - .build_cartesian_2d(oldest_time..newest_time, 0.0_f64..max_value) - .expect("failed to build chart"); - - let chart_color = self.theme.palette.bright.primary; - let chart_color = RGBColor( - (chart_color.r * 255.0) as u8, - (chart_color.g * 255.0) as u8, - (chart_color.b * 255.0) as u8, - ); - - let date_color = self.theme.palette.normal.surface; - let date_color = RGBColor( - (date_color.r * 255.0) as u8, - (date_color.g * 255.0) as u8, - (date_color.b * 255.0) as u8, - ); - - let background_color = self.theme.palette.base.background; - let background_color = RGBColor( - (background_color.r * 255.0) as u8, - (background_color.g * 255.0) as u8, - (background_color.b * 255.0) as u8, - ); - - let text_color = self.theme.palette.bright.surface; - let text_color = RGBColor( - (text_color.r * 255.0) as u8, - (text_color.g * 255.0) as u8, - (text_color.b * 255.0) as u8, - ); - - chart - .configure_mesh() - .bold_line_style(background_color) - .light_line_style(background_color) - .axis_style(background_color) - .x_labels(4) - .x_label_style( - ("sans-serif", 15) - .into_font() - .color(&date_color) - .transform(FontTransform::Rotate90), - ) - .x_label_formatter(&|x| format!("{}", x.format("%b %d, %Y"))) - .draw() - .expect("failed to draw chart mesh"); - - chart - .draw_series( - AreaSeries::new( - self.data_points.iter().map(|x| (x.0, x.1 as f64)), - 0.0, - chart_color.mix(0.075), - ) - .border_style(ShapeStyle::from(chart_color).stroke_width(2)), - ) - .expect("failed to draw chart data"); - - if let Some(cursor) = self.cursor_index { - let caption_index = self.caption_index.unwrap(); - let (time1, amount) = self.data_points[cursor]; - let (time2, _) = self.data_points[caption_index]; - //debug!("index: {}, time: {}, amount: {}", index, time, amount); - - // draws a circle at (date, balance) point of the chart - chart - .draw_series(std::iter::once(Circle::new( - (time1, amount), - 5_i32, - chart_color.filled(), - ))) - .expect("Failed to draw hover point"); - - // draw balance above the point - chart - .draw_series(std::iter::once(Text::new( - format!("{}", amount), - (time2, max_value), - ("sans-serif", CHART_CAPTION_HEAD) - .into_font() - .color(&text_color.mix(1.0)), - ))) - .expect("Failed to draw text"); - - // date below balance with a slight faded color - chart - .draw_series(std::iter::once(Text::new( - format!("{}", time1.format("%b %d, %Y")), - (time2, max_value * 0.84), - ("sans-serif", CHART_CAPTION_SUB) - .into_font() - .color(&text_color.mix(0.7)), - ))) - .expect("Failed to draw text"); - } - } + type State = (); + + fn update( + &self, + _state: &mut Self::State, + event: canvas::Event, + bounds: iced::Rectangle, + cursor: Cursor, + ) -> (iced_core::event::Status, Option) { + if let Cursor::Available(point) = cursor { + match event { + canvas::Event::Mouse(_evt) if bounds.contains(point) => { + let p_origin = bounds.position(); + let p = point - p_origin; + let percent = p.x / bounds.width; + + let len = self.data_points.len() - 1; + + let approx_index = len as f32 * percent; + let cursor_index = len.saturating_sub(approx_index.floor() as usize); + let mut caption_index = cursor_index; + + // TODO the caption width 55 here should be dynamic based on the width of the caption text + // USD value is 55px wide + // BTC value is ??px wide + // Grin value is ??px wide + let caption_width = 55.0; + + // caption index is cursor index until the caption reaches the edge of the chart + if p.x / (bounds.width - caption_width) >= 1.0 { + let tail = caption_width / bounds.width; + let approx_tail = len as f32 * tail; + caption_index = approx_tail.floor() as usize; + } + + return ( + iced_core::event::Status::Captured, + Some(Message::Interaction( + crate::gui::Interaction::WalletOperationHomeViewInteraction( + super::home::LocalViewInteraction::MouseIndex( + cursor_index, + caption_index, + ), + ), + )), + ); + } + _ => { + return ( + iced_core::event::Status::Captured, + Some(Message::Interaction( + crate::gui::Interaction::WalletOperationHomeViewInteraction( + super::home::LocalViewInteraction::MouseExit, + ), + )), + ); + } + } + } + (event::Status::Ignored, None) + } + + fn build_chart(&self, _state: &Self::State, mut chart: ChartBuilder) { + use plotters::{prelude::*, style::Color}; + + const PLOT_LINE_COLOR: RGBColor = RGBColor(0, 175, 255); + + // Acquire time range + let newest_time = self + .data_points + .front() + .unwrap_or(&(chrono::Utc::now(), 0.0)) + .0; + + let mut oldest_time = self + .data_points + .back() + .unwrap_or(&(chrono::Utc::now() - chrono::Duration::days(7), 0.0)) + .0; + + if newest_time == oldest_time { + oldest_time = chrono::Utc::now() - chrono::Duration::days(7); + } + + // get largest amount from data points + let mut max_value = 0.0; + for (_, amount) in self.data_points.iter() { + if *amount > max_value { + max_value = *amount; + } + } + // we add 10% to the max value to make sure the chart is not cut off + max_value = max_value * 1.1; + + let mut chart = chart + .x_label_area_size(6) + .y_label_area_size(0) + .build_cartesian_2d(oldest_time..newest_time, 0.0_f64..max_value) + .expect("failed to build chart"); + + let chart_color = self.theme.palette.bright.primary; + let chart_color = RGBColor( + (chart_color.r * 255.0) as u8, + (chart_color.g * 255.0) as u8, + (chart_color.b * 255.0) as u8, + ); + + let date_color = self.theme.palette.normal.surface; + let date_color = RGBColor( + (date_color.r * 255.0) as u8, + (date_color.g * 255.0) as u8, + (date_color.b * 255.0) as u8, + ); + + let background_color = self.theme.palette.base.background; + let background_color = RGBColor( + (background_color.r * 255.0) as u8, + (background_color.g * 255.0) as u8, + (background_color.b * 255.0) as u8, + ); + + let text_color = self.theme.palette.bright.surface; + let text_color = RGBColor( + (text_color.r * 255.0) as u8, + (text_color.g * 255.0) as u8, + (text_color.b * 255.0) as u8, + ); + + chart + .configure_mesh() + .bold_line_style(background_color) + .light_line_style(background_color) + .axis_style(background_color) + .x_labels(4) + .x_label_style( + ("sans-serif", 15) + .into_font() + .color(&date_color) + .transform(FontTransform::Rotate90), + ) + .x_label_formatter(&|x| format!("{}", x.format("%b %d, %Y"))) + .draw() + .expect("failed to draw chart mesh"); + + chart + .draw_series( + AreaSeries::new( + self.data_points.iter().map(|x| (x.0, x.1 as f64)), + 0.0, + chart_color.mix(0.075), + ) + .border_style(ShapeStyle::from(chart_color).stroke_width(2)), + ) + .expect("failed to draw chart data"); + + if let Some(cursor) = self.cursor_index { + let caption_index = self.caption_index.unwrap(); + let (time1, amount) = self.data_points[cursor]; + let (time2, _) = self.data_points[caption_index]; + //debug!("index: {}, time: {}, amount: {}", index, time, amount); + + // draws a circle at (date, balance) point of the chart + chart + .draw_series(std::iter::once(Circle::new( + (time1, amount), + 5_i32, + chart_color.filled(), + ))) + .expect("Failed to draw hover point"); + + // draw balance above the point + chart + .draw_series(std::iter::once(Text::new( + format!("{}", amount), + (time2, max_value), + ("sans-serif", CHART_CAPTION_HEAD) + .into_font() + .color(&text_color.mix(1.0)), + ))) + .expect("Failed to draw text"); + + // date below balance with a slight faded color + chart + .draw_series(std::iter::once(Text::new( + format!("{}", time1.format("%b %d, %Y")), + (time2, max_value * 0.84), + ("sans-serif", CHART_CAPTION_SUB) + .into_font() + .color(&text_color.mix(0.7)), + ))) + .expect("Failed to draw text"); + } + } } diff --git a/src/gui/element/wallet/operation/create_tx.rs b/src/gui/element/wallet/operation/create_tx.rs index d7b3ee0..12d9a13 100644 --- a/src/gui/element/wallet/operation/create_tx.rs +++ b/src/gui/element/wallet/operation/create_tx.rs @@ -2,9 +2,9 @@ use super::tx_list::{self, ExpandType}; use crate::log_error; use async_std::prelude::FutureExt; use grin_gui_core::{ - config::Config, - error::GrinWalletInterfaceError, - wallet::{TxLogEntry, TxLogEntryType}, + config::Config, + error::GrinWalletInterfaceError, + wallet::{TxLogEntry, TxLogEntryType}, }; use grin_gui_widgets::widget::header; use iced_aw::Card; @@ -16,48 +16,48 @@ use std::path::PathBuf; use super::tx_list::{HeaderState, TxList}; use { - super::super::super::{ - BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, - SMALLER_FONT_SIZE, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - anyhow::Context, - grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, - TextInput, - }, - grin_gui_core::wallet::{InitTxArgs, Slate, StatusMessage, WalletInfo, WalletInterface}, - grin_gui_core::{ - node::{amount_from_hr_string, amount_to_hr_string}, - theme::{ButtonStyle, ColorPalette, ContainerStyle}, - }, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - serde::{Deserialize, Serialize}, - std::sync::{Arc, RwLock}, + super::super::super::{ + BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, + SMALLER_FONT_SIZE, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + anyhow::Context, + grin_gui_core::theme::{ + Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, + TextInput, + }, + grin_gui_core::wallet::{InitTxArgs, Slate, StatusMessage, WalletInfo, WalletInterface}, + grin_gui_core::{ + node::{amount_from_hr_string, amount_to_hr_string}, + theme::{ButtonStyle, ColorPalette, ContainerStyle}, + }, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + serde::{Deserialize, Serialize}, + std::sync::{Arc, RwLock}, }; pub struct StateContainer { - pub recipient_address_value: String, - // pub amount_input_state: text_input::State, - pub amount_value: String, - // whether amount has errored - amount_error: bool, - // slatepack address error - slatepack_address_error: bool, + pub recipient_address_value: String, + // pub amount_input_state: text_input::State, + pub amount_value: String, + // whether amount has errored + amount_error: bool, + // slatepack address error + slatepack_address_error: bool, } impl Default for StateContainer { - fn default() -> Self { - Self { - recipient_address_value: Default::default(), - amount_value: Default::default(), - amount_error: false, - slatepack_address_error: false, - } - } + fn default() -> Self { + Self { + recipient_address_value: Default::default(), + amount_value: Default::default(), + amount_error: false, + slatepack_address_error: false, + } + } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -65,332 +65,332 @@ pub enum Action {} #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Back, - RecipientAddress(String), - Amount(String), - CreateTransaction(), - - TxCreatedOk(Slate, String), - TxCreateError(Arc>>), - SlatepackAddressError, + Back, + RecipientAddress(String), + Amount(String), + CreateTransaction(), + + TxCreatedOk(Slate, String), + TxCreateError(Arc>>), + SlatepackAddressError, } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.wallet_state.operation_state.create_tx_state; - - match message { - LocalViewInteraction::Back => { - log::debug!("Interaction::WalletOperationCreateTxViewInteraction(Back)"); - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::Home; - } - LocalViewInteraction::RecipientAddress(s) => { - state.recipient_address_value = s; - } - LocalViewInteraction::Amount(s) => { - state.amount_value = s; - } - LocalViewInteraction::CreateTransaction() => { - grin_gui.error.take(); - state.amount_error = false; - state.slatepack_address_error = false; - - log::debug!("Interaction::WalletOperationCreateTxViewInteraction"); - - let w = grin_gui.wallet_interface.clone(); - - let amount = match amount_from_hr_string(&state.amount_value) { - Ok(0) | Err(_) => { - state.amount_error = true; - return Ok(Command::none()); - } - Ok(a) => a, - }; - - // Todo: Amount parsing + validation, just testing the flow for now - let args = InitTxArgs { - src_acct_name: None, - amount, - minimum_confirmations: 2, - max_outputs: 500, - num_change_outputs: 1, - selection_strategy_is_use_all: false, - late_lock: Some(false), - ..Default::default() - }; - let fut = - move || WalletInterface::create_tx(w, args, state.recipient_address_value.clone()); - - return Ok(Command::perform(fut(), |r| match r { - Ok((enc_slate, unenc_slate)) => { - Message::Interaction(Interaction::WalletOperationCreateTxViewInteraction( - LocalViewInteraction::TxCreatedOk(enc_slate, unenc_slate), - )) - } - Err(e) => match e { - GrinWalletInterfaceError::InvalidSlatepackAddress => { - Message::Interaction(Interaction::WalletOperationCreateTxViewInteraction( - LocalViewInteraction::SlatepackAddressError, - )) - } - _ => Message::Interaction(Interaction::WalletOperationCreateTxViewInteraction( - LocalViewInteraction::TxCreateError(Arc::new(RwLock::new(Some( - anyhow::Error::from(e), - )))), - )), - }, - })); - } - LocalViewInteraction::TxCreatedOk(unencrypted_slate, encrypted_slate) => { - log::debug!("{:?}", encrypted_slate); - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .encrypted_slate = Some(encrypted_slate.to_string()); - - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .title_label = localized_string("tx-create-success-title"); - - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .desc = localized_string("tx-create-success-desc"); - - // create a directory to which files will be output, if it doesn't exist - if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { - let out_file_name = format!("{}/{}.slatepack", dir, unencrypted_slate.id); - let mut output = File::create(out_file_name.clone())?; - output.write_all(&encrypted_slate.as_bytes())?; - output.sync_all()?; - } - - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::ShowSlatepack; - } - LocalViewInteraction::TxCreateError(err) => { - grin_gui.error = err.write().unwrap().take(); - if let Some(e) = grin_gui.error.as_ref() { - log_error(e); - } - } - LocalViewInteraction::SlatepackAddressError => state.slatepack_address_error = true, - } - - Ok(Command::none()) + let state = &mut grin_gui.wallet_state.operation_state.create_tx_state; + + match message { + LocalViewInteraction::Back => { + log::debug!("Interaction::WalletOperationCreateTxViewInteraction(Back)"); + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::Home; + } + LocalViewInteraction::RecipientAddress(s) => { + state.recipient_address_value = s; + } + LocalViewInteraction::Amount(s) => { + state.amount_value = s; + } + LocalViewInteraction::CreateTransaction() => { + grin_gui.error.take(); + state.amount_error = false; + state.slatepack_address_error = false; + + log::debug!("Interaction::WalletOperationCreateTxViewInteraction"); + + let w = grin_gui.wallet_interface.clone(); + + let amount = match amount_from_hr_string(&state.amount_value) { + Ok(0) | Err(_) => { + state.amount_error = true; + return Ok(Command::none()); + } + Ok(a) => a, + }; + + // Todo: Amount parsing + validation, just testing the flow for now + let args = InitTxArgs { + src_acct_name: None, + amount, + minimum_confirmations: 2, + max_outputs: 500, + num_change_outputs: 1, + selection_strategy_is_use_all: false, + late_lock: Some(false), + ..Default::default() + }; + let fut = + move || WalletInterface::create_tx(w, args, state.recipient_address_value.clone()); + + return Ok(Command::perform(fut(), |r| match r { + Ok((enc_slate, unenc_slate)) => { + Message::Interaction(Interaction::WalletOperationCreateTxViewInteraction( + LocalViewInteraction::TxCreatedOk(enc_slate, unenc_slate), + )) + } + Err(e) => match e { + GrinWalletInterfaceError::InvalidSlatepackAddress => { + Message::Interaction(Interaction::WalletOperationCreateTxViewInteraction( + LocalViewInteraction::SlatepackAddressError, + )) + } + _ => Message::Interaction(Interaction::WalletOperationCreateTxViewInteraction( + LocalViewInteraction::TxCreateError(Arc::new(RwLock::new(Some( + anyhow::Error::from(e), + )))), + )), + }, + })); + } + LocalViewInteraction::TxCreatedOk(unencrypted_slate, encrypted_slate) => { + log::debug!("{:?}", encrypted_slate); + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .encrypted_slate = Some(encrypted_slate.to_string()); + + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .title_label = localized_string("tx-create-success-title"); + + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .desc = localized_string("tx-create-success-desc"); + + // create a directory to which files will be output, if it doesn't exist + if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { + let out_file_name = format!("{}/{}.slatepack", dir, unencrypted_slate.id); + let mut output = File::create(out_file_name.clone())?; + output.write_all(&encrypted_slate.as_bytes())?; + output.sync_all()?; + } + + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::ShowSlatepack; + } + LocalViewInteraction::TxCreateError(err) => { + grin_gui.error = err.write().unwrap().take(); + if let Some(e) = grin_gui.error.as_ref() { + log_error(e); + } + } + LocalViewInteraction::SlatepackAddressError => state.slatepack_address_error = true, + } + + Ok(Command::none()) } pub fn data_container<'a>(config: &'a Config, state: &'a StateContainer) -> Container<'a, Message> { - // Title row - let title = Text::new(localized_string("create-tx")) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - - let title_container = Container::new(title) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 2, // top - 0, // right - 2, // bottom - 5, // left - ])); - - // push more items on to header here: e.g. other buttons, things that belong on the header - let header_row = Row::new().push(title_container); - - let header_container = Container::new(header_row).padding(iced::Padding::from([ - 0, // top - 0, // right - DEFAULT_PADDING as u16, // bottom - 0, // left - ])); - - let recipient_address = Text::new(localized_string("recipient-address")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let recipient_address_container = Container::new(recipient_address) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let recipient_address_input = TextInput::new("", &state.recipient_address_value) - .on_input(|s| { - Interaction::WalletOperationCreateTxViewInteraction( - LocalViewInteraction::RecipientAddress(s), - ) - }) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(400.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); - - let recipient_address_input: Element = recipient_address_input.into(); - - let address_error = Text::new(localized_string("create-tx-address-error")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left) - .style(grin_gui_core::theme::text::TextStyle::Warning); - - let address_error_container = - Container::new(address_error).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let amount = Text::new(localized_string("create-tx-amount")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let amount_container = - Container::new(amount).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let amount_input = TextInput::new( - // &mut state.amount_input_state, - "", - &state.amount_value, - ) - .on_input(|s| { - Interaction::WalletOperationCreateTxViewInteraction(LocalViewInteraction::Amount(s)) - }) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(100.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); - - let amount_input: Element = amount_input.into(); - - let amount_error = Text::new(localized_string("create-tx-amount-error")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left) - .style(grin_gui_core::theme::text::TextStyle::Warning); - - let amount_error_container = - Container::new(amount_error).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let address_instruction_container = - Text::new(localized_string("recipient-address-instruction")) - .size(SMALLER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let address_instruction_container = - Container::new(address_instruction_container).style(ContainerStyle::NormalBackground); - - let button_height = Length::Fixed(BUTTON_HEIGHT); - let button_width = Length::Fixed(BUTTON_WIDTH); - - let submit_button_label_container = - Container::new(Text::new(localized_string("tx-create-submit")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let mut submit_button = Button::new(submit_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary); - let submit_button = - submit_button.on_press(Interaction::WalletOperationCreateTxViewInteraction( - LocalViewInteraction::CreateTransaction(), - )); - let submit_button: Element = submit_button.into(); - - let cancel_button_label_container = - Container::new(Text::new(localized_string("cancel")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let cancel_button: Element = Button::new(cancel_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletOperationCreateTxViewInteraction( - LocalViewInteraction::Back, - )) - .into(); - - let submit_container = Container::new(submit_button.map(Message::Interaction)).padding(1); - let submit_container = Container::new(submit_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); - let cancel_container = Container::new(cancel_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let unit_spacing = 15.0; - let button_row = Row::new() - .push(submit_container) - .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) - .push(cancel_container); - - let mut column = Column::new() - .push(recipient_address_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(address_instruction_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(recipient_address_input.map(Message::Interaction)) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); - - if state.slatepack_address_error { - column = column - .push(address_error_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); - } - - column = column - .push(amount_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(amount_input.map(Message::Interaction)) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); - - if state.amount_error { - column = column - .push(amount_error_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); - } - - column = column - .push(button_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 10.0), - )); - - let form_container = Container::new(column) - .width(Length::Fill) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - // form container should be scrollable in tiny windows - let scrollable = Scrollable::new(form_container) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); - - let content = Container::new(scrollable) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let wrapper_column = Column::new() - .height(Length::Fill) - .push(header_container) - .push(content); - - // Returns the final container. - Container::new(wrapper_column).padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + // Title row + let title = Text::new(localized_string("create-tx")) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + + let title_container = Container::new(title) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 2, // top + 0, // right + 2, // bottom + 5, // left + ])); + + // push more items on to header here: e.g. other buttons, things that belong on the header + let header_row = Row::new().push(title_container); + + let header_container = Container::new(header_row).padding(iced::Padding::from([ + 0, // top + 0, // right + DEFAULT_PADDING as u16, // bottom + 0, // left + ])); + + let recipient_address = Text::new(localized_string("recipient-address")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let recipient_address_container = Container::new(recipient_address) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let recipient_address_input = TextInput::new("", &state.recipient_address_value) + .on_input(|s| { + Interaction::WalletOperationCreateTxViewInteraction( + LocalViewInteraction::RecipientAddress(s), + ) + }) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(400.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); + + let recipient_address_input: Element = recipient_address_input.into(); + + let address_error = Text::new(localized_string("create-tx-address-error")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left) + .style(grin_gui_core::theme::text::TextStyle::Warning); + + let address_error_container = + Container::new(address_error).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let amount = Text::new(localized_string("create-tx-amount")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let amount_container = + Container::new(amount).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let amount_input = TextInput::new( + // &mut state.amount_input_state, + "", + &state.amount_value, + ) + .on_input(|s| { + Interaction::WalletOperationCreateTxViewInteraction(LocalViewInteraction::Amount(s)) + }) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(100.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); + + let amount_input: Element = amount_input.into(); + + let amount_error = Text::new(localized_string("create-tx-amount-error")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left) + .style(grin_gui_core::theme::text::TextStyle::Warning); + + let amount_error_container = + Container::new(amount_error).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let address_instruction_container = + Text::new(localized_string("recipient-address-instruction")) + .size(SMALLER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let address_instruction_container = + Container::new(address_instruction_container).style(ContainerStyle::NormalBackground); + + let button_height = Length::Fixed(BUTTON_HEIGHT); + let button_width = Length::Fixed(BUTTON_WIDTH); + + let submit_button_label_container = + Container::new(Text::new(localized_string("tx-create-submit")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let mut submit_button = Button::new(submit_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary); + let submit_button = + submit_button.on_press(Interaction::WalletOperationCreateTxViewInteraction( + LocalViewInteraction::CreateTransaction(), + )); + let submit_button: Element = submit_button.into(); + + let cancel_button_label_container = + Container::new(Text::new(localized_string("cancel")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let cancel_button: Element = Button::new(cancel_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletOperationCreateTxViewInteraction( + LocalViewInteraction::Back, + )) + .into(); + + let submit_container = Container::new(submit_button.map(Message::Interaction)).padding(1); + let submit_container = Container::new(submit_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); + let cancel_container = Container::new(cancel_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let unit_spacing = 15.0; + let button_row = Row::new() + .push(submit_container) + .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) + .push(cancel_container); + + let mut column = Column::new() + .push(recipient_address_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(address_instruction_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(recipient_address_input.map(Message::Interaction)) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); + + if state.slatepack_address_error { + column = column + .push(address_error_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); + } + + column = column + .push(amount_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(amount_input.map(Message::Interaction)) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); + + if state.amount_error { + column = column + .push(amount_error_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); + } + + column = column + .push(button_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 10.0), + )); + + let form_container = Container::new(column) + .width(Length::Fill) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + // form container should be scrollable in tiny windows + let scrollable = Scrollable::new(form_container) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); + + let content = Container::new(scrollable) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let wrapper_column = Column::new() + .height(Length::Fill) + .push(header_container) + .push(content); + + // Returns the final container. + Container::new(wrapper_column).padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/wallet/operation/create_tx_contracts.rs b/src/gui/element/wallet/operation/create_tx_contracts.rs index 32660c8..6f33d4c 100644 --- a/src/gui/element/wallet/operation/create_tx_contracts.rs +++ b/src/gui/element/wallet/operation/create_tx_contracts.rs @@ -2,9 +2,12 @@ use super::tx_list::{self, ExpandType}; use crate::log_error; use async_std::prelude::FutureExt; use grin_gui_core::{ - config::Config, - error::GrinWalletInterfaceError, - wallet::{ContractNewArgsAPI, ContractSetupArgsAPI, Slatepack, TxLogEntry, TxLogEntryType, SlatepackAddress}, + config::Config, + error::GrinWalletInterfaceError, + wallet::{ + ContractNewArgsAPI, ContractSetupArgsAPI, Slatepack, SlatepackAddress, TxLogEntry, + TxLogEntryType, + }, }; use grin_gui_widgets::widget::header; use iced_aw::Card; @@ -16,60 +19,60 @@ use std::path::PathBuf; use super::tx_list::{HeaderState, TxList}; use { - super::super::super::{ - BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, - SMALLER_FONT_SIZE, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - anyhow::Context, - grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Radio, Row, Scrollable, TableRow, - Text, TextInput, - }, - grin_gui_core::wallet::{InitTxArgs, Slate, StatusMessage, WalletInfo, WalletInterface}, - grin_gui_core::{ - node::{amount_from_hr_string, amount_to_hr_string}, - theme::{ButtonStyle, ColorPalette, ContainerStyle}, - }, - iced::widget::{button, pick_list, radio, scrollable, text_input, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - serde::{Deserialize, Serialize}, - std::sync::{Arc, RwLock}, + super::super::super::{ + BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, + SMALLER_FONT_SIZE, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + anyhow::Context, + grin_gui_core::theme::{ + Button, Column, Container, Element, Header, PickList, Radio, Row, Scrollable, TableRow, + Text, TextInput, + }, + grin_gui_core::wallet::{InitTxArgs, Slate, StatusMessage, WalletInfo, WalletInterface}, + grin_gui_core::{ + node::{amount_from_hr_string, amount_to_hr_string}, + theme::{ButtonStyle, ColorPalette, ContainerStyle}, + }, + iced::widget::{button, pick_list, radio, scrollable, text_input, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + serde::{Deserialize, Serialize}, + std::sync::{Arc, RwLock}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ContributionChoice { - Credit, - Debit, + Credit, + Debit, } pub struct StateContainer { - pub recipient_address_value: String, - // pub amount_input_state: text_input::State, - pub amount_value: String, - // Choice of contribution to transaction - pub contribution_choice: ContributionChoice, - // Self-send checkbox - pub is_self_send: bool, - // whether amount has errored - amount_error: bool, - // slatepack address error - slatepack_address_error: bool, + pub recipient_address_value: String, + // pub amount_input_state: text_input::State, + pub amount_value: String, + // Choice of contribution to transaction + pub contribution_choice: ContributionChoice, + // Self-send checkbox + pub is_self_send: bool, + // whether amount has errored + amount_error: bool, + // slatepack address error + slatepack_address_error: bool, } impl Default for StateContainer { - fn default() -> Self { - Self { - recipient_address_value: Default::default(), - contribution_choice: ContributionChoice::Debit, - amount_value: Default::default(), - is_self_send: false, - amount_error: false, - slatepack_address_error: false, - } - } + fn default() -> Self { + Self { + recipient_address_value: Default::default(), + contribution_choice: ContributionChoice::Debit, + amount_value: Default::default(), + is_self_send: false, + amount_error: false, + slatepack_address_error: false, + } + } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -77,526 +80,525 @@ pub enum Action {} #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Back, - RecipientAddress(String), - ContributionChoice(ContributionChoice), - Amount(String), - CreateTransaction(), - SelfSendSelected(bool), - - TxCreatedOk(Slate, String), - SelfSendCreatedOk(Slate, TxLogEntry), - TxCreateError(Arc>>), - SlatepackAddressError, + Back, + RecipientAddress(String), + ContributionChoice(ContributionChoice), + Amount(String), + CreateTransaction(), + SelfSendSelected(bool), + + TxCreatedOk(Slate, String), + SelfSendCreatedOk(Slate, TxLogEntry), + TxCreateError(Arc>>), + SlatepackAddressError, } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui - .wallet_state - .operation_state - .create_tx_contracts_state; - - match message { - LocalViewInteraction::Back => { - log::debug!("Interaction::WalletOperationCreateTxViewInteraction(Back)"); - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::Home; - } - LocalViewInteraction::RecipientAddress(s) => { - state.recipient_address_value = s; - if Some(state.recipient_address_value.clone()) - == grin_gui - .wallet_state - .operation_state - .home_state - .address_value - { - state.is_self_send = true; - } else { - state.is_self_send = false; - } - } - LocalViewInteraction::ContributionChoice(c) => { - log::debug!("Chosen: {:?}", c); - state.contribution_choice = c; - } - LocalViewInteraction::Amount(s) => { - state.amount_value = s; - } - LocalViewInteraction::SelfSendSelected(v) => { - state.is_self_send = v; - if let Some(ref a) = grin_gui - .wallet_state - .operation_state - .home_state - .address_value - { - state.recipient_address_value = a.clone(); - } - } - LocalViewInteraction::CreateTransaction() => { - grin_gui.error.take(); - state.amount_error = false; - state.slatepack_address_error = false; - - log::debug!("Interaction::WalletOperationCreateTxViewInteraction"); - - let w = grin_gui.wallet_interface.clone(); - - let amount = match amount_from_hr_string(&state.amount_value) { - Ok(0) | Err(_) => { - if !state.is_self_send { - state.amount_error = true; - return Ok(Command::none()); - } else { - 0i64 - } - } - Ok(a) => { - if a > std::i64::MAX as u64 { - state.amount_error = true; - return Ok(Command::none()); - } - if state.is_self_send { - 0i64 - } else { - a as i64 - } - } - }; - - let mut args = ContractNewArgsAPI { - setup_args: ContractSetupArgsAPI { - net_change: if state.is_self_send { - // None makes the contracts API cry for now - Some(0) - } else { - match state.contribution_choice { - ContributionChoice::Credit => Some(amount), - ContributionChoice::Debit => Some(-amount), - } - }, - num_participants: if state.is_self_send { - 1 - } else { - 2 - }, - ..Default::default() - }, - ..Default::default() - }; - - if state.contribution_choice == ContributionChoice::Credit { - debug!("CREDITOR ADDRESS: {}", state.recipient_address_value); - let res:Result = state.recipient_address_value.as_str().try_into(); - if let Ok(creditor_address) = res { - args.setup_args.proof_args.sender_address = Some(creditor_address.pub_key); - debug!("CREDITOR ADDRESS: {:?}", args.setup_args.proof_args.sender_address); - } - } - - if state.is_self_send || state.contribution_choice == ContributionChoice::Debit { - if let Some(a) = &grin_gui.wallet_state.operation_state.home_state.address { - args.setup_args.proof_args.sender_address = Some(a.pub_key); - } - }; - - if state.is_self_send { - let fut = move || WalletInterface::contract_self_send(w, args); - - return Ok(Command::perform(fut(), |r| match r { - Ok((unenc_slate, tx_log_entry)) => Message::Interaction( - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::SelfSendCreatedOk(unenc_slate, tx_log_entry), - ), - ), - Err(e) => match e { - GrinWalletInterfaceError::InvalidSlatepackAddress => Message::Interaction( - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::SlatepackAddressError, - ), - ), - _ => Message::Interaction( - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::TxCreateError(Arc::new(RwLock::new(Some( - anyhow::Error::from(e), - )))), - ), - ), - }, - })); - } else { - let fut = move || { - WalletInterface::contract_new(w, args, state.recipient_address_value.clone()) - }; - - return Ok(Command::perform(fut(), |r| match r { - Ok((enc_slate, unenc_slate)) => Message::Interaction( - - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::TxCreatedOk(enc_slate, unenc_slate), - ), - ), - Err(e) => match e { - GrinWalletInterfaceError::InvalidSlatepackAddress => Message::Interaction( - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::SlatepackAddressError, - ), - ), - _ => Message::Interaction( - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::TxCreateError(Arc::new(RwLock::new(Some( - anyhow::Error::from(e), - )))), - ), - ), - }, - })); - } - } - LocalViewInteraction::TxCreatedOk(unencrypted_slate, encrypted_slate) => { - log::debug!("{}", unencrypted_slate); - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .encrypted_slate = Some(encrypted_slate.to_string()); - - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .title_label = localized_string("tx-create-success-title"); - - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .desc = localized_string("tx-create-success-desc"); - - // create a directory to which files will be output, if it doesn't exist - if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { - let out_file_name = format!("{}/{}.slatepack", dir, unencrypted_slate.id); - let mut output = File::create(out_file_name.clone())?; - output.write_all(&encrypted_slate.as_bytes())?; - output.sync_all()?; - } - - grin_gui - .wallet_state - .operation_state - .apply_tx_state - .confirm_state - .is_self_send = false; - - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::ShowSlatepack; - } - LocalViewInteraction::SelfSendCreatedOk(unencrypted_slate, tx_log_entry) => { - grin_gui - .wallet_state - .operation_state - .apply_tx_state - .set_slate_direct(unencrypted_slate, tx_log_entry); - - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::ApplyTx; - } - LocalViewInteraction::TxCreateError(err) => { - grin_gui.error = err.write().unwrap().take(); - if let Some(e) = grin_gui.error.as_ref() { - log_error(e); - } - } - LocalViewInteraction::SlatepackAddressError => state.slatepack_address_error = true, - } - - Ok(Command::none()) + let state = &mut grin_gui + .wallet_state + .operation_state + .create_tx_contracts_state; + + match message { + LocalViewInteraction::Back => { + log::debug!("Interaction::WalletOperationCreateTxViewInteraction(Back)"); + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::Home; + } + LocalViewInteraction::RecipientAddress(s) => { + state.recipient_address_value = s; + if Some(state.recipient_address_value.clone()) + == grin_gui + .wallet_state + .operation_state + .home_state + .address_value + { + state.is_self_send = true; + } else { + state.is_self_send = false; + } + } + LocalViewInteraction::ContributionChoice(c) => { + log::debug!("Chosen: {:?}", c); + state.contribution_choice = c; + } + LocalViewInteraction::Amount(s) => { + state.amount_value = s; + } + LocalViewInteraction::SelfSendSelected(v) => { + state.is_self_send = v; + if let Some(ref a) = grin_gui + .wallet_state + .operation_state + .home_state + .address_value + { + state.recipient_address_value = a.clone(); + } + } + LocalViewInteraction::CreateTransaction() => { + grin_gui.error.take(); + state.amount_error = false; + state.slatepack_address_error = false; + + log::debug!("Interaction::WalletOperationCreateTxViewInteraction"); + + let w = grin_gui.wallet_interface.clone(); + + let amount = match amount_from_hr_string(&state.amount_value) { + Ok(0) | Err(_) => { + if !state.is_self_send { + state.amount_error = true; + return Ok(Command::none()); + } else { + 0i64 + } + } + Ok(a) => { + if a > std::i64::MAX as u64 { + state.amount_error = true; + return Ok(Command::none()); + } + if state.is_self_send { + 0i64 + } else { + a as i64 + } + } + }; + + let mut args = ContractNewArgsAPI { + setup_args: ContractSetupArgsAPI { + net_change: if state.is_self_send { + // None makes the contracts API cry for now + Some(0) + } else { + match state.contribution_choice { + ContributionChoice::Credit => Some(amount), + ContributionChoice::Debit => Some(-amount), + } + }, + num_participants: if state.is_self_send { 1 } else { 2 }, + ..Default::default() + }, + ..Default::default() + }; + + if state.contribution_choice == ContributionChoice::Credit { + debug!("CREDITOR ADDRESS: {}", state.recipient_address_value); + let res: Result = + state.recipient_address_value.as_str().try_into(); + if let Ok(creditor_address) = res { + args.setup_args.proof_args.sender_address = Some(creditor_address.pub_key); + debug!( + "CREDITOR ADDRESS: {:?}", + args.setup_args.proof_args.sender_address + ); + } + } + + if state.is_self_send || state.contribution_choice == ContributionChoice::Debit { + if let Some(a) = &grin_gui.wallet_state.operation_state.home_state.address { + args.setup_args.proof_args.sender_address = Some(a.pub_key); + } + }; + + if state.is_self_send { + let fut = move || WalletInterface::contract_self_send(w, args); + + return Ok(Command::perform(fut(), |r| match r { + Ok((unenc_slate, tx_log_entry)) => Message::Interaction( + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::SelfSendCreatedOk(unenc_slate, tx_log_entry), + ), + ), + Err(e) => match e { + GrinWalletInterfaceError::InvalidSlatepackAddress => Message::Interaction( + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::SlatepackAddressError, + ), + ), + _ => Message::Interaction( + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::TxCreateError(Arc::new(RwLock::new(Some( + anyhow::Error::from(e), + )))), + ), + ), + }, + })); + } else { + let fut = move || { + WalletInterface::contract_new(w, args, state.recipient_address_value.clone()) + }; + + return Ok(Command::perform(fut(), |r| match r { + Ok((enc_slate, unenc_slate)) => Message::Interaction( + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::TxCreatedOk(enc_slate, unenc_slate), + ), + ), + Err(e) => match e { + GrinWalletInterfaceError::InvalidSlatepackAddress => Message::Interaction( + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::SlatepackAddressError, + ), + ), + _ => Message::Interaction( + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::TxCreateError(Arc::new(RwLock::new(Some( + anyhow::Error::from(e), + )))), + ), + ), + }, + })); + } + } + LocalViewInteraction::TxCreatedOk(unencrypted_slate, encrypted_slate) => { + log::debug!("{}", unencrypted_slate); + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .encrypted_slate = Some(encrypted_slate.to_string()); + + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .title_label = localized_string("tx-create-success-title"); + + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .desc = localized_string("tx-create-success-desc"); + + // create a directory to which files will be output, if it doesn't exist + if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { + let out_file_name = format!("{}/{}.slatepack", dir, unencrypted_slate.id); + let mut output = File::create(out_file_name.clone())?; + output.write_all(&encrypted_slate.as_bytes())?; + output.sync_all()?; + } + + grin_gui + .wallet_state + .operation_state + .apply_tx_state + .confirm_state + .is_self_send = false; + + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::ShowSlatepack; + } + LocalViewInteraction::SelfSendCreatedOk(unencrypted_slate, tx_log_entry) => { + grin_gui + .wallet_state + .operation_state + .apply_tx_state + .set_slate_direct(unencrypted_slate, tx_log_entry); + + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::ApplyTx; + } + LocalViewInteraction::TxCreateError(err) => { + grin_gui.error = err.write().unwrap().take(); + if let Some(e) = grin_gui.error.as_ref() { + log_error(e); + } + } + LocalViewInteraction::SlatepackAddressError => state.slatepack_address_error = true, + } + + Ok(Command::none()) } pub fn data_container<'a>(config: &'a Config, state: &'a StateContainer) -> Container<'a, Message> { - let unit_spacing = 15.0; - - // Title row - let title = Text::new(localized_string("wallet-create-contract")) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - - let title_container = Container::new(title) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 2, // top - 0, // right - 2, // bottom - 5, // left - ])); - - // push more items on to header here: e.g. other buttons, things that belong on the header - let header_row = Row::new().push(title_container); - - let header_container = Container::new(header_row).padding(iced::Padding::from([ - 0, // top - 0, // right - DEFAULT_PADDING as u16, // bottom - 0, // left - ])); - - let recipient_address = Text::new(localized_string("wallet-create-contract-other-address")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let recipient_address_container = Container::new(recipient_address) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let recipient_address_input = TextInput::new("", &state.recipient_address_value) - .on_input(|s| { - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::RecipientAddress(s), - ) - }) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(400.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); - - let recipient_address_input: Element = recipient_address_input.into(); - - /*let self_send_check = Text::new(localized_string("wallet-self-send")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let self_send_container = Container::new(self_send_check) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground);*/ - - let checkbox_column = { - let checkbox = Checkbox::new( - localized_string("wallet-self-send"), - state.is_self_send, - |v| { - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::SelfSendSelected(v), - ) - }, - ) - .style(grin_gui_core::theme::CheckboxStyle::Normal) - .text_size(DEFAULT_FONT_SIZE) - .spacing(5); - - let checkbox: Element = checkbox.into(); - - let checkbox_container = Container::new(checkbox.map(Message::Interaction)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) - .align_y(alignment::Vertical::Bottom); - Column::new().push(checkbox_container) - }; - - let address_row = Row::new() - //.push(recipient_address_container) - .push(recipient_address_input.map(Message::Interaction)) - .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) - .push(checkbox_column) - .align_items(Alignment::End); - - let a: Element = Radio::new( - localized_string("tx-contract-debit"), - ContributionChoice::Debit, - Some(state.contribution_choice), - |c| { - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::ContributionChoice(c), - ) - }, - ) - .style(grin_gui_core::theme::radio::RadioStyle::Primary) - .into(); - - let b: Element = Radio::new( - localized_string("tx-contract-credit"), - ContributionChoice::Credit, - Some(state.contribution_choice), - |c| { - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::ContributionChoice(c), - ) - }, - ) - .style(grin_gui_core::theme::radio::RadioStyle::Primary) - .into(); - - let radio_column = Column::new() - .push(a.map(Message::Interaction)) - .push(b.map(Message::Interaction)); - - let address_error = Text::new(localized_string("create-tx-address-error")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left) - .style(grin_gui_core::theme::text::TextStyle::Warning); - - let address_error_container = - Container::new(address_error).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let amount = Text::new(localized_string("create-tx-amount")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let amount_container = - Container::new(amount).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let amount_input = TextInput::new( - // &mut state.amount_input_state, - "", - &state.amount_value, - ) - .on_input(|s| { - Interaction::WalletOperationCreateTxContractsViewInteraction(LocalViewInteraction::Amount( - s, - )) - }) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(100.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); - - let amount_input: Element = amount_input.into(); - - let amount_error = Text::new(localized_string("create-tx-amount-error")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left) - .style(grin_gui_core::theme::text::TextStyle::Warning); - - let amount_error_container = - Container::new(amount_error).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let address_instruction_container = - Text::new(localized_string("recipient-address-instruction-contract")) - .size(SMALLER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let address_instruction_container = - Container::new(address_instruction_container).style(ContainerStyle::NormalBackground); - - let button_height = Length::Fixed(BUTTON_HEIGHT); - let button_width = Length::Fixed(BUTTON_WIDTH); - - let submit_button_label_container = - Container::new(Text::new(localized_string("tx-create-submit")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let mut submit_button = Button::new(submit_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary); - let submit_button = submit_button.on_press( - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::CreateTransaction(), - ), - ); - let submit_button: Element = submit_button.into(); - - let cancel_button_label_container = - Container::new(Text::new(localized_string("cancel")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let cancel_button: Element = Button::new(cancel_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press( - Interaction::WalletOperationCreateTxContractsViewInteraction( - LocalViewInteraction::Back, - ), - ) - .into(); - - let submit_container = Container::new(submit_button.map(Message::Interaction)).padding(1); - let submit_container = Container::new(submit_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); - let cancel_container = Container::new(cancel_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let button_row = Row::new() - .push(submit_container) - .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) - .push(cancel_container); - - let mut column = Column::new() - .push(recipient_address_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(address_instruction_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(address_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); - - if state.slatepack_address_error { - column = column - .push(address_error_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); - } - - if !state.is_self_send { - column = column - .push(radio_column) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(amount_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(amount_input.map(Message::Interaction)) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); - } - - if state.amount_error { - column = column - .push(amount_error_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); - } - - column = column - .push(button_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 10.0), - )); - - let form_container = Container::new(column) - .width(Length::Fill) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - // form container should be scrollable in tiny windows - let scrollable = Scrollable::new(form_container) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); - - let content = Container::new(scrollable) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let wrapper_column = Column::new() - .height(Length::Fill) - .push(header_container) - .push(content); - - // Returns the final container. - Container::new(wrapper_column).padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + let unit_spacing = 15.0; + + // Title row + let title = Text::new(localized_string("wallet-create-contract")) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + + let title_container = Container::new(title) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 2, // top + 0, // right + 2, // bottom + 5, // left + ])); + + // push more items on to header here: e.g. other buttons, things that belong on the header + let header_row = Row::new().push(title_container); + + let header_container = Container::new(header_row).padding(iced::Padding::from([ + 0, // top + 0, // right + DEFAULT_PADDING as u16, // bottom + 0, // left + ])); + + let recipient_address = Text::new(localized_string("wallet-create-contract-other-address")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let recipient_address_container = Container::new(recipient_address) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let recipient_address_input = TextInput::new("", &state.recipient_address_value) + .on_input(|s| { + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::RecipientAddress(s), + ) + }) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(400.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); + + let recipient_address_input: Element = recipient_address_input.into(); + + /*let self_send_check = Text::new(localized_string("wallet-self-send")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let self_send_container = Container::new(self_send_check) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground);*/ + + let checkbox_column = { + let checkbox = Checkbox::new( + localized_string("wallet-self-send"), + state.is_self_send, + |v| { + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::SelfSendSelected(v), + ) + }, + ) + .style(grin_gui_core::theme::CheckboxStyle::Normal) + .text_size(DEFAULT_FONT_SIZE) + .spacing(5); + + let checkbox: Element = checkbox.into(); + + let checkbox_container = Container::new(checkbox.map(Message::Interaction)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + .align_y(alignment::Vertical::Bottom); + Column::new().push(checkbox_container) + }; + + let address_row = Row::new() + //.push(recipient_address_container) + .push(recipient_address_input.map(Message::Interaction)) + .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) + .push(checkbox_column) + .align_items(Alignment::End); + + let a: Element = Radio::new( + localized_string("tx-contract-debit"), + ContributionChoice::Debit, + Some(state.contribution_choice), + |c| { + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::ContributionChoice(c), + ) + }, + ) + .style(grin_gui_core::theme::radio::RadioStyle::Primary) + .into(); + + let b: Element = Radio::new( + localized_string("tx-contract-credit"), + ContributionChoice::Credit, + Some(state.contribution_choice), + |c| { + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::ContributionChoice(c), + ) + }, + ) + .style(grin_gui_core::theme::radio::RadioStyle::Primary) + .into(); + + let radio_column = Column::new() + .push(a.map(Message::Interaction)) + .push(b.map(Message::Interaction)); + + let address_error = Text::new(localized_string("create-tx-address-error")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left) + .style(grin_gui_core::theme::text::TextStyle::Warning); + + let address_error_container = + Container::new(address_error).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let amount = Text::new(localized_string("create-tx-amount")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let amount_container = + Container::new(amount).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let amount_input = TextInput::new( + // &mut state.amount_input_state, + "", + &state.amount_value, + ) + .on_input(|s| { + Interaction::WalletOperationCreateTxContractsViewInteraction(LocalViewInteraction::Amount( + s, + )) + }) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(100.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); + + let amount_input: Element = amount_input.into(); + + let amount_error = Text::new(localized_string("create-tx-amount-error")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left) + .style(grin_gui_core::theme::text::TextStyle::Warning); + + let amount_error_container = + Container::new(amount_error).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let address_instruction_container = + Text::new(localized_string("recipient-address-instruction-contract")) + .size(SMALLER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let address_instruction_container = + Container::new(address_instruction_container).style(ContainerStyle::NormalBackground); + + let button_height = Length::Fixed(BUTTON_HEIGHT); + let button_width = Length::Fixed(BUTTON_WIDTH); + + let submit_button_label_container = + Container::new(Text::new(localized_string("tx-create-submit")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let mut submit_button = Button::new(submit_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary); + let submit_button = submit_button.on_press( + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::CreateTransaction(), + ), + ); + let submit_button: Element = submit_button.into(); + + let cancel_button_label_container = + Container::new(Text::new(localized_string("cancel")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let cancel_button: Element = Button::new(cancel_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press( + Interaction::WalletOperationCreateTxContractsViewInteraction( + LocalViewInteraction::Back, + ), + ) + .into(); + + let submit_container = Container::new(submit_button.map(Message::Interaction)).padding(1); + let submit_container = Container::new(submit_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); + let cancel_container = Container::new(cancel_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let button_row = Row::new() + .push(submit_container) + .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) + .push(cancel_container); + + let mut column = Column::new() + .push(recipient_address_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(address_instruction_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(address_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); + + if state.slatepack_address_error { + column = column + .push(address_error_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); + } + + if !state.is_self_send { + column = column + .push(radio_column) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(amount_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(amount_input.map(Message::Interaction)) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); + } + + if state.amount_error { + column = column + .push(amount_error_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); + } + + column = column + .push(button_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 10.0), + )); + + let form_container = Container::new(column) + .width(Length::Fill) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + // form container should be scrollable in tiny windows + let scrollable = Scrollable::new(form_container) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); + + let content = Container::new(scrollable) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let wrapper_column = Column::new() + .height(Length::Fill) + .push(header_container) + .push(content); + + // Returns the final container. + Container::new(wrapper_column).padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/wallet/operation/home.rs b/src/gui/element/wallet/operation/home.rs index ddd18c6..2bbb959 100644 --- a/src/gui/element/wallet/operation/home.rs +++ b/src/gui/element/wallet/operation/home.rs @@ -1,8 +1,8 @@ use super::tx_list::{HeaderState, TxList, TxLogEntryWrap}; use super::{action_menu, tx_list_display}; use super::{ - chart::BalanceChart, - tx_list::{self, ExpandType}, + chart::BalanceChart, + tx_list::{self, ExpandType}, }; use async_std::{prelude::FutureExt, task::current}; use chrono::{DateTime, DurationRound, TimeZone, Utc}; @@ -10,16 +10,16 @@ use grin_gui_core::error::GrinWalletInterfaceError; use grin_gui_core::node::SyncStatus; use grin_gui_core::wallet::SlatepackAddress; use grin_gui_core::{ - config::{Config, Currency}, - wallet::{InvoiceProof, RetrieveTxQueryArgs, TxLogEntry, TxLogEntryType}, + config::{Config, Currency}, + wallet::{InvoiceProof, RetrieveTxQueryArgs, TxLogEntry, TxLogEntryType}, }; use grin_gui_widgets::widget::header; use iced::Point; use iced_aw::Card; use iced_core::Widget; use plotters::{ - coord::{types::RangedCoordf32, ReverseCoordTranslate}, - prelude::*, + coord::{types::RangedCoordf32, ReverseCoordTranslate}, + prelude::*, }; use serde::{Deserialize, Serialize}; use std::cell::RefCell; @@ -28,872 +28,871 @@ use std::io::Read; use std::{collections::HashMap, path::PathBuf}; use { - super::super::super::{ - DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, DEFAULT_SUB_HEADER_FONT_SIZE, - SMALLER_FONT_SIZE, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::log_error, - crate::Result, - anyhow::Context, - grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, - TextInput, - }, - grin_gui_core::wallet::{StatusMessage, WalletInfo, WalletInterface}, - grin_gui_core::{ - node::{amount_to_hr_string, ServerStats}, - theme::ColorPalette, - }, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - std::sync::{Arc, RwLock}, + super::super::super::{ + DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, DEFAULT_SUB_HEADER_FONT_SIZE, + SMALLER_FONT_SIZE, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::log_error, + crate::Result, + anyhow::Context, + grin_gui_core::theme::{ + Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, + TextInput, + }, + grin_gui_core::wallet::{StatusMessage, WalletInfo, WalletInterface}, + grin_gui_core::{ + node::{amount_to_hr_string, ServerStats}, + theme::ColorPalette, + }, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + std::sync::{Arc, RwLock}, }; #[derive(Default)] pub struct StateContainer { - pub action_menu_state: action_menu::StateContainer, - pub tx_list_display_state: tx_list_display::StateContainer, - pub address_value: Option, - pub address: Option, - - wallet_info: Option, - wallet_status: String, - last_summary_update: chrono::DateTime, - tx_header_state: HeaderState, - node_status: Option, - pub node_synched: bool, - - cursor_index: Option, - caption_index: Option, - price_history: HashMap, f64>, + pub action_menu_state: action_menu::StateContainer, + pub tx_list_display_state: tx_list_display::StateContainer, + pub address_value: Option, + pub address: Option, + + wallet_info: Option, + wallet_status: String, + last_summary_update: chrono::DateTime, + tx_header_state: HeaderState, + node_status: Option, + pub node_synched: bool, + + cursor_index: Option, + caption_index: Option, + price_history: HashMap, f64>, } impl StateContainer { - pub fn parse_node_status(&self) -> String { - // Node status string - // BodySync - match &self.node_status { - Some(s) => match s.sync_status { - SyncStatus::NoSync => localized_string("node-synched"), - _ => localized_string("node-unsynched"), - }, - None => localized_string("unknown"), - } - } - pub fn update_node_status(&mut self, stats: &ServerStats) { - self.node_status = Some(stats.clone()); - match stats.sync_status { - SyncStatus::NoSync => self.node_synched = true, - _ => self.node_synched = false, - } - } + pub fn parse_node_status(&self) -> String { + // Node status string + // BodySync + match &self.node_status { + Some(s) => match s.sync_status { + SyncStatus::NoSync => localized_string("node-synched"), + _ => localized_string("node-unsynched"), + }, + None => localized_string("unknown"), + } + } + pub fn update_node_status(&mut self, stats: &ServerStats) { + self.node_status = Some(stats.clone()); + match stats.sync_status { + SyncStatus::NoSync => self.node_synched = true, + _ => self.node_synched = false, + } + } } #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Back, - Submit, - /// was updated from node, info - WalletInfoUpdateSuccess(bool, WalletInfo), - WalletInfoUpdateFailure(Arc>>), - WalletSlatepackAddressUpdateSuccess((String, SlatepackAddress)), - WalletCloseError(Arc>>), - WalletCloseSuccess, - CancelTx(u32, String), - TxDetails(TxLogEntryWrap), - TxProof(TxLogEntryWrap), - TxCancelledOk(u32, String), - TxCancelError(Arc>>), - ProofRetrievedOk(InvoiceProof, TxLogEntryWrap), - ProofRetrievedError(Arc>>), - ReloadTxSlate(String), - - // chart stuff - MouseIndex(usize, usize), - MouseExit, - UpdatePrices, + Back, + Submit, + /// was updated from node, info + WalletInfoUpdateSuccess(bool, WalletInfo), + WalletInfoUpdateFailure(Arc>>), + WalletSlatepackAddressUpdateSuccess((String, SlatepackAddress)), + WalletCloseError(Arc>>), + WalletCloseSuccess, + CancelTx(u32, String), + TxDetails(TxLogEntryWrap), + TxProof(TxLogEntryWrap), + TxCancelledOk(u32, String), + TxCancelError(Arc>>), + ProofRetrievedOk(InvoiceProof, TxLogEntryWrap), + ProofRetrievedError(Arc>>), + ReloadTxSlate(String), + + // chart stuff + MouseIndex(usize, usize), + MouseExit, + UpdatePrices, } /// update the historical price data fn update_prices(state: &mut StateContainer, currency: Currency) -> Result<()> { - // if we are using grin, we don't need to update the price history - if currency == Currency::GRIN { - return Ok(()); - } - - #[derive(Deserialize, Serialize, Debug)] - struct Price { - time: u64, - price: f64, - } - - #[derive(Deserialize, Serialize, Debug)] - struct PriceHistory { - prices: Vec, - } - - // pull price history from coingecko - // TODO this url should not be hardcoded - let price_history_url = format!("https://api.coingecko.com/api/v3/coins/grin/market_chart?vs_currency={}&days=11430&interval=daily", currency.shortname()); - let mut res = reqwest::blocking::get(price_history_url)?; - let mut body = String::new(); - res.read_to_string(&mut body)?; - - //debug!("price history data: {:#?}", body); - let history = serde_json::from_str::(&body).unwrap(); - - let mut prices = std::collections::HashMap::new(); - for price in history.prices { - let date_time = Utc.timestamp_millis_opt(price.time as i64).unwrap(); - prices.insert(date_time, price.price); - } - - // update the price hashmap in the state - state.price_history = prices; - Ok(()) + // if we are using grin, we don't need to update the price history + if currency == Currency::GRIN { + return Ok(()); + } + + #[derive(Deserialize, Serialize, Debug)] + struct Price { + time: u64, + price: f64, + } + + #[derive(Deserialize, Serialize, Debug)] + struct PriceHistory { + prices: Vec, + } + + // pull price history from coingecko + // TODO this url should not be hardcoded + let price_history_url = format!("https://api.coingecko.com/api/v3/coins/grin/market_chart?vs_currency={}&days=11430&interval=daily", currency.shortname()); + let mut res = reqwest::blocking::get(price_history_url)?; + let mut body = String::new(); + res.read_to_string(&mut body)?; + + //debug!("price history data: {:#?}", body); + let history = serde_json::from_str::(&body).unwrap(); + + let mut prices = std::collections::HashMap::new(); + for price in history.prices { + let date_time = Utc.timestamp_millis_opt(price.time as i64).unwrap(); + prices.insert(date_time, price.price); + } + + // update the price hashmap in the state + state.price_history = prices; + Ok(()) } // Okay to modify state and access wallet here pub fn handle_tick<'a>( - grin_gui: &mut GrinGui, - time: chrono::DateTime, + grin_gui: &mut GrinGui, + time: chrono::DateTime, ) -> Result> { - { - let w = grin_gui.wallet_interface.read().unwrap(); - if !w.wallet_is_open() { - return Ok(Command::none()); - } - } - let messages = WalletInterface::get_wallet_updater_status(grin_gui.wallet_interface.clone())?; - let state = &mut grin_gui.wallet_state.operation_state.home_state; - let last_message = messages.get(0); - if let Some(m) = last_message { - state.wallet_status = match m { - StatusMessage::UpdatingOutputs(s) => format!("{}", s), - StatusMessage::UpdatingTransactions(s) => format!("{}", s), - StatusMessage::FullScanWarn(s) => format!("{}", s), - StatusMessage::Scanning(s, m) => format!("Scanning - {}% complete", m), - StatusMessage::ScanningComplete(s) => format!("{}", s), - StatusMessage::UpdateWarning(s) => format!("{}", s), - } - } - - // don't display status if node is still synching - if !state.node_synched { - state.wallet_status = localized_string("awaiting-sync"); - } - - // calls to API should be limited to once per minute - if time - state.last_summary_update - > chrono::Duration::from_std(std::time::Duration::from_secs(60)).unwrap() - { - update_prices(state, grin_gui.config.currency)?; - } - - if time - state.last_summary_update - > chrono::Duration::from_std(std::time::Duration::from_secs(10)).unwrap() - { - state.last_summary_update = chrono::Local::now(); - - let mut query_args = RetrieveTxQueryArgs::default(); - - query_args.exclude_cancelled = Some(true); - query_args.include_outstanding_only = Some(true); - - let w = grin_gui.wallet_interface.clone(); - let node_synched = state.node_synched; - - let fut = move || WalletInterface::get_wallet_info(w.clone(), node_synched); //.join(WalletInterface::get_txs(w, Some(query_args))); - - return Ok(Command::perform(fut(), |wallet_info_res| { - if wallet_info_res.is_err() { - let e = wallet_info_res - .context("Failed to retrieve wallet info status") - .unwrap_err(); - return Message::Interaction(Interaction::WalletOperationHomeViewInteraction( - LocalViewInteraction::WalletInfoUpdateFailure(Arc::new(RwLock::new(Some(e)))), - )); - } - let (node_success, wallet_info) = wallet_info_res.unwrap(); - //let (_, txs) = txs_res.unwrap(); - Message::Interaction(Interaction::WalletOperationHomeViewInteraction( - //LocalViewInteraction::WalletInfoUpdateSuccess(node_success, wallet_info, txs), - LocalViewInteraction::WalletInfoUpdateSuccess(node_success, wallet_info), - )) - })); - } - // If slatepack address is not filled out, go get it - if state.address_value.is_none() { - let w = grin_gui.wallet_interface.clone(); - - let fut = move || WalletInterface::get_slatepack_address(w.clone()); - return Ok(Command::perform(fut(), |get_slatepack_address_res| { - if get_slatepack_address_res.is_err() { - let e = get_slatepack_address_res - .context("Failed to retrieve wallet slatepack address") - .unwrap_err(); - return Message::Interaction(Interaction::WalletOperationHomeViewInteraction( - LocalViewInteraction::WalletInfoUpdateFailure(Arc::new(RwLock::new(Some(e)))), - )); - } - Message::Interaction(Interaction::WalletOperationHomeViewInteraction( - LocalViewInteraction::WalletSlatepackAddressUpdateSuccess( - get_slatepack_address_res.unwrap(), - ), - )) - })); - } - - Ok(Command::none()) + { + let w = grin_gui.wallet_interface.read().unwrap(); + if !w.wallet_is_open() { + return Ok(Command::none()); + } + } + let messages = WalletInterface::get_wallet_updater_status(grin_gui.wallet_interface.clone())?; + let state = &mut grin_gui.wallet_state.operation_state.home_state; + let last_message = messages.get(0); + if let Some(m) = last_message { + state.wallet_status = match m { + StatusMessage::UpdatingOutputs(s) => format!("{}", s), + StatusMessage::UpdatingTransactions(s) => format!("{}", s), + StatusMessage::FullScanWarn(s) => format!("{}", s), + StatusMessage::Scanning(s, m) => format!("Scanning - {}% complete", m), + StatusMessage::ScanningComplete(s) => format!("{}", s), + StatusMessage::UpdateWarning(s) => format!("{}", s), + } + } + + // don't display status if node is still synching + if !state.node_synched { + state.wallet_status = localized_string("awaiting-sync"); + } + + // calls to API should be limited to once per minute + if time - state.last_summary_update + > chrono::Duration::from_std(std::time::Duration::from_secs(60)).unwrap() + { + update_prices(state, grin_gui.config.currency)?; + } + + if time - state.last_summary_update + > chrono::Duration::from_std(std::time::Duration::from_secs(10)).unwrap() + { + state.last_summary_update = chrono::Local::now(); + + let mut query_args = RetrieveTxQueryArgs::default(); + + query_args.exclude_cancelled = Some(true); + query_args.include_outstanding_only = Some(true); + + let w = grin_gui.wallet_interface.clone(); + let node_synched = state.node_synched; + + let fut = move || WalletInterface::get_wallet_info(w.clone(), node_synched); //.join(WalletInterface::get_txs(w, Some(query_args))); + + return Ok(Command::perform(fut(), |wallet_info_res| { + if wallet_info_res.is_err() { + let e = wallet_info_res + .context("Failed to retrieve wallet info status") + .unwrap_err(); + return Message::Interaction(Interaction::WalletOperationHomeViewInteraction( + LocalViewInteraction::WalletInfoUpdateFailure(Arc::new(RwLock::new(Some(e)))), + )); + } + let (node_success, wallet_info) = wallet_info_res.unwrap(); + //let (_, txs) = txs_res.unwrap(); + Message::Interaction(Interaction::WalletOperationHomeViewInteraction( + //LocalViewInteraction::WalletInfoUpdateSuccess(node_success, wallet_info, txs), + LocalViewInteraction::WalletInfoUpdateSuccess(node_success, wallet_info), + )) + })); + } + // If slatepack address is not filled out, go get it + if state.address_value.is_none() { + let w = grin_gui.wallet_interface.clone(); + + let fut = move || WalletInterface::get_slatepack_address(w.clone()); + return Ok(Command::perform(fut(), |get_slatepack_address_res| { + if get_slatepack_address_res.is_err() { + let e = get_slatepack_address_res + .context("Failed to retrieve wallet slatepack address") + .unwrap_err(); + return Message::Interaction(Interaction::WalletOperationHomeViewInteraction( + LocalViewInteraction::WalletInfoUpdateFailure(Arc::new(RwLock::new(Some(e)))), + )); + } + Message::Interaction(Interaction::WalletOperationHomeViewInteraction( + LocalViewInteraction::WalletSlatepackAddressUpdateSuccess( + get_slatepack_address_res.unwrap(), + ), + )) + })); + } + + Ok(Command::none()) } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.wallet_state.operation_state.home_state; - match message { - LocalViewInteraction::UpdatePrices => { - update_prices(state, grin_gui.config.currency)?; - } - LocalViewInteraction::MouseIndex(index1, index2) => { - state.cursor_index = Some(index1); - state.caption_index = Some(index2); - } - LocalViewInteraction::MouseExit => { - state.cursor_index = None; - state.caption_index = None; - } - LocalViewInteraction::Back => { - let wallet_interface = grin_gui.wallet_interface.clone(); - let fut = WalletInterface::close_wallet(wallet_interface); - - return Ok(Command::perform(fut, |r| { - match r.context("Failed to close wallet") { - Ok(()) => { - Message::Interaction(Interaction::WalletOperationHomeViewInteraction( - LocalViewInteraction::WalletCloseSuccess, - )) - } - Err(e) => { - Message::Interaction(Interaction::WalletOperationHomeViewInteraction( - LocalViewInteraction::WalletCloseError(Arc::new(RwLock::new(Some(e)))), - )) - } - } - })); - } - LocalViewInteraction::Submit => {} - LocalViewInteraction::WalletInfoUpdateSuccess(node_success, wallet_info) => { - debug!( - "Update Wallet Info Summary: {}, {:?}", - node_success, wallet_info - ); - // check if different from last update, if so refresh the current transaction listing - if state.wallet_info.as_ref() != Some(&wallet_info) { - // If transaction list hasn't been init yet, refresh the list with latest - debug!( - "Updating transactions based on new wallet info data: {:?} (old) vs {:?} (new)", - state.wallet_info.as_ref(), - wallet_info - ); - state.wallet_info = Some(wallet_info); - if grin_gui - .wallet_state - .operation_state - .home_state - .tx_list_display_state - .mode - == crate::gui::element::wallet::operation::tx_list_display::Mode::NotInit - { - let fut = move || async {}; - return Ok(Command::perform(fut(), |_| { - return Message::Interaction( + let state = &mut grin_gui.wallet_state.operation_state.home_state; + match message { + LocalViewInteraction::UpdatePrices => { + update_prices(state, grin_gui.config.currency)?; + } + LocalViewInteraction::MouseIndex(index1, index2) => { + state.cursor_index = Some(index1); + state.caption_index = Some(index2); + } + LocalViewInteraction::MouseExit => { + state.cursor_index = None; + state.caption_index = None; + } + LocalViewInteraction::Back => { + let wallet_interface = grin_gui.wallet_interface.clone(); + let fut = WalletInterface::close_wallet(wallet_interface); + + return Ok(Command::perform(fut, |r| { + match r.context("Failed to close wallet") { + Ok(()) => { + Message::Interaction(Interaction::WalletOperationHomeViewInteraction( + LocalViewInteraction::WalletCloseSuccess, + )) + } + Err(e) => { + Message::Interaction(Interaction::WalletOperationHomeViewInteraction( + LocalViewInteraction::WalletCloseError(Arc::new(RwLock::new(Some(e)))), + )) + } + } + })); + } + LocalViewInteraction::Submit => {} + LocalViewInteraction::WalletInfoUpdateSuccess(node_success, wallet_info) => { + debug!( + "Update Wallet Info Summary: {}, {:?}", + node_success, wallet_info + ); + // check if different from last update, if so refresh the current transaction listing + if state.wallet_info.as_ref() != Some(&wallet_info) { + // If transaction list hasn't been init yet, refresh the list with latest + debug!( + "Updating transactions based on new wallet info data: {:?} (old) vs {:?} (new)", + state.wallet_info.as_ref(), + wallet_info + ); + state.wallet_info = Some(wallet_info); + if grin_gui + .wallet_state + .operation_state + .home_state + .tx_list_display_state + .mode == crate::gui::element::wallet::operation::tx_list_display::Mode::NotInit + { + let fut = move || async {}; + return Ok(Command::perform(fut(), |_| { + return Message::Interaction( Interaction::WalletOperationHomeTxListDisplayInteraction( crate::gui::element::wallet::operation::tx_list_display::LocalViewInteraction::SelectMode( crate::gui::element::wallet::operation::tx_list_display::Mode::Recent ), ), ); - })); - } else { - let fut = move || async {}; - return Ok(Command::perform(fut(), |_| { - return Message::Interaction( + })); + } else { + let fut = move || async {}; + return Ok(Command::perform(fut(), |_| { + return Message::Interaction( Interaction::WalletOperationHomeTxListDisplayInteraction( crate::gui::element::wallet::operation::tx_list_display::LocalViewInteraction::RefreshList , ), ); - })); - } - } - } - LocalViewInteraction::WalletInfoUpdateFailure(err) => { - grin_gui.error = err.write().unwrap().take(); - if let Some(e) = grin_gui.error.as_ref() { - log_error(e); - } - } - LocalViewInteraction::WalletCloseSuccess => { - // Also blank out all relevant info first, and perform all shutdown - // so it doesn't appear when opening another wallet - state.wallet_info = None; - state.address_value = None; - state.address = None; - grin_gui - .wallet_state - .operation_state - .tx_detail_state - .current_tx = None; - - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::Open; - } - LocalViewInteraction::WalletCloseError(err) => { - grin_gui.error = err.write().unwrap().take(); - if let Some(e) = grin_gui.error.as_ref() { - log_error(e); - } - } - LocalViewInteraction::WalletSlatepackAddressUpdateSuccess((address_string, address)) => { - state.address_value = Some(address_string); - state.address = Some(address); - } - LocalViewInteraction::TxDetails(tx_log_entry_wrap) => { - log::debug!("Interaction::WalletOperationHomeViewInteraction::TxDetails"); - grin_gui - .wallet_state - .operation_state - .tx_detail_state - .current_tx = Some(tx_log_entry_wrap.tx); - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::TxDetail; - } - LocalViewInteraction::TxProof(tx_log_entry_wrap) => { - log::debug!("Interaction::WalletOperationHomeViewInteraction::TxProof"); - grin_gui.error.take(); - - let w = grin_gui.wallet_interface.clone(); - let fut = move || { - WalletInterface::retrieve_payment_proof_invoice(w, Some(tx_log_entry_wrap.tx.id)) - }; - - return Ok(Command::perform(fut(), |r| { - match r.context("Failed to Retrieve Invoice Proof") { - Ok(ret) => { - Message::Interaction(Interaction::WalletOperationHomeViewInteraction( - LocalViewInteraction::ProofRetrievedOk(ret, tx_log_entry_wrap), - )) - } - Err(e) => { - Message::Interaction(Interaction::WalletOperationHomeViewInteraction( - LocalViewInteraction::ProofRetrievedError(Arc::new(RwLock::new(Some( - e, - )))), - )) - } - } - })); - } - LocalViewInteraction::CancelTx(id, uuid) => { - debug!("Cancel Tx: {}", id); - grin_gui.error.take(); - - log::debug!("Interaction::WalletOperationHomeViewInteraction::CancelTx"); - - let w = grin_gui.wallet_interface.clone(); - - let fut = move || WalletInterface::cancel_tx(w, id); - - return Ok(Command::perform(fut(), |r| { - match r.context("Failed to Cancel Transaction") { - Ok(ret) => { - Message::Interaction(Interaction::WalletOperationHomeViewInteraction( - LocalViewInteraction::TxCancelledOk(ret, uuid), - )) - } - Err(e) => { - Message::Interaction(Interaction::WalletOperationHomeViewInteraction( - LocalViewInteraction::TxCancelError(Arc::new(RwLock::new(Some(e)))), - )) - } - } - })); - } - LocalViewInteraction::TxCancelledOk(id, uuid) => { - // Delete file - if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { - let out_file_name = format!("{}/{}.slatepack", dir, uuid); - let _ = fs::remove_file(out_file_name); - } - - // Trigger event to reload transaction list - let mode = grin_gui - .wallet_state - .operation_state - .home_state - .tx_list_display_state - .mode - .clone(); - let fut = move || async {}; - return Ok(Command::perform(fut(), |r| { - Message::Interaction(Interaction::WalletOperationHomeTxListDisplayInteraction( - super::home::tx_list_display::LocalViewInteraction::SelectMode(mode), - )) - })); - } - LocalViewInteraction::TxCancelError(err) => { - grin_gui.error = err.write().unwrap().take(); - if let Some(e) = grin_gui.error.as_ref() { - log_error(e); - } - } - LocalViewInteraction::ProofRetrievedOk(proof, tx_log_entry_wrap)=> { - grin_gui - .wallet_state - .operation_state - .tx_proof_state - .current_tx = Some(tx_log_entry_wrap.tx); - grin_gui - .wallet_state - .operation_state - .tx_proof_state - .current_proof = Some(proof); - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::TxProof; - } - LocalViewInteraction::ProofRetrievedError(err) => { - grin_gui.error = err.write().unwrap().take(); - if let Some(e) = grin_gui.error.as_ref() { - log_error(e); - } - } - LocalViewInteraction::ReloadTxSlate(id) => { - debug!("Reload stored slatepack at: {}", id); - if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { - let in_file_name = format!("{}/{}.slatepack", dir, id); - if let Ok(s) = fs::read_to_string(in_file_name.clone()) { - grin_gui - .wallet_state - .operation_state - .show_slatepack_state - .encrypted_slate = Some(s.to_string()); - // Just go to create tx success screen for now - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::ShowSlatepack; - } else { - // Error that we're unable to load the file - grin_gui.error = Some( - GrinWalletInterfaceError::InvalidSlatepackFile { file: in_file_name } - .into(), - ); - } - } - } - } - Ok(Command::none()) + })); + } + } + } + LocalViewInteraction::WalletInfoUpdateFailure(err) => { + grin_gui.error = err.write().unwrap().take(); + if let Some(e) = grin_gui.error.as_ref() { + log_error(e); + } + } + LocalViewInteraction::WalletCloseSuccess => { + // Also blank out all relevant info first, and perform all shutdown + // so it doesn't appear when opening another wallet + state.wallet_info = None; + state.address_value = None; + state.address = None; + grin_gui + .wallet_state + .operation_state + .tx_detail_state + .current_tx = None; + + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::Open; + } + LocalViewInteraction::WalletCloseError(err) => { + grin_gui.error = err.write().unwrap().take(); + if let Some(e) = grin_gui.error.as_ref() { + log_error(e); + } + } + LocalViewInteraction::WalletSlatepackAddressUpdateSuccess((address_string, address)) => { + state.address_value = Some(address_string); + state.address = Some(address); + } + LocalViewInteraction::TxDetails(tx_log_entry_wrap) => { + log::debug!("Interaction::WalletOperationHomeViewInteraction::TxDetails"); + grin_gui + .wallet_state + .operation_state + .tx_detail_state + .current_tx = Some(tx_log_entry_wrap.tx); + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::TxDetail; + } + LocalViewInteraction::TxProof(tx_log_entry_wrap) => { + log::debug!("Interaction::WalletOperationHomeViewInteraction::TxProof"); + grin_gui.error.take(); + + let w = grin_gui.wallet_interface.clone(); + let fut = move || { + WalletInterface::retrieve_payment_proof_invoice(w, Some(tx_log_entry_wrap.tx.id)) + }; + + return Ok(Command::perform(fut(), |r| { + match r.context("Failed to Retrieve Invoice Proof") { + Ok(ret) => { + Message::Interaction(Interaction::WalletOperationHomeViewInteraction( + LocalViewInteraction::ProofRetrievedOk(ret, tx_log_entry_wrap), + )) + } + Err(e) => { + Message::Interaction(Interaction::WalletOperationHomeViewInteraction( + LocalViewInteraction::ProofRetrievedError(Arc::new(RwLock::new(Some( + e, + )))), + )) + } + } + })); + } + LocalViewInteraction::CancelTx(id, uuid) => { + debug!("Cancel Tx: {}", id); + grin_gui.error.take(); + + log::debug!("Interaction::WalletOperationHomeViewInteraction::CancelTx"); + + let w = grin_gui.wallet_interface.clone(); + + let fut = move || WalletInterface::cancel_tx(w, id); + + return Ok(Command::perform(fut(), |r| { + match r.context("Failed to Cancel Transaction") { + Ok(ret) => { + Message::Interaction(Interaction::WalletOperationHomeViewInteraction( + LocalViewInteraction::TxCancelledOk(ret, uuid), + )) + } + Err(e) => { + Message::Interaction(Interaction::WalletOperationHomeViewInteraction( + LocalViewInteraction::TxCancelError(Arc::new(RwLock::new(Some(e)))), + )) + } + } + })); + } + LocalViewInteraction::TxCancelledOk(id, uuid) => { + // Delete file + if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { + let out_file_name = format!("{}/{}.slatepack", dir, uuid); + let _ = fs::remove_file(out_file_name); + } + + // Trigger event to reload transaction list + let mode = grin_gui + .wallet_state + .operation_state + .home_state + .tx_list_display_state + .mode + .clone(); + let fut = move || async {}; + return Ok(Command::perform(fut(), |r| { + Message::Interaction(Interaction::WalletOperationHomeTxListDisplayInteraction( + super::home::tx_list_display::LocalViewInteraction::SelectMode(mode), + )) + })); + } + LocalViewInteraction::TxCancelError(err) => { + grin_gui.error = err.write().unwrap().take(); + if let Some(e) = grin_gui.error.as_ref() { + log_error(e); + } + } + LocalViewInteraction::ProofRetrievedOk(proof, tx_log_entry_wrap) => { + grin_gui + .wallet_state + .operation_state + .tx_proof_state + .current_tx = Some(tx_log_entry_wrap.tx); + grin_gui + .wallet_state + .operation_state + .tx_proof_state + .current_proof = Some(proof); + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::TxProof; + } + LocalViewInteraction::ProofRetrievedError(err) => { + grin_gui.error = err.write().unwrap().take(); + if let Some(e) = grin_gui.error.as_ref() { + log_error(e); + } + } + LocalViewInteraction::ReloadTxSlate(id) => { + debug!("Reload stored slatepack at: {}", id); + if let Some(dir) = grin_gui.config.get_wallet_slatepack_dir() { + let in_file_name = format!("{}/{}.slatepack", dir, id); + if let Ok(s) = fs::read_to_string(in_file_name.clone()) { + grin_gui + .wallet_state + .operation_state + .show_slatepack_state + .encrypted_slate = Some(s.to_string()); + // Just go to create tx success screen for now + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::ShowSlatepack; + } else { + // Error that we're unable to load the file + grin_gui.error = Some( + GrinWalletInterfaceError::InvalidSlatepackFile { file: in_file_name } + .into(), + ); + } + } + } + } + Ok(Command::none()) } pub fn data_container<'a>(config: &'a Config, state: &'a StateContainer) -> Container<'a, Message> { - // Buttons to perform operations go here, but empty container for now - let operations_menu = action_menu::data_container(config, &state.action_menu_state, &state); - - // Basic Info "Box" - let waiting_string = "---------"; - let ( - total_string, - amount_spendable_string, - awaiting_confirmation_string, - awaiting_finalization_string, - locked_string, - ) = match state.wallet_info.as_ref() { - Some(info) => ( - amount_to_hr_string(info.total, false), - amount_to_hr_string(info.amount_currently_spendable, false), - amount_to_hr_string(info.amount_awaiting_confirmation, false), - amount_to_hr_string(info.amount_awaiting_finalization, false), - amount_to_hr_string(info.amount_locked, false), - ), - None => ( - waiting_string.to_owned(), - waiting_string.to_owned(), - waiting_string.to_owned(), - waiting_string.to_owned(), - waiting_string.to_owned(), - ), - }; - - let wallet_name = if let Some(index) = config.current_wallet_index { - config.wallets[index].display_name.clone() - } else { - "wallet".to_owned() - }; - - let currency = config.currency; - let balance = if currency == Currency::GRIN { - amount_spendable_string.clone() - } else if let Some(info) = state.wallet_info.as_ref() { - let today = Utc::now() - .duration_trunc(chrono::Duration::days(1)) - .unwrap(); - - // grap latest price if we don't have one for today - let price = match state.price_history.get(&today) { - Some(price) => *price, - None => { - let prev = today - chrono::Duration::days(1); - *state.price_history.get(&prev).unwrap() - } - }; - - let amount_spendable = info.amount_currently_spendable / grin_gui_core::GRIN_BASE; - let price_adjusted = amount_spendable as f64 * price; - let trunc = format!("{:.1$}", price_adjusted, currency.precision()); - format!("{}{}", currency.symbol(), trunc) - } else { - waiting_string.to_owned() - }; - - // Title row - let title = Text::new(balance).size(DEFAULT_HEADER_FONT_SIZE); - let title_container = - Container::new(title).style(grin_gui_core::theme::ContainerStyle::BrightBackground); - - let subtitle = Text::new(wallet_name).size(SMALLER_FONT_SIZE); - let subtitle_container = Container::new(subtitle) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) - .padding(iced::Padding::from([ - 3, // top - 0, // right - 0, // bottom - 0, // left - ])); - - let close_wallet_label_container = - Container::new(Text::new(localized_string("close")).size(SMALLER_FONT_SIZE)) - .height(Length::Fixed(14.0)) - .width(Length::Fixed(30.0)) - .center_y() - .center_x(); - - let close_wallet_button: Element = Button::new(close_wallet_label_container) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::WalletOperationHomeViewInteraction( - LocalViewInteraction::Back, - )) - .padding(2) - .into(); - - let subtitle_row = Row::new() - .push(subtitle_container) - .push(Space::with_width(Length::Fixed(2.0))) - .push(close_wallet_button.map(Message::Interaction)); - - let address_label = Text::new(format!( - "{}: ", - localized_string("this-wallet-receive-addr") - )) - .size(SMALLER_FONT_SIZE); - let address_label_container = Container::new(address_label) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) - .padding(iced::Padding::from([ - 3, // top - 0, // right - 0, // bottom - 0, // left - ])); - - // Truncate a bit for compact display purposes - let mut copied_address_value = "".into(); - let address = match &state.address_value { - Some(a) => { - let mut s = a.clone(); - copied_address_value = a.clone(); - s.truncate(10); - let s2 = a.clone().split_off(60); - format!("{}...{}", s, s2) - } - None => "".to_owned(), - }; - - let address = Text::new(address).size(SMALLER_FONT_SIZE); - let address_container = Container::new(address) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) - .padding(iced::Padding::from([ - 3, // top - 0, // right - 0, // bottom - 0, // left - ])); - - let copy_address_label_container = - Container::new(Text::new(localized_string("copy-to-clipboard")).size(SMALLER_FONT_SIZE)) - .height(Length::Fixed(14.0)) - .width(Length::Fixed(30.0)) - .center_y() - .center_x(); - - let copy_address_button: Element = Button::new(copy_address_label_container) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::WriteToClipboard(copied_address_value)) - .padding(2) - .into(); - - let address_row = Row::new() - .push(address_label_container) - .push(Space::with_width(Length::Fixed(2.0))) - .push(address_container) - .push(Space::with_width(Length::Fixed(2.0))) - .push(copy_address_button.map(Message::Interaction)); - - let title_container = Container::new( - Column::new() - .push(title_container) - .push(subtitle_row) - .push(address_row), - ) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - let header_row = Row::new() - .push(title_container) - .push(Space::with_width(Length::Fill)) - .push(operations_menu); - - let header_container = Container::new(header_row).padding(iced::Padding::from([ - 0, // top - 0, // right - DEFAULT_PADDING as u16, // bottom - 0, // left - ])); - - let total_value_label = - Text::new(format!("{}:", localized_string("info-confirmed-total"))).size(DEFAULT_FONT_SIZE); - let total_value_label_container = Container::new(total_value_label) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground); - - let total_value = Text::new(total_string).size(DEFAULT_FONT_SIZE); - let total_value_container = Container::new(total_value) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .width(Length::Fill) - .align_x(alignment::Horizontal::Right); - - let total_row = Row::new() - .push(total_value_label_container) - .push(total_value_container) - .width(Length::Fill); - - let awaiting_confirmation_label = Text::new(format!( - "{}:", - localized_string("info-awaiting-confirmation") - )) - .size(DEFAULT_FONT_SIZE); - let awaiting_confirmation_label_container = Container::new(awaiting_confirmation_label) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground); - - let awaiting_confirmation_value = - Text::new(awaiting_confirmation_string).size(DEFAULT_FONT_SIZE); - let awaiting_confirmation_value_container = Container::new(awaiting_confirmation_value) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .width(Length::Fill) - .align_x(alignment::Horizontal::Right); - - let awaiting_confirmation_row = Row::new() - .push(awaiting_confirmation_label_container) - .push(awaiting_confirmation_value_container) - .width(Length::Fill); - - let awaiting_finalization_label = Text::new(format!( - "{}:", - localized_string("info-awaiting-finalization") - )) - .size(DEFAULT_FONT_SIZE); - let awaiting_finalization_label_container = Container::new(awaiting_finalization_label) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground); - - let awaiting_finalization_value = - Text::new(awaiting_finalization_string).size(DEFAULT_FONT_SIZE); - let awaiting_finalization_value_container = Container::new(awaiting_finalization_value) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .width(Length::Fill) - .align_x(alignment::Horizontal::Right); - - let awaiting_finalization_row = Row::new() - .push(awaiting_finalization_label_container) - .push(awaiting_finalization_value_container) - .width(Length::Fill); - - let locked_label = - Text::new(format!("{}:", localized_string("info-locked"))).size(DEFAULT_FONT_SIZE); - let locked_label_container = - Container::new(locked_label).style(grin_gui_core::theme::ContainerStyle::BrightBackground); - - let locked_value = Text::new(locked_string).size(DEFAULT_FONT_SIZE); - let locked_value_container = Container::new(locked_value) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .width(Length::Fill) - .align_x(alignment::Horizontal::Right); - - let locked_row = Row::new() - .push(locked_label_container) - .push(locked_value_container) - .width(Length::Fill); - - let amount_spendable_label = - Text::new(format!("{}:", localized_string("info-amount-spendable"))) - .size(DEFAULT_FONT_SIZE); - let amount_spendable_label_container = Container::new(amount_spendable_label) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground); - - let amount_spendable_value = Text::new(amount_spendable_string).size(DEFAULT_FONT_SIZE); - let amount_spendable_value_container = Container::new(amount_spendable_value) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .width(Length::Fill) - .align_x(alignment::Horizontal::Right); - - let amount_spendable_row = Row::new() - .push(amount_spendable_label_container) - .push(amount_spendable_value_container) - .width(Length::Fill); - - let info_column = Column::new() - .push(total_row) - .push(awaiting_confirmation_row) - .push(awaiting_finalization_row) - .push(locked_row) - .push(amount_spendable_row) - .spacing(7); - - let wallet_info_card_container = Container::new(info_column) - .width(Length::Fixed(240.0)) - .padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - 5.0, // left - ])); - - let mut first_row_container = Row::new() - .push(wallet_info_card_container) - .height(Length::Fixed(120.0)); - - // if there is transaction data, display the balance chart - let mut balance_data = state.tx_list_display_state.balance_data.clone(); - if !balance_data.is_empty() { - // if there is price history data, convert the balance data to the currency - if !state.price_history.is_empty() && currency != Currency::GRIN { - balance_data = balance_data - .iter() - .map(|(date, balance)| { - let price = state.price_history.get(date).unwrap_or(&0.0); - let precision = i32::pow(10, currency.precision() as u32) as f64; - let adjusted_price = f64::trunc(balance * price * precision) / precision; - (date.clone(), adjusted_price) - }) - .collect::>(); - } - - let theme_name = config.theme.clone().unwrap_or("Alliance".to_string()); - let theme = grin_gui_core::theme::Theme::all() - .iter() - .find(|t| t.0 == theme_name) - .unwrap() - .1 - .clone(); - - first_row_container = first_row_container.push(BalanceChart::new( - theme, - balance_data.into_iter().rev(), - state.cursor_index, - state.caption_index, - )); - } - - // Status container element for Node state - let status_container_node_state_label_text = - Text::new(format!("{}: ", localized_string("node-status"))) - .size(DEFAULT_FONT_SIZE) - .height(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Left) - .vertical_alignment(alignment::Vertical::Center); - - let status_container_node_state_text = Text::new(state.parse_node_status()) - .size(DEFAULT_FONT_SIZE) - .height(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Left) - .vertical_alignment(alignment::Vertical::Center); - - // Status container bar at bottom of screen - let status_container_label_text = Text::new(localized_string("wallet-status")) - .size(DEFAULT_FONT_SIZE) - .height(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Right) - .vertical_alignment(alignment::Vertical::Center); - - let status_container_separator_text = Text::new(": ") - .size(DEFAULT_FONT_SIZE) - .height(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Right) - .vertical_alignment(alignment::Vertical::Center); - - let status_container_status_text = Text::new(&state.wallet_status) - .size(DEFAULT_FONT_SIZE) - .height(Length::Fill) - .horizontal_alignment(alignment::Horizontal::Right) - .vertical_alignment(alignment::Vertical::Center); - - let status_container_contents = Row::new() - .push(Space::new( - Length::Fixed(DEFAULT_PADDING), - Length::Fixed(0.0), - )) - .push(status_container_node_state_label_text) - .push(status_container_node_state_text) - .push(Space::new(Length::Fill, Length::Fill)) - .push(status_container_label_text) - .push(status_container_separator_text) - .push(status_container_status_text) - .push(Space::new( - Length::Fixed(DEFAULT_PADDING), - Length::Fixed(0.0), - )); - - let status_container = Container::new(status_container_contents) - .style(grin_gui_core::theme::ContainerStyle::BrightForeground) - .height(Length::Fixed(25.0)) - .width(Length::Fill); - - let status_row = Row::new() - .push(status_container) - .align_items(Alignment::Center) - .spacing(25); - - // Buttons to perform operations go here, but empty container for now - let tx_list_display = - tx_list_display::data_container(config, &state, &state.tx_list_display_state) - .height(Length::Fill); - - // Overall Home screen layout column - let column = Column::new() - .push(header_container) - .push(first_row_container) - .push(Space::with_height(Length::Fixed(DEFAULT_PADDING * 3.0))) - .push(tx_list_display) - .push(Space::with_height(Length::Fixed(DEFAULT_PADDING))) - .push(status_row) - .height(Length::Fill); - - Container::new(column).padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + // Buttons to perform operations go here, but empty container for now + let operations_menu = action_menu::data_container(config, &state.action_menu_state, &state); + + // Basic Info "Box" + let waiting_string = "---------"; + let ( + total_string, + amount_spendable_string, + awaiting_confirmation_string, + awaiting_finalization_string, + locked_string, + ) = match state.wallet_info.as_ref() { + Some(info) => ( + amount_to_hr_string(info.total, false), + amount_to_hr_string(info.amount_currently_spendable, false), + amount_to_hr_string(info.amount_awaiting_confirmation, false), + amount_to_hr_string(info.amount_awaiting_finalization, false), + amount_to_hr_string(info.amount_locked, false), + ), + None => ( + waiting_string.to_owned(), + waiting_string.to_owned(), + waiting_string.to_owned(), + waiting_string.to_owned(), + waiting_string.to_owned(), + ), + }; + + let wallet_name = if let Some(index) = config.current_wallet_index { + config.wallets[index].display_name.clone() + } else { + "wallet".to_owned() + }; + + let currency = config.currency; + let balance = if currency == Currency::GRIN { + amount_spendable_string.clone() + } else if let Some(info) = state.wallet_info.as_ref() { + let today = Utc::now() + .duration_trunc(chrono::Duration::days(1)) + .unwrap(); + + // grap latest price if we don't have one for today + let price = match state.price_history.get(&today) { + Some(price) => *price, + None => { + let prev = today - chrono::Duration::days(1); + *state.price_history.get(&prev).unwrap() + } + }; + + let amount_spendable = info.amount_currently_spendable / grin_gui_core::GRIN_BASE; + let price_adjusted = amount_spendable as f64 * price; + let trunc = format!("{:.1$}", price_adjusted, currency.precision()); + format!("{}{}", currency.symbol(), trunc) + } else { + waiting_string.to_owned() + }; + + // Title row + let title = Text::new(balance).size(DEFAULT_HEADER_FONT_SIZE); + let title_container = + Container::new(title).style(grin_gui_core::theme::ContainerStyle::BrightBackground); + + let subtitle = Text::new(wallet_name).size(SMALLER_FONT_SIZE); + let subtitle_container = Container::new(subtitle) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + .padding(iced::Padding::from([ + 3, // top + 0, // right + 0, // bottom + 0, // left + ])); + + let close_wallet_label_container = + Container::new(Text::new(localized_string("close")).size(SMALLER_FONT_SIZE)) + .height(Length::Fixed(14.0)) + .width(Length::Fixed(30.0)) + .center_y() + .center_x(); + + let close_wallet_button: Element = Button::new(close_wallet_label_container) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::WalletOperationHomeViewInteraction( + LocalViewInteraction::Back, + )) + .padding(2) + .into(); + + let subtitle_row = Row::new() + .push(subtitle_container) + .push(Space::with_width(Length::Fixed(2.0))) + .push(close_wallet_button.map(Message::Interaction)); + + let address_label = Text::new(format!( + "{}: ", + localized_string("this-wallet-receive-addr") + )) + .size(SMALLER_FONT_SIZE); + let address_label_container = Container::new(address_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + .padding(iced::Padding::from([ + 3, // top + 0, // right + 0, // bottom + 0, // left + ])); + + // Truncate a bit for compact display purposes + let mut copied_address_value = "".into(); + let address = match &state.address_value { + Some(a) => { + let mut s = a.clone(); + copied_address_value = a.clone(); + s.truncate(10); + let s2 = a.clone().split_off(60); + format!("{}...{}", s, s2) + } + None => "".to_owned(), + }; + + let address = Text::new(address).size(SMALLER_FONT_SIZE); + let address_container = Container::new(address) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + .padding(iced::Padding::from([ + 3, // top + 0, // right + 0, // bottom + 0, // left + ])); + + let copy_address_label_container = + Container::new(Text::new(localized_string("copy-to-clipboard")).size(SMALLER_FONT_SIZE)) + .height(Length::Fixed(14.0)) + .width(Length::Fixed(30.0)) + .center_y() + .center_x(); + + let copy_address_button: Element = Button::new(copy_address_label_container) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::WriteToClipboard(copied_address_value)) + .padding(2) + .into(); + + let address_row = Row::new() + .push(address_label_container) + .push(Space::with_width(Length::Fixed(2.0))) + .push(address_container) + .push(Space::with_width(Length::Fixed(2.0))) + .push(copy_address_button.map(Message::Interaction)); + + let title_container = Container::new( + Column::new() + .push(title_container) + .push(subtitle_row) + .push(address_row), + ) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + let header_row = Row::new() + .push(title_container) + .push(Space::with_width(Length::Fill)) + .push(operations_menu); + + let header_container = Container::new(header_row).padding(iced::Padding::from([ + 0, // top + 0, // right + DEFAULT_PADDING as u16, // bottom + 0, // left + ])); + + let total_value_label = + Text::new(format!("{}:", localized_string("info-confirmed-total"))).size(DEFAULT_FONT_SIZE); + let total_value_label_container = Container::new(total_value_label) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground); + + let total_value = Text::new(total_string).size(DEFAULT_FONT_SIZE); + let total_value_container = Container::new(total_value) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .width(Length::Fill) + .align_x(alignment::Horizontal::Right); + + let total_row = Row::new() + .push(total_value_label_container) + .push(total_value_container) + .width(Length::Fill); + + let awaiting_confirmation_label = Text::new(format!( + "{}:", + localized_string("info-awaiting-confirmation") + )) + .size(DEFAULT_FONT_SIZE); + let awaiting_confirmation_label_container = Container::new(awaiting_confirmation_label) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground); + + let awaiting_confirmation_value = + Text::new(awaiting_confirmation_string).size(DEFAULT_FONT_SIZE); + let awaiting_confirmation_value_container = Container::new(awaiting_confirmation_value) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .width(Length::Fill) + .align_x(alignment::Horizontal::Right); + + let awaiting_confirmation_row = Row::new() + .push(awaiting_confirmation_label_container) + .push(awaiting_confirmation_value_container) + .width(Length::Fill); + + let awaiting_finalization_label = Text::new(format!( + "{}:", + localized_string("info-awaiting-finalization") + )) + .size(DEFAULT_FONT_SIZE); + let awaiting_finalization_label_container = Container::new(awaiting_finalization_label) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground); + + let awaiting_finalization_value = + Text::new(awaiting_finalization_string).size(DEFAULT_FONT_SIZE); + let awaiting_finalization_value_container = Container::new(awaiting_finalization_value) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .width(Length::Fill) + .align_x(alignment::Horizontal::Right); + + let awaiting_finalization_row = Row::new() + .push(awaiting_finalization_label_container) + .push(awaiting_finalization_value_container) + .width(Length::Fill); + + let locked_label = + Text::new(format!("{}:", localized_string("info-locked"))).size(DEFAULT_FONT_SIZE); + let locked_label_container = + Container::new(locked_label).style(grin_gui_core::theme::ContainerStyle::BrightBackground); + + let locked_value = Text::new(locked_string).size(DEFAULT_FONT_SIZE); + let locked_value_container = Container::new(locked_value) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .width(Length::Fill) + .align_x(alignment::Horizontal::Right); + + let locked_row = Row::new() + .push(locked_label_container) + .push(locked_value_container) + .width(Length::Fill); + + let amount_spendable_label = + Text::new(format!("{}:", localized_string("info-amount-spendable"))) + .size(DEFAULT_FONT_SIZE); + let amount_spendable_label_container = Container::new(amount_spendable_label) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground); + + let amount_spendable_value = Text::new(amount_spendable_string).size(DEFAULT_FONT_SIZE); + let amount_spendable_value_container = Container::new(amount_spendable_value) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .width(Length::Fill) + .align_x(alignment::Horizontal::Right); + + let amount_spendable_row = Row::new() + .push(amount_spendable_label_container) + .push(amount_spendable_value_container) + .width(Length::Fill); + + let info_column = Column::new() + .push(total_row) + .push(awaiting_confirmation_row) + .push(awaiting_finalization_row) + .push(locked_row) + .push(amount_spendable_row) + .spacing(7); + + let wallet_info_card_container = Container::new(info_column) + .width(Length::Fixed(240.0)) + .padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + 5.0, // left + ])); + + let mut first_row_container = Row::new() + .push(wallet_info_card_container) + .height(Length::Fixed(120.0)); + + // if there is transaction data, display the balance chart + let mut balance_data = state.tx_list_display_state.balance_data.clone(); + if !balance_data.is_empty() { + // if there is price history data, convert the balance data to the currency + if !state.price_history.is_empty() && currency != Currency::GRIN { + balance_data = balance_data + .iter() + .map(|(date, balance)| { + let price = state.price_history.get(date).unwrap_or(&0.0); + let precision = i32::pow(10, currency.precision() as u32) as f64; + let adjusted_price = f64::trunc(balance * price * precision) / precision; + (date.clone(), adjusted_price) + }) + .collect::>(); + } + + let theme_name = config.theme.clone().unwrap_or("Alliance".to_string()); + let theme = grin_gui_core::theme::Theme::all() + .iter() + .find(|t| t.0 == theme_name) + .unwrap() + .1 + .clone(); + + first_row_container = first_row_container.push(BalanceChart::new( + theme, + balance_data.into_iter().rev(), + state.cursor_index, + state.caption_index, + )); + } + + // Status container element for Node state + let status_container_node_state_label_text = + Text::new(format!("{}: ", localized_string("node-status"))) + .size(DEFAULT_FONT_SIZE) + .height(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Left) + .vertical_alignment(alignment::Vertical::Center); + + let status_container_node_state_text = Text::new(state.parse_node_status()) + .size(DEFAULT_FONT_SIZE) + .height(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Left) + .vertical_alignment(alignment::Vertical::Center); + + // Status container bar at bottom of screen + let status_container_label_text = Text::new(localized_string("wallet-status")) + .size(DEFAULT_FONT_SIZE) + .height(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Right) + .vertical_alignment(alignment::Vertical::Center); + + let status_container_separator_text = Text::new(": ") + .size(DEFAULT_FONT_SIZE) + .height(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Right) + .vertical_alignment(alignment::Vertical::Center); + + let status_container_status_text = Text::new(&state.wallet_status) + .size(DEFAULT_FONT_SIZE) + .height(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Right) + .vertical_alignment(alignment::Vertical::Center); + + let status_container_contents = Row::new() + .push(Space::new( + Length::Fixed(DEFAULT_PADDING), + Length::Fixed(0.0), + )) + .push(status_container_node_state_label_text) + .push(status_container_node_state_text) + .push(Space::new(Length::Fill, Length::Fill)) + .push(status_container_label_text) + .push(status_container_separator_text) + .push(status_container_status_text) + .push(Space::new( + Length::Fixed(DEFAULT_PADDING), + Length::Fixed(0.0), + )); + + let status_container = Container::new(status_container_contents) + .style(grin_gui_core::theme::ContainerStyle::BrightForeground) + .height(Length::Fixed(25.0)) + .width(Length::Fill); + + let status_row = Row::new() + .push(status_container) + .align_items(Alignment::Center) + .spacing(25); + + // Buttons to perform operations go here, but empty container for now + let tx_list_display = + tx_list_display::data_container(config, &state, &state.tx_list_display_state) + .height(Length::Fill); + + // Overall Home screen layout column + let column = Column::new() + .push(header_container) + .push(first_row_container) + .push(Space::with_height(Length::Fixed(DEFAULT_PADDING * 3.0))) + .push(tx_list_display) + .push(Space::with_height(Length::Fixed(DEFAULT_PADDING))) + .push(status_row) + .height(Length::Fill); + + Container::new(column).padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/wallet/operation/mod.rs b/src/gui/element/wallet/operation/mod.rs index 097a35c..c269dc7 100644 --- a/src/gui/element/wallet/operation/mod.rs +++ b/src/gui/element/wallet/operation/mod.rs @@ -4,124 +4,118 @@ pub mod apply_tx_confirm; pub mod chart; pub mod create_tx; pub mod create_tx_contracts; -pub mod show_slatepack; pub mod home; pub mod open; +pub mod show_slatepack; +pub mod tx_detail; +pub mod tx_done; pub mod tx_list; pub mod tx_list_display; -pub mod tx_detail; pub mod tx_proof; -pub mod tx_done; use { - crate::gui::{GrinGui, Message}, - crate::Result, - grin_gui_core::config::{Config, TxMethod}, - grin_gui_core::theme::ColorPalette, - grin_gui_core::theme::{ - Button, Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, - }, - iced::{Command, Length}, + crate::gui::{GrinGui, Message}, + crate::Result, + grin_gui_core::config::{Config, TxMethod}, + grin_gui_core::theme::ColorPalette, + grin_gui_core::theme::{ + Button, Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, + }, + iced::{Command, Length}, }; pub struct StateContainer { - pub mode: Mode, - pub open_state: open::StateContainer, - pub home_state: home::StateContainer, - pub create_tx_state: create_tx::StateContainer, - pub create_tx_contracts_state: create_tx_contracts::StateContainer, - pub show_slatepack_state: show_slatepack::StateContainer, - pub apply_tx_state: apply_tx::StateContainer, - pub tx_detail_state: tx_detail::StateContainer, - pub tx_proof_state: tx_proof::StateContainer, - pub tx_done_state: tx_done::StateContainer, - // When changed to true, this should stay false until a wallet is opened with a password - has_wallet_open_check_failed_one_time: bool, + pub mode: Mode, + pub open_state: open::StateContainer, + pub home_state: home::StateContainer, + pub create_tx_state: create_tx::StateContainer, + pub create_tx_contracts_state: create_tx_contracts::StateContainer, + pub show_slatepack_state: show_slatepack::StateContainer, + pub apply_tx_state: apply_tx::StateContainer, + pub tx_detail_state: tx_detail::StateContainer, + pub tx_proof_state: tx_proof::StateContainer, + pub tx_done_state: tx_done::StateContainer, + // When changed to true, this should stay false until a wallet is opened with a password + has_wallet_open_check_failed_one_time: bool, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Mode { - Open, - Home, - CreateTx, - ApplyTx, - ShowSlatepack, - TxDetail, - TxProof, - TxDone, + Open, + Home, + CreateTx, + ApplyTx, + ShowSlatepack, + TxDetail, + TxProof, + TxDone, } impl Default for StateContainer { - fn default() -> Self { - Self { - mode: Mode::Home, - open_state: Default::default(), - home_state: Default::default(), - create_tx_state: Default::default(), - create_tx_contracts_state: Default::default(), - show_slatepack_state: Default::default(), - apply_tx_state: Default::default(), - tx_detail_state: Default::default(), - tx_proof_state: Default::default(), - tx_done_state: Default::default(), - has_wallet_open_check_failed_one_time: false, - } - } + fn default() -> Self { + Self { + mode: Mode::Home, + open_state: Default::default(), + home_state: Default::default(), + create_tx_state: Default::default(), + create_tx_contracts_state: Default::default(), + show_slatepack_state: Default::default(), + apply_tx_state: Default::default(), + tx_detail_state: Default::default(), + tx_proof_state: Default::default(), + tx_done_state: Default::default(), + has_wallet_open_check_failed_one_time: false, + } + } } impl StateContainer { - pub fn wallet_not_open(&self) -> bool { - self.has_wallet_open_check_failed_one_time - } + pub fn wallet_not_open(&self) -> bool { + self.has_wallet_open_check_failed_one_time + } - pub fn set_wallet_not_open(&mut self) { - self.has_wallet_open_check_failed_one_time = true; - self.mode = Mode::Open; - } + pub fn set_wallet_not_open(&mut self) { + self.has_wallet_open_check_failed_one_time = true; + self.mode = Mode::Open; + } - pub fn clear_wallet_not_open(&mut self) { - self.has_wallet_open_check_failed_one_time = false; - } + pub fn clear_wallet_not_open(&mut self) { + self.has_wallet_open_check_failed_one_time = false; + } } #[derive(Debug, Clone)] pub enum LocalViewInteraction {} pub fn handle_message( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - Ok(Command::none()) + Ok(Command::none()) } pub fn data_container<'a>(state: &'a StateContainer, config: &'a Config) -> Container<'a, Message> { - let content = match state.mode { - Mode::Open => open::data_container(&state.open_state, config), - Mode::Home => home::data_container(config, &state.home_state), - Mode::CreateTx => match config.tx_method { - TxMethod::Legacy => create_tx::data_container(config, &state.create_tx_state), - TxMethod::Contracts => create_tx_contracts::data_container(config, &state.create_tx_contracts_state), - } - Mode::ShowSlatepack => { - show_slatepack::data_container(config, &state.show_slatepack_state) - } - Mode::ApplyTx => apply_tx::data_container(config, &state.apply_tx_state), - Mode::TxDetail => { - tx_detail::data_container(config, &state.tx_detail_state) - } - Mode::TxProof => { - tx_proof::data_container(config, &state.tx_proof_state) - } - Mode::TxDone => { - tx_done::data_container(config, &state.tx_done_state) - } - }; + let content = match state.mode { + Mode::Open => open::data_container(&state.open_state, config), + Mode::Home => home::data_container(config, &state.home_state), + Mode::CreateTx => match config.tx_method { + TxMethod::Legacy => create_tx::data_container(config, &state.create_tx_state), + TxMethod::Contracts => { + create_tx_contracts::data_container(config, &state.create_tx_contracts_state) + } + }, + Mode::ShowSlatepack => show_slatepack::data_container(config, &state.show_slatepack_state), + Mode::ApplyTx => apply_tx::data_container(config, &state.apply_tx_state), + Mode::TxDetail => tx_detail::data_container(config, &state.tx_detail_state), + Mode::TxProof => tx_proof::data_container(config, &state.tx_proof_state), + Mode::TxDone => tx_done::data_container(config, &state.tx_done_state), + }; - let column = Column::new().push(content); + let column = Column::new().push(content); - Container::new(column) - .center_y() - .center_x() - .width(Length::Fill) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + Container::new(column) + .center_y() + .center_x() + .width(Length::Fill) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) } diff --git a/src/gui/element/wallet/operation/open.rs b/src/gui/element/wallet/operation/open.rs index ae41a8f..2ddc073 100644 --- a/src/gui/element/wallet/operation/open.rs +++ b/src/gui/element/wallet/operation/open.rs @@ -4,296 +4,296 @@ use crate::log_error; //use std::path::PathBuf; use { - super::super::super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING}, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - anyhow::Context, - grin_gui_core::config::Config, - grin_gui_core::theme::ColorPalette, - grin_gui_core::theme::{ - Button, Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, - }, - grin_gui_core::{ - node::ChainTypes::Mainnet, node::ChainTypes::Testnet, wallet::WalletInterface, - }, - iced::widget::{button, pick_list, scrollable, text_input, Space}, - iced::{alignment, Alignment, Command, Length}, - std::sync::{Arc, RwLock}, + super::super::super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING}, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + anyhow::Context, + grin_gui_core::config::Config, + grin_gui_core::theme::ColorPalette, + grin_gui_core::theme::{ + Button, Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, + }, + grin_gui_core::{ + node::ChainTypes::Mainnet, node::ChainTypes::Testnet, wallet::WalletInterface, + }, + iced::widget::{button, pick_list, scrollable, text_input, Space}, + iced::{alignment, Alignment, Command, Length}, + std::sync::{Arc, RwLock}, }; static INPUT_WIDTH: f32 = 212.0; static UNIT_SPACE: f32 = 15.0; pub struct StateContainer { - pub password_state: PasswordState, - pub wallet_message: String, + pub password_state: PasswordState, + pub wallet_message: String, } impl Default for StateContainer { - fn default() -> Self { - Self { - password_state: Default::default(), - wallet_message: localized_string("open-wallet-password"), - } - } + fn default() -> Self { + Self { + password_state: Default::default(), + wallet_message: localized_string("open-wallet-password"), + } + } } #[derive(Debug, Clone)] pub struct PasswordState { - pub input_value: String, + pub input_value: String, } impl Default for PasswordState { - fn default() -> Self { - PasswordState { - input_value: Default::default(), - } - } + fn default() -> Self { + PasswordState { + input_value: Default::default(), + } + } } #[derive(Debug, Clone)] pub enum LocalViewInteraction { - //TODO: ZeroingString these - PasswordInput(String), - PasswordInputEnterPressed, - OpenWallet, - CancelOpenWallet, - WalletOpenedOkay, - WalletOpenError(Arc>>), + //TODO: ZeroingString these + PasswordInput(String), + PasswordInputEnterPressed, + OpenWallet, + CancelOpenWallet, + WalletOpenedOkay, + WalletOpenError(Arc>>), } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.wallet_state.operation_state.open_state; - match message { - LocalViewInteraction::CancelOpenWallet => { - // TODO @sheldonth do we need to "close" any wallet interface? - // @sheldonth if the wallet we're currently prompted for uses - // the node it needs to be shutdown. - - // return user to wallet list - grin_gui.wallet_state.mode = element::wallet::Mode::Init; - grin_gui.wallet_state.setup_state.mode = element::wallet::setup::Mode::ListWallets; - - // reset user input values - grin_gui.wallet_state.operation_state.open_state = Default::default(); - } - LocalViewInteraction::PasswordInput(password) => { - state.password_state.input_value = password; - state.wallet_message = localized_string("open-wallet-password"); - } - LocalViewInteraction::PasswordInputEnterPressed => { - //state.password_state.input_state.unfocus(); - } - LocalViewInteraction::OpenWallet => { - grin_gui.error.take(); - - log::debug!("setup::wallet::operation::open::OpenWallet"); - - let password = state.password_state.input_value.clone(); - let wallet_interface = grin_gui.wallet_interface.clone(); - let running_chain_type = grin_gui.node_interface.read().unwrap().chain_type.unwrap(); - let wallet_index = grin_gui.config.current_wallet_index.unwrap(); - let current_wallet = &grin_gui.config.wallets[wallet_index]; - let wallet_chain_type = current_wallet.chain_type; - - if current_wallet.use_embedded_node { - // restart embedded server is chain types differ - if running_chain_type != wallet_chain_type { - let mut node = grin_gui.node_interface.write().unwrap(); - node.restart_server(wallet_chain_type); - } - - let node_interface = grin_gui.node_interface.read().unwrap(); - if let Some(c) = &node_interface.config { - if let Some(m) = &c.members { - WalletInterface::set_use_embedded_node(wallet_interface.clone(), true); - let mut w = wallet_interface.write().unwrap(); - w.check_node_foreign_api_secret_path = - m.server.foreign_api_secret_path.clone(); - } - } - } - let tld = current_wallet.tld.clone().unwrap(); - let fut = move || { - WalletInterface::open_wallet( - wallet_interface, - password.clone(), - tld, - current_wallet.chain_type, - ) - }; - - return Ok(Command::perform(fut(), |r| { - match r.context("Failed to Open Wallet") { - Ok(()) => { - Message::Interaction(Interaction::WalletOperationOpenViewInteraction( - LocalViewInteraction::WalletOpenedOkay, - )) - } - Err(e) => { - Message::Interaction(Interaction::WalletOperationOpenViewInteraction( - LocalViewInteraction::WalletOpenError(Arc::new(RwLock::new(Some(e)))), - )) - } - } - })); - } - LocalViewInteraction::WalletOpenedOkay => { - grin_gui - .wallet_state - .operation_state - .clear_wallet_not_open(); - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::Home; - - // reset user input values - grin_gui.wallet_state.operation_state.open_state = Default::default(); - } - - LocalViewInteraction::WalletOpenError(err) => { - //grin_gui.error = err.write().unwrap().take(); - // display wallet message to user - state.wallet_message = localized_string("open-wallet-error"); - err.write() - .unwrap() - .take() - .and_then(|e| Some(log_error(&e))); - } - } - Ok(Command::none()) + let state = &mut grin_gui.wallet_state.operation_state.open_state; + match message { + LocalViewInteraction::CancelOpenWallet => { + // TODO @sheldonth do we need to "close" any wallet interface? + // @sheldonth if the wallet we're currently prompted for uses + // the node it needs to be shutdown. + + // return user to wallet list + grin_gui.wallet_state.mode = element::wallet::Mode::Init; + grin_gui.wallet_state.setup_state.mode = element::wallet::setup::Mode::ListWallets; + + // reset user input values + grin_gui.wallet_state.operation_state.open_state = Default::default(); + } + LocalViewInteraction::PasswordInput(password) => { + state.password_state.input_value = password; + state.wallet_message = localized_string("open-wallet-password"); + } + LocalViewInteraction::PasswordInputEnterPressed => { + //state.password_state.input_state.unfocus(); + } + LocalViewInteraction::OpenWallet => { + grin_gui.error.take(); + + log::debug!("setup::wallet::operation::open::OpenWallet"); + + let password = state.password_state.input_value.clone(); + let wallet_interface = grin_gui.wallet_interface.clone(); + let running_chain_type = grin_gui.node_interface.read().unwrap().chain_type.unwrap(); + let wallet_index = grin_gui.config.current_wallet_index.unwrap(); + let current_wallet = &grin_gui.config.wallets[wallet_index]; + let wallet_chain_type = current_wallet.chain_type; + + if current_wallet.use_embedded_node { + // restart embedded server is chain types differ + if running_chain_type != wallet_chain_type { + let mut node = grin_gui.node_interface.write().unwrap(); + node.restart_server(wallet_chain_type); + } + + let node_interface = grin_gui.node_interface.read().unwrap(); + if let Some(c) = &node_interface.config { + if let Some(m) = &c.members { + WalletInterface::set_use_embedded_node(wallet_interface.clone(), true); + let mut w = wallet_interface.write().unwrap(); + w.check_node_foreign_api_secret_path = + m.server.foreign_api_secret_path.clone(); + } + } + } + let tld = current_wallet.tld.clone().unwrap(); + let fut = move || { + WalletInterface::open_wallet( + wallet_interface, + password.clone(), + tld, + current_wallet.chain_type, + ) + }; + + return Ok(Command::perform(fut(), |r| { + match r.context("Failed to Open Wallet") { + Ok(()) => { + Message::Interaction(Interaction::WalletOperationOpenViewInteraction( + LocalViewInteraction::WalletOpenedOkay, + )) + } + Err(e) => { + Message::Interaction(Interaction::WalletOperationOpenViewInteraction( + LocalViewInteraction::WalletOpenError(Arc::new(RwLock::new(Some(e)))), + )) + } + } + })); + } + LocalViewInteraction::WalletOpenedOkay => { + grin_gui + .wallet_state + .operation_state + .clear_wallet_not_open(); + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::Home; + + // reset user input values + grin_gui.wallet_state.operation_state.open_state = Default::default(); + } + + LocalViewInteraction::WalletOpenError(err) => { + //grin_gui.error = err.write().unwrap().take(); + // display wallet message to user + state.wallet_message = localized_string("open-wallet-error"); + err.write() + .unwrap() + .take() + .and_then(|e| Some(log_error(&e))); + } + } + Ok(Command::none()) } pub fn data_container<'a>(state: &'a StateContainer, config: &Config) -> Container<'a, Message> { - let mut display_name_string = match config.current_wallet_index { - Some(index) => config.wallets[index].display_name.clone(), - None => "".to_owned(), - }; - - // if there is no wallet display name - if display_name_string.is_empty() { - display_name_string = localized_string("open-wallet"); - } - - let display_name = Text::new(display_name_string) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - let display_name_container = - Container::new(display_name).style(grin_gui_core::theme::ContainerStyle::BrightBackground); - - let password_column = { - let password_input = TextInput::new( - // &mut state.password_state.input_state, - &localized_string("password")[..], - &state.password_state.input_value, - ) - .on_submit(Interaction::WalletOperationOpenViewInteraction( - LocalViewInteraction::PasswordInputEnterPressed, - )) - .on_input(|s| { - Interaction::WalletOperationOpenViewInteraction(LocalViewInteraction::PasswordInput(s)) - }) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery) - .password(); - - let password_input: Element = password_input.into(); - - let password_input_col = Column::new() - .push(password_input.map(Message::Interaction)) - .spacing(DEFAULT_PADDING) - .align_items(Alignment::Center); - - Column::new().push(password_input_col) - }; - - let description = Text::new(state.wallet_message.clone()) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - - let description_container = Container::new(description) - .width(Length::Fixed(INPUT_WIDTH)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) - .padding(6); - - let submit_button_label_container = - Container::new(Text::new(localized_string("open")).size(DEFAULT_FONT_SIZE)) - .center_x() - .center_y() - .width(Length::Fixed(BUTTON_WIDTH)) - .height(Length::Fixed(BUTTON_HEIGHT)) - .align_x(alignment::Horizontal::Center); - - let mut submit_button = Button::new(submit_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary); - - submit_button = submit_button.on_press(Interaction::WalletOperationOpenViewInteraction( - LocalViewInteraction::OpenWallet, - )); - - let submit_button: Element = submit_button.into(); - - let cancel_button_label_container = - Container::new(Text::new(localized_string("cancel")).size(DEFAULT_FONT_SIZE)) - .center_x() - .center_y() - .width(Length::Fixed(BUTTON_WIDTH)) - .height(Length::Fixed(BUTTON_HEIGHT)) - .align_x(alignment::Horizontal::Center); - - let mut cancel_button = Button::new( - // &mut state.cancel_button_state, - cancel_button_label_container, - ) - .style(grin_gui_core::theme::ButtonStyle::Primary); - - cancel_button = cancel_button.on_press(Interaction::WalletOperationOpenViewInteraction( - LocalViewInteraction::CancelOpenWallet, - )); - - // give our buttons a nice double bordered look to match toolbar buttons - let submit_button: Element = submit_button.into(); - let submit_container = Container::new(submit_button.map(Message::Interaction)).padding(1); - let submit_container = Container::new(submit_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let cancel_button: Element = cancel_button.into(); - let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); - let cancel_container = Container::new(cancel_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let button_row = Row::new() - .push(submit_container) - .push(Space::with_width(Length::Fixed(UNIT_SPACE))) - .push(cancel_container); - - let input_container = Container::new( - Column::new() - .push(description_container) - .push(Space::with_height(Length::Fixed(UNIT_SPACE / 2.0))) - .push(password_column), - ) - .width(Length::Fixed(INPUT_WIDTH)); - - let column = Column::new() - .push(display_name_container) - .push(Space::with_height(Length::Fixed(UNIT_SPACE))) - .push(input_container) - .push(Space::with_height(Length::Fixed( - UNIT_SPACE + DEFAULT_PADDING, - ))) - .push(button_row) - .align_items(Alignment::Center); - - Container::new(column) - .center_y() - .center_x() - .width(Length::Fill) - .height(Length::Fill) + let mut display_name_string = match config.current_wallet_index { + Some(index) => config.wallets[index].display_name.clone(), + None => "".to_owned(), + }; + + // if there is no wallet display name + if display_name_string.is_empty() { + display_name_string = localized_string("open-wallet"); + } + + let display_name = Text::new(display_name_string) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + let display_name_container = + Container::new(display_name).style(grin_gui_core::theme::ContainerStyle::BrightBackground); + + let password_column = { + let password_input = TextInput::new( + // &mut state.password_state.input_state, + &localized_string("password")[..], + &state.password_state.input_value, + ) + .on_submit(Interaction::WalletOperationOpenViewInteraction( + LocalViewInteraction::PasswordInputEnterPressed, + )) + .on_input(|s| { + Interaction::WalletOperationOpenViewInteraction(LocalViewInteraction::PasswordInput(s)) + }) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery) + .password(); + + let password_input: Element = password_input.into(); + + let password_input_col = Column::new() + .push(password_input.map(Message::Interaction)) + .spacing(DEFAULT_PADDING) + .align_items(Alignment::Center); + + Column::new().push(password_input_col) + }; + + let description = Text::new(state.wallet_message.clone()) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + + let description_container = Container::new(description) + .width(Length::Fixed(INPUT_WIDTH)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + .padding(6); + + let submit_button_label_container = + Container::new(Text::new(localized_string("open")).size(DEFAULT_FONT_SIZE)) + .center_x() + .center_y() + .width(Length::Fixed(BUTTON_WIDTH)) + .height(Length::Fixed(BUTTON_HEIGHT)) + .align_x(alignment::Horizontal::Center); + + let mut submit_button = Button::new(submit_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary); + + submit_button = submit_button.on_press(Interaction::WalletOperationOpenViewInteraction( + LocalViewInteraction::OpenWallet, + )); + + let submit_button: Element = submit_button.into(); + + let cancel_button_label_container = + Container::new(Text::new(localized_string("cancel")).size(DEFAULT_FONT_SIZE)) + .center_x() + .center_y() + .width(Length::Fixed(BUTTON_WIDTH)) + .height(Length::Fixed(BUTTON_HEIGHT)) + .align_x(alignment::Horizontal::Center); + + let mut cancel_button = Button::new( + // &mut state.cancel_button_state, + cancel_button_label_container, + ) + .style(grin_gui_core::theme::ButtonStyle::Primary); + + cancel_button = cancel_button.on_press(Interaction::WalletOperationOpenViewInteraction( + LocalViewInteraction::CancelOpenWallet, + )); + + // give our buttons a nice double bordered look to match toolbar buttons + let submit_button: Element = submit_button.into(); + let submit_container = Container::new(submit_button.map(Message::Interaction)).padding(1); + let submit_container = Container::new(submit_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let cancel_button: Element = cancel_button.into(); + let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); + let cancel_container = Container::new(cancel_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let button_row = Row::new() + .push(submit_container) + .push(Space::with_width(Length::Fixed(UNIT_SPACE))) + .push(cancel_container); + + let input_container = Container::new( + Column::new() + .push(description_container) + .push(Space::with_height(Length::Fixed(UNIT_SPACE / 2.0))) + .push(password_column), + ) + .width(Length::Fixed(INPUT_WIDTH)); + + let column = Column::new() + .push(display_name_container) + .push(Space::with_height(Length::Fixed(UNIT_SPACE))) + .push(input_container) + .push(Space::with_height(Length::Fixed( + UNIT_SPACE + DEFAULT_PADDING, + ))) + .push(button_row) + .align_items(Alignment::Center); + + Container::new(column) + .center_y() + .center_x() + .width(Length::Fill) + .height(Length::Fill) } diff --git a/src/gui/element/wallet/operation/show_slatepack.rs b/src/gui/element/wallet/operation/show_slatepack.rs index 45c78ed..f7b3dd6 100644 --- a/src/gui/element/wallet/operation/show_slatepack.rs +++ b/src/gui/element/wallet/operation/show_slatepack.rs @@ -1,215 +1,214 @@ use { - super::super::super::{ - BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, - SMALLER_FONT_SIZE, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - grin_gui_core::config::Config, - grin_gui_core::theme::ColorPalette, - grin_gui_core::theme::{ - Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, - }, - iced::widget::{button, pick_list, scrollable, text_input, Button, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - iced_aw::Card, + super::super::super::{ + BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, + SMALLER_FONT_SIZE, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + grin_gui_core::config::Config, + grin_gui_core::theme::ColorPalette, + grin_gui_core::theme::{ + Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, + }, + iced::widget::{button, pick_list, scrollable, text_input, Button, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + iced_aw::Card, }; pub struct StateContainer { - // Encrypted slate to send to recipient - pub encrypted_slate: Option, - // Where the 'submit' or back button leads to - pub submit_mode: Option, - // Label to display as title - pub title_label: String, - // description - pub desc: String, + // Encrypted slate to send to recipient + pub encrypted_slate: Option, + // Where the 'submit' or back button leads to + pub submit_mode: Option, + // Label to display as title + pub title_label: String, + // description + pub desc: String, } impl Default for StateContainer { - fn default() -> Self { - Self { - encrypted_slate: Default::default(), - submit_mode: None, - title_label: localized_string("tx-view"), - desc: localized_string("tx-view-desc") - } - } + fn default() -> Self { + Self { + encrypted_slate: Default::default(), + submit_mode: None, + title_label: localized_string("tx-view"), + desc: localized_string("tx-view-desc"), + } + } } impl StateContainer { - pub fn reset_defaults(&mut self) { - self.title_label = localized_string("tx-view"); - self.desc = localized_string("tx-view-desc"); - } + pub fn reset_defaults(&mut self) { + self.title_label = localized_string("tx-view"); + self.desc = localized_string("tx-view-desc"); + } } #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Submit, + Submit, } pub fn handle_message( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.wallet_state.operation_state.show_slatepack_state; - match message { - LocalViewInteraction::Submit => { - state.encrypted_slate = None; - state.reset_defaults(); - if let Some(ref m) = state.submit_mode { - grin_gui.wallet_state.operation_state.mode = m.clone(); - } else { - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::Home; - } - state.submit_mode = None; - } - } - Ok(Command::none()) + let state = &mut grin_gui.wallet_state.operation_state.show_slatepack_state; + match message { + LocalViewInteraction::Submit => { + state.encrypted_slate = None; + state.reset_defaults(); + if let Some(ref m) = state.submit_mode { + grin_gui.wallet_state.operation_state.mode = m.clone(); + } else { + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::Home; + } + state.submit_mode = None; + } + } + Ok(Command::none()) } pub fn data_container<'a>( - _config: &'a Config, - state: &'a StateContainer, + _config: &'a Config, + state: &'a StateContainer, ) -> Container<'a, Message> { - // Title row - let title = Text::new(state.title_label.clone()) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - - let title_container = Container::new(title) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 2, // top - 0, // right - 2, // bottom - 5, // left - ])); - - // push more items on to header here: e.g. other buttons, things that belong on the header - let header_row = Row::new().push(title_container); - - let header_container = Container::new(header_row).padding(iced::Padding::from([ - 0, // top - 0, // right - DEFAULT_PADDING as u16, // bottom - 0, // left - ])); - - let description = Text::new(state.desc.clone()) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - let description_container = - Container::new(description).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let card_contents = match &state.encrypted_slate { - Some(s) => s.to_owned(), - None => "".to_owned() - }; - - let encrypted_slate_card = Card::new( - Text::new(localized_string("tx-paste-success-title")) - .size(DEFAULT_HEADER_FONT_SIZE), - Text::new(card_contents.clone()).size(DEFAULT_FONT_SIZE), - ) - .foot( - Column::new() - .spacing(10) - .padding(5) - .width(Length::Fill) - .align_items(Alignment::Center) - .push( - Button::new( - Text::new(localized_string("copy-to-clipboard")) - .size(SMALLER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center), - ) - .style(grin_gui_core::theme::ButtonStyle::NormalText) - .on_press(Message::Interaction(Interaction::WriteToClipboard( - card_contents.clone(), - ))), - ), - ) - .max_width(400.0) - .style(grin_gui_core::theme::CardStyle::Normal); - - let unit_spacing = 15.0; - - let button_height = Length::Fixed(BUTTON_HEIGHT); - let button_width = Length::Fixed(BUTTON_WIDTH); - - let cancel_button_label_container = - Container::new(Text::new(localized_string("ok-caps")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let cancel_button: Element = Button::new(cancel_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletOperationShowSlatepackViewInteraction( - LocalViewInteraction::Submit, - )) - .into(); - - let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); - let cancel_container = Container::new(cancel_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let unit_spacing = 15.0; - let button_row = Row::new().push(cancel_container); - - let column = Column::new() - .push(description_container) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 5.0), - )) - .push(encrypted_slate_card) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 10.0), - )) - .push(button_row) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 10.0), - )); - - let form_container = Container::new(column) - .width(Length::Fill) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - // form container should be scrollable in tiny windows - let scrollable = Scrollable::new(form_container) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); - - let content = Container::new(scrollable) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let wrapper_column = Column::new() - .height(Length::Fill) - .push(header_container) - .push(content); - - // Returns the final container. - Container::new(wrapper_column).padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + // Title row + let title = Text::new(state.title_label.clone()) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + + let title_container = Container::new(title) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 2, // top + 0, // right + 2, // bottom + 5, // left + ])); + + // push more items on to header here: e.g. other buttons, things that belong on the header + let header_row = Row::new().push(title_container); + + let header_container = Container::new(header_row).padding(iced::Padding::from([ + 0, // top + 0, // right + DEFAULT_PADDING as u16, // bottom + 0, // left + ])); + + let description = Text::new(state.desc.clone()) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + let description_container = + Container::new(description).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let card_contents = match &state.encrypted_slate { + Some(s) => s.to_owned(), + None => "".to_owned(), + }; + + let encrypted_slate_card = Card::new( + Text::new(localized_string("tx-paste-success-title")).size(DEFAULT_HEADER_FONT_SIZE), + Text::new(card_contents.clone()).size(DEFAULT_FONT_SIZE), + ) + .foot( + Column::new() + .spacing(10) + .padding(5) + .width(Length::Fill) + .align_items(Alignment::Center) + .push( + Button::new( + Text::new(localized_string("copy-to-clipboard")) + .size(SMALLER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center), + ) + .style(grin_gui_core::theme::ButtonStyle::NormalText) + .on_press(Message::Interaction(Interaction::WriteToClipboard( + card_contents.clone(), + ))), + ), + ) + .max_width(400.0) + .style(grin_gui_core::theme::CardStyle::Normal); + + let unit_spacing = 15.0; + + let button_height = Length::Fixed(BUTTON_HEIGHT); + let button_width = Length::Fixed(BUTTON_WIDTH); + + let cancel_button_label_container = + Container::new(Text::new(localized_string("ok-caps")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let cancel_button: Element = Button::new(cancel_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletOperationShowSlatepackViewInteraction( + LocalViewInteraction::Submit, + )) + .into(); + + let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); + let cancel_container = Container::new(cancel_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let unit_spacing = 15.0; + let button_row = Row::new().push(cancel_container); + + let column = Column::new() + .push(description_container) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 5.0), + )) + .push(encrypted_slate_card) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 10.0), + )) + .push(button_row) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 10.0), + )); + + let form_container = Container::new(column) + .width(Length::Fill) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + // form container should be scrollable in tiny windows + let scrollable = Scrollable::new(form_container) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); + + let content = Container::new(scrollable) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let wrapper_column = Column::new() + .height(Length::Fill) + .push(header_container) + .push(content); + + // Returns the final container. + Container::new(wrapper_column).padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/wallet/operation/tx_detail.rs b/src/gui/element/wallet/operation/tx_detail.rs index b99a5a4..6c01635 100644 --- a/src/gui/element/wallet/operation/tx_detail.rs +++ b/src/gui/element/wallet/operation/tx_detail.rs @@ -2,9 +2,9 @@ use super::tx_list::{self, ExpandType}; use crate::log_error; use async_std::prelude::FutureExt; use grin_gui_core::{ - config::Config, - error::GrinWalletInterfaceError, - wallet::{TxLogEntry, TxLogEntryType}, + config::Config, + error::GrinWalletInterfaceError, + wallet::{TxLogEntry, TxLogEntryType}, }; use grin_gui_widgets::widget::header; use iced_aw::Card; @@ -16,40 +16,40 @@ use std::path::PathBuf; use super::tx_list::{HeaderState, TxList}; use { - super::super::super::{ - BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, - SMALLER_FONT_SIZE, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - anyhow::Context, - grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, - TextInput, - }, - grin_gui_core::wallet::{InitTxArgs, Slate, StatusMessage, WalletInfo, WalletInterface}, - grin_gui_core::{ - node::{amount_from_hr_string, amount_to_hr_string}, - theme::{ButtonStyle, ColorPalette, ContainerStyle}, - }, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - serde::{Deserialize, Serialize}, - std::sync::{Arc, RwLock}, + super::super::super::{ + BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, + SMALLER_FONT_SIZE, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + anyhow::Context, + grin_gui_core::theme::{ + Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, + TextInput, + }, + grin_gui_core::wallet::{InitTxArgs, Slate, StatusMessage, WalletInfo, WalletInterface}, + grin_gui_core::{ + node::{amount_from_hr_string, amount_to_hr_string}, + theme::{ButtonStyle, ColorPalette, ContainerStyle}, + }, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + serde::{Deserialize, Serialize}, + std::sync::{Arc, RwLock}, }; pub struct StateContainer { - // Transaction that we're viewing - pub current_tx: Option, + // Transaction that we're viewing + pub current_tx: Option, } impl Default for StateContainer { - fn default() -> Self { - Self { - current_tx: Default::default(), - } - } + fn default() -> Self { + Self { + current_tx: Default::default(), + } + } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -57,333 +57,361 @@ pub enum Action {} #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Back, + Back, } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.wallet_state.operation_state.create_tx_state; + let state = &mut grin_gui.wallet_state.operation_state.create_tx_state; - match message { - LocalViewInteraction::Back => { - log::debug!("Interaction::WalletOperationTxDetailViewInteraction(Back)"); - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::Home; - } - } + match message { + LocalViewInteraction::Back => { + log::debug!("Interaction::WalletOperationTxDetailViewInteraction(Back)"); + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::Home; + } + } - Ok(Command::none()) + Ok(Command::none()) } pub fn data_container<'a>(config: &'a Config, state: &'a StateContainer) -> Container<'a, Message> { - // Title row - let title = Text::new(localized_string("tx-details-title")) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); + // Title row + let title = Text::new(localized_string("tx-details-title")) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); - let title_container = Container::new(title) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 2, // top - 0, // right - 2, // bottom - 5, // left - ])); + let title_container = Container::new(title) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 2, // top + 0, // right + 2, // bottom + 5, // left + ])); - let header_row = Row::new().push(title_container); + let header_row = Row::new().push(title_container); - let header_container = Container::new(header_row).padding(iced::Padding::from([ - 0, // top - 0, // right - DEFAULT_PADDING as u16, // bottom - 0, // left - ])); + let header_container = Container::new(header_row).padding(iced::Padding::from([ + 0, // top + 0, // right + DEFAULT_PADDING as u16, // bottom + 0, // left + ])); - let unit_spacing = 15.0; - let row_spacing = 5.0; + let unit_spacing = 15.0; + let row_spacing = 5.0; - let button_height = Length::Fixed(BUTTON_HEIGHT); - let button_width = Length::Fixed(BUTTON_WIDTH); + let button_height = Length::Fixed(BUTTON_HEIGHT); + let button_width = Length::Fixed(BUTTON_WIDTH); - let mut column = Column::new(); + let mut column = Column::new(); - if let Some(ref tx) = state.current_tx { - // ID - let id_label = Text::new(format!("{}: ", localized_string("tx-id"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + if let Some(ref tx) = state.current_tx { + // ID + let id_label = Text::new(format!("{}: ", localized_string("tx-id"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let id_label_container = - Container::new(id_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let id_label_container = + Container::new(id_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let id_value = Text::new(format!("{}", tx.id)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + let id_value = Text::new(format!("{}", tx.id)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let id_value_container = - Container::new(id_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let id_value_container = + Container::new(id_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let id_row = Row::new().push(id_label_container).push(id_value_container); + let id_row = Row::new().push(id_label_container).push(id_value_container); - column = column - .push(id_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + column = column + .push(id_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - // Tx Type - let tx_type_label = Text::new(format!("{}: ", localized_string("tx-type"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + // Tx Type + let tx_type_label = Text::new(format!("{}: ", localized_string("tx-type"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let tx_type_label_container = - Container::new(tx_type_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let tx_type_label_container = Container::new(tx_type_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let tx_type_value = Text::new(format!("{}", tx.tx_type)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + let tx_type_value = Text::new(format!("{}", tx.tx_type)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let tx_type_value_container = - Container::new(tx_type_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let tx_type_value_container = Container::new(tx_type_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let tx_type_row = Row::new().push(tx_type_label_container).push(tx_type_value_container); + let tx_type_row = Row::new() + .push(tx_type_label_container) + .push(tx_type_value_container); - column = column - .push(tx_type_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + column = column + .push(tx_type_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + // UUID + let shared_tx_id_label = Text::new(format!("{}: ", localized_string("tx-shared-id"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - // UUID - let shared_tx_id_label = Text::new(format!("{}: ", localized_string("tx-shared-id"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + let shared_tx_id_label_container = Container::new(shared_tx_id_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let shared_tx_id_label_container = - Container::new(shared_tx_id_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let uuid = match tx.tx_slate_id { - Some(u) => u.to_string(), - None => "None".to_owned(), - }; - - let shared_tx_id_value = Text::new(format!("{}", uuid)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let shared_tx_id_value_container = - Container::new(shared_tx_id_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let shared_tx_id_row = Row::new().push(shared_tx_id_label_container).push(shared_tx_id_value_container); - - column = column - .push(shared_tx_id_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - - // Creation Time - let tx_creation_time_label = Text::new(format!("{}: ", localized_string("tx-creation-time"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let tx_creation_time_label_container = - Container::new(tx_creation_time_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let tx_creation_time_value = Text::new(format!("{}", tx.creation_ts)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let tx_creation_time_value_container = - Container::new(tx_creation_time_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let tx_creation_time_row = Row::new().push(tx_creation_time_label_container).push(tx_creation_time_value_container); - - column = column - .push(tx_creation_time_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - - // TTL Cutoff Height - let ttl_cutoff_label = Text::new(format!("{}: ", localized_string("tx-ttl-cutoff"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let ttl_cutoff_label_container = - Container::new(ttl_cutoff_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let ttl = match tx.ttl_cutoff_height { - Some(u) => u.to_string(), - None => "None".to_owned(), - }; - - let ttl_cutoff_value = Text::new(format!("{}", ttl)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let ttl_cutoff_value_container = - Container::new(ttl_cutoff_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let ttl_cutoff_row = Row::new().push(ttl_cutoff_label_container).push(ttl_cutoff_value_container); - - column = column - .push(ttl_cutoff_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - - // Confirmed - let confirmed_label = Text::new(format!("{}: ", localized_string("tx-is-confirmed"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let confirmed_label_container = - Container::new(confirmed_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let confirmed_value = Text::new(format!("{}", tx.confirmed)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let confirmed_value_container = - Container::new(confirmed_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let confirmed_row = Row::new().push(confirmed_label_container).push(confirmed_value_container); - - column = column - .push(confirmed_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - - // Confirmation Time - let tx_confirmation_time_label = Text::new(format!("{}: ", localized_string("tx-confirmation-time"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let tx_confirmation_time_label_container = - Container::new(tx_confirmation_time_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let uuid = match tx.tx_slate_id { + Some(u) => u.to_string(), + None => "None".to_owned(), + }; - let time = match tx.confirmation_ts { - Some(u) => u.to_string(), - None => "None".to_owned(), - }; + let shared_tx_id_value = Text::new(format!("{}", uuid)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let tx_confirmation_time_value = Text::new(format!("{}", time)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + let shared_tx_id_value_container = Container::new(shared_tx_id_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let tx_confirmation_time_value_container = - Container::new(tx_confirmation_time_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let shared_tx_id_row = Row::new() + .push(shared_tx_id_label_container) + .push(shared_tx_id_value_container); - let tx_confirmation_time_row = Row::new().push(tx_confirmation_time_label_container).push(tx_confirmation_time_value_container); - column = column - .push(tx_confirmation_time_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + column = column + .push(shared_tx_id_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - // Number of Inputs - let tx_num_inputs_label = Text::new(format!("{}: ", localized_string("tx-num-inputs"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + // Creation Time + let tx_creation_time_label = + Text::new(format!("{}: ", localized_string("tx-creation-time"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let tx_num_inputs_label_container = - Container::new(tx_num_inputs_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let tx_creation_time_label_container = Container::new(tx_creation_time_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let tx_num_inputs_value = Text::new(format!("{}", tx.num_inputs)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + let tx_creation_time_value = Text::new(format!("{}", tx.creation_ts)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let tx_num_inputs_value_container = - Container::new(tx_num_inputs_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let tx_creation_time_value_container = Container::new(tx_creation_time_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let tx_num_inputs_row = Row::new().push(tx_num_inputs_label_container).push(tx_num_inputs_value_container); - column = column - .push(tx_num_inputs_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + let tx_creation_time_row = Row::new() + .push(tx_creation_time_label_container) + .push(tx_creation_time_value_container); - // Number of Outputs - let tx_num_outputs_label = Text::new(format!("{}: ", localized_string("tx-num-outputs"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + column = column + .push(tx_creation_time_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - let tx_num_outputs_label_container = - Container::new(tx_num_outputs_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + // TTL Cutoff Height + let ttl_cutoff_label = Text::new(format!("{}: ", localized_string("tx-ttl-cutoff"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let tx_num_outputs_value = Text::new(format!("{}", tx.num_outputs)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + let ttl_cutoff_label_container = Container::new(ttl_cutoff_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let tx_num_outputs_value_container = - Container::new(tx_num_outputs_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let ttl = match tx.ttl_cutoff_height { + Some(u) => u.to_string(), + None => "None".to_owned(), + }; - let tx_num_outputs_row = Row::new().push(tx_num_outputs_label_container).push(tx_num_outputs_value_container); - column = column - .push(tx_num_outputs_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + let ttl_cutoff_value = Text::new(format!("{}", ttl)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - // Amount Credited - let tx_amount_credited_label = Text::new(format!("{}: ", localized_string("tx-amount-credited"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + let ttl_cutoff_value_container = Container::new(ttl_cutoff_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let tx_amount_credited_label_container = - Container::new(tx_amount_credited_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let ttl_cutoff_row = Row::new() + .push(ttl_cutoff_label_container) + .push(ttl_cutoff_value_container); - let tx_amount_credited_value = Text::new(format!("{}", amount_to_hr_string(tx.amount_credited, true))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + column = column + .push(ttl_cutoff_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - let tx_amount_credited_value_container = - Container::new(tx_amount_credited_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + // Confirmed + let confirmed_label = Text::new(format!("{}: ", localized_string("tx-is-confirmed"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let tx_amount_credited_row = Row::new().push(tx_amount_credited_label_container).push(tx_amount_credited_value_container); - column = column - .push(tx_amount_credited_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + let confirmed_label_container = Container::new(confirmed_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - // Amount Debited - let tx_amount_debited_label = Text::new(format!("{}: ", localized_string("tx-amount-debited"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + let confirmed_value = Text::new(format!("{}", tx.confirmed)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let tx_amount_debited_label_container = - Container::new(tx_amount_debited_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let confirmed_value_container = Container::new(confirmed_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let tx_amount_debited_value = Text::new(format!("{}", amount_to_hr_string(tx.amount_debited, true))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + let confirmed_row = Row::new() + .push(confirmed_label_container) + .push(confirmed_value_container); - let tx_amount_debited_value_container = - Container::new(tx_amount_debited_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + column = column + .push(confirmed_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - let tx_amount_debited_row = Row::new().push(tx_amount_debited_label_container).push(tx_amount_debited_value_container); - column = column - .push(tx_amount_debited_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + // Confirmation Time + let tx_confirmation_time_label = + Text::new(format!("{}: ", localized_string("tx-confirmation-time"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - // Fee - let tx_fee_label = Text::new(format!("{}: ", localized_string("tx-fee"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + let tx_confirmation_time_label_container = Container::new(tx_confirmation_time_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let tx_fee_label_container = - Container::new(tx_fee_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let time = match tx.confirmation_ts { + Some(u) => u.to_string(), + None => "None".to_owned(), + }; - let fee = match tx.fee { - Some(u) => format!("{}", amount_to_hr_string(u.fee(), true)), - None => "None".to_owned(), - }; + let tx_confirmation_time_value = Text::new(format!("{}", time)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tx_confirmation_time_value_container = Container::new(tx_confirmation_time_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let tx_confirmation_time_row = Row::new() + .push(tx_confirmation_time_label_container) + .push(tx_confirmation_time_value_container); + column = column + .push(tx_confirmation_time_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + + // Number of Inputs + let tx_num_inputs_label = Text::new(format!("{}: ", localized_string("tx-num-inputs"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tx_num_inputs_label_container = Container::new(tx_num_inputs_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let tx_num_inputs_value = Text::new(format!("{}", tx.num_inputs)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tx_num_inputs_value_container = Container::new(tx_num_inputs_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let tx_num_inputs_row = Row::new() + .push(tx_num_inputs_label_container) + .push(tx_num_inputs_value_container); + column = column + .push(tx_num_inputs_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + + // Number of Outputs + let tx_num_outputs_label = Text::new(format!("{}: ", localized_string("tx-num-outputs"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tx_num_outputs_label_container = Container::new(tx_num_outputs_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let tx_num_outputs_value = Text::new(format!("{}", tx.num_outputs)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tx_num_outputs_value_container = Container::new(tx_num_outputs_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let tx_num_outputs_row = Row::new() + .push(tx_num_outputs_label_container) + .push(tx_num_outputs_value_container); + column = column + .push(tx_num_outputs_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + + // Amount Credited + let tx_amount_credited_label = + Text::new(format!("{}: ", localized_string("tx-amount-credited"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tx_amount_credited_label_container = Container::new(tx_amount_credited_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let tx_amount_credited_value = + Text::new(format!("{}", amount_to_hr_string(tx.amount_credited, true))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tx_amount_credited_value_container = Container::new(tx_amount_credited_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let tx_amount_credited_row = Row::new() + .push(tx_amount_credited_label_container) + .push(tx_amount_credited_value_container); + column = column + .push(tx_amount_credited_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + + // Amount Debited + let tx_amount_debited_label = + Text::new(format!("{}: ", localized_string("tx-amount-debited"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tx_amount_debited_label_container = Container::new(tx_amount_debited_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let tx_amount_debited_value = + Text::new(format!("{}", amount_to_hr_string(tx.amount_debited, true))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tx_amount_debited_value_container = Container::new(tx_amount_debited_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let tx_amount_debited_row = Row::new() + .push(tx_amount_debited_label_container) + .push(tx_amount_debited_value_container); + column = column + .push(tx_amount_debited_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + + // Fee + let tx_fee_label = Text::new(format!("{}: ", localized_string("tx-fee"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tx_fee_label_container = Container::new(tx_fee_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let fee = match tx.fee { + Some(u) => format!("{}", amount_to_hr_string(u.fee(), true)), + None => "None".to_owned(), + }; - let tx_fee_value = Text::new(format!("{}", fee)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + let tx_fee_value = Text::new(format!("{}", fee)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let tx_fee_value_container = - Container::new(tx_fee_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let tx_fee_value_container = Container::new(tx_fee_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let tx_fee_row = Row::new().push(tx_fee_label_container).push(tx_fee_value_container); - column = column - .push(tx_fee_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + let tx_fee_row = Row::new() + .push(tx_fee_label_container) + .push(tx_fee_value_container); + column = column + .push(tx_fee_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - // Net Difference - let tx_net_difference_label = Text::new(format!("{}: ", localized_string("tx-net-difference"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); + // Net Difference + let tx_net_difference_label = + Text::new(format!("{}: ", localized_string("tx-net-difference"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let tx_net_difference_label_container = - Container::new(tx_net_difference_label).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let tx_net_difference_label_container = Container::new(tx_net_difference_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); let net_diff = if tx.amount_credited >= tx.amount_debited { amount_to_hr_string(tx.amount_credited - tx.amount_debited, true) @@ -393,76 +421,77 @@ pub fn data_container<'a>(config: &'a Config, state: &'a StateContainer) -> Cont amount_to_hr_string(tx.amount_debited - tx.amount_credited, true) ) }; - - let tx_net_difference_value = Text::new(format!("{}", net_diff)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let tx_net_difference_value_container = - Container::new(tx_net_difference_value).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let tx_net_difference_row = Row::new().push(tx_net_difference_label_container).push(tx_net_difference_value_container); - column = column - .push(tx_net_difference_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); - - } - - let cancel_button_label_container = - Container::new(Text::new(localized_string("back")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let cancel_button: Element = Button::new(cancel_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletOperationTxDetailViewInteraction( - LocalViewInteraction::Back, - )) - .into(); - - let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); - let cancel_container = Container::new(cancel_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let button_row = Row::new() - .push(cancel_container) - .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))); - - column = column.push(button_row); - - let form_container = Container::new(column) - .width(Length::Fill) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - // form container should be scrollable in tiny windows - let scrollable = Scrollable::new(form_container) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); - - let content = Container::new(scrollable) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let wrapper_column = Column::new() - .height(Length::Fill) - .push(header_container) - .push(content); - - // Returns the final container. - Container::new(wrapper_column).padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + + let tx_net_difference_value = Text::new(format!("{}", net_diff)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tx_net_difference_value_container = Container::new(tx_net_difference_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let tx_net_difference_row = Row::new() + .push(tx_net_difference_label_container) + .push(tx_net_difference_value_container); + column = column + .push(tx_net_difference_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))); + } + + let cancel_button_label_container = + Container::new(Text::new(localized_string("back")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let cancel_button: Element = Button::new(cancel_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletOperationTxDetailViewInteraction( + LocalViewInteraction::Back, + )) + .into(); + + let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); + let cancel_container = Container::new(cancel_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let button_row = Row::new() + .push(cancel_container) + .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))); + + column = column.push(button_row); + + let form_container = Container::new(column) + .width(Length::Fill) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + // form container should be scrollable in tiny windows + let scrollable = Scrollable::new(form_container) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); + + let content = Container::new(scrollable) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let wrapper_column = Column::new() + .height(Length::Fill) + .push(header_container) + .push(content); + + // Returns the final container. + Container::new(wrapper_column).padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/wallet/operation/tx_done.rs b/src/gui/element/wallet/operation/tx_done.rs index cc77af6..5ce20fc 100644 --- a/src/gui/element/wallet/operation/tx_done.rs +++ b/src/gui/element/wallet/operation/tx_done.rs @@ -1,153 +1,153 @@ use { - super::super::super::{ - BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, - SMALLER_FONT_SIZE, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - grin_gui_core::config::Config, - grin_gui_core::theme::ColorPalette, - grin_gui_core::theme::{ - Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, - }, - iced::widget::{button, pick_list, scrollable, text_input, Button, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - iced_aw::Card, + super::super::super::{ + BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, + SMALLER_FONT_SIZE, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + grin_gui_core::config::Config, + grin_gui_core::theme::ColorPalette, + grin_gui_core::theme::{ + Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, + }, + iced::widget::{button, pick_list, scrollable, text_input, Button, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + iced_aw::Card, }; pub struct StateContainer {} impl Default for StateContainer { - fn default() -> Self { - Self {} - } + fn default() -> Self { + Self {} + } } impl StateContainer {} #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Submit, + Submit, } pub fn handle_message( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.wallet_state.operation_state.tx_done_state; - match message { - LocalViewInteraction::Submit => { - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::Home; - } - } - Ok(Command::none()) + let state = &mut grin_gui.wallet_state.operation_state.tx_done_state; + match message { + LocalViewInteraction::Submit => { + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::Home; + } + } + Ok(Command::none()) } pub fn data_container<'a>( - _config: &'a Config, - state: &'a StateContainer, + _config: &'a Config, + state: &'a StateContainer, ) -> Container<'a, Message> { - // Title row - let title = Text::new(localized_string("tx-done")) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - - let title_container = Container::new(title) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 2, // top - 0, // right - 2, // bottom - 5, // left - ])); - - // push more items on to header here: e.g. other buttons, things that belong on the header - let header_row = Row::new().push(title_container); - - let header_container = Container::new(header_row).padding(iced::Padding::from([ - 0, // top - 0, // right - DEFAULT_PADDING as u16, // bottom - 0, // left - ])); - - let description = Text::new(localized_string("tx-done-instruction")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - let description_container = - Container::new(description).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let unit_spacing = 15.0; - - let button_height = Length::Fixed(BUTTON_HEIGHT); - let button_width = Length::Fixed(BUTTON_WIDTH); - - let cancel_button_label_container = - Container::new(Text::new(localized_string("ok-caps")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let cancel_button: Element = Button::new(cancel_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletOperationTxDoneViewInteraction( - LocalViewInteraction::Submit, - )) - .into(); - - let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); - let cancel_container = Container::new(cancel_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let unit_spacing = 15.0; - let button_row = Row::new().push(cancel_container); - - let column = Column::new() - .push(description_container) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 5.0), - )) - .push(button_row) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 10.0), - )); - - let form_container = Container::new(column) - .width(Length::Fill) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - // form container should be scrollable in tiny windows - let scrollable = Scrollable::new(form_container) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); - - let content = Container::new(scrollable) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let wrapper_column = Column::new() - .height(Length::Fill) - .push(header_container) - .push(content); - - // Returns the final container. - Container::new(wrapper_column).padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + // Title row + let title = Text::new(localized_string("tx-done")) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + + let title_container = Container::new(title) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 2, // top + 0, // right + 2, // bottom + 5, // left + ])); + + // push more items on to header here: e.g. other buttons, things that belong on the header + let header_row = Row::new().push(title_container); + + let header_container = Container::new(header_row).padding(iced::Padding::from([ + 0, // top + 0, // right + DEFAULT_PADDING as u16, // bottom + 0, // left + ])); + + let description = Text::new(localized_string("tx-done-instruction")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + let description_container = + Container::new(description).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let unit_spacing = 15.0; + + let button_height = Length::Fixed(BUTTON_HEIGHT); + let button_width = Length::Fixed(BUTTON_WIDTH); + + let cancel_button_label_container = + Container::new(Text::new(localized_string("ok-caps")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let cancel_button: Element = Button::new(cancel_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletOperationTxDoneViewInteraction( + LocalViewInteraction::Submit, + )) + .into(); + + let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); + let cancel_container = Container::new(cancel_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let unit_spacing = 15.0; + let button_row = Row::new().push(cancel_container); + + let column = Column::new() + .push(description_container) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 5.0), + )) + .push(button_row) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 10.0), + )); + + let form_container = Container::new(column) + .width(Length::Fill) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + // form container should be scrollable in tiny windows + let scrollable = Scrollable::new(form_container) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); + + let content = Container::new(scrollable) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let wrapper_column = Column::new() + .height(Length::Fill) + .push(header_container) + .push(content); + + // Returns the final container. + Container::new(wrapper_column).padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/wallet/operation/tx_list.rs b/src/gui/element/wallet/operation/tx_list.rs index e363415..6a64718 100644 --- a/src/gui/element/wallet/operation/tx_list.rs +++ b/src/gui/element/wallet/operation/tx_list.rs @@ -4,38 +4,38 @@ use grin_gui_core::config::TxMethod; use iced_core::Widget; use { - super::super::super::{BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_PADDING, SMALLER_FONT_SIZE}, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, - TextInput, - }, - grin_gui_core::{ - config::Config, - node::amount_to_hr_string, - theme::{ButtonStyle, ColorPalette, ContainerStyle}, - wallet::TxLogEntry, - }, - grin_gui_widgets::widget::header, - iced::widget::{button, pick_list, scrollable, text_input, Space}, - iced::{alignment, Alignment, Command, Length}, - serde::{Deserialize, Serialize}, - std::collections::HashMap, - strfmt::strfmt, + super::super::super::{BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_PADDING, SMALLER_FONT_SIZE}, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + grin_gui_core::theme::{ + Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, + TextInput, + }, + grin_gui_core::{ + config::Config, + node::amount_to_hr_string, + theme::{ButtonStyle, ColorPalette, ContainerStyle}, + wallet::TxLogEntry, + }, + grin_gui_widgets::widget::header, + iced::widget::{button, pick_list, scrollable, text_input, Space}, + iced::{alignment, Alignment, Command, Length}, + serde::{Deserialize, Serialize}, + std::collections::HashMap, + strfmt::strfmt, }; #[derive(Debug, Clone)] pub enum ExpandType { - Details(TxLogEntryWrap), - None, + Details(TxLogEntryWrap), + None, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Confirm { - DeleteAddon, - DeleteSavedVariables, + DeleteAddon, + DeleteSavedVariables, } /* @@ -44,7 +44,7 @@ For reference: Transaction Log - Account 'default' - Block Height: 1926614 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Id Type Shared Transaction Id Creation Time TTL Cutoff Height Confirmed? Confirmation Time Num. Num. Amount Amount Fee Net Payment Kernel Tx - Inputs Outputs Credited Debited Difference Proof Data + Inputs Outputs Credited Debited Difference Proof Data ========================================================================================================================================================================================================= 0 Received Tx None 2021-03-31 08:41:34 None true 2021-03-31 08:41:34 0 1 x.x 0.0 None x.x None None None --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -57,1894 +57,1894 @@ Transaction Log - Account 'default' - Block Height: 1926614 #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub enum ColumnKey { - Id, - Type, - SharedTransactionId, - CreationTime, - Status, - TTLCutoff, - Height, - IsConfirmed, - ConfirmationTime, - NumInputs, - NumOutputs, - AmountCredited, - AmountDebited, - Fee, - NetDifference, - PaymentProof, - Kernel, - TxData, - // Only used for sorting, not an actual visible column that can be shown - FuzzyScore, + Id, + Type, + SharedTransactionId, + CreationTime, + Status, + TTLCutoff, + Height, + IsConfirmed, + ConfirmationTime, + NumInputs, + NumOutputs, + AmountCredited, + AmountDebited, + Fee, + NetDifference, + PaymentProof, + Kernel, + TxData, + // Only used for sorting, not an actual visible column that can be shown + FuzzyScore, } impl ColumnKey { - fn title(self) -> String { - use ColumnKey::*; - - match self { - Id => localized_string("tx_id"), - Type => localized_string("tx_type"), - SharedTransactionId => localized_string("tx_shared_id"), - CreationTime => localized_string("tx-creation-time"), - Status => localized_string("tx-status"), - TTLCutoff => localized_string("tx_ttl_cutoff"), - Height => localized_string("tx_height"), - IsConfirmed => localized_string("tx_is_confirmed"), - ConfirmationTime => localized_string("tx-confirmation-time"), - NumInputs => localized_string("tx_num_inputs"), - NumOutputs => localized_string("tx_num_outputs"), - AmountCredited => localized_string("tx_amount_credited"), - AmountDebited => localized_string("tx_amount_debited"), - Fee => localized_string("tx_fee"), - NetDifference => localized_string("tx-net-difference"), - PaymentProof => localized_string("tx_payment_proof"), - Kernel => localized_string("tx_kernel"), - TxData => localized_string("tx_data"), - FuzzyScore => unreachable!("fuzzy score not used as an actual column"), - } - } - - fn as_string(self) -> String { - use ColumnKey::*; - - let s = match self { - Id => "tx_id", - Type => "tx_type", - SharedTransactionId => "tx_shared_id", - CreationTime => "tx-creation-time", - Status => "tx-status", - TTLCutoff => "tx_ttl_cutoff", - Height => "tx_height", - IsConfirmed => "tx_is_confirmed", - ConfirmationTime => "tx-confirmation-time", - NumInputs => "tx_num_inputs", - NumOutputs => "tx_num_outputs", - AmountCredited => "tx_amount_credited", - AmountDebited => "tx_amount_debited", - Fee => "tx_fee", - NetDifference => "tx-net-difference", - PaymentProof => "tx_payment_proof", - Kernel => "tx_kernel", - TxData => "tx_data", - FuzzyScore => unreachable!("fuzzy score not used as an actual column"), - }; - - s.to_string() - } + fn title(self) -> String { + use ColumnKey::*; + + match self { + Id => localized_string("tx_id"), + Type => localized_string("tx_type"), + SharedTransactionId => localized_string("tx_shared_id"), + CreationTime => localized_string("tx-creation-time"), + Status => localized_string("tx-status"), + TTLCutoff => localized_string("tx_ttl_cutoff"), + Height => localized_string("tx_height"), + IsConfirmed => localized_string("tx_is_confirmed"), + ConfirmationTime => localized_string("tx-confirmation-time"), + NumInputs => localized_string("tx_num_inputs"), + NumOutputs => localized_string("tx_num_outputs"), + AmountCredited => localized_string("tx_amount_credited"), + AmountDebited => localized_string("tx_amount_debited"), + Fee => localized_string("tx_fee"), + NetDifference => localized_string("tx-net-difference"), + PaymentProof => localized_string("tx_payment_proof"), + Kernel => localized_string("tx_kernel"), + TxData => localized_string("tx_data"), + FuzzyScore => unreachable!("fuzzy score not used as an actual column"), + } + } + + fn as_string(self) -> String { + use ColumnKey::*; + + let s = match self { + Id => "tx_id", + Type => "tx_type", + SharedTransactionId => "tx_shared_id", + CreationTime => "tx-creation-time", + Status => "tx-status", + TTLCutoff => "tx_ttl_cutoff", + Height => "tx_height", + IsConfirmed => "tx_is_confirmed", + ConfirmationTime => "tx-confirmation-time", + NumInputs => "tx_num_inputs", + NumOutputs => "tx_num_outputs", + AmountCredited => "tx_amount_credited", + AmountDebited => "tx_amount_debited", + Fee => "tx_fee", + NetDifference => "tx-net-difference", + PaymentProof => "tx_payment_proof", + Kernel => "tx_kernel", + TxData => "tx_data", + FuzzyScore => unreachable!("fuzzy score not used as an actual column"), + }; + + s.to_string() + } } impl From<&str> for ColumnKey { - fn from(s: &str) -> Self { - match s { - "tx_id" => ColumnKey::Id, - "tx_type" => ColumnKey::Type, - "tx_shared_id" => ColumnKey::SharedTransactionId, - "tx_creation_time" => ColumnKey::CreationTime, - "tx-status" => ColumnKey::Status, - "tx_ttl_cutoff" => ColumnKey::TTLCutoff, - "tx_height" => ColumnKey::Height, - "tx_is_confirmed" => ColumnKey::IsConfirmed, - "tx-confirmation-time" => ColumnKey::ConfirmationTime, - "tx_num_inputs" => ColumnKey::NumInputs, - "tx_num_outputs" => ColumnKey::NumOutputs, - "tx_amount_credited" => ColumnKey::AmountCredited, - "tx_amount_debited" => ColumnKey::AmountDebited, - "tx_fee" => ColumnKey::Fee, - "tx-net-difference" => ColumnKey::NetDifference, - "tx_payment_proof" => ColumnKey::PaymentProof, - "tx_kernel" => ColumnKey::Kernel, - "tx_data" => ColumnKey::TxData, - _ => panic!("Unknown ColumnKey for {}", s), - } - } + fn from(s: &str) -> Self { + match s { + "tx_id" => ColumnKey::Id, + "tx_type" => ColumnKey::Type, + "tx_shared_id" => ColumnKey::SharedTransactionId, + "tx_creation_time" => ColumnKey::CreationTime, + "tx-status" => ColumnKey::Status, + "tx_ttl_cutoff" => ColumnKey::TTLCutoff, + "tx_height" => ColumnKey::Height, + "tx_is_confirmed" => ColumnKey::IsConfirmed, + "tx-confirmation-time" => ColumnKey::ConfirmationTime, + "tx_num_inputs" => ColumnKey::NumInputs, + "tx_num_outputs" => ColumnKey::NumOutputs, + "tx_amount_credited" => ColumnKey::AmountCredited, + "tx_amount_debited" => ColumnKey::AmountDebited, + "tx_fee" => ColumnKey::Fee, + "tx-net-difference" => ColumnKey::NetDifference, + "tx_payment_proof" => ColumnKey::PaymentProof, + "tx_kernel" => ColumnKey::Kernel, + "tx_data" => ColumnKey::TxData, + _ => panic!("Unknown ColumnKey for {}", s), + } + } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum SortDirection { - Asc, - Desc, + Asc, + Desc, } impl SortDirection { - fn toggle(self) -> SortDirection { - match self { - SortDirection::Asc => SortDirection::Desc, - SortDirection::Desc => SortDirection::Asc, - } - } + fn toggle(self) -> SortDirection { + match self { + SortDirection::Asc => SortDirection::Desc, + SortDirection::Desc => SortDirection::Asc, + } + } } #[derive(Debug, Clone)] pub struct TxLogEntryWrap { - pub tx: TxLogEntry, + pub tx: TxLogEntry, } impl TxLogEntryWrap { - pub fn new(tx: TxLogEntry) -> Self { - Self { tx } - } + pub fn new(tx: TxLogEntry) -> Self { + Self { tx } + } } #[derive(Debug, Clone)] pub struct TxList { - pub txs: Vec, + pub txs: Vec, } impl Default for TxList { - fn default() -> Self { - Self { txs: vec![] } - } + fn default() -> Self { + Self { txs: vec![] } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum TxListResultSize { - _25, - _50, - _100, - _500, + _25, + _50, + _100, + _500, } impl Default for TxListResultSize { - fn default() -> Self { - TxListResultSize::_25 - } + fn default() -> Self { + TxListResultSize::_25 + } } impl TxListResultSize { - pub fn all() -> Vec { - vec![ - TxListResultSize::_25, - TxListResultSize::_50, - TxListResultSize::_100, - TxListResultSize::_500, - ] - } - - pub fn as_usize(self) -> usize { - match self { - TxListResultSize::_25 => 25, - TxListResultSize::_50 => 50, - TxListResultSize::_100 => 100, - TxListResultSize::_500 => 500, - } - } + pub fn all() -> Vec { + vec![ + TxListResultSize::_25, + TxListResultSize::_50, + TxListResultSize::_100, + TxListResultSize::_500, + ] + } + + pub fn as_usize(self) -> usize { + match self { + TxListResultSize::_25 => 25, + TxListResultSize::_50 => 50, + TxListResultSize::_100 => 100, + TxListResultSize::_500 => 500, + } + } } impl std::fmt::Display for TxListResultSize { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut vars = HashMap::new(); - vars.insert("number".to_string(), self.as_usize()); - let fmt = localized_string("tx-list-results"); + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut vars = HashMap::new(); + vars.insert("number".to_string(), self.as_usize()); + let fmt = localized_string("tx-list-results"); - write!(f, "{}", strfmt(&fmt, &vars).unwrap()) - } + write!(f, "{}", strfmt(&fmt, &vars).unwrap()) + } } pub struct HeaderState { - pub state: header::State, - pub previous_column_key: Option, - pub previous_sort_direction: Option, - pub columns: Vec, + pub state: header::State, + pub previous_column_key: Option, + pub previous_sort_direction: Option, + pub columns: Vec, } impl HeaderState { - pub fn column_config(&self) -> Vec<(ColumnKey, Length, bool)> { - self.columns - .iter() - .map(|c| (c.key, c.width, c.hidden)) - .collect() - } + pub fn column_config(&self) -> Vec<(ColumnKey, Length, bool)> { + self.columns + .iter() + .map(|c| (c.key, c.width, c.hidden)) + .collect() + } } impl Default for HeaderState { - fn default() -> Self { - Self { - state: Default::default(), - previous_column_key: None, - previous_sort_direction: None, - columns: vec![ - ColumnState { - key: ColumnKey::Id, - // btn_state: Default::default(), - width: Length::Fixed(20.0), - hidden: true, - order: 0, - }, - ColumnState { - key: ColumnKey::NetDifference, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: false, - order: 1, - }, - ColumnState { - key: ColumnKey::CreationTime, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: false, - order: 2, - }, - ColumnState { - key: ColumnKey::Status, - // btn_state: Default::default(), - width: Length::Fixed(300.0), - hidden: false, - order: 3, - }, - ColumnState { - key: ColumnKey::ConfirmationTime, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: true, - order: 4, - }, - ColumnState { - key: ColumnKey::Type, - // btn_state: Default::default(), - width: Length::Fixed(150.0), - hidden: true, - order: 5, - }, - ColumnState { - key: ColumnKey::SharedTransactionId, - // btn_state: Default::default(), - width: Length::Fixed(150.0), - hidden: true, - order: 6, - }, - ColumnState { - key: ColumnKey::TTLCutoff, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: true, - order: 7, - }, - ColumnState { - key: ColumnKey::Height, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: true, - order: 8, - }, - ColumnState { - key: ColumnKey::IsConfirmed, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: true, - order: 9, - }, - ColumnState { - key: ColumnKey::NumInputs, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: true, - order: 10, - }, - ColumnState { - key: ColumnKey::NumOutputs, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: true, - order: 11, - }, - ColumnState { - key: ColumnKey::AmountCredited, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: true, - order: 12, - }, - ColumnState { - key: ColumnKey::AmountDebited, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: true, - order: 13, - }, - ColumnState { - key: ColumnKey::Fee, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: true, - order: 14, - }, - ColumnState { - key: ColumnKey::PaymentProof, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: true, - order: 15, - }, - ColumnState { - key: ColumnKey::Kernel, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: true, - order: 16, - }, - ColumnState { - key: ColumnKey::TxData, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: true, - order: 17, - }, - ], - } - } + fn default() -> Self { + Self { + state: Default::default(), + previous_column_key: None, + previous_sort_direction: None, + columns: vec![ + ColumnState { + key: ColumnKey::Id, + // btn_state: Default::default(), + width: Length::Fixed(20.0), + hidden: true, + order: 0, + }, + ColumnState { + key: ColumnKey::NetDifference, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: false, + order: 1, + }, + ColumnState { + key: ColumnKey::CreationTime, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: false, + order: 2, + }, + ColumnState { + key: ColumnKey::Status, + // btn_state: Default::default(), + width: Length::Fixed(300.0), + hidden: false, + order: 3, + }, + ColumnState { + key: ColumnKey::ConfirmationTime, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: true, + order: 4, + }, + ColumnState { + key: ColumnKey::Type, + // btn_state: Default::default(), + width: Length::Fixed(150.0), + hidden: true, + order: 5, + }, + ColumnState { + key: ColumnKey::SharedTransactionId, + // btn_state: Default::default(), + width: Length::Fixed(150.0), + hidden: true, + order: 6, + }, + ColumnState { + key: ColumnKey::TTLCutoff, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: true, + order: 7, + }, + ColumnState { + key: ColumnKey::Height, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: true, + order: 8, + }, + ColumnState { + key: ColumnKey::IsConfirmed, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: true, + order: 9, + }, + ColumnState { + key: ColumnKey::NumInputs, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: true, + order: 10, + }, + ColumnState { + key: ColumnKey::NumOutputs, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: true, + order: 11, + }, + ColumnState { + key: ColumnKey::AmountCredited, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: true, + order: 12, + }, + ColumnState { + key: ColumnKey::AmountDebited, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: true, + order: 13, + }, + ColumnState { + key: ColumnKey::Fee, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: true, + order: 14, + }, + ColumnState { + key: ColumnKey::PaymentProof, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: true, + order: 15, + }, + ColumnState { + key: ColumnKey::Kernel, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: true, + order: 16, + }, + ColumnState { + key: ColumnKey::TxData, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: true, + order: 17, + }, + ], + } + } } pub struct ColumnState { - key: ColumnKey, - // btn_state: button::State, - width: Length, - hidden: bool, - order: usize, + key: ColumnKey, + // btn_state: button::State, + width: Length, + hidden: bool, + order: usize, } pub struct ColumnSettings { - // pub scrollable_state: scrollable::State, - pub columns: Vec, + // pub scrollable_state: scrollable::State, + pub columns: Vec, } impl Default for ColumnSettings { - fn default() -> Self { - ColumnSettings { - // scrollable_state: Default::default(), - columns: vec![ - ColumnSettingState { - key: ColumnKey::Id, - order: 0, - }, - ColumnSettingState { - key: ColumnKey::NetDifference, - order: 1, - }, - ColumnSettingState { - key: ColumnKey::CreationTime, - order: 2, - }, - ColumnSettingState { - key: ColumnKey::Status, - order: 3, - }, - ColumnSettingState { - key: ColumnKey::ConfirmationTime, - order: 4, - }, - ColumnSettingState { - key: ColumnKey::Type, - order: 5, - }, - ColumnSettingState { - key: ColumnKey::SharedTransactionId, - order: 6, - }, - ColumnSettingState { - key: ColumnKey::TTLCutoff, - order: 7, - }, - ColumnSettingState { - key: ColumnKey::Height, - order: 8, - }, - ColumnSettingState { - key: ColumnKey::IsConfirmed, - order: 9, - }, - ColumnSettingState { - key: ColumnKey::NumInputs, - order: 10, - }, - ColumnSettingState { - key: ColumnKey::NumOutputs, - order: 11, - }, - ColumnSettingState { - key: ColumnKey::AmountCredited, - order: 12, - }, - ColumnSettingState { - key: ColumnKey::AmountDebited, - order: 13, - }, - ColumnSettingState { - key: ColumnKey::Fee, - order: 14, - }, - ColumnSettingState { - key: ColumnKey::PaymentProof, - order: 15, - }, - ColumnSettingState { - key: ColumnKey::Kernel, - order: 16, - }, - ColumnSettingState { - key: ColumnKey::TxData, - order: 17, - }, - ], - } - } + fn default() -> Self { + ColumnSettings { + // scrollable_state: Default::default(), + columns: vec![ + ColumnSettingState { + key: ColumnKey::Id, + order: 0, + }, + ColumnSettingState { + key: ColumnKey::NetDifference, + order: 1, + }, + ColumnSettingState { + key: ColumnKey::CreationTime, + order: 2, + }, + ColumnSettingState { + key: ColumnKey::Status, + order: 3, + }, + ColumnSettingState { + key: ColumnKey::ConfirmationTime, + order: 4, + }, + ColumnSettingState { + key: ColumnKey::Type, + order: 5, + }, + ColumnSettingState { + key: ColumnKey::SharedTransactionId, + order: 6, + }, + ColumnSettingState { + key: ColumnKey::TTLCutoff, + order: 7, + }, + ColumnSettingState { + key: ColumnKey::Height, + order: 8, + }, + ColumnSettingState { + key: ColumnKey::IsConfirmed, + order: 9, + }, + ColumnSettingState { + key: ColumnKey::NumInputs, + order: 10, + }, + ColumnSettingState { + key: ColumnKey::NumOutputs, + order: 11, + }, + ColumnSettingState { + key: ColumnKey::AmountCredited, + order: 12, + }, + ColumnSettingState { + key: ColumnKey::AmountDebited, + order: 13, + }, + ColumnSettingState { + key: ColumnKey::Fee, + order: 14, + }, + ColumnSettingState { + key: ColumnKey::PaymentProof, + order: 15, + }, + ColumnSettingState { + key: ColumnKey::Kernel, + order: 16, + }, + ColumnSettingState { + key: ColumnKey::TxData, + order: 17, + }, + ], + } + } } pub struct ColumnSettingState { - pub key: ColumnKey, - pub order: usize, - // pub up_btn_state: button::State, - // pub down_btn_state: button::State, + pub key: ColumnKey, + pub order: usize, + // pub up_btn_state: button::State, + // pub down_btn_state: button::State, } #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] pub enum TxListColumnKey { - Id, - Type, - SharedTransactionId, - CreationTime, - TTLCutoff, - Height, - IsConfirmed, - ConfirmationTime, - NumInputs, - NumOutputs, - AmountCredited, - AmountDebited, - Fee, - NetDifference, - PaymentProof, - Kernel, - TxData, + Id, + Type, + SharedTransactionId, + CreationTime, + TTLCutoff, + Height, + IsConfirmed, + ConfirmationTime, + NumInputs, + NumOutputs, + AmountCredited, + AmountDebited, + Fee, + NetDifference, + PaymentProof, + Kernel, + TxData, } impl TxListColumnKey { - fn title(self) -> String { - use TxListColumnKey::*; - - match self { - Id => localized_string("tx_id"), - Type => localized_string("tx_type"), - SharedTransactionId => localized_string("tx_shared_id"), - CreationTime => localized_string("tx_creation_time"), - TTLCutoff => localized_string("tx_ttl_cutoff"), - Height => localized_string("tx_height"), - IsConfirmed => localized_string("tx_is_confirmed"), - ConfirmationTime => localized_string("tx-confirmation-time"), - NumInputs => localized_string("tx_num_inputs"), - NumOutputs => localized_string("tx_num_outputs"), - AmountCredited => localized_string("tx_amount_credited"), - AmountDebited => localized_string("tx_amount_debited"), - Fee => localized_string("tx_fee"), - NetDifference => localized_string("tx-net-difference"), - PaymentProof => localized_string("tx_payment_proof"), - Kernel => localized_string("tx_kernel"), - TxData => localized_string("tx_data"), - } - } - - fn as_string(self) -> String { - use TxListColumnKey::*; - - let s = match self { - Id => "tx_id", - Type => "tx_type", - SharedTransactionId => "tx_shared_id", - CreationTime => "tx_creation_time", - TTLCutoff => "tx_ttl_cutoff", - Height => "tx_height", - IsConfirmed => "tx_is_confirmed", - ConfirmationTime => "tx-confirmation-time", - NumInputs => "tx_num_inputs", - NumOutputs => "tx_num_outputs", - AmountCredited => "tx_amount_credited", - AmountDebited => "tx_amount_debited", - Fee => "tx_fee", - NetDifference => "tx-net-difference", - PaymentProof => "tx_payment_proof", - Kernel => "tx_kernel", - TxData => "tx_data", - }; - - s.to_string() - } + fn title(self) -> String { + use TxListColumnKey::*; + + match self { + Id => localized_string("tx_id"), + Type => localized_string("tx_type"), + SharedTransactionId => localized_string("tx_shared_id"), + CreationTime => localized_string("tx_creation_time"), + TTLCutoff => localized_string("tx_ttl_cutoff"), + Height => localized_string("tx_height"), + IsConfirmed => localized_string("tx_is_confirmed"), + ConfirmationTime => localized_string("tx-confirmation-time"), + NumInputs => localized_string("tx_num_inputs"), + NumOutputs => localized_string("tx_num_outputs"), + AmountCredited => localized_string("tx_amount_credited"), + AmountDebited => localized_string("tx_amount_debited"), + Fee => localized_string("tx_fee"), + NetDifference => localized_string("tx-net-difference"), + PaymentProof => localized_string("tx_payment_proof"), + Kernel => localized_string("tx_kernel"), + TxData => localized_string("tx_data"), + } + } + + fn as_string(self) -> String { + use TxListColumnKey::*; + + let s = match self { + Id => "tx_id", + Type => "tx_type", + SharedTransactionId => "tx_shared_id", + CreationTime => "tx_creation_time", + TTLCutoff => "tx_ttl_cutoff", + Height => "tx_height", + IsConfirmed => "tx_is_confirmed", + ConfirmationTime => "tx-confirmation-time", + NumInputs => "tx_num_inputs", + NumOutputs => "tx_num_outputs", + AmountCredited => "tx_amount_credited", + AmountDebited => "tx_amount_debited", + Fee => "tx_fee", + NetDifference => "tx-net-difference", + PaymentProof => "tx_payment_proof", + Kernel => "tx_kernel", + TxData => "tx_data", + }; + + s.to_string() + } } impl From<&str> for TxListColumnKey { - fn from(s: &str) -> Self { - match s { - "tx_id" => TxListColumnKey::Id, - "tx_type" => TxListColumnKey::Type, - "tx_shared_id" => TxListColumnKey::SharedTransactionId, - "tx_creation_time" => TxListColumnKey::CreationTime, - "tx_ttl_cutoff" => TxListColumnKey::TTLCutoff, - "tx_height" => TxListColumnKey::Height, - "tx_is_confirmed" => TxListColumnKey::IsConfirmed, - "tx-confirmation-time" => TxListColumnKey::ConfirmationTime, - "tx_num_inputs" => TxListColumnKey::NumInputs, - "tx_num_outputs" => TxListColumnKey::NumOutputs, - "tx_amount_credited" => TxListColumnKey::AmountCredited, - "tx_amount_debited" => TxListColumnKey::AmountDebited, - "tx_fee" => TxListColumnKey::Fee, - "tx-net-difference" => TxListColumnKey::NetDifference, - "tx_payment_proof" => TxListColumnKey::PaymentProof, - "tx_kernel" => TxListColumnKey::Kernel, - "tx_data" => TxListColumnKey::TxData, - _ => panic!("Unknown CatalogTxListColumnKey for {}", s), - } - } + fn from(s: &str) -> Self { + match s { + "tx_id" => TxListColumnKey::Id, + "tx_type" => TxListColumnKey::Type, + "tx_shared_id" => TxListColumnKey::SharedTransactionId, + "tx_creation_time" => TxListColumnKey::CreationTime, + "tx_ttl_cutoff" => TxListColumnKey::TTLCutoff, + "tx_height" => TxListColumnKey::Height, + "tx_is_confirmed" => TxListColumnKey::IsConfirmed, + "tx-confirmation-time" => TxListColumnKey::ConfirmationTime, + "tx_num_inputs" => TxListColumnKey::NumInputs, + "tx_num_outputs" => TxListColumnKey::NumOutputs, + "tx_amount_credited" => TxListColumnKey::AmountCredited, + "tx_amount_debited" => TxListColumnKey::AmountDebited, + "tx_fee" => TxListColumnKey::Fee, + "tx-net-difference" => TxListColumnKey::NetDifference, + "tx_payment_proof" => TxListColumnKey::PaymentProof, + "tx_kernel" => TxListColumnKey::Kernel, + "tx_data" => TxListColumnKey::TxData, + _ => panic!("Unknown CatalogTxListColumnKey for {}", s), + } + } } pub struct TxListColumnState { - key: ColumnKey, - width: Length, - hidden: bool, - order: usize, + key: ColumnKey, + width: Length, + hidden: bool, + order: usize, } pub struct TxListHeaderState { - state: header::State, - previous_column_key: Option, - previous_sort_direction: Option, - columns: Vec, + state: header::State, + previous_column_key: Option, + previous_sort_direction: Option, + columns: Vec, } impl TxListHeaderState { - fn column_config(&self) -> Vec<(ColumnKey, Length, bool)> { - self.columns - .iter() - .map(|c| (c.key, c.width, c.hidden)) - .collect() - } + fn column_config(&self) -> Vec<(ColumnKey, Length, bool)> { + self.columns + .iter() + .map(|c| (c.key, c.width, c.hidden)) + .collect() + } } impl Default for TxListHeaderState { - fn default() -> Self { - Self { - state: Default::default(), - previous_column_key: None, - previous_sort_direction: None, - columns: vec![ - TxListColumnState { - key: ColumnKey::Id, - // btn_state: Default::default(), - width: Length::Fixed(20.0), - hidden: false, - order: 0, - }, - TxListColumnState { - key: ColumnKey::NetDifference, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: true, - order: 1, - }, - TxListColumnState { - key: ColumnKey::CreationTime, - // btn_state: Default::default(), - width: Length::Fixed(105.0), - hidden: true, - order: 2, - }, - TxListColumnState { - key: ColumnKey::Status, - // btn_state: Default::default(), - width: Length::Fixed(105.0), - hidden: false, - order: 3, - }, - TxListColumnState { - key: ColumnKey::ConfirmationTime, - // btn_state: Default::default(), - width: Length::Fixed(105.0), - hidden: false, - order: 4, - }, - TxListColumnState { - key: ColumnKey::Type, - // btn_state: Default::default(), - width: Length::Fixed(150.0), - hidden: true, - order: 5, - }, - TxListColumnState { - key: ColumnKey::SharedTransactionId, - // btn_state: Default::default(), - width: Length::Fixed(110.0), - hidden: false, - order: 6, - }, - TxListColumnState { - key: ColumnKey::TTLCutoff, - // btn_state: Default::default(), - width: Length::Fixed(105.0), - hidden: true, - order: 7, - }, - TxListColumnState { - key: ColumnKey::Height, - // btn_state: Default::default(), - width: Length::Fixed(105.0), - hidden: false, - order: 8, - }, - TxListColumnState { - key: ColumnKey::IsConfirmed, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: false, - order: 9, - }, - TxListColumnState { - key: ColumnKey::NumInputs, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: true, - order: 10, - }, - TxListColumnState { - key: ColumnKey::NumOutputs, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: true, - order: 11, - }, - TxListColumnState { - key: ColumnKey::AmountCredited, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: true, - order: 12, - }, - TxListColumnState { - key: ColumnKey::AmountDebited, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: true, - order: 13, - }, - TxListColumnState { - key: ColumnKey::Fee, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: true, - order: 14, - }, - TxListColumnState { - key: ColumnKey::PaymentProof, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: true, - order: 15, - }, - TxListColumnState { - key: ColumnKey::Kernel, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: true, - order: 16, - }, - TxListColumnState { - key: ColumnKey::TxData, - // btn_state: Default::default(), - width: Length::Fixed(85.0), - hidden: true, - order: 17, - }, - ], - } - } + fn default() -> Self { + Self { + state: Default::default(), + previous_column_key: None, + previous_sort_direction: None, + columns: vec![ + TxListColumnState { + key: ColumnKey::Id, + // btn_state: Default::default(), + width: Length::Fixed(20.0), + hidden: false, + order: 0, + }, + TxListColumnState { + key: ColumnKey::NetDifference, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: true, + order: 1, + }, + TxListColumnState { + key: ColumnKey::CreationTime, + // btn_state: Default::default(), + width: Length::Fixed(105.0), + hidden: true, + order: 2, + }, + TxListColumnState { + key: ColumnKey::Status, + // btn_state: Default::default(), + width: Length::Fixed(105.0), + hidden: false, + order: 3, + }, + TxListColumnState { + key: ColumnKey::ConfirmationTime, + // btn_state: Default::default(), + width: Length::Fixed(105.0), + hidden: false, + order: 4, + }, + TxListColumnState { + key: ColumnKey::Type, + // btn_state: Default::default(), + width: Length::Fixed(150.0), + hidden: true, + order: 5, + }, + TxListColumnState { + key: ColumnKey::SharedTransactionId, + // btn_state: Default::default(), + width: Length::Fixed(110.0), + hidden: false, + order: 6, + }, + TxListColumnState { + key: ColumnKey::TTLCutoff, + // btn_state: Default::default(), + width: Length::Fixed(105.0), + hidden: true, + order: 7, + }, + TxListColumnState { + key: ColumnKey::Height, + // btn_state: Default::default(), + width: Length::Fixed(105.0), + hidden: false, + order: 8, + }, + TxListColumnState { + key: ColumnKey::IsConfirmed, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: false, + order: 9, + }, + TxListColumnState { + key: ColumnKey::NumInputs, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: true, + order: 10, + }, + TxListColumnState { + key: ColumnKey::NumOutputs, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: true, + order: 11, + }, + TxListColumnState { + key: ColumnKey::AmountCredited, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: true, + order: 12, + }, + TxListColumnState { + key: ColumnKey::AmountDebited, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: true, + order: 13, + }, + TxListColumnState { + key: ColumnKey::Fee, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: true, + order: 14, + }, + TxListColumnState { + key: ColumnKey::PaymentProof, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: true, + order: 15, + }, + TxListColumnState { + key: ColumnKey::Kernel, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: true, + order: 16, + }, + TxListColumnState { + key: ColumnKey::TxData, + // btn_state: Default::default(), + width: Length::Fixed(85.0), + hidden: true, + order: 17, + }, + ], + } + } } pub struct TxListColumnSettings { - pub columns: Vec, + pub columns: Vec, } impl Default for TxListColumnSettings { - fn default() -> Self { - TxListColumnSettings { - columns: vec![ - TxListColumnSettingState { - key: ColumnKey::Id, - order: 0, - }, - TxListColumnSettingState { - key: ColumnKey::NetDifference, - order: 1, - }, - TxListColumnSettingState { - key: ColumnKey::CreationTime, - order: 2, - }, - TxListColumnSettingState { - key: ColumnKey::Status, - order: 3, - }, - TxListColumnSettingState { - key: ColumnKey::ConfirmationTime, - order: 4, - }, - TxListColumnSettingState { - key: ColumnKey::Type, - order: 5, - }, - TxListColumnSettingState { - key: ColumnKey::SharedTransactionId, - order: 6, - }, - TxListColumnSettingState { - key: ColumnKey::TTLCutoff, - order: 7, - }, - TxListColumnSettingState { - key: ColumnKey::Height, - order: 8, - }, - TxListColumnSettingState { - key: ColumnKey::IsConfirmed, - order: 9, - }, - TxListColumnSettingState { - key: ColumnKey::NumInputs, - order: 10, - }, - TxListColumnSettingState { - key: ColumnKey::NumOutputs, - order: 11, - }, - TxListColumnSettingState { - key: ColumnKey::AmountCredited, - order: 12, - }, - TxListColumnSettingState { - key: ColumnKey::AmountDebited, - order: 13, - }, - TxListColumnSettingState { - key: ColumnKey::Fee, - order: 14, - }, - TxListColumnSettingState { - key: ColumnKey::PaymentProof, - order: 15, - }, - TxListColumnSettingState { - key: ColumnKey::Kernel, - order: 16, - }, - TxListColumnSettingState { - key: ColumnKey::TxData, - order: 17, - }, - ], - } - } + fn default() -> Self { + TxListColumnSettings { + columns: vec![ + TxListColumnSettingState { + key: ColumnKey::Id, + order: 0, + }, + TxListColumnSettingState { + key: ColumnKey::NetDifference, + order: 1, + }, + TxListColumnSettingState { + key: ColumnKey::CreationTime, + order: 2, + }, + TxListColumnSettingState { + key: ColumnKey::Status, + order: 3, + }, + TxListColumnSettingState { + key: ColumnKey::ConfirmationTime, + order: 4, + }, + TxListColumnSettingState { + key: ColumnKey::Type, + order: 5, + }, + TxListColumnSettingState { + key: ColumnKey::SharedTransactionId, + order: 6, + }, + TxListColumnSettingState { + key: ColumnKey::TTLCutoff, + order: 7, + }, + TxListColumnSettingState { + key: ColumnKey::Height, + order: 8, + }, + TxListColumnSettingState { + key: ColumnKey::IsConfirmed, + order: 9, + }, + TxListColumnSettingState { + key: ColumnKey::NumInputs, + order: 10, + }, + TxListColumnSettingState { + key: ColumnKey::NumOutputs, + order: 11, + }, + TxListColumnSettingState { + key: ColumnKey::AmountCredited, + order: 12, + }, + TxListColumnSettingState { + key: ColumnKey::AmountDebited, + order: 13, + }, + TxListColumnSettingState { + key: ColumnKey::Fee, + order: 14, + }, + TxListColumnSettingState { + key: ColumnKey::PaymentProof, + order: 15, + }, + TxListColumnSettingState { + key: ColumnKey::Kernel, + order: 16, + }, + TxListColumnSettingState { + key: ColumnKey::TxData, + order: 17, + }, + ], + } + } } pub struct TxListColumnSettingState { - pub key: ColumnKey, - pub order: usize, + pub key: ColumnKey, + pub order: usize, } pub struct CatalogSearchState { - pub catalog_rows: Vec, - pub query: Option, - // pub query_state: text_input::State, - pub result_size: TxListResultSize, - pub result_sizes: Vec, - // pub result_sizes_state: pick_list::State, + pub catalog_rows: Vec, + pub query: Option, + // pub query_state: text_input::State, + pub result_size: TxListResultSize, + pub result_sizes: Vec, + // pub result_sizes_state: pick_list::State, } impl Default for CatalogSearchState { - fn default() -> Self { - CatalogSearchState { - catalog_rows: Default::default(), - query: None, - // query_state: Default::default(), - result_size: Default::default(), - result_sizes: TxListResultSize::all(), - // result_sizes_state: Default::default(), - } - } + fn default() -> Self { + CatalogSearchState { + catalog_rows: Default::default(), + query: None, + // query_state: Default::default(), + result_size: Default::default(), + result_sizes: TxListResultSize::all(), + // result_sizes_state: Default::default(), + } + } } pub struct CatalogRow { - // install_button_state: button::State, + // install_button_state: button::State, } fn row_title( - column_key: T, - previous_column_key: Option, - previous_sort_direction: Option, - title: &str, + column_key: T, + previous_column_key: Option, + previous_sort_direction: Option, + title: &str, ) -> String { - if Some(column_key) == previous_column_key { - match previous_sort_direction { - Some(SortDirection::Asc) => format!("{} ▲", title), - Some(SortDirection::Desc) => format!("{} ▼", title), - _ => title.to_string(), - } - } else { - title.to_string() - } + if Some(column_key) == previous_column_key { + match previous_sort_direction { + Some(SortDirection::Asc) => format!("{} ▲", title), + Some(SortDirection::Desc) => format!("{} ▼", title), + _ => title.to_string(), + } + } else { + title.to_string() + } } pub fn titles_row_header<'a>( - tx_list: &TxList, - header_state: &'a header::State, - column_state: &'a [ColumnState], - previous_column_key: Option, - previous_sort_direction: Option, + tx_list: &TxList, + header_state: &'a header::State, + column_state: &'a [ColumnState], + previous_column_key: Option, + previous_sort_direction: Option, ) -> Header<'a, Message> { - // A row containing titles above the addon rows. - let mut row_titles = vec![]; - - for column in column_state.iter().filter(|c| !c.hidden) { - let column_key = column.key; - - let row_title = row_title( - column_key, - previous_column_key, - previous_sort_direction, - &column.key.title(), - ); - - let mut row_header = Button::new( - // &mut column.btn_state, - Text::new(row_title) - .size(DEFAULT_FONT_SIZE) - .width(Length::Fill), - ) - .width(Length::Fill); - - //if column_key != ColumnKey::Install { - //TODO - row_header = row_header.on_press(Interaction::SortCatalogColumn(column_key)); - //} - - if previous_column_key == Some(column_key) { - row_header = row_header.style(grin_gui_core::theme::ButtonStyle::SelectedColumn); - } - /*else if column_key == ColumnKey::Install { - row_header = row_header.style(style::UnclickableColumnHeaderButton); - } */ - else { - row_header = row_header.style(grin_gui_core::theme::ButtonStyle::ColumnHeader); - } - - let row_header: Element = row_header.into(); - - let row_container = Container::new(row_header.map(Message::Interaction)) - .width(column.width) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - // Only shows row titles if we have any catalog results. - if !tx_list.txs.is_empty() { - row_titles.push((column.key.as_string(), row_container)); - } - } - - Header::new( - header_state.clone(), - row_titles, - // Some(Length::Fixed(DEFAULT_PADDING)), - // Some(Length::Fixed(DEFAULT_PADDING + 5)), - None, - None, - ) - .spacing(1) - .height(Length::Fixed(25.0)) - /* .on_resize(3, |event| { - //TODO - //Message::Interaction(Interaction::ResizeColumn(Mode::Catalog, event)) - })*/ + // A row containing titles above the addon rows. + let mut row_titles = vec![]; + + for column in column_state.iter().filter(|c| !c.hidden) { + let column_key = column.key; + + let row_title = row_title( + column_key, + previous_column_key, + previous_sort_direction, + &column.key.title(), + ); + + let mut row_header = Button::new( + // &mut column.btn_state, + Text::new(row_title) + .size(DEFAULT_FONT_SIZE) + .width(Length::Fill), + ) + .width(Length::Fill); + + //if column_key != ColumnKey::Install { + //TODO + row_header = row_header.on_press(Interaction::SortCatalogColumn(column_key)); + //} + + if previous_column_key == Some(column_key) { + row_header = row_header.style(grin_gui_core::theme::ButtonStyle::SelectedColumn); + } + /*else if column_key == ColumnKey::Install { + row_header = row_header.style(style::UnclickableColumnHeaderButton); + } */ + else { + row_header = row_header.style(grin_gui_core::theme::ButtonStyle::ColumnHeader); + } + + let row_header: Element = row_header.into(); + + let row_container = Container::new(row_header.map(Message::Interaction)) + .width(column.width) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + // Only shows row titles if we have any catalog results. + if !tx_list.txs.is_empty() { + row_titles.push((column.key.as_string(), row_container)); + } + } + + Header::new( + header_state.clone(), + row_titles, + // Some(Length::Fixed(DEFAULT_PADDING)), + // Some(Length::Fixed(DEFAULT_PADDING + 5)), + None, + None, + ) + .spacing(1) + .height(Length::Fixed(25.0)) + /* .on_resize(3, |event| { + //TODO + //Message::Interaction(Interaction::ResizeColumn(Mode::Catalog, event)) + })*/ } //TODO: Move somewhere else pub fn create_tx_display_status(log_entry: &TxLogEntry) -> String { - if log_entry.confirmed { - localized_string("tx-confirmed") - } else { - localized_string("tx-unconfirmed") - } + if log_entry.confirmed { + localized_string("tx-confirmed") + } else { + localized_string("tx-unconfirmed") + } } #[allow(clippy::too_many_arguments)] pub fn data_row_container<'a, 'b>( - tx_log_entry_wrap: &'a TxLogEntryWrap, - is_tx_expanded: bool, - expand_type: &'a ExpandType, - config: &Config, - column_config: &'b [(ColumnKey, Length, bool)], - is_odd: Option, - pending_confirmation: &Option, - node_synched: bool, + tx_log_entry_wrap: &'a TxLogEntryWrap, + is_tx_expanded: bool, + expand_type: &'a ExpandType, + config: &Config, + column_config: &'b [(ColumnKey, Length, bool)], + is_odd: Option, + pending_confirmation: &Option, + node_synched: bool, ) -> Container<'a, Message> { - let default_height = Length::Fixed(26.0); - let mut default_row_height = 26; - - let mut row_containers = vec![]; - - let id = tx_log_entry_wrap.tx.id.to_string(); - let mut tx_type = format!( - "{}", - tx_log_entry_wrap.tx.tx_type.to_string().replace("\n", "") - ); - let shared_tx_id = match tx_log_entry_wrap.tx.tx_slate_id { - Some(t) => t.to_string(), - None => "None".to_string(), - }; - let creation_time = tx_log_entry_wrap.tx.creation_ts.to_string(); - let ttl_cutoff = tx_log_entry_wrap.tx.ttl_cutoff_height; - let height = tx_log_entry_wrap.tx.kernel_lookup_min_height; - - let tx_cloned = tx_log_entry_wrap.clone(); - let tx_cloned_for_row = tx_log_entry_wrap.clone(); - - let creation_time = tx_log_entry_wrap.tx.creation_ts.to_string(); - let confirmation_time = tx_log_entry_wrap.tx.creation_ts.to_string(); - let net_diff = if tx_log_entry_wrap.tx.amount_credited >= tx_log_entry_wrap.tx.amount_debited { - amount_to_hr_string( - tx_log_entry_wrap.tx.amount_credited - tx_log_entry_wrap.tx.amount_debited, - true, - ) - } else { - format!( - "-{}", - amount_to_hr_string( - tx_log_entry_wrap.tx.amount_debited - tx_log_entry_wrap.tx.amount_credited, - true - ) - ) - }; - //TODO this will show the latest status - // Unconfirmed - Created time - // Confirmed - let status = create_tx_display_status(&tx_log_entry_wrap.tx); - - /*let version = tx - .version() - .map(str::to_string) - .unwrap_or_else(|| "-".to_string()); - let release_package = addon_cloned.relevant_release_package(global_release_channel);*/ - - /*if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::Title && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let title = Text::new(addon.title()).size(DEFAULT_FONT_SIZE); - - let mut title_row = Row::new().push(title).spacing(5).align_items(Align::Center); - - if addon.release_channel != ReleaseChannel::Default { - let release_channel = - Container::new(Text::new(addon.release_channel.to_string()).size(10)) - .style(style::ChannelBadge) - .padding(3); - - title_row = title_row.push(release_channel); - } - - let mut title_container = Container::new(title_row) - .padding(5) - .height(default_height) - .width(*width) - .center_y(); - if is_addon_expanded && matches!(expand_type, ExpandType::Details(_)) { - title_container = - title_container.style(style::SelectedBrightForegroundContainer); - } else { - title_container = - title_container.style(grin_gui_core::theme::container::Container::HoverableBrightForeground); - } - - row_containers.push((idx, title_container)); - }*/ - - if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::Id && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let display_id = Text::new(id.clone()).size(DEFAULT_FONT_SIZE); - - let id_container = Container::new(display_id) - .padding(5) - .height(default_height) - .width(*width) - .center_y() - .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); - - row_containers.push((idx, id_container)); - } - - if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::CreationTime && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let display_creation_time = Text::new(creation_time).size(DEFAULT_FONT_SIZE); - - let display_creation_time_container = Container::new(display_creation_time) - .padding(5) - .height(default_height) - .width(*width) - .center_y() - .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); - - row_containers.push((idx, display_creation_time_container)); - } - - if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::ConfirmationTime && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let display_confirmation_time = Text::new(confirmation_time).size(DEFAULT_FONT_SIZE); - - let display_confirmation_time_container = Container::new(display_confirmation_time) - .padding(5) - .height(default_height) - .width(*width) - .center_y() - .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); - - row_containers.push((idx, display_confirmation_time_container)); - } - - if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::NetDifference && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let display_net_difference = Text::new(net_diff).size(DEFAULT_FONT_SIZE); - - let display_net_difference_container = Container::new(display_net_difference) - .padding(5) - .height(default_height) - .width(*width) - .center_y() - .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); - - row_containers.push((idx, display_net_difference_container)); - } - - if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::Status && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let display_status = Text::new(status).size(DEFAULT_FONT_SIZE); - - let display_status_container = Container::new(display_status) - .padding(5) - .height(default_height) - .width(*width) - .center_y() - .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); - - row_containers.push((idx, display_status_container)); - } - - /*if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::Type && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let display_type = if let Some(package) = &release_package { - package.version.clone() - } else { - String::from("-") - }; - let remote_version = Text::new(remote_version).size(DEFAULT_FONT_SIZE); - - let mut remote_version_button = - Button::new(&mut addon.remote_version_btn_state, remote_version) - .style(grin_gui_core::theme::button::Button::NormalText); - - if changelog_url.is_some() { - remote_version_button = - remote_version_button.on_press(Interaction::Expand(ExpandType::Changelog { - addon: addon_cloned.clone(), - changelog: None, - })); - } - - let remote_version_button: Element = remote_version_button.into(); - - let remote_version_container = - Container::new(remote_version_button.map(Message::Interaction)) - .height(default_height) - .width(*width) - .center_y() - .style(grin_gui_core::theme::container::Container::HoverableForeground); - - row_containers.push((idx, remote_version_container)); - }*/ - - if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::Type && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let display_tx_type = Text::new(tx_type.clone()).size(SMALLER_FONT_SIZE); - let display_tx_type_container = Container::new(display_tx_type) - .height(default_height) - .width(*width) - .center_y() - .padding(5) - .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); - - row_containers.push((idx, display_tx_type_container)); - } - - /*if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::Author && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let author = Text::new(author.as_deref().unwrap_or("-")).size(DEFAULT_FONT_SIZE); - let author_container = Container::new(author) - .height(default_height) - .width(*width) - .center_y() - .padding(5) - .style(grin_gui_core::theme::container::Container::HoverableForeground); - - row_containers.push((idx, author_container)); - }*/ - - /*if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::GameVersion && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let game_version = - Text::new(game_version.as_deref().unwrap_or("-")).size(DEFAULT_FONT_SIZE); - let game_version_container = Container::new(game_version) - .height(default_height) - .width(*width) - .center_y() - .padding(5) - .style(grin_gui_core::theme::container::Container::HoverableForeground); - - row_containers.push((idx, game_version_container)); - }*/ - - /*if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::DateReleased && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let release_date_text: String = if let Some(package) = &release_package { - let f = localized_timeago_formatter(); - let now = Local::now(); - - if let Some(time) = package.date_time.as_ref() { - f.convert_chrono(*time, now) - } else { - "".to_string() - } - } else { - "-".to_string() - }; - let release_date_text = Text::new(release_date_text).size(DEFAULT_FONT_SIZE); - let game_version_container = Container::new(release_date_text) - .height(default_height) - .width(*width) - .center_y() - .padding(5) - .style(grin_gui_core::theme::container::Container::HoverableForeground); - - row_containers.push((idx, game_version_container)); - }*/ - - /*if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::Source && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let source_text = - repository_kind.map_or_else(|| localized_string("unknown"), |a| a.to_string()); - let source = Text::new(source_text).size(DEFAULT_FONT_SIZE); - let source_container = Container::new(source) - .height(default_height) - .width(*width) - .center_y() - .padding(5) - .style(grin_gui_core::theme::container::Container::HoverableForeground); - - row_containers.push((idx, source_container)); - }*/ - - /*if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::Summary && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let text = addon_cloned.notes().unwrap_or("-"); - let summary = Text::new(text).size(DEFAULT_FONT_SIZE); - let container = Container::new(summary) - .height(default_height) - .width(*width) - .center_y() - .padding(5) - .style(grin_gui_core::theme::container::Container::HoverableForeground); - - row_containers.push((idx, container)); - }*/ - - /*if let Some((idx, width)) = column_config - .iter() - .enumerate() - .filter_map(|(idx, (key, width, hidden))| { - if *key == ColumnKey::Status && !hidden { - Some((idx, width)) - } else { - None - } - }) - .next() - { - let update_button_container = match &addon.state { - AddonState::Idle => Container::new(Text::new("".to_string()).size(DEFAULT_FONT_SIZE)) - .height(default_height) - .width(*width) - .center_y() - .center_x() - .style(grin_gui_core::theme::container::Container::HoverableForeground), - AddonState::Completed => { - Container::new(Text::new(localized_string("completed")).size(DEFAULT_FONT_SIZE)) - .height(default_height) - .width(*width) - .center_y() - .center_x() - .style(grin_gui_core::theme::container::Container::HoverableForeground) - } - AddonState::Error(message) => { - Container::new(Text::new(message).size(DEFAULT_FONT_SIZE)) - .height(default_height) - .width(*width) - .center_y() - .center_x() - .style(grin_gui_core::theme::container::Container::HoverableForeground) - } - AddonState::Updatable | AddonState::Retry => { - let id = addon.primary_folder_id.clone(); - - let text = match addon.state { - AddonState::Updatable => localized_string("update"), - AddonState::Retry => localized_string("retry"), - _ => "".to_owned(), - }; - - let update_wrapper = Container::new(Text::new(text).size(DEFAULT_FONT_SIZE)) - .width(*width) - .center_x() - .align_x(Align::Center); - let update_button: Element = - Button::new(&mut addon.update_btn_state, update_wrapper) - .width(Length::FillPortion(1)) - .style(style::SecondaryButton) - .on_press(Interaction::Update(id)) - .into(); - - Container::new(update_button.map(Message::Interaction)) - .height(default_height) - .width(*width) - .center_y() - .center_x() - .style(grin_gui_core::theme::container::Container::HoverableBrightForeground) - } - AddonState::Downloading => { - Container::new(Text::new(localized_string("downloading")).size(DEFAULT_FONT_SIZE)) - .height(default_height) - .width(*width) - .center_y() - .center_x() - .padding(5) - .style(grin_gui_core::theme::container::Container::HoverableForeground) - } - AddonState::Unpacking => { - Container::new(Text::new(localized_string("unpacking")).size(DEFAULT_FONT_SIZE)) - .height(default_height) - .width(*width) - .center_y() - .center_x() - .padding(5) - .style(grin_gui_core::theme::container::Container::HoverableForeground) - } - AddonState::Fingerprint => { - Container::new(Text::new(localized_string("hashing")).size(DEFAULT_FONT_SIZE)) - .height(default_height) - .width(*width) - .center_y() - .center_x() - .padding(5) - .style(grin_gui_core::theme::container::Container::HoverableForeground) - } - AddonState::Ignored => { - Container::new(Text::new(localized_string("ignored")).size(DEFAULT_FONT_SIZE)) - .height(default_height) - .width(*width) - .center_y() - .center_x() - .padding(5) - .style(grin_gui_core::theme::container::Container::HoverableForeground) - } - AddonState::Unknown => Container::new(Text::new("").size(DEFAULT_FONT_SIZE)) - .height(default_height) - .width(*width) - .center_y() - .center_x() - .padding(5) - .style(grin_gui_core::theme::container::Container::HoverableForeground), - }; - - row_containers.push((idx, update_button_container)); - }*/ - - let left_spacer = Space::new(Length::Fixed(DEFAULT_PADDING), Length::Fixed(0.0)); - let right_spacer = Space::new(Length::Fixed(DEFAULT_PADDING + 5.0), Length::Fixed(0.0)); - - //let mut row = Row::new().push(left_spacer).spacing(1); - let mut row = Row::new().spacing(1); - - // Sort columns and push them into row - row_containers.sort_by(|a, b| a.0.cmp(&b.0)); - for (_, elem) in row_containers.into_iter() { - row = row.push(elem); - } - - row = row.push(right_spacer); - - let mut tx_column = Column::new().push(row); - let mut action_button_row = Row::new(); - - if is_tx_expanded { - match expand_type { - ExpandType::Details(_) => { - let button_width = Length::Fixed(BUTTON_WIDTH); - - // ID - let id_title_text = - Text::new(format!("{}: ", localized_string("tx-id"))).size(DEFAULT_FONT_SIZE); - let id_title_container = Container::new(id_title_text) - .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); - - let id_text = Text::new(id).size(DEFAULT_FONT_SIZE); - let id_text_container = Container::new(id_text) - .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); - - let id_row = Row::new() - .push(id_title_container) - .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) - .push(id_text_container); - - // UUID - let uuid_title_text = Text::new(format!("{}: ", localized_string("tx-shared-id"))) - .size(DEFAULT_FONT_SIZE); - let uuid_title_container = Container::new(uuid_title_text) - .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); - - let uuid_text = Text::new(shared_tx_id).size(DEFAULT_FONT_SIZE); - let uuid_text_container = Container::new(uuid_text) - .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); - - let uuid_row = Row::new() - .push(uuid_title_container) - .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) - .push(uuid_text_container); - - // Transaction type - let type_title_text = - Text::new(format!("{}: ", localized_string("tx-type"))).size(DEFAULT_FONT_SIZE); - let type_title_container = Container::new(type_title_text) - .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); - - let type_text = Text::new(tx_type).size(DEFAULT_FONT_SIZE); - let type_text_container = Container::new(type_text) - .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); - - let type_row = Row::new() - .push(type_title_container) - .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) - .push(type_text_container); - - /*let notes = notes.unwrap_or_else(|| localized_string("no-addon-description")); - let author = author.unwrap_or_else(|| "-".to_string());*/ - let left_spacer = Space::new(Length::Fixed(DEFAULT_PADDING), Length::Fixed(0.0)); - let space = Space::new(Length::Fixed(0.0), Length::Fixed(DEFAULT_PADDING * 2.0)); - let bottom_space = Space::new(Length::Fixed(0.0), Length::Fixed(4.0)); - - let confirmed = tx_cloned.tx.confirmed; - - let tx_details_container = Container::new( - Text::new(localized_string("tx-details")).size(DEFAULT_FONT_SIZE), - ) - .width(button_width) - .align_y(alignment::Vertical::Center) - .align_x(alignment::Horizontal::Center); - - let tx_details_button: Element = Button::new(tx_details_container) - .width(Length::Fixed(BUTTON_WIDTH)) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletOperationHomeViewInteraction( - super::home::LocalViewInteraction::TxDetails(tx_cloned.clone()), - )) - .into(); - - let tx_details_wrap = - Container::new(tx_details_button.map(Message::Interaction)).padding(1.0); - let tx_details_wrap = Container::new(tx_details_wrap) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - action_button_row = Row::new() - .push(Space::new( - Length::Fixed(DEFAULT_PADDING * 3.0), - Length::Fixed(0.0), - )) - .push(tx_details_wrap) - .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))); - - // Invoice proof view/copy paste - if config.tx_method == TxMethod::Contracts { - let tx_proof_container = Container::new( - Text::new(localized_string("tx-proof")).size(DEFAULT_FONT_SIZE), - ) - .width(button_width) - .align_y(alignment::Vertical::Center) - .align_x(alignment::Horizontal::Center); - - let tx_proof_button: Element = Button::new(tx_proof_container) - .width(Length::Fixed(BUTTON_WIDTH)) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletOperationHomeViewInteraction( - super::home::LocalViewInteraction::TxProof(tx_cloned), - )) - .into(); - - let tx_proof_wrap = - Container::new(tx_proof_button.map(Message::Interaction)).padding(1.0); - let tx_proof_wrap = Container::new(tx_proof_wrap) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - action_button_row = action_button_row - .push(tx_proof_wrap) - .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))); - } - - if !confirmed { - // Re-fetch the slate representing the last saved state - let tx_reload_slate_container = Container::new( - Text::new(localized_string("tx-reload-slate")).size(DEFAULT_FONT_SIZE), - ) - .width(Length::Fixed(BUTTON_WIDTH * 2.0)) - .align_y(alignment::Vertical::Center) - .align_x(alignment::Horizontal::Center); - - let tx_reload_slate_button: Element = - Button::new(tx_reload_slate_container) - .width(Length::Fixed(BUTTON_WIDTH * 2.0)) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletOperationHomeViewInteraction( - super::home::LocalViewInteraction::ReloadTxSlate( - tx_cloned_for_row.tx.tx_slate_id.unwrap().to_string(), - ), - )) - .into(); - - let tx_reload_slate_wrap = - Container::new(tx_reload_slate_button.map(Message::Interaction)).padding(1); - let tx_reload_slate_wrap = Container::new(tx_reload_slate_wrap) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - // Present cancel button - let tx_button_cancel_container = Container::new( - Text::new(localized_string("cancel-tx")).size(DEFAULT_FONT_SIZE), - ) - .width(button_width) - .align_y(alignment::Vertical::Center) - .align_x(alignment::Horizontal::Center); - - let mut tx_cancel_button = Button::new(tx_button_cancel_container) - .width(Length::Fixed(BUTTON_WIDTH)) - .style(grin_gui_core::theme::ButtonStyle::Primary); - - if node_synched { - tx_cancel_button = tx_cancel_button.on_press( - Interaction::WalletOperationHomeViewInteraction( - super::home::LocalViewInteraction::CancelTx( - tx_log_entry_wrap.tx.id, - tx_log_entry_wrap.tx.tx_slate_id.unwrap().to_string(), - ), - ), - ); - } - let tx_cancel_button: Element = tx_cancel_button.into(); - - let tx_cancel_wrap = - Container::new(tx_cancel_button.map(Message::Interaction)).padding(1); - let tx_cancel_wrap = Container::new(tx_cancel_wrap) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - action_button_row = action_button_row - .push(tx_reload_slate_wrap) - .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))) - .push(tx_cancel_wrap) - } - - /* - let notes_title_text = - Text::new(localized_string("summary")).size(DEFAULT_FONT_SIZE); - let notes_text = Text::new(notes).size(DEFAULT_FONT_SIZE); - let author_text = Text::new(author).size(DEFAULT_FONT_SIZE); - let author_title_text = - Text::new(localized_string("authors")).size(DEFAULT_FONT_SIZE); - let author_title_container = Container::new(author_title_text) - .style(grin_gui_core::theme::container::Container::HoverableBrightForeground); - let notes_title_container = Container::new(notes_title_text) - .style(grin_gui_core::theme::container::Container::HoverableBrightForeground); - - let release_date_text: String = if let Some(package) = &release_package { - let f = localized_timeago_formatter(); - let now = Local::now(); - - if let Some(time) = package.date_time.as_ref() { - f.convert_chrono(*time, now) - } else { - "".to_string() - } - } else { - localized_string("release-channel-no-release") - }; - let release_date_text = Text::new(release_date_text).size(DEFAULT_FONT_SIZE); - let release_date_text_container = Container::new(release_date_text) - .center_y() - .padding(5) - .style(grin_gui_core::theme::container::Container::NormalBackground); - - let release_channel_title = - Text::new(localized_string("remote-release-channel")).size(DEFAULT_FONT_SIZE); - let release_channel_title_container = Container::new(release_channel_title) - .style(grin_gui_core::theme::container::Container::NormalBackground); - let release_channel_list = PickList::new( - &mut addon.pick_release_channel_state, - &ReleaseChannel::ALL[..], - Some(addon.release_channel), - Message::ReleaseChannelSelected, - ) - .text_size(14) - .width(Length::Fixed(100)) - .style(style::PickList); - - let mut website_button = Button::new( - &mut addon.website_btn_state, - Text::new(localized_string("website")).size(DEFAULT_FONT_SIZE), - ) - .style(grin_gui_core::theme::button::Button::Primary); - - if let Some(link) = website_url { - website_button = website_button.on_press(Interaction::OpenLink(link)); - } - - let website_button: Element = website_button.into(); - - let is_ignored = addon.state == AddonState::Ignored; - let ignore_button_text = if is_ignored { - Text::new(localized_string("unignore")).size(DEFAULT_FONT_SIZE) - } else { - Text::new(localized_string("ignore")).size(DEFAULT_FONT_SIZE) - }; - - let mut ignore_button = - Button::new(&mut addon.ignore_btn_state, ignore_button_text) - .on_press(Interaction::Ignore(addon.primary_folder_id.clone())) - .style(grin_gui_core::theme::button::Button::Primary); - - if is_ignored { - ignore_button = ignore_button - .on_press(Interaction::Unignore(addon.primary_folder_id.clone())); - } else { - ignore_button = ignore_button - .on_press(Interaction::Ignore(addon.primary_folder_id.clone())); - } - - let ignore_button: Element = ignore_button.into(); - - let (title, interaction) = if Some(Confirm::DeleteAddon) == *pending_confirmation { - ( - localized_string("confirm-deletion"), - Interaction::ConfirmDeleteAddon(addon.primary_folder_id.clone()), - ) - } else { - let mut vars = HashMap::new(); - vars.insert("addon".to_string(), addon_cloned.title()); - let fmt = localized_string("delete-addon"); - - (strfmt(&fmt, &vars).unwrap(), Interaction::DeleteAddon()) - }; - - let delete_button: Element = Button::new( - &mut addon.delete_btn_state, - Text::new(title).size(DEFAULT_FONT_SIZE), - ) - .on_press(interaction) - .style(style::DefaultDeleteButton) - .into(); - - let (title, interaction) = if Some(Confirm::DeleteSavedVariables) - == *pending_confirmation - { - ( - localized_string("confirm-deletion"), - Interaction::ConfirmDeleteSavedVariables(addon.primary_folder_id.clone()), - ) - } else { - ( - localized_string("delete-addon-saved-variables"), - Interaction::DeleteSavedVariables(), - ) - }; - let delete_savedvariables_button: Element = Button::new( - &mut addon.delete_saved_variables_btn_state, - Text::new(title).size(DEFAULT_FONT_SIZE), - ) - .on_press(interaction) - .style(style::DefaultDeleteButton) - .into(); - - let mut changelog_button = Button::new( - &mut addon.changelog_btn_state, - Text::new(localized_string("changelog")).size(DEFAULT_FONT_SIZE), - ) - .style(grin_gui_core::theme::button::Button::Primary); - - if changelog_url.is_some() { - changelog_button = - changelog_button.on_press(Interaction::Expand(ExpandType::Changelog { - addon: addon_cloned, - changelog: None, - })); - } - - let changelog_button: Element = changelog_button.into();*/ - - /*let test_row = Row::new() - .push(release_channel_list) - .push(release_date_text_container);*/ - - /*let button_row = Row::new() - .push(Space::new(Length::Fill, Length::Fixed(0.0))) - .push(website_button.map(Message::Interaction)) - .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) - .push(changelog_button.map(Message::Interaction)) - .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) - .push(ignore_button.map(Message::Interaction)) - .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) - .push(delete_savedvariables_button.map(Message::Interaction)) - .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) - .push(delete_button.map(Message::Interaction)) - .width(Length::Fill);*/ - let column = Column::new() - .push(id_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(3.0))) - .push(uuid_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(3.0))) - .push(type_row); - //.push(Space::new(Length::Fixed(0.0), Length::Fixed(3))) - /* .push(notes_title_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(3))) - .push(notes_text) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(15))) - .push(release_channel_title_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(3))) - .push(test_row) - .push(space) - .push(button_row)*/ - //.push(bottom_space); - let details_container = Container::new(column) - .width(Length::Fill) - .padding(20) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let row = Row::new() - .push(left_spacer) - .push(details_container) - .push(Space::new( - Length::Fixed(DEFAULT_PADDING + 5.0), - Length::Fixed(0.0), - )) - .spacing(1); - tx_column = tx_column - .push(Space::new(Length::FillPortion(1), Length::Fixed(1.0))) - .push(row); - } - ExpandType::None => {} - } - } - - let mut table_row = TableRow::new(tx_column) - .width(Length::Fill) - .inner_row_height(default_row_height) - .on_press(move |_| { - Message::Interaction(Interaction::WalletOperationTxListInteraction( - LocalViewInteraction::Expand(ExpandType::Details(tx_cloned_for_row.clone())), - )) - }); - - if is_odd == Some(true) { - table_row = table_row.style(grin_gui_core::theme::TableRowStyle::TableRowAlternate) - } else { - table_row = table_row.style(grin_gui_core::theme::TableRowStyle::Default) - } - - // Due to what feels like an iced-rs bug, don't put buttons within the actual row as they appear - // to clear their state between the press down and up event if included within the row itself - // Some kind of fix to the table row widget might rectify this - let mut return_column = Column::new().push(table_row).push(action_button_row); - - if is_tx_expanded { - return_column = return_column.push(Space::new( - Length::Fixed(0.0), - Length::Fixed(DEFAULT_PADDING * 2.0), - )); - } - - let return_container = Container::new(return_column); - - return_container + let default_height = Length::Fixed(26.0); + let mut default_row_height = 26; + + let mut row_containers = vec![]; + + let id = tx_log_entry_wrap.tx.id.to_string(); + let mut tx_type = format!( + "{}", + tx_log_entry_wrap.tx.tx_type.to_string().replace("\n", "") + ); + let shared_tx_id = match tx_log_entry_wrap.tx.tx_slate_id { + Some(t) => t.to_string(), + None => "None".to_string(), + }; + let creation_time = tx_log_entry_wrap.tx.creation_ts.to_string(); + let ttl_cutoff = tx_log_entry_wrap.tx.ttl_cutoff_height; + let height = tx_log_entry_wrap.tx.kernel_lookup_min_height; + + let tx_cloned = tx_log_entry_wrap.clone(); + let tx_cloned_for_row = tx_log_entry_wrap.clone(); + + let creation_time = tx_log_entry_wrap.tx.creation_ts.to_string(); + let confirmation_time = tx_log_entry_wrap.tx.creation_ts.to_string(); + let net_diff = if tx_log_entry_wrap.tx.amount_credited >= tx_log_entry_wrap.tx.amount_debited { + amount_to_hr_string( + tx_log_entry_wrap.tx.amount_credited - tx_log_entry_wrap.tx.amount_debited, + true, + ) + } else { + format!( + "-{}", + amount_to_hr_string( + tx_log_entry_wrap.tx.amount_debited - tx_log_entry_wrap.tx.amount_credited, + true + ) + ) + }; + //TODO this will show the latest status + // Unconfirmed - Created time + // Confirmed + let status = create_tx_display_status(&tx_log_entry_wrap.tx); + + /*let version = tx + .version() + .map(str::to_string) + .unwrap_or_else(|| "-".to_string()); + let release_package = addon_cloned.relevant_release_package(global_release_channel);*/ + + /*if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::Title && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let title = Text::new(addon.title()).size(DEFAULT_FONT_SIZE); + + let mut title_row = Row::new().push(title).spacing(5).align_items(Align::Center); + + if addon.release_channel != ReleaseChannel::Default { + let release_channel = + Container::new(Text::new(addon.release_channel.to_string()).size(10)) + .style(style::ChannelBadge) + .padding(3); + + title_row = title_row.push(release_channel); + } + + let mut title_container = Container::new(title_row) + .padding(5) + .height(default_height) + .width(*width) + .center_y(); + if is_addon_expanded && matches!(expand_type, ExpandType::Details(_)) { + title_container = + title_container.style(style::SelectedBrightForegroundContainer); + } else { + title_container = + title_container.style(grin_gui_core::theme::container::Container::HoverableBrightForeground); + } + + row_containers.push((idx, title_container)); + }*/ + + if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::Id && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let display_id = Text::new(id.clone()).size(DEFAULT_FONT_SIZE); + + let id_container = Container::new(display_id) + .padding(5) + .height(default_height) + .width(*width) + .center_y() + .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); + + row_containers.push((idx, id_container)); + } + + if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::CreationTime && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let display_creation_time = Text::new(creation_time).size(DEFAULT_FONT_SIZE); + + let display_creation_time_container = Container::new(display_creation_time) + .padding(5) + .height(default_height) + .width(*width) + .center_y() + .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); + + row_containers.push((idx, display_creation_time_container)); + } + + if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::ConfirmationTime && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let display_confirmation_time = Text::new(confirmation_time).size(DEFAULT_FONT_SIZE); + + let display_confirmation_time_container = Container::new(display_confirmation_time) + .padding(5) + .height(default_height) + .width(*width) + .center_y() + .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); + + row_containers.push((idx, display_confirmation_time_container)); + } + + if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::NetDifference && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let display_net_difference = Text::new(net_diff).size(DEFAULT_FONT_SIZE); + + let display_net_difference_container = Container::new(display_net_difference) + .padding(5) + .height(default_height) + .width(*width) + .center_y() + .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); + + row_containers.push((idx, display_net_difference_container)); + } + + if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::Status && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let display_status = Text::new(status).size(DEFAULT_FONT_SIZE); + + let display_status_container = Container::new(display_status) + .padding(5) + .height(default_height) + .width(*width) + .center_y() + .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); + + row_containers.push((idx, display_status_container)); + } + + /*if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::Type && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let display_type = if let Some(package) = &release_package { + package.version.clone() + } else { + String::from("-") + }; + let remote_version = Text::new(remote_version).size(DEFAULT_FONT_SIZE); + + let mut remote_version_button = + Button::new(&mut addon.remote_version_btn_state, remote_version) + .style(grin_gui_core::theme::button::Button::NormalText); + + if changelog_url.is_some() { + remote_version_button = + remote_version_button.on_press(Interaction::Expand(ExpandType::Changelog { + addon: addon_cloned.clone(), + changelog: None, + })); + } + + let remote_version_button: Element = remote_version_button.into(); + + let remote_version_container = + Container::new(remote_version_button.map(Message::Interaction)) + .height(default_height) + .width(*width) + .center_y() + .style(grin_gui_core::theme::container::Container::HoverableForeground); + + row_containers.push((idx, remote_version_container)); + }*/ + + if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::Type && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let display_tx_type = Text::new(tx_type.clone()).size(SMALLER_FONT_SIZE); + let display_tx_type_container = Container::new(display_tx_type) + .height(default_height) + .width(*width) + .center_y() + .padding(5) + .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); + + row_containers.push((idx, display_tx_type_container)); + } + + /*if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::Author && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let author = Text::new(author.as_deref().unwrap_or("-")).size(DEFAULT_FONT_SIZE); + let author_container = Container::new(author) + .height(default_height) + .width(*width) + .center_y() + .padding(5) + .style(grin_gui_core::theme::container::Container::HoverableForeground); + + row_containers.push((idx, author_container)); + }*/ + + /*if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::GameVersion && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let game_version = + Text::new(game_version.as_deref().unwrap_or("-")).size(DEFAULT_FONT_SIZE); + let game_version_container = Container::new(game_version) + .height(default_height) + .width(*width) + .center_y() + .padding(5) + .style(grin_gui_core::theme::container::Container::HoverableForeground); + + row_containers.push((idx, game_version_container)); + }*/ + + /*if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::DateReleased && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let release_date_text: String = if let Some(package) = &release_package { + let f = localized_timeago_formatter(); + let now = Local::now(); + + if let Some(time) = package.date_time.as_ref() { + f.convert_chrono(*time, now) + } else { + "".to_string() + } + } else { + "-".to_string() + }; + let release_date_text = Text::new(release_date_text).size(DEFAULT_FONT_SIZE); + let game_version_container = Container::new(release_date_text) + .height(default_height) + .width(*width) + .center_y() + .padding(5) + .style(grin_gui_core::theme::container::Container::HoverableForeground); + + row_containers.push((idx, game_version_container)); + }*/ + + /*if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::Source && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let source_text = + repository_kind.map_or_else(|| localized_string("unknown"), |a| a.to_string()); + let source = Text::new(source_text).size(DEFAULT_FONT_SIZE); + let source_container = Container::new(source) + .height(default_height) + .width(*width) + .center_y() + .padding(5) + .style(grin_gui_core::theme::container::Container::HoverableForeground); + + row_containers.push((idx, source_container)); + }*/ + + /*if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::Summary && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let text = addon_cloned.notes().unwrap_or("-"); + let summary = Text::new(text).size(DEFAULT_FONT_SIZE); + let container = Container::new(summary) + .height(default_height) + .width(*width) + .center_y() + .padding(5) + .style(grin_gui_core::theme::container::Container::HoverableForeground); + + row_containers.push((idx, container)); + }*/ + + /*if let Some((idx, width)) = column_config + .iter() + .enumerate() + .filter_map(|(idx, (key, width, hidden))| { + if *key == ColumnKey::Status && !hidden { + Some((idx, width)) + } else { + None + } + }) + .next() + { + let update_button_container = match &addon.state { + AddonState::Idle => Container::new(Text::new("".to_string()).size(DEFAULT_FONT_SIZE)) + .height(default_height) + .width(*width) + .center_y() + .center_x() + .style(grin_gui_core::theme::container::Container::HoverableForeground), + AddonState::Completed => { + Container::new(Text::new(localized_string("completed")).size(DEFAULT_FONT_SIZE)) + .height(default_height) + .width(*width) + .center_y() + .center_x() + .style(grin_gui_core::theme::container::Container::HoverableForeground) + } + AddonState::Error(message) => { + Container::new(Text::new(message).size(DEFAULT_FONT_SIZE)) + .height(default_height) + .width(*width) + .center_y() + .center_x() + .style(grin_gui_core::theme::container::Container::HoverableForeground) + } + AddonState::Updatable | AddonState::Retry => { + let id = addon.primary_folder_id.clone(); + + let text = match addon.state { + AddonState::Updatable => localized_string("update"), + AddonState::Retry => localized_string("retry"), + _ => "".to_owned(), + }; + + let update_wrapper = Container::new(Text::new(text).size(DEFAULT_FONT_SIZE)) + .width(*width) + .center_x() + .align_x(Align::Center); + let update_button: Element = + Button::new(&mut addon.update_btn_state, update_wrapper) + .width(Length::FillPortion(1)) + .style(style::SecondaryButton) + .on_press(Interaction::Update(id)) + .into(); + + Container::new(update_button.map(Message::Interaction)) + .height(default_height) + .width(*width) + .center_y() + .center_x() + .style(grin_gui_core::theme::container::Container::HoverableBrightForeground) + } + AddonState::Downloading => { + Container::new(Text::new(localized_string("downloading")).size(DEFAULT_FONT_SIZE)) + .height(default_height) + .width(*width) + .center_y() + .center_x() + .padding(5) + .style(grin_gui_core::theme::container::Container::HoverableForeground) + } + AddonState::Unpacking => { + Container::new(Text::new(localized_string("unpacking")).size(DEFAULT_FONT_SIZE)) + .height(default_height) + .width(*width) + .center_y() + .center_x() + .padding(5) + .style(grin_gui_core::theme::container::Container::HoverableForeground) + } + AddonState::Fingerprint => { + Container::new(Text::new(localized_string("hashing")).size(DEFAULT_FONT_SIZE)) + .height(default_height) + .width(*width) + .center_y() + .center_x() + .padding(5) + .style(grin_gui_core::theme::container::Container::HoverableForeground) + } + AddonState::Ignored => { + Container::new(Text::new(localized_string("ignored")).size(DEFAULT_FONT_SIZE)) + .height(default_height) + .width(*width) + .center_y() + .center_x() + .padding(5) + .style(grin_gui_core::theme::container::Container::HoverableForeground) + } + AddonState::Unknown => Container::new(Text::new("").size(DEFAULT_FONT_SIZE)) + .height(default_height) + .width(*width) + .center_y() + .center_x() + .padding(5) + .style(grin_gui_core::theme::container::Container::HoverableForeground), + }; + + row_containers.push((idx, update_button_container)); + }*/ + + let left_spacer = Space::new(Length::Fixed(DEFAULT_PADDING), Length::Fixed(0.0)); + let right_spacer = Space::new(Length::Fixed(DEFAULT_PADDING + 5.0), Length::Fixed(0.0)); + + //let mut row = Row::new().push(left_spacer).spacing(1); + let mut row = Row::new().spacing(1); + + // Sort columns and push them into row + row_containers.sort_by(|a, b| a.0.cmp(&b.0)); + for (_, elem) in row_containers.into_iter() { + row = row.push(elem); + } + + row = row.push(right_spacer); + + let mut tx_column = Column::new().push(row); + let mut action_button_row = Row::new(); + + if is_tx_expanded { + match expand_type { + ExpandType::Details(_) => { + let button_width = Length::Fixed(BUTTON_WIDTH); + + // ID + let id_title_text = + Text::new(format!("{}: ", localized_string("tx-id"))).size(DEFAULT_FONT_SIZE); + let id_title_container = Container::new(id_title_text) + .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); + + let id_text = Text::new(id).size(DEFAULT_FONT_SIZE); + let id_text_container = Container::new(id_text) + .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); + + let id_row = Row::new() + .push(id_title_container) + .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) + .push(id_text_container); + + // UUID + let uuid_title_text = Text::new(format!("{}: ", localized_string("tx-shared-id"))) + .size(DEFAULT_FONT_SIZE); + let uuid_title_container = Container::new(uuid_title_text) + .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); + + let uuid_text = Text::new(shared_tx_id).size(DEFAULT_FONT_SIZE); + let uuid_text_container = Container::new(uuid_text) + .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); + + let uuid_row = Row::new() + .push(uuid_title_container) + .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) + .push(uuid_text_container); + + // Transaction type + let type_title_text = + Text::new(format!("{}: ", localized_string("tx-type"))).size(DEFAULT_FONT_SIZE); + let type_title_container = Container::new(type_title_text) + .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); + + let type_text = Text::new(tx_type).size(DEFAULT_FONT_SIZE); + let type_text_container = Container::new(type_text) + .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); + + let type_row = Row::new() + .push(type_title_container) + .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) + .push(type_text_container); + + /*let notes = notes.unwrap_or_else(|| localized_string("no-addon-description")); + let author = author.unwrap_or_else(|| "-".to_string());*/ + let left_spacer = Space::new(Length::Fixed(DEFAULT_PADDING), Length::Fixed(0.0)); + let space = Space::new(Length::Fixed(0.0), Length::Fixed(DEFAULT_PADDING * 2.0)); + let bottom_space = Space::new(Length::Fixed(0.0), Length::Fixed(4.0)); + + let confirmed = tx_cloned.tx.confirmed; + + let tx_details_container = Container::new( + Text::new(localized_string("tx-details")).size(DEFAULT_FONT_SIZE), + ) + .width(button_width) + .align_y(alignment::Vertical::Center) + .align_x(alignment::Horizontal::Center); + + let tx_details_button: Element = Button::new(tx_details_container) + .width(Length::Fixed(BUTTON_WIDTH)) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletOperationHomeViewInteraction( + super::home::LocalViewInteraction::TxDetails(tx_cloned.clone()), + )) + .into(); + + let tx_details_wrap = + Container::new(tx_details_button.map(Message::Interaction)).padding(1.0); + let tx_details_wrap = Container::new(tx_details_wrap) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + action_button_row = Row::new() + .push(Space::new( + Length::Fixed(DEFAULT_PADDING * 3.0), + Length::Fixed(0.0), + )) + .push(tx_details_wrap) + .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))); + + // Invoice proof view/copy paste + if config.tx_method == TxMethod::Contracts { + let tx_proof_container = Container::new( + Text::new(localized_string("tx-proof")).size(DEFAULT_FONT_SIZE), + ) + .width(button_width) + .align_y(alignment::Vertical::Center) + .align_x(alignment::Horizontal::Center); + + let tx_proof_button: Element = Button::new(tx_proof_container) + .width(Length::Fixed(BUTTON_WIDTH)) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletOperationHomeViewInteraction( + super::home::LocalViewInteraction::TxProof(tx_cloned), + )) + .into(); + + let tx_proof_wrap = + Container::new(tx_proof_button.map(Message::Interaction)).padding(1.0); + let tx_proof_wrap = Container::new(tx_proof_wrap) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + action_button_row = action_button_row + .push(tx_proof_wrap) + .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))); + } + + if !confirmed { + // Re-fetch the slate representing the last saved state + let tx_reload_slate_container = Container::new( + Text::new(localized_string("tx-reload-slate")).size(DEFAULT_FONT_SIZE), + ) + .width(Length::Fixed(BUTTON_WIDTH * 2.0)) + .align_y(alignment::Vertical::Center) + .align_x(alignment::Horizontal::Center); + + let tx_reload_slate_button: Element = + Button::new(tx_reload_slate_container) + .width(Length::Fixed(BUTTON_WIDTH * 2.0)) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletOperationHomeViewInteraction( + super::home::LocalViewInteraction::ReloadTxSlate( + tx_cloned_for_row.tx.tx_slate_id.unwrap().to_string(), + ), + )) + .into(); + + let tx_reload_slate_wrap = + Container::new(tx_reload_slate_button.map(Message::Interaction)).padding(1); + let tx_reload_slate_wrap = Container::new(tx_reload_slate_wrap) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + // Present cancel button + let tx_button_cancel_container = Container::new( + Text::new(localized_string("cancel-tx")).size(DEFAULT_FONT_SIZE), + ) + .width(button_width) + .align_y(alignment::Vertical::Center) + .align_x(alignment::Horizontal::Center); + + let mut tx_cancel_button = Button::new(tx_button_cancel_container) + .width(Length::Fixed(BUTTON_WIDTH)) + .style(grin_gui_core::theme::ButtonStyle::Primary); + + if node_synched { + tx_cancel_button = tx_cancel_button.on_press( + Interaction::WalletOperationHomeViewInteraction( + super::home::LocalViewInteraction::CancelTx( + tx_log_entry_wrap.tx.id, + tx_log_entry_wrap.tx.tx_slate_id.unwrap().to_string(), + ), + ), + ); + } + let tx_cancel_button: Element = tx_cancel_button.into(); + + let tx_cancel_wrap = + Container::new(tx_cancel_button.map(Message::Interaction)).padding(1); + let tx_cancel_wrap = Container::new(tx_cancel_wrap) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + action_button_row = action_button_row + .push(tx_reload_slate_wrap) + .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))) + .push(tx_cancel_wrap) + } + + /* + let notes_title_text = + Text::new(localized_string("summary")).size(DEFAULT_FONT_SIZE); + let notes_text = Text::new(notes).size(DEFAULT_FONT_SIZE); + let author_text = Text::new(author).size(DEFAULT_FONT_SIZE); + let author_title_text = + Text::new(localized_string("authors")).size(DEFAULT_FONT_SIZE); + let author_title_container = Container::new(author_title_text) + .style(grin_gui_core::theme::container::Container::HoverableBrightForeground); + let notes_title_container = Container::new(notes_title_text) + .style(grin_gui_core::theme::container::Container::HoverableBrightForeground); + + let release_date_text: String = if let Some(package) = &release_package { + let f = localized_timeago_formatter(); + let now = Local::now(); + + if let Some(time) = package.date_time.as_ref() { + f.convert_chrono(*time, now) + } else { + "".to_string() + } + } else { + localized_string("release-channel-no-release") + }; + let release_date_text = Text::new(release_date_text).size(DEFAULT_FONT_SIZE); + let release_date_text_container = Container::new(release_date_text) + .center_y() + .padding(5) + .style(grin_gui_core::theme::container::Container::NormalBackground); + + let release_channel_title = + Text::new(localized_string("remote-release-channel")).size(DEFAULT_FONT_SIZE); + let release_channel_title_container = Container::new(release_channel_title) + .style(grin_gui_core::theme::container::Container::NormalBackground); + let release_channel_list = PickList::new( + &mut addon.pick_release_channel_state, + &ReleaseChannel::ALL[..], + Some(addon.release_channel), + Message::ReleaseChannelSelected, + ) + .text_size(14) + .width(Length::Fixed(100)) + .style(style::PickList); + + let mut website_button = Button::new( + &mut addon.website_btn_state, + Text::new(localized_string("website")).size(DEFAULT_FONT_SIZE), + ) + .style(grin_gui_core::theme::button::Button::Primary); + + if let Some(link) = website_url { + website_button = website_button.on_press(Interaction::OpenLink(link)); + } + + let website_button: Element = website_button.into(); + + let is_ignored = addon.state == AddonState::Ignored; + let ignore_button_text = if is_ignored { + Text::new(localized_string("unignore")).size(DEFAULT_FONT_SIZE) + } else { + Text::new(localized_string("ignore")).size(DEFAULT_FONT_SIZE) + }; + + let mut ignore_button = + Button::new(&mut addon.ignore_btn_state, ignore_button_text) + .on_press(Interaction::Ignore(addon.primary_folder_id.clone())) + .style(grin_gui_core::theme::button::Button::Primary); + + if is_ignored { + ignore_button = ignore_button + .on_press(Interaction::Unignore(addon.primary_folder_id.clone())); + } else { + ignore_button = ignore_button + .on_press(Interaction::Ignore(addon.primary_folder_id.clone())); + } + + let ignore_button: Element = ignore_button.into(); + + let (title, interaction) = if Some(Confirm::DeleteAddon) == *pending_confirmation { + ( + localized_string("confirm-deletion"), + Interaction::ConfirmDeleteAddon(addon.primary_folder_id.clone()), + ) + } else { + let mut vars = HashMap::new(); + vars.insert("addon".to_string(), addon_cloned.title()); + let fmt = localized_string("delete-addon"); + + (strfmt(&fmt, &vars).unwrap(), Interaction::DeleteAddon()) + }; + + let delete_button: Element = Button::new( + &mut addon.delete_btn_state, + Text::new(title).size(DEFAULT_FONT_SIZE), + ) + .on_press(interaction) + .style(style::DefaultDeleteButton) + .into(); + + let (title, interaction) = if Some(Confirm::DeleteSavedVariables) + == *pending_confirmation + { + ( + localized_string("confirm-deletion"), + Interaction::ConfirmDeleteSavedVariables(addon.primary_folder_id.clone()), + ) + } else { + ( + localized_string("delete-addon-saved-variables"), + Interaction::DeleteSavedVariables(), + ) + }; + let delete_savedvariables_button: Element = Button::new( + &mut addon.delete_saved_variables_btn_state, + Text::new(title).size(DEFAULT_FONT_SIZE), + ) + .on_press(interaction) + .style(style::DefaultDeleteButton) + .into(); + + let mut changelog_button = Button::new( + &mut addon.changelog_btn_state, + Text::new(localized_string("changelog")).size(DEFAULT_FONT_SIZE), + ) + .style(grin_gui_core::theme::button::Button::Primary); + + if changelog_url.is_some() { + changelog_button = + changelog_button.on_press(Interaction::Expand(ExpandType::Changelog { + addon: addon_cloned, + changelog: None, + })); + } + + let changelog_button: Element = changelog_button.into();*/ + + /*let test_row = Row::new() + .push(release_channel_list) + .push(release_date_text_container);*/ + + /*let button_row = Row::new() + .push(Space::new(Length::Fill, Length::Fixed(0.0))) + .push(website_button.map(Message::Interaction)) + .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) + .push(changelog_button.map(Message::Interaction)) + .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) + .push(ignore_button.map(Message::Interaction)) + .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) + .push(delete_savedvariables_button.map(Message::Interaction)) + .push(Space::new(Length::Fixed(5.0), Length::Fixed(0.0))) + .push(delete_button.map(Message::Interaction)) + .width(Length::Fill);*/ + let column = Column::new() + .push(id_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(3.0))) + .push(uuid_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(3.0))) + .push(type_row); + //.push(Space::new(Length::Fixed(0.0), Length::Fixed(3))) + /* .push(notes_title_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(3))) + .push(notes_text) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(15))) + .push(release_channel_title_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(3))) + .push(test_row) + .push(space) + .push(button_row)*/ + //.push(bottom_space); + let details_container = Container::new(column) + .width(Length::Fill) + .padding(20) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let row = Row::new() + .push(left_spacer) + .push(details_container) + .push(Space::new( + Length::Fixed(DEFAULT_PADDING + 5.0), + Length::Fixed(0.0), + )) + .spacing(1); + tx_column = tx_column + .push(Space::new(Length::FillPortion(1), Length::Fixed(1.0))) + .push(row); + } + ExpandType::None => {} + } + } + + let mut table_row = TableRow::new(tx_column) + .width(Length::Fill) + .inner_row_height(default_row_height) + .on_press(move |_| { + Message::Interaction(Interaction::WalletOperationTxListInteraction( + LocalViewInteraction::Expand(ExpandType::Details(tx_cloned_for_row.clone())), + )) + }); + + if is_odd == Some(true) { + table_row = table_row.style(grin_gui_core::theme::TableRowStyle::TableRowAlternate) + } else { + table_row = table_row.style(grin_gui_core::theme::TableRowStyle::Default) + } + + // Due to what feels like an iced-rs bug, don't put buttons within the actual row as they appear + // to clear their state between the press down and up event if included within the row itself + // Some kind of fix to the table row widget might rectify this + let mut return_column = Column::new().push(table_row).push(action_button_row); + + if is_tx_expanded { + return_column = return_column.push(Space::new( + Length::Fixed(0.0), + Length::Fixed(DEFAULT_PADDING * 2.0), + )); + } + + let return_container = Container::new(return_column); + + return_container } #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Expand(ExpandType), + Expand(ExpandType), } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui - .wallet_state - .operation_state - .home_state - .tx_list_display_state; - match message { - LocalViewInteraction::Expand(expand_type) => match &expand_type { - ExpandType::Details(tx_wrap) => { - log::debug!("Interaction::Expand(Tx({:?}))", &tx_wrap.tx.id,); - let should_close = match &state.expanded_type { - ExpandType::Details(a) => tx_wrap.tx.id == a.tx.id, - _ => false, - }; - - if should_close { - state.expanded_type = ExpandType::None; - } else { - state.expanded_type = expand_type.clone(); - } - } - ExpandType::None => { - log::debug!("Interaction::Expand(ExpandType::None)"); - } - }, - } - Ok(Command::none()) + let state = &mut grin_gui + .wallet_state + .operation_state + .home_state + .tx_list_display_state; + match message { + LocalViewInteraction::Expand(expand_type) => match &expand_type { + ExpandType::Details(tx_wrap) => { + log::debug!("Interaction::Expand(Tx({:?}))", &tx_wrap.tx.id,); + let should_close = match &state.expanded_type { + ExpandType::Details(a) => tx_wrap.tx.id == a.tx.id, + _ => false, + }; + + if should_close { + state.expanded_type = ExpandType::None; + } else { + state.expanded_type = expand_type.clone(); + } + } + ExpandType::None => { + log::debug!("Interaction::Expand(ExpandType::None)"); + } + }, + } + Ok(Command::none()) } diff --git a/src/gui/element/wallet/operation/tx_list_display.rs b/src/gui/element/wallet/operation/tx_list_display.rs index f69efc7..184f5a0 100644 --- a/src/gui/element/wallet/operation/tx_list_display.rs +++ b/src/gui/element/wallet/operation/tx_list_display.rs @@ -3,8 +3,8 @@ use crate::log_error; use async_std::prelude::FutureExt; use chrono::DurationRound; use grin_gui_core::{ - config::Config, - wallet::{TxLogEntry, TxLogEntryType}, + config::Config, + wallet::{TxLogEntry, TxLogEntryType}, }; use grin_gui_widgets::widget::header; use iced_aw::Card; @@ -14,58 +14,58 @@ use std::{borrow::Borrow, path::PathBuf, str::FromStr}; use super::tx_list::{HeaderState, TxList, TxLogEntryWrap}; use { - super::super::super::{ - BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, - SMALLER_FONT_SIZE, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - anyhow::Context, - grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, - TextInput, - }, - grin_gui_core::wallet::{ - InitTxArgs, RetrieveTxQueryArgs, RetrieveTxQuerySortOrder, Slate, StatusMessage, - WalletInfo, WalletInterface, - }, - grin_gui_core::{ - node::amount_to_hr_string, - theme::{ButtonStyle, ColorPalette, ContainerStyle}, - }, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - serde::{Deserialize, Serialize}, - std::sync::{Arc, RwLock}, + super::super::super::{ + BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, + SMALLER_FONT_SIZE, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + anyhow::Context, + grin_gui_core::theme::{ + Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, + TextInput, + }, + grin_gui_core::wallet::{ + InitTxArgs, RetrieveTxQueryArgs, RetrieveTxQuerySortOrder, Slate, StatusMessage, + WalletInfo, WalletInterface, + }, + grin_gui_core::{ + node::amount_to_hr_string, + theme::{ButtonStyle, ColorPalette, ContainerStyle}, + }, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + serde::{Deserialize, Serialize}, + std::sync::{Arc, RwLock}, }; pub struct StateContainer { - // maintains a list of all confirmed transactions sorted by date - confirmed_txns: Vec, - wallet_txs: TxList, - tx_header_state: HeaderState, - query_args: RetrieveTxQueryArgs, - pub mode: Mode, + // maintains a list of all confirmed transactions sorted by date + confirmed_txns: Vec, + wallet_txs: TxList, + tx_header_state: HeaderState, + query_args: RetrieveTxQueryArgs, + pub mode: Mode, - pub expanded_type: ExpandType, + pub expanded_type: ExpandType, - // balance history for wallet as (date, grin_balance) - pub balance_data: Vec<(chrono::DateTime, f64)>, + // balance history for wallet as (date, grin_balance) + pub balance_data: Vec<(chrono::DateTime, f64)>, } impl Default for StateContainer { - fn default() -> Self { - Self { - wallet_txs: Default::default(), - tx_header_state: Default::default(), - expanded_type: ExpandType::None, - query_args: Default::default(), - mode: Mode::NotInit, - balance_data: vec![], - confirmed_txns: vec![], - } - } + fn default() -> Self { + Self { + wallet_txs: Default::default(), + tx_header_state: Default::default(), + expanded_type: ExpandType::None, + query_args: Default::default(), + mode: Mode::NotInit, + balance_data: vec![], + confirmed_txns: vec![], + } + } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -73,371 +73,375 @@ pub enum Action {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Mode { - NotInit, - Recent, - Outstanding, + NotInit, + Recent, + Outstanding, } #[derive(Debug, Clone)] pub enum LocalViewInteraction { - SelectMode(Mode), - RefreshList, - TxListUpdateSuccess(bool, Vec), - TxListUpdateFailure(Arc>>), + SelectMode(Mode), + RefreshList, + TxListUpdateSuccess(bool, Vec), + TxListUpdateFailure(Arc>>), } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui - .wallet_state - .operation_state - .home_state - .tx_list_display_state; - - match message { - LocalViewInteraction::SelectMode(new_mode) => { - state.query_args = RetrieveTxQueryArgs::default(); - - state.query_args.sort_order = Some(RetrieveTxQuerySortOrder::Desc); - - match new_mode { - Mode::NotInit => {} - Mode::Recent => { - state.query_args.exclude_cancelled = Some(true); - } - Mode::Outstanding => { - state.query_args.exclude_cancelled = Some(true); - state.query_args.include_outstanding_only = Some(true); - } - } - - state.mode = new_mode; - - let fut = move || async {}; - return Ok(Command::perform(fut(), |_| { - return Message::Interaction( - Interaction::WalletOperationHomeTxListDisplayInteraction( - LocalViewInteraction::RefreshList, - ), - ); - })); - } - - LocalViewInteraction::RefreshList => { - let w = grin_gui.wallet_interface.clone(); - - let fut = move || WalletInterface::get_txs(w, Some(state.query_args.clone())); - return Ok(Command::perform(fut(), |tx_list_res| { - if tx_list_res.is_err() { - let e = tx_list_res - .context("Failed to retrieve transaction list") - .unwrap_err(); - return Message::Interaction( - Interaction::WalletOperationHomeTxListDisplayInteraction( - LocalViewInteraction::TxListUpdateFailure(Arc::new(RwLock::new(Some( - e, - )))), - ), - ); - } - let (node_success, txs) = tx_list_res.unwrap(); - Message::Interaction(Interaction::WalletOperationHomeTxListDisplayInteraction( - //LocalViewInteraction::WalletInfoUpdateSuccess(node_success, wallet_info, txs), - LocalViewInteraction::TxListUpdateSuccess(node_success, txs), - )) - })); - } - LocalViewInteraction::TxListUpdateSuccess(node_success, txs) => { - debug!("Update Tx List Summary: {}", node_success); - debug!("Update Wallet Txs Summary: {:?}", txs); - let tx_wrap_list = txs - .iter() - .map(|tx| TxLogEntryWrap::new(tx.clone())) - .collect(); - state.wallet_txs = TxList { txs: tx_wrap_list }; - - let confirmed_txns: Vec<&TxLogEntry> = txs.iter().filter(|tx| tx.confirmed).collect(); - - if !confirmed_txns.is_empty() { - // added new confirmed transactions to state confirmed set? - let mut added = false; - - for tx in confirmed_txns.iter() { - // if tx is not in state confirmed transactions, add it - if state - .confirmed_txns - .iter() - .find(|t| t.id == tx.id) - .is_none() - { - // push to state confirmed transactions - state.confirmed_txns.push(tx.clone().to_owned()); - added = true; - debug!("Confirmed Tx: {:?}", tx); - } - } - - if added { - // sort state transactions by date - state.confirmed_txns.sort_by(|a, b| { - a.confirmation_ts.unwrap().cmp(&b.confirmation_ts.unwrap()) - }); - - let mut datetime_sums = vec![]; - for tx in state.confirmed_txns.iter() { - // trunc transaction date to day - //let datetime = tx.confirmation_ts.unwrap().duration_trunc(chrono::Duration::days(1)).unwrap(); - // this should be the date time above but for dev purposes lets backdate it - let datetime = chrono::DateTime::from_str("2019-01-20T00:00:00Z").unwrap(); - let credits = tx.amount_credited; - let debits = tx.amount_debited; - - datetime_sums.push((datetime, credits as i64 - debits as i64)); - } - - let mut sum = 0; - let mut dt = datetime_sums.first().unwrap().0; - let today = chrono::Utc::now() - .duration_trunc(chrono::Duration::days(1)) - .unwrap(); - - // fill in sum data for days without transactions - let mut balance_history = vec![]; - while dt <= today { - // get all transactions for this date - let txns = datetime_sums.iter().filter(|(date, _)| *date == dt); - - // sum up balance amount - sum = sum + txns.map(|x| x.1).collect::>().iter().sum::(); - - // convert to grin units - let grin_sum = (sum as f64 / grin_gui_core::GRIN_BASE as f64) as f64; - balance_history.push((dt.to_owned(), grin_sum)); - - dt = dt + chrono::Duration::days(1); - } - - // finally we update state with the newly constructed balance history - state.balance_data = balance_history; - } - } - } - LocalViewInteraction::TxListUpdateFailure(err) => { - grin_gui.error = err.write().unwrap().take(); - if let Some(e) = grin_gui.error.as_ref() { - log_error(e); - } - } - } - - Ok(Command::none()) + let state = &mut grin_gui + .wallet_state + .operation_state + .home_state + .tx_list_display_state; + + match message { + LocalViewInteraction::SelectMode(new_mode) => { + state.query_args = RetrieveTxQueryArgs::default(); + + state.query_args.sort_order = Some(RetrieveTxQuerySortOrder::Desc); + + match new_mode { + Mode::NotInit => {} + Mode::Recent => { + state.query_args.exclude_cancelled = Some(true); + } + Mode::Outstanding => { + state.query_args.exclude_cancelled = Some(true); + state.query_args.include_outstanding_only = Some(true); + } + } + + state.mode = new_mode; + + let fut = move || async {}; + return Ok(Command::perform(fut(), |_| { + return Message::Interaction( + Interaction::WalletOperationHomeTxListDisplayInteraction( + LocalViewInteraction::RefreshList, + ), + ); + })); + } + + LocalViewInteraction::RefreshList => { + let w = grin_gui.wallet_interface.clone(); + + let fut = move || WalletInterface::get_txs(w, Some(state.query_args.clone())); + return Ok(Command::perform(fut(), |tx_list_res| { + if tx_list_res.is_err() { + let e = tx_list_res + .context("Failed to retrieve transaction list") + .unwrap_err(); + return Message::Interaction( + Interaction::WalletOperationHomeTxListDisplayInteraction( + LocalViewInteraction::TxListUpdateFailure(Arc::new(RwLock::new(Some( + e, + )))), + ), + ); + } + let (node_success, txs) = tx_list_res.unwrap(); + Message::Interaction(Interaction::WalletOperationHomeTxListDisplayInteraction( + //LocalViewInteraction::WalletInfoUpdateSuccess(node_success, wallet_info, txs), + LocalViewInteraction::TxListUpdateSuccess(node_success, txs), + )) + })); + } + LocalViewInteraction::TxListUpdateSuccess(node_success, txs) => { + debug!("Update Tx List Summary: {}", node_success); + debug!("Update Wallet Txs Summary: {:?}", txs); + let tx_wrap_list = txs + .iter() + .map(|tx| TxLogEntryWrap::new(tx.clone())) + .collect(); + state.wallet_txs = TxList { txs: tx_wrap_list }; + + let confirmed_txns: Vec<&TxLogEntry> = txs.iter().filter(|tx| tx.confirmed).collect(); + + if !confirmed_txns.is_empty() { + // added new confirmed transactions to state confirmed set? + let mut added = false; + + for tx in confirmed_txns.iter() { + // if tx is not in state confirmed transactions, add it + if state + .confirmed_txns + .iter() + .find(|t| t.id == tx.id) + .is_none() + { + // push to state confirmed transactions + state.confirmed_txns.push(tx.clone().to_owned()); + added = true; + debug!("Confirmed Tx: {:?}", tx); + } + } + + if added { + // sort state transactions by date + state.confirmed_txns.sort_by(|a, b| { + a.confirmation_ts.unwrap().cmp(&b.confirmation_ts.unwrap()) + }); + + let mut datetime_sums = vec![]; + for tx in state.confirmed_txns.iter() { + // trunc transaction date to day + //let datetime = tx.confirmation_ts.unwrap().duration_trunc(chrono::Duration::days(1)).unwrap(); + // this should be the date time above but for dev purposes lets backdate it + let datetime = chrono::DateTime::from_str("2019-01-20T00:00:00Z").unwrap(); + let credits = tx.amount_credited; + let debits = tx.amount_debited; + + datetime_sums.push((datetime, credits as i64 - debits as i64)); + } + + let mut sum = 0; + let mut dt = datetime_sums.first().unwrap().0; + let today = chrono::Utc::now() + .duration_trunc(chrono::Duration::days(1)) + .unwrap(); + + // fill in sum data for days without transactions + let mut balance_history = vec![]; + while dt <= today { + // get all transactions for this date + let txns = datetime_sums.iter().filter(|(date, _)| *date == dt); + + // sum up balance amount + sum = sum + txns.map(|x| x.1).collect::>().iter().sum::(); + + // convert to grin units + let grin_sum = (sum as f64 / grin_gui_core::GRIN_BASE as f64) as f64; + balance_history.push((dt.to_owned(), grin_sum)); + + dt = dt + chrono::Duration::days(1); + } + + // finally we update state with the newly constructed balance history + state.balance_data = balance_history; + } + } + } + LocalViewInteraction::TxListUpdateFailure(err) => { + grin_gui.error = err.write().unwrap().take(); + if let Some(e) = grin_gui.error.as_ref() { + log_error(e); + } + } + } + + Ok(Command::none()) } -pub fn data_container<'a>(config: &'a Config, home_state: &'a super::home::StateContainer, state: &'a StateContainer) -> Container<'a, Message> { - let button_height = Length::Fixed(BUTTON_HEIGHT); - let button_width = Length::Fixed(BUTTON_WIDTH); - - let title = Text::new(localized_string("tx-list")).size(DEFAULT_HEADER_FONT_SIZE); - let title_container = Container::new(title) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - let latest_container = - Container::new(Text::new(localized_string("tx-recent")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .align_y(alignment::Vertical::Center) - .align_x(alignment::Horizontal::Center); - - let latest_button = Button::new(latest_container).width(button_width).on_press( - Interaction::WalletOperationHomeTxListDisplayInteraction(LocalViewInteraction::SelectMode( - Mode::Recent, - )), - ); - - let latest_button = if state.mode == Mode::Recent { - latest_button.style(grin_gui_core::theme::ButtonStyle::Selected) - } else { - latest_button.style(grin_gui_core::theme::ButtonStyle::Primary) - }; - - let latest_button: Element = latest_button.into(); - - // add a nice double border around our buttons - // TODO refactor since many of the buttons around the UI repeat this theme - let latest_container_wrap = Container::new(latest_button.map(Message::Interaction)).padding(1); - let latest_container_wrap = Container::new(latest_container_wrap) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let outstanding_container = - Container::new(Text::new(localized_string("tx-outstanding")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .align_y(alignment::Vertical::Center) - .align_x(alignment::Horizontal::Center); - - let outstanding_button = Button::new(outstanding_container) - .width(button_width) - .on_press(Interaction::WalletOperationHomeTxListDisplayInteraction( - LocalViewInteraction::SelectMode(Mode::Outstanding), - )); - - let outstanding_button = if state.mode == Mode::Outstanding { - outstanding_button.style(grin_gui_core::theme::ButtonStyle::Selected) - } else { - outstanding_button.style(grin_gui_core::theme::ButtonStyle::Primary) - }; - - let outstanding_button: Element = outstanding_button.into(); - - let outstanding_container_wrap = - Container::new(outstanding_button.map(Message::Interaction)).padding(1); - let outstanding_container_wrap = Container::new(outstanding_container_wrap) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - // add additional buttons here - let button_row = Row::new() - .push(latest_container_wrap) - .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))) - .push(outstanding_container_wrap); - - /*let segmented_mode_container = Container::new(button_row).padding(1); - let segmented_mode_control_container = Container::new(segmented_mode_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1);*/ - - let header_row = Row::new() - .push(title_container) - .push(Space::with_width(Length::Fill)) - .push(button_row) - .align_items(Alignment::Center); - - let header_container = Container::new(header_row).padding(iced::Padding::from([ - 0, // top - 0, // right - DEFAULT_PADDING as u16, // bottom - 0, // left - ])); - - // TRANSACTION HEADER - let column_config = state.tx_header_state.column_config(); - - // Tx row titles is a row of titles above the tx scrollable. - // This is to add titles above each section of the tx row, to let - // the user easily identify what the value is. - let table_header_row = super::tx_list::titles_row_header( - &state.wallet_txs, - &state.tx_header_state.state, - &state.tx_header_state.columns, - state.tx_header_state.previous_column_key, - state.tx_header_state.previous_sort_direction, - ); - - let table_header_container = Container::new(table_header_row).padding(iced::Padding::from([ - 0, // top - DEFAULT_PADDING as u16 * 3, // right - should roughly match width of content scroll bar to align table headers - 0, // bottom - 0, // left - ])); - //.style(grin_gui_core::theme::ContainerStyle::PanelForeground); - - // A scrollable list containing rows. - // Each row holds data about a single tx. - let mut content = Column::new().spacing(1); - //.height(Length::Fill) - //.style(grin_gui_core::theme::ScrollableStyles::Primary); - - let mut has_txs = false; - - // Loops though the txs. - for (idx, tx_wrap) in state.wallet_txs.txs.iter().enumerate() { - has_txs = true; - // If hiding ignored addons, we will skip it. - /*if addon.state == AddonState::Ignored && self.config.hide_ignored_addons { - continue; - }*/ - - // Skip addon if we are filter from query and addon doesn't have fuzzy score - /*if query.is_some() && addon.fuzzy_score.is_none() { - continue; - }*/ - - // Checks if the current tx is expanded. - let is_tx_expanded = match &state.expanded_type { - ExpandType::Details(a) => a.tx.id == tx_wrap.tx.id, - ExpandType::None => false, - }; - - let is_odd = if config.alternating_row_colors { - Some(idx % 2 != 0) - } else { - None - }; - - // A container cell which has all data about the current tx. - // If the tx is expanded, then this is also included in this container. - let tx_data_cell = tx_list::data_row_container( - tx_wrap, - is_tx_expanded, - &state.expanded_type, - config, - &column_config, - is_odd, - &None, - home_state.node_synched, - ); - - // Adds the addon data cell to the scrollable. - content = content.push(tx_data_cell); - } - - let mut tx_list_scrollable = - Scrollable::new(content).style(grin_gui_core::theme::ScrollableStyle::Primary); - - // This column gathers all the tx list elements together. - let mut tx_list_content = Column::new(); - - // Adds the rest of the elements to the content column. - if has_txs { - tx_list_content = tx_list_content.push(tx_list_scrollable); - } else { - let no_txs_label = if state.mode == Mode::NotInit { - Text::new(localized_string("txs-list-loading")).size(DEFAULT_FONT_SIZE) - } else { - Text::new(localized_string("no-txs-list")).size(DEFAULT_FONT_SIZE) - }; - let no_txs_container = Container::new(no_txs_label) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - tx_list_content = tx_list_content.push(no_txs_container); - } - - // TRANSACTION LISTING - - let column = Column::new() - .push(header_container) - .push(table_header_container) - .push(tx_list_content); - - // Returns the final container. - Container::new(column) - .padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) - .style(grin_gui_core::theme::ContainerStyle::PanelBordered) +pub fn data_container<'a>( + config: &'a Config, + home_state: &'a super::home::StateContainer, + state: &'a StateContainer, +) -> Container<'a, Message> { + let button_height = Length::Fixed(BUTTON_HEIGHT); + let button_width = Length::Fixed(BUTTON_WIDTH); + + let title = Text::new(localized_string("tx-list")).size(DEFAULT_HEADER_FONT_SIZE); + let title_container = Container::new(title) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + let latest_container = + Container::new(Text::new(localized_string("tx-recent")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .align_y(alignment::Vertical::Center) + .align_x(alignment::Horizontal::Center); + + let latest_button = Button::new(latest_container).width(button_width).on_press( + Interaction::WalletOperationHomeTxListDisplayInteraction(LocalViewInteraction::SelectMode( + Mode::Recent, + )), + ); + + let latest_button = if state.mode == Mode::Recent { + latest_button.style(grin_gui_core::theme::ButtonStyle::Selected) + } else { + latest_button.style(grin_gui_core::theme::ButtonStyle::Primary) + }; + + let latest_button: Element = latest_button.into(); + + // add a nice double border around our buttons + // TODO refactor since many of the buttons around the UI repeat this theme + let latest_container_wrap = Container::new(latest_button.map(Message::Interaction)).padding(1); + let latest_container_wrap = Container::new(latest_container_wrap) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let outstanding_container = + Container::new(Text::new(localized_string("tx-outstanding")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .align_y(alignment::Vertical::Center) + .align_x(alignment::Horizontal::Center); + + let outstanding_button = Button::new(outstanding_container) + .width(button_width) + .on_press(Interaction::WalletOperationHomeTxListDisplayInteraction( + LocalViewInteraction::SelectMode(Mode::Outstanding), + )); + + let outstanding_button = if state.mode == Mode::Outstanding { + outstanding_button.style(grin_gui_core::theme::ButtonStyle::Selected) + } else { + outstanding_button.style(grin_gui_core::theme::ButtonStyle::Primary) + }; + + let outstanding_button: Element = outstanding_button.into(); + + let outstanding_container_wrap = + Container::new(outstanding_button.map(Message::Interaction)).padding(1); + let outstanding_container_wrap = Container::new(outstanding_container_wrap) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + // add additional buttons here + let button_row = Row::new() + .push(latest_container_wrap) + .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))) + .push(outstanding_container_wrap); + + /*let segmented_mode_container = Container::new(button_row).padding(1); + let segmented_mode_control_container = Container::new(segmented_mode_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1);*/ + + let header_row = Row::new() + .push(title_container) + .push(Space::with_width(Length::Fill)) + .push(button_row) + .align_items(Alignment::Center); + + let header_container = Container::new(header_row).padding(iced::Padding::from([ + 0, // top + 0, // right + DEFAULT_PADDING as u16, // bottom + 0, // left + ])); + + // TRANSACTION HEADER + let column_config = state.tx_header_state.column_config(); + + // Tx row titles is a row of titles above the tx scrollable. + // This is to add titles above each section of the tx row, to let + // the user easily identify what the value is. + let table_header_row = super::tx_list::titles_row_header( + &state.wallet_txs, + &state.tx_header_state.state, + &state.tx_header_state.columns, + state.tx_header_state.previous_column_key, + state.tx_header_state.previous_sort_direction, + ); + + let table_header_container = Container::new(table_header_row).padding(iced::Padding::from([ + 0, // top + DEFAULT_PADDING as u16 * 3, // right - should roughly match width of content scroll bar to align table headers + 0, // bottom + 0, // left + ])); + //.style(grin_gui_core::theme::ContainerStyle::PanelForeground); + + // A scrollable list containing rows. + // Each row holds data about a single tx. + let mut content = Column::new().spacing(1); + //.height(Length::Fill) + //.style(grin_gui_core::theme::ScrollableStyles::Primary); + + let mut has_txs = false; + + // Loops though the txs. + for (idx, tx_wrap) in state.wallet_txs.txs.iter().enumerate() { + has_txs = true; + // If hiding ignored addons, we will skip it. + /*if addon.state == AddonState::Ignored && self.config.hide_ignored_addons { + continue; + }*/ + + // Skip addon if we are filter from query and addon doesn't have fuzzy score + /*if query.is_some() && addon.fuzzy_score.is_none() { + continue; + }*/ + + // Checks if the current tx is expanded. + let is_tx_expanded = match &state.expanded_type { + ExpandType::Details(a) => a.tx.id == tx_wrap.tx.id, + ExpandType::None => false, + }; + + let is_odd = if config.alternating_row_colors { + Some(idx % 2 != 0) + } else { + None + }; + + // A container cell which has all data about the current tx. + // If the tx is expanded, then this is also included in this container. + let tx_data_cell = tx_list::data_row_container( + tx_wrap, + is_tx_expanded, + &state.expanded_type, + config, + &column_config, + is_odd, + &None, + home_state.node_synched, + ); + + // Adds the addon data cell to the scrollable. + content = content.push(tx_data_cell); + } + + let mut tx_list_scrollable = + Scrollable::new(content).style(grin_gui_core::theme::ScrollableStyle::Primary); + + // This column gathers all the tx list elements together. + let mut tx_list_content = Column::new(); + + // Adds the rest of the elements to the content column. + if has_txs { + tx_list_content = tx_list_content.push(tx_list_scrollable); + } else { + let no_txs_label = if state.mode == Mode::NotInit { + Text::new(localized_string("txs-list-loading")).size(DEFAULT_FONT_SIZE) + } else { + Text::new(localized_string("no-txs-list")).size(DEFAULT_FONT_SIZE) + }; + let no_txs_container = Container::new(no_txs_label) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + tx_list_content = tx_list_content.push(no_txs_container); + } + + // TRANSACTION LISTING + + let column = Column::new() + .push(header_container) + .push(table_header_container) + .push(tx_list_content); + + // Returns the final container. + Container::new(column) + .padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) + .style(grin_gui_core::theme::ContainerStyle::PanelBordered) } diff --git a/src/gui/element/wallet/operation/tx_proof.rs b/src/gui/element/wallet/operation/tx_proof.rs index 55a90b2..397a233 100644 --- a/src/gui/element/wallet/operation/tx_proof.rs +++ b/src/gui/element/wallet/operation/tx_proof.rs @@ -2,9 +2,9 @@ use super::tx_list::{self, ExpandType}; use crate::log_error; use async_std::prelude::FutureExt; use grin_gui_core::{ - config::Config, - error::GrinWalletInterfaceError, - wallet::{InvoiceProof, SlatepackAddress, TxLogEntry, TxLogEntryType}, + config::Config, + error::GrinWalletInterfaceError, + wallet::{InvoiceProof, SlatepackAddress, TxLogEntry, TxLogEntryType}, }; use grin_gui_widgets::widget::header; use iced_aw::Card; @@ -16,42 +16,42 @@ use std::path::PathBuf; use super::tx_list::{HeaderState, TxList}; use { - super::super::super::{ - BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, - SMALLER_FONT_SIZE, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - anyhow::Context, - grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, - TextInput, - }, - grin_gui_core::wallet::{InitTxArgs, Slate, StatusMessage, WalletInfo, WalletInterface}, - grin_gui_core::{ - node::{amount_from_hr_string, amount_to_hr_string}, - theme::{ButtonStyle, ColorPalette, ContainerStyle}, - }, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - serde::{Deserialize, Serialize}, - std::sync::{Arc, RwLock}, + super::super::super::{ + BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, + SMALLER_FONT_SIZE, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + anyhow::Context, + grin_gui_core::theme::{ + Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, + TextInput, + }, + grin_gui_core::wallet::{InitTxArgs, Slate, StatusMessage, WalletInfo, WalletInterface}, + grin_gui_core::{ + node::{amount_from_hr_string, amount_to_hr_string}, + theme::{ButtonStyle, ColorPalette, ContainerStyle}, + }, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + serde::{Deserialize, Serialize}, + std::sync::{Arc, RwLock}, }; pub struct StateContainer { - // Transaction that we're viewing - pub current_tx: Option, - pub current_proof: Option, + // Transaction that we're viewing + pub current_tx: Option, + pub current_proof: Option, } impl Default for StateContainer { - fn default() -> Self { - Self { - current_tx: Default::default(), - current_proof: Default::default(), - } - } + fn default() -> Self { + Self { + current_tx: Default::default(), + current_proof: Default::default(), + } + } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -59,223 +59,222 @@ pub enum Action {} #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Back, + Back, } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.wallet_state.operation_state.create_tx_state; + let state = &mut grin_gui.wallet_state.operation_state.create_tx_state; - match message { - LocalViewInteraction::Back => { - log::debug!("Interaction::WalletOperationTxProofViewInteraction(Back)"); - grin_gui.wallet_state.operation_state.mode = - crate::gui::element::wallet::operation::Mode::Home; - } - } + match message { + LocalViewInteraction::Back => { + log::debug!("Interaction::WalletOperationTxProofViewInteraction(Back)"); + grin_gui.wallet_state.operation_state.mode = + crate::gui::element::wallet::operation::Mode::Home; + } + } - Ok(Command::none()) + Ok(Command::none()) } pub fn data_container<'a>(config: &'a Config, state: &'a StateContainer) -> Container<'a, Message> { - // Title row - let title = Text::new(localized_string("tx-proof-title")) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - - let title_container = Container::new(title) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 2, // top - 0, // right - 2, // bottom - 5, // left - ])); - - let header_row = Row::new().push(title_container); - - let header_container = Container::new(header_row).padding(iced::Padding::from([ - 0, // top - 0, // right - DEFAULT_PADDING as u16, // bottom - 0, // left - ])); - - let unit_spacing = 15.0; - let row_spacing = 5.0; - - let button_height = Length::Fixed(BUTTON_HEIGHT); - let button_width = Length::Fixed(BUTTON_WIDTH); - - let mut column = Column::new(); - - if let Some(ref proof) = state.current_proof { - // Amount - let pr_amount_label = Text::new(format!("{}: ", localized_string("pr-amount"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let pr_amount_label_container = Container::new(pr_amount_label) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let pr_amount_value = Text::new(format!("{}", amount_to_hr_string(proof.amount, true))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let pr_amount_value_container = Container::new(pr_amount_value) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let pr_amount_row = Row::new() - .push(pr_amount_label_container) - .push(pr_amount_value_container); - column = column - .push(pr_amount_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - - // Timestamp - let pr_timestamp_label = Text::new(format!("{}: ", localized_string("pr-timestamp"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let pr_timestamp_label_container = Container::new(pr_timestamp_label) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - // convert i64 timestamp to utc time - let ts_display = chrono::NaiveDateTime::from_timestamp(proof.timestamp, 0) - .format("%Y-%m-%d %H:%M:%S") - .to_string(); - - let pr_timestamp_value = Text::new(format!("{} UTC", ts_display)) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let pr_timestamp_value_container = Container::new(pr_timestamp_value) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let pr_timestamp_row = Row::new() - .push(pr_timestamp_label_container) - .push(pr_timestamp_value_container); - column = column - .push(pr_timestamp_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - - // sender address - let pr_sender_address_label = - Text::new(format!("{}: ", localized_string("pr-sender-address"))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let pr_sender_address_label_container = Container::new(pr_sender_address_label) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let pr_sender_address_value = - Text::new(format!("{}", SlatepackAddress::new(&proof.sender_address))) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let pr_sender_address_value_container = Container::new(pr_sender_address_value) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let pr_sender_address_row = Row::new() - .push(pr_sender_address_label_container) - .push(pr_sender_address_value_container); - column = column - .push(pr_sender_address_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - - let card_contents = format!("{}", serde_json::to_string_pretty(&proof).unwrap()); - - let json_proof_card = Card::new( - Text::new(localized_string("pr-json-proof")).size(DEFAULT_HEADER_FONT_SIZE), - Text::new(card_contents.clone()).size(DEFAULT_FONT_SIZE), - ) - .foot( - Column::new() - .spacing(10) - .padding(5) - .width(Length::Fill) - .align_items(Alignment::Center) - .push( - Button::new( - Text::new(localized_string("copy-to-clipboard")) - .size(SMALLER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center), - ) - .style(grin_gui_core::theme::ButtonStyle::NormalText) - .on_press(Message::Interaction( - Interaction::WriteToClipboard(card_contents.clone()), - )), - ), - ) - .max_width(400.0) - .style(grin_gui_core::theme::CardStyle::Normal); - - let json_proof_row = Row::new() - .push(json_proof_card); - - column = column - .push(json_proof_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); - } - - let cancel_button_label_container = - Container::new(Text::new(localized_string("back")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let cancel_button: Element = Button::new(cancel_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletOperationTxProofViewInteraction( - LocalViewInteraction::Back, - )) - .into(); - - let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); - let cancel_container = Container::new(cancel_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let button_row = Row::new() - .push(cancel_container) - .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))); - - column = column.push(button_row); - - let form_container = Container::new(column) - .width(Length::Fill) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - // form container should be scrollable in tiny windows - let scrollable = Scrollable::new(form_container) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); - - let content = Container::new(scrollable) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let wrapper_column = Column::new() - .height(Length::Fill) - .push(header_container) - .push(content); - - // Returns the final container. - Container::new(wrapper_column).padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + // Title row + let title = Text::new(localized_string("tx-proof-title")) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + + let title_container = Container::new(title) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 2, // top + 0, // right + 2, // bottom + 5, // left + ])); + + let header_row = Row::new().push(title_container); + + let header_container = Container::new(header_row).padding(iced::Padding::from([ + 0, // top + 0, // right + DEFAULT_PADDING as u16, // bottom + 0, // left + ])); + + let unit_spacing = 15.0; + let row_spacing = 5.0; + + let button_height = Length::Fixed(BUTTON_HEIGHT); + let button_width = Length::Fixed(BUTTON_WIDTH); + + let mut column = Column::new(); + + if let Some(ref proof) = state.current_proof { + // Amount + let pr_amount_label = Text::new(format!("{}: ", localized_string("pr-amount"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let pr_amount_label_container = Container::new(pr_amount_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let pr_amount_value = Text::new(format!("{}", amount_to_hr_string(proof.amount, true))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let pr_amount_value_container = Container::new(pr_amount_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let pr_amount_row = Row::new() + .push(pr_amount_label_container) + .push(pr_amount_value_container); + column = column + .push(pr_amount_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + + // Timestamp + let pr_timestamp_label = Text::new(format!("{}: ", localized_string("pr-timestamp"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let pr_timestamp_label_container = Container::new(pr_timestamp_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + // convert i64 timestamp to utc time + let ts_display = chrono::NaiveDateTime::from_timestamp(proof.timestamp, 0) + .format("%Y-%m-%d %H:%M:%S") + .to_string(); + + let pr_timestamp_value = Text::new(format!("{} UTC", ts_display)) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let pr_timestamp_value_container = Container::new(pr_timestamp_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let pr_timestamp_row = Row::new() + .push(pr_timestamp_label_container) + .push(pr_timestamp_value_container); + column = column + .push(pr_timestamp_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + + // sender address + let pr_sender_address_label = + Text::new(format!("{}: ", localized_string("pr-sender-address"))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let pr_sender_address_label_container = Container::new(pr_sender_address_label) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let pr_sender_address_value = + Text::new(format!("{}", SlatepackAddress::new(&proof.sender_address))) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let pr_sender_address_value_container = Container::new(pr_sender_address_value) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let pr_sender_address_row = Row::new() + .push(pr_sender_address_label_container) + .push(pr_sender_address_value_container); + column = column + .push(pr_sender_address_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + + let card_contents = format!("{}", serde_json::to_string_pretty(&proof).unwrap()); + + let json_proof_card = Card::new( + Text::new(localized_string("pr-json-proof")).size(DEFAULT_HEADER_FONT_SIZE), + Text::new(card_contents.clone()).size(DEFAULT_FONT_SIZE), + ) + .foot( + Column::new() + .spacing(10) + .padding(5) + .width(Length::Fill) + .align_items(Alignment::Center) + .push( + Button::new( + Text::new(localized_string("copy-to-clipboard")) + .size(SMALLER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center), + ) + .style(grin_gui_core::theme::ButtonStyle::NormalText) + .on_press(Message::Interaction(Interaction::WriteToClipboard( + card_contents.clone(), + ))), + ), + ) + .max_width(400.0) + .style(grin_gui_core::theme::CardStyle::Normal); + + let json_proof_row = Row::new().push(json_proof_card); + + column = column + .push(json_proof_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(row_spacing))); + } + + let cancel_button_label_container = + Container::new(Text::new(localized_string("back")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let cancel_button: Element = Button::new(cancel_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletOperationTxProofViewInteraction( + LocalViewInteraction::Back, + )) + .into(); + + let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); + let cancel_container = Container::new(cancel_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let button_row = Row::new() + .push(cancel_container) + .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))); + + column = column.push(button_row); + + let form_container = Container::new(column) + .width(Length::Fill) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + // form container should be scrollable in tiny windows + let scrollable = Scrollable::new(form_container) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); + + let content = Container::new(scrollable) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let wrapper_column = Column::new() + .height(Length::Fill) + .push(header_container) + .push(content); + + // Returns the final container. + Container::new(wrapper_column).padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/wallet/setup/init.rs b/src/gui/element/wallet/setup/init.rs index e1a32a8..1f71b28 100644 --- a/src/gui/element/wallet/setup/init.rs +++ b/src/gui/element/wallet/setup/init.rs @@ -1,174 +1,172 @@ use { - super::super::super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE}, - crate::gui::{GrinGui, Interaction, Message, element::settings::wallet}, - crate::localization::localized_string, - crate::Result, - grin_gui_core::{ - theme::ColorPalette, - wallet::{create_grin_wallet_path, ChainTypes}, - }, - grin_gui_core::theme::{Column, Element, Container, PickList, Row, Scrollable, Text, TextInput}, - iced::{alignment, Alignment, Command, Length}, - iced::widget::{ - button, pick_list, scrollable, text_input, Button, Checkbox, Space, - }, + super::super::super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE}, + crate::gui::{element::settings::wallet, GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + grin_gui_core::theme::{ + Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, + }, + grin_gui_core::{ + theme::ColorPalette, + wallet::{create_grin_wallet_path, ChainTypes}, + }, + iced::widget::{button, pick_list, scrollable, text_input, Button, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, }; pub struct StateContainer { - pub setup_wallet_defaults_is_selected: bool, + pub setup_wallet_defaults_is_selected: bool, } impl Default for StateContainer { - fn default() -> Self { - Self { - setup_wallet_defaults_is_selected: true, - } - } + fn default() -> Self { + Self { + setup_wallet_defaults_is_selected: true, + } + } } #[derive(Debug, Clone)] pub enum LocalViewInteraction { - WalletSetup, - WalletList, + WalletSetup, + WalletList, } pub fn handle_message( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.wallet_state.setup_state; - match message { - LocalViewInteraction::WalletSetup => { - let config = &grin_gui.config; - let wallet_default_name = localized_string("wallet-default-name"); - let mut wallet_display_name = wallet_default_name.clone(); - let mut i = 1; - - // wallet display name must be unique: i.e. Default 1, Default 2, ... - while let Some(_) = config - .wallets - .iter() - .find(|wallet| wallet.display_name == wallet_display_name) - { - wallet_display_name = format!("{} {}", wallet_default_name, i); - i += 1; - } - - // i.e. default_1, default_2, ... - let wallet_dir: String = str::replace(&wallet_display_name.to_lowercase(), " ", "_"); - - state - .setup_wallet_state - .advanced_options_state - .top_level_directory = create_grin_wallet_path(&ChainTypes::Mainnet, &wallet_dir); - - state.mode = super::Mode::CreateWallet(wallet_display_name); - } - LocalViewInteraction::WalletList => state.mode = super::Mode::ListWallets, - } - Ok(Command::none()) + let state = &mut grin_gui.wallet_state.setup_state; + match message { + LocalViewInteraction::WalletSetup => { + let config = &grin_gui.config; + let wallet_default_name = localized_string("wallet-default-name"); + let mut wallet_display_name = wallet_default_name.clone(); + let mut i = 1; + + // wallet display name must be unique: i.e. Default 1, Default 2, ... + while let Some(_) = config + .wallets + .iter() + .find(|wallet| wallet.display_name == wallet_display_name) + { + wallet_display_name = format!("{} {}", wallet_default_name, i); + i += 1; + } + + // i.e. default_1, default_2, ... + let wallet_dir: String = str::replace(&wallet_display_name.to_lowercase(), " ", "_"); + + state + .setup_wallet_state + .advanced_options_state + .top_level_directory = create_grin_wallet_path(&ChainTypes::Mainnet, &wallet_dir); + + state.mode = super::Mode::CreateWallet(wallet_display_name); + } + LocalViewInteraction::WalletList => state.mode = super::Mode::ListWallets, + } + Ok(Command::none()) } pub fn data_container<'a>() -> Container<'a, Message> { - // Title row - let title = Text::new(localized_string("setup-grin-first-time")) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - - let title_container = - Container::new(title).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let title_row = Row::new() - .push(title_container) - .align_items(Alignment::Center) - .padding(6) - .spacing(20); - - let description = Text::new(localized_string("setup-grin-wallet-description")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - let description_container = - Container::new(description).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let or_text = Text::new(localized_string("or-caps")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - - let or_text_container = - Container::new(or_text).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let create_default_wallet_button_label_container = Container::new( - Text::new(localized_string("setup-grin-autogenerate-wallet")).size(DEFAULT_FONT_SIZE), - ) - .center_x() - .align_x(alignment::Horizontal::Center); - - let create_default_wallet_button: Element = Button::new( - create_default_wallet_button_label_container, - ) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::WalletSetupInitViewInteraction( - LocalViewInteraction::WalletSetup, - )) - .into(); - - let select_wallet_button_label_container = - Container::new(Text::new(localized_string("select-wallet-toml")).size(DEFAULT_FONT_SIZE)) - .center_x() - .align_x(alignment::Horizontal::Center); - - let select_wallet_button: Element = Button::new( - select_wallet_button_label_container, - ) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::WalletSetupInitViewInteraction( - LocalViewInteraction::WalletList, - )) - .into(); - - let select_wallet_button_container = - Container::new(select_wallet_button.map(Message::Interaction)).center_x(); - - //let mut wallet_setup_modal_column = - /*let wallet_setup_select_column = { - let checkbox = Checkbox::new( - state.setup_wallet_defaults_is_selected, - localized_string("setup-grin-autogenerate-wallet"), - Interaction::ToggleCloseToTray, - ) - .style(grin_gui_core::theme::CheckboxStyles::Normal) - .text_size(DEFAULT_FONT_SIZE) - .spacing(5); - - let checkbox: Element = checkbox.into(); - - let checkbox_container = Container::new(checkbox.map(Message::Interaction)) - .style(grin_gui_core::theme::container::Container::NormalBackground); - - Column::new().push(checkbox_container) - };*/ - - let unit_spacing = 15.0; - - let select_column = Column::new() - .push(create_default_wallet_button.map(Message::Interaction)) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(or_text_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(select_wallet_button_container) - .align_items(Alignment::Center); - - let column = Column::new() - .push(Space::new(Length::Fixed(0.0), Length::Fixed(20.0))) - .push(title_row) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(description_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(select_column) - .align_items(Alignment::Center); - - Container::new(column) - .center_y() - .center_x() - .width(Length::Fill) + // Title row + let title = Text::new(localized_string("setup-grin-first-time")) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + + let title_container = + Container::new(title).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let title_row = Row::new() + .push(title_container) + .align_items(Alignment::Center) + .padding(6) + .spacing(20); + + let description = Text::new(localized_string("setup-grin-wallet-description")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + let description_container = + Container::new(description).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let or_text = Text::new(localized_string("or-caps")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + + let or_text_container = + Container::new(or_text).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let create_default_wallet_button_label_container = Container::new( + Text::new(localized_string("setup-grin-autogenerate-wallet")).size(DEFAULT_FONT_SIZE), + ) + .center_x() + .align_x(alignment::Horizontal::Center); + + let create_default_wallet_button: Element = + Button::new(create_default_wallet_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::WalletSetupInitViewInteraction( + LocalViewInteraction::WalletSetup, + )) + .into(); + + let select_wallet_button_label_container = + Container::new(Text::new(localized_string("select-wallet-toml")).size(DEFAULT_FONT_SIZE)) + .center_x() + .align_x(alignment::Horizontal::Center); + + let select_wallet_button: Element = + Button::new(select_wallet_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::WalletSetupInitViewInteraction( + LocalViewInteraction::WalletList, + )) + .into(); + + let select_wallet_button_container = + Container::new(select_wallet_button.map(Message::Interaction)).center_x(); + + //let mut wallet_setup_modal_column = + /*let wallet_setup_select_column = { + let checkbox = Checkbox::new( + state.setup_wallet_defaults_is_selected, + localized_string("setup-grin-autogenerate-wallet"), + Interaction::ToggleCloseToTray, + ) + .style(grin_gui_core::theme::CheckboxStyles::Normal) + .text_size(DEFAULT_FONT_SIZE) + .spacing(5); + + let checkbox: Element = checkbox.into(); + + let checkbox_container = Container::new(checkbox.map(Message::Interaction)) + .style(grin_gui_core::theme::container::Container::NormalBackground); + + Column::new().push(checkbox_container) + };*/ + + let unit_spacing = 15.0; + + let select_column = Column::new() + .push(create_default_wallet_button.map(Message::Interaction)) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(or_text_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(select_wallet_button_container) + .align_items(Alignment::Center); + + let column = Column::new() + .push(Space::new(Length::Fixed(0.0), Length::Fixed(20.0))) + .push(title_row) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(description_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(select_column) + .align_items(Alignment::Center); + + Container::new(column) + .center_y() + .center_x() + .width(Length::Fill) } diff --git a/src/gui/element/wallet/setup/mod.rs b/src/gui/element/wallet/setup/mod.rs index cc15e23..c5dfa6f 100644 --- a/src/gui/element/wallet/setup/mod.rs +++ b/src/gui/element/wallet/setup/mod.rs @@ -1,77 +1,73 @@ pub mod init; +pub mod wallet_list; pub mod wallet_setup; pub mod wallet_success; -pub mod wallet_list; use { - crate::gui::{GrinGui, Message}, - crate::Result, - grin_gui_core::theme::ColorPalette, - grin_gui_core::config::Config, - iced::{Command, Length}, - grin_gui_core::theme::{Column, Element, Container, PickList, Row, Scrollable, Text, TextInput}, - iced::widget::{Space} + crate::gui::{GrinGui, Message}, + crate::Result, + grin_gui_core::config::Config, + grin_gui_core::theme::ColorPalette, + grin_gui_core::theme::{ + Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, + }, + iced::widget::Space, + iced::{Command, Length}, }; pub struct StateContainer { - pub mode: Mode, - pub setup_init_state: init::StateContainer, - pub setup_wallet_state: wallet_setup::StateContainer, - pub setup_wallet_success_state: wallet_success::StateContainer, - pub setup_wallet_list_state: wallet_list::StateContainer + pub mode: Mode, + pub setup_init_state: init::StateContainer, + pub setup_wallet_state: wallet_setup::StateContainer, + pub setup_wallet_success_state: wallet_success::StateContainer, + pub setup_wallet_list_state: wallet_list::StateContainer, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Mode { - Init, - CreateWallet(String), - ListWallets, - WalletCreateSuccess, + Init, + CreateWallet(String), + ListWallets, + WalletCreateSuccess, } impl Default for StateContainer { - fn default() -> Self { - Self { - mode: Mode::Init, - setup_init_state: Default::default(), - setup_wallet_state: Default::default(), - setup_wallet_success_state: Default::default(), - setup_wallet_list_state: Default::default() - } - } + fn default() -> Self { + Self { + mode: Mode::Init, + setup_init_state: Default::default(), + setup_wallet_state: Default::default(), + setup_wallet_success_state: Default::default(), + setup_wallet_list_state: Default::default(), + } + } } #[derive(Debug, Clone)] pub enum LocalViewInteraction {} pub fn handle_message( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - Ok(Command::none()) + Ok(Command::none()) } -pub fn data_container<'a>( - state: &'a StateContainer, - config: &Config, -) -> Container<'a, Message> { - let content = match &state.mode { - Mode::Init => init::data_container(), - Mode::CreateWallet(default_display_name) => { - wallet_setup::data_container(&state.setup_wallet_state, default_display_name) - } - Mode::WalletCreateSuccess => { - wallet_success::data_container(&state.setup_wallet_success_state) - } - Mode::ListWallets => { - wallet_list::data_container(&state.setup_wallet_list_state, - config) - } - }; +pub fn data_container<'a>(state: &'a StateContainer, config: &Config) -> Container<'a, Message> { + let content = match &state.mode { + Mode::Init => init::data_container(), + Mode::CreateWallet(default_display_name) => { + wallet_setup::data_container(&state.setup_wallet_state, default_display_name) + } + Mode::WalletCreateSuccess => { + wallet_success::data_container(&state.setup_wallet_success_state) + } + Mode::ListWallets => wallet_list::data_container(&state.setup_wallet_list_state, config), + }; - Container::new(content) - .center_y() - .center_x() - .width(Length::Fill) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground) + Container::new(content) + .center_y() + .center_x() + .width(Length::Fill) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground) } diff --git a/src/gui/element/wallet/setup/wallet_list.rs b/src/gui/element/wallet/setup/wallet_list.rs index 42055c3..f7339d6 100644 --- a/src/gui/element/wallet/setup/wallet_list.rs +++ b/src/gui/element/wallet/setup/wallet_list.rs @@ -1,23 +1,23 @@ use { - super::super::super::{ - BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - grin_gui_core::config::Config, - grin_gui_core::theme::{ - Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, - TextInput, - }, - grin_gui_core::{ - theme::ColorPalette, - wallet::{create_grin_wallet_path, ChainTypes}, - }, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - native_dialog::FileDialog, - std::path::PathBuf, + super::super::super::{ + BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + grin_gui_core::config::Config, + grin_gui_core::theme::{ + Button, Column, Container, Element, Header, PickList, Row, Scrollable, TableRow, Text, + TextInput, + }, + grin_gui_core::{ + theme::ColorPalette, + wallet::{create_grin_wallet_path, ChainTypes}, + }, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + native_dialog::FileDialog, + std::path::PathBuf, }; use grin_gui_widgets::widget::table_row; @@ -26,382 +26,380 @@ use isahc::head; use crate::gui::element::DEFAULT_SUB_HEADER_FONT_SIZE; pub struct StateContainer { - selected_wallet_index: usize, + selected_wallet_index: usize, } impl Default for StateContainer { - fn default() -> Self { - Self { - selected_wallet_index: 0, - } - } + fn default() -> Self { + Self { + selected_wallet_index: 0, + } + } } #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Back, - WalletRowSelect(bool, usize), - LoadWallet(usize), - LocateWallet, - CreateWallet, + Back, + WalletRowSelect(bool, usize), + LoadWallet(usize), + LocateWallet, + CreateWallet, } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - match message { - LocalViewInteraction::Back => { - grin_gui.wallet_state.setup_state.mode = super::Mode::Init; - } - LocalViewInteraction::WalletRowSelect(is_selected, index) => { - if is_selected { - grin_gui - .wallet_state - .setup_state - .setup_wallet_list_state - .selected_wallet_index = index; - } - } - LocalViewInteraction::LoadWallet(index) => { - grin_gui.config.current_wallet_index = Some(index); - grin_gui.wallet_state.mode = crate::gui::element::wallet::Mode::Operation; - - // If transaction list hasn't been init yet, refresh the list with latest - if grin_gui - .wallet_state - .operation_state - .home_state - .tx_list_display_state - .mode - == crate::gui::element::wallet::operation::tx_list_display::Mode::NotInit - { - let fut = move || async {}; - return Ok(Command::perform(fut(), |_| { - return Message::Interaction( + match message { + LocalViewInteraction::Back => { + grin_gui.wallet_state.setup_state.mode = super::Mode::Init; + } + LocalViewInteraction::WalletRowSelect(is_selected, index) => { + if is_selected { + grin_gui + .wallet_state + .setup_state + .setup_wallet_list_state + .selected_wallet_index = index; + } + } + LocalViewInteraction::LoadWallet(index) => { + grin_gui.config.current_wallet_index = Some(index); + grin_gui.wallet_state.mode = crate::gui::element::wallet::Mode::Operation; + + // If transaction list hasn't been init yet, refresh the list with latest + if grin_gui + .wallet_state + .operation_state + .home_state + .tx_list_display_state + .mode == crate::gui::element::wallet::operation::tx_list_display::Mode::NotInit + { + let fut = move || async {}; + return Ok(Command::perform(fut(), |_| { + return Message::Interaction( Interaction::WalletOperationHomeTxListDisplayInteraction( crate::gui::element::wallet::operation::tx_list_display::LocalViewInteraction::SelectMode( crate::gui::element::wallet::operation::tx_list_display::Mode::Recent ), ), ); - })); - } - - } - LocalViewInteraction::LocateWallet => { - match FileDialog::new().show_open_single_file() { - Ok(path) => { - match path { - Some(d) => { - match validate_directory(d) { - Ok(wallet_was_imported) => {} - Err(err) => { - // tell the user why this directory failed - } - } - } - None => {} - } - } - Err(e) => { - log::debug!("wallet_list.rs::LocalViewInteraction::LocateWallet {}", e); - } - }; - } - LocalViewInteraction::CreateWallet => { - let state = &mut grin_gui.wallet_state.setup_state; - let config = &grin_gui.config; - let wallet_default_name = localized_string("wallet-default-name"); - let mut wallet_display_name = wallet_default_name.clone(); - let mut i = 1; - - // wallet display name must be unique: i.e. Default 1, Default 2, ... - while let Some(_) = config - .wallets - .iter() - .find(|wallet| wallet.display_name == wallet_display_name) - { - wallet_display_name = format!("{} {}", wallet_default_name, i); - i += 1; - } - - // i.e. default_1, default_2, ... - let wallet_dir: String = str::replace(&wallet_display_name.to_lowercase(), " ", "_"); - - state - .setup_wallet_state - .advanced_options_state - .top_level_directory = create_grin_wallet_path(&ChainTypes::Mainnet, &wallet_dir); - - grin_gui.wallet_state.mode = - crate::gui::element::wallet::Mode::CreateWallet(wallet_display_name); - } - } - - Ok(Command::none()) + })); + } + } + LocalViewInteraction::LocateWallet => { + match FileDialog::new().show_open_single_file() { + Ok(path) => { + match path { + Some(d) => { + match validate_directory(d) { + Ok(wallet_was_imported) => {} + Err(err) => { + // tell the user why this directory failed + } + } + } + None => {} + } + } + Err(e) => { + log::debug!("wallet_list.rs::LocalViewInteraction::LocateWallet {}", e); + } + }; + } + LocalViewInteraction::CreateWallet => { + let state = &mut grin_gui.wallet_state.setup_state; + let config = &grin_gui.config; + let wallet_default_name = localized_string("wallet-default-name"); + let mut wallet_display_name = wallet_default_name.clone(); + let mut i = 1; + + // wallet display name must be unique: i.e. Default 1, Default 2, ... + while let Some(_) = config + .wallets + .iter() + .find(|wallet| wallet.display_name == wallet_display_name) + { + wallet_display_name = format!("{} {}", wallet_default_name, i); + i += 1; + } + + // i.e. default_1, default_2, ... + let wallet_dir: String = str::replace(&wallet_display_name.to_lowercase(), " ", "_"); + + state + .setup_wallet_state + .advanced_options_state + .top_level_directory = create_grin_wallet_path(&ChainTypes::Mainnet, &wallet_dir); + + grin_gui.wallet_state.mode = + crate::gui::element::wallet::Mode::CreateWallet(wallet_display_name); + } + } + + Ok(Command::none()) } struct DirectoryValidationError; fn validate_directory(_d: PathBuf) -> Result { - Ok(true) + Ok(true) } pub fn data_container<'a>(state: &'a StateContainer, config: &Config) -> Container<'a, Message> { - let button_height = Length::Fixed(BUTTON_HEIGHT); - let button_width = Length::Fixed(BUTTON_WIDTH); - - let title = Text::new(localized_string("wallet-list")).size(DEFAULT_HEADER_FONT_SIZE); - let title_container = Container::new(title) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - let new_wallet_container = - Container::new(Text::new(localized_string("create-wallet")).size(DEFAULT_FONT_SIZE)) - .align_y(alignment::Vertical::Center) - .align_x(alignment::Horizontal::Center); - - let new_wallet_button: Element = Button::new(new_wallet_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletListWalletViewInteraction( - LocalViewInteraction::CreateWallet, - )) - .into(); - - // add additional buttons here - let button_row = Row::new().push(new_wallet_button.map(Message::Interaction)); - - let segmented_mode_container = Container::new(button_row).padding(1); - let segmented_mode_control_container = Container::new(segmented_mode_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let header_row = Row::new() - .push(title_container) - .push(Space::with_width(Length::Fill)) - .push(segmented_mode_control_container) - .align_items(Alignment::Center); - - let header_container = Container::new(header_row).padding(iced::Padding::from([ - 0, // top - 0, // right - DEFAULT_PADDING as u16, // bottom - 0, // left - ])); - - let name_header = Text::new(localized_string("name")).size(DEFAULT_FONT_SIZE); - let name_header_container = - Container::new(name_header).style(grin_gui_core::theme::ContainerStyle::BrightForeground); - - let chain_header = Text::new(localized_string("type")).size(DEFAULT_FONT_SIZE); - let chain_header_container = - Container::new(chain_header).style(grin_gui_core::theme::ContainerStyle::BrightForeground); - - let directory_header = Text::new(localized_string("directory")).size(DEFAULT_FONT_SIZE); - let directory_header_container = Container::new(directory_header) - .style(grin_gui_core::theme::ContainerStyle::BrightForeground); - - let table_header_row = Row::new() - .push( - Column::new() - .push(name_header_container) - .width(Length::FillPortion(1)), - ) - .push( - Column::new() - .push(chain_header_container) - .width(Length::FillPortion(1)), - ) - .push( - Column::new() - .push(directory_header_container) - .width(Length::FillPortion(3)), - ); - - let table_header_container = Container::new(table_header_row) - .padding(iced::Padding::from([ - 9, // top - DEFAULT_PADDING as u16, // right - should roughly match width of content scroll bar to align table headers - 9, // bottom - 9, // left - ])) - .style(grin_gui_core::theme::ContainerStyle::PanelForeground); - - let mut wallet_rows: Vec<_> = vec![]; - for (pos, w) in config.wallets.iter().enumerate() { - // si quieres el checkbox - // let checkbox = Checkbox::new(state.selected_wallet_index == pos, "", move |b| { - // Message::Interaction(Interaction::WalletListWalletViewInteraction( - // LocalViewInteraction::WalletRowSelect(b, pos), - // )) - // }) - // .style(grin_gui_core::theme::CheckboxStyles::Normal) - // .text_size(DEFAULT_FONT_SIZE) - // .spacing(10); - - let selected_wallet = state.selected_wallet_index == pos; - let wallet_name = Text::new(w.display_name.clone()).size(DEFAULT_FONT_SIZE); - let chain_name = Text::new(w.chain_type.shortname()).size(DEFAULT_FONT_SIZE); - - let mut wallet_name_container = Container::new(wallet_name) - .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); - - let mut wallet_chain_container = Container::new(chain_name) - .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); - - let tld_string = match &w.tld { - Some(path_buf) => path_buf.display().to_string(), - None => String::from("Unknown"), - }; - let wallet_directory = Text::new(tld_string).size(DEFAULT_FONT_SIZE); - - let mut wallet_directory_container = Container::new(wallet_directory) - .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); - - if selected_wallet { - wallet_name_container = wallet_name_container - .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); - wallet_chain_container = wallet_chain_container - .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); - wallet_directory_container = wallet_directory_container - .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); - } - - let wallet_row = Row::new() - //.push(checkbox) - .push( - Column::new() - .push(wallet_name_container) - .width(Length::FillPortion(1)), - ) - .push( - Column::new() - .push(wallet_chain_container) - .width(Length::FillPortion(1)), - ) - .push( - Column::new() - .push(wallet_directory_container) - .width(Length::FillPortion(3)), - ); - - let mut table_row = TableRow::new(wallet_row) - .padding(iced::Padding::from(9)) - .width(Length::Fill) - .on_press(move |_| { - log::debug!("data_container::table_row::on_press {}", pos); - - Message::Interaction(Interaction::WalletListWalletViewInteraction( - LocalViewInteraction::WalletRowSelect(true, pos), - )) - }); - - if selected_wallet { - // selected wallet should be highlighted - table_row = table_row.style(grin_gui_core::theme::TableRowStyle::TableRowSelected); - } else { - // contrast row styles to spice things up - if pos % 2 == 0 { - table_row = table_row.style(grin_gui_core::theme::TableRowStyle::TableRowLowlife); - } else { - table_row = table_row.style(grin_gui_core::theme::TableRowStyle::TableRowHighlife); - } - } - - wallet_rows.push(table_row.into()); - } - - let wallet_column = Column::new().push(Column::with_children(wallet_rows)); - - let load_wallet_button_container = - Container::new(Text::new(localized_string("load-wallet")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .align_y(alignment::Vertical::Center) - .align_x(alignment::Horizontal::Center); - - let mut load_wallet_button = - Button::new(load_wallet_button_container).style(grin_gui_core::theme::ButtonStyle::Primary); - - // the load wallet button should be disabled if there are no wallets - if !config.wallets.is_empty() { - load_wallet_button = - load_wallet_button.on_press(Interaction::WalletListWalletViewInteraction( - LocalViewInteraction::LoadWallet(state.selected_wallet_index), - )) - } - - let load_wallet_button: Element = load_wallet_button.into(); - - let select_folder_button_container = - Container::new(Text::new(localized_string("select-other")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .align_y(alignment::Vertical::Center) - .align_x(alignment::Horizontal::Center); - - let select_other_button: Element = Button::new(select_folder_button_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletListWalletViewInteraction( - LocalViewInteraction::LocateWallet, - )) - .into(); - - // button lipstick - let load_container = Container::new(load_wallet_button.map(Message::Interaction)).padding(1); - let load_container = Container::new(load_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let select_container = Container::new(select_other_button.map(Message::Interaction)).padding(1); - let select_container = Container::new(select_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let button_row = Row::new() - .push(load_container) - .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))) - .push(select_container) - .height(Length::Shrink); - - let scrollable = - Scrollable::new(wallet_column).style(grin_gui_core::theme::ScrollableStyle::Primary); - - let table_colummn = Column::new().push(table_header_container).push(scrollable); - let table_container = Container::new(table_colummn) - .style(grin_gui_core::theme::ContainerStyle::PanelBordered) - .height(Length::Fill) - .padding(1); - - let row = Row::new().push( - Column::new() - .push(table_container) - .push(Space::with_height(Length::Fixed(DEFAULT_PADDING))) - .push(button_row), - ); - - let content = Container::new(row) - .center_x() - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let wrapper_column = Column::new() - .height(Length::Fill) - .push(header_container) - .push(content); - - // Returns the final container. - Container::new(wrapper_column).padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + let button_height = Length::Fixed(BUTTON_HEIGHT); + let button_width = Length::Fixed(BUTTON_WIDTH); + + let title = Text::new(localized_string("wallet-list")).size(DEFAULT_HEADER_FONT_SIZE); + let title_container = Container::new(title) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + let new_wallet_container = + Container::new(Text::new(localized_string("create-wallet")).size(DEFAULT_FONT_SIZE)) + .align_y(alignment::Vertical::Center) + .align_x(alignment::Horizontal::Center); + + let new_wallet_button: Element = Button::new(new_wallet_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletListWalletViewInteraction( + LocalViewInteraction::CreateWallet, + )) + .into(); + + // add additional buttons here + let button_row = Row::new().push(new_wallet_button.map(Message::Interaction)); + + let segmented_mode_container = Container::new(button_row).padding(1); + let segmented_mode_control_container = Container::new(segmented_mode_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let header_row = Row::new() + .push(title_container) + .push(Space::with_width(Length::Fill)) + .push(segmented_mode_control_container) + .align_items(Alignment::Center); + + let header_container = Container::new(header_row).padding(iced::Padding::from([ + 0, // top + 0, // right + DEFAULT_PADDING as u16, // bottom + 0, // left + ])); + + let name_header = Text::new(localized_string("name")).size(DEFAULT_FONT_SIZE); + let name_header_container = + Container::new(name_header).style(grin_gui_core::theme::ContainerStyle::BrightForeground); + + let chain_header = Text::new(localized_string("type")).size(DEFAULT_FONT_SIZE); + let chain_header_container = + Container::new(chain_header).style(grin_gui_core::theme::ContainerStyle::BrightForeground); + + let directory_header = Text::new(localized_string("directory")).size(DEFAULT_FONT_SIZE); + let directory_header_container = Container::new(directory_header) + .style(grin_gui_core::theme::ContainerStyle::BrightForeground); + + let table_header_row = Row::new() + .push( + Column::new() + .push(name_header_container) + .width(Length::FillPortion(1)), + ) + .push( + Column::new() + .push(chain_header_container) + .width(Length::FillPortion(1)), + ) + .push( + Column::new() + .push(directory_header_container) + .width(Length::FillPortion(3)), + ); + + let table_header_container = Container::new(table_header_row) + .padding(iced::Padding::from([ + 9, // top + DEFAULT_PADDING as u16, // right - should roughly match width of content scroll bar to align table headers + 9, // bottom + 9, // left + ])) + .style(grin_gui_core::theme::ContainerStyle::PanelForeground); + + let mut wallet_rows: Vec<_> = vec![]; + for (pos, w) in config.wallets.iter().enumerate() { + // si quieres el checkbox + // let checkbox = Checkbox::new(state.selected_wallet_index == pos, "", move |b| { + // Message::Interaction(Interaction::WalletListWalletViewInteraction( + // LocalViewInteraction::WalletRowSelect(b, pos), + // )) + // }) + // .style(grin_gui_core::theme::CheckboxStyles::Normal) + // .text_size(DEFAULT_FONT_SIZE) + // .spacing(10); + + let selected_wallet = state.selected_wallet_index == pos; + let wallet_name = Text::new(w.display_name.clone()).size(DEFAULT_FONT_SIZE); + let chain_name = Text::new(w.chain_type.shortname()).size(DEFAULT_FONT_SIZE); + + let mut wallet_name_container = Container::new(wallet_name) + .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); + + let mut wallet_chain_container = Container::new(chain_name) + .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); + + let tld_string = match &w.tld { + Some(path_buf) => path_buf.display().to_string(), + None => String::from("Unknown"), + }; + let wallet_directory = Text::new(tld_string).size(DEFAULT_FONT_SIZE); + + let mut wallet_directory_container = Container::new(wallet_directory) + .style(grin_gui_core::theme::ContainerStyle::HoverableForeground); + + if selected_wallet { + wallet_name_container = wallet_name_container + .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); + wallet_chain_container = wallet_chain_container + .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); + wallet_directory_container = wallet_directory_container + .style(grin_gui_core::theme::ContainerStyle::HoverableBrightForeground); + } + + let wallet_row = Row::new() + //.push(checkbox) + .push( + Column::new() + .push(wallet_name_container) + .width(Length::FillPortion(1)), + ) + .push( + Column::new() + .push(wallet_chain_container) + .width(Length::FillPortion(1)), + ) + .push( + Column::new() + .push(wallet_directory_container) + .width(Length::FillPortion(3)), + ); + + let mut table_row = TableRow::new(wallet_row) + .padding(iced::Padding::from(9)) + .width(Length::Fill) + .on_press(move |_| { + log::debug!("data_container::table_row::on_press {}", pos); + + Message::Interaction(Interaction::WalletListWalletViewInteraction( + LocalViewInteraction::WalletRowSelect(true, pos), + )) + }); + + if selected_wallet { + // selected wallet should be highlighted + table_row = table_row.style(grin_gui_core::theme::TableRowStyle::TableRowSelected); + } else { + // contrast row styles to spice things up + if pos % 2 == 0 { + table_row = table_row.style(grin_gui_core::theme::TableRowStyle::TableRowLowlife); + } else { + table_row = table_row.style(grin_gui_core::theme::TableRowStyle::TableRowHighlife); + } + } + + wallet_rows.push(table_row.into()); + } + + let wallet_column = Column::new().push(Column::with_children(wallet_rows)); + + let load_wallet_button_container = + Container::new(Text::new(localized_string("load-wallet")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .align_y(alignment::Vertical::Center) + .align_x(alignment::Horizontal::Center); + + let mut load_wallet_button = + Button::new(load_wallet_button_container).style(grin_gui_core::theme::ButtonStyle::Primary); + + // the load wallet button should be disabled if there are no wallets + if !config.wallets.is_empty() { + load_wallet_button = + load_wallet_button.on_press(Interaction::WalletListWalletViewInteraction( + LocalViewInteraction::LoadWallet(state.selected_wallet_index), + )) + } + + let load_wallet_button: Element = load_wallet_button.into(); + + let select_folder_button_container = + Container::new(Text::new(localized_string("select-other")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .align_y(alignment::Vertical::Center) + .align_x(alignment::Horizontal::Center); + + let select_other_button: Element = Button::new(select_folder_button_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletListWalletViewInteraction( + LocalViewInteraction::LocateWallet, + )) + .into(); + + // button lipstick + let load_container = Container::new(load_wallet_button.map(Message::Interaction)).padding(1); + let load_container = Container::new(load_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let select_container = Container::new(select_other_button.map(Message::Interaction)).padding(1); + let select_container = Container::new(select_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let button_row = Row::new() + .push(load_container) + .push(Space::with_width(Length::Fixed(DEFAULT_PADDING))) + .push(select_container) + .height(Length::Shrink); + + let scrollable = + Scrollable::new(wallet_column).style(grin_gui_core::theme::ScrollableStyle::Primary); + + let table_colummn = Column::new().push(table_header_container).push(scrollable); + let table_container = Container::new(table_colummn) + .style(grin_gui_core::theme::ContainerStyle::PanelBordered) + .height(Length::Fill) + .padding(1); + + let row = Row::new().push( + Column::new() + .push(table_container) + .push(Space::with_height(Length::Fixed(DEFAULT_PADDING))) + .push(button_row), + ); + + let content = Container::new(row) + .center_x() + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let wrapper_column = Column::new() + .height(Length::Fill) + .push(header_container) + .push(content); + + // Returns the final container. + Container::new(wrapper_column).padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/wallet/setup/wallet_setup.rs b/src/gui/element/wallet/setup/wallet_setup.rs index d80c7c6..e22087a 100644 --- a/src/gui/element/wallet/setup/wallet_setup.rs +++ b/src/gui/element/wallet/setup/wallet_setup.rs @@ -4,634 +4,634 @@ use native_dialog::FileDialog; use std::path::PathBuf; use { - super::super::super::{ - BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, - }, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - anyhow::Context, - grin_gui_core::theme::ColorPalette, - grin_gui_core::theme::{ - Button, Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, - }, - grin_gui_core::{ - config::Wallet, - fs::PersistentData, - node::ChainTypes::{self, Mainnet, Testnet}, - wallet::create_grin_wallet_path, - wallet::WalletInterface, - }, - iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, - iced::{alignment, Alignment, Command, Length}, - std::sync::{Arc, RwLock}, + super::super::super::{ + BUTTON_HEIGHT, BUTTON_WIDTH, DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, DEFAULT_PADDING, + }, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + anyhow::Context, + grin_gui_core::theme::ColorPalette, + grin_gui_core::theme::{ + Button, Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, + }, + grin_gui_core::{ + config::Wallet, + fs::PersistentData, + node::ChainTypes::{self, Mainnet, Testnet}, + wallet::create_grin_wallet_path, + wallet::WalletInterface, + }, + iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + std::sync::{Arc, RwLock}, }; pub struct StateContainer { - pub password_state: PasswordState, - pub restore_from_seed: bool, - pub seed_input_value: String, - pub show_advanced_options: bool, - pub is_testnet: bool, - pub advanced_options_state: AdvancedOptionsState, + pub password_state: PasswordState, + pub restore_from_seed: bool, + pub seed_input_value: String, + pub show_advanced_options: bool, + pub is_testnet: bool, + pub advanced_options_state: AdvancedOptionsState, } impl Default for StateContainer { - fn default() -> Self { - Self { - password_state: Default::default(), - show_advanced_options: false, - is_testnet: false, - restore_from_seed: false, - seed_input_value: Default::default(), - advanced_options_state: Default::default(), - } - } + fn default() -> Self { + Self { + password_state: Default::default(), + show_advanced_options: false, + is_testnet: false, + restore_from_seed: false, + seed_input_value: Default::default(), + advanced_options_state: Default::default(), + } + } } pub struct AdvancedOptionsState { - pub display_name_value: String, - pub top_level_directory: PathBuf, + pub display_name_value: String, + pub top_level_directory: PathBuf, } impl Default for AdvancedOptionsState { - fn default() -> Self { - Self { - display_name_value: Default::default(), - top_level_directory: Default::default(), - } - } + fn default() -> Self { + Self { + display_name_value: Default::default(), + top_level_directory: Default::default(), + } + } } #[derive(Debug, Clone)] pub struct PasswordState { - pub input_value: String, - pub repeat_input_value: String, + pub input_value: String, + pub repeat_input_value: String, } impl Default for PasswordState { - fn default() -> Self { - PasswordState { - input_value: Default::default(), - repeat_input_value: Default::default(), - } - } + fn default() -> Self { + PasswordState { + input_value: Default::default(), + repeat_input_value: Default::default(), + } + } } #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Back, - //TODO: ZeroingString these - PasswordInput(String), - PasswordInputEnterPressed, - PasswordRepeatInput(String), - PasswordRepeatInputEnterPressed, - ToggleRestoreFromSeed(bool), - ToggleAdvancedOptions(bool), - ToggleIsTestnet(bool), - DisplayName(String), - CreateWallet(String, PathBuf), - WalletCreatedOk((String, String, String, ChainTypes)), - WalletCreateError(Arc>>), - SeedInput(String), - ShowFolderPicker, + Back, + //TODO: ZeroingString these + PasswordInput(String), + PasswordInputEnterPressed, + PasswordRepeatInput(String), + PasswordRepeatInputEnterPressed, + ToggleRestoreFromSeed(bool), + ToggleAdvancedOptions(bool), + ToggleIsTestnet(bool), + DisplayName(String), + CreateWallet(String, PathBuf), + WalletCreatedOk((String, String, String, ChainTypes)), + WalletCreateError(Arc>>), + SeedInput(String), + ShowFolderPicker, } pub fn handle_message<'a>( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.wallet_state.setup_state.setup_wallet_state; - match message { - LocalViewInteraction::Back => { - // reset user input values - grin_gui.wallet_state.setup_state.setup_wallet_state = Default::default(); - - // return user to proper view - match grin_gui.wallet_state.mode { - // back to init screen - element::wallet::Mode::Init => { - grin_gui.wallet_state.setup_state.mode = super::Mode::Init - } - _ => { - // back to list view - grin_gui.wallet_state.mode = element::wallet::Mode::Init; - grin_gui.wallet_state.setup_state.mode = super::Mode::ListWallets; - } - }; - } - LocalViewInteraction::PasswordInput(password) => { - state.password_state.input_value = password; - } - LocalViewInteraction::PasswordInputEnterPressed => { - // state.password_state.input_state.unfocus(); - // state.password_state.repeat_input_state.focus(); - } - LocalViewInteraction::PasswordRepeatInput(repeat_password) => { - state.password_state.repeat_input_value = repeat_password; - } - LocalViewInteraction::PasswordRepeatInputEnterPressed => { - //state.password_state.repeat_input_state.unfocus(); - } - LocalViewInteraction::ToggleRestoreFromSeed(_) => { - state.restore_from_seed = !state.restore_from_seed - } - LocalViewInteraction::ToggleAdvancedOptions(_) => { - state.show_advanced_options = !state.show_advanced_options - } - LocalViewInteraction::ToggleIsTestnet(_) => { - state.is_testnet = !state.is_testnet; - let current_tld = state.advanced_options_state.top_level_directory.clone(); - let directory = current_tld.file_name().unwrap().to_str().unwrap(); - - if state.is_testnet { - let default_path = create_grin_wallet_path(&Mainnet, directory); - // Only change if nobody's modified the default path - if default_path == current_tld { - state.advanced_options_state.top_level_directory = - create_grin_wallet_path(&Testnet, directory); - } - } else { - let default_path = create_grin_wallet_path(&Testnet, directory); - // Only change if nobody's modified the default path - if default_path == current_tld { - state.advanced_options_state.top_level_directory = - create_grin_wallet_path(&Mainnet, directory); - } - } - } - LocalViewInteraction::DisplayName(display_name_value) => { - state.advanced_options_state.display_name_value = display_name_value; - } - LocalViewInteraction::ShowFolderPicker => { - match FileDialog::new().show_open_single_dir() { - Ok(path) => match path { - Some(d) => { - state.advanced_options_state.top_level_directory = d; - } - None => {} - }, - Err(e) => { - log::debug!( - "wallet_setup.rs::LocalViewInteraction::ShowFolderPicker {}", - e - ); - } - }; - } - LocalViewInteraction::CreateWallet(display_name, top_level_directory) => { - grin_gui.error.take(); - - log::debug!( - "setup::wallet::LocalViewInteraction::CreateWallet {}", - display_name, - ); - - let password = state.password_state.input_value.clone(); - let w = grin_gui.wallet_interface.clone(); - let chain_type = if state.is_testnet { Testnet } else { Mainnet }; - let recovery_phrase = if !state.seed_input_value.is_empty() { - Some(state.seed_input_value.clone()) - } else { - None - }; - - let fut = move || { - WalletInterface::init( - w, - password.clone(), - top_level_directory, - display_name, - chain_type, - recovery_phrase, - ) - }; - - return Ok(Command::perform(fut(), |r| { - match r.context("Failed to Create Wallet") { - Ok(ret) => Message::Interaction(Interaction::WalletSetupWalletViewInteraction( - LocalViewInteraction::WalletCreatedOk(ret), - )), - Err(e) => Message::Interaction(Interaction::WalletSetupWalletViewInteraction( - LocalViewInteraction::WalletCreateError(Arc::new(RwLock::new(Some(e)))), - )), - } - })); - } - LocalViewInteraction::WalletCreatedOk((tld, mnemonic, display_name, chain_type)) => { - let tld = Some(PathBuf::from(&tld)); - let saved_wallet = Wallet::new(tld, display_name, chain_type); - - let index = grin_gui.config.add_wallet(saved_wallet); - grin_gui.config.current_wallet_index = Some(index); - grin_gui.wallet_state.clear_config_missing(); - grin_gui - .wallet_state - .setup_state - .setup_wallet_success_state - .recovery_phrase = mnemonic; - - // reset user input values - grin_gui.wallet_state.setup_state.setup_wallet_state = Default::default(); - - let _ = grin_gui.config.save(); - - grin_gui.wallet_state.setup_state.mode = - crate::gui::element::wallet::setup::Mode::WalletCreateSuccess; - - if grin_gui.wallet_state.mode != element::wallet::Mode::Init { - // set init state - grin_gui.wallet_state.mode = element::wallet::Mode::Init; - } - } - LocalViewInteraction::WalletCreateError(err) => { - grin_gui.error = err.write().unwrap().take(); - if let Some(e) = grin_gui.error.as_ref() { - log_error(e); - } - } - LocalViewInteraction::SeedInput(seed) => { - state.seed_input_value = seed; - } - } - - Ok(Command::none()) + let state = &mut grin_gui.wallet_state.setup_state.setup_wallet_state; + match message { + LocalViewInteraction::Back => { + // reset user input values + grin_gui.wallet_state.setup_state.setup_wallet_state = Default::default(); + + // return user to proper view + match grin_gui.wallet_state.mode { + // back to init screen + element::wallet::Mode::Init => { + grin_gui.wallet_state.setup_state.mode = super::Mode::Init + } + _ => { + // back to list view + grin_gui.wallet_state.mode = element::wallet::Mode::Init; + grin_gui.wallet_state.setup_state.mode = super::Mode::ListWallets; + } + }; + } + LocalViewInteraction::PasswordInput(password) => { + state.password_state.input_value = password; + } + LocalViewInteraction::PasswordInputEnterPressed => { + // state.password_state.input_state.unfocus(); + // state.password_state.repeat_input_state.focus(); + } + LocalViewInteraction::PasswordRepeatInput(repeat_password) => { + state.password_state.repeat_input_value = repeat_password; + } + LocalViewInteraction::PasswordRepeatInputEnterPressed => { + //state.password_state.repeat_input_state.unfocus(); + } + LocalViewInteraction::ToggleRestoreFromSeed(_) => { + state.restore_from_seed = !state.restore_from_seed + } + LocalViewInteraction::ToggleAdvancedOptions(_) => { + state.show_advanced_options = !state.show_advanced_options + } + LocalViewInteraction::ToggleIsTestnet(_) => { + state.is_testnet = !state.is_testnet; + let current_tld = state.advanced_options_state.top_level_directory.clone(); + let directory = current_tld.file_name().unwrap().to_str().unwrap(); + + if state.is_testnet { + let default_path = create_grin_wallet_path(&Mainnet, directory); + // Only change if nobody's modified the default path + if default_path == current_tld { + state.advanced_options_state.top_level_directory = + create_grin_wallet_path(&Testnet, directory); + } + } else { + let default_path = create_grin_wallet_path(&Testnet, directory); + // Only change if nobody's modified the default path + if default_path == current_tld { + state.advanced_options_state.top_level_directory = + create_grin_wallet_path(&Mainnet, directory); + } + } + } + LocalViewInteraction::DisplayName(display_name_value) => { + state.advanced_options_state.display_name_value = display_name_value; + } + LocalViewInteraction::ShowFolderPicker => { + match FileDialog::new().show_open_single_dir() { + Ok(path) => match path { + Some(d) => { + state.advanced_options_state.top_level_directory = d; + } + None => {} + }, + Err(e) => { + log::debug!( + "wallet_setup.rs::LocalViewInteraction::ShowFolderPicker {}", + e + ); + } + }; + } + LocalViewInteraction::CreateWallet(display_name, top_level_directory) => { + grin_gui.error.take(); + + log::debug!( + "setup::wallet::LocalViewInteraction::CreateWallet {}", + display_name, + ); + + let password = state.password_state.input_value.clone(); + let w = grin_gui.wallet_interface.clone(); + let chain_type = if state.is_testnet { Testnet } else { Mainnet }; + let recovery_phrase = if !state.seed_input_value.is_empty() { + Some(state.seed_input_value.clone()) + } else { + None + }; + + let fut = move || { + WalletInterface::init( + w, + password.clone(), + top_level_directory, + display_name, + chain_type, + recovery_phrase, + ) + }; + + return Ok(Command::perform(fut(), |r| { + match r.context("Failed to Create Wallet") { + Ok(ret) => Message::Interaction(Interaction::WalletSetupWalletViewInteraction( + LocalViewInteraction::WalletCreatedOk(ret), + )), + Err(e) => Message::Interaction(Interaction::WalletSetupWalletViewInteraction( + LocalViewInteraction::WalletCreateError(Arc::new(RwLock::new(Some(e)))), + )), + } + })); + } + LocalViewInteraction::WalletCreatedOk((tld, mnemonic, display_name, chain_type)) => { + let tld = Some(PathBuf::from(&tld)); + let saved_wallet = Wallet::new(tld, display_name, chain_type); + + let index = grin_gui.config.add_wallet(saved_wallet); + grin_gui.config.current_wallet_index = Some(index); + grin_gui.wallet_state.clear_config_missing(); + grin_gui + .wallet_state + .setup_state + .setup_wallet_success_state + .recovery_phrase = mnemonic; + + // reset user input values + grin_gui.wallet_state.setup_state.setup_wallet_state = Default::default(); + + let _ = grin_gui.config.save(); + + grin_gui.wallet_state.setup_state.mode = + crate::gui::element::wallet::setup::Mode::WalletCreateSuccess; + + if grin_gui.wallet_state.mode != element::wallet::Mode::Init { + // set init state + grin_gui.wallet_state.mode = element::wallet::Mode::Init; + } + } + LocalViewInteraction::WalletCreateError(err) => { + grin_gui.error = err.write().unwrap().take(); + if let Some(e) = grin_gui.error.as_ref() { + log_error(e); + } + } + LocalViewInteraction::SeedInput(seed) => { + state.seed_input_value = seed; + } + } + + Ok(Command::none()) } pub fn data_container<'a>( - state: &'a StateContainer, - default_display_name: &str, + state: &'a StateContainer, + default_display_name: &str, ) -> Container<'a, Message> { - let check_password = || { - state.password_state.input_value == state.password_state.repeat_input_value - && !state.password_state.input_value.is_empty() - }; - - let disp_password_status = || { - !state.password_state.input_value.is_empty() - && !state.password_state.repeat_input_value.is_empty() - }; - - let title = Text::new(localized_string("setup-grin-wallet-title")) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - - // we need 2 pts of padding here to match the position with other views: i.e. wallet list, settings. - // otherwise this title container will look like it shifts up slightly when the user toggles - // between views with buttons on the header row. - let title_container = Container::new(title) - .style(grin_gui_core::theme::ContainerStyle::BrightBackground) - .padding(iced::Padding::from([ - 2, // top - 0, // right - 2, // bottom - 5, // left - ])); - - // push more items on to header here: e.g. other buttons, things that belong on the header - let header_row = Row::new().push(title_container); - - let header_container = Container::new(header_row).padding(iced::Padding::from([ - 0, // top - 0, // right - DEFAULT_PADDING as u16, // bottom - 0, // left - ])); - - let password_column = { - let password_input = TextInput::new( - &localized_string("password")[..], - &state.password_state.input_value, - ) - .on_input(|s| { - Interaction::WalletSetupWalletViewInteraction(LocalViewInteraction::PasswordInput(s)) - }) - .on_submit(Interaction::WalletSetupWalletViewInteraction( - LocalViewInteraction::PasswordInputEnterPressed, - )) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(200.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery) - .password(); - - let password_input: Element = password_input.into(); - - let repeat_password_input = TextInput::new( - &localized_string("password-repeat")[..], - &state.password_state.repeat_input_value, - ) - .on_input(|s| { - Interaction::WalletSetupWalletViewInteraction( - LocalViewInteraction::PasswordRepeatInput(s), - ) - }) - .on_submit(Interaction::WalletSetupWalletViewInteraction( - LocalViewInteraction::PasswordRepeatInputEnterPressed, - )) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(200.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery) - .password(); - - let repeat_password_input: Element = repeat_password_input.into(); - - let mut password_entry_value = localized_string("setup-grin-passwords-dont-match"); - if check_password() { - password_entry_value = localized_string("setup-grin-passwords-okay") - } - let password_entry_status = Text::new(password_entry_value) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - let mut password_entry_status_container = Container::new(password_entry_status) - //.width(Length::Fill) - .style(grin_gui_core::theme::ContainerStyle::ErrorForeground); - - let mut password_input_col = Column::new() - .push(password_input.map(Message::Interaction)) - .push(repeat_password_input.map(Message::Interaction)) - .spacing(DEFAULT_PADDING) - .align_items(Alignment::Start); - - if !check_password() && disp_password_status() { - password_input_col = password_input_col.push(password_entry_status_container) - } else if check_password() { - password_entry_status_container = password_entry_status_container - .style(grin_gui_core::theme::ContainerStyle::SuccessBackground); - password_input_col = password_input_col.push(password_entry_status_container) - } - Column::new().push(password_input_col) - }; - - let description = Text::new(localized_string("setup-grin-wallet-enter-password")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - let description_container = - Container::new(description).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let mut restore_from_seed_column = { - let checkbox = Checkbox::new( - localized_string("restore-from-seed"), - state.restore_from_seed, - |b| { - Interaction::WalletSetupWalletViewInteraction( - LocalViewInteraction::ToggleRestoreFromSeed(b), - ) - }, - ) - .style(grin_gui_core::theme::CheckboxStyle::Normal) - .text_size(DEFAULT_FONT_SIZE) - .spacing(10); - - let checkbox: Element = checkbox.into(); - - let checkbox_container = Container::new(checkbox.map(Message::Interaction)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - Column::new().push(checkbox_container) - }; - - let show_advanced_options_column = { - let checkbox = Checkbox::new( - localized_string("show-advanced-options"), - state.show_advanced_options, - |b| { - Interaction::WalletSetupWalletViewInteraction( - LocalViewInteraction::ToggleAdvancedOptions(b), - ) - }, - ) - .style(grin_gui_core::theme::CheckboxStyle::Normal) - .text_size(DEFAULT_FONT_SIZE) - .spacing(10); - - let checkbox: Element = checkbox.into(); - - let checkbox_container = Container::new(checkbox.map(Message::Interaction)) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - Column::new().push(checkbox_container) - }; - - // ** start hideable restore from seed section - let seed_input: Element = TextInput::new( - "seed", - &state.seed_input_value, /*, |s| { - Interaction::WalletSetupWalletViewInteraction(LocalViewInteraction::SeedInput(s)) - }*/ - ) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(200.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery) - .into(); - - let seed_column = Column::with_children(vec![seed_input.map(Message::Interaction)]); - - if state.restore_from_seed { - restore_from_seed_column = restore_from_seed_column - .push(Space::with_height(Length::Fixed(DEFAULT_PADDING))) - .push(seed_column); - } - - // ** end hideable restore - - // ** start hideable advanced options - //let display_name_label = - let display_name = Text::new(localized_string("display-name")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let display_name_container = - Container::new(display_name).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let display_name_input = TextInput::new( - default_display_name, - &state.advanced_options_state.display_name_value, - ) - .on_input(|s| { - Interaction::WalletSetupWalletViewInteraction(LocalViewInteraction::DisplayName(s)) - }) - .size(DEFAULT_FONT_SIZE) - .padding(6) - .width(Length::Fixed(200.0)) - .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); - - let tld = Text::new(localized_string("top-level-domain")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); - - let tld_container = - Container::new(tld).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let folder_select_button_container = - Container::new(Text::new(localized_string("select-directory")).size(DEFAULT_FONT_SIZE)); - let folder_select_button = Button::new(folder_select_button_container) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::WalletSetupWalletViewInteraction( - LocalViewInteraction::ShowFolderPicker, - )); - let folder_select_button: Element = folder_select_button.into(); - - let tld_string = state - .advanced_options_state - .top_level_directory - .to_str() - .unwrap(); - let current_tld = Text::new(tld_string).size(DEFAULT_FONT_SIZE); - - let current_tld_container = - Container::new(current_tld).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let current_tld_column = Column::new() - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(current_tld_container); - - let folder_select_row = Row::new() - .push(folder_select_button.map(Message::Interaction)) - .push(Space::new( - Length::Fixed(DEFAULT_PADDING), - Length::Fixed(0.0), - )) - .push(current_tld_column); - - let display_name_input: Element = display_name_input.into(); - - let is_testnet_checkbox = - Checkbox::new(localized_string("use-testnet"), state.is_testnet, |b| { - Interaction::WalletSetupWalletViewInteraction(LocalViewInteraction::ToggleIsTestnet(b)) - }) - .style(grin_gui_core::theme::CheckboxStyle::Normal) - .text_size(DEFAULT_FONT_SIZE) - .spacing(10); - - let is_testnet_checkbox: Element = is_testnet_checkbox.into(); - - let is_testnet_row = Row::new().push(is_testnet_checkbox.map(Message::Interaction)); - - let advanced_options_column = Column::new() - .push(display_name_container) - .push(display_name_input.map(Message::Interaction)) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(tld_container) - .spacing(DEFAULT_PADDING) - .push(folder_select_row) - .spacing(DEFAULT_PADDING) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) - .push(is_testnet_row) - .push(restore_from_seed_column) - .align_items(Alignment::Start); - - // ** end hideable advanced options - - let button_height = Length::Fixed(BUTTON_HEIGHT); - let button_width = Length::Fixed(BUTTON_WIDTH); - - let submit_button_label_container = Container::new( - Text::new(localized_string("setup-grin-create-wallet")).size(DEFAULT_FONT_SIZE), - ) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let mut submit_button = Button::new(submit_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary); - if check_password() { - let top_level_directory = state.advanced_options_state.top_level_directory.clone(); - let display_name = if state.advanced_options_state.display_name_value.is_empty() { - default_display_name.to_string() - } else { - state.advanced_options_state.display_name_value.clone() - }; - - submit_button = submit_button.on_press(Interaction::WalletSetupWalletViewInteraction( - LocalViewInteraction::CreateWallet(display_name, top_level_directory), - )); - } - - let submit_button: Element = submit_button.into(); - - let cancel_button_label_container = - Container::new(Text::new(localized_string("cancel")).size(DEFAULT_FONT_SIZE)) - .width(button_width) - .height(button_height) - .center_x() - .center_y() - .align_x(alignment::Horizontal::Center); - - let cancel_button: Element = Button::new(cancel_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Primary) - .on_press(Interaction::WalletSetupWalletViewInteraction( - LocalViewInteraction::Back, - )) - .into(); - - let submit_container = Container::new(submit_button.map(Message::Interaction)).padding(1); - let submit_container = Container::new(submit_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); - let cancel_container = Container::new(cancel_container) - .style(grin_gui_core::theme::ContainerStyle::Segmented) - .padding(1); - - let unit_spacing = 15.0; - let button_row = Row::new() - .push(submit_container) - .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) - .push(cancel_container); - - let mut column = Column::new() - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(description_container) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(password_column) - .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) - .push(show_advanced_options_column) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 10.0), - )); - - if state.show_advanced_options { - column = column.push(advanced_options_column).push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 10.0), - )); - } - - column = column.push(button_row).align_items(Alignment::Start); - let form_container = Container::new(column) - .width(Length::Fill) - .padding(iced::Padding::from([ - 0, // top - 0, // right - 0, // bottom - 5, // left - ])); - - // form container should be scrollable in tiny windows - let scrollable = Scrollable::new(form_container) - .height(Length::Fill) - .style(grin_gui_core::theme::ScrollableStyle::Primary); - - let content = Container::new(scrollable) - .width(Length::Fill) - .height(Length::Shrink) - .style(grin_gui_core::theme::ContainerStyle::NormalBackground); - - let wrapper_column = Column::new() - .height(Length::Fill) - .push(header_container) - .push(content); - - // Returns the final container. - Container::new(wrapper_column).padding(iced::Padding::from([ - DEFAULT_PADDING, // top - DEFAULT_PADDING, // right - DEFAULT_PADDING, // bottom - DEFAULT_PADDING, // left - ])) + let check_password = || { + state.password_state.input_value == state.password_state.repeat_input_value + && !state.password_state.input_value.is_empty() + }; + + let disp_password_status = || { + !state.password_state.input_value.is_empty() + && !state.password_state.repeat_input_value.is_empty() + }; + + let title = Text::new(localized_string("setup-grin-wallet-title")) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + + // we need 2 pts of padding here to match the position with other views: i.e. wallet list, settings. + // otherwise this title container will look like it shifts up slightly when the user toggles + // between views with buttons on the header row. + let title_container = Container::new(title) + .style(grin_gui_core::theme::ContainerStyle::BrightBackground) + .padding(iced::Padding::from([ + 2, // top + 0, // right + 2, // bottom + 5, // left + ])); + + // push more items on to header here: e.g. other buttons, things that belong on the header + let header_row = Row::new().push(title_container); + + let header_container = Container::new(header_row).padding(iced::Padding::from([ + 0, // top + 0, // right + DEFAULT_PADDING as u16, // bottom + 0, // left + ])); + + let password_column = { + let password_input = TextInput::new( + &localized_string("password")[..], + &state.password_state.input_value, + ) + .on_input(|s| { + Interaction::WalletSetupWalletViewInteraction(LocalViewInteraction::PasswordInput(s)) + }) + .on_submit(Interaction::WalletSetupWalletViewInteraction( + LocalViewInteraction::PasswordInputEnterPressed, + )) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(200.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery) + .password(); + + let password_input: Element = password_input.into(); + + let repeat_password_input = TextInput::new( + &localized_string("password-repeat")[..], + &state.password_state.repeat_input_value, + ) + .on_input(|s| { + Interaction::WalletSetupWalletViewInteraction( + LocalViewInteraction::PasswordRepeatInput(s), + ) + }) + .on_submit(Interaction::WalletSetupWalletViewInteraction( + LocalViewInteraction::PasswordRepeatInputEnterPressed, + )) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(200.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery) + .password(); + + let repeat_password_input: Element = repeat_password_input.into(); + + let mut password_entry_value = localized_string("setup-grin-passwords-dont-match"); + if check_password() { + password_entry_value = localized_string("setup-grin-passwords-okay") + } + let password_entry_status = Text::new(password_entry_value) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + let mut password_entry_status_container = Container::new(password_entry_status) + //.width(Length::Fill) + .style(grin_gui_core::theme::ContainerStyle::ErrorForeground); + + let mut password_input_col = Column::new() + .push(password_input.map(Message::Interaction)) + .push(repeat_password_input.map(Message::Interaction)) + .spacing(DEFAULT_PADDING) + .align_items(Alignment::Start); + + if !check_password() && disp_password_status() { + password_input_col = password_input_col.push(password_entry_status_container) + } else if check_password() { + password_entry_status_container = password_entry_status_container + .style(grin_gui_core::theme::ContainerStyle::SuccessBackground); + password_input_col = password_input_col.push(password_entry_status_container) + } + Column::new().push(password_input_col) + }; + + let description = Text::new(localized_string("setup-grin-wallet-enter-password")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + let description_container = + Container::new(description).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let mut restore_from_seed_column = { + let checkbox = Checkbox::new( + localized_string("restore-from-seed"), + state.restore_from_seed, + |b| { + Interaction::WalletSetupWalletViewInteraction( + LocalViewInteraction::ToggleRestoreFromSeed(b), + ) + }, + ) + .style(grin_gui_core::theme::CheckboxStyle::Normal) + .text_size(DEFAULT_FONT_SIZE) + .spacing(10); + + let checkbox: Element = checkbox.into(); + + let checkbox_container = Container::new(checkbox.map(Message::Interaction)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + Column::new().push(checkbox_container) + }; + + let show_advanced_options_column = { + let checkbox = Checkbox::new( + localized_string("show-advanced-options"), + state.show_advanced_options, + |b| { + Interaction::WalletSetupWalletViewInteraction( + LocalViewInteraction::ToggleAdvancedOptions(b), + ) + }, + ) + .style(grin_gui_core::theme::CheckboxStyle::Normal) + .text_size(DEFAULT_FONT_SIZE) + .spacing(10); + + let checkbox: Element = checkbox.into(); + + let checkbox_container = Container::new(checkbox.map(Message::Interaction)) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + Column::new().push(checkbox_container) + }; + + // ** start hideable restore from seed section + let seed_input: Element = TextInput::new( + "seed", + &state.seed_input_value, /*, |s| { + Interaction::WalletSetupWalletViewInteraction(LocalViewInteraction::SeedInput(s)) + }*/ + ) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(200.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery) + .into(); + + let seed_column = Column::with_children(vec![seed_input.map(Message::Interaction)]); + + if state.restore_from_seed { + restore_from_seed_column = restore_from_seed_column + .push(Space::with_height(Length::Fixed(DEFAULT_PADDING))) + .push(seed_column); + } + + // ** end hideable restore + + // ** start hideable advanced options + //let display_name_label = + let display_name = Text::new(localized_string("display-name")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let display_name_container = + Container::new(display_name).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let display_name_input = TextInput::new( + default_display_name, + &state.advanced_options_state.display_name_value, + ) + .on_input(|s| { + Interaction::WalletSetupWalletViewInteraction(LocalViewInteraction::DisplayName(s)) + }) + .size(DEFAULT_FONT_SIZE) + .padding(6) + .width(Length::Fixed(200.0)) + .style(grin_gui_core::theme::TextInputStyle::AddonsQuery); + + let tld = Text::new(localized_string("top-level-domain")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); + + let tld_container = + Container::new(tld).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let folder_select_button_container = + Container::new(Text::new(localized_string("select-directory")).size(DEFAULT_FONT_SIZE)); + let folder_select_button = Button::new(folder_select_button_container) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::WalletSetupWalletViewInteraction( + LocalViewInteraction::ShowFolderPicker, + )); + let folder_select_button: Element = folder_select_button.into(); + + let tld_string = state + .advanced_options_state + .top_level_directory + .to_str() + .unwrap(); + let current_tld = Text::new(tld_string).size(DEFAULT_FONT_SIZE); + + let current_tld_container = + Container::new(current_tld).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let current_tld_column = Column::new() + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(current_tld_container); + + let folder_select_row = Row::new() + .push(folder_select_button.map(Message::Interaction)) + .push(Space::new( + Length::Fixed(DEFAULT_PADDING), + Length::Fixed(0.0), + )) + .push(current_tld_column); + + let display_name_input: Element = display_name_input.into(); + + let is_testnet_checkbox = + Checkbox::new(localized_string("use-testnet"), state.is_testnet, |b| { + Interaction::WalletSetupWalletViewInteraction(LocalViewInteraction::ToggleIsTestnet(b)) + }) + .style(grin_gui_core::theme::CheckboxStyle::Normal) + .text_size(DEFAULT_FONT_SIZE) + .spacing(10); + + let is_testnet_checkbox: Element = is_testnet_checkbox.into(); + + let is_testnet_row = Row::new().push(is_testnet_checkbox.map(Message::Interaction)); + + let advanced_options_column = Column::new() + .push(display_name_container) + .push(display_name_input.map(Message::Interaction)) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(tld_container) + .spacing(DEFAULT_PADDING) + .push(folder_select_row) + .spacing(DEFAULT_PADDING) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(5.0))) + .push(is_testnet_row) + .push(restore_from_seed_column) + .align_items(Alignment::Start); + + // ** end hideable advanced options + + let button_height = Length::Fixed(BUTTON_HEIGHT); + let button_width = Length::Fixed(BUTTON_WIDTH); + + let submit_button_label_container = Container::new( + Text::new(localized_string("setup-grin-create-wallet")).size(DEFAULT_FONT_SIZE), + ) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let mut submit_button = Button::new(submit_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary); + if check_password() { + let top_level_directory = state.advanced_options_state.top_level_directory.clone(); + let display_name = if state.advanced_options_state.display_name_value.is_empty() { + default_display_name.to_string() + } else { + state.advanced_options_state.display_name_value.clone() + }; + + submit_button = submit_button.on_press(Interaction::WalletSetupWalletViewInteraction( + LocalViewInteraction::CreateWallet(display_name, top_level_directory), + )); + } + + let submit_button: Element = submit_button.into(); + + let cancel_button_label_container = + Container::new(Text::new(localized_string("cancel")).size(DEFAULT_FONT_SIZE)) + .width(button_width) + .height(button_height) + .center_x() + .center_y() + .align_x(alignment::Horizontal::Center); + + let cancel_button: Element = Button::new(cancel_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Primary) + .on_press(Interaction::WalletSetupWalletViewInteraction( + LocalViewInteraction::Back, + )) + .into(); + + let submit_container = Container::new(submit_button.map(Message::Interaction)).padding(1); + let submit_container = Container::new(submit_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let cancel_container = Container::new(cancel_button.map(Message::Interaction)).padding(1); + let cancel_container = Container::new(cancel_container) + .style(grin_gui_core::theme::ContainerStyle::Segmented) + .padding(1); + + let unit_spacing = 15.0; + let button_row = Row::new() + .push(submit_container) + .push(Space::new(Length::Fixed(unit_spacing), Length::Fixed(0.0))) + .push(cancel_container); + + let mut column = Column::new() + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(description_container) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(password_column) + .push(Space::new(Length::Fixed(0.0), Length::Fixed(unit_spacing))) + .push(show_advanced_options_column) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 10.0), + )); + + if state.show_advanced_options { + column = column.push(advanced_options_column).push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 10.0), + )); + } + + column = column.push(button_row).align_items(Alignment::Start); + let form_container = Container::new(column) + .width(Length::Fill) + .padding(iced::Padding::from([ + 0, // top + 0, // right + 0, // bottom + 5, // left + ])); + + // form container should be scrollable in tiny windows + let scrollable = Scrollable::new(form_container) + .height(Length::Fill) + .style(grin_gui_core::theme::ScrollableStyle::Primary); + + let content = Container::new(scrollable) + .width(Length::Fill) + .height(Length::Shrink) + .style(grin_gui_core::theme::ContainerStyle::NormalBackground); + + let wrapper_column = Column::new() + .height(Length::Fill) + .push(header_container) + .push(content); + + // Returns the final container. + Container::new(wrapper_column).padding(iced::Padding::from([ + DEFAULT_PADDING, // top + DEFAULT_PADDING, // right + DEFAULT_PADDING, // bottom + DEFAULT_PADDING, // left + ])) } diff --git a/src/gui/element/wallet/setup/wallet_success.rs b/src/gui/element/wallet/setup/wallet_success.rs index c99fae5..b74de02 100644 --- a/src/gui/element/wallet/setup/wallet_success.rs +++ b/src/gui/element/wallet/setup/wallet_success.rs @@ -1,132 +1,132 @@ use { - super::super::super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, SMALLER_FONT_SIZE}, - crate::gui::{GrinGui, Interaction, Message}, - crate::localization::localized_string, - crate::Result, - grin_gui_core::theme::ColorPalette, - iced::{alignment, Alignment, Command, Length}, - grin_gui_core::theme::{Column, Element, Container, PickList, Row, Scrollable, Text, TextInput}, - iced::widget::{ - button, pick_list, scrollable, text_input, Button, Checkbox, Space, - }, - iced_aw::Card, + super::super::super::{DEFAULT_FONT_SIZE, DEFAULT_HEADER_FONT_SIZE, SMALLER_FONT_SIZE}, + crate::gui::{GrinGui, Interaction, Message}, + crate::localization::localized_string, + crate::Result, + grin_gui_core::theme::ColorPalette, + grin_gui_core::theme::{ + Column, Container, Element, PickList, Row, Scrollable, Text, TextInput, + }, + iced::widget::{button, pick_list, scrollable, text_input, Button, Checkbox, Space}, + iced::{alignment, Alignment, Command, Length}, + iced_aw::Card, }; pub struct StateContainer { - // TODO: ZeroingString this - pub recovery_phrase: String, + // TODO: ZeroingString this + pub recovery_phrase: String, } impl Default for StateContainer { - fn default() -> Self { - Self { - recovery_phrase: Default::default(), - } - } + fn default() -> Self { + Self { + recovery_phrase: Default::default(), + } + } } #[derive(Debug, Clone)] pub enum LocalViewInteraction { - Submit, + Submit, } pub fn handle_message( - grin_gui: &mut GrinGui, - message: LocalViewInteraction, + grin_gui: &mut GrinGui, + message: LocalViewInteraction, ) -> Result> { - let state = &mut grin_gui.wallet_state.setup_state.setup_wallet_state; - match message { - LocalViewInteraction::Submit => { - grin_gui.wallet_state.mode = super::super::Mode::Operation; - grin_gui.wallet_state.setup_state.mode = crate::gui::element::wallet::setup::Mode::Init; - } - } - Ok(Command::none()) + let state = &mut grin_gui.wallet_state.setup_state.setup_wallet_state; + match message { + LocalViewInteraction::Submit => { + grin_gui.wallet_state.mode = super::super::Mode::Operation; + grin_gui.wallet_state.setup_state.mode = crate::gui::element::wallet::setup::Mode::Init; + } + } + Ok(Command::none()) } -pub fn data_container<'a>( - state: &'a StateContainer, -) -> Container<'a, Message> { - // Title row - let title = Text::new(localized_string("setup-grin-wallet-success")) - .size(DEFAULT_HEADER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Left); +pub fn data_container<'a>(state: &'a StateContainer) -> Container<'a, Message> { + // Title row + let title = Text::new(localized_string("setup-grin-wallet-success")) + .size(DEFAULT_HEADER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Left); - let title_container = - Container::new(title).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let title_container = + Container::new(title).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let title_row = Row::new() - .push(title_container) - .align_items(Alignment::Center) - .spacing(20); + let title_row = Row::new() + .push(title_container) + .align_items(Alignment::Center) + .spacing(20); - let description = Text::new(localized_string("setup-grin-wallet-recovery-phrase")) - .size(DEFAULT_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center); - let description_container = - Container::new(description).style(grin_gui_core::theme::ContainerStyle::NormalBackground); + let description = Text::new(localized_string("setup-grin-wallet-recovery-phrase")) + .size(DEFAULT_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center); + let description_container = + Container::new(description).style(grin_gui_core::theme::ContainerStyle::NormalBackground); - let recovery_phrase_card = Card::new( - Text::new(localized_string("setup-grin-wallet-recovery-phrase-title")).size(DEFAULT_HEADER_FONT_SIZE), - Text::new(&state.recovery_phrase).size(DEFAULT_FONT_SIZE), - ) - .foot( - Column::new() - .spacing(10) - .padding(5) - .width(Length::Fill) - .align_items(Alignment::Center) - .push( - Button::new( - Text::new(localized_string("copy-to-clipboard")) - .size(SMALLER_FONT_SIZE) - .horizontal_alignment(alignment::Horizontal::Center), - ) - .style(grin_gui_core::theme::ButtonStyle::NormalText) - .on_press(Message::Interaction(Interaction::WriteToClipboard( - state.recovery_phrase.clone(), - ))), - ), - ) - .max_width(400.0) - .style(grin_gui_core::theme::CardStyle::Normal); + let recovery_phrase_card = Card::new( + Text::new(localized_string("setup-grin-wallet-recovery-phrase-title")) + .size(DEFAULT_HEADER_FONT_SIZE), + Text::new(&state.recovery_phrase).size(DEFAULT_FONT_SIZE), + ) + .foot( + Column::new() + .spacing(10) + .padding(5) + .width(Length::Fill) + .align_items(Alignment::Center) + .push( + Button::new( + Text::new(localized_string("copy-to-clipboard")) + .size(SMALLER_FONT_SIZE) + .horizontal_alignment(alignment::Horizontal::Center), + ) + .style(grin_gui_core::theme::ButtonStyle::NormalText) + .on_press(Message::Interaction(Interaction::WriteToClipboard( + state.recovery_phrase.clone(), + ))), + ), + ) + .max_width(400.0) + .style(grin_gui_core::theme::CardStyle::Normal); - let submit_button_label_container = - Container::new(Text::new(localized_string("setup-grin-wallet-done")).size(DEFAULT_FONT_SIZE)) - .center_x() - .align_x(alignment::Horizontal::Center); + let submit_button_label_container = Container::new( + Text::new(localized_string("setup-grin-wallet-done")).size(DEFAULT_FONT_SIZE), + ) + .center_x() + .align_x(alignment::Horizontal::Center); - let next_button = Button::new(submit_button_label_container) - .style(grin_gui_core::theme::ButtonStyle::Bordered) - .on_press(Interaction::WalletSetupWalletSuccessViewInteraction( - LocalViewInteraction::Submit, - )); + let next_button = Button::new(submit_button_label_container) + .style(grin_gui_core::theme::ButtonStyle::Bordered) + .on_press(Interaction::WalletSetupWalletSuccessViewInteraction( + LocalViewInteraction::Submit, + )); - let next_button: Element = next_button.into(); + let next_button: Element = next_button.into(); - let unit_spacing = 15.0; + let unit_spacing = 15.0; - let colum = Column::new() - .push(title_row) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 5.0), - )) - .push(description_container) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 5.0), - )) - .push(recovery_phrase_card) - .push(Space::new( - Length::Fixed(0.0), - Length::Fixed(unit_spacing + 10.0), - )) - .push(next_button.map(Message::Interaction)) - .align_items(Alignment::Center); + let colum = Column::new() + .push(title_row) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 5.0), + )) + .push(description_container) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 5.0), + )) + .push(recovery_phrase_card) + .push(Space::new( + Length::Fixed(0.0), + Length::Fixed(unit_spacing + 10.0), + )) + .push(next_button.map(Message::Interaction)) + .align_items(Alignment::Center); - Container::new(colum) - .center_y() - .center_x() - .width(Length::Fill) + Container::new(colum) + .center_y() + .center_x() + .width(Length::Fill) } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 99dbff7..dd44ed2 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,24 +1,27 @@ mod element; //mod style; -mod update; mod time; +mod update; use crate::cli::Opts; use crate::error_cause_string; -use crate::localization::{localized_string, LANG}; use crate::gui::element::{DEFAULT_FONT_SIZE, SMALLER_FONT_SIZE}; +use crate::localization::{localized_string, LANG}; use grin_gui_core::theme::Element; use grin_gui_core::{ - config::Config, - fs::PersistentData, - theme::{Theme, Container, Column, ColorPalette, Button, PickList, Row, Scrollable, Text}, - wallet::{WalletInterfaceHttpNodeClient, HTTPNodeClient, global, get_grin_wallet_default_path}, - node::{NodeInterface, subscriber::{self, UIMessage}, ChainTypes}, + config::Config, + fs::PersistentData, + node::{ + subscriber::{self, UIMessage}, + ChainTypes, NodeInterface, + }, + theme::{Button, ColorPalette, Column, Container, PickList, Row, Scrollable, Text, Theme}, + wallet::{get_grin_wallet_default_path, global, HTTPNodeClient, WalletInterfaceHttpNodeClient}, }; -use iced::{alignment, font, Alignment, Application, Command, Length, Subscription, Settings, window}; -use iced::widget::{ - button, pick_list, scrollable, text_input, Checkbox, Space, TextInput, +use iced::widget::{button, pick_list, scrollable, text_input, Checkbox, Space, TextInput}; +use iced::{ + alignment, font, window, Alignment, Application, Command, Length, Settings, Subscription, }; use iced_aw::{modal, Card, Modal}; @@ -31,238 +34,246 @@ use std::borrow::BorrowMut; //use std::collections::HashMap; use std::sync::{Arc, RwLock}; -use element::{DEFAULT_HEADER_FONT_SIZE}; +use element::DEFAULT_HEADER_FONT_SIZE; -static WINDOW_ICON: &[u8] = include_bytes!("../../resources/windows/ajour.ico"); +static WINDOW_ICON: &[u8] = include_bytes!("../../resources/windows/grin.ico"); pub struct GrinGui { - /// Wallet Interface - wallet_interface: Arc>, + /// Wallet Interface + wallet_interface: Arc>, - /// Node Interface - node_interface: Arc>, + /// Node Interface + node_interface: Arc>, - error: Option, - mode: Mode, - config: Config, + error: Option, + mode: Mode, + config: Config, - /// Main menu state - menu_state: element::menu::StateContainer, + /// Main menu state + menu_state: element::menu::StateContainer, - /// Top-Level Wallet area state - wallet_state: element::wallet::StateContainer, + /// Top-Level Wallet area state + wallet_state: element::wallet::StateContainer, - /// Top-Level Node area state - node_state: element::node::StateContainer, + /// Top-Level Node area state + node_state: element::node::StateContainer, - /// Settings screen + sub-screens states - settings_state: element::settings::StateContainer, - wallet_settings_state: element::settings::wallet::StateContainer, - node_settings_state: element::settings::node::StateContainer, - general_settings_state: element::settings::general::StateContainer, + /// Settings screen + sub-screens states + settings_state: element::settings::StateContainer, + wallet_settings_state: element::settings::wallet::StateContainer, + node_settings_state: element::settings::node::StateContainer, + general_settings_state: element::settings::general::StateContainer, - /// About screen state - about_state: element::about::StateContainer, + /// About screen state + about_state: element::about::StateContainer, - show_modal: bool, - modal_type: ModalType, - exit: bool, - theme: Theme, + show_modal: bool, + modal_type: ModalType, + exit: bool, + theme: Theme, } impl GrinGui { - pub fn show_exit (&mut self, show: bool) { - self.show_modal = show; - if show { - self.modal_type = ModalType::Exit; - } else { - self.modal_type = ModalType::Error; - } - } - - pub fn safe_exit (&mut self) { - let mut node = self.node_interface.write().unwrap(); - node.shutdown_server(true); - } + pub fn show_exit(&mut self, show: bool) { + self.show_modal = show; + if show { + self.modal_type = ModalType::Exit; + } else { + self.modal_type = ModalType::Error; + } + } + + pub fn safe_exit(&mut self) { + let mut node = self.node_interface.write().unwrap(); + node.shutdown_server(true); + } } -impl GrinGui{ - fn from_config(config: &Config) -> Self { - - // Instantiate wallet node client - // TODO: Fill out - let node_url = "http://localhost:8080"; - let node_client = HTTPNodeClient::new(node_url, None).unwrap(); - - // restore theme from config - let name = config.theme.clone().unwrap_or("Alliance".to_string()); - let theme = Theme::all().iter().find(|t| t.0 == name).unwrap().1.clone(); - - Self { - wallet_interface: Arc::new(RwLock::new(WalletInterfaceHttpNodeClient::new(node_client))), - node_interface: Arc::new(RwLock::new(NodeInterface::new())), - error: None, - mode: Mode::Catalog, - config: Config::default(), - menu_state: Default::default(), - wallet_state: Default::default(), - node_state: Default::default(), - settings_state: Default::default(), - wallet_settings_state: Default::default(), - node_settings_state: Default::default(), - general_settings_state: Default::default(), - about_state: Default::default(), - show_modal: false, - modal_type: ModalType::Error, - exit: false, - theme, - } - } +impl GrinGui { + fn from_config(config: &Config) -> Self { + // Instantiate wallet node client + // TODO: Fill out + let node_url = "http://localhost:8080"; + let node_client = HTTPNodeClient::new(node_url, None).unwrap(); + + // restore theme from config + let name = config.theme.clone().unwrap_or("Alliance".to_string()); + let theme = Theme::all().iter().find(|t| t.0 == name).unwrap().1.clone(); + + Self { + wallet_interface: Arc::new(RwLock::new(WalletInterfaceHttpNodeClient::new( + node_client, + ))), + node_interface: Arc::new(RwLock::new(NodeInterface::new())), + error: None, + mode: Mode::Catalog, + config: Config::default(), + menu_state: Default::default(), + wallet_state: Default::default(), + node_state: Default::default(), + settings_state: Default::default(), + wallet_settings_state: Default::default(), + node_settings_state: Default::default(), + general_settings_state: Default::default(), + about_state: Default::default(), + show_modal: false, + modal_type: ModalType::Error, + exit: false, + theme, + } + } } #[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum Message { - Error(Arc>>), - SendNodeMessage((usize, UIMessage, Option>)), - Interaction(Interaction), - Tick(chrono::DateTime), - RuntimeEvent(iced_core::Event), - FontLoaded(Result<(), font::Error>), - None(()), + Error(Arc>>), + SendNodeMessage((usize, UIMessage, Option>)), + Interaction(Interaction), + Tick(chrono::DateTime), + RuntimeEvent(iced_core::Event), + FontLoaded(Result<(), font::Error>), + None(()), } pub enum ModalType { - Exit, - Error, + Exit, + Error, } impl Application for GrinGui { - type Executor = iced::executor::Default; - type Message = Message; - type Flags = Config; - type Theme = Theme; - - fn theme(&self) -> Theme { - self.theme.clone() - } - - fn new(config: Config) -> (Self, Command) { - let mut grin_gui = GrinGui::from_config(&config); - - // default Mainnet - global::set_local_chain_type(ChainTypes::Mainnet); - - if let Some(wallet_index) = config.current_wallet_index { - let wallet = config.wallets[wallet_index].clone(); - global::set_local_chain_type(wallet.chain_type); - } - - // Check initial wallet status - /*if !config.wallet.toml_file_path.is_some() - || !w.config_exists( - config - .wallet - .toml_file_path - .as_ref() - .unwrap() - .to_str() - .unwrap(), - ) - { - grin_gui.menu_state.mode = element::menu::Mode::Wallet; - }*/ - - apply_config(&mut grin_gui, config); - let load_font_reg = iced::font::load(include_bytes!("../../fonts/notosans-regular.ttf").as_slice()).map(Message::FontLoaded); - let load_font_bold = iced::font::load(include_bytes!("../../fonts/notosans-bold.ttf").as_slice()).map(Message::FontLoaded); - (grin_gui, Command::batch(vec![load_font_reg, load_font_bold])) - } - - fn title(&self) -> String { - String::from("Grin") - } - - fn scale_factor(&self) -> f64 { - self.general_settings_state.scale_state.scale - } - - /*#[cfg(target_os = "windows")] - fn mode(&self) -> iced::window::Mode { - use crate::tray::GUI_VISIBLE; - use iced::window::Mode; - use std::sync::atomic::Ordering; - - if GUI_VISIBLE.load(Ordering::Relaxed) { - Mode::Windowed - } else { - Mode::Hidden - } - }*/ - - fn subscription(&self) -> Subscription { - let runtime_subscription = iced_futures::subscription::events().map(Message::RuntimeEvent); - let tick_subscription = time::every(std::time::Duration::from_millis(1000)).map(Message::Tick); - let node_subscription = subscriber::subscriber(0).map(|e| - Message::SendNodeMessage(e) - ); - - iced::Subscription::batch(vec![runtime_subscription, tick_subscription, node_subscription]) - } - - fn update(&mut self, message: Message) -> Command { - match update::handle_message(self, message) { - Ok(x) => x, - Err(e) => Command::perform(async { Arc::new(RwLock::new(Some(e))) }, Message::Error), - } - } - - fn view(&self) -> Element { - let menu_state = self.menu_state.clone(); - - let mut content = Column::new().push(element::menu::data_container( - &self.menu_state, - &self.error, - )); - - // Spacer between menu and content. - //content = content.push(Space::new(Length::Fixed(0.0), Length::Fixed(DEFAULT_PADDING))); - match menu_state.mode { - element::menu::Mode::Wallet => { - let setup_container = element::wallet::data_container( - &self.wallet_state, - &self.config - ); - content = content.push(setup_container) - } - element::menu::Mode::Node => { - let chain_type = self.node_interface.read().unwrap().chain_type.unwrap_or_else( || ChainTypes::Mainnet); - let node_container = element::node::data_container( - &self.node_state, - chain_type, - ); - content = content.push(node_container) - } - element::menu::Mode::About => { - let about_container = - element::about::data_container(&None, &self.about_state); - content = content.push(about_container) - } - element::menu::Mode::Settings => { - content = content.push(element::settings::data_container( - &self.settings_state, - &self.config, - &self.wallet_settings_state, - &self.node_settings_state, - &self.general_settings_state, - )) - /*if let Some(settings_container) = views.get_mut(settings_view_index) { - content = content.push(settings_container.view.data_container) - }*/ - } - } - - let underlay: Element = + type Executor = iced::executor::Default; + type Message = Message; + type Flags = Config; + type Theme = Theme; + + fn theme(&self) -> Theme { + self.theme.clone() + } + + fn new(config: Config) -> (Self, Command) { + let mut grin_gui = GrinGui::from_config(&config); + + // default Mainnet + global::set_local_chain_type(ChainTypes::Mainnet); + + if let Some(wallet_index) = config.current_wallet_index { + let wallet = config.wallets[wallet_index].clone(); + global::set_local_chain_type(wallet.chain_type); + } + + // Check initial wallet status + /*if !config.wallet.toml_file_path.is_some() + || !w.config_exists( + config + .wallet + .toml_file_path + .as_ref() + .unwrap() + .to_str() + .unwrap(), + ) + { + grin_gui.menu_state.mode = element::menu::Mode::Wallet; + }*/ + + apply_config(&mut grin_gui, config); + let load_font_reg = + iced::font::load(include_bytes!("../../fonts/notosans-regular.ttf").as_slice()) + .map(Message::FontLoaded); + let load_font_bold = + iced::font::load(include_bytes!("../../fonts/notosans-bold.ttf").as_slice()) + .map(Message::FontLoaded); + ( + grin_gui, + Command::batch(vec![load_font_reg, load_font_bold]), + ) + } + + fn title(&self) -> String { + String::from("Grin") + } + + fn scale_factor(&self) -> f64 { + self.general_settings_state.scale_state.scale + } + + /*#[cfg(target_os = "windows")] + fn mode(&self) -> iced::window::Mode { + use crate::tray::GUI_VISIBLE; + use iced::window::Mode; + use std::sync::atomic::Ordering; + + if GUI_VISIBLE.load(Ordering::Relaxed) { + Mode::Windowed + } else { + Mode::Hidden + } + }*/ + + fn subscription(&self) -> Subscription { + let runtime_subscription = iced_futures::subscription::events().map(Message::RuntimeEvent); + let tick_subscription = + time::every(std::time::Duration::from_millis(1000)).map(Message::Tick); + let node_subscription = subscriber::subscriber(0).map(|e| Message::SendNodeMessage(e)); + + iced::Subscription::batch(vec![ + runtime_subscription, + tick_subscription, + node_subscription, + ]) + } + + fn update(&mut self, message: Message) -> Command { + match update::handle_message(self, message) { + Ok(x) => x, + Err(e) => Command::perform(async { Arc::new(RwLock::new(Some(e))) }, Message::Error), + } + } + + fn view(&self) -> Element { + let menu_state = self.menu_state.clone(); + + let mut content = + Column::new().push(element::menu::data_container(&self.menu_state, &self.error)); + + // Spacer between menu and content. + //content = content.push(Space::new(Length::Fixed(0.0), Length::Fixed(DEFAULT_PADDING))); + match menu_state.mode { + element::menu::Mode::Wallet => { + let setup_container = + element::wallet::data_container(&self.wallet_state, &self.config); + content = content.push(setup_container) + } + element::menu::Mode::Node => { + let chain_type = self + .node_interface + .read() + .unwrap() + .chain_type + .unwrap_or_else(|| ChainTypes::Mainnet); + let node_container = element::node::data_container(&self.node_state, chain_type); + content = content.push(node_container) + } + element::menu::Mode::About => { + let about_container = element::about::data_container(&None, &self.about_state); + content = content.push(about_container) + } + element::menu::Mode::Settings => { + content = content.push(element::settings::data_container( + &self.settings_state, + &self.config, + &self.wallet_settings_state, + &self.node_settings_state, + &self.general_settings_state, + )) + /*if let Some(settings_container) = views.get_mut(settings_view_index) { + content = content.push(settings_container.view.data_container) + }*/ + } + } + + let underlay: Element = // Wraps everything in a container. Container::new(content) .width(Length::Fill) @@ -270,438 +281,457 @@ impl Application for GrinGui { .style(grin_gui_core::theme::ContainerStyle::NormalBackground) .into(); - let content: Element = - match self.modal_type { - ModalType::Exit => element::modal::exit_card().into(), - ModalType::Error => { - let error_cause = self.error - .as_ref() - .map_or_else(|| "unknown error".to_owned(), |e| error_cause_string(e)); - - element::modal::error_card(error_cause.clone()).into() - } - }; - - Modal::new(self.show_modal, underlay, content) - .on_esc(Message::Interaction(Interaction::CloseErrorModal)) - .style(grin_gui_core::theme::ModalStyle::Normal) - .into() - - } + let content: Element = match self.modal_type { + ModalType::Exit => element::modal::exit_card().into(), + ModalType::Error => { + let error_cause = self + .error + .as_ref() + .map_or_else(|| "unknown error".to_owned(), |e| error_cause_string(e)); + + element::modal::error_card(error_cause.clone()).into() + } + }; + + Modal::new(self.show_modal, underlay, content) + .on_esc(Message::Interaction(Interaction::CloseErrorModal)) + .style(grin_gui_core::theme::ModalStyle::Normal) + .into() + } } /// Starts the GUI. /// This function does not return. pub fn run(opts: Opts, config: Config) { - // Set LANG using config (defaults to "en_US") - LANG.set(RwLock::new(config.language.language_code())) - .expect("setting LANG from config"); - - log::debug!("config loaded:\n{:#?}", &config); - - let mut settings = Settings::default(); - settings.window.size = config.window_size.unwrap_or((900, 620)); - - #[cfg(target_os = "macos")] - { - // false needed for Application shutdown - settings.exit_on_close_request = false; - } - - #[cfg(target_os = "windows")] - { - settings.exit_on_close_request = false; - } - - #[cfg(not(target_os = "linux"))] - // TODO (casperstorm): Due to an upstream bug, min_size causes the window to become unresizable - // on Linux. - // @see: https://github.com/ajour/ajour/issues/427 - { - settings.window.min_size = Some((600, 300)); - } - - #[cfg(feature = "wgpu")] - { - let antialiasing = opts.antialiasing.unwrap_or(true); - log::debug!("antialiasing: {}", antialiasing); - settings.antialiasing = antialiasing; - } - - #[cfg(feature = "opengl")] - { - let antialiasing = opts.antialiasing.unwrap_or(false); - log::debug!("antialiasing: {}", antialiasing); - settings.antialiasing = antialiasing; - } - - // Sets the Window icon. - let image = image::load_from_memory_with_format(WINDOW_ICON, ImageFormat::Ico) - .expect("loading icon") - .to_rgba8(); - let (width, height) = image.dimensions(); - let icon = iced_core::window::icon::from_rgba(image.into_raw(), width, height); - settings.window.icon = Some(icon.unwrap()); - - settings.flags = config; - - // Runs the GUI. - GrinGui::run(settings).expect("running Grin gui"); + // Set LANG using config (defaults to "en_US") + LANG.set(RwLock::new(config.language.language_code())) + .expect("setting LANG from config"); + + log::debug!("config loaded:\n{:#?}", &config); + + let mut settings = Settings::default(); + settings.window.size = config.window_size.unwrap_or((900, 620)); + + #[cfg(target_os = "macos")] + { + // false needed for Application shutdown + settings.exit_on_close_request = false; + } + + #[cfg(target_os = "windows")] + { + settings.exit_on_close_request = false; + } + + #[cfg(not(target_os = "linux"))] + // TODO (casperstorm): Due to an upstream bug, min_size causes the window to become unresizable + // on Linux. + // @see: https://github.com/ajour/ajour/issues/427 + { + settings.window.min_size = Some((600, 300)); + } + + #[cfg(feature = "wgpu")] + { + let antialiasing = opts.antialiasing.unwrap_or(true); + log::debug!("antialiasing: {}", antialiasing); + settings.antialiasing = antialiasing; + } + + #[cfg(feature = "opengl")] + { + let antialiasing = opts.antialiasing.unwrap_or(false); + log::debug!("antialiasing: {}", antialiasing); + settings.antialiasing = antialiasing; + } + + // Sets the Window icon. + let image = image::load_from_memory_with_format(WINDOW_ICON, ImageFormat::Ico) + .expect("loading icon") + .to_rgba8(); + let (width, height) = image.dimensions(); + let icon = iced_core::window::icon::from_rgba(image.into_raw(), width, height); + settings.window.icon = Some(icon.unwrap()); + + settings.flags = config; + + // Runs the GUI. + GrinGui::run(settings).expect("running Grin gui"); } #[derive(Debug)] pub enum State { - Ready, - Loading, - Error(anyhow::Error), + Ready, + Loading, + Error(anyhow::Error), } impl Default for State { - fn default() -> Self { - State::Ready - } + fn default() -> Self { + State::Ready + } } #[derive(Debug, Clone, Copy)] pub enum SelfUpdateStatus { - InProgress, - Failed, + InProgress, + Failed, } impl std::fmt::Display for SelfUpdateStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = match self { - SelfUpdateStatus::InProgress => localized_string("updating"), - SelfUpdateStatus::Failed => localized_string("failed"), - }; - write!(f, "{}", s) - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + SelfUpdateStatus::InProgress => localized_string("updating"), + SelfUpdateStatus::Failed => localized_string("failed"), + }; + write!(f, "{}", s) + } } #[derive(Default, Debug)] pub struct SelfUpdateState { - status: Option, + status: Option, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Mode { - Catalog, - Install, - Settings, - About, + Catalog, + Install, + Settings, + About, } #[derive(Debug, Clone)] #[allow(clippy::large_enum_variant)] pub enum Interaction { - /// Error modal - OpenErrorModal, - CloseErrorModal, - /// Clipboard copy - WriteToClipboard(String), - ReadSlatepackFromClipboard, - /// View interactions - MenuViewInteraction(element::menu::LocalViewInteraction), - SettingsViewInteraction(element::settings::LocalViewInteraction), - WalletSettingsViewInteraction(element::settings::wallet::LocalViewInteraction), - NodeSettingsViewInteraction(element::settings::node::LocalViewInteraction), - GeneralSettingsViewInteraction(element::settings::general::LocalViewInteraction), - GeneralSettingsViewImportTheme, - WalletSetupViewInteraction(element::wallet::setup::LocalViewInteraction), - WalletSetupInitViewInteraction(element::wallet::setup::init::LocalViewInteraction), - WalletSetupWalletViewInteraction(element::wallet::setup::wallet_setup::LocalViewInteraction), - WalletListWalletViewInteraction(element::wallet::setup::wallet_list::LocalViewInteraction), - WalletSetupWalletSuccessViewInteraction(element::wallet::setup::wallet_success::LocalViewInteraction), - WalletOperationOpenViewInteraction(element::wallet::operation::open::LocalViewInteraction), - WalletOperationHomeViewInteraction(element::wallet::operation::home::LocalViewInteraction), - WalletOperationTxListInteraction(element::wallet::operation::tx_list::LocalViewInteraction), - WalletOperationHomeTxListDisplayInteraction(element::wallet::operation::tx_list_display::LocalViewInteraction), - WalletOperationHomeActionMenuViewInteraction(element::wallet::operation::action_menu::LocalViewInteraction), - WalletOperationCreateTxViewInteraction(element::wallet::operation::create_tx::LocalViewInteraction), - WalletOperationApplyTxViewInteraction(element::wallet::operation::apply_tx::LocalViewInteraction), - WalletOperationApplyTxConfirmViewInteraction(element::wallet::operation::apply_tx_confirm::LocalViewInteraction), - WalletOperationShowSlatepackViewInteraction(element::wallet::operation::show_slatepack::LocalViewInteraction), - WalletOperationTxDetailViewInteraction(element::wallet::operation::tx_detail::LocalViewInteraction), - WalletOperationTxProofViewInteraction(element::wallet::operation::tx_proof::LocalViewInteraction), - WalletOperationTxDoneViewInteraction(element::wallet::operation::tx_done::LocalViewInteraction), - WalletOperationCreateTxContractsViewInteraction(element::wallet::operation::create_tx_contracts::LocalViewInteraction), - ViewInteraction(String, String), - ModeSelected(Mode), - ModeSelectedSettings(element::settings::Mode), - //Expand(ExpandType), - Ignore(String), - SelectBackupDirectory(), - OpenLink(String), - Unignore(String), - Update(String), - ScaleUp, - ScaleDown, - SortCatalogColumn(element::wallet::operation::tx_list::ColumnKey), - Backup, - ToggleHideIgnoredAddons(bool), - CatalogQuery(String), - InstallScmQuery(String), - InstallScmUrl, - UpdateGrin, - AlternatingRowColorToggled(bool), - KeybindingsToggle(bool), - #[cfg(target_os = "windows")] - ToggleCloseToTray(bool), - #[cfg(target_os = "windows")] - ToggleAutoStart(bool), - #[cfg(target_os = "windows")] - ToggleStartClosedToTray(bool), - - /// Application shutdown - Exit, - ExitCancel + /// Error modal + OpenErrorModal, + CloseErrorModal, + /// Clipboard copy + WriteToClipboard(String), + ReadSlatepackFromClipboard, + /// View interactions + MenuViewInteraction(element::menu::LocalViewInteraction), + SettingsViewInteraction(element::settings::LocalViewInteraction), + WalletSettingsViewInteraction(element::settings::wallet::LocalViewInteraction), + NodeSettingsViewInteraction(element::settings::node::LocalViewInteraction), + GeneralSettingsViewInteraction(element::settings::general::LocalViewInteraction), + GeneralSettingsViewImportTheme, + WalletSetupViewInteraction(element::wallet::setup::LocalViewInteraction), + WalletSetupInitViewInteraction(element::wallet::setup::init::LocalViewInteraction), + WalletSetupWalletViewInteraction(element::wallet::setup::wallet_setup::LocalViewInteraction), + WalletListWalletViewInteraction(element::wallet::setup::wallet_list::LocalViewInteraction), + WalletSetupWalletSuccessViewInteraction( + element::wallet::setup::wallet_success::LocalViewInteraction, + ), + WalletOperationOpenViewInteraction(element::wallet::operation::open::LocalViewInteraction), + WalletOperationHomeViewInteraction(element::wallet::operation::home::LocalViewInteraction), + WalletOperationTxListInteraction(element::wallet::operation::tx_list::LocalViewInteraction), + WalletOperationHomeTxListDisplayInteraction( + element::wallet::operation::tx_list_display::LocalViewInteraction, + ), + WalletOperationHomeActionMenuViewInteraction( + element::wallet::operation::action_menu::LocalViewInteraction, + ), + WalletOperationCreateTxViewInteraction( + element::wallet::operation::create_tx::LocalViewInteraction, + ), + WalletOperationApplyTxViewInteraction( + element::wallet::operation::apply_tx::LocalViewInteraction, + ), + WalletOperationApplyTxConfirmViewInteraction( + element::wallet::operation::apply_tx_confirm::LocalViewInteraction, + ), + WalletOperationShowSlatepackViewInteraction( + element::wallet::operation::show_slatepack::LocalViewInteraction, + ), + WalletOperationTxDetailViewInteraction( + element::wallet::operation::tx_detail::LocalViewInteraction, + ), + WalletOperationTxProofViewInteraction( + element::wallet::operation::tx_proof::LocalViewInteraction, + ), + WalletOperationTxDoneViewInteraction(element::wallet::operation::tx_done::LocalViewInteraction), + WalletOperationCreateTxContractsViewInteraction( + element::wallet::operation::create_tx_contracts::LocalViewInteraction, + ), + ViewInteraction(String, String), + ModeSelected(Mode), + ModeSelectedSettings(element::settings::Mode), + //Expand(ExpandType), + Ignore(String), + SelectBackupDirectory(), + OpenLink(String), + Unignore(String), + Update(String), + ScaleUp, + ScaleDown, + SortCatalogColumn(element::wallet::operation::tx_list::ColumnKey), + Backup, + ToggleHideIgnoredAddons(bool), + CatalogQuery(String), + InstallScmQuery(String), + InstallScmUrl, + UpdateGrin, + AlternatingRowColorToggled(bool), + KeybindingsToggle(bool), + #[cfg(target_os = "windows")] + ToggleCloseToTray(bool), + #[cfg(target_os = "windows")] + ToggleAutoStart(bool), + #[cfg(target_os = "windows")] + ToggleStartClosedToTray(bool), + + /// Application shutdown + Exit, + ExitCancel, } pub struct ThemeState { - themes: Vec<(String, Theme)>, - current_theme_name: String, - // pick_list_state: pick_list::State, - // input_state: text_input::State, - input_url: String, + themes: Vec<(String, Theme)>, + current_theme_name: String, + // pick_list_state: pick_list::State, + // input_state: text_input::State, + input_url: String, } impl Default for ThemeState { - fn default() -> Self { - let themes = Theme::all(); - - ThemeState { - themes, - current_theme_name: "Dark".to_string(), - input_url: Default::default(), - } - } + fn default() -> Self { + let themes = Theme::all(); + + ThemeState { + themes, + current_theme_name: "Dark".to_string(), + input_url: Default::default(), + } + } } fn apply_config(grin_gui: &mut GrinGui, mut config: Config) { - // Set column widths from the config - /*match &config.column_config { - ColumnConfig::V1 { - local_version_width, - remote_version_width, - status_width, - } => { - grin_gui - .header_state - .columns - .get_mut(1) - .as_mut() - .unwrap() - .width = Length::Fixed(*local_version_width); - grin_gui - .header_state - .columns - .get_mut(2) - .as_mut() - .unwrap() - .width = Length::Fixed(*remote_version_width); - grin_gui - .header_state - .columns - .get_mut(3) - .as_mut() - .unwrap() - .width = Length::Fixed(*status_width); - } - ColumnConfig::V2 { columns } => { - grin_gui.header_state.columns.iter_mut().for_each(|a| { - if let Some((idx, column)) = columns - .iter() - .enumerate() - .filter_map(|(idx, column)| { - if column.key == a.key.as_string() { - Some((idx, column)) - } else { - None - } - }) - .next() - { - a.width = column.width.map_or(Length::Fill, Length::Fixed); - a.hidden = column.hidden; - a.order = idx; - } - }); - - grin_gui.column_settings.columns.iter_mut().for_each(|a| { - if let Some(idx) = columns - .iter() - .enumerate() - .filter_map(|(idx, column)| { - if column.key == a.key.as_string() { - Some(idx) - } else { - None - } - }) - .next() - { - a.order = idx; - } - }); - - // My Addons - grin_gui.header_state.columns.sort_by_key(|c| c.order); - grin_gui.column_settings.columns.sort_by_key(|c| c.order); - } - ColumnConfig::V3 { - my_addons_columns, - catalog_columns, - aura_columns, - } => { - grin_gui.header_state.columns.iter_mut().for_each(|a| { - if let Some((idx, column)) = my_addons_columns - .iter() - .enumerate() - .filter_map(|(idx, column)| { - if column.key == a.key.as_string() { - Some((idx, column)) - } else { - None - } - }) - .next() - { - // Always force "Title" column as Length::Fill - // - // Shouldn't be an issue here, as it was for catalog column fix - // below, but will cover things in case anyone accidently manually - // modifies their config and sets a fixed width on this column. - a.width = if a.key == ColumnKey::Title { - Length::Fill - } else { - column.width.map_or(Length::Fill, Length::Fixed) - }; - - a.hidden = column.hidden; - a.order = idx; - } - }); - - grin_gui.column_settings.columns.iter_mut().for_each(|a| { - if let Some(idx) = my_addons_columns - .iter() - .enumerate() - .filter_map(|(idx, column)| { - if column.key == a.key.as_string() { - Some(idx) - } else { - None - } - }) - .next() - { - a.order = idx; - } - }); - - grin_gui - .catalog_column_settings - .columns - .iter_mut() - .for_each(|a| { - if let Some(idx) = catalog_columns - .iter() - .enumerate() - .filter_map(|(idx, column)| { - if column.key == a.key.as_string() { - Some(idx) - } else { - None - } - }) - .next() - { - a.order = idx; - } - }); - - grin_gui.catalog_header_state.columns.iter_mut().for_each(|a| { - if let Some((idx, column)) = catalog_columns - .iter() - .enumerate() - .filter_map(|(idx, column)| { - if column.key == a.key.as_string() { - Some((idx, column)) - } else { - None - } - }) - .next() - { - // Always force "Title" column as Length::Fill - // - // An older version of ajour used a different column as the fill - // column and some users have migration issues when updating to - // a newer version, causing NO columns to be set as Fill and - // making resizing columns work incorrectly - a.width = if a.key == CatalogColumnKey::Title { - Length::Fill - } else { - column.width.map_or(Length::Fill, Length::Fixed) - }; - - a.hidden = column.hidden; - a.order = idx; - } - }); - - grin_gui.aura_header_state.columns.iter_mut().for_each(|a| { - if let Some((_idx, column)) = aura_columns - .iter() - .enumerate() - .filter_map(|(idx, column)| { - if column.key == a.key.as_string() { - Some((idx, column)) - } else { - None - } - }) - .next() - { - // Always force "Title" column as Length::Fill - // - // An older version of ajour used a different column as the fill - // column and some users have migration issues when updating to - // a newer version, causing NO columns to be set as Fill and - // making resizing columns work incorrectly - a.width = if a.key == AuraColumnKey::Title { - Length::Fill - } else { - column.width.map_or(Length::Fill, Length::Fixed) - }; - } - }); - - // My Addons - grin_gui.header_state.columns.sort_by_key(|c| c.order); - grin_gui.column_settings.columns.sort_by_key(|c| c.order); - - // Catalog - grin_gui.catalog_header_state.columns.sort_by_key(|c| c.order); - grin_gui - .catalog_column_settings - .columns - .sort_by_key(|c| c.order); - - // No sorting on Aura columns currently - } - }*/ - - // Use theme from config. Set to "Dark" if not defined. - grin_gui - .general_settings_state - .theme_state - .current_theme_name = config.theme.as_deref().unwrap_or("Dark").to_string(); - - // Use scale from config. Set to 1.0 if not defined. - grin_gui.general_settings_state.scale_state.scale = config.scale.unwrap_or(1.0); - - grin_gui.config = config; - - let _ = &grin_gui.config.save(); + // Set column widths from the config + /*match &config.column_config { + ColumnConfig::V1 { + local_version_width, + remote_version_width, + status_width, + } => { + grin_gui + .header_state + .columns + .get_mut(1) + .as_mut() + .unwrap() + .width = Length::Fixed(*local_version_width); + grin_gui + .header_state + .columns + .get_mut(2) + .as_mut() + .unwrap() + .width = Length::Fixed(*remote_version_width); + grin_gui + .header_state + .columns + .get_mut(3) + .as_mut() + .unwrap() + .width = Length::Fixed(*status_width); + } + ColumnConfig::V2 { columns } => { + grin_gui.header_state.columns.iter_mut().for_each(|a| { + if let Some((idx, column)) = columns + .iter() + .enumerate() + .filter_map(|(idx, column)| { + if column.key == a.key.as_string() { + Some((idx, column)) + } else { + None + } + }) + .next() + { + a.width = column.width.map_or(Length::Fill, Length::Fixed); + a.hidden = column.hidden; + a.order = idx; + } + }); + + grin_gui.column_settings.columns.iter_mut().for_each(|a| { + if let Some(idx) = columns + .iter() + .enumerate() + .filter_map(|(idx, column)| { + if column.key == a.key.as_string() { + Some(idx) + } else { + None + } + }) + .next() + { + a.order = idx; + } + }); + + // My Addons + grin_gui.header_state.columns.sort_by_key(|c| c.order); + grin_gui.column_settings.columns.sort_by_key(|c| c.order); + } + ColumnConfig::V3 { + my_addons_columns, + catalog_columns, + aura_columns, + } => { + grin_gui.header_state.columns.iter_mut().for_each(|a| { + if let Some((idx, column)) = my_addons_columns + .iter() + .enumerate() + .filter_map(|(idx, column)| { + if column.key == a.key.as_string() { + Some((idx, column)) + } else { + None + } + }) + .next() + { + // Always force "Title" column as Length::Fill + // + // Shouldn't be an issue here, as it was for catalog column fix + // below, but will cover things in case anyone accidently manually + // modifies their config and sets a fixed width on this column. + a.width = if a.key == ColumnKey::Title { + Length::Fill + } else { + column.width.map_or(Length::Fill, Length::Fixed) + }; + + a.hidden = column.hidden; + a.order = idx; + } + }); + + grin_gui.column_settings.columns.iter_mut().for_each(|a| { + if let Some(idx) = my_addons_columns + .iter() + .enumerate() + .filter_map(|(idx, column)| { + if column.key == a.key.as_string() { + Some(idx) + } else { + None + } + }) + .next() + { + a.order = idx; + } + }); + + grin_gui + .catalog_column_settings + .columns + .iter_mut() + .for_each(|a| { + if let Some(idx) = catalog_columns + .iter() + .enumerate() + .filter_map(|(idx, column)| { + if column.key == a.key.as_string() { + Some(idx) + } else { + None + } + }) + .next() + { + a.order = idx; + } + }); + + grin_gui.catalog_header_state.columns.iter_mut().for_each(|a| { + if let Some((idx, column)) = catalog_columns + .iter() + .enumerate() + .filter_map(|(idx, column)| { + if column.key == a.key.as_string() { + Some((idx, column)) + } else { + None + } + }) + .next() + { + // Always force "Title" column as Length::Fill + // + // An older version of ajour used a different column as the fill + // column and some users have migration issues when updating to + // a newer version, causing NO columns to be set as Fill and + // making resizing columns work incorrectly + a.width = if a.key == CatalogColumnKey::Title { + Length::Fill + } else { + column.width.map_or(Length::Fill, Length::Fixed) + }; + + a.hidden = column.hidden; + a.order = idx; + } + }); + + grin_gui.aura_header_state.columns.iter_mut().for_each(|a| { + if let Some((_idx, column)) = aura_columns + .iter() + .enumerate() + .filter_map(|(idx, column)| { + if column.key == a.key.as_string() { + Some((idx, column)) + } else { + None + } + }) + .next() + { + // Always force "Title" column as Length::Fill + // + // An older version of ajour used a different column as the fill + // column and some users have migration issues when updating to + // a newer version, causing NO columns to be set as Fill and + // making resizing columns work incorrectly + a.width = if a.key == AuraColumnKey::Title { + Length::Fill + } else { + column.width.map_or(Length::Fill, Length::Fixed) + }; + } + }); + + // My Addons + grin_gui.header_state.columns.sort_by_key(|c| c.order); + grin_gui.column_settings.columns.sort_by_key(|c| c.order); + + // Catalog + grin_gui.catalog_header_state.columns.sort_by_key(|c| c.order); + grin_gui + .catalog_column_settings + .columns + .sort_by_key(|c| c.order); + + // No sorting on Aura columns currently + } + }*/ + + // Use theme from config. Set to "Dark" if not defined. + grin_gui + .general_settings_state + .theme_state + .current_theme_name = config.theme.as_deref().unwrap_or("Dark").to_string(); + + // Use scale from config. Set to 1.0 if not defined. + grin_gui.general_settings_state.scale_state.scale = config.scale.unwrap_or(1.0); + + grin_gui.config = config; + + let _ = &grin_gui.config.save(); } diff --git a/src/gui/time.rs b/src/gui/time.rs index 59587fc..aafc7ff 100644 --- a/src/gui/time.rs +++ b/src/gui/time.rs @@ -1,36 +1,32 @@ -use iced_futures::{ - self, - subscription -}; +use iced_futures::{self, subscription}; use iced_core::Hasher; use std::hash::Hash; pub fn every(duration: std::time::Duration) -> iced::Subscription> { - iced::Subscription::from_recipe(Every(duration)) + iced::Subscription::from_recipe(Every(duration)) } struct Every(std::time::Duration); -impl iced_futures::subscription::Recipe for Every -{ - type Output = chrono::DateTime; +impl iced_futures::subscription::Recipe for Every { + type Output = chrono::DateTime; - fn hash(&self, state: &mut Hasher) { - use std::hash::Hash; + fn hash(&self, state: &mut Hasher) { + use std::hash::Hash; - std::any::TypeId::of::().hash(state); - self.0.hash(state); - } + std::any::TypeId::of::().hash(state); + self.0.hash(state); + } - fn stream( - self: Box, - _input: subscription::EventStream, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; + fn stream( + self: Box, + _input: subscription::EventStream, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; - async_std::stream::interval(self.0) - .map(|_| chrono::Local::now()) - .boxed() - } + async_std::stream::interval(self.0) + .map(|_| chrono::Local::now()) + .boxed() + } } diff --git a/src/gui/update.rs b/src/gui/update.rs index 21b6eee..949ca11 100644 --- a/src/gui/update.rs +++ b/src/gui/update.rs @@ -1,13 +1,13 @@ use { - super::{GrinGui, Interaction, Message, Mode}, - crate::{gui::element, log_error, Result}, - grin_gui_core::{ - fs::PersistentData, node::subscriber::UIMessage, node::ChainTypes::Mainnet, - node::ChainTypes::Testnet, - }, - iced::{clipboard, Command}, - //grin_gui_widgets::header::ResizeEvent, - std::path::PathBuf, + super::{GrinGui, Interaction, Message, Mode}, + crate::{gui::element, log_error, Result}, + grin_gui_core::{ + fs::PersistentData, node::subscriber::UIMessage, node::ChainTypes::Mainnet, + node::ChainTypes::Testnet, + }, + iced::{clipboard, Command}, + //grin_gui_widgets::header::ResizeEvent, + std::path::PathBuf, }; use iced::window; @@ -18,445 +18,445 @@ use crate::tray::{TrayMessage, SHOULD_EXIT, TRAY_SENDER}; use std::sync::atomic::Ordering; pub fn handle_message(grin_gui: &mut GrinGui, message: Message) -> Result> { - if let Some(index) = grin_gui.config.current_wallet_index { - // Take opportunity to check if we don't have a wallet config file for some reason - if !grin_gui.wallet_state.config_missing() { - match &grin_gui.config.wallets[index].tld { - Some(t) => { - let wallet_interface = grin_gui.wallet_interface.clone(); - let r = wallet_interface.read(); - if let Ok(w) = r { - if !w.config_exists(t.to_str().unwrap()) { - grin_gui.wallet_state.set_config_missing(); - } - } - } - None => { - grin_gui.wallet_state.set_config_missing(); - } - } - } - - // Check if password needs entering in wallet mode - if !grin_gui.wallet_state.config_missing() - && !grin_gui.wallet_state.operation_state.wallet_not_open() - { - let wallet_interface = grin_gui.wallet_interface.clone(); - let r = wallet_interface.read(); - if let Ok(w) = r { - if !w.wallet_is_open() { - grin_gui.wallet_state.operation_state.set_wallet_not_open() - } - } - } - // Check if embedded node needs starting - if grin_gui.config.wallets[index].use_embedded_node { - let (node_started, has_ui_sender) = { - let n = grin_gui.node_interface.read().unwrap(); - (n.node_started, n.ui_sender.is_some()) - }; - - if !node_started && has_ui_sender { - let mut node = grin_gui.node_interface.write().unwrap(); - //let is_testnet = grin_gui.config.wallets[index].is_testnet; - let wallet_chain_type = grin_gui.config.wallets[index].chain_type; - - if !node_started { - node.start_server(wallet_chain_type); - } else { - let running_chain_type = { - let node = grin_gui.node_interface.read().unwrap(); - node.chain_type - } - .unwrap(); - - if running_chain_type != wallet_chain_type { - node.restart_server(wallet_chain_type); - } - } - } - } - } else { - if !grin_gui.wallet_state.config_missing() { - grin_gui.wallet_state.set_config_missing(); - } - } - - // Clear errors when necessary - match message { - Message::Interaction(Interaction::OpenErrorModal) => {} - Message::Interaction(Interaction::CloseErrorModal) => {} - Message::Interaction(Interaction::WriteToClipboard(_)) => {} - Message::Interaction(Interaction::ReadSlatepackFromClipboard) => {} - Message::Interaction(Interaction::WalletOperationHomeViewInteraction(ref i)) => match i { - element::wallet::operation::home::LocalViewInteraction::WalletInfoUpdateSuccess( - _, - _, - ) => {} - _ => { - grin_gui.error.take(); - () - } - }, - Message::Interaction(_) => { - grin_gui.error.take(); - } - _ => {} - } - - match message { - Message::FontLoaded(f) => { - debug!("Font Loaded: {:?}", f) - } - // Ticks, for stuff that happens frequently, like checking wallet status - Message::Tick(time) => { - // Call all views 'registered' for ticks - return element::wallet::operation::home::handle_tick(grin_gui, time); - } - // Update from embedded node server - Message::SendNodeMessage((_id, msg, sender)) => match sender { - Some(sender) => { - let mut n = grin_gui.node_interface.write().unwrap(); - n.set_ui_sender(sender); - return Ok(Command::none()); - } - None => { - match msg { - UIMessage::None => {} - UIMessage::UpdateStatus(stats) => { - grin_gui - .wallet_state - .operation_state - .home_state - .update_node_status(&stats); - grin_gui.node_state.embedded_state.server_stats = Some(stats); - } - } - return Ok(Command::none()); - } - }, - // Error modal state - Message::Interaction(Interaction::OpenErrorModal) => grin_gui.show_modal = true, - Message::Interaction(Interaction::CloseErrorModal) => grin_gui.show_modal = false, - // Clipboard messages - Message::Interaction(Interaction::WriteToClipboard(contents)) => { - return Ok(clipboard::write::(contents)); - } - Message::Interaction(Interaction::ReadSlatepackFromClipboard) => { - return Ok(clipboard::read::(|value| { - match value { + if let Some(index) = grin_gui.config.current_wallet_index { + // Take opportunity to check if we don't have a wallet config file for some reason + if !grin_gui.wallet_state.config_missing() { + match &grin_gui.config.wallets[index].tld { + Some(t) => { + let wallet_interface = grin_gui.wallet_interface.clone(); + let r = wallet_interface.read(); + if let Ok(w) = r { + if !w.config_exists(t.to_str().unwrap()) { + grin_gui.wallet_state.set_config_missing(); + } + } + } + None => { + grin_gui.wallet_state.set_config_missing(); + } + } + } + + // Check if password needs entering in wallet mode + if !grin_gui.wallet_state.config_missing() + && !grin_gui.wallet_state.operation_state.wallet_not_open() + { + let wallet_interface = grin_gui.wallet_interface.clone(); + let r = wallet_interface.read(); + if let Ok(w) = r { + if !w.wallet_is_open() { + grin_gui.wallet_state.operation_state.set_wallet_not_open() + } + } + } + // Check if embedded node needs starting + if grin_gui.config.wallets[index].use_embedded_node { + let (node_started, has_ui_sender) = { + let n = grin_gui.node_interface.read().unwrap(); + (n.node_started, n.ui_sender.is_some()) + }; + + if !node_started && has_ui_sender { + let mut node = grin_gui.node_interface.write().unwrap(); + //let is_testnet = grin_gui.config.wallets[index].is_testnet; + let wallet_chain_type = grin_gui.config.wallets[index].chain_type; + + if !node_started { + node.start_server(wallet_chain_type); + } else { + let running_chain_type = { + let node = grin_gui.node_interface.read().unwrap(); + node.chain_type + } + .unwrap(); + + if running_chain_type != wallet_chain_type { + node.restart_server(wallet_chain_type); + } + } + } + } + } else { + if !grin_gui.wallet_state.config_missing() { + grin_gui.wallet_state.set_config_missing(); + } + } + + // Clear errors when necessary + match message { + Message::Interaction(Interaction::OpenErrorModal) => {} + Message::Interaction(Interaction::CloseErrorModal) => {} + Message::Interaction(Interaction::WriteToClipboard(_)) => {} + Message::Interaction(Interaction::ReadSlatepackFromClipboard) => {} + Message::Interaction(Interaction::WalletOperationHomeViewInteraction(ref i)) => match i { + element::wallet::operation::home::LocalViewInteraction::WalletInfoUpdateSuccess( + _, + _, + ) => {} + _ => { + grin_gui.error.take(); + () + } + }, + Message::Interaction(_) => { + grin_gui.error.take(); + } + _ => {} + } + + match message { + Message::FontLoaded(f) => { + debug!("Font Loaded: {:?}", f) + } + // Ticks, for stuff that happens frequently, like checking wallet status + Message::Tick(time) => { + // Call all views 'registered' for ticks + return element::wallet::operation::home::handle_tick(grin_gui, time); + } + // Update from embedded node server + Message::SendNodeMessage((_id, msg, sender)) => match sender { + Some(sender) => { + let mut n = grin_gui.node_interface.write().unwrap(); + n.set_ui_sender(sender); + return Ok(Command::none()); + } + None => { + match msg { + UIMessage::None => {} + UIMessage::UpdateStatus(stats) => { + grin_gui + .wallet_state + .operation_state + .home_state + .update_node_status(&stats); + grin_gui.node_state.embedded_state.server_stats = Some(stats); + } + } + return Ok(Command::none()); + } + }, + // Error modal state + Message::Interaction(Interaction::OpenErrorModal) => grin_gui.show_modal = true, + Message::Interaction(Interaction::CloseErrorModal) => grin_gui.show_modal = false, + // Clipboard messages + Message::Interaction(Interaction::WriteToClipboard(contents)) => { + return Ok(clipboard::write::(contents)); + } + Message::Interaction(Interaction::ReadSlatepackFromClipboard) => { + return Ok(clipboard::read::(|value| { + match value { Some(v) => return Message::Interaction(Interaction::WalletOperationApplyTxViewInteraction(element::wallet::operation::apply_tx::LocalViewInteraction::ReadFromClipboardSuccess(v))), None => return Message::Interaction(Interaction::WalletOperationApplyTxViewInteraction(element::wallet::operation::apply_tx::LocalViewInteraction::ReadFromClipboardFailure)) } - })) - } // Top level menu - Message::Interaction(Interaction::MenuViewInteraction(l)) => { - let _ = element::menu::handle_message(grin_gui, l); - } - // Top level settings view - Message::Interaction(Interaction::SettingsViewInteraction(l)) => { - element::settings::handle_message(grin_gui, l); - } - // Settings -> Wallet Settings - Message::Interaction(Interaction::WalletSettingsViewInteraction(l)) => { - element::settings::wallet::handle_message(grin_gui, l); - } - // Settings -> Node Settings - Message::Interaction(Interaction::NodeSettingsViewInteraction(l)) => { - element::settings::node::handle_message(grin_gui, l); - } - - // Settings -> General Settings - Message::Interaction(Interaction::GeneralSettingsViewInteraction(l)) => { - return element::settings::general::handle_message(grin_gui, l); - } - // Setup Top Level - Message::Interaction(Interaction::WalletSetupViewInteraction(l)) => { - return element::wallet::setup::handle_message(grin_gui, l); - } - // Setup -> Initial View (To appear when no wallet toml file is set) - Message::Interaction(Interaction::WalletSetupInitViewInteraction(l)) => { - return element::wallet::setup::init::handle_message(grin_gui, l); - } - // Setup -> Wallet Init Settings - Message::Interaction(Interaction::WalletSetupWalletViewInteraction(l)) => { - return element::wallet::setup::wallet_setup::handle_message(grin_gui, l); - } - // Setup -> Wallet List - Message::Interaction(Interaction::WalletListWalletViewInteraction(l)) => { - return element::wallet::setup::wallet_list::handle_message(grin_gui, l); - } - // Setup -> Wallet Success Settings - Message::Interaction(Interaction::WalletSetupWalletSuccessViewInteraction(l)) => { - return element::wallet::setup::wallet_success::handle_message(grin_gui, l); - } - // Wallet -> Operation -> Open Settings - Message::Interaction(Interaction::WalletOperationOpenViewInteraction(l)) => { - return element::wallet::operation::open::handle_message(grin_gui, l); - } - // Wallet -> Operation -> Home Settings - Message::Interaction(Interaction::WalletOperationHomeViewInteraction(l)) => { - return element::wallet::operation::home::handle_message(grin_gui, l); - } - // Wallet -> Operation -> Home -> TxListDisplay Settings - Message::Interaction(Interaction::WalletOperationHomeTxListDisplayInteraction(l)) => { - return element::wallet::operation::tx_list_display::handle_message(grin_gui, l); - } - // Wallet -> Operation -> TxList - Message::Interaction(Interaction::WalletOperationTxListInteraction(l)) => { - return element::wallet::operation::tx_list::handle_message(grin_gui, l); - } - // Wallet -> Operation -> CreateTx - Message::Interaction(Interaction::WalletOperationCreateTxViewInteraction(l)) => { - return element::wallet::operation::create_tx::handle_message(grin_gui, l); - } - // Wallet -> Operation -> CreateTxSuccess - Message::Interaction(Interaction::WalletOperationShowSlatepackViewInteraction(l)) => { - return element::wallet::operation::show_slatepack::handle_message(grin_gui, l); - } - // Wallet -> Operation -> Home -> Action - Message::Interaction(Interaction::WalletOperationApplyTxViewInteraction(l)) => { - return element::wallet::operation::apply_tx::handle_message(grin_gui, l); - } - // Wallet -> Operation -> Home -> Action - Message::Interaction(Interaction::WalletOperationApplyTxConfirmViewInteraction(l)) => { - return element::wallet::operation::apply_tx_confirm::handle_message(grin_gui, l); - } - // Wallet -> Operation -> Home -> Action - Message::Interaction(Interaction::WalletOperationTxDetailViewInteraction(l)) => { - return element::wallet::operation::tx_detail::handle_message(grin_gui, l); - } - // Wallet -> Operation -> Proof -> Action - Message::Interaction(Interaction::WalletOperationTxProofViewInteraction(l)) => { - return element::wallet::operation::tx_proof::handle_message(grin_gui, l); - } - // Wallet -> Operation -> Home -> Action - Message::Interaction(Interaction::WalletOperationHomeActionMenuViewInteraction(l)) => { - return element::wallet::operation::action_menu::handle_message(grin_gui, l); - } - // Wallet -> Operation -> Home -> Action - Message::Interaction(Interaction::WalletOperationTxDoneViewInteraction(l)) => { - return element::wallet::operation::tx_done::handle_message(grin_gui, l); - } - // Wallet -> Operation -> CreateTxContract - Message::Interaction(Interaction::WalletOperationCreateTxContractsViewInteraction(l)) => { - return element::wallet::operation::create_tx_contracts::handle_message(grin_gui, l); - } - Message::Interaction(Interaction::ModeSelected(mode)) => { - log::debug!("Interaction::ModeSelected({:?})", mode); - // Set Mode - grin_gui.mode = mode; - } - /*Message::MessageInteraction(m) => { - m.handle_message() - }*/ - Message::Interaction(Interaction::ModeSelectedSettings(mode)) => { - log::debug!("Interaction::ModeSelectedSettings({:?})", mode); - // Set Mode - //grin_gui.settings_state.mode = mode; - } - Message::Error(error) => { - let mut e = error.write().unwrap(); - let err = e.take(); - if let Some(ref e) = err { - log_error(e); - } - grin_gui.error = err; - } - Message::RuntimeEvent(iced_core::Event::Window(iced_core::window::Event::Resized { - width, - height, - })) => { - let width = (width as f64 * grin_gui.general_settings_state.scale_state.scale) as u32; - let height = (height as f64 * grin_gui.general_settings_state.scale_state.scale) as u32; - - // Minimizing Grin GUI on Windows will call this function with 0, 0. - // We don't want to save that in config, because then it will start with zero size. - if width > 0 && height > 0 { - grin_gui.config.window_size = Some((width, height)); - let _ = grin_gui.config.save(); - } - } - - #[cfg(target_os = "macos")] - // Application shutdown - Message::RuntimeEvent(iced_core::Event::Window( - iced_core::window::Event::CloseRequested, - )) => { - log::debug!("Message::RuntimeEvent(CloseRequested)"); - grin_gui.show_exit(true); - } - - #[cfg(target_os = "windows")] - Message::RuntimeEvent(iced_core::Event::Window( - iced_core::window::Event::CloseRequested, - )) => { - log::debug!("Message::RuntimeEvent(CloseRequested)"); - - if let Some(sender) = TRAY_SENDER.get() { - if grin_gui.config.close_to_tray { - let _ = sender.try_send(TrayMessage::CloseToTray); - } else { - return Ok(window::close()); - } - } - } - Message::RuntimeEvent(iced_core::Event::Keyboard( - iced_core::keyboard::Event::KeyReleased { - key_code, - modifiers, - }, - )) => { - // Bail out of keybindings if keybindings is diabled, or we are - // pressing any modifiers. - if !grin_gui.config.is_keybindings_enabled - || modifiers != iced::keyboard::Modifiers::default() - { - return Ok(Command::none()); - } - - match key_code { - iced::keyboard::KeyCode::A => {} - iced::keyboard::KeyCode::C => { - grin_gui.mode = Mode::Catalog; - } - iced::keyboard::KeyCode::R => {} - iced::keyboard::KeyCode::S => { - grin_gui.mode = Mode::Settings; - } - iced::keyboard::KeyCode::U => {} - iced::keyboard::KeyCode::W => {} - iced::keyboard::KeyCode::I => { - grin_gui.mode = Mode::Install; - } - iced::keyboard::KeyCode::Escape => match grin_gui.mode { - _ => (), - }, - _ => (), - } - } - #[cfg(target_os = "windows")] - Message::Interaction(Interaction::ToggleCloseToTray(enable)) => { - log::debug!("Interaction::ToggleCloseToTray({})", enable); - - grin_gui.config.close_to_tray = enable; - - // Remove start closed to tray if we are disabling - if !enable { - grin_gui.config.start_closed_to_tray = false; - } - - let _ = grin_gui.config.save(); - - if let Some(sender) = TRAY_SENDER.get() { - let msg = if enable { - TrayMessage::Enable - } else { - TrayMessage::Disable - }; - - let _ = sender.try_send(msg); - } - } - #[cfg(target_os = "windows")] - Message::Interaction(Interaction::ToggleAutoStart(enable)) => { - log::debug!("Interaction::ToggleAutoStart({})", enable); - - grin_gui.config.autostart = enable; - - let _ = grin_gui.config.save(); - - if let Some(sender) = TRAY_SENDER.get() { - let _ = sender.try_send(TrayMessage::ToggleAutoStart(enable)); - } - } - #[cfg(target_os = "windows")] - Message::Interaction(Interaction::ToggleStartClosedToTray(enable)) => { - log::debug!("Interaction::ToggleStartClosedToTray({})", enable); - - grin_gui.config.start_closed_to_tray = enable; - - // Enable tray if this feature is enabled - if enable && !grin_gui.config.close_to_tray { - grin_gui.config.close_to_tray = true; - - if let Some(sender) = TRAY_SENDER.get() { - let _ = sender.try_send(TrayMessage::Enable); - } - } - - let _ = grin_gui.config.save(); - } - Message::Interaction(Interaction::OpenLink(link)) => { - log::debug!("Interaction::OpenLink({})", &link); - - return Ok(Command::perform( - async { - let _ = opener::open(link); - }, - Message::None, - )); - } - // Application shutdown - Message::Interaction(Interaction::Exit) => { - grin_gui.safe_exit(); - return Ok(window::close()); - } - Message::Interaction(Interaction::ExitCancel) => { - grin_gui.show_exit(false); - } - Message::Interaction(_) => {} - Message::RuntimeEvent(_) => {} - Message::None(_) => {} - } - - #[cfg(target_os = "windows")] - if SHOULD_EXIT.load(Ordering::Relaxed) { - return Ok(window::close()); - } - - Ok(Command::none()) + })) + } // Top level menu + Message::Interaction(Interaction::MenuViewInteraction(l)) => { + let _ = element::menu::handle_message(grin_gui, l); + } + // Top level settings view + Message::Interaction(Interaction::SettingsViewInteraction(l)) => { + element::settings::handle_message(grin_gui, l); + } + // Settings -> Wallet Settings + Message::Interaction(Interaction::WalletSettingsViewInteraction(l)) => { + element::settings::wallet::handle_message(grin_gui, l); + } + // Settings -> Node Settings + Message::Interaction(Interaction::NodeSettingsViewInteraction(l)) => { + element::settings::node::handle_message(grin_gui, l); + } + + // Settings -> General Settings + Message::Interaction(Interaction::GeneralSettingsViewInteraction(l)) => { + return element::settings::general::handle_message(grin_gui, l); + } + // Setup Top Level + Message::Interaction(Interaction::WalletSetupViewInteraction(l)) => { + return element::wallet::setup::handle_message(grin_gui, l); + } + // Setup -> Initial View (To appear when no wallet toml file is set) + Message::Interaction(Interaction::WalletSetupInitViewInteraction(l)) => { + return element::wallet::setup::init::handle_message(grin_gui, l); + } + // Setup -> Wallet Init Settings + Message::Interaction(Interaction::WalletSetupWalletViewInteraction(l)) => { + return element::wallet::setup::wallet_setup::handle_message(grin_gui, l); + } + // Setup -> Wallet List + Message::Interaction(Interaction::WalletListWalletViewInteraction(l)) => { + return element::wallet::setup::wallet_list::handle_message(grin_gui, l); + } + // Setup -> Wallet Success Settings + Message::Interaction(Interaction::WalletSetupWalletSuccessViewInteraction(l)) => { + return element::wallet::setup::wallet_success::handle_message(grin_gui, l); + } + // Wallet -> Operation -> Open Settings + Message::Interaction(Interaction::WalletOperationOpenViewInteraction(l)) => { + return element::wallet::operation::open::handle_message(grin_gui, l); + } + // Wallet -> Operation -> Home Settings + Message::Interaction(Interaction::WalletOperationHomeViewInteraction(l)) => { + return element::wallet::operation::home::handle_message(grin_gui, l); + } + // Wallet -> Operation -> Home -> TxListDisplay Settings + Message::Interaction(Interaction::WalletOperationHomeTxListDisplayInteraction(l)) => { + return element::wallet::operation::tx_list_display::handle_message(grin_gui, l); + } + // Wallet -> Operation -> TxList + Message::Interaction(Interaction::WalletOperationTxListInteraction(l)) => { + return element::wallet::operation::tx_list::handle_message(grin_gui, l); + } + // Wallet -> Operation -> CreateTx + Message::Interaction(Interaction::WalletOperationCreateTxViewInteraction(l)) => { + return element::wallet::operation::create_tx::handle_message(grin_gui, l); + } + // Wallet -> Operation -> CreateTxSuccess + Message::Interaction(Interaction::WalletOperationShowSlatepackViewInteraction(l)) => { + return element::wallet::operation::show_slatepack::handle_message(grin_gui, l); + } + // Wallet -> Operation -> Home -> Action + Message::Interaction(Interaction::WalletOperationApplyTxViewInteraction(l)) => { + return element::wallet::operation::apply_tx::handle_message(grin_gui, l); + } + // Wallet -> Operation -> Home -> Action + Message::Interaction(Interaction::WalletOperationApplyTxConfirmViewInteraction(l)) => { + return element::wallet::operation::apply_tx_confirm::handle_message(grin_gui, l); + } + // Wallet -> Operation -> Home -> Action + Message::Interaction(Interaction::WalletOperationTxDetailViewInteraction(l)) => { + return element::wallet::operation::tx_detail::handle_message(grin_gui, l); + } + // Wallet -> Operation -> Proof -> Action + Message::Interaction(Interaction::WalletOperationTxProofViewInteraction(l)) => { + return element::wallet::operation::tx_proof::handle_message(grin_gui, l); + } + // Wallet -> Operation -> Home -> Action + Message::Interaction(Interaction::WalletOperationHomeActionMenuViewInteraction(l)) => { + return element::wallet::operation::action_menu::handle_message(grin_gui, l); + } + // Wallet -> Operation -> Home -> Action + Message::Interaction(Interaction::WalletOperationTxDoneViewInteraction(l)) => { + return element::wallet::operation::tx_done::handle_message(grin_gui, l); + } + // Wallet -> Operation -> CreateTxContract + Message::Interaction(Interaction::WalletOperationCreateTxContractsViewInteraction(l)) => { + return element::wallet::operation::create_tx_contracts::handle_message(grin_gui, l); + } + Message::Interaction(Interaction::ModeSelected(mode)) => { + log::debug!("Interaction::ModeSelected({:?})", mode); + // Set Mode + grin_gui.mode = mode; + } + /*Message::MessageInteraction(m) => { + m.handle_message() + }*/ + Message::Interaction(Interaction::ModeSelectedSettings(mode)) => { + log::debug!("Interaction::ModeSelectedSettings({:?})", mode); + // Set Mode + //grin_gui.settings_state.mode = mode; + } + Message::Error(error) => { + let mut e = error.write().unwrap(); + let err = e.take(); + if let Some(ref e) = err { + log_error(e); + } + grin_gui.error = err; + } + Message::RuntimeEvent(iced_core::Event::Window(iced_core::window::Event::Resized { + width, + height, + })) => { + let width = (width as f64 * grin_gui.general_settings_state.scale_state.scale) as u32; + let height = (height as f64 * grin_gui.general_settings_state.scale_state.scale) as u32; + + // Minimizing Grin GUI on Windows will call this function with 0, 0. + // We don't want to save that in config, because then it will start with zero size. + if width > 0 && height > 0 { + grin_gui.config.window_size = Some((width, height)); + let _ = grin_gui.config.save(); + } + } + + #[cfg(target_os = "macos")] + // Application shutdown + Message::RuntimeEvent(iced_core::Event::Window( + iced_core::window::Event::CloseRequested, + )) => { + log::debug!("Message::RuntimeEvent(CloseRequested)"); + grin_gui.show_exit(true); + } + + #[cfg(target_os = "windows")] + Message::RuntimeEvent(iced_core::Event::Window( + iced_core::window::Event::CloseRequested, + )) => { + log::debug!("Message::RuntimeEvent(CloseRequested)"); + + if let Some(sender) = TRAY_SENDER.get() { + if grin_gui.config.close_to_tray { + let _ = sender.try_send(TrayMessage::CloseToTray); + } else { + return Ok(window::close()); + } + } + } + Message::RuntimeEvent(iced_core::Event::Keyboard( + iced_core::keyboard::Event::KeyReleased { + key_code, + modifiers, + }, + )) => { + // Bail out of keybindings if keybindings is diabled, or we are + // pressing any modifiers. + if !grin_gui.config.is_keybindings_enabled + || modifiers != iced::keyboard::Modifiers::default() + { + return Ok(Command::none()); + } + + match key_code { + iced::keyboard::KeyCode::A => {} + iced::keyboard::KeyCode::C => { + grin_gui.mode = Mode::Catalog; + } + iced::keyboard::KeyCode::R => {} + iced::keyboard::KeyCode::S => { + grin_gui.mode = Mode::Settings; + } + iced::keyboard::KeyCode::U => {} + iced::keyboard::KeyCode::W => {} + iced::keyboard::KeyCode::I => { + grin_gui.mode = Mode::Install; + } + iced::keyboard::KeyCode::Escape => match grin_gui.mode { + _ => (), + }, + _ => (), + } + } + #[cfg(target_os = "windows")] + Message::Interaction(Interaction::ToggleCloseToTray(enable)) => { + log::debug!("Interaction::ToggleCloseToTray({})", enable); + + grin_gui.config.close_to_tray = enable; + + // Remove start closed to tray if we are disabling + if !enable { + grin_gui.config.start_closed_to_tray = false; + } + + let _ = grin_gui.config.save(); + + if let Some(sender) = TRAY_SENDER.get() { + let msg = if enable { + TrayMessage::Enable + } else { + TrayMessage::Disable + }; + + let _ = sender.try_send(msg); + } + } + #[cfg(target_os = "windows")] + Message::Interaction(Interaction::ToggleAutoStart(enable)) => { + log::debug!("Interaction::ToggleAutoStart({})", enable); + + grin_gui.config.autostart = enable; + + let _ = grin_gui.config.save(); + + if let Some(sender) = TRAY_SENDER.get() { + let _ = sender.try_send(TrayMessage::ToggleAutoStart(enable)); + } + } + #[cfg(target_os = "windows")] + Message::Interaction(Interaction::ToggleStartClosedToTray(enable)) => { + log::debug!("Interaction::ToggleStartClosedToTray({})", enable); + + grin_gui.config.start_closed_to_tray = enable; + + // Enable tray if this feature is enabled + if enable && !grin_gui.config.close_to_tray { + grin_gui.config.close_to_tray = true; + + if let Some(sender) = TRAY_SENDER.get() { + let _ = sender.try_send(TrayMessage::Enable); + } + } + + let _ = grin_gui.config.save(); + } + Message::Interaction(Interaction::OpenLink(link)) => { + log::debug!("Interaction::OpenLink({})", &link); + + return Ok(Command::perform( + async { + let _ = opener::open(link); + }, + Message::None, + )); + } + // Application shutdown + Message::Interaction(Interaction::Exit) => { + grin_gui.safe_exit(); + return Ok(window::close()); + } + Message::Interaction(Interaction::ExitCancel) => { + grin_gui.show_exit(false); + } + Message::Interaction(_) => {} + Message::RuntimeEvent(_) => {} + Message::None(_) => {} + } + + #[cfg(target_os = "windows")] + if SHOULD_EXIT.load(Ordering::Relaxed) { + return Ok(window::close()); + } + + Ok(Command::none()) } #[cfg(not(target_os = "linux"))] async fn select_directory() -> Option { - use rfd::AsyncFileDialog; + use rfd::AsyncFileDialog; - let dialog = AsyncFileDialog::new(); - if let Some(show) = dialog.pick_folder().await { - return Some(show.path().to_path_buf()); - } + let dialog = AsyncFileDialog::new(); + if let Some(show) = dialog.pick_folder().await { + return Some(show.path().to_path_buf()); + } - None + None } #[cfg(target_os = "linux")] async fn select_directory() -> Option { - use native_dialog::FileDialog; + use native_dialog::FileDialog; - let dialog = FileDialog::new(); - if let Ok(Some(show)) = dialog.show_open_single_dir() { - return Some(show); - } + let dialog = FileDialog::new(); + if let Ok(Some(show)) = dialog.show_open_single_dir() { + return Some(show); + } - None + None } /// Hardcoded binary names for each compilation target /// that gets published to the Github Release const fn bin_name() -> &'static str { - #[cfg(target_os = "windows")] - { - "grin-gui.exe" - } - - #[cfg(target_os = "macos")] - { - "grin-gui" - } - - #[cfg(target_os = "linux")] - { - "grin-gui.AppImage" - } + #[cfg(target_os = "windows")] + { + "grin-gui.exe" + } + + #[cfg(target_os = "macos")] + { + "grin-gui" + } + + #[cfg(target_os = "linux")] + { + "grin-gui.AppImage" + } } diff --git a/src/localization.rs b/src/localization.rs index 387e4b2..fa39289 100644 --- a/src/localization.rs +++ b/src/localization.rs @@ -4,44 +4,44 @@ use once_cell::sync::{Lazy, OnceCell}; use std::sync::RwLock; pub static LOCALIZATION_CTX: Lazy> = Lazy::new(|| { - static_json_gettext_build!( - "en_US", - "en_US", - "locale/en.json", - "de_DE", - "locale/de.json" - ) - .unwrap() + static_json_gettext_build!( + "en_US", + "en_US", + "locale/en.json", + "de_DE", + "locale/de.json" + ) + .unwrap() }); pub static LANG: OnceCell> = OnceCell::new(); pub fn localized_string(key: &str) -> String { - let lang = LANG.get().expect("LANG not set").read().unwrap(); + let lang = LANG.get().expect("LANG not set").read().unwrap(); - if let Some(text) = get_text!(LOCALIZATION_CTX, *lang, key) { - let text = text.to_string(); - if text.is_empty() { - key.to_owned() - } else { - text - } - } else { - key.to_owned() - } + if let Some(text) = get_text!(LOCALIZATION_CTX, *lang, key) { + let text = text.to_string(); + if text.is_empty() { + key.to_owned() + } else { + text + } + } else { + key.to_owned() + } } /// Returns a localized `timeago::Formatter`. /// If user has chosen a language whic his not supported by `timeago` we fallback to english. pub fn localized_timeago_formatter() -> timeago::Formatter> { - let lang = LANG.get().expect("LANG not set").read().unwrap(); - let isolang = isolang::Language::from_locale(&lang).unwrap(); + let lang = LANG.get().expect("LANG not set").read().unwrap(); + let isolang = isolang::Language::from_locale(&lang).unwrap(); - // this step might fail if timeago does not support the chosen language. - // In that case we fallback to `en_US`. - if let Some(timeago_lang) = timeago::from_isolang(isolang) { - timeago::Formatter::with_language(timeago_lang) - } else { - timeago::Formatter::with_language(Box::new(timeago::English)) - } + // this step might fail if timeago does not support the chosen language. + // In that case we fallback to `en_US`. + if let Some(timeago_lang) = timeago::from_isolang(isolang) { + timeago::Formatter::with_language(timeago_lang) + } else { + timeago::Formatter::with_language(Box::new(timeago::English)) + } } diff --git a/src/main.rs b/src/main.rs index 88e73ca..4974cc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ extern crate log; use grin_gui_core::config::Config; use grin_gui_core::fs::{PersistentData, CONFIG_DIR}; use grin_gui_core::utility::{remove_file, rename}; -use grin_gui_core::{LoggingConfig, logger}; +use grin_gui_core::{logger, LoggingConfig}; #[cfg(target_os = "linux")] use anyhow::Context; @@ -35,165 +35,168 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub type Result = std::result::Result; pub fn main() { - let opts_result = cli::get_opts(); - - #[cfg(debug_assertions)] - let is_debug = true; - #[cfg(not(debug_assertions))] - let is_debug = false; - - // If this is a clap error, we map to None since we are going to exit and display - // an error message anyway and this value won't matter. If it's not an error, - // the underlying `command` will drive this variable. If a `command` is passed - // on the command line, Grin GUI functions as a CLI instead of launching the GUI. - let is_cli = opts_result - .as_ref() - .map(|o| &o.command) - .unwrap_or(&None) - .is_some(); - - // This function validates whether or not we need to exit and print any message - // due to arguments passed on the command line. If not, it will return a - // parsed `Opts` struct. This also handles setting up our windows release build - // fix that allows us to print to the console when not using the GUI. - let opts = cli::validate_opts_or_exit(opts_result, is_cli, is_debug); - - let config_dir_local = { - let mut config_dir = CONFIG_DIR.lock().unwrap(); - if let Some(data_dir) = &opts.data_directory { - *config_dir = data_dir.clone(); - } - config_dir.clone() - }; - - // Set up logging config for grin-gui code itself - let mut gui_logging_config = LoggingConfig::default(); - gui_logging_config.tui_running = Some(false); - gui_logging_config.stdout_log_level = log::Level::Debug; - gui_logging_config.file_log_level = log::Level::Debug; - let mut gui_log_dir = config_dir_local.clone(); - gui_log_dir.push("grin-gui.log"); - gui_logging_config.log_file_path = gui_log_dir.into_os_string().into_string().unwrap(); - - logger::update_logging_config(logger::LogArea::Gui, gui_logging_config); - - // Called when we launch from the temp (new release) binary during the self update - // process. We will rename the temp file (running process) to the original binary - if let Some(cleanup_path) = &opts.self_update_temp { - if let Err(e) = handle_self_update_temp(cleanup_path) { - log_error(&e); - std::process::exit(1); - } - } - - log_panics::init(); - - log::info!("Grin GUI {} has started.", VERSION); - - // Ensures another instance of Grin GUI isn't already running. - #[cfg(target_os = "windows")] - process::avoid_multiple_instances(); - - /*match opts.command { - Some(command) => { - // Process the command and exit - if let Err(e) = match command { - cli::Command::Backup { - backup_folder, - destination, - flavors, - compression_format, - level, - } => command::backup( - backup_folder, - destination, - flavors, - compression_format, - level, - ), - cli::Command::Update => command::update_both(), - cli::Command::UpdateAddons => command::update_all_addons(), - cli::Command::UpdateAuras => command::update_all_auras(), - cli::Command::Install { url, flavor } => command::install_from_source(url, flavor), - cli::Command::PathAdd { path, flavor } => command::path_add(path, flavor), - } { - log_error(&e); - } - } - None => {*/ - let config: Config = Config::load_or_default().expect("loading config on application startup"); - - #[cfg(target_os = "windows")] - tray::spawn_sys_tray(config.close_to_tray, config.start_closed_to_tray); - - // Start the GUI - gui::run(opts, config); - /* - }*/ + let opts_result = cli::get_opts(); + + #[cfg(debug_assertions)] + let is_debug = true; + #[cfg(not(debug_assertions))] + let is_debug = false; + + // If this is a clap error, we map to None since we are going to exit and display + // an error message anyway and this value won't matter. If it's not an error, + // the underlying `command` will drive this variable. If a `command` is passed + // on the command line, Grin GUI functions as a CLI instead of launching the GUI. + /*let is_cli = opts_result + .as_ref() + .map(|o| &o.command) + .unwrap_or(&None) + .is_some();*/ + + // disable command line for now + let is_cli = false; + + // This function validates whether or not we need to exit and print any message + // due to arguments passed on the command line. If not, it will return a + // parsed `Opts` struct. This also handles setting up our windows release build + // fix that allows us to print to the console when not using the GUI. + let opts = cli::validate_opts_or_exit(opts_result, is_cli, is_debug); + + let config_dir_local = { + let mut config_dir = CONFIG_DIR.lock().unwrap(); + if let Some(data_dir) = &opts.data_directory { + *config_dir = data_dir.clone(); + } + config_dir.clone() + }; + + // Set up logging config for grin-gui code itself + let mut gui_logging_config = LoggingConfig::default(); + gui_logging_config.tui_running = Some(false); + gui_logging_config.stdout_log_level = log::Level::Debug; + gui_logging_config.file_log_level = log::Level::Debug; + let mut gui_log_dir = config_dir_local.clone(); + gui_log_dir.push("grin-gui.log"); + gui_logging_config.log_file_path = gui_log_dir.into_os_string().into_string().unwrap(); + + logger::update_logging_config(logger::LogArea::Gui, gui_logging_config); + + // Called when we launch from the temp (new release) binary during the self update + // process. We will rename the temp file (running process) to the original binary + if let Some(cleanup_path) = &opts.self_update_temp { + if let Err(e) = handle_self_update_temp(cleanup_path) { + log_error(&e); + std::process::exit(1); + } + } + + log_panics::init(); + + log::info!("Grin GUI {} has started.", VERSION); + + // Ensures another instance of Grin GUI isn't already running. + #[cfg(target_os = "windows")] + process::avoid_multiple_instances(); + + /*match opts.command { + Some(command) => { + // Process the command and exit + if let Err(e) = match command { + cli::Command::Backup { + backup_folder, + destination, + flavors, + compression_format, + level, + } => command::backup( + backup_folder, + destination, + flavors, + compression_format, + level, + ), + cli::Command::Update => command::update_both(), + cli::Command::UpdateAddons => command::update_all_addons(), + cli::Command::UpdateAuras => command::update_all_auras(), + cli::Command::Install { url, flavor } => command::install_from_source(url, flavor), + cli::Command::PathAdd { path, flavor } => command::path_add(path, flavor), + } { + log_error(&e); + } + } + None => {*/ + let config: Config = Config::load_or_default().expect("loading config on application startup"); + + #[cfg(target_os = "windows")] + tray::spawn_sys_tray(config.close_to_tray, config.start_closed_to_tray); + + // Start the GUI + gui::run(opts, config); + /* + }*/ } /// Log any errors pub fn log_error(error: &anyhow::Error) { - log::error!("{}", error); + log::error!("{}", error); - let mut causes = error.chain(); - // Remove first entry since it's same as top level error - causes.next(); + let mut causes = error.chain(); + // Remove first entry since it's same as top level error + causes.next(); - for cause in causes { - log::error!("caused by: {}", cause); - } + for cause in causes { + log::error!("caused by: {}", cause); + } } pub fn error_cause_string(error: &anyhow::Error) -> String { - let mut ret_val = String::new(); - let mut causes = error.chain(); - - // Remove first entry since it's same as top level error - let top_level_cause = causes.next(); - if let Some(t) = top_level_cause { - ret_val.push_str(&format!("{}\n\n", t)); - } - - for cause in causes { - ret_val.push_str(&format!("{}\n\n", cause)); - } - ret_val + let mut ret_val = String::new(); + let mut causes = error.chain(); + + // Remove first entry since it's same as top level error + let top_level_cause = causes.next(); + if let Some(t) = top_level_cause { + ret_val.push_str(&format!("{}\n\n", t)); + } + + for cause in causes { + ret_val.push_str(&format!("{}\n\n", cause)); + } + ret_val } fn handle_self_update_temp(cleanup_path: &Path) -> Result<()> { - #[cfg(not(target_os = "linux"))] - let current_bin = env::current_exe()?; - - #[cfg(target_os = "linux")] - let current_bin = - PathBuf::from(env::var("APPIMAGE").context("error getting APPIMAGE env variable")?); - - // Fix for self updating pre 0.5.4 to >= 0.5.4 - // - // Pre 0.5.4, `cleanup_path` is actually the file name of the main bin name that - // got passed via the CLI in the self update process. We want to rename the - // current bin to that bin name. This was passed as a string of just the file - // name, so we want to make an actual full path out of it first. - if current_bin - .file_name() - .unwrap_or_default() - .to_str() - .unwrap_or_default() - .starts_with("tmp_") - { - let main_bin_name = cleanup_path; - - let parent_dir = current_bin.parent().unwrap(); - - let main_bin = parent_dir.join(&main_bin_name); - - rename(¤t_bin, &main_bin)?; - } else { - remove_file(cleanup_path)?; - } - - log::debug!("Grin GUI updated successfully"); - - Ok(()) + #[cfg(not(target_os = "linux"))] + let current_bin = env::current_exe()?; + + #[cfg(target_os = "linux")] + let current_bin = + PathBuf::from(env::var("APPIMAGE").context("error getting APPIMAGE env variable")?); + + // Fix for self updating pre 0.5.4 to >= 0.5.4 + // + // Pre 0.5.4, `cleanup_path` is actually the file name of the main bin name that + // got passed via the CLI in the self update process. We want to rename the + // current bin to that bin name. This was passed as a string of just the file + // name, so we want to make an actual full path out of it first. + if current_bin + .file_name() + .unwrap_or_default() + .to_str() + .unwrap_or_default() + .starts_with("tmp_") + { + let main_bin_name = cleanup_path; + + let parent_dir = current_bin.parent().unwrap(); + + let main_bin = parent_dir.join(&main_bin_name); + + rename(¤t_bin, &main_bin)?; + } else { + remove_file(cleanup_path)?; + } + + log::debug!("Grin GUI updated successfully"); + + Ok(()) } diff --git a/src/process.rs b/src/process.rs index c77b5b1..185558e 100644 --- a/src/process.rs +++ b/src/process.rs @@ -4,93 +4,93 @@ use std::path::PathBuf; use grin_gui_core::fs::PersistentData; use serde::{Deserialize, Serialize}; use winapi::{ - shared::winerror::WAIT_TIMEOUT, - um::{ - processthreadsapi::{GetCurrentProcess, GetCurrentProcessId, OpenProcess}, - synchapi::WaitForSingleObject, - winbase::QueryFullProcessImageNameW, - winnt::{PROCESS_QUERY_LIMITED_INFORMATION, SYNCHRONIZE}, - }, + shared::winerror::WAIT_TIMEOUT, + um::{ + processthreadsapi::{GetCurrentProcess, GetCurrentProcessId, OpenProcess}, + synchapi::WaitForSingleObject, + winbase::QueryFullProcessImageNameW, + winnt::{PROCESS_QUERY_LIMITED_INFORMATION, SYNCHRONIZE}, + }, }; #[derive(Debug, Serialize, Deserialize)] struct Process { - pid: u32, - name: String, + pid: u32, + name: String, } impl PersistentData for Process { - fn relative_path() -> PathBuf { - PathBuf::from("pid") - } + fn relative_path() -> PathBuf { + PathBuf::from("pid") + } } pub fn avoid_multiple_instances() { - if process_already_running() { - log::info!("Another instance of Grin GUI is already running. Exiting..."); - std::process::exit(0); - } else { - // Otherwise this is the only instance. Save info about this process to the - // pid file so future launches of Grin GUI can detect this running process. - save_current_process_file(); - } + if process_already_running() { + log::info!("Another instance of Grin GUI is already running. Exiting..."); + std::process::exit(0); + } else { + // Otherwise this is the only instance. Save info about this process to the + // pid file so future launches of Grin GUI can detect this running process. + save_current_process_file(); + } } fn process_already_running() -> bool { - let old_process = if let Ok(process) = Process::load() { - process - } else { - return false; - }; - - unsafe { - let current_pid = GetCurrentProcessId(); - - // In case new process somehow got recycled PID of old process - if current_pid == old_process.pid { - return false; - } - - let handle = OpenProcess( - SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, - 0, - old_process.pid, - ); - - if let Some(name) = get_process_name(handle) { - if name == old_process.name { - let status = WaitForSingleObject(handle, 0); - - return status == WAIT_TIMEOUT; - } - } - } - - false + let old_process = if let Ok(process) = Process::load() { + process + } else { + return false; + }; + + unsafe { + let current_pid = GetCurrentProcessId(); + + // In case new process somehow got recycled PID of old process + if current_pid == old_process.pid { + return false; + } + + let handle = OpenProcess( + SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, + 0, + old_process.pid, + ); + + if let Some(name) = get_process_name(handle) { + if name == old_process.name { + let status = WaitForSingleObject(handle, 0); + + return status == WAIT_TIMEOUT; + } + } + } + + false } fn save_current_process_file() { - unsafe { - let handle = GetCurrentProcess(); - let pid = GetCurrentProcessId(); + unsafe { + let handle = GetCurrentProcess(); + let pid = GetCurrentProcessId(); - if let Some(name) = get_process_name(handle) { - let process = Process { pid, name }; + if let Some(name) = get_process_name(handle) { + let process = Process { pid, name }; - let _ = process.save(); - } - } + let _ = process.save(); + } + } } unsafe fn get_process_name(handle: *mut c_void) -> Option { - let mut size = 256; - let mut buffer = [0u16; 256]; + let mut size = 256; + let mut buffer = [0u16; 256]; - let status = QueryFullProcessImageNameW(handle, 0, buffer.as_mut_ptr(), &mut size); + let status = QueryFullProcessImageNameW(handle, 0, buffer.as_mut_ptr(), &mut size); - if status != 0 { - String::from_utf16(&buffer[..(size as usize).min(buffer.len())]).ok() - } else { - None - } + if status != 0 { + String::from_utf16(&buffer[..(size as usize).min(buffer.len())]).ok() + } else { + None + } } diff --git a/src/tray/autostart.rs b/src/tray/autostart.rs index 3d584b0..08f4415 100644 --- a/src/tray/autostart.rs +++ b/src/tray/autostart.rs @@ -3,8 +3,8 @@ use std::fs; use std::mem; use std::ptr; -use grin_gui_core::fs::CONFIG_DIR; use anyhow::Error; +use grin_gui_core::fs::CONFIG_DIR; use winapi::shared::minwindef::HKEY; use winapi::um::winnt::{KEY_SET_VALUE, REG_OPTION_NON_VOLATILE, REG_SZ}; use winapi::um::winreg::{RegCreateKeyExW, RegDeleteKeyValueW, RegSetValueExW, HKEY_CURRENT_USER}; @@ -12,50 +12,50 @@ use winapi::um::winreg::{RegCreateKeyExW, RegDeleteKeyValueW, RegSetValueExW, HK use crate::str_to_wide; pub unsafe fn toggle_autostart(enabled: bool) -> Result<(), Error> { - let mut app_path = CONFIG_DIR.lock().unwrap().to_owned(); - app_path.push("grin-gui.exe"); - - // Copy Grin GUI to config directory so we can autostart it from there - let current_path = env::current_exe()?; - if current_path != app_path && enabled { - fs::copy(current_path, &app_path)?; - } - - let app_path = str_to_wide!(app_path.to_str().unwrap_or_default()); - let mut key_name = str_to_wide!("Software\\Microsoft\\Windows\\CurrentVersion\\Run"); - let mut value_name = str_to_wide!("grin-gui"); - - let mut key: HKEY = mem::zeroed(); - - if enabled { - if RegCreateKeyExW( - HKEY_CURRENT_USER, - key_name.as_mut_ptr(), - 0, - ptr::null_mut(), - REG_OPTION_NON_VOLATILE, - KEY_SET_VALUE, - ptr::null_mut(), - &mut key, - ptr::null_mut(), - ) == 0 - { - RegSetValueExW( - key, - value_name.as_mut_ptr(), - 0, - REG_SZ, - app_path.as_ptr() as _, - app_path.len() as u32 * 2, - ); - } - } else { - RegDeleteKeyValueW( - HKEY_CURRENT_USER, - key_name.as_mut_ptr(), - value_name.as_mut_ptr(), - ); - } - - Ok(()) + let mut app_path = CONFIG_DIR.lock().unwrap().to_owned(); + app_path.push("grin-gui.exe"); + + // Copy Grin GUI to config directory so we can autostart it from there + let current_path = env::current_exe()?; + if current_path != app_path && enabled { + fs::copy(current_path, &app_path)?; + } + + let app_path = str_to_wide!(app_path.to_str().unwrap_or_default()); + let mut key_name = str_to_wide!("Software\\Microsoft\\Windows\\CurrentVersion\\Run"); + let mut value_name = str_to_wide!("grin-gui"); + + let mut key: HKEY = mem::zeroed(); + + if enabled { + if RegCreateKeyExW( + HKEY_CURRENT_USER, + key_name.as_mut_ptr(), + 0, + ptr::null_mut(), + REG_OPTION_NON_VOLATILE, + KEY_SET_VALUE, + ptr::null_mut(), + &mut key, + ptr::null_mut(), + ) == 0 + { + RegSetValueExW( + key, + value_name.as_mut_ptr(), + 0, + REG_SZ, + app_path.as_ptr() as _, + app_path.len() as u32 * 2, + ); + } + } else { + RegDeleteKeyValueW( + HKEY_CURRENT_USER, + key_name.as_mut_ptr(), + value_name.as_mut_ptr(), + ); + } + + Ok(()) } diff --git a/src/tray/mod.rs b/src/tray/mod.rs index b419402..e9cfa07 100644 --- a/src/tray/mod.rs +++ b/src/tray/mod.rs @@ -8,24 +8,24 @@ use once_cell::sync::OnceCell; use winapi::shared::windef::{HWND, POINT}; use winapi::um::libloaderapi::GetModuleHandleW; use winapi::um::shellapi::{ - Shell_NotifyIconW, NIF_ICON, NIF_INFO, NIF_MESSAGE, NIF_TIP, NIIF_NONE, NIIF_NOSOUND, NIM_ADD, - NIM_DELETE, NIM_MODIFY, NOTIFYICONDATAW, + Shell_NotifyIconW, NIF_ICON, NIF_INFO, NIF_MESSAGE, NIF_TIP, NIIF_NONE, NIIF_NOSOUND, NIM_ADD, + NIM_DELETE, NIM_MODIFY, NOTIFYICONDATAW, }; use winapi::um::wingdi::{CreateSolidBrush, RGB}; use winapi::um::winuser::{ - CreatePopupMenu, CreateWindowExW, DefWindowProcW, DestroyMenu, DestroyWindow, DispatchMessageW, - EnumWindows, GetCursorPos, GetMessageW, GetWindowLongPtrW, GetWindowTextW, - GetWindowThreadProcessId, InsertMenuW, LoadIconW, MessageBoxW, PostMessageW, PostQuitMessage, - RegisterClassExW, SendMessageW, SetFocus, SetForegroundWindow, SetMenuDefaultItem, - SetWindowLongPtrW, ShowWindow, TrackPopupMenu, TranslateMessage, CREATESTRUCTW, GWLP_USERDATA, - MAKEINTRESOURCEW, MB_ICONINFORMATION, MB_OK, MF_BYPOSITION, MF_GRAYED, MF_SEPARATOR, MF_STRING, - SW_HIDE, TPM_LEFTALIGN, TPM_NONOTIFY, TPM_RETURNCMD, TPM_RIGHTBUTTON, WM_APP, WM_CLOSE, - WM_COMMAND, WM_CREATE, WM_DESTROY, WM_INITMENUPOPUP, WM_LBUTTONDBLCLK, WM_RBUTTONUP, - WNDCLASSEXW, WS_EX_NOACTIVATE, + CreatePopupMenu, CreateWindowExW, DefWindowProcW, DestroyMenu, DestroyWindow, DispatchMessageW, + EnumWindows, GetCursorPos, GetMessageW, GetWindowLongPtrW, GetWindowTextW, + GetWindowThreadProcessId, InsertMenuW, LoadIconW, MessageBoxW, PostMessageW, PostQuitMessage, + RegisterClassExW, SendMessageW, SetFocus, SetForegroundWindow, SetMenuDefaultItem, + SetWindowLongPtrW, ShowWindow, TrackPopupMenu, TranslateMessage, CREATESTRUCTW, GWLP_USERDATA, + MAKEINTRESOURCEW, MB_ICONINFORMATION, MB_OK, MF_BYPOSITION, MF_GRAYED, MF_SEPARATOR, MF_STRING, + SW_HIDE, TPM_LEFTALIGN, TPM_NONOTIFY, TPM_RETURNCMD, TPM_RIGHTBUTTON, WM_APP, WM_CLOSE, + WM_COMMAND, WM_CREATE, WM_DESTROY, WM_INITMENUPOPUP, WM_LBUTTONDBLCLK, WM_RBUTTONUP, + WNDCLASSEXW, WS_EX_NOACTIVATE, }; use winapi::{ - shared::minwindef::{BOOL, LOWORD, LPARAM, LRESULT, UINT, WPARAM}, - um::winuser::SW_SHOW, + shared::minwindef::{BOOL, LOWORD, LPARAM, LRESULT, UINT, WPARAM}, + um::winuser::SW_SHOW, }; use crate::localization::localized_string; @@ -43,20 +43,20 @@ const ID_EXIT: u16 = 2002; const WM_HIDE_GUI: u32 = WM_APP + 1; pub enum TrayMessage { - Enable, - Disable, - CloseToTray, - TrayCreated(WindowHandle), - ToggleAutoStart(bool), + Enable, + Disable, + CloseToTray, + TrayCreated(WindowHandle), + ToggleAutoStart(bool), } #[derive(Debug)] struct TrayState { - gui_handle: Option, - gui_hidden: bool, - about_shown: bool, - close_gui: bool, - show_balloon: bool, + gui_handle: Option, + gui_hidden: bool, + about_shown: bool, + close_gui: bool, + show_balloon: bool, } unsafe impl Send for TrayState {} @@ -69,377 +69,377 @@ unsafe impl Sync for WindowHandle {} #[macro_export] macro_rules! str_to_wide { - ($str:expr) => {{ - $str.encode_utf16() - .chain(std::iter::once(0)) - .collect::>() - }}; + ($str:expr) => {{ + $str.encode_utf16() + .chain(std::iter::once(0)) + .collect::>() + }}; } pub fn spawn_sys_tray(enabled: bool, start_closed_to_tray: bool) { - thread::spawn(move || { - let (sender, receiver) = sync_channel(1); - let _ = TRAY_SENDER.set(sender); - - // Stores the window handle so we can post messages to its queue - let mut window: Option = None; - - // Spawn tray initially if enabled - if enabled { - unsafe { create_window(false, start_closed_to_tray) }; - } - - // Make GUI visible if we don't start to tray - if !start_closed_to_tray { - GUI_VISIBLE.store(true, Ordering::Relaxed); - } - - while let Ok(msg) = receiver.recv() { - match msg { - TrayMessage::Enable => unsafe { - if window.is_none() { - create_window(true, false); - } - }, - TrayMessage::Disable => unsafe { - if let Some(window) = window.take() { - PostMessageW(window.0, WM_CLOSE, 1, 0); - } - }, - TrayMessage::CloseToTray => unsafe { - if let Some(window) = window.as_ref() { - PostMessageW(window.0, WM_HIDE_GUI, 0, 0); - } - }, - TrayMessage::TrayCreated(win) => { - window = Some(win); - } - TrayMessage::ToggleAutoStart(enabled) => unsafe { - if let Err(e) = autostart::toggle_autostart(enabled) { - log_error(&e); - } - }, - } - } - }); + thread::spawn(move || { + let (sender, receiver) = sync_channel(1); + let _ = TRAY_SENDER.set(sender); + + // Stores the window handle so we can post messages to its queue + let mut window: Option = None; + + // Spawn tray initially if enabled + if enabled { + unsafe { create_window(false, start_closed_to_tray) }; + } + + // Make GUI visible if we don't start to tray + if !start_closed_to_tray { + GUI_VISIBLE.store(true, Ordering::Relaxed); + } + + while let Ok(msg) = receiver.recv() { + match msg { + TrayMessage::Enable => unsafe { + if window.is_none() { + create_window(true, false); + } + }, + TrayMessage::Disable => unsafe { + if let Some(window) = window.take() { + PostMessageW(window.0, WM_CLOSE, 1, 0); + } + }, + TrayMessage::CloseToTray => unsafe { + if let Some(window) = window.as_ref() { + PostMessageW(window.0, WM_HIDE_GUI, 0, 0); + } + }, + TrayMessage::TrayCreated(win) => { + window = Some(win); + } + TrayMessage::ToggleAutoStart(enabled) => unsafe { + if let Err(e) = autostart::toggle_autostart(enabled) { + log_error(&e); + } + }, + } + } + }); } unsafe fn create_window(show_balloon: bool, gui_hidden: bool) { - thread::spawn(move || { - let mut tray_state = TrayState { - gui_handle: None, - gui_hidden, - about_shown: false, - close_gui: false, - show_balloon, - }; - - // Keep searching for window handle until its found - while tray_state.gui_handle.is_none() { - EnumWindows(Some(enum_proc), &mut tray_state as *mut _ as LPARAM); - } - - let h_instance = GetModuleHandleW(ptr::null()); - - let class_name = str_to_wide!("Grin GUI Tray"); - - let mut class = mem::zeroed::(); - class.cbSize = mem::size_of::() as u32; - class.lpfnWndProc = Some(window_proc); - class.hInstance = h_instance; - class.lpszClassName = class_name.as_ptr(); - class.hbrBackground = CreateSolidBrush(RGB(0, 77, 128)); - - RegisterClassExW(&class); - - let hwnd = CreateWindowExW( - WS_EX_NOACTIVATE, - class_name.as_ptr(), - ptr::null(), - 0, - 0, - 0, - 0, - 0, - ptr::null_mut(), - ptr::null_mut(), - h_instance, - &mut tray_state as *mut _ as *mut std::ffi::c_void, - ); - - // Send window handle back to main loop so we can post messages to it - let _ = TRAY_SENDER - .get() - .unwrap() - .try_send(TrayMessage::TrayCreated(WindowHandle(hwnd))); - - let mut msg = mem::zeroed(); - while GetMessageW(&mut msg, ptr::null_mut(), 0, 0) != 0 { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - - if tray_state.close_gui { - // WM_QUIT was sent, which was triggered by the Exit button. Close entire program. - SHOULD_EXIT.store(true, Ordering::Relaxed); - - // Activate window to force event loop to run / see that it should now close - SetForegroundWindow(tray_state.gui_handle.unwrap()); - } - }); + thread::spawn(move || { + let mut tray_state = TrayState { + gui_handle: None, + gui_hidden, + about_shown: false, + close_gui: false, + show_balloon, + }; + + // Keep searching for window handle until its found + while tray_state.gui_handle.is_none() { + EnumWindows(Some(enum_proc), &mut tray_state as *mut _ as LPARAM); + } + + let h_instance = GetModuleHandleW(ptr::null()); + + let class_name = str_to_wide!("Grin GUI Tray"); + + let mut class = mem::zeroed::(); + class.cbSize = mem::size_of::() as u32; + class.lpfnWndProc = Some(window_proc); + class.hInstance = h_instance; + class.lpszClassName = class_name.as_ptr(); + class.hbrBackground = CreateSolidBrush(RGB(0, 77, 128)); + + RegisterClassExW(&class); + + let hwnd = CreateWindowExW( + WS_EX_NOACTIVATE, + class_name.as_ptr(), + ptr::null(), + 0, + 0, + 0, + 0, + 0, + ptr::null_mut(), + ptr::null_mut(), + h_instance, + &mut tray_state as *mut _ as *mut std::ffi::c_void, + ); + + // Send window handle back to main loop so we can post messages to it + let _ = TRAY_SENDER + .get() + .unwrap() + .try_send(TrayMessage::TrayCreated(WindowHandle(hwnd))); + + let mut msg = mem::zeroed(); + while GetMessageW(&mut msg, ptr::null_mut(), 0, 0) != 0 { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + if tray_state.close_gui { + // WM_QUIT was sent, which was triggered by the Exit button. Close entire program. + SHOULD_EXIT.store(true, Ordering::Relaxed); + + // Activate window to force event loop to run / see that it should now close + SetForegroundWindow(tray_state.gui_handle.unwrap()); + } + }); } unsafe fn add_icon(hwnd: HWND) { - let h_instance = GetModuleHandleW(ptr::null()); - - let icon_handle = LoadIconW(h_instance, MAKEINTRESOURCEW(0x101)); - - let mut tooltip_array = [0u16; 128]; - let tooltip = "Grin GUI"; - let mut tooltip = tooltip.encode_utf16().collect::>(); - tooltip.extend(vec![0; 128 - tooltip.len()]); - tooltip_array.swap_with_slice(&mut tooltip[..]); - - let mut icon_data: NOTIFYICONDATAW = mem::zeroed(); - icon_data.cbSize = mem::size_of::() as u32; - icon_data.hWnd = hwnd; - icon_data.uID = 1; - icon_data.uCallbackMessage = WM_APP; - icon_data.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; - icon_data.hIcon = icon_handle; - icon_data.szTip = tooltip_array; - - Shell_NotifyIconW(NIM_ADD, &mut icon_data); + let h_instance = GetModuleHandleW(ptr::null()); + + let icon_handle = LoadIconW(h_instance, MAKEINTRESOURCEW(0x101)); + + let mut tooltip_array = [0u16; 128]; + let tooltip = "Grin GUI"; + let mut tooltip = tooltip.encode_utf16().collect::>(); + tooltip.extend(vec![0; 128 - tooltip.len()]); + tooltip_array.swap_with_slice(&mut tooltip[..]); + + let mut icon_data: NOTIFYICONDATAW = mem::zeroed(); + icon_data.cbSize = mem::size_of::() as u32; + icon_data.hWnd = hwnd; + icon_data.uID = 1; + icon_data.uCallbackMessage = WM_APP; + icon_data.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + icon_data.hIcon = icon_handle; + icon_data.szTip = tooltip_array; + + Shell_NotifyIconW(NIM_ADD, &mut icon_data); } unsafe fn display_balloon_message(hwnd: HWND, message: &str) { - let mut info = [0u16; 256]; - let message = str_to_wide!(message); - - for (idx, b) in message[0..message.len().min(255)].iter().enumerate() { - info[idx] = *b; - } - - let mut icon_data: NOTIFYICONDATAW = mem::zeroed(); - icon_data.cbSize = mem::size_of::() as u32; - icon_data.hWnd = hwnd; - icon_data.uID = 1; - icon_data.uFlags = NIF_INFO; - icon_data.szInfo = info; - icon_data.dwInfoFlags = NIIF_NONE | NIIF_NOSOUND; - - Shell_NotifyIconW(NIM_MODIFY, &mut icon_data); + let mut info = [0u16; 256]; + let message = str_to_wide!(message); + + for (idx, b) in message[0..message.len().min(255)].iter().enumerate() { + info[idx] = *b; + } + + let mut icon_data: NOTIFYICONDATAW = mem::zeroed(); + icon_data.cbSize = mem::size_of::() as u32; + icon_data.hWnd = hwnd; + icon_data.uID = 1; + icon_data.uFlags = NIF_INFO; + icon_data.szInfo = info; + icon_data.dwInfoFlags = NIIF_NONE | NIIF_NOSOUND; + + Shell_NotifyIconW(NIM_MODIFY, &mut icon_data); } unsafe fn remove_icon(hwnd: HWND) { - let mut icon_data: NOTIFYICONDATAW = mem::zeroed(); - icon_data.cbSize = mem::size_of::() as u32; - icon_data.hWnd = hwnd; - icon_data.uID = 1; + let mut icon_data: NOTIFYICONDATAW = mem::zeroed(); + icon_data.cbSize = mem::size_of::() as u32; + icon_data.hWnd = hwnd; + icon_data.uID = 1; - Shell_NotifyIconW(NIM_DELETE, &mut icon_data); + Shell_NotifyIconW(NIM_DELETE, &mut icon_data); } unsafe fn show_popup_menu(hwnd: HWND, state: &TrayState) { - let menu = CreatePopupMenu(); - - let hidden = state.gui_hidden; - - let mut about = str_to_wide!(localized_string("about")); - let mut toggle = str_to_wide!(if hidden { - localized_string("open-grin-gui") - } else { - localized_string("hide-grin-gui") - }); - let mut exit = str_to_wide!(localized_string("exit")); - - InsertMenuW( - menu, - 0, - MF_BYPOSITION | MF_STRING, - ID_TOGGLE_WINDOW as usize, - toggle.as_mut_ptr(), - ); - - InsertMenuW( - menu, - 1, - MF_BYPOSITION | MF_STRING, - ID_ABOUT as usize, - about.as_mut_ptr(), - ); - - InsertMenuW(menu, 2, MF_SEPARATOR | MF_GRAYED, 0, ptr::null_mut()); - - InsertMenuW( - menu, - 3, - MF_BYPOSITION | MF_STRING, - ID_EXIT as usize, - exit.as_mut_ptr(), - ); - - SetMenuDefaultItem(menu, ID_TOGGLE_WINDOW as u32, 0); - SetFocus(hwnd); - SendMessageW(hwnd, WM_INITMENUPOPUP, menu as usize, 0); - - let mut point: POINT = mem::zeroed(); - GetCursorPos(&mut point); - - let cmd = TrackPopupMenu( - menu, - TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, - point.x, - point.y, - 0, - hwnd, - ptr::null_mut(), - ); - - SendMessageW(hwnd, WM_COMMAND, cmd as usize, 0); - - DestroyMenu(menu); + let menu = CreatePopupMenu(); + + let hidden = state.gui_hidden; + + let mut about = str_to_wide!(localized_string("about")); + let mut toggle = str_to_wide!(if hidden { + localized_string("open-grin-gui") + } else { + localized_string("hide-grin-gui") + }); + let mut exit = str_to_wide!(localized_string("exit")); + + InsertMenuW( + menu, + 0, + MF_BYPOSITION | MF_STRING, + ID_TOGGLE_WINDOW as usize, + toggle.as_mut_ptr(), + ); + + InsertMenuW( + menu, + 1, + MF_BYPOSITION | MF_STRING, + ID_ABOUT as usize, + about.as_mut_ptr(), + ); + + InsertMenuW(menu, 2, MF_SEPARATOR | MF_GRAYED, 0, ptr::null_mut()); + + InsertMenuW( + menu, + 3, + MF_BYPOSITION | MF_STRING, + ID_EXIT as usize, + exit.as_mut_ptr(), + ); + + SetMenuDefaultItem(menu, ID_TOGGLE_WINDOW as u32, 0); + SetFocus(hwnd); + SendMessageW(hwnd, WM_INITMENUPOPUP, menu as usize, 0); + + let mut point: POINT = mem::zeroed(); + GetCursorPos(&mut point); + + let cmd = TrackPopupMenu( + menu, + TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, + point.x, + point.y, + 0, + hwnd, + ptr::null_mut(), + ); + + SendMessageW(hwnd, WM_COMMAND, cmd as usize, 0); + + DestroyMenu(menu); } unsafe fn show_about() { - let mut title = str_to_wide!("About"); + let mut title = str_to_wide!("About"); - let msg = format!( - "Grin GUI - {}\n\nCopyright © 2016-2022 The Grin Developers", - env!("CARGO_PKG_VERSION") - ); + let msg = format!( + "Grin GUI - {}\n\nCopyright © 2016-2022 The Grin Developers", + env!("CARGO_PKG_VERSION") + ); - let mut msg = str_to_wide!(msg); + let mut msg = str_to_wide!(msg); - MessageBoxW( - ptr::null_mut(), - msg.as_mut_ptr(), - title.as_mut_ptr(), - MB_ICONINFORMATION | MB_OK, - ); + MessageBoxW( + ptr::null_mut(), + msg.as_mut_ptr(), + title.as_mut_ptr(), + MB_ICONINFORMATION | MB_OK, + ); } unsafe extern "system" fn window_proc( - hwnd: HWND, - msg: UINT, - wparam: WPARAM, - lparam: LPARAM, + hwnd: HWND, + msg: UINT, + wparam: WPARAM, + lparam: LPARAM, ) -> LRESULT { - let mut state: &mut TrayState; - - if msg == WM_CREATE { - let create_struct = &*(lparam as *const CREATESTRUCTW); - state = &mut *(create_struct.lpCreateParams as *mut TrayState); - SetWindowLongPtrW(hwnd, GWLP_USERDATA, state as *mut _ as LPARAM); - - add_icon(hwnd); - - if state.show_balloon { - display_balloon_message(hwnd, "Running from Tray..."); - } - - return 0; - } else { - let ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA); - state = &mut *(ptr as *mut TrayState); - } - - match msg { - WM_CLOSE => { - // We send wparam as 1 when disabling tray from gui, and we don't want - // to shut down gui. Otherwise tray is closing because we selected Exit - // from tray icon - if wparam == 0 { - state.close_gui = true; - } - - remove_icon(hwnd); - - DestroyWindow(hwnd); - } - WM_DESTROY => { - PostQuitMessage(0); - } - WM_COMMAND => { - match LOWORD(wparam as u32) { - ID_ABOUT => { - // Don't show if already up - if !state.about_shown { - state.about_shown = true; - - show_about(); - - state.about_shown = false; - - return 0; - } - } - ID_TOGGLE_WINDOW => { - state.gui_hidden = !state.gui_hidden; - ShowWindow( - *state.gui_handle.as_ref().unwrap(), - if state.gui_hidden { SW_HIDE } else { SW_SHOW }, - ); - SetForegroundWindow(*state.gui_handle.as_ref().unwrap()); - - return 0; - } - ID_EXIT => { - PostMessageW(hwnd, WM_CLOSE, 0, 0); - return 0; - } - _ => {} - } - } - WM_APP => match lparam as u32 { - WM_LBUTTONDBLCLK => { - state.gui_hidden = !state.gui_hidden; - ShowWindow( - *state.gui_handle.as_ref().unwrap(), - if state.gui_hidden { SW_HIDE } else { SW_SHOW }, - ); - SetForegroundWindow(*state.gui_handle.as_ref().unwrap()); - - return 0; - } - WM_RBUTTONUP => { - SetForegroundWindow(hwnd); - show_popup_menu(hwnd, state); - - return 0; - } - _ => {} - }, - WM_HIDE_GUI => { - state.gui_hidden = true; - ShowWindow(*state.gui_handle.as_ref().unwrap(), SW_HIDE); - SetForegroundWindow(*state.gui_handle.as_ref().unwrap()); - - return 0; - } - _ => {} - } - - DefWindowProcW(hwnd, msg, wparam, lparam) + let mut state: &mut TrayState; + + if msg == WM_CREATE { + let create_struct = &*(lparam as *const CREATESTRUCTW); + state = &mut *(create_struct.lpCreateParams as *mut TrayState); + SetWindowLongPtrW(hwnd, GWLP_USERDATA, state as *mut _ as LPARAM); + + add_icon(hwnd); + + if state.show_balloon { + display_balloon_message(hwnd, "Running from Tray..."); + } + + return 0; + } else { + let ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA); + state = &mut *(ptr as *mut TrayState); + } + + match msg { + WM_CLOSE => { + // We send wparam as 1 when disabling tray from gui, and we don't want + // to shut down gui. Otherwise tray is closing because we selected Exit + // from tray icon + if wparam == 0 { + state.close_gui = true; + } + + remove_icon(hwnd); + + DestroyWindow(hwnd); + } + WM_DESTROY => { + PostQuitMessage(0); + } + WM_COMMAND => { + match LOWORD(wparam as u32) { + ID_ABOUT => { + // Don't show if already up + if !state.about_shown { + state.about_shown = true; + + show_about(); + + state.about_shown = false; + + return 0; + } + } + ID_TOGGLE_WINDOW => { + state.gui_hidden = !state.gui_hidden; + ShowWindow( + *state.gui_handle.as_ref().unwrap(), + if state.gui_hidden { SW_HIDE } else { SW_SHOW }, + ); + SetForegroundWindow(*state.gui_handle.as_ref().unwrap()); + + return 0; + } + ID_EXIT => { + PostMessageW(hwnd, WM_CLOSE, 0, 0); + return 0; + } + _ => {} + } + } + WM_APP => match lparam as u32 { + WM_LBUTTONDBLCLK => { + state.gui_hidden = !state.gui_hidden; + ShowWindow( + *state.gui_handle.as_ref().unwrap(), + if state.gui_hidden { SW_HIDE } else { SW_SHOW }, + ); + SetForegroundWindow(*state.gui_handle.as_ref().unwrap()); + + return 0; + } + WM_RBUTTONUP => { + SetForegroundWindow(hwnd); + show_popup_menu(hwnd, state); + + return 0; + } + _ => {} + }, + WM_HIDE_GUI => { + state.gui_hidden = true; + ShowWindow(*state.gui_handle.as_ref().unwrap(), SW_HIDE); + SetForegroundWindow(*state.gui_handle.as_ref().unwrap()); + + return 0; + } + _ => {} + } + + DefWindowProcW(hwnd, msg, wparam, lparam) } unsafe extern "system" fn enum_proc(hwnd: HWND, lparam: LPARAM) -> BOOL { - let mut state = &mut *(lparam as *mut TrayState); + let mut state = &mut *(lparam as *mut TrayState); - let mut id = mem::zeroed(); - GetWindowThreadProcessId(hwnd, &mut id); + let mut id = mem::zeroed(); + GetWindowThreadProcessId(hwnd, &mut id); - if id == std::process::id() { - let mut title = [0u16; 12]; - let read_len = GetWindowTextW(hwnd, title.as_mut_ptr(), 12); - let title = String::from_utf16_lossy(&title[0..read_len.min(12) as usize]); + if id == std::process::id() { + let mut title = [0u16; 12]; + let read_len = GetWindowTextW(hwnd, title.as_mut_ptr(), 12); + let title = String::from_utf16_lossy(&title[0..read_len.min(12) as usize]); - if title == "Grin" { - state.gui_handle = Some(hwnd); + if title == "Grin" { + state.gui_handle = Some(hwnd); - return 0; - } - } + return 0; + } + } - 1 + 1 }