diff --git a/gui/src/app/state/settings/wallet.rs b/gui/src/app/state/settings/wallet.rs index 90d78cf3..3c324064 100644 --- a/gui/src/app/state/settings/wallet.rs +++ b/gui/src/app/state/settings/wallet.rs @@ -284,8 +284,9 @@ impl RegisterWalletModal { } fn load(&self, _daemon: Arc) -> Command { + let wallet = self.wallet.clone(); Command::perform( - list_hws(self.wallet.clone()), + async move { list_hardware_wallets(&wallet).await }, Message::ConnectedHardwareWallets, ) } @@ -359,11 +360,3 @@ async fn update_keys_aliases( Ok(()) } - -async fn list_hws(wallet: Arc) -> Vec { - list_hardware_wallets( - &wallet.hardware_wallets, - Some((&wallet.name, &wallet.main_descriptor.to_string())), - ) - .await -} diff --git a/gui/src/app/state/spend/detail.rs b/gui/src/app/state/spend/detail.rs index 7c100cf9..ef6b9765 100644 --- a/gui/src/app/state/spend/detail.rs +++ b/gui/src/app/state/spend/detail.rs @@ -292,8 +292,9 @@ impl Action for SignAction { } fn load(&self, _daemon: Arc) -> Command { + let wallet = self.wallet.clone(); Command::perform( - list_hws(self.wallet.clone()), + async move { list_hardware_wallets(&wallet).await }, Message::ConnectedHardwareWallets, ) } @@ -385,14 +386,6 @@ impl Action for SignAction { } } -async fn list_hws(wallet: Arc) -> Vec { - list_hardware_wallets( - &wallet.hardware_wallets, - Some((&wallet.name, &wallet.main_descriptor.to_string())), - ) - .await -} - async fn sign_psbt_with_hot_signer( wallet: Arc, psbt: Psbt, diff --git a/gui/src/app/view/hw.rs b/gui/src/app/view/hw.rs index 8ee703b9..e31b0a56 100644 --- a/gui/src/app/view/hw.rs +++ b/gui/src/app/view/hw.rs @@ -1,81 +1,88 @@ -use iced::{widget::tooltip, Alignment, Length}; +use iced::Length; -use liana_ui::{ - color, - component::text::{text, Text}, - icon, theme, - util::Collection, - widget::*, -}; +use liana_ui::{component::hw, theme, widget::*}; use crate::{app::view::message::*, hw::HardwareWallet}; -pub fn hw_list_view<'a>( +pub fn hw_list_view( i: usize, - hw: &'a HardwareWallet, + hw: &HardwareWallet, chosen: bool, processing: bool, - status: Option<&'a str>, -) -> Element<'a, Message> { - let mut bttn = Button::new( - Row::new() - .push( - Column::new() - .push(text(format!("{}", hw.kind())).bold()) - .push(match hw { - HardwareWallet::Supported { - fingerprint, - version, - .. - } => Row::new() - .spacing(5) - .push(text(format!("fingerprint: {}", fingerprint)).small()) - .push_maybe( - version - .as_ref() - .map(|v| text(format!("version: {}", v)).small()), - ), - HardwareWallet::Unsupported { - version, message, .. - } => Row::new() - .spacing(5) - .push_maybe( - version - .as_ref() - .map(|v| text(format!("version: {}", v)).small()), - ) - .push( - tooltip::Tooltip::new( - icon::warning_icon(), - message, - tooltip::Position::Bottom, - ) - .style(theme::Container::Card(theme::Card::Simple)), - ), - }) - .spacing(5) - .width(Length::Fill), - ) - .push_maybe(if chosen && processing { - Some( - Column::new() - .push(text("Processing...")) - .push(text("Please check your device").small()), + signed: bool, +) -> Element { + let mut bttn = Button::new(match hw { + HardwareWallet::Supported { + kind, + version, + fingerprint, + alias, + .. + } => { + if chosen && processing { + hw::processing_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) + } else if signed { + hw::sign_success_hardware_wallet( + kind, + version.as_ref(), + fingerprint, + alias.as_ref(), ) } else { - None - }) - .push_maybe(status.map(|v| { - Row::new() - .align_items(Alignment::Center) - .spacing(5) - .push(icon::circle_check_icon().style(color::legacy::SUCCESS)) - .push(text(v).style(color::legacy::SUCCESS)) - })) - .align_items(Alignment::Center) - .width(Length::Fill), - ) - .padding(10) + hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) + } + } + HardwareWallet::Unsupported { version, kind, .. } => { + hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()) + } + }) + .style(theme::Button::Secondary) + .width(Length::Fill); + if !processing { + if let HardwareWallet::Supported { registered, .. } = hw { + if *registered != Some(false) { + bttn = bttn.on_press(Message::SelectHardwareWallet(i)); + } + } + } + Container::new(bttn) + .width(Length::Fill) + .style(theme::Container::Card(theme::Card::Simple)) + .into() +} + +pub fn hw_list_view_for_registration( + i: usize, + hw: &HardwareWallet, + chosen: bool, + processing: bool, + registered: bool, +) -> Element { + let mut bttn = Button::new(match hw { + HardwareWallet::Supported { + kind, + version, + fingerprint, + alias, + .. + } => { + if chosen && processing { + hw::processing_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) + } else if registered { + hw::registration_success_hardware_wallet( + kind, + version.as_ref(), + fingerprint, + alias.as_ref(), + ) + } else { + hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) + } + } + HardwareWallet::Unsupported { version, kind, .. } => { + hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()) + } + }) .style(theme::Button::Secondary) .width(Length::Fill); if !processing && hw.is_supported() { diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index c1e014fd..6a97c720 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -644,18 +644,14 @@ pub fn register_wallet_modal<'a>( .push(hws.iter().enumerate().fold( Column::new().spacing(10), |col, (i, hw)| { - col.push(hw::hw_list_view( + col.push(hw::hw_list_view_for_registration( i, hw, Some(i) == chosen_hw, processing, - hw.fingerprint().and_then(|f| { - if registered.contains(&f) { - Some("Registered") - } else { - None - } - }), + hw.fingerprint() + .map(|f| registered.contains(&f)) + .unwrap_or(false), )) }, )) diff --git a/gui/src/app/view/spend/detail.rs b/gui/src/app/view/spend/detail.rs index a02b66e2..1e3b51b8 100644 --- a/gui/src/app/view/spend/detail.rs +++ b/gui/src/app/view/spend/detail.rs @@ -739,13 +739,9 @@ pub fn sign_action<'a>( hw, Some(i) == chosen_hw, processing, - hw.fingerprint().and_then(|f| { - if signed.contains(&f) { - Some("Signed") - } else { - None - } - }), + hw.fingerprint() + .map(|f| signed.contains(&f)) + .unwrap_or(false), )) }, )) diff --git a/gui/src/hw.rs b/gui/src/hw.rs index 383fe502..d6ad3c47 100644 --- a/gui/src/hw.rs +++ b/gui/src/hw.rs @@ -1,5 +1,6 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; +use crate::app::wallet::Wallet; use async_hwi::{ledger, specter, DeviceKind, Error as HWIError, Version, HWI}; use liana::miniscript::bitcoin::{ hashes::hex::{FromHex, ToHex}, @@ -20,11 +21,16 @@ pub enum HardwareWallet { kind: DeviceKind, fingerprint: Fingerprint, version: Option, + registered: Option, + alias: Option, }, } impl HardwareWallet { - async fn new(device: Arc) -> Result { + async fn new( + device: Arc, + aliases: Option<&HashMap>, + ) -> Result { let kind = device.device_kind(); let fingerprint = device.get_master_fingerprint().await?; let version = device.get_version().await.ok(); @@ -33,6 +39,8 @@ impl HardwareWallet { kind, fingerprint, version, + registered: None, + alias: aliases.and_then(|aliases| aliases.get(&fingerprint).cloned()), }) } @@ -78,13 +86,12 @@ impl HardwareWalletConfig { } } -pub async fn list_hardware_wallets( - cfg: &[HardwareWalletConfig], - wallet: Option<(&str, &str)>, -) -> Vec { +pub async fn list_hardware_wallets(wallet: &Wallet) -> Vec { + let descriptor = wallet.main_descriptor.to_string(); let mut hws: Vec = Vec::new(); match specter::SpecterSimulator::try_connect().await { - Ok(device) => match HardwareWallet::new(Arc::new(device)).await { + Ok(device) => match HardwareWallet::new(Arc::new(device), Some(&wallet.keys_aliases)).await + { Ok(hw) => hws.push(hw), Err(e) => { debug!("{}", e); @@ -96,7 +103,8 @@ pub async fn list_hardware_wallets( } } match specter::Specter::try_connect_serial().await { - Ok(device) => match HardwareWallet::new(Arc::new(device)).await { + Ok(device) => match HardwareWallet::new(Arc::new(device), Some(&wallet.keys_aliases)).await + { Ok(hw) => hws.push(hw), Err(e) => { debug!("{}", e); @@ -110,25 +118,26 @@ pub async fn list_hardware_wallets( match ledger::LedgerSimulator::try_connect().await { Ok(mut device) => match device.get_master_fingerprint().await { Ok(fingerprint) => { - if let Some((name, descriptor)) = wallet { - device - .load_wallet( - name, - descriptor, - cfg.iter() - .find(|cfg| cfg.fingerprint == fingerprint) - .map(|cfg| cfg.token()), - ) - .expect("Configuration must be correct"); - } - let version = device.get_version().await.ok(); if ledger_version_supported(version.as_ref()) { + let mut registered = false; + if let Some(cfg) = wallet + .hardware_wallets + .iter() + .find(|cfg| cfg.fingerprint == fingerprint) + { + device + .load_wallet(&wallet.name, &descriptor, Some(cfg.token())) + .expect("Configuration must be correct"); + registered = true; + } hws.push(HardwareWallet::Supported { kind: device.device_kind(), fingerprint, device: Arc::new(device), version, + registered: Some(registered), + alias: wallet.keys_aliases.get(&fingerprint).cloned(), }); } else { hws.push(HardwareWallet::Unsupported { @@ -160,25 +169,26 @@ pub async fn list_hardware_wallets( match ledger::Ledger::::connect(&api, detected) { Ok(mut device) => match device.get_master_fingerprint().await { Ok(fingerprint) => { - if let Some((name, descriptor)) = wallet { - device - .load_wallet( - name, - descriptor, - cfg.iter() - .find(|cfg| cfg.fingerprint == fingerprint) - .map(|cfg| cfg.token()), - ) - .expect("Configuration must be correct"); - } - let version = device.get_version().await.ok(); if ledger_version_supported(version.as_ref()) { + let mut registered = false; + if let Some(cfg) = wallet + .hardware_wallets + .iter() + .find(|cfg| cfg.fingerprint == fingerprint) + { + device + .load_wallet(&wallet.name, &descriptor, Some(cfg.token())) + .expect("Configuration must be correct"); + registered = true; + } hws.push(HardwareWallet::Supported { kind: device.device_kind(), fingerprint, device: Arc::new(device), version, + registered: Some(registered), + alias: wallet.keys_aliases.get(&fingerprint).cloned(), }); } else { hws.push(HardwareWallet::Unsupported { @@ -222,3 +232,112 @@ fn ledger_version_supported(version: Option<&Version>) -> bool { false } } + +pub async fn list_unregistered_hardware_wallets( + aliases: Option<&HashMap>, +) -> Vec { + let mut hws: Vec = Vec::new(); + match specter::SpecterSimulator::try_connect().await { + Ok(device) => match HardwareWallet::new(Arc::new(device), aliases).await { + Ok(hw) => hws.push(hw), + Err(e) => { + debug!("{}", e); + } + }, + Err(HWIError::DeviceNotFound) => {} + Err(e) => { + debug!("{}", e); + } + } + match specter::Specter::try_connect_serial().await { + Ok(device) => match HardwareWallet::new(Arc::new(device), aliases).await { + Ok(hw) => hws.push(hw), + Err(e) => { + debug!("{}", e); + } + }, + Err(HWIError::DeviceNotFound) => {} + Err(e) => { + debug!("{}", e); + } + } + match ledger::LedgerSimulator::try_connect().await { + Ok(device) => match device.get_master_fingerprint().await { + Ok(fingerprint) => { + let version = device.get_version().await.ok(); + if ledger_version_supported(version.as_ref()) { + hws.push(HardwareWallet::Supported { + kind: device.device_kind(), + fingerprint, + device: Arc::new(device), + version, + registered: None, + alias: aliases.and_then(|aliases| aliases.get(&fingerprint).cloned()), + }); + } else { + hws.push(HardwareWallet::Unsupported { + kind: device.device_kind(), + version, + message: "Minimal supported app version is 2.1.0".to_string(), + }); + } + } + Err(_) => { + hws.push(HardwareWallet::Unsupported { + kind: device.device_kind(), + version: None, + message: "Minimal supported app version is 2.1.0".to_string(), + }); + } + }, + Err(HWIError::DeviceNotFound) => {} + Err(e) => { + debug!("{}", e); + } + } + match ledger::HidApi::new() { + Err(e) => { + debug!("{}", e); + } + Ok(api) => { + for detected in ledger::Ledger::::enumerate(&api) { + match ledger::Ledger::::connect(&api, detected) { + Ok(device) => match device.get_master_fingerprint().await { + Ok(fingerprint) => { + let version = device.get_version().await.ok(); + if ledger_version_supported(version.as_ref()) { + hws.push(HardwareWallet::Supported { + kind: device.device_kind(), + fingerprint, + device: Arc::new(device), + version, + registered: None, + alias: aliases + .and_then(|aliases| aliases.get(&fingerprint).cloned()), + }); + } else { + hws.push(HardwareWallet::Unsupported { + kind: device.device_kind(), + version, + message: "Minimal supported app version is 2.1.0".to_string(), + }); + } + } + Err(_) => { + hws.push(HardwareWallet::Unsupported { + kind: device.device_kind(), + version: None, + message: "Minimal supported app version is 2.1.0".to_string(), + }); + } + }, + Err(HWIError::DeviceNotFound) => {} + Err(e) => { + debug!("{}", e); + } + } + } + } + } + hws +} diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index 1196cf15..e7aa99aa 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -26,7 +26,7 @@ use async_hwi::DeviceKind; use crate::{ app::settings::KeySetting, - hw::{list_hardware_wallets, HardwareWallet}, + hw::{list_unregistered_hardware_wallets, HardwareWallet}, installer::{ message::{self, Message}, step::{Context, Step}, @@ -754,8 +754,9 @@ impl EditXpubModal { } } fn load(&self) -> Command { + let keys_aliases = self.keys_aliases.clone(); Command::perform( - list_hardware_wallets(&[], None), + async move { list_unregistered_hardware_wallets(Some(&keys_aliases)).await }, Message::ConnectedHardwareWallets, ) } @@ -1174,7 +1175,7 @@ impl Step for ParticipateXpub { fn load(&self) -> Command { Command::perform( - list_hardware_wallets(&[], None), + list_unregistered_hardware_wallets(None), Message::ConnectedHardwareWallets, ) } @@ -1301,6 +1302,7 @@ impl From for Box { #[derive(Default)] pub struct RegisterDescriptor { descriptor: Option, + keys_aliases: HashMap, processing: bool, chosen_hw: Option, hws: Vec, @@ -1313,6 +1315,11 @@ pub struct RegisterDescriptor { impl Step for RegisterDescriptor { fn load_context(&mut self, ctx: &Context) { self.descriptor = ctx.descriptor.clone(); + let mut map = HashMap::new(); + for key in ctx.keys.iter().filter(|k| !k.name.is_empty()) { + map.insert(key.master_fingerprint, key.name.clone()); + } + self.keys_aliases = map; } fn update(&mut self, message: Message) -> Command { match message { @@ -1373,8 +1380,9 @@ impl Step for RegisterDescriptor { true } fn load(&self) -> Command { + let keys_aliases = self.keys_aliases.clone(); Command::perform( - list_hardware_wallets(&[], None), + async move { list_unregistered_hardware_wallets(Some(&keys_aliases)).await }, Message::ConnectedHardwareWallets, ) } diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 3a98152d..2fe03071 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -9,7 +9,7 @@ use liana::miniscript::bitcoin; use liana_ui::{ color, component::{ - button, card, collapse, form, separation, + button, card, collapse, form, hw, separation, text::{text, Text}, tooltip, }, @@ -473,67 +473,32 @@ pub fn hardware_wallet_xpubs<'a>( processing: bool, error: Option<&Error>, ) -> Element<'a, Message> { - let mut bttn = Button::new( - Row::new() - .align_items(Alignment::Center) - .push( - Column::new() - .push(text(format!("{}", hw.kind())).bold()) - .push(match hw { - HardwareWallet::Supported { - fingerprint, - version, - .. - } => Row::new() - .spacing(5) - .push(text(format!("fingerprint: {}", fingerprint)).small()) - .push_maybe( - version - .as_ref() - .map(|v| text(format!("version: {}", v)).small()), - ), - HardwareWallet::Unsupported { - version, message, .. - } => Row::new() - .spacing(5) - .push_maybe( - version - .as_ref() - .map(|v| text(format!("version: {}", v)).small()), - ) - .push( - iced::widget::tooltip::Tooltip::new( - icon::warning_icon(), - message, - iced::widget::tooltip::Position::Bottom, - ) - .style(theme::Container::Card(theme::Card::Simple)), - ), - }) - .spacing(5) - .width(Length::Fill), - ) - .push_maybe(error.map(|e| { - iced::widget::tooltip( - Row::new() - .spacing(5) - .align_items(Alignment::Center) - .push(icon::warning_icon().style(color::legacy::ALERT)) - .push(text("An error occured").style(color::legacy::ALERT)), - e, - iced::widget::tooltip::Position::Bottom, - ) - .style(theme::Container::Card(theme::Card::Error)) - })), - ) - .padding(10) - .style(theme::Button::TransparentBorder) + let mut bttn = Button::new(match hw { + HardwareWallet::Supported { + kind, + version, + fingerprint, + alias, + .. + } => { + if processing { + hw::processing_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) + } else { + hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) + } + } + HardwareWallet::Unsupported { version, kind, .. } => { + hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()) + } + }) + .style(theme::Button::Secondary) .width(Length::Fill); if !processing && hw.is_supported() { bttn = bttn.on_press(Message::Select(i)); } Container::new( Column::new() + .push_maybe(error.map(|e| card::warning(e.to_string()).width(Length::Fill))) .push(bttn) .push_maybe(if xpubs.is_empty() { None @@ -1468,66 +1433,29 @@ fn hw_list_view( hw: &HardwareWallet, chosen: bool, processing: bool, - registered: bool, + selected: bool, ) -> Element { - let mut bttn = Button::new( - Row::new() - .push( - Column::new() - .push(text(format!("{}", hw.kind())).bold()) - .push(match hw { - HardwareWallet::Supported { - fingerprint, - version, - .. - } => Row::new() - .spacing(5) - .push(text(format!("fingerprint: {}", fingerprint)).small()) - .push_maybe( - version - .as_ref() - .map(|v| text(format!("version: {}", v)).small()), - ), - HardwareWallet::Unsupported { - version, message, .. - } => Row::new() - .spacing(5) - .push_maybe( - version - .as_ref() - .map(|v| text(format!("version: {}", v)).small()), - ) - .push( - iced::widget::tooltip::Tooltip::new( - icon::warning_icon(), - message, - iced::widget::tooltip::Position::Bottom, - ) - .style(theme::Container::Card(theme::Card::Simple)), - ), - }) - .spacing(5) - .width(Length::Fill), - ) - .push_maybe(if chosen && processing { - Some( - Column::new() - .push(text("Processing...")) - .push(text("Please check your device").small()), - ) + let mut bttn = Button::new(match hw { + HardwareWallet::Supported { + kind, + version, + fingerprint, + alias, + .. + } => { + if chosen && processing { + hw::processing_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) + } else if selected { + hw::selected_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) } else { - None - }) - .push_maybe(if registered { - Some(Column::new().push(icon::circle_check_icon().style(color::legacy::SUCCESS))) - } else { - None - }) - .align_items(Alignment::Center) - .width(Length::Fill), - ) - .padding(10) - .style(theme::Button::TransparentBorder) + hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) + } + } + HardwareWallet::Unsupported { version, kind, .. } => { + hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()) + } + }) + .style(theme::Button::Secondary) .width(Length::Fill); if !processing && hw.is_supported() { bttn = bttn.on_press(Message::Select(i)); diff --git a/gui/ui/examples/design-system/Cargo.toml b/gui/ui/examples/design-system/Cargo.toml index 57c6c389..7ea4e589 100644 --- a/gui/ui/examples/design-system/Cargo.toml +++ b/gui/ui/examples/design-system/Cargo.toml @@ -10,3 +10,5 @@ iced = "0.7" iced_native = "0.8" web-sys = "0.3.61" liana_ui = { path = "../.." } + +[workspace] diff --git a/gui/ui/examples/design-system/src/main.rs b/gui/ui/examples/design-system/src/main.rs index bbbc5b59..f54a08bb 100644 --- a/gui/ui/examples/design-system/src/main.rs +++ b/gui/ui/examples/design-system/src/main.rs @@ -49,6 +49,7 @@ impl Application for DesignSystem { Box::new(section::Overview {}), Box::new(section::Colors {}), Box::new(section::Buttons {}), + Box::new(section::HardwareWallets {}), ], current: 0, }; @@ -111,21 +112,23 @@ impl Application for DesignSystem { fn view(&self) -> Element { let sidebar = container( column![ - [ThemeType::Light, ThemeType::Dark].iter().fold( - column![text("Choose a theme:")].spacing(10), - |column, theme| { - column.push(radio( - format!("{theme:?}"), - *theme, - Some(match self.theme { - theme::Theme::Light => ThemeType::Light, - theme::Theme::Dark => ThemeType::Dark, - theme::Theme::Legacy => ThemeType::Legacy, - }), - Message::ThemeChanged, - )) - }, - ), + [ThemeType::Light, ThemeType::Dark, ThemeType::Legacy] + .iter() + .fold( + column![text("Choose a theme:")].spacing(10), + |column, theme| { + column.push(radio( + format!("{theme:?}"), + *theme, + Some(match self.theme { + theme::Theme::Light => ThemeType::Light, + theme::Theme::Dark => ThemeType::Dark, + theme::Theme::Legacy => ThemeType::Legacy, + }), + Message::ThemeChanged, + )) + }, + ), Space::with_height(Length::Units(100)), self.sections.iter().enumerate().fold( Column::new().spacing(10), diff --git a/gui/ui/examples/design-system/src/section.rs b/gui/ui/examples/design-system/src/section.rs index 930df1ab..f34233c6 100644 --- a/gui/ui/examples/design-system/src/section.rs +++ b/gui/ui/examples/design-system/src/section.rs @@ -3,7 +3,12 @@ use iced::{ widget::{button, column, container, row, Space}, Alignment, Length, }; -use liana_ui::{color, component::text::*, theme, widget::Element}; +use liana_ui::{ + color, + component::{hw, text::*}, + theme, + widget::Element, +}; use super::{Message, Section}; @@ -102,3 +107,94 @@ fn button_row(style: theme::Button, label: &'static str) -> Element { .on_press(Message::Ignore) .into() } + +pub struct HardwareWallets {} + +impl Section for HardwareWallets { + fn title(&self) -> &'static str { + "Hardware wallets" + } + + fn view(&self) -> Element { + column![ + text(self.title()).bold().size(50), + column![ + button( + hw::supported_hardware_wallet( + "ledger", + Some("v2.1.0"), + "f123de", + None:: + ) + .width(Length::Units(500)) + ) + .on_press(Message::Ignore) + .style(theme::Button::Secondary), + button( + hw::supported_hardware_wallet( + "ledger", + Some("v2.1.0"), + "f123de", + Some("Edouard key") + ) + .width(Length::Units(500)) + ) + .on_press(Message::Ignore) + .style(theme::Button::Secondary), + button( + hw::unregistered_hardware_wallet( + "ledger", + Some("v2.1.0"), + "f123de", + Some("Edouard key") + ) + .width(Length::Units(500)) + ) + .on_press(Message::Ignore) + .style(theme::Button::Secondary), + button( + hw::unsupported_hardware_wallet("ledger", Some("v2.1.0")) + .width(Length::Units(500)) + ) + .on_press(Message::Ignore) + .style(theme::Button::Secondary), + button( + hw::processing_hardware_wallet( + "ledger", + Some("v2.1.0"), + "f123de", + Some("Edouard key") + ) + .width(Length::Units(500)) + ) + .on_press(Message::Ignore) + .style(theme::Button::Secondary), + button( + hw::sign_success_hardware_wallet( + "ledger", + Some("v2.1.0"), + "f123de", + Some("Edouard key") + ) + .width(Length::Units(500)) + ) + .on_press(Message::Ignore) + .style(theme::Button::Secondary), + button( + hw::registration_success_hardware_wallet( + "ledger", + Some("v2.1.0"), + "f123de", + Some("Edouard key") + ) + .width(Length::Units(500)) + ) + .on_press(Message::Ignore) + .style(theme::Button::Secondary), + ] + .spacing(20) + ] + .spacing(100) + .into() + } +} diff --git a/gui/ui/src/component/hw.rs b/gui/ui/src/component/hw.rs new file mode 100644 index 00000000..30484792 --- /dev/null +++ b/gui/ui/src/component/hw.rs @@ -0,0 +1,237 @@ +use crate::{color, component::text::*, icon, theme, util::*, widget::*}; +use iced::{ + widget::{column, container, row, tooltip}, + Alignment, Length, +}; +use std::borrow::Cow; +use std::fmt::Display; + +pub fn supported_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( + kind: K, + version: Option, + fingerprint: F, + alias: Option>>, +) -> Container<'a, T> { + Container::new( + column(vec![ + Row::new() + .spacing(5) + .push_maybe(alias.map(|a| text(a).bold())) + .push(text(format!("#{}", fingerprint))) + .into(), + Row::new() + .spacing(5) + .push(text(kind.to_string()).small()) + .push_maybe(version.map(|v| text(v.to_string()).small())) + .into(), + ]) + .width(Length::Fill), + ) + .padding(10) +} + +pub fn unregistered_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( + kind: K, + version: Option, + fingerprint: F, + alias: Option>>, +) -> Container<'a, T> { + container( + row(vec![ + column(vec![ + Row::new() + .spacing(5) + .push_maybe(alias.map(|a| text(a).bold())) + .push(text(format!("#{}", fingerprint))) + .into(), + Row::new() + .spacing(5) + .push(text(kind.to_string()).small()) + .push_maybe(version.map(|v| text(v.to_string()).small())) + .into(), + ]) + .width(Length::Fill) + .into(), + column(vec![tooltip::Tooltip::new( + icon::warning_icon(), + "The wallet descriptor is not registered on the device.\n You can register it in the settings.", + tooltip::Position::Bottom, + ) + .style(theme::Container::Card(theme::Card::Simple)) + .into()]) + .into(), + ]) + .align_items(Alignment::Center), + ) + .padding(10) +} + +pub fn processing_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( + kind: K, + version: Option, + fingerprint: F, + alias: Option>>, +) -> Container<'a, T> { + container( + row(vec![ + column(vec![ + Row::new() + .spacing(5) + .push_maybe(alias.map(|a| text(a).bold())) + .push(text(format!("#{}", fingerprint))) + .into(), + Row::new() + .spacing(5) + .push(text(kind.to_string()).small()) + .push_maybe(version.map(|v| text(v.to_string()).small())) + .into(), + ]) + .width(Length::Fill) + .into(), + column(vec![ + text("Processing...").into(), + text("Please check your device").small().into(), + ]) + .into(), + ]) + .align_items(Alignment::Center), + ) + .padding(10) +} + +pub fn selected_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( + kind: K, + version: Option, + fingerprint: F, + alias: Option>>, +) -> Container<'a, T> { + container( + row(vec![ + column(vec![ + Row::new() + .spacing(5) + .push_maybe(alias.map(|a| text(a).bold())) + .push(text(format!("#{}", fingerprint))) + .into(), + Row::new() + .spacing(5) + .push(text(kind.to_string()).small()) + .push_maybe(version.map(|v| text(v.to_string()).small())) + .into(), + ]) + .width(Length::Fill) + .into(), + icon::circle_check_icon() + .style(color::legacy::SUCCESS) + .into(), + ]) + .align_items(Alignment::Center), + ) + .padding(10) +} + +pub fn sign_success_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( + kind: K, + version: Option, + fingerprint: F, + alias: Option>>, +) -> Container<'a, T> { + container( + row(vec![ + column(vec![ + Row::new() + .spacing(5) + .push_maybe(alias.map(|a| text(a).bold())) + .push(text(format!("#{}", fingerprint))) + .into(), + Row::new() + .spacing(5) + .push(text(kind.to_string()).small()) + .push_maybe(version.map(|v| text(v.to_string()).small())) + .into(), + ]) + .width(Length::Fill) + .into(), + row(vec![ + icon::circle_check_icon() + .style(color::legacy::SUCCESS) + .into(), + text("Signed").style(color::legacy::SUCCESS).into(), + ]) + .align_items(Alignment::Center) + .spacing(5) + .into(), + ]) + .align_items(Alignment::Center), + ) + .padding(10) +} + +pub fn registration_success_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( + kind: K, + version: Option, + fingerprint: F, + alias: Option>>, +) -> Container<'a, T> { + container( + row(vec![ + column(vec![ + Row::new() + .spacing(5) + .push_maybe(alias.map(|a| text(a).bold())) + .push(text(format!("#{}", fingerprint))) + .into(), + Row::new() + .spacing(5) + .push(text(kind.to_string()).small()) + .push_maybe(version.map(|v| text(v.to_string()).small())) + .into(), + ]) + .width(Length::Fill) + .into(), + row(vec![ + icon::circle_check_icon() + .style(color::legacy::SUCCESS) + .into(), + text("Registered").style(color::legacy::SUCCESS).into(), + ]) + .align_items(Alignment::Center) + .spacing(5) + .into(), + ]) + .align_items(Alignment::Center), + ) + .padding(10) +} + +pub fn unsupported_hardware_wallet<'a, T: 'a, K: Display, V: Display>( + kind: K, + version: Option, +) -> Container<'a, T> { + container( + row(vec![ + column(vec![ + Row::new() + .spacing(5) + .push(text("Unsupported version").bold()) + .into(), + Row::new() + .spacing(5) + .push(text(kind.to_string()).small()) + .push_maybe(version.map(|v| text(v.to_string()).small())) + .into(), + ]) + .width(Length::Fill) + .into(), + tooltip::Tooltip::new( + icon::warning_icon(), + "Please update the application on your device", + tooltip::Position::Bottom, + ) + .style(theme::Container::Card(theme::Card::Simple)) + .into(), + ]) + .align_items(Alignment::Center), + ) + .padding(10) +} diff --git a/gui/ui/src/component/mod.rs b/gui/ui/src/component/mod.rs index e8ac9f8e..7ac5ff42 100644 --- a/gui/ui/src/component/mod.rs +++ b/gui/ui/src/component/mod.rs @@ -3,6 +3,7 @@ pub mod button; pub mod card; pub mod collapse; pub mod form; +pub mod hw; pub mod modal; pub mod notification; pub mod text;