From 95aa8a152993a43dc2451c45efcfe73d68f4c45b Mon Sep 17 00:00:00 2001 From: edouard Date: Thu, 27 Oct 2022 10:52:29 +0200 Subject: [PATCH] Add spend txs to app cache --- gui/src/app/cache.rs | 3 +- gui/src/app/menu.rs | 1 + gui/src/app/mod.rs | 1 + gui/src/app/state/coins.rs | 3 +- gui/src/app/state/mod.rs | 14 +++- gui/src/app/state/settings.rs | 1 + gui/src/app/view/mod.rs | 147 +++++++++++++++++++++++++++++++--- gui/src/app/view/settings.rs | 4 +- gui/src/daemon/client/mod.rs | 4 + gui/src/daemon/embedded.rs | 11 +++ gui/src/daemon/mod.rs | 2 + gui/src/daemon/model.rs | 6 +- gui/src/loader.rs | 15 +++- gui/src/main.rs | 3 +- gui/src/ui/component/badge.rs | 24 ++++++ 15 files changed, 216 insertions(+), 23 deletions(-) diff --git a/gui/src/app/cache.rs b/gui/src/app/cache.rs index 39ede414..146afeba 100644 --- a/gui/src/app/cache.rs +++ b/gui/src/app/cache.rs @@ -1,7 +1,8 @@ -use crate::daemon::model::Coin; +use crate::daemon::model::{Coin, SpendTx}; #[derive(Default)] pub struct Cache { pub blockheight: i32, pub coins: Vec, + pub spend_txs: Vec, } diff --git a/gui/src/app/menu.rs b/gui/src/app/menu.rs index 8f1ba2f8..1fca2c30 100644 --- a/gui/src/app/menu.rs +++ b/gui/src/app/menu.rs @@ -2,6 +2,7 @@ pub enum Menu { Home, Receive, + Spend, Settings, Coins, } diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index 53fc656e..a8593d4a 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -65,6 +65,7 @@ impl App { menu::Menu::Home => Home::new(&self.cache.coins).into(), menu::Menu::Coins => CoinsPanel::new(&self.cache.coins).into(), menu::Menu::Receive => ReceivePanel::default().into(), + menu::Menu::Spend => ReceivePanel::default().into(), }; self.state.load(self.daemon.clone()) } diff --git a/gui/src/app/state/coins.rs b/gui/src/app/state/coins.rs index 14c66770..0b5123c5 100644 --- a/gui/src/app/state/coins.rs +++ b/gui/src/app/state/coins.rs @@ -25,9 +25,10 @@ impl CoinsPanel { } impl State for CoinsPanel { - fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> { + fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { view::dashboard( &Menu::Coins, + cache, self.warning.as_ref(), view::coins::coins_view(&self.coins), ) diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index 3160de69..2a8f0b65 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -42,8 +42,13 @@ impl Home { } impl State for Home { - fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> { - view::dashboard(&Menu::Home, None, view::home::home_view(&self.balance)) + fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { + view::dashboard( + &Menu::Home, + cache, + None, + view::home::home_view(&self.balance), + ) } fn update( @@ -83,15 +88,16 @@ pub struct ReceivePanel { } impl State for ReceivePanel { - fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> { + fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { if let Some(address) = &self.address { view::dashboard( &Menu::Receive, + cache, self.warning.as_ref(), view::receive::receive(address, self.qr_code.as_ref().unwrap()), ) } else { - view::dashboard(&Menu::Receive, self.warning.as_ref(), column()) + view::dashboard(&Menu::Receive, cache, self.warning.as_ref(), column()) } } fn update( diff --git a/gui/src/app/state/settings.rs b/gui/src/app/state/settings.rs index 3241703b..d9a54b25 100644 --- a/gui/src/app/state/settings.rs +++ b/gui/src/app/state/settings.rs @@ -103,6 +103,7 @@ impl State for SettingsState { 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( + cache, self.warning.as_ref(), self.settings .iter() diff --git a/gui/src/app/view/mod.rs b/gui/src/app/view/mod.rs index 86037f9f..4627e1b1 100644 --- a/gui/src/app/view/mod.rs +++ b/gui/src/app/view/mod.rs @@ -16,13 +16,14 @@ use iced::{ use crate::ui::{ color, - component::{button, separation, text::*}, - icon::{coin_icon, home_icon, receive_icon, settings_icon}, + component::{badge, button, separation, text::*}, + icon::{coin_icon, home_icon, receive_icon, send_icon, settings_icon}, + util::Collection, }; -use crate::app::{error::Error, menu::Menu}; +use crate::app::{cache::Cache, error::Error, menu::Menu}; -pub fn sidebar(menu: &Menu) -> widget::Container { +pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Message> { let home_button = if *menu == Menu::Home { button::primary(Some(home_icon()), "Home") .on_press(Message::Reload) @@ -34,13 +35,131 @@ pub fn sidebar(menu: &Menu) -> widget::Container { }; let coins_button = if *menu == Menu::Coins { - button::primary(Some(coin_icon()), "Coins") - .on_press(Message::Reload) - .width(iced::Length::Units(200)) + iced::pure::widget::button::Button::new( + container( + row() + .push( + row() + .push(coin_icon()) + .push(text("Coins")) + .spacing(10) + .width(iced::Length::Fill) + .align_items(iced::Alignment::Center), + ) + .push( + container(text(&format!(" {} ", cache.coins.len())).small().bold()) + .style(badge::PillStyle::InversePrimary), + ) + .spacing(10) + .width(iced::Length::Fill) + .align_items(iced::Alignment::Center), + ) + .width(iced::Length::Fill) + .padding(5) + .center_x(), + ) + .style(button::Style::Primary) + .on_press(Message::Reload) + .width(iced::Length::Units(200)) } else { - button::transparent(Some(coin_icon()), "Coins") - .on_press(Message::Menu(Menu::Coins)) - .width(iced::Length::Units(200)) + iced::pure::widget::button::Button::new( + container( + row() + .push( + row() + .push(coin_icon()) + .push(text("Coins")) + .spacing(10) + .width(iced::Length::Fill) + .align_items(iced::Alignment::Center), + ) + .push( + container(text(&format!(" {} ", cache.coins.len())).small().bold()) + .style(badge::PillStyle::Primary), + ) + .spacing(10) + .width(iced::Length::Fill) + .align_items(iced::Alignment::Center), + ) + .width(iced::Length::Fill) + .padding(5) + .center_x(), + ) + .style(button::Style::Transparent) + .on_press(Message::Menu(Menu::Coins)) + .width(iced::Length::Units(200)) + }; + + let spend_button = if *menu == Menu::Spend { + iced::pure::widget::button::Button::new( + container( + row() + .push( + row() + .push(send_icon()) + .push(text("Send")) + .spacing(10) + .width(iced::Length::Fill) + .align_items(iced::Alignment::Center), + ) + .push_maybe(if cache.spend_txs.is_empty() { + None + } else { + Some( + container( + text(&format!(" {} ", cache.spend_txs.len())) + .small() + .bold(), + ) + .style(badge::PillStyle::InversePrimary), + ) + }) + .spacing(10) + .width(iced::Length::Fill) + .align_items(iced::Alignment::Center), + ) + .width(iced::Length::Fill) + .padding(5) + .center_x(), + ) + .style(button::Style::Primary) + .on_press(Message::Reload) + .width(iced::Length::Units(200)) + } else { + iced::pure::widget::button::Button::new( + container( + row() + .push( + row() + .push(coin_icon()) + .push(text("Send")) + .spacing(10) + .width(iced::Length::Fill) + .align_items(iced::Alignment::Center), + ) + .push_maybe(if cache.spend_txs.is_empty() { + None + } else { + Some( + container( + text(&format!(" {} ", cache.spend_txs.len())) + .small() + .bold(), + ) + .style(badge::PillStyle::Primary), + ) + }) + .spacing(10) + .width(iced::Length::Fill) + .align_items(iced::Alignment::Center), + ) + .width(iced::Length::Fill) + .padding(5) + .center_x(), + ) + .style(button::Style::Transparent) + .on_press(Message::Menu(Menu::Spend)) + .width(iced::Length::Units(200)) }; let receive_button = if *menu == Menu::Receive { @@ -76,6 +195,7 @@ pub fn sidebar(menu: &Menu) -> widget::Container { ) .push(home_button) .push(coins_button) + .push(spend_button) .push(receive_button) .spacing(15) .height(Length::Fill), @@ -99,11 +219,16 @@ impl widget::container::StyleSheet for SidebarStyle { pub fn dashboard<'a, T: Into>>( menu: &'a Menu, + cache: &'a Cache, warning: Option<&Error>, content: T, ) -> Element<'a, Message> { row() - .push(sidebar(menu).width(Length::Shrink).height(Length::Fill)) + .push( + sidebar(menu, cache) + .width(Length::Shrink) + .height(Length::Fill), + ) .push( column().push(warn(warning)).push( main_section(container(scrollable(content))) diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index 0767418b..a8f3223b 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -12,7 +12,7 @@ use super::{ }; use crate::{ - app::{error::Error, menu::Menu}, + app::{cache::Cache, error::Error, menu::Menu}, ui::{ color, component::{badge, button, card, form, separation, text::*}, @@ -21,11 +21,13 @@ use crate::{ }; pub fn list<'a>( + cache: &'a Cache, warning: Option<&Error>, settings: Vec>, ) -> Element<'a, Message> { dashboard( &Menu::Settings, + cache, warning, widget::Column::with_children(settings).spacing(20), ) diff --git a/gui/src/daemon/client/mod.rs b/gui/src/daemon/client/mod.rs index 4db519a9..a378e0ab 100644 --- a/gui/src/daemon/client/mod.rs +++ b/gui/src/daemon/client/mod.rs @@ -70,6 +70,10 @@ impl Daemon for Minisafed { fn list_coins(&self) -> Result { self.call("listcoins", Option::::None) } + + fn list_spend_txs(&self) -> Result { + self.call("listspend", Option::::None) + } } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/gui/src/daemon/embedded.rs b/gui/src/daemon/embedded.rs index 924c1699..1ec3da82 100644 --- a/gui/src/daemon/embedded.rs +++ b/gui/src/daemon/embedded.rs @@ -91,4 +91,15 @@ impl Daemon for EmbeddedDaemon { .control .list_coins()) } + + fn list_spend_txs(&self) -> Result { + Ok(self + .handle + .as_ref() + .ok_or(DaemonError::NoAnswer)? + .lock() + .unwrap() + .control + .list_spend()) + } } diff --git a/gui/src/daemon/mod.rs b/gui/src/daemon/mod.rs index 32e6ab40..7feec6a1 100644 --- a/gui/src/daemon/mod.rs +++ b/gui/src/daemon/mod.rs @@ -49,4 +49,6 @@ pub trait Daemon: Debug { fn get_new_address(&self) -> Result; fn list_coins(&self) -> Result; + + fn list_spend_txs(&self) -> Result; } diff --git a/gui/src/daemon/model.rs b/gui/src/daemon/model.rs index 33178c77..43e0ff84 100644 --- a/gui/src/daemon/model.rs +++ b/gui/src/daemon/model.rs @@ -1,3 +1,7 @@ -pub use minisafe::commands::{GetAddressResult, GetInfoResult, ListCoinsEntry, ListCoinsResult}; +pub use minisafe::commands::{ + GetAddressResult, GetInfoResult, ListCoinsEntry, ListCoinsResult, ListSpendEntry, + ListSpendResult, +}; pub type Coin = ListCoinsEntry; +pub type SpendTx = ListSpendEntry; diff --git a/gui/src/loader.rs b/gui/src/loader.rs index 89988c66..e8123f06 100644 --- a/gui/src/loader.rs +++ b/gui/src/loader.rs @@ -42,7 +42,12 @@ enum Step { pub enum Message { Event(iced_native::Event), Syncing(Result), - Synced(GetInfoResult, Vec, Arc), + Synced( + GetInfoResult, + Vec, + Vec, + Arc, + ), Started(Result, Error>), Loaded(Result, Error>), Failure(DaemonError), @@ -127,9 +132,13 @@ impl Loader { .list_coins() .map(|res| res.coins) .unwrap_or_else(|_| Vec::new()); - (info, coins, daemon) + let spend_txs = daemon + .list_spend_txs() + .map(|res| res.spend_txs) + .unwrap_or_else(|_| Vec::new()); + (info, coins, spend_txs, daemon) }, - |res| Message::Synced(res.0, res.1, res.2), + |res| Message::Synced(res.0, res.1, res.2, res.3), ); } else { *progress = info.sync diff --git a/gui/src/main.rs b/gui/src/main.rs index 807ae5d6..c0980818 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -161,10 +161,11 @@ impl Application for GUI { } } (State::Loader(loader), Message::Load(msg)) => { - if let loader::Message::Synced(info, coins, minisafed) = *msg { + if let loader::Message::Synced(info, coins, spend_txs, minisafed) = *msg { let cache = Cache { blockheight: info.blockheight, coins, + spend_txs, }; let (app, command) = App::new(cache, loader.gui_config.clone(), minisafed); diff --git a/gui/src/ui/component/badge.rs b/gui/src/ui/component/badge.rs index 9a793fb1..ae7c8cc7 100644 --- a/gui/src/ui/component/badge.rs +++ b/gui/src/ui/component/badge.rs @@ -117,3 +117,27 @@ pub fn coin() -> widget::container::Container<'static, T> { .center_x() .center_y() } + +pub enum PillStyle { + InversePrimary, + Primary, +} + +impl widget::container::StyleSheet for PillStyle { + fn style(&self) -> widget::container::Style { + match self { + Self::Primary => widget::container::Style { + background: color::PRIMARY.into(), + border_radius: 10.0, + text_color: iced::Color::WHITE.into(), + ..widget::container::Style::default() + }, + Self::InversePrimary => widget::container::Style { + background: color::FOREGROUND.into(), + border_radius: 10.0, + text_color: color::PRIMARY.into(), + ..widget::container::Style::default() + }, + } + } +}