From 86eddb28c625a96a0e9b3db7cc80dbb3eb0813f5 Mon Sep 17 00:00:00 2001 From: edouard Date: Wed, 14 Sep 2022 18:01:41 +0200 Subject: [PATCH 1/2] add coins panel to gui --- gui/src/app/menu.rs | 1 + gui/src/app/mod.rs | 3 +- gui/src/app/state/coins.rs | 79 ++++++++++++++++++++++++++++++++++ gui/src/app/state/mod.rs | 3 ++ gui/src/app/view/coins.rs | 61 ++++++++++++++++++++++++++ gui/src/app/view/message.rs | 2 + gui/src/app/view/mod.rs | 16 ++++++- gui/src/ui/component/badge.rs | 51 +++++++++++++++++++++- gui/src/ui/component/button.rs | 10 ++--- gui/src/ui/icon.rs | 4 ++ 10 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 gui/src/app/state/coins.rs create mode 100644 gui/src/app/view/coins.rs diff --git a/gui/src/app/menu.rs b/gui/src/app/menu.rs index ddeb4b09..8f1ba2f8 100644 --- a/gui/src/app/menu.rs +++ b/gui/src/app/menu.rs @@ -3,4 +3,5 @@ pub enum Menu { Home, Receive, Settings, + Coins, } diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index fa33403e..53fc656e 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, ReceivePanel, State}; +use state::{CoinsPanel, Home, ReceivePanel, State}; use crate::{ app::{cache::Cache, error::Error, menu::Menu}, @@ -63,6 +63,7 @@ impl App { .into() } menu::Menu::Home => Home::new(&self.cache.coins).into(), + menu::Menu::Coins => CoinsPanel::new(&self.cache.coins).into(), menu::Menu::Receive => 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 new file mode 100644 index 00000000..14c66770 --- /dev/null +++ b/gui/src/app/state/coins.rs @@ -0,0 +1,79 @@ +use std::sync::Arc; + +use iced::pure::Element; +use iced::Command; + +use crate::{ + app::{cache::Cache, error::Error, menu::Menu, message::Message, state::State, view}, + daemon::{model::Coin, Daemon}, +}; + +pub struct CoinsPanel { + coins: Vec, + selected_coin: Option, + warning: Option, +} + +impl CoinsPanel { + pub fn new(coins: &[Coin]) -> Self { + Self { + coins: coins.to_owned(), + selected_coin: None, + warning: None, + } + } +} + +impl State for CoinsPanel { + fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> { + view::dashboard( + &Menu::Coins, + self.warning.as_ref(), + view::coins::coins_view(&self.coins), + ) + } + + fn update( + &mut self, + _daemon: Arc, + _cache: &Cache, + message: Message, + ) -> Command { + match message { + Message::Coins(res) => match res { + Err(e) => self.warning = Some(e), + Ok(coins) => { + self.warning = None; + self.coins = coins; + } + }, + Message::View(view::Message::Close) => { + self.selected_coin = None; + } + Message::View(view::Message::Select(i)) => { + self.selected_coin = Some(i); + } + _ => {} + }; + 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 { + fn from(s: CoinsPanel) -> Box { + Box::new(s) + } +} diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index 74ff0867..23992672 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -1,3 +1,4 @@ +mod coins; mod settings; use std::sync::Arc; @@ -8,6 +9,8 @@ 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 coins::CoinsPanel; pub use settings::SettingsState; pub trait State { diff --git a/gui/src/app/view/coins.rs b/gui/src/app/view/coins.rs new file mode 100644 index 00000000..a6e52c34 --- /dev/null +++ b/gui/src/app/view/coins.rs @@ -0,0 +1,61 @@ +use iced::{ + pure::{button, column, container, row, Element}, + Alignment, Length, +}; + +use crate::ui::component::{badge, button::Style, card, text::*}; + +use crate::{app::view::message::Message, daemon::model::Coin}; + +pub fn coins_view<'a>(coins: &[Coin]) -> Element<'a, Message> { + column() + .push( + container( + row() + .push(text(&format!(" {}", coins.len())).bold()) + .push(text(" coins")), + ) + .width(Length::Fill), + ) + .push( + column().spacing(10).push( + coins + .iter() + .enumerate() + .fold(column().spacing(10), |col, (i, coin)| { + col.push(coin_list_view(i, coin)) + }), + ), + ) + .align_items(Alignment::Center) + .spacing(20) + .into() +} + +fn coin_list_view<'a>(i: usize, coin: &Coin) -> Element<'a, Message> { + container( + button( + row() + .push( + row() + .push(badge::coin()) + .push(text(&format!("block: {}", coin.block_height.unwrap_or(0))).small()) + .spacing(10) + .align_items(Alignment::Center) + .width(Length::Fill), + ) + .push( + text(&format!("{} BTC", coin.amount.as_btc())) + .bold() + .width(Length::Shrink), + ) + .align_items(Alignment::Center) + .spacing(20), + ) + .padding(10) + .on_press(Message::Select(i)) + .style(Style::TransparentBorder), + ) + .style(card::SimpleCardStyle) + .into() +} diff --git a/gui/src/app/view/message.rs b/gui/src/app/view/message.rs index 30910eab..ba779777 100644 --- a/gui/src/app/view/message.rs +++ b/gui/src/app/view/message.rs @@ -5,6 +5,8 @@ pub enum Message { Reload, Clipboard(String), Menu(Menu), + Close, + Select(usize), Settings(usize, SettingsMessage), } diff --git a/gui/src/app/view/mod.rs b/gui/src/app/view/mod.rs index 9814bc64..86037f9f 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 coins; pub mod home; pub mod receive; pub mod settings; @@ -16,7 +17,7 @@ use iced::{ use crate::ui::{ color, component::{button, separation, text::*}, - icon::{home_icon, receive_icon, settings_icon}, + icon::{coin_icon, home_icon, receive_icon, settings_icon}, }; use crate::app::{error::Error, menu::Menu}; @@ -32,6 +33,16 @@ pub fn sidebar(menu: &Menu) -> widget::Container { .width(iced::Length::Units(200)) }; + let coins_button = if *menu == Menu::Coins { + button::primary(Some(coin_icon()), "Coins") + .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)) + }; + let receive_button = if *menu == Menu::Receive { button::primary(Some(receive_icon()), "Receive") .on_press(Message::Reload) @@ -64,8 +75,9 @@ pub fn sidebar(menu: &Menu) -> widget::Container { .spacing(10), ) .push(home_button) + .push(coins_button) .push(receive_button) - .spacing(20) + .spacing(15) .height(Length::Fill), ) .push(container(settings_button).height(Length::Shrink)), diff --git a/gui/src/ui/component/badge.rs b/gui/src/ui/component/badge.rs index 88a205cf..9a793fb1 100644 --- a/gui/src/ui/component/badge.rs +++ b/gui/src/ui/component/badge.rs @@ -3,7 +3,7 @@ use iced::{ Length, }; -use crate::ui::color; +use crate::ui::{color, icon}; pub enum Style { Standard, @@ -68,3 +68,52 @@ impl<'a, Message: 'a, S: 'a + widget::container::StyleSheet> From> .into() } } + +pub struct ReceiveStyle; +impl widget::container::StyleSheet for ReceiveStyle { + fn style(&self) -> widget::container::Style { + widget::container::Style { + border_radius: 40.0, + background: color::BACKGROUND.into(), + ..widget::container::Style::default() + } + } +} + +pub fn receive() -> widget::container::Container<'static, T> { + container(icon::receive_icon().width(Length::Units(20))) + .width(Length::Units(40)) + .height(Length::Units(40)) + .style(ReceiveStyle) + .center_x() + .center_y() +} + +pub struct SpendStyle; +impl widget::container::StyleSheet for SpendStyle { + fn style(&self) -> widget::container::Style { + widget::container::Style { + border_radius: 40.0, + background: color::BACKGROUND.into(), + ..widget::container::Style::default() + } + } +} + +pub fn spend() -> widget::container::Container<'static, T> { + container(icon::send_icon().width(Length::Units(20))) + .width(Length::Units(40)) + .height(Length::Units(40)) + .style(ReceiveStyle) + .center_x() + .center_y() +} + +pub fn coin() -> widget::container::Container<'static, T> { + container(icon::coin_icon().width(Length::Units(20))) + .width(Length::Units(40)) + .height(Length::Units(40)) + .style(ReceiveStyle) + .center_x() + .center_y() +} diff --git a/gui/src/ui/component/button.rs b/gui/src/ui/component/button.rs index 671efabb..70186a7b 100644 --- a/gui/src/ui/component/button.rs +++ b/gui/src/ui/component/button.rs @@ -60,7 +60,7 @@ impl button::StyleSheet for Style { border_radius: 10.0, border_width: 0.0, border_color: Color::TRANSPARENT, - text_color: color::DARK_GREY, + text_color: Color::BLACK, }, } } @@ -77,18 +77,18 @@ impl button::StyleSheet for Style { }, Style::Transparent => button::Style { shadow_offset: Vector::default(), - background: color::FOREGROUND.into(), + background: color::BACKGROUND.into(), border_radius: 10.0, border_width: 0.0, border_color: Color::TRANSPARENT, - text_color: color::DARK_GREY, + text_color: Color::BLACK, }, Style::TransparentBorder => button::Style { shadow_offset: Vector::default(), background: Color::TRANSPARENT.into(), border_radius: 10.0, - border_width: 2.0, - border_color: Color::TRANSPARENT, + border_width: 1.0, + border_color: Color::BLACK, text_color: Color::BLACK, }, } diff --git a/gui/src/ui/icon.rs b/gui/src/ui/icon.rs index a769cb00..f06196c8 100644 --- a/gui/src/ui/icon.rs +++ b/gui/src/ui/icon.rs @@ -66,6 +66,10 @@ pub fn vaults_icon() -> Text { icon('\u{F1C7}') } +pub fn coin_icon() -> Text { + icon('\u{F567}') +} + pub fn settings_icon() -> Text { icon('\u{F3E5}') } From b849f9bb3d8577f6a0c52e6903842ab72d602583 Mon Sep 17 00:00:00 2001 From: edouard Date: Tue, 18 Oct 2022 17:58:38 +0200 Subject: [PATCH 2/2] Update minisafe master --- gui/Cargo.lock | 3 ++- gui/src/installer/config.rs | 4 ++-- gui/src/installer/step/mod.rs | 12 +++++++----- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/gui/Cargo.lock b/gui/Cargo.lock index 19bb550e..6dd21646 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -1390,9 +1390,10 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "minisafe" version = "0.0.1" -source = "git+https://github.com/revault/minisafe?branch=master#4a802890634f60d77e4da5a56f9189024ef99500" +source = "git+https://github.com/revault/minisafe?branch=master#9bb20303e71cd95528b39e0aa7c8d44f3e61d1c2" dependencies = [ "backtrace", + "base64", "dirs", "fern", "jsonrpc", diff --git a/gui/src/installer/config.rs b/gui/src/installer/config.rs index b0aa9f05..b3daaec8 100644 --- a/gui/src/installer/config.rs +++ b/gui/src/installer/config.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; use bitcoin::Network; use minisafe::{ config::{BitcoinConfig, BitcoindConfig, Config as MinisafeConfig}, - miniscript::{Descriptor, DescriptorPublicKey}, + descriptors::InheritanceDescriptor, }; use serde::Serialize; @@ -14,7 +14,7 @@ use std::{net::SocketAddr, path::PathBuf, time::Duration}; #[derive(Debug, Clone, Serialize)] pub struct Config { #[serde(serialize_with = "serialize_option_to_string")] - pub main_descriptor: Option>, + pub main_descriptor: Option, pub bitcoin_config: BitcoinConfig, /// Everything we need to know to talk to bitcoind pub bitcoind_config: BitcoindConfig, diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index e7aaca04..e5e06995 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use iced::pure::Element; use minisafe::{ - descriptors::inheritance_descriptor, + descriptors::InheritanceDescriptor, miniscript::descriptor::{Descriptor, DescriptorPublicKey}, }; @@ -153,9 +153,7 @@ impl Step for DefineDescriptor { } false } else if !self.imported_descriptor.value.is_empty() { - if let Ok(desc) = - Descriptor::::from_str(&self.imported_descriptor.value) - { + if let Ok(desc) = InheritanceDescriptor::from_str(&self.imported_descriptor.value) { config.main_descriptor = Some(desc); true } else { @@ -176,7 +174,11 @@ impl Step for DefineDescriptor { return false; } - match inheritance_descriptor(user_key.unwrap(), heir_key.unwrap(), sequence.unwrap()) { + match InheritanceDescriptor::new( + user_key.unwrap(), + heir_key.unwrap(), + sequence.unwrap(), + ) { Ok(desc) => { config.main_descriptor = Some(desc); true