diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index b6292e06..1126f62f 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -2,29 +2,30 @@ mod coins; mod label; mod psbt; mod psbts; +mod receive; mod recovery; mod settings; mod spend; mod transactions; -use std::collections::HashMap; use std::convert::TryInto; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; -use iced::{widget::qr_code, Command, Subscription}; -use liana::miniscript::bitcoin::{Address, Amount, OutPoint}; +use iced::{Command, Subscription}; +use liana::miniscript::bitcoin::{Amount, OutPoint}; use liana_ui::widget::*; use super::{cache::Cache, error::Error, menu::Menu, message::Message, view, wallet::Wallet}; use crate::daemon::{ - model::{remaining_sequence, Coin, HistoryTransaction, LabelItem, Labelled}, + model::{remaining_sequence, Coin, HistoryTransaction, Labelled}, Daemon, }; pub use coins::CoinsPanel; use label::LabelsEdited; pub use psbts::PsbtsPanel; +pub use receive::ReceivePanel; pub use recovery::RecoveryPanel; pub use settings::SettingsState; pub use spend::CreateSpendPanel; @@ -48,6 +49,13 @@ pub trait State { } } +/// redirect to another state with a message menu +pub fn redirect(menu: Menu) -> Command { + Command::perform(async { menu }, |menu| { + Message::View(view::Message::Menu(menu)) + }) +} + pub struct Home { wallet: Arc, balance: Amount, @@ -292,142 +300,3 @@ impl From for Box { Box::new(s) } } - -#[derive(Debug, Default)] -pub struct Addresses { - list: Vec
, - labels: HashMap, -} - -impl Labelled for Addresses { - fn labelled(&self) -> Vec { - self.list - .iter() - .map(|a| LabelItem::Address(a.clone())) - .collect() - } - fn labels(&mut self) -> &mut HashMap { - &mut self.labels - } -} - -#[derive(Default)] -pub struct ReceivePanel { - addresses: Addresses, - labels_edited: LabelsEdited, - qr_code: Option, - warning: Option, -} - -impl State for ReceivePanel { - fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { - view::dashboard( - &Menu::Receive, - cache, - self.warning.as_ref(), - view::receive::receive( - &self.addresses.list, - self.qr_code.as_ref(), - &self.addresses.labels, - self.labels_edited.cache(), - ), - ) - } - fn update( - &mut self, - daemon: Arc, - _cache: &Cache, - message: Message, - ) -> Command { - match message { - Message::View(view::Message::Label(_, _)) | Message::LabelsUpdated(_) => { - match self.labels_edited.update( - daemon, - message, - std::iter::once(&mut self.addresses).map(|a| a as &mut dyn Labelled), - ) { - Ok(cmd) => cmd, - Err(e) => { - self.warning = Some(e); - Command::none() - } - } - } - Message::ReceiveAddress(res) => { - match res { - Ok(address) => { - self.warning = None; - self.qr_code = Some(qr_code::State::new(address.to_qr_uri()).unwrap()); - self.addresses.list.push(address); - } - Err(e) => self.warning = Some(e), - } - Command::none() - } - Message::View(view::Message::Next) => self.load(daemon), - _ => Command::none(), - } - } - - fn load(&self, daemon: Arc) -> Command { - let daemon = daemon.clone(); - Command::perform( - async move { - daemon - .get_new_address() - .map(|res| res.address().clone()) - .map_err(|e| e.into()) - }, - Message::ReceiveAddress, - ) - } -} - -impl From for Box { - fn from(s: ReceivePanel) -> Box { - Box::new(s) - } -} - -/// redirect to another state with a message menu -pub fn redirect(menu: Menu) -> Command { - Command::perform(async { menu }, |menu| { - Message::View(view::Message::Menu(menu)) - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - app::cache::Cache, - daemon::{ - client::{Lianad, Request}, - model::*, - }, - utils::{mock::Daemon, sandbox::Sandbox}, - }; - - use liana::miniscript::bitcoin::Address; - use serde_json::json; - use std::str::FromStr; - - #[tokio::test] - async fn test_receive_panel() { - let addr = - Address::from_str("tb1qkldgvljmjpxrjq2ev5qxe8dvhn0dph9q85pwtfkjeanmwdue2akqj4twxj") - .unwrap() - .assume_checked(); - let daemon = Daemon::new(vec![( - Some(json!({"method": "getnewaddress", "params": Option::::None})), - Ok(json!(GetAddressResult::new(addr.clone()))), - )]); - - let sandbox: Sandbox = Sandbox::new(ReceivePanel::default()); - let client = Arc::new(Lianad::new(daemon.run())); - let sandbox = sandbox.load(client, &Cache::default()).await; - - let panel = sandbox.state(); - assert_eq!(panel.addresses.list, vec![addr]); - } -} diff --git a/gui/src/app/state/receive.rs b/gui/src/app/state/receive.rs new file mode 100644 index 00000000..e0e419b9 --- /dev/null +++ b/gui/src/app/state/receive.rs @@ -0,0 +1,153 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use iced::{widget::qr_code, Command, Subscription}; +use liana::miniscript::bitcoin::Address; +use liana_ui::widget::*; + +use crate::app::{ + cache::Cache, + error::Error, + menu::Menu, + message::Message, + state::{label::LabelsEdited, State}, + view, + wallet::Wallet, +}; + +use crate::daemon::{ + model::{LabelItem, Labelled}, + Daemon, +}; + +#[derive(Debug, Default)] +pub struct Addresses { + list: Vec
, + labels: HashMap, +} + +impl Labelled for Addresses { + fn labelled(&self) -> Vec { + self.list + .iter() + .map(|a| LabelItem::Address(a.clone())) + .collect() + } + fn labels(&mut self) -> &mut HashMap { + &mut self.labels + } +} + +#[derive(Default)] +pub struct ReceivePanel { + addresses: Addresses, + labels_edited: LabelsEdited, + qr_code: Option, + warning: Option, +} + +impl State for ReceivePanel { + fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { + view::dashboard( + &Menu::Receive, + cache, + self.warning.as_ref(), + view::receive::receive( + &self.addresses.list, + self.qr_code.as_ref(), + &self.addresses.labels, + self.labels_edited.cache(), + ), + ) + } + fn update( + &mut self, + daemon: Arc, + _cache: &Cache, + message: Message, + ) -> Command { + match message { + Message::View(view::Message::Label(_, _)) | Message::LabelsUpdated(_) => { + match self.labels_edited.update( + daemon, + message, + std::iter::once(&mut self.addresses).map(|a| a as &mut dyn Labelled), + ) { + Ok(cmd) => cmd, + Err(e) => { + self.warning = Some(e); + Command::none() + } + } + } + Message::ReceiveAddress(res) => { + match res { + Ok(address) => { + self.warning = None; + self.qr_code = Some(qr_code::State::new(address.to_qr_uri()).unwrap()); + self.addresses.list.push(address); + } + Err(e) => self.warning = Some(e), + } + Command::none() + } + Message::View(view::Message::Next) => self.load(daemon), + _ => Command::none(), + } + } + + fn load(&self, daemon: Arc) -> Command { + let daemon = daemon.clone(); + Command::perform( + async move { + daemon + .get_new_address() + .map(|res| res.address().clone()) + .map_err(|e| e.into()) + }, + Message::ReceiveAddress, + ) + } +} + +impl From for Box { + fn from(s: ReceivePanel) -> Box { + Box::new(s) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + app::cache::Cache, + daemon::{ + client::{Lianad, Request}, + model::*, + }, + utils::{mock::Daemon, sandbox::Sandbox}, + }; + + use liana::miniscript::bitcoin::Address; + use serde_json::json; + use std::str::FromStr; + + #[tokio::test] + async fn test_receive_panel() { + let addr = + Address::from_str("tb1qkldgvljmjpxrjq2ev5qxe8dvhn0dph9q85pwtfkjeanmwdue2akqj4twxj") + .unwrap() + .assume_checked(); + let daemon = Daemon::new(vec![( + Some(json!({"method": "getnewaddress", "params": Option::::None})), + Ok(json!(GetAddressResult::new(addr.clone()))), + )]); + + let sandbox: Sandbox = Sandbox::new(ReceivePanel::default()); + let client = Arc::new(Lianad::new(daemon.run())); + let sandbox = sandbox.load(client, &Cache::default()).await; + + let panel = sandbox.state(); + assert_eq!(panel.addresses.list, vec![addr]); + } +}