From 29e134748df363963092e16dcd288ed6431d7434 Mon Sep 17 00:00:00 2001 From: edouard Date: Fri, 19 Aug 2022 17:58:59 +0200 Subject: [PATCH] Add settings panel to gui Remove context module for a cache module. --- gui/src/app/cache.rs | 3 + gui/src/app/context.rs | 75 ---------- gui/src/app/message.rs | 1 + gui/src/app/mod.rs | 104 +++++++++++--- gui/src/app/state/mod.rs | 32 ++++- gui/src/app/state/settings.rs | 238 ++++++++++++++++++++++++++++++++ gui/src/app/view/message.rs | 9 ++ gui/src/app/view/mod.rs | 4 +- gui/src/app/view/settings.rs | 241 +++++++++++++++++++++++++++++++++ gui/src/conversion.rs | 43 ------ gui/src/daemon/client/mod.rs | 11 +- gui/src/daemon/embedded.rs | 19 ++- gui/src/daemon/mod.rs | 2 + gui/src/lib.rs | 1 - gui/src/loader.rs | 15 +- gui/src/main.rs | 17 +-- gui/src/ui/component/badge.rs | 70 ++++++++++ gui/src/ui/component/button.rs | 38 +++++- gui/src/ui/component/card.rs | 18 +++ gui/src/ui/component/mod.rs | 2 + gui/src/ui/component/text.rs | 4 + 21 files changed, 772 insertions(+), 175 deletions(-) create mode 100644 gui/src/app/cache.rs delete mode 100644 gui/src/app/context.rs create mode 100644 gui/src/app/state/settings.rs create mode 100644 gui/src/app/view/settings.rs delete mode 100644 gui/src/conversion.rs create mode 100644 gui/src/ui/component/badge.rs create mode 100644 gui/src/ui/component/card.rs diff --git a/gui/src/app/cache.rs b/gui/src/app/cache.rs new file mode 100644 index 00000000..01636ca5 --- /dev/null +++ b/gui/src/app/cache.rs @@ -0,0 +1,3 @@ +pub struct Cache { + pub blockheight: i32, +} diff --git a/gui/src/app/context.rs b/gui/src/app/context.rs deleted file mode 100644 index 6e1a0572..00000000 --- a/gui/src/app/context.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::fs::OpenOptions; -use std::io::Write; -use std::sync::Arc; - -use minisafe::config::Config as DaemonConfig; - -use crate::{ - app::{config, error::Error, menu::Menu}, - conversion::Converter, - daemon::Daemon, -}; - -/// Context is an object passing general information -/// and service clients through the application components. -pub struct Context { - pub config: ConfigContext, - pub blockheight: i32, - pub daemon: Arc, - pub converter: Converter, - pub menu: Menu, - pub managers_threshold: usize, -} - -impl Context { - pub fn new( - config: ConfigContext, - daemon: Arc, - converter: Converter, - menu: Menu, - ) -> Self { - Self { - config, - blockheight: 0, - daemon, - converter, - menu, - managers_threshold: 0, - } - } - - pub fn network(&self) -> bitcoin::Network { - self.config.daemon.bitcoin_config.network - } - - pub fn load_daemon_config(&mut self, cfg: DaemonConfig) -> Result<(), Error> { - loop { - if let Some(daemon) = Arc::get_mut(&mut self.daemon) { - daemon.load_config(cfg)?; - break; - } - } - - let mut daemon_config_file = OpenOptions::new() - .write(true) - .open(&self.config.gui.minisafed_config_path) - .map_err(|e| Error::Config(e.to_string()))?; - - let content = - toml::to_string(&self.config.daemon).map_err(|e| Error::Config(e.to_string()))?; - - daemon_config_file - .write_all(content.as_bytes()) - .map_err(|e| { - log::warn!("failed to write to file: {:?}", e); - Error::Config(e.to_string()) - })?; - - Ok(()) - } -} - -pub struct ConfigContext { - pub daemon: DaemonConfig, - pub gui: config::Config, -} diff --git a/gui/src/app/message.rs b/gui/src/app/message.rs index 212ce892..6183977f 100644 --- a/gui/src/app/message.rs +++ b/gui/src/app/message.rs @@ -9,4 +9,5 @@ pub enum Message { View(view::Message), LoadDaemonConfig(Box), DaemonConfigLoaded(Result<(), Error>), + BlockHeight(Result), } diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index 3ebc038d..f6dfcccf 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -1,5 +1,5 @@ +pub mod cache; pub mod config; -pub mod context; pub mod menu; pub mod message; pub mod state; @@ -7,6 +7,8 @@ pub mod view; mod error; +use std::fs::OpenOptions; +use std::io::Write; use std::sync::Arc; use std::time::Duration; @@ -14,37 +16,57 @@ use iced::pure::Element; use iced::{clipboard, time, Command, Subscription}; use iced_native::{window, Event}; +pub use minisafe::config::Config as DaemonConfig; + pub use config::Config; pub use message::Message; use state::{Home, State}; -use crate::app::context::Context; +use crate::{ + app::{cache::Cache, error::Error, menu::Menu}, + daemon::Daemon, +}; pub struct App { should_exit: bool, state: Box, - context: Context, -} - -pub fn new_state(_context: &Context) -> Box { - Home {}.into() + cache: Cache, + config: Config, + daemon: Arc, } impl App { - pub fn new(context: Context) -> (App, Command) { - let state = new_state(&context); - let cmd = state.load(&context); + pub fn new( + cache: Cache, + config: Config, + daemon: Arc, + ) -> (App, Command) { + let state: Box = Home {}.into(); + let cmd = state.load(daemon.clone()); ( Self { should_exit: false, state, - context, + cache, + config, + daemon, }, cmd, ) } + fn load_state(&mut self, menu: &Menu) -> Command { + self.state = match menu { + menu::Menu::Settings => { + state::SettingsState::new(self.daemon.config().clone(), self.daemon.is_external()) + .into() + } + menu::Menu::Home => Home {}.into(), + }; + self.state.load(self.daemon.clone()) + } + pub fn subscription(&self) -> Subscription { Subscription::batch(vec![ iced_native::subscription::events().map(Message::Event), @@ -59,9 +81,9 @@ impl App { pub fn stop(&mut self) { log::info!("Close requested"); - if !self.context.daemon.is_external() { + if !self.daemon.is_external() { log::info!("Stopping internal daemon..."); - if let Some(d) = Arc::get_mut(&mut self.context.daemon) { + if let Some(d) = Arc::get_mut(&mut self.daemon) { d.stop().expect("Daemon is internal"); log::info!("Internal daemon stopped"); self.should_exit = true; @@ -73,25 +95,65 @@ impl App { pub fn update(&mut self, message: Message) -> Command { match message { + Message::Tick => { + let daemon = self.daemon.clone(); + Command::perform( + async move { + daemon + .get_info() + .map(|res| res.blockheight) + .map_err(|e| e.into()) + }, + Message::BlockHeight, + ) + } + Message::BlockHeight(res) => { + if let Ok(blockheight) = res { + self.cache.blockheight = blockheight; + } + Command::none() + } Message::LoadDaemonConfig(cfg) => { - let res = self.context.load_daemon_config(*cfg); + let res = self.load_daemon_config(*cfg); self.update(Message::DaemonConfigLoaded(res)) } - Message::View(view::Message::Menu(menu)) => { - self.context.menu = menu; - self.state = new_state(&self.context); - self.state.load(&self.context) - } + Message::View(view::Message::Menu(menu)) => self.load_state(&menu), Message::View(view::Message::Clipboard(text)) => clipboard::write(text), Message::Event(Event::Window(window::Event::CloseRequested)) => { self.stop(); Command::none() } - _ => self.state.update(&self.context, message), + _ => self.state.update(self.daemon.clone(), &self.cache, message), } } + pub fn load_daemon_config(&mut self, cfg: DaemonConfig) -> Result<(), Error> { + loop { + if let Some(daemon) = Arc::get_mut(&mut self.daemon) { + daemon.load_config(cfg)?; + break; + } + } + + let mut daemon_config_file = OpenOptions::new() + .write(true) + .open(&self.config.minisafed_config_path) + .map_err(|e| Error::Config(e.to_string()))?; + + let content = + toml::to_string(&self.daemon.config()).map_err(|e| Error::Config(e.to_string()))?; + + daemon_config_file + .write_all(content.as_bytes()) + .map_err(|e| { + log::warn!("failed to write to file: {:?}", e); + Error::Config(e.to_string()) + })?; + + Ok(()) + } + pub fn view(&self) -> Element { - self.state.view(&self.context).map(Message::View) + self.state.view(&self.cache).map(Message::View) } } diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index 91d02279..aa87e7b2 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -1,15 +1,28 @@ +mod settings; + +use std::sync::Arc; + use iced::pure::{column, Element}; use iced::{Command, Subscription}; -use super::{context::Context, message::Message, view}; +use super::{cache::Cache, menu::Menu, message::Message, view}; + +pub use settings::SettingsState; + +use crate::daemon::Daemon; pub trait State { - fn view<'a>(&self, ctx: &'a Context) -> Element<'a, view::Message>; - fn update(&mut self, ctx: &Context, message: Message) -> Command; + fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message>; + fn update( + &mut self, + daemon: Arc, + cache: &Cache, + message: Message, + ) -> Command; fn subscription(&self) -> Subscription { Subscription::none() } - fn load(&self, _ctx: &Context) -> Command { + fn load(&self, _daemon: Arc) -> Command { Command::none() } } @@ -17,10 +30,15 @@ pub trait State { pub struct Home {} impl State for Home { - fn view<'a>(&self, ctx: &'a Context) -> Element<'a, view::Message> { - view::dashboard(&ctx.menu, None, column()) + fn view<'a>(&self, _cache: &'a Cache) -> Element<'a, view::Message> { + view::dashboard(&Menu::Home, None, column()) } - fn update(&mut self, _ctx: &Context, _message: Message) -> Command { + fn update( + &mut self, + _daemon: Arc, + _cache: &Cache, + _message: Message, + ) -> Command { Command::none() } } diff --git a/gui/src/app/state/settings.rs b/gui/src/app/state/settings.rs new file mode 100644 index 00000000..3241703b --- /dev/null +++ b/gui/src/app/state/settings.rs @@ -0,0 +1,238 @@ +use std::convert::From; +use std::net::SocketAddr; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::Arc; + +use iced::{pure::Element, Command}; + +use minisafe::config::Config; + +use crate::{ + app::{cache::Cache, error::Error, message::Message, state::State, view}, + daemon::Daemon, + ui::component::form, +}; + +trait Setting: std::fmt::Debug { + fn edited(&mut self, success: bool); + fn update( + &mut self, + daemon: Arc, + cache: &Cache, + message: view::SettingsMessage, + ) -> Command; + fn view<'a>( + &self, + cfg: &'a Config, + cache: &'a Cache, + can_edit: bool, + ) -> Element<'a, view::SettingsMessage>; +} + +#[derive(Debug)] +pub struct SettingsState { + warning: Option, + config_updated: bool, + config: Config, + daemon_is_external: bool, + + settings: Vec>, + current: Option, +} + +impl SettingsState { + pub fn new(config: Config, daemon_is_external: bool) -> Self { + let settings = vec![BitcoindSettings::new(&config).into()]; + + SettingsState { + daemon_is_external, + warning: None, + config_updated: false, + config, + settings, + current: None, + } + } +} + +impl State for SettingsState { + fn update( + &mut self, + daemon: Arc, + cache: &Cache, + message: Message, + ) -> Command { + match message { + Message::DaemonConfigLoaded(res) => match res { + Ok(()) => { + self.config_updated = true; + self.warning = None; + if let Some(current) = self.current { + if let Some(setting) = self.settings.get_mut(current) { + setting.edited(true) + } + } + self.current = None; + } + Err(e) => { + self.config_updated = false; + self.warning = Some(e); + if let Some(current) = self.current { + if let Some(setting) = self.settings.get_mut(current) { + setting.edited(false); + } + } + } + }, + Message::View(view::Message::Settings(i, msg)) => { + if let Some(setting) = self.settings.get_mut(i) { + match msg { + view::SettingsMessage::Edit => self.current = Some(i), + view::SettingsMessage::CancelEdit => self.current = None, + _ => {} + }; + return setting.update(daemon, cache, msg); + } + } + _ => {} + }; + Command::none() + } + + fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { + let can_edit = self.current.is_none() && !self.daemon_is_external; + view::settings::list( + self.warning.as_ref(), + self.settings + .iter() + .enumerate() + .map(|(i, setting)| { + setting + .view(&self.config, cache, can_edit) + .map(move |msg| view::Message::Settings(i, msg)) + }) + .collect(), + ) + } +} + +impl From for Box { + fn from(s: SettingsState) -> Box { + Box::new(s) + } +} + +#[derive(Debug)] +pub struct BitcoindSettings { + edit: bool, + processing: bool, + cookie_path: form::Value, + addr: form::Value, +} + +impl From for Box { + fn from(s: BitcoindSettings) -> Box { + Box::new(s) + } +} + +impl BitcoindSettings { + fn new(cfg: &Config) -> BitcoindSettings { + let cfg = cfg.bitcoind_config.as_ref().unwrap(); + BitcoindSettings { + edit: false, + processing: false, + cookie_path: form::Value { + valid: true, + value: cfg.cookie_path.to_str().unwrap().to_string(), + }, + addr: form::Value { + valid: true, + value: cfg.addr.to_string(), + }, + } + } +} + +impl Setting for BitcoindSettings { + fn edited(&mut self, success: bool) { + self.processing = false; + if success { + self.edit = false; + } + } + + fn update( + &mut self, + daemon: Arc, + _cache: &Cache, + message: view::SettingsMessage, + ) -> Command { + match message { + view::SettingsMessage::Edit => { + if !self.processing { + self.edit = true; + } + } + view::SettingsMessage::CancelEdit => { + if !self.processing { + self.edit = false; + } + } + view::SettingsMessage::FieldEdited(field, value) => { + if !self.processing { + match field { + "socket_address" => self.addr.value = value, + "cookie_file_path" => self.cookie_path.value = value, + _ => {} + } + } + } + view::SettingsMessage::ConfirmEdit => { + let new_addr = SocketAddr::from_str(&self.addr.value); + self.addr.valid = new_addr.is_ok(); + let new_path = PathBuf::from_str(&self.cookie_path.value); + self.cookie_path.valid = new_path.is_ok(); + + if self.addr.valid & self.cookie_path.valid { + let mut daemon_config = daemon.config().clone(); + daemon_config.bitcoind_config = Some(minisafe::config::BitcoindConfig { + cookie_path: new_path.unwrap(), + addr: new_addr.unwrap(), + }); + self.processing = true; + return Command::perform(async move { daemon_config }, |cfg| { + Message::LoadDaemonConfig(Box::new(cfg)) + }); + } + } + }; + Command::none() + } + + fn view<'a>( + &self, + config: &'a Config, + cache: &'a Cache, + can_edit: bool, + ) -> Element<'a, view::SettingsMessage> { + if self.edit { + view::settings::bitcoind_edit( + config.bitcoin_config.network, + cache.blockheight, + &self.addr, + &self.cookie_path, + self.processing, + ) + } else { + view::settings::bitcoind( + config.bitcoin_config.network, + config.bitcoind_config.as_ref().unwrap(), + cache.blockheight, + Some(cache.blockheight != 0), + can_edit, + ) + } + } +} diff --git a/gui/src/app/view/message.rs b/gui/src/app/view/message.rs index 9fd7bd83..30910eab 100644 --- a/gui/src/app/view/message.rs +++ b/gui/src/app/view/message.rs @@ -5,4 +5,13 @@ pub enum Message { Reload, Clipboard(String), Menu(Menu), + Settings(usize, SettingsMessage), +} + +#[derive(Debug, Clone)] +pub enum SettingsMessage { + Edit, + FieldEdited(&'static str, String), + CancelEdit, + ConfirmEdit, } diff --git a/gui/src/app/view/mod.rs b/gui/src/app/view/mod.rs index b2e1c1f0..d2b66791 100644 --- a/gui/src/app/view/mod.rs +++ b/gui/src/app/view/mod.rs @@ -1,7 +1,9 @@ mod message; mod warning; -pub use message::Message; +pub mod settings; + +pub use message::*; use warning::warn; use iced::{ diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs new file mode 100644 index 00000000..3aae7692 --- /dev/null +++ b/gui/src/app/view/settings.rs @@ -0,0 +1,241 @@ +use iced::{ + alignment, + pure::{column, container, row, widget, Element}, + Alignment, Length, +}; + +use super::{ + dashboard, + message::{Message, SettingsMessage}, +}; + +use crate::{ + app::{error::Error, menu::Menu}, + ui::{ + color, + component::{badge, button, card, form, separation, text::*}, + icon, + }, +}; + +pub fn list<'a>( + warning: Option<&Error>, + settings: Vec>, +) -> Element<'a, Message> { + dashboard( + &Menu::Settings, + warning, + widget::Column::with_children(settings).spacing(20), + ) +} + +pub fn bitcoind_edit<'a>( + network: bitcoin::Network, + blockheight: i32, + addr: &form::Value, + cookie_path: &form::Value, + processing: bool, +) -> Element<'a, SettingsMessage> { + let mut col = column().spacing(20); + if blockheight != 0 { + col = col + .push( + row() + .push( + row() + .push(badge::Badge::new(icon::network_icon())) + .push( + column() + .push(text("Network:")) + .push(text(&network.to_string()).bold()), + ) + .spacing(10) + .width(Length::FillPortion(1)), + ) + .push( + row() + .push(badge::Badge::new(icon::block_icon())) + .push( + column() + .push(text("Block Height:")) + .push(text(&blockheight.to_string()).bold()), + ) + .spacing(10) + .width(Length::FillPortion(1)), + ), + ) + .push(separation().width(Length::Fill)); + } + + col = col + .push( + column() + .push(text("Cookie file path:").bold().small()) + .push( + form::Form::new("Cookie file path", cookie_path, |value| { + SettingsMessage::FieldEdited("cookie_file_path", value) + }) + .warning("Please enter a valid filesystem path") + .size(20) + .padding(5), + ) + .spacing(5), + ) + .push( + column() + .push(text("Socket address:").bold().small()) + .push( + form::Form::new("Socket address:", addr, |value| { + SettingsMessage::FieldEdited("socket_address", value) + }) + .warning("Please enter a valid address") + .size(20) + .padding(5), + ) + .spacing(5), + ); + + let mut cancel_button = button::transparent(None, " Cancel ").padding(5); + let mut confirm_button = button::primary(None, " Save ").padding(5); + if !processing { + cancel_button = cancel_button.on_press(SettingsMessage::CancelEdit); + confirm_button = confirm_button.on_press(SettingsMessage::ConfirmEdit); + } + + card::simple(container( + column() + .push( + row() + .push(badge::Badge::new(icon::bitcoin_icon())) + .push(text("Bitcoind")) + .padding(10) + .spacing(20) + .align_items(Alignment::Center) + .width(Length::Fill), + ) + .push(separation().width(Length::Fill)) + .push(col) + .push( + container( + row() + .push(cancel_button) + .push(confirm_button) + .spacing(10) + .align_items(Alignment::Center), + ) + .width(Length::Fill) + .align_x(alignment::Horizontal::Right), + ) + .spacing(20), + )) + .width(Length::Fill) + .into() +} + +pub fn bitcoind<'a>( + network: bitcoin::Network, + config: &minisafe::config::BitcoindConfig, + blockheight: i32, + is_running: Option, + can_edit: bool, +) -> Element<'a, SettingsMessage> { + let mut col = column().spacing(20); + if blockheight != 0 { + col = col + .push( + row() + .push( + row() + .push(badge::Badge::new(icon::network_icon())) + .push( + column() + .push(text("Network:")) + .push(text(&network.to_string()).bold()), + ) + .spacing(10) + .width(Length::FillPortion(1)), + ) + .push( + row() + .push(badge::Badge::new(icon::block_icon())) + .push( + column() + .push(text("Block Height:")) + .push(text(&blockheight.to_string()).bold()), + ) + .spacing(10) + .width(Length::FillPortion(1)), + ), + ) + .push(separation().width(Length::Fill)); + } + + let rows = vec![ + ( + "Cookie file path:", + config.cookie_path.to_str().unwrap().to_string(), + ), + ("Socket address:", config.addr.to_string()), + ]; + + let mut col_fields = column(); + for (k, v) in rows { + col_fields = col_fields.push( + row() + .push(container(text(k).bold().small()).width(Length::Fill)) + .push(text(&v).small()), + ); + } + + card::simple(container( + column() + .push( + row() + .push( + row() + .push(badge::Badge::new(icon::bitcoin_icon())) + .push(text("Bitcoind")) + .push(is_running_label(is_running)) + .spacing(20) + .align_items(Alignment::Center) + .width(Length::Fill), + ) + .push(if can_edit { + widget::Button::new(icon::pencil_icon()) + .style(button::Style::TransparentBorder) + .on_press(SettingsMessage::Edit) + } else { + widget::Button::new(icon::pencil_icon()) + .style(button::Style::TransparentBorder) + }) + .align_items(Alignment::Center), + ) + .push(separation().width(Length::Fill)) + .push(col.push(col_fields)) + .spacing(20), + )) + .width(Length::Fill) + .into() +} + +pub fn is_running_label<'a, T: 'a>(is_running: Option) -> widget::Container<'a, T> { + if let Some(running) = is_running { + if running { + container( + row() + .push(icon::dot_icon().size(5).color(color::SUCCESS)) + .push(text("Running").small().color(color::SUCCESS)) + .align_items(Alignment::Center), + ) + } else { + container( + row() + .push(icon::dot_icon().size(5).color(color::ALERT)) + .push(text("Not running").small().color(color::ALERT)) + .align_items(Alignment::Center), + ) + } + } else { + container(column()) + } +} diff --git a/gui/src/conversion.rs b/gui/src/conversion.rs deleted file mode 100644 index d4cd0faa..00000000 --- a/gui/src/conversion.rs +++ /dev/null @@ -1,43 +0,0 @@ -use bitcoin::Network; - -/// Converter purpose is to give a Conversion from a given amount in satoshis according to its -/// parameters. -pub struct Converter { - pub unit: Unit, -} - -impl Converter { - pub fn new(bitcoin_network: Network) -> Self { - let unit = match bitcoin_network { - Network::Testnet => Unit::TestnetBitcoin, - Network::Bitcoin => Unit::Bitcoin, - Network::Regtest => Unit::RegtestBitcoin, - Network::Signet => Unit::SignetBitcoin, - }; - Self { unit } - } - - /// converts amount in satoshis to BTC float. - pub fn converts(&self, amount: bitcoin::Amount) -> String { - format!("{:.8}", amount.as_btc()) - } -} - -/// Unit is the bitcoin ticker according to the network used. -pub enum Unit { - TestnetBitcoin, - RegtestBitcoin, - SignetBitcoin, - Bitcoin, -} - -impl std::fmt::Display for Unit { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::TestnetBitcoin => write!(f, "tBTC"), - Self::RegtestBitcoin => write!(f, "rBTC"), - Self::SignetBitcoin => write!(f, "sBTC"), - Self::Bitcoin => write!(f, "BTC"), - } - } -} diff --git a/gui/src/daemon/client/mod.rs b/gui/src/daemon/client/mod.rs index 425be25e..b4eedfd0 100644 --- a/gui/src/daemon/client/mod.rs +++ b/gui/src/daemon/client/mod.rs @@ -7,6 +7,8 @@ use serde::{Deserialize, Serialize}; pub mod error; pub mod jsonrpc; +use minisafe::config::Config; + use super::{model::*, Daemon, DaemonError}; pub trait Client { @@ -20,12 +22,13 @@ pub trait Client { #[derive(Debug, Clone)] pub struct Minisafed { + config: Config, client: C, } impl Minisafed { - pub fn new(client: C) -> Minisafed { - Minisafed { client } + pub fn new(client: C, config: Config) -> Minisafed { + Minisafed { client, config } } /// Generic call function for RPC calls. @@ -47,6 +50,10 @@ impl Daemon for Minisafed { true } + fn config(&self) -> &Config { + &self.config + } + fn stop(&mut self) -> Result<(), DaemonError> { let _res: serde_json::value::Value = self.call("stop", Option::::None)?; Ok(()) diff --git a/gui/src/daemon/embedded.rs b/gui/src/daemon/embedded.rs index 6850942b..798d51ae 100644 --- a/gui/src/daemon/embedded.rs +++ b/gui/src/daemon/embedded.rs @@ -3,15 +3,22 @@ use std::sync::Mutex; use super::{model::*, Daemon, DaemonError}; use minisafe::{config::Config, DaemonHandle}; -#[derive(Default)] pub struct EmbeddedDaemon { + config: Config, handle: Option>, } impl EmbeddedDaemon { - pub fn start(&mut self, config: Config) -> Result<(), DaemonError> { - let handle = - DaemonHandle::start_default(config).map_err(|e| DaemonError::Start(e.to_string()))?; + pub fn new(config: Config) -> Self { + Self { + config, + handle: None, + } + } + + pub fn start(&mut self) -> Result<(), DaemonError> { + let handle = DaemonHandle::start_default(self.config.clone()) + .map_err(|e| DaemonError::Start(e.to_string()))?; self.handle = Some(Mutex::new(handle)); Ok(()) } @@ -40,6 +47,10 @@ impl Daemon for EmbeddedDaemon { Ok(()) } + fn config(&self) -> &Config { + &self.config + } + fn stop(&mut self) -> Result<(), DaemonError> { if let Some(h) = self.handle.take() { let handle = h.into_inner().unwrap(); diff --git a/gui/src/daemon/mod.rs b/gui/src/daemon/mod.rs index 094067bf..950ec8b8 100644 --- a/gui/src/daemon/mod.rs +++ b/gui/src/daemon/mod.rs @@ -40,6 +40,8 @@ pub trait Daemon: Debug { Ok(()) } + fn config(&self) -> &Config; + fn stop(&mut self) -> Result<(), DaemonError>; fn get_info(&self) -> Result; diff --git a/gui/src/lib.rs b/gui/src/lib.rs index 7681b165..36ca6d86 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -1,5 +1,4 @@ pub mod app; -pub mod conversion; pub mod daemon; pub mod installer; pub mod loader; diff --git a/gui/src/loader.rs b/gui/src/loader.rs index a3f03f5a..f4fc6f2a 100644 --- a/gui/src/loader.rs +++ b/gui/src/loader.rs @@ -19,7 +19,6 @@ type Minisafed = client::Minisafed; pub struct Loader { pub gui_config: GUIConfig, - pub daemon_config: Config, pub daemon_started: bool, should_exit: bool, @@ -56,13 +55,12 @@ impl Loader { .unwrap(); ( Loader { - daemon_config, gui_config, step: Step::Connecting, should_exit: false, daemon_started: false, }, - Command::perform(connect(path), Message::Loaded), + Command::perform(connect(path, daemon_config), Message::Loaded), ) } @@ -203,9 +201,12 @@ pub fn cover<'a, T: 'a, C: Into>>(content: C) -> Element<'a, T> { .into() } -async fn connect(socket_path: PathBuf) -> Result, Error> { +async fn connect( + socket_path: PathBuf, + config: Config, +) -> Result, Error> { let client = client::jsonrpc::JsonRPCClient::new(socket_path); - let minisafed = Minisafed::new(client); + let minisafed = Minisafed::new(client, config); debug!("Searching for external daemon"); minisafed.get_info()?; @@ -221,8 +222,8 @@ pub async fn start_daemon(config_path: PathBuf) -> Result { - if let loader::Message::Synced(_info, minisafed) = *msg { - let config = ConfigContext { - gui: loader.gui_config.clone(), - daemon: loader.daemon_config.clone(), + if let loader::Message::Synced(info, minisafed) = *msg { + let cache = Cache { + blockheight: info.blockheight, }; - let converter = Converter::new(config.daemon.bitcoin_config.network); - - let context = Context::new(config, minisafed, converter, Menu::Home); - - let (app, command) = App::new(context); + let (app, command) = App::new(cache, loader.gui_config.clone(), minisafed); self.state = State::App(app); command.map(|msg| Message::Run(Box::new(msg))) } else { diff --git a/gui/src/ui/component/badge.rs b/gui/src/ui/component/badge.rs new file mode 100644 index 00000000..88a205cf --- /dev/null +++ b/gui/src/ui/component/badge.rs @@ -0,0 +1,70 @@ +use iced::{ + pure::{container, widget, Element}, + Length, +}; + +use crate::ui::color; + +pub enum Style { + Standard, + Success, + Warning, +} + +impl widget::container::StyleSheet for Style { + fn style(&self) -> widget::container::Style { + match self { + Self::Standard => widget::container::Style { + border_radius: 40.0, + background: color::BACKGROUND.into(), + ..widget::container::Style::default() + }, + Self::Success => widget::container::Style { + border_radius: 40.0, + background: color::SUCCESS_LIGHT.into(), + text_color: color::SUCCESS.into(), + ..widget::container::Style::default() + }, + Self::Warning => widget::container::Style { + border_radius: 40.0, + background: color::WARNING_LIGHT.into(), + text_color: color::WARNING.into(), + ..widget::container::Style::default() + }, + } + } +} + +pub struct Badge { + icon: widget::Text, + style: S, +} + +impl Badge