From 0fbba9eaa24bc4fbb0966235415eba1459b7d8bd Mon Sep 17 00:00:00 2001 From: edouardparis Date: Fri, 9 Aug 2024 12:51:00 +0200 Subject: [PATCH] Add backend section to settings to send invitation --- gui/src/app/state/settings/mod.rs | 131 ++++++++++++++++++++++++++---- gui/src/app/view/message.rs | 8 ++ gui/src/app/view/settings.rs | 93 ++++++++++++++++++++- 3 files changed, 211 insertions(+), 21 deletions(-) diff --git a/gui/src/app/state/settings/mod.rs b/gui/src/app/state/settings/mod.rs index 8d5a4b4b..ef2b3596 100644 --- a/gui/src/app/state/settings/mod.rs +++ b/gui/src/app/state/settings/mod.rs @@ -7,13 +7,20 @@ use std::sync::Arc; use iced::Command; -use liana_ui::widget::Element; +use liana_ui::{component::form, widget::Element}; use bitcoind::BitcoindSettingsState; use wallet::WalletSettingsState; use crate::{ - app::{cache::Cache, error::Error, message::Message, state::State, view, wallet::Wallet}, + app::{ + cache::Cache, + error::Error, + message::Message, + state::State, + view::{self}, + wallet::Wallet, + }, daemon::{Daemon, DaemonBackend}, }; @@ -66,6 +73,12 @@ impl State for SettingsState { .map(|s| s.reload(daemon, wallet)) .unwrap_or_else(Command::none) } + Message::View(view::Message::Settings( + view::SettingsMessage::EditRemoteBackendSettings, + )) => { + self.setting = Some(BackendSettingsState::new().into()); + Command::none() + } Message::View(view::Message::Settings(view::SettingsMessage::AboutSection)) => { self.setting = Some(AboutSettingsState::default().into()); let wallet = self.wallet.clone(); @@ -138,19 +151,6 @@ pub struct AboutSettingsState { warning: Option, } -impl AboutSettingsState { - pub fn new(daemon_is_external: bool) -> Self { - AboutSettingsState { - daemon_version: if !daemon_is_external { - Some(liana::VERSION.to_string()) - } else { - None - }, - warning: None, - } - } -} - impl State for AboutSettingsState { fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { view::settings::about_section(cache, self.warning.as_ref(), self.daemon_version.as_ref()) @@ -158,13 +158,19 @@ impl State for AboutSettingsState { fn update( &mut self, - _daemon: Arc, + daemon: Arc, _cache: &Cache, message: Message, ) -> Command { if let Message::Info(res) = message { match res { - Ok(info) => self.daemon_version = Some(info.version), + Ok(info) => { + if daemon.backend() == DaemonBackend::RemoteBackend { + self.daemon_version = None; + } else { + self.daemon_version = Some(info.version) + } + } Err(e) => self.warning = Some(e), } } @@ -189,3 +195,94 @@ impl From for Box { Box::new(s) } } + +#[derive(Default)] +pub struct BackendSettingsState { + email_form: form::Value, + processing: bool, + success: bool, + warning: Option, +} + +impl BackendSettingsState { + pub fn new() -> Self { + Self { + email_form: form::Value::default(), + processing: false, + success: false, + warning: None, + } + } +} + +impl State for BackendSettingsState { + fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { + view::settings::remote_backend_section( + cache, + &self.email_form, + self.processing, + self.success, + self.warning.as_ref(), + ) + } + + fn update( + &mut self, + daemon: Arc, + _cache: &Cache, + message: Message, + ) -> Command { + match message { + Message::View(view::Message::Settings( + view::SettingsMessage::RemoteBackendSettings(message), + )) => match message { + view::RemoteBackendSettingsMessage::SendInvitation => { + if !self.email_form.valid { + return Command::none(); + } + let email = self.email_form.value.clone(); + self.processing = true; + self.success = false; + self.warning = None; + Command::perform( + async move { + daemon.send_wallet_invitation(&email).await?; + Ok(()) + }, + Message::Updated, + ) + } + view::RemoteBackendSettingsMessage::EditInvitationEmail(email) => { + if !self.processing { + self.email_form.valid = email_address::EmailAddress::parse_with_options( + &email, + email_address::Options::default().with_required_tld(), + ) + .is_ok(); + self.email_form.value = email; + self.success = false; + } + Command::none() + } + }, + Message::Updated(res) => { + self.processing = false; + match res { + Ok(()) => self.success = true, + Err(e) => { + self.success = false; + self.warning = Some(e); + } + } + Command::none() + } + _ => Command::none(), + } + } +} + +impl From for Box { + fn from(s: BackendSettingsState) -> Box { + Box::new(s) + } +} diff --git a/gui/src/app/view/message.rs b/gui/src/app/view/message.rs index 2b7b0e87..001e8952 100644 --- a/gui/src/app/view/message.rs +++ b/gui/src/app/view/message.rs @@ -68,6 +68,8 @@ pub enum SettingsMessage { EditBitcoindSettings, BitcoindSettings(SettingsEditMessage), RescanSettings(SettingsEditMessage), + EditRemoteBackendSettings, + RemoteBackendSettings(RemoteBackendSettingsMessage), EditWalletSettings, AboutSection, RegisterWallet, @@ -75,6 +77,12 @@ pub enum SettingsMessage { Save, } +#[derive(Debug, Clone)] +pub enum RemoteBackendSettingsMessage { + EditInvitationEmail(String), + SendInvitation, +} + #[derive(Debug, Clone)] pub enum SettingsEditMessage { Select, diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index 92352f9d..e85838f6 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -44,9 +44,9 @@ pub fn list(cache: &Cache, is_remote_backend: bool) -> Element { Button::new(text("Settings").size(30).bold()) .style(theme::Button::Transparent) .on_press(Message::Menu(Menu::Settings))) - .push_maybe( + .push( if !is_remote_backend { - Some(Container::new( + Container::new( Button::new( Row::new() .push(badge::Badge::new(icon::bitcoin_icon())) @@ -61,9 +61,24 @@ pub fn list(cache: &Cache, is_remote_backend: bool) -> Element { .on_press(Message::Settings(SettingsMessage::EditBitcoindSettings)) ) .width(Length::Fill) - .style(theme::Container::Card(theme::Card::Simple))) + .style(theme::Container::Card(theme::Card::Simple)) } else { - None + Container::new( + Button::new( + Row::new() + .push(badge::Badge::new(icon::bitcoin_icon())) + .push(text("Backend").bold()) + .padding(10) + .spacing(20) + .align_items(Alignment::Center) + .width(Length::Fill), + ) + .width(Length::Fill) + .style(theme::Button::TransparentBorder) + .on_press(Message::Settings(SettingsMessage::EditRemoteBackendSettings)) + ) + .width(Length::Fill) + .style(theme::Container::Card(theme::Card::Simple)) } ) .push( @@ -211,6 +226,76 @@ pub fn about_section<'a>( ) } +pub fn remote_backend_section<'a>( + cache: &'a Cache, + email_form: &form::Value, + processing: bool, + success: bool, + warning: Option<&Error>, +) -> Element<'a, Message> { + dashboard( + &Menu::Settings, + cache, + warning, + Column::new() + .spacing(20) + .push( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push( + Button::new(text("Settings").size(30).bold()) + .style(theme::Button::Transparent) + .on_press(Message::Menu(Menu::Settings)), + ) + .push(icon::chevron_right().size(30)) + .push( + Button::new(text("Backend").size(30).bold()) + .style(theme::Button::Transparent) + .on_press(Message::Settings(SettingsMessage::AboutSection)), + ), + ) + .push( + card::simple( + Column::new() + .spacing(20) + .push(text("Grant access to wallet to another user")) + .push( + form::Form::new_trimmed("User email", email_form, |email| { + Message::Settings(SettingsMessage::RemoteBackendSettings( + RemoteBackendSettingsMessage::EditInvitationEmail(email), + )) + }) + .warning("Email is invalid") + .size(P1_SIZE) + .padding(10), + ) + .push( + Row::new() + .push_maybe(if success { + Some(text("Invitation was sent").style(color::GREEN)) + } else { + None + }) + .push(Space::with_width(Length::Fill)) + .push(button::primary(None, "Send invitation").on_press_maybe( + if !processing && email_form.valid { + Some(Message::Settings( + SettingsMessage::RemoteBackendSettings( + RemoteBackendSettingsMessage::SendInvitation, + ), + )) + } else { + None + }, + )), + ), + ) + .width(Length::Fill), + ), + ) +} + pub fn bitcoind_edit<'a>( network: Network, blockheight: i32,