From 2286a19e20cfde07d4c7eb4bdd3190abea3a09d1 Mon Sep 17 00:00:00 2001 From: edouard Date: Wed, 7 Sep 2022 15:11:51 +0200 Subject: [PATCH 1/8] Update minisafe master:#4a802890 --- gui/Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/Cargo.lock b/gui/Cargo.lock index c5a56114..19bb550e 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -1390,7 +1390,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "minisafe" version = "0.0.1" -source = "git+https://github.com/revault/minisafe?branch=master#b548451292f4f497a5a837244b6432e473e6cd55" +source = "git+https://github.com/revault/minisafe?branch=master#4a802890634f60d77e4da5a56f9189024ef99500" dependencies = [ "backtrace", "dirs", From 1026d2b487c3f0d9955b93d1cf1b79a0a8ad94bf Mon Sep 17 00:00:00 2001 From: edouard Date: Thu, 1 Sep 2022 16:47:52 +0200 Subject: [PATCH 2/8] Add get_new_address to daemon trait --- gui/src/daemon/client/mod.rs | 4 ++++ gui/src/daemon/embedded.rs | 11 +++++++++++ gui/src/daemon/mod.rs | 2 ++ gui/src/daemon/model.rs | 2 +- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/gui/src/daemon/client/mod.rs b/gui/src/daemon/client/mod.rs index b4eedfd0..94d507d4 100644 --- a/gui/src/daemon/client/mod.rs +++ b/gui/src/daemon/client/mod.rs @@ -62,6 +62,10 @@ impl Daemon for Minisafed { fn get_info(&self) -> Result { self.call("getinfo", Option::::None) } + + fn get_new_address(&self) -> Result { + self.call("getnewaddress", Option::::None) + } } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/gui/src/daemon/embedded.rs b/gui/src/daemon/embedded.rs index 798d51ae..cca381b7 100644 --- a/gui/src/daemon/embedded.rs +++ b/gui/src/daemon/embedded.rs @@ -69,4 +69,15 @@ impl Daemon for EmbeddedDaemon { .control .get_info()) } + + fn get_new_address(&self) -> Result { + Ok(self + .handle + .as_ref() + .ok_or(DaemonError::NoAnswer)? + .lock() + .unwrap() + .control + .get_new_address()) + } } diff --git a/gui/src/daemon/mod.rs b/gui/src/daemon/mod.rs index 950ec8b8..37f8f1d4 100644 --- a/gui/src/daemon/mod.rs +++ b/gui/src/daemon/mod.rs @@ -45,4 +45,6 @@ pub trait Daemon: Debug { fn stop(&mut self) -> Result<(), DaemonError>; fn get_info(&self) -> Result; + + fn get_new_address(&self) -> Result; } diff --git a/gui/src/daemon/model.rs b/gui/src/daemon/model.rs index 65dbc0f0..dabccf2d 100644 --- a/gui/src/daemon/model.rs +++ b/gui/src/daemon/model.rs @@ -1 +1 @@ -pub use minisafe::commands::GetInfoResult; +pub use minisafe::commands::{GetAddressResult, GetInfoResult}; From 928294b32e2afa0ae4472817f8bbb1e477b8afdb Mon Sep 17 00:00:00 2001 From: edouard Date: Thu, 1 Sep 2022 16:48:07 +0200 Subject: [PATCH 3/8] Add receive panel --- gui/src/app/menu.rs | 1 + gui/src/app/message.rs | 1 + gui/src/app/mod.rs | 3 +- gui/src/app/state/mod.rs | 66 ++++++++++++++++++++++++++++++++++--- gui/src/app/view/mod.rs | 15 ++++++++- gui/src/app/view/receive.rs | 32 ++++++++++++++++++ gui/src/ui/icon.rs | 2 +- 7 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 gui/src/app/view/receive.rs diff --git a/gui/src/app/menu.rs b/gui/src/app/menu.rs index cca33768..ddeb4b09 100644 --- a/gui/src/app/menu.rs +++ b/gui/src/app/menu.rs @@ -1,5 +1,6 @@ #[derive(Debug, Clone, PartialEq, Eq)] pub enum Menu { Home, + Receive, Settings, } diff --git a/gui/src/app/message.rs b/gui/src/app/message.rs index 6183977f..c4e2cabc 100644 --- a/gui/src/app/message.rs +++ b/gui/src/app/message.rs @@ -10,4 +10,5 @@ pub enum Message { LoadDaemonConfig(Box), DaemonConfigLoaded(Result<(), Error>), BlockHeight(Result), + ReceiveAddress(Result), } diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index f6dfcccf..c55a87f2 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -21,7 +21,7 @@ pub use minisafe::config::Config as DaemonConfig; pub use config::Config; pub use message::Message; -use state::{Home, State}; +use state::{Home, ReceivePanel, State}; use crate::{ app::{cache::Cache, error::Error, menu::Menu}, @@ -63,6 +63,7 @@ impl App { .into() } menu::Menu::Home => Home {}.into(), + menu::Menu::Receive => ReceivePanel::default().into(), }; self.state.load(self.daemon.clone()) } diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index aa87e7b2..18dd4242 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -3,9 +3,9 @@ mod settings; use std::sync::Arc; use iced::pure::{column, Element}; -use iced::{Command, Subscription}; +use iced::{widget::qr_code, Command, Subscription}; -use super::{cache::Cache, menu::Menu, message::Message, view}; +use super::{cache::Cache, error::Error, menu::Menu, message::Message, view}; pub use settings::SettingsState; @@ -15,7 +15,7 @@ pub trait State { fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message>; fn update( &mut self, - daemon: Arc, + daemon: Arc, cache: &Cache, message: Message, ) -> Command; @@ -35,7 +35,7 @@ impl State for Home { } fn update( &mut self, - _daemon: Arc, + _daemon: Arc, _cache: &Cache, _message: Message, ) -> Command { @@ -48,3 +48,61 @@ impl From for Box { Box::new(s) } } + +#[derive(Default)] +pub struct ReceivePanel { + address: Option, + qr_code: Option, + warning: Option, +} + +impl State for ReceivePanel { + fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> { + if let Some(address) = &self.address { + view::dashboard( + &Menu::Receive, + self.warning.as_ref(), + view::receive::receive(address, self.qr_code.as_ref().unwrap()), + ) + } else { + view::dashboard(&Menu::Receive, self.warning.as_ref(), column()) + } + } + fn update( + &mut self, + _daemon: Arc, + _cache: &Cache, + message: Message, + ) -> Command { + if let Message::ReceiveAddress(res) = message { + match res { + Ok(address) => { + self.warning = None; + self.qr_code = Some(qr_code::State::new(&address.to_qr_uri()).unwrap()); + self.address = Some(address); + } + Err(e) => self.warning = Some(e), + } + }; + Command::none() + } + + fn load(&self, daemon: Arc) -> Command { + let daemon = daemon.clone(); + Command::perform( + async move { + daemon + .get_new_address() + .map(|res| res.address) + .map_err(|e| e.into()) + }, + Message::ReceiveAddress, + ) + } +} + +impl From for Box { + fn from(s: ReceivePanel) -> Box { + Box::new(s) + } +} diff --git a/gui/src/app/view/mod.rs b/gui/src/app/view/mod.rs index d2b66791..d5d05d5d 100644 --- a/gui/src/app/view/mod.rs +++ b/gui/src/app/view/mod.rs @@ -1,6 +1,7 @@ mod message; mod warning; +pub mod receive; pub mod settings; pub use message::*; @@ -14,7 +15,7 @@ use iced::{ use crate::ui::{ color, component::{button, separation, text::*}, - icon::{home_icon, settings_icon}, + icon::{home_icon, receive_icon, settings_icon}, }; use crate::app::{error::Error, menu::Menu}; @@ -29,6 +30,17 @@ pub fn sidebar(menu: &Menu) -> widget::Container { .on_press(Message::Menu(Menu::Home)) .width(iced::Length::Units(200)) }; + + let receive_button = if *menu == Menu::Receive { + button::primary(Some(receive_icon()), "Receive") + .on_press(Message::Reload) + .width(iced::Length::Units(200)) + } else { + button::transparent(Some(receive_icon()), "Receive") + .on_press(Message::Menu(Menu::Receive)) + .width(iced::Length::Units(200)) + }; + let settings_button = if *menu == Menu::Settings { button::primary(Some(settings_icon()), "Settings") .on_press(Message::Menu(Menu::Settings)) @@ -51,6 +63,7 @@ pub fn sidebar(menu: &Menu) -> widget::Container { .spacing(10), ) .push(home_button) + .push(receive_button) .spacing(20) .height(Length::Fill), ) diff --git a/gui/src/app/view/receive.rs b/gui/src/app/view/receive.rs new file mode 100644 index 00000000..0d931d78 --- /dev/null +++ b/gui/src/app/view/receive.rs @@ -0,0 +1,32 @@ +use iced::{ + pure::{column, row, widget::Button, Element}, + widget::qr_code::{self, QRCode}, + Alignment, +}; + +use crate::ui::{ + component::{button, card, text::*}, + icon, +}; + +use super::message::Message; + +pub fn receive<'a>(address: &'a bitcoin::Address, qr: &'a qr_code::State) -> Element<'a, Message> { + card::simple( + column() + .push(QRCode::new(qr).cell_size(10)) + .push( + row() + .push(text(&address.to_string()).small()) + .push( + Button::new(icon::clipboard_icon()) + .on_press(Message::Clipboard(address.to_string())) + .style(button::Style::TransparentBorder), + ) + .align_items(Alignment::Center), + ) + .align_items(Alignment::Center) + .spacing(20), + ) + .into() +} diff --git a/gui/src/ui/icon.rs b/gui/src/ui/icon.rs index 497bd5af..a769cb00 100644 --- a/gui/src/ui/icon.rs +++ b/gui/src/ui/icon.rs @@ -50,7 +50,7 @@ pub fn connected_device_icon() -> Text { icon('\u{F350}') } -pub fn deposit_icon() -> Text { +pub fn receive_icon() -> Text { icon('\u{F123}') } From ebc239733c3a508f08f5539a862d3029d45c753f Mon Sep 17 00:00:00 2001 From: edouard Date: Mon, 5 Sep 2022 12:19:00 +0200 Subject: [PATCH 4/8] Add sandbox and daemon mock to utils mod --- gui/src/lib.rs | 1 + gui/src/utils/mock.rs | 93 ++++++++++++++++++++++++++++++++++++++++ gui/src/utils/mod.rs | 5 +++ gui/src/utils/sandbox.rs | 51 ++++++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 gui/src/utils/mock.rs create mode 100644 gui/src/utils/mod.rs create mode 100644 gui/src/utils/sandbox.rs diff --git a/gui/src/lib.rs b/gui/src/lib.rs index 36ca6d86..883f95ce 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -3,3 +3,4 @@ pub mod daemon; pub mod installer; pub mod loader; pub mod ui; +pub mod utils; diff --git a/gui/src/utils/mock.rs b/gui/src/utils/mock.rs new file mode 100644 index 00000000..05e3c01d --- /dev/null +++ b/gui/src/utils/mock.rs @@ -0,0 +1,93 @@ +use crate::daemon::{client::Client, DaemonError}; +use minisafe::config::Config; +use serde::{de::DeserializeOwned, Serialize}; +use serde_json::{json, Value}; +use std::fmt::Debug; +use std::sync::{ + mpsc::{channel, Receiver, Sender}, + Mutex, +}; +use std::thread; + +#[derive(Debug)] +pub struct DaemonClient { + transport: Mutex<(Sender, Receiver>)>, +} + +impl Client for DaemonClient { + type Error = DaemonError; + fn request( + &self, + method: &str, + params: Option, + ) -> Result { + let req = json!({"method": method, "params": params}); + let connection = self.transport.lock().expect("Failed to unlock"); + connection + .0 + .send(req) + .expect("Mock client failed to send request"); + connection + .1 + .recv() + .expect("Mock client failed to receive response") + .map(|value| serde_json::from_value(value).unwrap()) + } +} + +pub struct Daemon { + requests: Vec<(Option, Result)>, +} + +impl Daemon { + pub fn new(requests: Vec<(Option, Result)>) -> Self { + Self { requests } + } + + pub fn run(self) -> DaemonClient { + let (client_sender, daemon_receiver) = channel(); + let (daemon_sender, client_receiver) = channel(); + + thread::spawn(move || { + let mut requests = self.requests.into_iter(); + while let Ok(msg) = daemon_receiver.recv() { + let request = requests + .next() + .expect("Mock Daemon must have all requests mocked in the right order"); + if let Some(body) = request.0 { + assert_eq!(body, msg); + } + daemon_sender + .send(request.1) + .expect("Mock daemon failed to send response") + } + // close the daemon -> client channel after + // the client -> daemon channel is closed. + // (client -> daemon channel is closed when DaemonClient is dropped) + drop(daemon_sender); + // Readable with `cargo test -- --nocapture` + println!("The daemon has stopped!"); + }); + + DaemonClient { + transport: Mutex::new((client_sender, client_receiver)), + } + } +} + +pub fn fake_daemon_config() -> Config { + toml::from_str( +r#" +data_dir = "/home/edouard/code/revault/demo/minisafe/datadir" +main_descriptor = "wsh(or_d(pk(tpubDCbK3Ysvk8HjcF6mPyrgMu3KgLiaaP19RjKpNezd8GrbAbNg6v5BtWLaCt8FNm6QkLseopKLf5MNYQFtochDTKHdfgG6iqJ8cqnLNAwtXuP/*),and_v(v:pkh(tpubDDtb2WPYwEWw2WWDV7reLV348iJHw2HmhzvPysKKrJw3hYmvrd4jasyoioVPdKGQqjyaBMEvTn1HvHWDSVqQ6amyyxRZ5YjpPBBGjJ8yu8S/*),older(100))))#459t6xxr" + +[bitcoin_config] +network = "regtest" +poll_interval_secs = 30 + +[bitcoind_config] +addr = "127.0.0.1:9001" +cookie_path = "/home/edouard/code/revault/demo/minisafe/regtest/bcdir1/regtest/.cookie" +"# + ).unwrap() +} diff --git a/gui/src/utils/mod.rs b/gui/src/utils/mod.rs new file mode 100644 index 00000000..084f16d9 --- /dev/null +++ b/gui/src/utils/mod.rs @@ -0,0 +1,5 @@ +#[cfg(test)] +pub mod sandbox; + +#[cfg(test)] +pub mod mock; diff --git a/gui/src/utils/sandbox.rs b/gui/src/utils/sandbox.rs new file mode 100644 index 00000000..4c8defd2 --- /dev/null +++ b/gui/src/utils/sandbox.rs @@ -0,0 +1,51 @@ +use std::sync::Arc; + +use iced_native::command::Action; + +use crate::{ + app::{cache::Cache, message::Message, state::State}, + daemon::Daemon, +}; + +pub struct Sandbox { + state: S, +} + +impl Sandbox { + pub fn new(state: S) -> Self { + return Self { state }; + } + + pub fn state(&self) -> &S { + &self.state + } + + pub async fn update( + mut self, + daemon: Arc, + cache: &Cache, + message: Message, + ) -> Self { + let cmd = self.state.update(daemon.clone(), cache, message); + for action in cmd.actions() { + if let Action::Future(f) = action { + let msg = f.await; + let _cmd = self.state.update(daemon.clone(), cache, msg); + } + } + + self + } + + pub async fn load(mut self, daemon: Arc, cache: &Cache) -> Self { + let cmd = self.state.load(daemon.clone()); + for action in cmd.actions() { + if let Action::Future(f) = action { + let msg = f.await; + self = self.update(daemon.clone(), cache, msg).await; + } + } + + self + } +} From 647b74ebcf789e8df7bf530e83258a3a38286be9 Mon Sep 17 00:00:00 2001 From: edouard Date: Mon, 5 Sep 2022 17:21:59 +0200 Subject: [PATCH 5/8] Add unit test for receive panel state --- gui/src/app/state/mod.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index 18dd4242..6e47ea8e 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -106,3 +106,43 @@ impl From for Box { Box::new(s) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + app::cache::Cache, + daemon::{ + client::{Minisafed, Request}, + model::*, + }, + utils::{ + mock::{fake_daemon_config, Daemon}, + sandbox::Sandbox, + }, + }; + + use bitcoin::Address; + use serde_json::json; + use std::str::FromStr; + + #[tokio::test] + async fn test_receive_panel() { + let addr = + Address::from_str("tb1qkldgvljmjpxrjq2ev5qxe8dvhn0dph9q85pwtfkjeanmwdue2akqj4twxj") + .unwrap(); + let daemon = Daemon::new(vec![( + Some(json!({"method": "getnewaddress", "params": Option::::None})), + Ok(json!(GetAddressResult { + address: addr.clone() + })), + )]); + + let sandbox: Sandbox = Sandbox::new(ReceivePanel::default()); + let client = Arc::new(Minisafed::new(daemon.run(), fake_daemon_config())); + let sandbox = sandbox.load(client, &Cache { blockheight: 0 }).await; + + let panel = sandbox.state(); + assert_eq!(panel.address, Some(addr)); + } +} From f32b4d52d2facf409d73b8e27a67d05e13255373 Mon Sep 17 00:00:00 2001 From: edouard Date: Mon, 5 Sep 2022 18:18:12 +0200 Subject: [PATCH 6/8] daemon: add list_coins method --- gui/src/daemon/client/mod.rs | 4 ++++ gui/src/daemon/embedded.rs | 11 +++++++++++ gui/src/daemon/mod.rs | 2 ++ gui/src/daemon/model.rs | 2 +- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/gui/src/daemon/client/mod.rs b/gui/src/daemon/client/mod.rs index 94d507d4..4db519a9 100644 --- a/gui/src/daemon/client/mod.rs +++ b/gui/src/daemon/client/mod.rs @@ -66,6 +66,10 @@ impl Daemon for Minisafed { fn get_new_address(&self) -> Result { self.call("getnewaddress", Option::::None) } + + fn list_coins(&self) -> Result { + self.call("listcoins", Option::::None) + } } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/gui/src/daemon/embedded.rs b/gui/src/daemon/embedded.rs index cca381b7..924c1699 100644 --- a/gui/src/daemon/embedded.rs +++ b/gui/src/daemon/embedded.rs @@ -80,4 +80,15 @@ impl Daemon for EmbeddedDaemon { .control .get_new_address()) } + + fn list_coins(&self) -> Result { + Ok(self + .handle + .as_ref() + .ok_or(DaemonError::NoAnswer)? + .lock() + .unwrap() + .control + .list_coins()) + } } diff --git a/gui/src/daemon/mod.rs b/gui/src/daemon/mod.rs index 37f8f1d4..32e6ab40 100644 --- a/gui/src/daemon/mod.rs +++ b/gui/src/daemon/mod.rs @@ -47,4 +47,6 @@ pub trait Daemon: Debug { fn get_info(&self) -> Result; fn get_new_address(&self) -> Result; + + fn list_coins(&self) -> Result; } diff --git a/gui/src/daemon/model.rs b/gui/src/daemon/model.rs index dabccf2d..ce1401d7 100644 --- a/gui/src/daemon/model.rs +++ b/gui/src/daemon/model.rs @@ -1 +1 @@ -pub use minisafe::commands::{GetAddressResult, GetInfoResult}; +pub use minisafe::commands::{GetAddressResult, GetInfoResult, ListCoinsResult}; From 157d9b39333e9eae1b1fc231bc6abd2f6661bca6 Mon Sep 17 00:00:00 2001 From: edouard Date: Tue, 6 Sep 2022 16:19:02 +0200 Subject: [PATCH 7/8] Add coins to cache --- gui/src/app/cache.rs | 12 ++++++++++++ gui/src/app/message.rs | 6 +++++- gui/src/app/mod.rs | 22 ++++++++++++++-------- gui/src/daemon/model.rs | 4 +++- gui/src/loader.rs | 15 +++++++++++---- gui/src/main.rs | 3 ++- 6 files changed, 47 insertions(+), 15 deletions(-) diff --git a/gui/src/app/cache.rs b/gui/src/app/cache.rs index 01636ca5..c403e043 100644 --- a/gui/src/app/cache.rs +++ b/gui/src/app/cache.rs @@ -1,3 +1,15 @@ +use crate::daemon::model::Coin; + pub struct Cache { pub blockheight: i32, + pub coins: Vec, +} + +impl Default for Cache { + fn default() -> Self { + Self { + blockheight: 0, + coins: Vec::new(), + } + } } diff --git a/gui/src/app/message.rs b/gui/src/app/message.rs index c4e2cabc..0c66e434 100644 --- a/gui/src/app/message.rs +++ b/gui/src/app/message.rs @@ -1,6 +1,9 @@ use minisafe::config::Config as DaemonConfig; -use crate::app::{error::Error, view}; +use crate::{ + app::{error::Error, view}, + daemon::model::*, +}; #[derive(Debug)] pub enum Message { @@ -11,4 +14,5 @@ pub enum Message { DaemonConfigLoaded(Result<(), Error>), BlockHeight(Result), ReceiveAddress(Result), + Coins(Result, Error>), } diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index c55a87f2..79c29fbb 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -42,7 +42,7 @@ impl App { config: Config, daemon: Arc, ) -> (App, Command) { - let state: Box = Home {}.into(); + let state: Box = Home::new(&cache.coins).into(); let cmd = state.load(daemon.clone()); ( Self { @@ -62,7 +62,7 @@ impl App { state::SettingsState::new(self.daemon.config().clone(), self.daemon.is_external()) .into() } - menu::Menu::Home => Home {}.into(), + menu::Menu::Home => Home::new(&self.cache.coins).into(), menu::Menu::Receive => ReceivePanel::default().into(), }; self.state.load(self.daemon.clone()) @@ -95,6 +95,18 @@ impl App { } pub fn update(&mut self, message: Message) -> Command { + // Update cache when values are passing by. + // State will handle the error case. + match &message { + Message::Coins(Ok(coins)) => { + self.cache.coins = coins.clone(); + } + Message::BlockHeight(Ok(blockheight)) => { + self.cache.blockheight = blockheight.clone(); + } + _ => {} + }; + match message { Message::Tick => { let daemon = self.daemon.clone(); @@ -108,12 +120,6 @@ impl App { Message::BlockHeight, ) } - Message::BlockHeight(res) => { - if let Ok(blockheight) = res { - self.cache.blockheight = blockheight; - } - Command::none() - } Message::LoadDaemonConfig(cfg) => { let res = self.load_daemon_config(*cfg); self.update(Message::DaemonConfigLoaded(res)) diff --git a/gui/src/daemon/model.rs b/gui/src/daemon/model.rs index ce1401d7..33178c77 100644 --- a/gui/src/daemon/model.rs +++ b/gui/src/daemon/model.rs @@ -1 +1,3 @@ -pub use minisafe::commands::{GetAddressResult, GetInfoResult, ListCoinsResult}; +pub use minisafe::commands::{GetAddressResult, GetInfoResult, ListCoinsEntry, ListCoinsResult}; + +pub type Coin = ListCoinsEntry; diff --git a/gui/src/loader.rs b/gui/src/loader.rs index 5cf275dc..1812a116 100644 --- a/gui/src/loader.rs +++ b/gui/src/loader.rs @@ -39,7 +39,7 @@ enum Step { pub enum Message { Event(iced_native::Event), Syncing(Result), - Synced(GetInfoResult, Arc), + Synced(GetInfoResult, Vec, Arc), Started(Result, Error>), Loaded(Result, Error>), Failure(DaemonError), @@ -118,9 +118,16 @@ impl Loader { Ok(info) => { if (info.sync - 1.0_f64).abs() < f64::EPSILON { let daemon = daemon.clone(); - return Command::perform(async move { (info, daemon) }, |res| { - Message::Synced(res.0, res.1) - }); + return Command::perform( + async move { + let coins = daemon + .list_coins() + .map(|res| res.coins) + .unwrap_or_else(|_| Vec::new()); + (info, coins, daemon) + }, + |res| Message::Synced(res.0, res.1, res.2), + ); } else { *progress = info.sync } diff --git a/gui/src/main.rs b/gui/src/main.rs index e6a7b1ef..349c92b5 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -161,9 +161,10 @@ impl Application for GUI { } } (State::Loader(loader), Message::Load(msg)) => { - if let loader::Message::Synced(info, minisafed) = *msg { + if let loader::Message::Synced(info, coins, minisafed) = *msg { let cache = Cache { blockheight: info.blockheight, + coins, }; let (app, command) = App::new(cache, loader.gui_config.clone(), minisafed); From 7f9fd84beead64ef8fe259f1174e01cbc0dd4cfa Mon Sep 17 00:00:00 2001 From: edouard Date: Tue, 6 Sep 2022 16:46:25 +0200 Subject: [PATCH 8/8] Add balance to panel home --- gui/src/app/state/mod.rs | 37 ++++++++++++++++++++++++++++++------- gui/src/app/view/home.rs | 17 +++++++++++++++++ gui/src/app/view/mod.rs | 1 + 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 gui/src/app/view/home.rs diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index 6e47ea8e..6f2203bd 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -2,15 +2,14 @@ mod settings; use std::sync::Arc; +use bitcoin::Amount; use iced::pure::{column, Element}; use iced::{widget::qr_code, Command, Subscription}; use super::{cache::Cache, error::Error, menu::Menu, message::Message, view}; - +use crate::daemon::{model::Coin, Daemon}; pub use settings::SettingsState; -use crate::daemon::Daemon; - pub trait State { fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message>; fn update( @@ -27,12 +26,23 @@ pub trait State { } } -pub struct Home {} +pub struct Home { + balance: Amount, +} + +impl Home { + pub fn new(coins: &Vec) -> Self { + Self { + balance: Amount::from_sat(coins.iter().map(|coin| coin.amount.as_sat()).sum()), + } + } +} impl State for Home { - fn view<'a>(&self, _cache: &'a Cache) -> Element<'a, view::Message> { - view::dashboard(&Menu::Home, None, column()) + fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> { + view::dashboard(&Menu::Home, None, view::home::home_view(&self.balance)) } + fn update( &mut self, _daemon: Arc, @@ -41,6 +51,19 @@ impl State for Home { ) -> Command { Command::none() } + + fn load(&self, daemon: Arc) -> Command { + let daemon = daemon.clone(); + Command::perform( + async move { + daemon + .list_coins() + .map(|res| res.coins) + .map_err(|e| e.into()) + }, + Message::Coins, + ) + } } impl From for Box { @@ -140,7 +163,7 @@ mod tests { let sandbox: Sandbox = Sandbox::new(ReceivePanel::default()); let client = Arc::new(Minisafed::new(daemon.run(), fake_daemon_config())); - let sandbox = sandbox.load(client, &Cache { blockheight: 0 }).await; + let sandbox = sandbox.load(client, &Cache::default()).await; let panel = sandbox.state(); assert_eq!(panel.address, Some(addr)); diff --git a/gui/src/app/view/home.rs b/gui/src/app/view/home.rs new file mode 100644 index 00000000..17fb1107 --- /dev/null +++ b/gui/src/app/view/home.rs @@ -0,0 +1,17 @@ +use iced::{ + pure::{column, Element}, + Alignment, +}; + +use crate::ui::component::text::*; + +use super::message::Message; + +pub fn home_view<'a>(balance: &'a bitcoin::Amount) -> Element<'a, Message> { + column() + .push(column().padding(40)) + .push(text(&format!("{} BTC", balance.as_btc())).bold().size(50)) + .align_items(Alignment::Center) + .spacing(20) + .into() +} diff --git a/gui/src/app/view/mod.rs b/gui/src/app/view/mod.rs index d5d05d5d..9814bc64 100644 --- a/gui/src/app/view/mod.rs +++ b/gui/src/app/view/mod.rs @@ -1,6 +1,7 @@ mod message; mod warning; +pub mod home; pub mod receive; pub mod settings;