From c89b8d88f59f4dec556d083b6f39c8263279741c Mon Sep 17 00:00:00 2001 From: edouard Date: Thu, 24 Nov 2022 13:48:06 +0100 Subject: [PATCH 1/8] installer: rename network pick_list --- gui/src/installer/view.rs | 56 ++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 2efb2677..c9962002 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -20,18 +20,62 @@ use crate::{ }, }; -const NETWORKS: [bitcoin::Network; 4] = [ - bitcoin::Network::Bitcoin, - bitcoin::Network::Testnet, - bitcoin::Network::Signet, - bitcoin::Network::Regtest, +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Network { + Mainnet, + Testnet, + Regtest, + Signet, +} + +impl From for Network { + fn from(n: bitcoin::Network) -> Self { + match n { + bitcoin::Network::Bitcoin => Network::Mainnet, + bitcoin::Network::Testnet => Network::Testnet, + bitcoin::Network::Regtest => Network::Regtest, + bitcoin::Network::Signet => Network::Signet, + } + } +} + +impl From for bitcoin::Network { + fn from(network: Network) -> bitcoin::Network { + match network { + Network::Mainnet => bitcoin::Network::Bitcoin, + Network::Testnet => bitcoin::Network::Testnet, + Network::Regtest => bitcoin::Network::Regtest, + Network::Signet => bitcoin::Network::Signet, + } + } +} + +impl std::fmt::Display for Network { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Mainnet => write!(f, "Bitcoin mainnet"), + Self::Testnet => write!(f, "Bitcoin testnet"), + Self::Regtest => write!(f, "Bitcoin regtest"), + Self::Signet => write!(f, "Bitcoin signet"), + } + } +} + +const NETWORKS: [Network; 4] = [ + Network::Mainnet, + Network::Testnet, + Network::Signet, + Network::Regtest, ]; pub fn welcome(network: &bitcoin::Network, valid: bool) -> Element { Container::new(Container::new( Column::new() .push(Container::new( - PickList::new(&NETWORKS[..], Some(*network), message::Message::Network).padding(10), + PickList::new(&NETWORKS[..], Some(Network::from(*network)), |net| { + Message::Network(net.into()) + }) + .padding(10), )) .push(if valid { Container::new( From 92699c645b38f97bfcde35d30b83981335285bdf Mon Sep 17 00:00:00 2001 From: edouard Date: Thu, 24 Nov 2022 15:16:59 +0100 Subject: [PATCH 2/8] installer: create or import wallet --- gui/src/installer/config.rs | 2 +- gui/src/installer/message.rs | 2 + gui/src/installer/mod.rs | 101 +++++++------ gui/src/installer/step/descriptor.rs | 207 +++++++++++++++++---------- gui/src/installer/step/mod.rs | 36 +---- gui/src/installer/view.rs | 156 +++++++++++++++----- gui/src/ui/icon.rs | 8 ++ 7 files changed, 324 insertions(+), 188 deletions(-) diff --git a/gui/src/installer/config.rs b/gui/src/installer/config.rs index cc92bcb5..696ab269 100644 --- a/gui/src/installer/config.rs +++ b/gui/src/installer/config.rs @@ -18,7 +18,7 @@ impl TryFrom for LianaConfig { daemon: false, log_level: log::LevelFilter::Info, main_descriptor: ctx.descriptor.unwrap(), - data_dir: ctx.data_dir, + data_dir: Some(ctx.data_dir), bitcoin_config: ctx.bitcoin_config, bitcoind_config: ctx.bitcoind_config, }) diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index 5d28bb2d..6d8ec736 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -6,6 +6,8 @@ use crate::hw::HardwareWallet; #[derive(Debug, Clone)] pub enum Message { + CreateWallet, + ImportWallet, Event(iced_native::Event), Exit(PathBuf), Clibpboard(String), diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index 8f4f9e0e..b95563d4 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -16,7 +16,10 @@ use crate::{ }; pub use message::Message; -use step::{Context, DefineBitcoind, DefineDescriptor, Final, RegisterDescriptor, Step, Welcome}; +use step::{ + Context, DefineBitcoind, DefineDescriptor, Final, ImportDescriptor, RegisterDescriptor, Step, + Welcome, +}; pub struct Installer { should_exit: bool, @@ -28,12 +31,6 @@ pub struct Installer { } impl Installer { - fn next(&mut self) { - if self.current < self.steps.len() - 1 { - self.current += 1; - } - } - fn previous(&mut self) { if self.current > 0 { self.current -= 1; @@ -48,14 +45,8 @@ impl Installer { Installer { should_exit: false, current: 0, - steps: vec![ - Welcome::new(network, destination_path.clone()).into(), - DefineDescriptor::new().into(), - RegisterDescriptor::default().into(), - DefineBitcoind::new().into(), - Final::new().into(), - ], - context: Context::new(network, Some(destination_path)), + steps: vec![Welcome::default().into()], + context: Context::new(network, destination_path), }, Command::none(), ) @@ -73,35 +64,61 @@ impl Installer { self.should_exit = true; } + fn next(&mut self) -> Command { + let current_step = self + .steps + .get_mut(self.current) + .expect("There is always a step"); + if current_step.apply(&mut self.context) { + if self.current < self.steps.len() - 1 { + self.current += 1; + } + // skip the step according to the current context. + while self + .steps + .get(self.current) + .expect("There is always a step") + .skip(&self.context) + { + if self.current < self.steps.len() - 1 { + self.current += 1; + } + } + // calculate new current_step. + let current_step = self + .steps + .get_mut(self.current) + .expect("There is always a step"); + current_step.load_context(&self.context); + return current_step.load(); + } + Command::none() + } + pub fn update(&mut self, message: Message) -> Command { match message { - Message::Clibpboard(s) => clipboard::write(s), - Message::Next => { - let current_step = self - .steps - .get_mut(self.current) - .expect("There is always a step"); - if current_step.apply(&mut self.context) { - self.next(); - // skip the step according to the current context. - while self - .steps - .get(self.current) - .expect("There is always a step") - .skip(&self.context) - { - self.next(); - } - // calculate new current_step. - let current_step = self - .steps - .get_mut(self.current) - .expect("There is always a step"); - current_step.load_context(&self.context); - return current_step.load(); - } - Command::none() + Message::CreateWallet => { + self.steps = vec![ + Welcome::default().into(), + DefineDescriptor::new().into(), + RegisterDescriptor::default().into(), + DefineBitcoind::new().into(), + Final::new().into(), + ]; + self.next() } + Message::ImportWallet => { + self.steps = vec![ + Welcome::default().into(), + ImportDescriptor::new().into(), + RegisterDescriptor::default().into(), + DefineBitcoind::new().into(), + Final::new().into(), + ]; + self.next() + } + Message::Clibpboard(s) => clipboard::write(s), + Message::Next => self.next(), Message::Previous => { self.previous(); Command::none() @@ -114,7 +131,7 @@ impl Installer { Command::perform(install(self.context.clone()), Message::Installed) } Message::Installed(Err(e)) => { - let mut data_dir = self.context.data_dir.clone().unwrap(); + let mut data_dir = self.context.data_dir.clone(); data_dir.push(self.context.bitcoin_config.network.to_string()); // In case of failure during install, block the thread to // deleted the data_dir/network directory in order to start clean again. diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index 22c6ec49..ff3710a7 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::str::FromStr; use iced::{Command, Element}; @@ -8,7 +9,7 @@ use liana::{ util::bip32::{DerivationPath, Fingerprint}, Network, }, - descriptor::{Descriptor, DescriptorMultiXKey, DescriptorPublicKey, Wildcard}, + descriptor::{DescriptorMultiXKey, DescriptorPublicKey, Wildcard}, }, }; @@ -24,7 +25,8 @@ use crate::{ pub struct DefineDescriptor { network: Network, - imported_descriptor: form::Value, + network_valid: bool, + data_dir: Option, user_xpub: form::Value, heir_xpub: form::Value, sequence: form::Value, @@ -37,7 +39,8 @@ impl DefineDescriptor { pub fn new() -> Self { Self { network: Network::Bitcoin, - imported_descriptor: form::Value::default(), + data_dir: None, + network_valid: true, user_xpub: form::Value::default(), heir_xpub: form::Value::default(), sequence: form::Value::default(), @@ -55,12 +58,14 @@ impl Step for DefineDescriptor { Message::Close => { self.modal = None; } + Message::Network(network) => { + self.network = network; + let mut network_datadir = self.data_dir.clone().unwrap(); + network_datadir.push(self.network.to_string()); + self.network_valid = !network_datadir.exists(); + } Message::DefineDescriptor(msg) => { match msg { - message::DefineDescriptor::ImportDescriptor(desc) => { - self.imported_descriptor.value = desc; - self.imported_descriptor.valid = true; - } message::DefineDescriptor::UserXpubEdited(xpub) => { self.user_xpub.value = xpub; self.user_xpub.valid = true; @@ -107,78 +112,41 @@ impl Step for DefineDescriptor { fn load_context(&mut self, ctx: &Context) { self.network = ctx.bitcoin_config.network; + self.data_dir = Some(ctx.data_dir.clone()); + let mut network_datadir = ctx.data_dir.clone(); + network_datadir.push(self.network.to_string()); + self.network_valid = !network_datadir.exists(); } fn apply(&mut self, ctx: &mut Context) -> bool { + ctx.bitcoin_config.network = self.network; // descriptor forms for import or creation cannot be both empty or filled. - if self.imported_descriptor.value.is_empty() - == (self.user_xpub.value.is_empty() - || self.heir_xpub.value.is_empty() - || self.sequence.value.is_empty()) + let user_key = DescriptorPublicKey::from_str(&self.user_xpub.value); + self.user_xpub.valid = user_key.is_ok(); + if let Ok(key) = &user_key { + self.user_xpub.valid = check_key_network(key, self.network); + } + + let heir_key = DescriptorPublicKey::from_str(&self.heir_xpub.value); + self.heir_xpub.valid = heir_key.is_ok(); + if let Ok(key) = &heir_key { + self.heir_xpub.valid = check_key_network(key, self.network); + } + + let sequence = self.sequence.value.parse::(); + self.sequence.valid = sequence.is_ok(); + + if !self.network_valid + || !self.user_xpub.valid + || !self.heir_xpub.valid + || !self.sequence.valid { - if !self.user_xpub.value.is_empty() { - let key = DescriptorPublicKey::from_str(&self.user_xpub.value); - self.user_xpub.valid = key.is_ok(); - // Check the Network - if let Ok(key) = &key { - self.user_xpub.valid = check_key_network(key, ctx.bitcoin_config.network); - } - } + return false; + } - if !self.heir_xpub.value.is_empty() { - let key = DescriptorPublicKey::from_str(&self.heir_xpub.value); - self.heir_xpub.valid = key.is_ok(); - // Check the Network - if let Ok(key) = &key { - self.heir_xpub.valid = check_key_network(key, ctx.bitcoin_config.network); - } - } - - if !self.sequence.value.is_empty() { - self.sequence.valid = self.sequence.value.parse::().is_ok(); - } else { - self.sequence.valid = false; - } - - if !self.imported_descriptor.value.is_empty() { - self.imported_descriptor.valid = - Descriptor::::from_str(&self.imported_descriptor.value) - .is_ok(); - } - false - } else if !self.imported_descriptor.value.is_empty() { - if let Ok(desc) = MultipathDescriptor::from_str(&self.imported_descriptor.value) { - ctx.descriptor = Some(desc); - true - } else { - self.imported_descriptor.valid = false; - false - } - } else { - let user_key = DescriptorPublicKey::from_str(&self.user_xpub.value); - self.user_xpub.valid = user_key.is_ok(); - if let Ok(key) = &user_key { - self.user_xpub.valid = check_key_network(key, ctx.bitcoin_config.network); - } - - let heir_key = DescriptorPublicKey::from_str(&self.heir_xpub.value); - self.heir_xpub.valid = heir_key.is_ok(); - if let Ok(key) = &heir_key { - self.heir_xpub.valid = check_key_network(key, ctx.bitcoin_config.network); - } - - let sequence = self.sequence.value.parse::(); - self.sequence.valid = sequence.is_ok(); - - if !self.user_xpub.valid || !self.heir_xpub.valid || !self.sequence.valid { - return false; - } - - let desc = match MultipathDescriptor::new( - user_key.unwrap(), - heir_key.unwrap(), - sequence.unwrap(), - ) { + let desc = + match MultipathDescriptor::new(user_key.unwrap(), heir_key.unwrap(), sequence.unwrap()) + { Ok(desc) => desc, Err(e) => { self.error = Some(e.to_string()); @@ -186,9 +154,8 @@ impl Step for DefineDescriptor { } }; - ctx.descriptor = Some(desc); - true - } + ctx.descriptor = Some(desc); + true } fn view(&self) -> Element { @@ -197,7 +164,7 @@ impl Step for DefineDescriptor { } else { view::define_descriptor( self.network, - &self.imported_descriptor, + self.network_valid, &self.user_xpub, &self.heir_xpub, &self.sequence, @@ -341,6 +308,92 @@ async fn get_extended_pubkey( })) } +pub struct ImportDescriptor { + network: Network, + network_valid: bool, + data_dir: Option, + imported_descriptor: form::Value, + error: Option, +} + +impl ImportDescriptor { + pub fn new() -> Self { + Self { + network: Network::Bitcoin, + network_valid: true, + data_dir: None, + imported_descriptor: form::Value::default(), + error: None, + } + } +} + +impl Step for ImportDescriptor { + // form value is set as valid each time it is edited. + // Verification of the values is happening when the user click on Next button. + fn update(&mut self, message: Message) -> Command { + match message { + Message::Network(network) => { + self.network = network; + let mut network_datadir = self.data_dir.clone().unwrap(); + network_datadir.push(self.network.to_string()); + self.network_valid = !network_datadir.exists(); + } + Message::DefineDescriptor(message::DefineDescriptor::ImportDescriptor(desc)) => { + self.imported_descriptor.value = desc; + self.imported_descriptor.valid = true; + } + _ => {} + }; + Command::none() + } + + fn load_context(&mut self, ctx: &Context) { + self.network = ctx.bitcoin_config.network; + self.data_dir = Some(ctx.data_dir.clone()); + let mut network_datadir = ctx.data_dir.clone(); + network_datadir.push(self.network.to_string()); + self.network_valid = !network_datadir.exists(); + } + + fn apply(&mut self, ctx: &mut Context) -> bool { + ctx.bitcoin_config.network = self.network; + // descriptor forms for import or creation cannot be both empty or filled. + if !self.imported_descriptor.value.is_empty() { + if let Ok(desc) = MultipathDescriptor::from_str(&self.imported_descriptor.value) { + ctx.descriptor = Some(desc); + true + } else { + self.imported_descriptor.valid = false; + false + } + } else { + false + } + } + + fn view(&self) -> Element { + view::import_descriptor( + self.network, + self.network_valid, + &self.imported_descriptor, + self.error.as_ref(), + ) + } +} + +impl Default for ImportDescriptor { + fn default() -> Self { + Self::new() + } +} + +impl From for Box { + fn from(s: ImportDescriptor) -> Box { + Box::new(s) + } +} + #[derive(Default)] pub struct RegisterDescriptor { descriptor: Option, diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index 52c243d6..00e56977 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -1,5 +1,5 @@ mod descriptor; -pub use descriptor::{DefineDescriptor, RegisterDescriptor}; +pub use descriptor::{DefineDescriptor, ImportDescriptor, RegisterDescriptor}; use std::path::PathBuf; use std::str::FromStr; @@ -43,11 +43,11 @@ pub struct Context { pub bitcoind_config: Option, pub descriptor: Option, pub hw_tokens: Vec<(DeviceKind, bitcoin::util::bip32::Fingerprint, [u8; 32])>, - pub data_dir: Option, + pub data_dir: PathBuf, } impl Context { - pub fn new(network: bitcoin::Network, data_dir: Option) -> Self { + pub fn new(network: bitcoin::Network, data_dir: PathBuf) -> Self { Self { bitcoin_config: BitcoinConfig { network, @@ -61,36 +61,12 @@ impl Context { } } -pub struct Welcome { - network: bitcoin::Network, - data_dir: PathBuf, -} - -impl Welcome { - pub fn new(network: bitcoin::Network, data_dir: PathBuf) -> Self { - Self { network, data_dir } - } - - fn valid(&self) -> bool { - let mut network_datadir = self.data_dir.clone(); - network_datadir.push(self.network.to_string()); - !network_datadir.exists() - } -} +#[derive(Default)] +pub struct Welcome {} impl Step for Welcome { - fn update(&mut self, message: Message) -> Command { - if let message::Message::Network(network) = message { - self.network = network; - } - Command::none() - } - fn apply(&mut self, ctx: &mut Context) -> bool { - ctx.bitcoin_config.network = self.network; - true - } fn view(&self) -> Element { - view::welcome(&self.network, self.valid()) + view::welcome() } } diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index c9962002..bf578a97 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -68,24 +68,41 @@ const NETWORKS: [Network; 4] = [ Network::Regtest, ]; -pub fn welcome(network: &bitcoin::Network, valid: bool) -> Element { +pub fn welcome<'a>() -> Element<'a, Message> { Container::new(Container::new( Column::new() - .push(Container::new( - PickList::new(&NETWORKS[..], Some(Network::from(*network)), |net| { - Message::Network(net.into()) - }) - .padding(10), - )) - .push(if valid { - Container::new( - button::primary(None, "Start the install") - .on_press(Message::Next) - .width(Length::Units(200)), - ) - } else { - card::warning("A data directory already exists for this network".to_string()) - }) + .push( + Row::new() + .spacing(20) + .push( + Button::new( + Container::new( + Column::new() + .width(Length::Units(200)) + .push(icon::wallet_icon().size(50).width(Length::Units(100))) + .push(text("Create new wallet")) + .align_items(Alignment::Center), + ) + .padding(50), + ) + .style(button::Style::Border.into()) + .on_press(Message::CreateWallet), + ) + .push( + Button::new( + Container::new( + Column::new() + .width(Length::Units(200)) + .push(icon::import_icon().size(50).width(Length::Units(100))) + .push(text("Import wallet")) + .align_items(Alignment::Center), + ) + .padding(50), + ) + .style(button::Style::Border.into()) + .on_press(Message::ImportWallet), + ), + ) .width(Length::Fill) .height(Length::Fill) .padding(100) @@ -101,26 +118,32 @@ pub fn welcome(network: &bitcoin::Network, valid: bool) -> Element { pub fn define_descriptor<'a>( network: bitcoin::Network, - imported_descriptor: &form::Value, + network_valid: bool, user_xpub: &form::Value, heir_xpub: &form::Value, sequence: &form::Value, error: Option<&String>, ) -> Element<'a, Message> { - let col_descriptor = Column::new() - .push(text("Descriptor:").bold()) - .push( - form::Form::new("Descriptor", imported_descriptor, |msg| { - Message::DefineDescriptor(message::DefineDescriptor::ImportDescriptor(msg)) + let row_network = Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(text("Network:").bold()) + .push(Container::new( + PickList::new(&NETWORKS[..], Some(Network::from(network)), |net| { + Message::Network(net.into()) }) - .warning("Please enter correct descriptor") - .size(20) .padding(10), - ) - .spacing(10); + )) + .push_maybe(if network_valid { + None + } else { + Some(card::warning( + "A data directory already exists for this network".to_string(), + )) + }); let col_user_xpub = Column::new() - .push(text("Your xpub:").bold()) + .push(text("Your public key:").bold()) .push( Row::new() .push( @@ -144,7 +167,7 @@ pub fn define_descriptor<'a>( .spacing(10); let col_heir_xpub = Column::new() - .push(text("Heir xpub:").bold()) + .push(text("Public key of the recovery key:").bold()) .push( Row::new() .push( @@ -168,7 +191,7 @@ pub fn define_descriptor<'a>( .spacing(10); let col_sequence = Column::new() - .push(text("Number of block:").bold()) + .push(text("Number of block before enabling recovery:").bold()) .push( Container::new( form::Form::new("Number of block", sequence, |msg| { @@ -184,21 +207,19 @@ pub fn define_descriptor<'a>( layout( Column::new() - .push(text("Create the descriptor").bold().size(50)) + .push(text("Create the wallet").bold().size(50)) .push( Column::new() + .push(row_network) .push(col_user_xpub) .push(col_sequence) .push(col_heir_xpub) .spacing(20), ) - .push(text("or import it").bold().size(25)) - .push(col_descriptor) .push( - if !imported_descriptor.value.is_empty() - && (!user_xpub.value.is_empty() - || !heir_xpub.value.is_empty() - || !sequence.value.is_empty()) + if !user_xpub.value.is_empty() + || !heir_xpub.value.is_empty() + || !sequence.value.is_empty() { button::primary(None, "Next").width(Length::Units(200)) } else { @@ -216,6 +237,65 @@ pub fn define_descriptor<'a>( ) } +pub fn import_descriptor<'a>( + network: bitcoin::Network, + network_valid: bool, + imported_descriptor: &form::Value, + error: Option<&String>, +) -> Element<'a, Message> { + let row_network = Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(text("Network:").bold()) + .push(Container::new( + PickList::new(&NETWORKS[..], Some(Network::from(network)), |net| { + Message::Network(net.into()) + }) + .padding(10), + )) + .push_maybe(if network_valid { + None + } else { + Some(card::warning( + "A data directory already exists for this network".to_string(), + )) + }); + let col_descriptor = Column::new() + .push(text("Descriptor:").bold()) + .push( + form::Form::new("Descriptor", imported_descriptor, |msg| { + Message::DefineDescriptor(message::DefineDescriptor::ImportDescriptor(msg)) + }) + .warning("Please enter correct descriptor") + .size(20) + .padding(10), + ) + .spacing(10); + layout( + Column::new() + .push(text("Import the wallet").bold().size(50)) + .push( + Column::new() + .spacing(20) + .push(row_network) + .push(col_descriptor), + ) + .push(if !imported_descriptor.value.is_empty() { + button::primary(None, "Next").width(Length::Units(200)) + } else { + button::primary(None, "Next") + .width(Length::Units(200)) + .on_press(Message::Next) + }) + .push_maybe(error.map(|e| card::error("Invalid descriptor", e.to_string()))) + .width(Length::Fill) + .height(Length::Fill) + .padding(100) + .spacing(50) + .align_items(Alignment::Center), + ) +} + pub fn register_descriptor<'a>( descriptor: String, hws: &[(HardwareWallet, Option<[u8; 32]>)], @@ -385,9 +465,9 @@ pub fn hardware_wallet_xpubs_modal<'a>( Column::new() .push( text(if is_heir { - "Import the Heir xpub" + "Import the recovery public key" } else { - "Import the user xpub" + "Import the user public key" }) .bold() .size(50), diff --git a/gui/src/ui/icon.rs b/gui/src/ui/icon.rs index 537ee82d..892f76b9 100644 --- a/gui/src/ui/icon.rs +++ b/gui/src/ui/icon.rs @@ -13,6 +13,14 @@ fn icon(unicode: char) -> Text<'static> { .size(20) } +pub fn import_icon() -> Text<'static> { + icon('\u{F30A}') +} + +pub fn wallet_icon() -> Text<'static> { + icon('\u{F615}') +} + pub fn hourglass_icon() -> Text<'static> { icon('\u{F41F}') } From b260ac420819cb41f8740fa12f65b3a79d69eed3 Mon Sep 17 00:00:00 2001 From: edouard Date: Fri, 25 Nov 2022 11:18:20 +0100 Subject: [PATCH 3/8] installer: keep refresh button to list hws --- gui/src/installer/view.rs | 69 +++++++++++++++++++--------------- gui/src/ui/component/button.rs | 4 ++ gui/src/ui/icon.rs | 4 ++ 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index bf578a97..72417d74 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -317,9 +317,24 @@ pub fn register_descriptor<'a>( .align_items(Alignment::Center), ) .push_maybe(error.map(|e| card::error("Failed to import xpub", e.to_string()))) - .push(if !hws.is_empty() { + .push( Column::new() - .push(text(format!("{} hardware wallets connected", hws.len())).bold()) + .push( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push( + Container::new( + text(format!("{} hardware wallets connected", hws.len())) + .bold(), + ) + .width(Length::Fill), + ) + .push( + button::border(Some(icon::reload_icon()), "Refresh") + .on_press(Message::Reload), + ), + ) .spacing(10) .push( hws.iter() @@ -334,17 +349,8 @@ pub fn register_descriptor<'a>( )) }), ) - .width(Length::Fill) - } else { - Column::new().push(card::simple( - Column::new() - .spacing(20) - .push("No hardware wallet connected") - .push(button::primary(None, "Refresh").on_press(Message::Reload)) - .align_items(Alignment::Center) - .width(Length::Fill), - )) - }) + .width(Length::Fill), + ) .push( button::primary(None, "Next") .on_press(Message::Next) @@ -473,9 +479,24 @@ pub fn hardware_wallet_xpubs_modal<'a>( .size(50), ) .push_maybe(error.map(|e| card::error("Failed to import xpub", e.to_string()))) - .push(if !hws.is_empty() { + .push( Column::new() - .push(text(format!("{} hardware wallets connected", hws.len())).bold()) + .push( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push( + Container::new( + text(format!("{} hardware wallets connected", hws.len())) + .bold(), + ) + .width(Length::Fill), + ) + .push( + button::border(Some(icon::reload_icon()), "Refresh") + .on_press(Message::Reload), + ), + ) .spacing(10) .push( hws.iter() @@ -490,22 +511,8 @@ pub fn hardware_wallet_xpubs_modal<'a>( )) }), ) - .width(Length::Fill) - } else { - Column::new() - .push( - card::simple( - Column::new() - .spacing(20) - .width(Length::Fill) - .push("Please connect a hardware wallet") - .push(button::primary(None, "Refresh").on_press(Message::Reload)) - .align_items(Alignment::Center), - ) - .width(Length::Fill), - ) - .width(Length::Fill) - }) + .width(Length::Fill), + ) .width(Length::Fill) .height(Length::Fill) .padding(100) diff --git a/gui/src/ui/component/button.rs b/gui/src/ui/component/button.rs index 7fb50fbf..8f537114 100644 --- a/gui/src/ui/component/button.rs +++ b/gui/src/ui/component/button.rs @@ -16,6 +16,10 @@ pub fn transparent<'a, T: 'a>(icon: Option>, t: &'static str) -> button button::Button::new(content(icon, t)).style(Style::Transparent.into()) } +pub fn border<'a, T: 'a>(icon: Option>, t: &'static str) -> button::Button<'a, T> { + button::Button::new(content(icon, t)).style(Style::Border.into()) +} + pub fn transparent_border<'a, T: 'a>( icon: Option>, t: &'static str, diff --git a/gui/src/ui/icon.rs b/gui/src/ui/icon.rs index 892f76b9..b155a63e 100644 --- a/gui/src/ui/icon.rs +++ b/gui/src/ui/icon.rs @@ -13,6 +13,10 @@ fn icon(unicode: char) -> Text<'static> { .size(20) } +pub fn reload_icon() -> Text<'static> { + icon('\u{F130}') +} + pub fn import_icon() -> Text<'static> { icon('\u{F30A}') } From 8348ad1afadc2e46994b364d595491cc327d4961 Mon Sep 17 00:00:00 2001 From: edouard Date: Fri, 25 Nov 2022 16:08:15 +0100 Subject: [PATCH 4/8] installer: add backup descriptor step --- gui/src/installer/message.rs | 1 + gui/src/installer/mod.rs | 6 ++-- gui/src/installer/prompt.rs | 2 ++ gui/src/installer/step/descriptor.rs | 28 +++++++++++++++ gui/src/installer/step/mod.rs | 2 +- gui/src/installer/view.rs | 54 +++++++++++++++++++++++++--- 6 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 gui/src/installer/prompt.rs diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index 6d8ec736..d9baf202 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -8,6 +8,7 @@ use crate::hw::HardwareWallet; pub enum Message { CreateWallet, ImportWallet, + BackupDone(bool), Event(iced_native::Event), Exit(PathBuf), Clibpboard(String), diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index b95563d4..625ba123 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -1,5 +1,6 @@ mod config; mod message; +mod prompt; mod step; mod view; @@ -17,8 +18,8 @@ use crate::{ pub use message::Message; use step::{ - Context, DefineBitcoind, DefineDescriptor, Final, ImportDescriptor, RegisterDescriptor, Step, - Welcome, + BackupDescriptor, Context, DefineBitcoind, DefineDescriptor, Final, ImportDescriptor, + RegisterDescriptor, Step, Welcome, }; pub struct Installer { @@ -101,6 +102,7 @@ impl Installer { self.steps = vec![ Welcome::default().into(), DefineDescriptor::new().into(), + BackupDescriptor::default().into(), RegisterDescriptor::default().into(), DefineBitcoind::new().into(), Final::new().into(), diff --git a/gui/src/installer/prompt.rs b/gui/src/installer/prompt.rs new file mode 100644 index 00000000..1cdc9e1c --- /dev/null +++ b/gui/src/installer/prompt.rs @@ -0,0 +1,2 @@ +pub const BACKUP_DESCRIPTOR_MESSAGE: &str = "The descriptor is necessary to recover your funds.\nThe backup of your key (via mnemonics, sometimes called 'seed words') is not enough.\nPlease make sure you have backed up both your private key and your descriptor."; +pub const BACKUP_DESCRIPTOR_HELP: &str = "In Bitcoin, the coins are locked using a Script (related to the 'address'). In order to recover your funds you need both to know the Scripts you have participated in (your 'addresses'), and be able to sign a transaction that spends from those. For the ability to sign you backup your private key, this is your mnemonics ('seed words'). For finding the coins that belongs to you you backup a template of your Script ( / 'addresses'), this is your descriptor. Note however the descriptor needs not be as securely stored as the private key. A thief that steals your descriptor but not your private key will not be able to steal your funds."; diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index ff3710a7..898e8a28 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -503,3 +503,31 @@ impl From for Box { Box::new(s) } } + +#[derive(Default)] +pub struct BackupDescriptor { + done: bool, + descriptor: Option, +} + +impl Step for BackupDescriptor { + fn update(&mut self, message: Message) -> Command { + if let Message::BackupDone(done) = message { + self.done = done; + } + Command::none() + } + fn load_context(&mut self, ctx: &Context) { + self.descriptor = ctx.descriptor.clone(); + } + fn view(&self) -> Element { + let desc = self.descriptor.as_ref().unwrap(); + view::backup_descriptor(desc.to_string(), self.done) + } +} + +impl From for Box { + fn from(s: BackupDescriptor) -> Box { + Box::new(s) + } +} diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index 00e56977..a0b404fb 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -1,5 +1,5 @@ mod descriptor; -pub use descriptor::{DefineDescriptor, ImportDescriptor, RegisterDescriptor}; +pub use descriptor::{BackupDescriptor, DefineDescriptor, ImportDescriptor, RegisterDescriptor}; use std::path::PathBuf; use std::str::FromStr; diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 72417d74..67d3d10a 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -1,4 +1,4 @@ -use iced::widget::{Button, Column, Container, PickList, Row, Scrollable}; +use iced::widget::{Button, Checkbox, Column, Container, PickList, Row, Scrollable}; use iced::{Alignment, Element, Length}; use liana::miniscript::bitcoin; @@ -217,9 +217,9 @@ pub fn define_descriptor<'a>( .spacing(20), ) .push( - if !user_xpub.value.is_empty() - || !heir_xpub.value.is_empty() - || !sequence.value.is_empty() + if user_xpub.value.is_empty() + && heir_xpub.value.is_empty() + && sequence.value.is_empty() { button::primary(None, "Next").width(Length::Units(200)) } else { @@ -364,6 +364,52 @@ pub fn register_descriptor<'a>( ) } +pub fn backup_descriptor<'a>(descriptor: String, done: bool) -> Element<'a, Message> { + layout( + Column::new() + .push( + text("Did you backup your wallet descriptor ?") + .bold() + .size(50), + ) + .push(text(super::prompt::BACKUP_DESCRIPTOR_MESSAGE)) + .push(card::simple( + Column::new() + .push(text("The descriptor:").small().bold()) + .push(text(descriptor.clone()).small()) + .push( + Row::new().push(Column::new().width(Length::Fill)).push( + button::transparent_border(Some(icon::clipboard_icon()), "Copy") + .on_press(Message::Clibpboard(descriptor)), + ), + ) + .spacing(10), + )) + .push(Checkbox::new( + done, + "I have backed up my descriptor", + Message::BackupDone, + )) + .push(if done { + button::primary(None, "Next") + .on_press(Message::Next) + .width(Length::Units(200)) + } else { + button::primary(None, "Next").width(Length::Units(200)) + }) + .push( + Column::new() + .push(text("Help:").bold()) + .push(text(super::prompt::BACKUP_DESCRIPTOR_HELP).small()), + ) + .width(Length::Fill) + .height(Length::Fill) + .padding(100) + .spacing(50) + .align_items(Alignment::Center), + ) +} + pub fn define_bitcoin<'a>( address: &form::Value, cookie_path: &form::Value, From 437faef1af01d9952f5ed3156ce38d9964ba5efb Mon Sep 17 00:00:00 2001 From: edouard Date: Fri, 25 Nov 2022 19:33:02 +0100 Subject: [PATCH 5/8] Add learn more collapse to installer --- gui/src/installer/prompt.rs | 2 +- gui/src/installer/view.rs | 43 +++++++++++++++++---- gui/src/ui/component/collapse.rs | 65 ++++++++++++++------------------ gui/src/ui/icon.rs | 8 ++++ 4 files changed, 73 insertions(+), 45 deletions(-) diff --git a/gui/src/installer/prompt.rs b/gui/src/installer/prompt.rs index 1cdc9e1c..c84211c9 100644 --- a/gui/src/installer/prompt.rs +++ b/gui/src/installer/prompt.rs @@ -1,2 +1,2 @@ -pub const BACKUP_DESCRIPTOR_MESSAGE: &str = "The descriptor is necessary to recover your funds.\nThe backup of your key (via mnemonics, sometimes called 'seed words') is not enough.\nPlease make sure you have backed up both your private key and your descriptor."; +pub const BACKUP_DESCRIPTOR_MESSAGE: &str = "The descriptor is necessary to recover your funds. The backup of your key (via mnemonics, sometimes called 'seed words') is not enough. Please make sure you have backed up both your private key and your descriptor."; pub const BACKUP_DESCRIPTOR_HELP: &str = "In Bitcoin, the coins are locked using a Script (related to the 'address'). In order to recover your funds you need both to know the Scripts you have participated in (your 'addresses'), and be able to sign a transaction that spends from those. For the ability to sign you backup your private key, this is your mnemonics ('seed words'). For finding the coins that belongs to you you backup a template of your Script ( / 'addresses'), this is your descriptor. Note however the descriptor needs not be as securely stored as the private key. A thief that steals your descriptor but not your private key will not be able to steal your funds."; diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 67d3d10a..96c11170 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -12,7 +12,7 @@ use crate::{ ui::{ color, component::{ - button, card, container, form, + button, card, collapse, container, form, text::{text, Text}, }, icon, @@ -372,7 +372,34 @@ pub fn backup_descriptor<'a>(descriptor: String, done: bool) -> Element<'a, Mess .bold() .size(50), ) - .push(text(super::prompt::BACKUP_DESCRIPTOR_MESSAGE)) + .push( + Column::new() + .push(text(super::prompt::BACKUP_DESCRIPTOR_MESSAGE)) + .push(collapse::Collapse::new( + || { + Button::new( + Row::new() + .align_items(Alignment::Center) + .spacing(10) + .push(text("Learn more").small().bold()) + .push(icon::collapse_icon()), + ) + .style(button::Style::Transparent.into()) + }, + || { + Button::new( + Row::new() + .align_items(Alignment::Center) + .spacing(10) + .push(text("Learn more").small().bold()) + .push(icon::collapsed_icon()), + ) + .style(button::Style::Transparent.into()) + }, + help_backup, + )) + .max_width(1000), + ) .push(card::simple( Column::new() .push(text("The descriptor:").small().bold()) @@ -383,7 +410,8 @@ pub fn backup_descriptor<'a>(descriptor: String, done: bool) -> Element<'a, Mess .on_press(Message::Clibpboard(descriptor)), ), ) - .spacing(10), + .spacing(10) + .max_width(1000), )) .push(Checkbox::new( done, @@ -397,11 +425,6 @@ pub fn backup_descriptor<'a>(descriptor: String, done: bool) -> Element<'a, Mess } else { button::primary(None, "Next").width(Length::Units(200)) }) - .push( - Column::new() - .push(text("Help:").bold()) - .push(text(super::prompt::BACKUP_DESCRIPTOR_HELP).small()), - ) .width(Length::Fill) .height(Length::Fill) .padding(100) @@ -410,6 +433,10 @@ pub fn backup_descriptor<'a>(descriptor: String, done: bool) -> Element<'a, Mess ) } +pub fn help_backup<'a>() -> Element<'a, Message> { + text(super::prompt::BACKUP_DESCRIPTOR_HELP).small().into() +} + pub fn define_bitcoin<'a>( address: &form::Value, cookie_path: &form::Value, diff --git a/gui/src/ui/component/collapse.rs b/gui/src/ui/component/collapse.rs index 480e6c38..839cbd81 100644 --- a/gui/src/ui/component/collapse.rs +++ b/gui/src/ui/component/collapse.rs @@ -5,41 +5,42 @@ use iced::{ use iced_lazy::{self, Component}; use std::marker::PhantomData; -use super::button::Style; +pub struct Collapse<'a, M, H, F, C> { + before: H, + after: F, + content: C, + phantom: PhantomData<&'a M>, +} -pub fn collapse< - 'a, +impl<'a, Message, T, H, F, C> Collapse<'a, Message, H, F, C> +where Message: 'a, T: Into + Clone + 'a, - H: Fn() -> Element<'a, T> + 'a, + H: Fn() -> Button<'a, Event> + 'a, + F: Fn() -> Button<'a, Event> + 'a, C: Fn() -> Element<'a, T> + 'a, ->( - header: H, - content: C, -) -> impl Into> { - Collapse { - header, - content, - phantom: PhantomData, +{ + pub fn new(before: H, after: F, content: C) -> Self { + Collapse { + before, + after, + content, + phantom: PhantomData, + } } } -struct Collapse<'a, H, C> { - header: H, - content: C, - phantom: PhantomData<&'a H>, -} - #[derive(Debug, Clone, Copy)] -enum Event { +pub enum Event { Internal(T), Collapse(bool), } -impl<'a, Message, T, H, C> Component for Collapse<'a, H, C> +impl<'a, Message, T, H, F, C> Component for Collapse<'a, Message, H, F, C> where T: Into + Clone + 'a, - H: Fn() -> Element<'a, T>, + H: Fn() -> Button<'a, Event>, + F: Fn() -> Button<'a, Event>, C: Fn() -> Element<'a, T>, { type State = bool; @@ -58,35 +59,27 @@ where fn view(&self, state: &Self::State) -> Element { if *state { Column::new() - .push( - Button::new((self.header)().map(Event::Internal)) - .style(Style::TransparentBorder.into()) - .padding(10) - .on_press(Event::Collapse(false)), - ) + .push((self.after)().on_press(Event::Collapse(false))) .push((self.content)().map(Event::Internal)) .into() } else { Column::new() - .push( - Button::new((self.header)().map(Event::Internal)) - .style(Style::TransparentBorder.into()) - .padding(10) - .on_press(Event::Collapse(true)), - ) + .push((self.before)().on_press(Event::Collapse(true))) .into() } } } -impl<'a, Message, T, H: 'a, C: 'a> From> for Element<'a, Message> +impl<'a, Message, T, H: 'a, F: 'a, C: 'a> From> + for Element<'a, Message> where Message: 'a, T: Into + Clone + 'a, - H: Fn() -> Element<'a, T, iced::Renderer>, + H: Fn() -> Button<'a, Event, iced::Renderer>, + F: Fn() -> Button<'a, Event, iced::Renderer>, C: Fn() -> Element<'a, T, iced::Renderer>, { - fn from(c: Collapse<'a, H, C>) -> Self { + fn from(c: Collapse<'a, Message, H, F, C>) -> Self { iced_lazy::component(c) } } diff --git a/gui/src/ui/icon.rs b/gui/src/ui/icon.rs index b155a63e..50b7aee6 100644 --- a/gui/src/ui/icon.rs +++ b/gui/src/ui/icon.rs @@ -190,3 +190,11 @@ pub fn done_icon() -> Text<'static> { pub fn todo_icon() -> Text<'static> { icon('\u{F28A}') } + +pub fn collapse_icon() -> Text<'static> { + icon('\u{F284}') +} + +pub fn collapsed_icon() -> Text<'static> { + icon('\u{F282}') +} From 0365794f910833e400f2418e52b6259f3ccb9b22 Mon Sep 17 00:00:00 2001 From: edouard Date: Mon, 28 Nov 2022 18:07:22 +0100 Subject: [PATCH 6/8] installer: add final step summary --- gui/src/installer/mod.rs | 8 ++- gui/src/installer/step/descriptor.rs | 19 +++--- gui/src/installer/step/mod.rs | 17 ++++- gui/src/installer/view.rs | 94 +++++++++++++++++++++++++--- 4 files changed, 115 insertions(+), 23 deletions(-) diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index 625ba123..5ba5f7de 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -166,9 +166,13 @@ impl Installer { pub async fn install(ctx: Context) -> Result { let hardware_wallets = ctx - .hw_tokens + .hws .iter() - .map(|(kind, fingerprint, token)| HardwareWalletConfig::new(kind, fingerprint, token)) + .filter_map(|(kind, fingerprint, token)| { + token + .as_ref() + .map(|token| HardwareWalletConfig::new(kind, fingerprint, token)) + }) .collect(); let mut cfg: liana::config::Config = ctx diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index 898e8a28..ba0b8739 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -399,7 +399,7 @@ pub struct RegisterDescriptor { descriptor: Option, processing: bool, chosen_hw: Option, - hws: Vec<(HardwareWallet, Option<[u8; 32]>)>, + hws: Vec<(HardwareWallet, Option<[u8; 32]>, bool)>, error: Option, } @@ -410,7 +410,7 @@ impl Step for RegisterDescriptor { fn update(&mut self, message: Message) -> Command { match message { Message::Select(i) => { - if let Some((hw, hmac)) = self.hws.get(i) { + if let Some((hw, hmac, _)) = self.hws.get(i) { if hmac.is_none() { let device = hw.device.clone(); let descriptor = self.descriptor.as_ref().unwrap().to_string(); @@ -434,7 +434,8 @@ impl Step for RegisterDescriptor { .iter_mut() .find(|hw_h| hw_h.0.fingerprint == fingerprint) { - hw_h.1 = Some(hmac.unwrap_or([0x00; 32])); + hw_h.1 = hmac; + hw_h.2 = true; } } Err(e) => self.error = Some(e), @@ -445,9 +446,9 @@ impl Step for RegisterDescriptor { if !self .hws .iter() - .any(|(h, _)| h.fingerprint == hw.fingerprint) + .any(|(h, _, _)| h.fingerprint == hw.fingerprint) { - self.hws.push((hw, None)); + self.hws.push((hw, None, false)); } } } @@ -459,11 +460,9 @@ impl Step for RegisterDescriptor { Command::none() } fn apply(&mut self, ctx: &mut Context) -> bool { - for (hw, token) in &self.hws { - if let Some(token) = token { - if *token != [0x00; 32] { - ctx.hw_tokens.push((hw.kind, hw.fingerprint, *token)); - } + for (hw, token, registered) in &self.hws { + if *registered { + ctx.hws.push((hw.kind, hw.fingerprint, *token)); } } true diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index a0b404fb..e0e08ee7 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -42,7 +42,11 @@ pub struct Context { pub bitcoin_config: BitcoinConfig, pub bitcoind_config: Option, pub descriptor: Option, - pub hw_tokens: Vec<(DeviceKind, bitcoin::util::bip32::Fingerprint, [u8; 32])>, + pub hws: Vec<( + DeviceKind, + bitcoin::util::bip32::Fingerprint, + Option<[u8; 32]>, + )>, pub data_dir: PathBuf, } @@ -53,7 +57,7 @@ impl Context { network, poll_interval_secs: Duration::from_secs(30), }, - hw_tokens: Vec::new(), + hws: Vec::new(), bitcoind_config: None, descriptor: None, data_dir, @@ -206,6 +210,7 @@ impl From for Box { pub struct Final { generating: bool, + context: Option, warning: Option, config_path: Option, } @@ -213,6 +218,7 @@ pub struct Final { impl Final { pub fn new() -> Self { Self { + context: None, generating: false, warning: None, config_path: None, @@ -221,6 +227,9 @@ impl Final { } impl Step for Final { + fn load_context(&mut self, ctx: &Context) { + self.context = Some(ctx.clone()); + } fn update(&mut self, message: Message) -> Command { match message { Message::Installed(res) => { @@ -244,7 +253,11 @@ impl Step for Final { } fn view(&self) -> Element { + let ctx = self.context.as_ref().unwrap(); + let desc = ctx.descriptor.as_ref().unwrap().to_string(); view::install( + ctx, + desc, self.generating, self.config_path.as_ref(), self.warning.as_ref(), diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 96c11170..8c923de7 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -7,6 +7,7 @@ use crate::{ hw::HardwareWallet, installer::{ message::{self, Message}, + step::Context, Error, }, ui::{ @@ -298,7 +299,7 @@ pub fn import_descriptor<'a>( pub fn register_descriptor<'a>( descriptor: String, - hws: &[(HardwareWallet, Option<[u8; 32]>)], + hws: &[(HardwareWallet, Option<[u8; 32]>, bool)], error: Option<&Error>, processing: bool, chosen_hw: Option, @@ -306,16 +307,19 @@ pub fn register_descriptor<'a>( layout( Column::new() .push(text("Register descriptor").bold().size(50)) - .push( + .push(card::simple( Column::new() + .push(text("The descriptor:").small().bold()) .push(text(descriptor.clone()).small()) .push( - button::transparent_border(Some(icon::clipboard_icon()), "Copy") - .on_press(Message::Clibpboard(descriptor)), + Row::new().push(Column::new().width(Length::Fill)).push( + button::transparent_border(Some(icon::clipboard_icon()), "Copy") + .on_press(Message::Clibpboard(descriptor)), + ), ) .spacing(10) - .align_items(Alignment::Center), - ) + .max_width(1000), + )) .push_maybe(error.map(|e| card::error("Failed to import xpub", e.to_string()))) .push( Column::new() @@ -345,7 +349,7 @@ pub fn register_descriptor<'a>( &hw.0, Some(i) == chosen_hw, processing, - hw.1.is_some(), + hw.2, )) }), ) @@ -488,15 +492,87 @@ pub fn define_bitcoin<'a>( } pub fn install<'a>( + context: &Context, + descriptor: String, generating: bool, config_path: Option<&std::path::PathBuf>, warning: Option<&'a String>, ) -> Element<'a, Message> { let mut col = Column::new() + .push( + Container::new( + Column::new() + .spacing(10) + .push( + card::simple( + Column::new() + .spacing(5) + .push(text("Descriptor:").small().bold()) + .push(text(descriptor).small()), + ) + .width(Length::Fill), + ) + .push( + card::simple( + Column::new() + .spacing(5) + .push(text("Hardware devices:").small().bold()) + .push(context.hws.iter().fold(Column::new(), |acc, hw| { + acc.push( + Row::new() + .spacing(5) + .push(text(hw.0.to_string()).small()) + .push(text(format!("(fingerprint: {})", hw.1)).small()), + ) + })), + ) + .width(Length::Fill), + ) + .push( + card::simple( + Column::new() + .push(text("Bitcoind:").small().bold()) + .push( + Row::new() + .spacing(5) + .align_items(Alignment::Center) + .push(text("Cookie path:").small()) + .push( + text(format!( + "{}", + context + .bitcoind_config + .as_ref() + .unwrap() + .cookie_path + .to_string_lossy() + )) + .small(), + ), + ) + .push( + Row::new() + .spacing(5) + .align_items(Alignment::Center) + .push(text("Address:").small()) + .push( + text(format!( + "{}", + context.bitcoind_config.as_ref().unwrap().addr + )) + .small(), + ), + ), + ) + .width(Length::Fill), + ), + ) + .padding(50) + .max_width(1000), + ) + .spacing(50) .width(Length::Fill) .height(Length::Fill) - .padding(100) - .spacing(50) .align_items(Alignment::Center); if let Some(error) = warning { From 0577b971b77c0691f0dbb948956eb7616c4c8614 Mon Sep 17 00:00:00 2001 From: edouard Date: Tue, 29 Nov 2022 09:48:56 +0100 Subject: [PATCH 7/8] installer: add progress information --- gui/src/installer/mod.rs | 2 +- gui/src/installer/step/descriptor.rs | 13 ++++++++----- gui/src/installer/step/mod.rs | 11 ++++++----- gui/src/installer/view.rs | 28 +++++++++++++++++++++++++--- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index 5ba5f7de..3f8596fd 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -160,7 +160,7 @@ impl Installer { self.steps .get(self.current) .expect("There is always a step") - .view() + .view((self.current, self.steps.len() - 1)) } } diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index ba0b8739..45cd5632 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -158,11 +158,12 @@ impl Step for DefineDescriptor { true } - fn view(&self) -> Element { + fn view(&self, progress: (usize, usize)) -> Element { if let Some(modal) = &self.modal { modal.view() } else { view::define_descriptor( + progress, self.network, self.network_valid, &self.user_xpub, @@ -372,8 +373,9 @@ impl Step for ImportDescriptor { } } - fn view(&self) -> Element { + fn view(&self, progress: (usize, usize)) -> Element { view::import_descriptor( + progress, self.network, self.network_valid, &self.imported_descriptor, @@ -473,9 +475,10 @@ impl Step for RegisterDescriptor { Message::ConnectedHardwareWallets, ) } - fn view(&self) -> Element { + fn view(&self, progress: (usize, usize)) -> Element { let desc = self.descriptor.as_ref().unwrap(); view::register_descriptor( + progress, desc.to_string(), &self.hws, self.error.as_ref(), @@ -519,9 +522,9 @@ impl Step for BackupDescriptor { fn load_context(&mut self, ctx: &Context) { self.descriptor = ctx.descriptor.clone(); } - fn view(&self) -> Element { + fn view(&self, progress: (usize, usize)) -> Element { let desc = self.descriptor.as_ref().unwrap(); - view::backup_descriptor(desc.to_string(), self.done) + view::backup_descriptor(progress, desc.to_string(), self.done) } } diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index e0e08ee7..b3a20ff1 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -24,7 +24,7 @@ pub trait Step { fn update(&mut self, _message: Message) -> Command { Command::none() } - fn view(&self) -> Element; + fn view(&self, progress: (usize, usize)) -> Element; fn load_context(&mut self, _ctx: &Context) {} fn load(&self) -> Command { Command::none() @@ -69,7 +69,7 @@ impl Context { pub struct Welcome {} impl Step for Welcome { - fn view(&self) -> Element { + fn view(&self, _progress: (usize, usize)) -> Element { view::welcome() } } @@ -191,8 +191,8 @@ impl Step for DefineBitcoind { } } - fn view(&self) -> Element { - view::define_bitcoin(&self.address, &self.cookie_path) + fn view(&self, progress: (usize, usize)) -> Element { + view::define_bitcoin(progress, &self.address, &self.cookie_path) } } @@ -252,10 +252,11 @@ impl Step for Final { Command::none() } - fn view(&self) -> Element { + fn view(&self, progress: (usize, usize)) -> Element { let ctx = self.context.as_ref().unwrap(); let desc = ctx.descriptor.as_ref().unwrap().to_string(); view::install( + progress, ctx, desc, self.generating, diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 8c923de7..4cfd0e19 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -118,6 +118,7 @@ pub fn welcome<'a>() -> Element<'a, Message> { } pub fn define_descriptor<'a>( + progress: (usize, usize), network: bitcoin::Network, network_valid: bool, user_xpub: &form::Value, @@ -207,6 +208,7 @@ pub fn define_descriptor<'a>( .spacing(10); layout( + progress, Column::new() .push(text("Create the wallet").bold().size(50)) .push( @@ -239,6 +241,7 @@ pub fn define_descriptor<'a>( } pub fn import_descriptor<'a>( + progress: (usize, usize), network: bitcoin::Network, network_valid: bool, imported_descriptor: &form::Value, @@ -273,6 +276,7 @@ pub fn import_descriptor<'a>( ) .spacing(10); layout( + progress, Column::new() .push(text("Import the wallet").bold().size(50)) .push( @@ -298,6 +302,7 @@ pub fn import_descriptor<'a>( } pub fn register_descriptor<'a>( + progress: (usize, usize), descriptor: String, hws: &[(HardwareWallet, Option<[u8; 32]>, bool)], error: Option<&Error>, @@ -305,6 +310,7 @@ pub fn register_descriptor<'a>( chosen_hw: Option, ) -> Element<'a, Message> { layout( + progress, Column::new() .push(text("Register descriptor").bold().size(50)) .push(card::simple( @@ -368,8 +374,13 @@ pub fn register_descriptor<'a>( ) } -pub fn backup_descriptor<'a>(descriptor: String, done: bool) -> Element<'a, Message> { +pub fn backup_descriptor<'a>( + progress: (usize, usize), + descriptor: String, + done: bool, +) -> Element<'a, Message> { layout( + progress, Column::new() .push( text("Did you backup your wallet descriptor ?") @@ -442,6 +453,7 @@ pub fn help_backup<'a>() -> Element<'a, Message> { } pub fn define_bitcoin<'a>( + progress: (usize, usize), address: &form::Value, cookie_path: &form::Value, ) -> Element<'a, Message> { @@ -470,6 +482,7 @@ pub fn define_bitcoin<'a>( .spacing(10); layout( + progress, Column::new() .push( text("Set up connection to the Bitcoin full node") @@ -492,6 +505,7 @@ pub fn define_bitcoin<'a>( } pub fn install<'a>( + progress: (usize, usize), context: &Context, descriptor: String, generating: bool, @@ -606,7 +620,7 @@ pub fn install<'a>( ); } - layout(col) + layout(progress, col) } pub fn hardware_wallet_xpubs_modal<'a>( @@ -715,13 +729,21 @@ fn hw_list_view<'a>( .into() } -fn layout<'a>(content: impl Into>) -> Element<'a, Message> { +fn layout<'a>( + progress: (usize, usize), + content: impl Into>, +) -> Element<'a, Message> { Container::new(Scrollable::new( Column::new() .push( Container::new(button::transparent(None, "< Previous").on_press(Message::Previous)) .padding(5), ) + .push( + Container::new(text(format!("{}/{}", progress.0, progress.1))) + .width(Length::Fill) + .center_x(), + ) .push(Container::new(content).width(Length::Fill).center_x()), )) .center_x() From 59c12cd5844e19f296066465fd34a3be48e26f2e Mon Sep 17 00:00:00 2001 From: edouard Date: Tue, 29 Nov 2022 17:00:03 +0100 Subject: [PATCH 8/8] installer: wait process is done before next step --- gui/src/installer/view.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 4cfd0e19..713d0d71 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -361,11 +361,13 @@ pub fn register_descriptor<'a>( ) .width(Length::Fill), ) - .push( + .push(if processing { + button::primary(None, "Next").width(Length::Units(200)) + } else { button::primary(None, "Next") .on_press(Message::Next) - .width(Length::Units(200)), - ) + .width(Length::Units(200)) + }) .width(Length::Fill) .height(Length::Fill) .padding(100)