diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index 390c4a5e..ea2388a4 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -25,7 +25,7 @@ pub enum Message { Network(Network), DefineBitcoind(DefineBitcoind), DefineDescriptor(DefineDescriptor), - ImportXpub(Result), + ImportXpub(usize, Result), ConnectedHardwareWallets(Vec), WalletRegistered(Result<(Fingerprint, Option<[u8; 32]>), Error>), } diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index 44777cb4..bf4048e4 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -746,18 +746,76 @@ async fn get_extended_pubkey( })) } +pub struct HardwareWalletXpubs { + hw: HardwareWallet, + xpubs: Vec, + processing: bool, + error: Option, + next_account: ChildNumber, +} + +impl HardwareWalletXpubs { + fn new(hw: HardwareWallet) -> Self { + Self { + hw, + xpubs: Vec::new(), + processing: false, + error: None, + next_account: ChildNumber::from_hardened_idx(0).unwrap(), + } + } + + fn update(&mut self, res: Result) { + self.processing = false; + match res { + Err(e) => { + self.error = e.into(); + } + Ok(xpub) => { + self.error = None; + self.next_account = self.next_account.increment().unwrap(); + self.xpubs + .push(xpub.to_string().trim_end_matches("/<0;1>/*").to_string()); + } + } + } + + fn select(&mut self, i: usize, network: Network) -> Command { + let device = self.hw.device.clone(); + self.processing = true; + self.error = None; + let fingerprint = self.hw.fingerprint; + let next_account = self.next_account; + Command::perform( + async move { + ( + i, + get_extended_pubkey(device, fingerprint, network, next_account).await, + ) + }, + |(i, res)| Message::ImportXpub(i, res), + ) + } + + pub fn view(&self, i: usize) -> Element { + view::hardware_wallet_xpubs( + i, + &self.xpubs, + &self.hw, + self.processing, + self.error.as_ref(), + ) + } +} + pub struct ParticipateXpub { network: Network, network_valid: bool, data_dir: Option, - xpub: Option, shared: bool, - processing: bool, - chosen_hw: Option, - hws: Vec<(HardwareWallet, bool)>, - error: Option, + xpubs_hw: Vec, } impl ParticipateXpub { @@ -766,12 +824,8 @@ impl ParticipateXpub { network: Network::Bitcoin, network_valid: true, data_dir: None, - processing: false, - xpub: None, - chosen_hw: None, - hws: Vec::new(), + xpubs_hw: Vec::new(), shared: false, - error: None, } } } @@ -788,48 +842,26 @@ impl Step for ParticipateXpub { self.network_valid = !network_datadir.exists(); } Message::UserActionDone(shared) => self.shared = shared, - Message::ImportXpub(res) => { - self.processing = false; - match res { - Err(e) => { - self.error = e.into(); - self.chosen_hw = None; - } - Ok(xpub) => { - self.error = None; - self.xpub = Some(xpub.to_string().trim_end_matches("/<0;1>/*").to_string()); - for (i, (_, imported)) in self.hws.iter_mut().enumerate() { - *imported = Some(i) == self.chosen_hw; - } - self.chosen_hw = None; - } + Message::ImportXpub(i, res) => { + if let Some(hw) = self.xpubs_hw.get_mut(i) { + hw.update(res); } } Message::Select(i) => { - if let Some((hw, _)) = self.hws.get(i) { - let device = hw.device.clone(); - self.chosen_hw = Some(i); - self.processing = true; - self.error = None; - return Command::perform( - get_extended_pubkey( - device, - hw.fingerprint, - self.network, - ChildNumber::from_hardened_idx(0).unwrap(), - ), - Message::ImportXpub, - ); + if let Some(hw) = self.xpubs_hw.get_mut(i) { + return hw.select(i, self.network); } } Message::ConnectedHardwareWallets(hws) => { for hw in hws { - if !self - .hws - .iter() - .any(|(h, _)| h.fingerprint == hw.fingerprint) + if let Some(xpub_hw) = self + .xpubs_hw + .iter_mut() + .find(|h| h.hw.fingerprint == hw.fingerprint) { - self.hws.push((hw, false)); + xpub_hw.hw = hw; + } else { + self.xpubs_hw.push(HardwareWalletXpubs::new(hw)); } } } @@ -866,12 +898,12 @@ impl Step for ParticipateXpub { progress, self.network, self.network_valid, - &self.hws, - self.processing, - self.chosen_hw, - self.xpub.as_ref(), + self.xpubs_hw + .iter() + .enumerate() + .map(|(i, hw)| hw.view(i)) + .collect(), self.shared, - self.error.as_ref(), ) } } diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 18341093..31857563 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -409,18 +409,103 @@ pub fn import_descriptor<'a>( ) } -#[allow(clippy::too_many_arguments)] -pub fn participate_xpub<'a>( +pub fn hardware_wallet_xpubs<'a>( + i: usize, + xpubs: &'a Vec, + hw: &HardwareWallet, + 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(text(format!("fingerprint: {}", hw.fingerprint)).small()) + .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::ALERT)) + .push(text("An error occured").style(color::ALERT)), + e, + iced::widget::tooltip::Position::Bottom, + ) + .style(card::ErrorCardStyle) + })), + ) + .padding(10) + .style(button::Style::TransparentBorder.into()) + .width(Length::Fill); + if !processing { + bttn = bttn.on_press(Message::Select(i)); + } + Container::new( + Column::new() + .push(bttn) + .push_maybe(if xpubs.is_empty() { + None + } else { + Some(separation().width(Length::Fill)) + }) + .push_maybe(if xpubs.is_empty() { + None + } else { + Some(xpubs.iter().fold(Column::new().padding(15), |col, xpub| { + col.push( + Row::new() + .spacing(5) + .align_items(Alignment::Center) + .push( + Container::new( + Scrollable::new(Container::new(text(xpub).small()).padding(10)) + .horizontal_scroll( + Properties::new().width(2).scroller_width(2), + ), + ) + .width(Length::Fill), + ) + .push( + Container::new( + button::border(Some(icon::clipboard_icon()), "Copy") + .on_press(Message::Clibpboard(xpub.clone())) + .width(Length::Shrink), + ) + .padding(10), + ), + ) + })) + }) + .push_maybe(if !xpubs.is_empty() { + Some( + Container::new(if !processing { + button::border(Some(icon::plus_icon()), "New public key") + .on_press(Message::Select(i)) + } else { + button::border(Some(icon::plus_icon()), "New public key") + }) + .padding(10), + ) + } else { + None + }), + ) + .style(card::SimpleCardStyle) + .into() +} + +pub fn participate_xpub( progress: (usize, usize), network: bitcoin::Network, network_valid: bool, - hws: &[(HardwareWallet, bool)], - processing: bool, - chosen_hw: Option, - xpub: Option<&'a String>, + hws: Vec>, shared: bool, - error: Option<&Error>, -) -> Element<'a, Message> { +) -> Element { let row_network = Row::new() .spacing(10) .align_items(Alignment::Center) @@ -442,7 +527,7 @@ pub fn participate_xpub<'a>( layout( progress, Column::new() - .push(text("Share your public key").bold().size(50)) + .push(text("Share your public keys").bold().size(50)) .push( Column::new() .spacing(20) @@ -456,7 +541,7 @@ pub fn participate_xpub<'a>( .spacing(10) .align_items(Alignment::Center) .push( - Container::new(text("Select your hardware wallet:").bold()) + Container::new(text("Generate an extended public key by selecting a signing device:").bold()) .width(Length::Fill), ) .push( @@ -465,50 +550,11 @@ pub fn participate_xpub<'a>( ), ) .spacing(10) - .push( - hws.iter() - .enumerate() - .fold(Column::new().spacing(10), |col, (i, hw)| { - col.push(hw_list_view( - i, - &hw.0, - Some(i) == chosen_hw, - processing, - hw.1, - )) - }), - ) + .push(Column::with_children(hws).spacing(10)) .width(Length::Fill), ) - .push_maybe(xpub.map(|xpub| { - Column::new() - .spacing(5) - .push(text("Your extended pubkey:").bold()) - .push( - Row::new() - .spacing(5) - .align_items(Alignment::Center) - .push( - Container::new( - Scrollable::new(Container::new(text(xpub)).padding(10)) - .horizontal_scroll( - Properties::new().width(2).scroller_width(2), - ), - ) - .width(Length::Fill), - ) - .push( - Container::new( - button::border(Some(icon::clipboard_icon()), "Copy") - .on_press(Message::Clibpboard(xpub.clone())) - .width(Length::Shrink), - ) - .padding(10), - ), - ) - })) .push(Checkbox::new( - "I have shared my xpub", + "I have shared my public keys", shared, Message::UserActionDone, )) @@ -519,7 +565,6 @@ pub fn participate_xpub<'a>( } else { button::primary(None, "Next").width(Length::Units(200)) }) - .push_maybe(error.map(|e| card::error("Hardware error", e.to_string()))) .width(Length::Fill) .height(Length::Fill) .padding(100)