From f5394bf8894e30194942bf9de8d458e7ffd783af Mon Sep 17 00:00:00 2001 From: edouard Date: Sat, 18 Mar 2023 15:37:49 +0100 Subject: [PATCH] wallet settings: edit fingerprint aliases --- gui/src/app/state/settings/wallet.rs | 113 ++++++++++++++++++++++++++- gui/src/app/view/message.rs | 3 + gui/src/app/view/settings.rs | 53 ++++++++++++- 3 files changed, 165 insertions(+), 4 deletions(-) diff --git a/gui/src/app/state/settings/wallet.rs b/gui/src/app/state/settings/wallet.rs index 5b6dac60..10149449 100644 --- a/gui/src/app/state/settings/wallet.rs +++ b/gui/src/app/state/settings/wallet.rs @@ -13,15 +13,18 @@ use crate::{ }, daemon::Daemon, hw::{list_hardware_wallets, HardwareWallet, HardwareWalletConfig}, - ui::component::modal, + ui::component::{form, modal}, }; pub struct WalletSettingsState { data_dir: PathBuf, warning: Option, descriptor: String, + keys_aliases: Vec<(Fingerprint, form::Value)>, wallet: Arc, modal: Option, + processing: bool, + updated: bool, } impl WalletSettingsState { @@ -29,17 +32,52 @@ impl WalletSettingsState { WalletSettingsState { data_dir, descriptor: wallet.main_descriptor.to_string(), + keys_aliases: Self::keys_aliases(&wallet), wallet, warning: None, modal: None, + processing: false, + updated: false, } } + + fn keys_aliases(wallet: &Wallet) -> Vec<(Fingerprint, form::Value)> { + let mut keys_aliases: Vec<(Fingerprint, form::Value)> = wallet + .keys_aliases + .clone() + .into_iter() + .map(|(fg, name)| { + ( + fg, + form::Value { + value: name, + valid: true, + }, + ) + }) + .collect(); + + for fingerprint in wallet.descriptor_keys().into_iter() { + if wallet.keys_aliases.get(&fingerprint).is_none() { + keys_aliases.push((fingerprint, form::Value::default())); + } + } + + keys_aliases.sort_by(|(fg1, _), (fg2, _)| fg1.cmp(fg2)); + keys_aliases + } } impl State for WalletSettingsState { fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { - let content = - view::settings::wallet_settings(cache, self.warning.as_ref(), &self.descriptor); + let content = view::settings::wallet_settings( + cache, + self.warning.as_ref(), + &self.descriptor, + &self.keys_aliases, + self.processing, + self.updated, + ); if let Some(m) = &self.modal { modal::Modal::new(content, m.view()) .on_blur(Some(view::Message::Close)) @@ -56,18 +94,60 @@ impl State for WalletSettingsState { message: Message, ) -> Command { match message { + Message::Updated(res) => match res { + Ok(()) => { + self.processing = false; + self.updated = true; + Command::perform(async {}, |_| Message::LoadWallet) + } + Err(e) => { + self.processing = false; + self.warning = Some(e); + Command::none() + } + }, Message::WalletLoaded(res) => { match res { Ok(wallet) => { if let Some(modal) = &mut self.modal { modal.wallet = wallet.clone(); } + self.keys_aliases = Self::keys_aliases(&wallet); self.wallet = wallet; } Err(e) => self.warning = Some(e), }; Command::none() } + Message::View(view::Message::Settings( + view::SettingsMessage::FingerprintAliasEdited(fg, value), + )) => { + if let Some((_, name)) = self + .keys_aliases + .iter_mut() + .find(|(fingerprint, _)| fg == *fingerprint) + { + name.value = value; + } + Command::none() + } + Message::View(view::Message::Settings(view::SettingsMessage::Save)) => { + self.modal = None; + self.processing = true; + self.updated = false; + Command::perform( + update_keys_aliases( + self.data_dir.clone(), + cache.network, + self.wallet.clone(), + self.keys_aliases + .iter() + .map(|(fg, name)| (*fg, name.value.to_owned())) + .collect(), + ), + Message::Updated, + ) + } Message::View(view::Message::Close) => { self.modal = None; Command::none() @@ -249,6 +329,33 @@ async fn register_wallet( Ok(fingerprint) } +async fn update_keys_aliases( + data_dir: PathBuf, + network: Network, + wallet: Arc, + keys_aliases: Vec<(Fingerprint, String)>, +) -> Result<(), Error> { + let mut settings = settings::Settings::from_file(data_dir.clone(), network)?; + let checksum = wallet.descriptor_checksum(); + if let Some(wallet_setting) = settings + .wallets + .iter_mut() + .find(|w| w.descriptor_checksum == checksum) + { + wallet_setting.keys = keys_aliases + .into_iter() + .map(|(master_fingerprint, name)| settings::KeySetting { + master_fingerprint, + name, + }) + .collect(); + } + + settings.to_file(data_dir, network)?; + + Ok(()) +} + async fn list_hws(wallet: Arc) -> Vec { list_hardware_wallets( &wallet.hardware_wallets, diff --git a/gui/src/app/view/message.rs b/gui/src/app/view/message.rs index ca14b267..e636d723 100644 --- a/gui/src/app/view/message.rs +++ b/gui/src/app/view/message.rs @@ -1,4 +1,5 @@ use crate::app::menu::Menu; +use liana::miniscript::bitcoin::util::bip32::Fingerprint; #[derive(Debug, Clone)] pub enum Message { @@ -53,6 +54,8 @@ pub enum SettingsMessage { EditWalletSettings, AboutSection, RegisterWallet, + FingerprintAliasEdited(Fingerprint, String), + Save, Edit(usize, SettingsEditMessage), } diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index 052e6a4f..c66dbf76 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -523,6 +523,9 @@ pub fn wallet_settings<'a>( cache: &'a Cache, warning: Option<&Error>, descriptor: &'a str, + keys_aliases: &[(Fingerprint, form::Value)], + processing: bool, + updated: bool, ) -> Element<'a, Message> { dashboard( &Menu::Settings, @@ -548,7 +551,7 @@ pub fn wallet_settings<'a>( ) .push(card::simple( Column::new() - .push(text("Wallet descriptor:").small().bold()) + .push(text("Wallet descriptor:").bold()) .push(text(descriptor.to_owned()).small()) .push( Row::new() @@ -567,6 +570,54 @@ pub fn wallet_settings<'a>( ), ) .spacing(10), + )) + .push(card::simple( + Column::new() + .push(text("Fingerprint aliases:").bold()) + .push(keys_aliases.iter().fold( + Column::new().spacing(10), + |col, (fingerprint, name)| { + let fg = *fingerprint; + col.push( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(text(fg.to_string()).bold().width(Length::Units(100))) + .push( + form::Form::new("Alias", name, move |msg| { + Message::Settings( + SettingsMessage::FingerprintAliasEdited(fg, msg), + ) + }) + .warning("Please enter correct alias") + .size(20) + .padding(10), + ), + ) + }, + )) + .push( + Row::new() + .align_items(Alignment::Center) + .push(Space::with_width(Length::Fill)) + .push_maybe(if updated { + Some( + Row::new() + .align_items(Alignment::Center) + .push(icon::circle_check_icon().style(color::SUCCESS)) + .push(text("Updated").style(color::SUCCESS)), + ) + } else { + None + }) + .push(if !processing { + button::primary(None, "Update") + .on_press(Message::Settings(SettingsMessage::Save)) + } else { + button::primary(None, "Updating") + }), + ) + .spacing(10), )), ) }