From c99df6ccfa88a75ab31ae1edb2b17ef771f50b42 Mon Sep 17 00:00:00 2001 From: edouardparis Date: Thu, 29 Aug 2024 23:19:10 +0200 Subject: [PATCH] Merge launcher and first installer step When the gui launches, it checks if there is a wallet in the network datadirs with bitcoin mainnet in priority. If it is the case, it tries to display information about the wallet by reading the settings.json file. If no wallet was found, then the gui displays the choice to either create a wallet or add it. --- gui/src/installer/message.rs | 10 +- gui/src/installer/mod.rs | 131 +++++---- gui/src/installer/step/mod.rs | 20 -- gui/src/installer/view.rs | 96 ------- gui/src/launcher.rs | 503 ++++++++++++++++------------------ gui/src/lianalite/login.rs | 4 +- gui/src/main.rs | 67 ++--- 7 files changed, 334 insertions(+), 497 deletions(-) diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index 457441836..744efd28f 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -1,4 +1,7 @@ -use liana::miniscript::{bitcoin::bip32::Fingerprint, DescriptorPublicKey}; +use liana::miniscript::{ + bitcoin::{bip32::Fingerprint, Network}, + DescriptorPublicKey, +}; use std::path::PathBuf; use super::{context, Error}; @@ -12,16 +15,13 @@ use async_hwi::{DeviceKind, Version}; #[derive(Debug, Clone)] pub enum Message { - CreateWallet, - ShareXpubs, - ImportWallet, UserActionDone(bool), Exit(PathBuf, Option), Clibpboard(String), Next, Skip, Previous, - BackToLauncher, + BackToLauncher(Network), Install, Close, Reload, diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index 8d4c81578..ab0526bf3 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -41,9 +41,16 @@ pub use message::Message; use step::{ BackupDescriptor, BackupMnemonic, ChooseBackend, DefineBitcoind, DefineDescriptor, Final, ImportDescriptor, ImportRemoteWallet, InternalBitcoindStep, RecoverMnemonic, - RegisterDescriptor, SelectBitcoindTypeStep, ShareXpubs, Step, Welcome, + RegisterDescriptor, SelectBitcoindTypeStep, ShareXpubs, Step, }; +#[derive(Debug, Clone)] +pub enum UserFlow { + CreateWallet, + AddWallet, + ShareXpubs, +} + pub struct Installer { pub network: bitcoin::Network, pub datadir: PathBuf, @@ -58,9 +65,12 @@ pub struct Installer { } impl Installer { - fn previous(&mut self) { + fn previous(&mut self) -> Command { + let network = self.network; if self.current > 0 { self.current -= 1; + } else { + return Command::perform(async move { network }, Message::BackToLauncher); } // skip the previous step according to the current context. while self.current > 0 @@ -76,29 +86,61 @@ impl Installer { if let Some(step) = self.steps.get(self.current) { step.revert(&mut self.context) } + Command::none() } pub fn new( destination_path: PathBuf, network: bitcoin::Network, remote_backend: Option, + user_flow: UserFlow, ) -> (Installer, Command) { - ( - Installer { - network, - datadir: destination_path.clone(), - current: 0, - hws: HardwareWallets::new(destination_path.clone(), network), - steps: vec![Welcome::default().into()], - context: Context::new( - network, - destination_path, - remote_backend.map(RemoteBackend::WithoutWallet), - ), - signer: Arc::new(Mutex::new(Signer::generate(network).unwrap())), + let signer = Arc::new(Mutex::new(Signer::generate(network).unwrap())); + let context = Context::new( + network, + destination_path.clone(), + remote_backend.map(RemoteBackend::WithoutWallet), + ); + let mut installer = Installer { + network, + datadir: destination_path.clone(), + current: 0, + hws: HardwareWallets::new(destination_path.clone(), network), + steps: match user_flow { + UserFlow::CreateWallet => vec![ + DefineDescriptor::new(network, signer.clone()).into(), + BackupMnemonic::new(signer.clone()).into(), + BackupDescriptor::default().into(), + RegisterDescriptor::new_create_wallet().into(), + ChooseBackend::new(network).into(), + SelectBitcoindTypeStep::new().into(), + InternalBitcoindStep::new(&context.data_dir).into(), + DefineBitcoind::new().into(), + Final::new().into(), + ], + UserFlow::ShareXpubs => vec![ShareXpubs::new(network, signer.clone()).into()], + UserFlow::AddWallet => vec![ + ChooseBackend::new(network).into(), + ImportRemoteWallet::new(network).into(), + ImportDescriptor::new(network).into(), + RecoverMnemonic::default().into(), + RegisterDescriptor::new_import_wallet().into(), + SelectBitcoindTypeStep::new().into(), + InternalBitcoindStep::new(&context.data_dir).into(), + DefineBitcoind::new().into(), + Final::new().into(), + ], }, - Command::none(), - ) + context, + signer, + }; + let current_step = installer + .steps + .get_mut(installer.current) + .expect("There is always a step"); + current_step.load_context(&installer.context); + let command = current_step.load(); + (installer, command) } pub fn destination_path(&self) -> PathBuf { @@ -106,14 +148,10 @@ impl Installer { } pub fn subscription(&self) -> Subscription { - if self.current > 0 { - self.steps - .get(self.current) - .expect("There is always a step") - .subscription(&self.hws) - } else { - Subscription::none() - } + self.steps + .get(self.current) + .expect("There is always a step") + .subscription(&self.hws) } pub fn stop(&mut self) { @@ -166,44 +204,6 @@ impl Installer { pub fn update(&mut self, message: Message) -> Command { match message { - Message::CreateWallet => { - self.steps = vec![ - Welcome::default().into(), - DefineDescriptor::new(self.network, self.signer.clone()).into(), - BackupMnemonic::new(self.signer.clone()).into(), - BackupDescriptor::default().into(), - RegisterDescriptor::new_create_wallet().into(), - ChooseBackend::new(self.network).into(), - SelectBitcoindTypeStep::new().into(), - InternalBitcoindStep::new(&self.context.data_dir).into(), - DefineBitcoind::new().into(), - Final::new().into(), - ]; - self.next() - } - Message::ShareXpubs => { - self.steps = vec![ - Welcome::default().into(), - ShareXpubs::new(self.network, self.signer.clone()).into(), - ]; - self.next() - } - Message::ImportWallet => { - self.steps = vec![ - Welcome::default().into(), - ChooseBackend::new(self.network).into(), - ImportRemoteWallet::new(self.network).into(), - ImportDescriptor::new(self.network).into(), - RecoverMnemonic::default().into(), - RegisterDescriptor::new_import_wallet().into(), - SelectBitcoindTypeStep::new().into(), - InternalBitcoindStep::new(&self.context.data_dir).into(), - DefineBitcoind::new().into(), - Final::new().into(), - ]; - - self.next() - } Message::HardwareWallets(msg) => match self.hws.update(msg) { Ok(cmd) => cmd.map(Message::HardwareWallets), Err(e) => { @@ -213,10 +213,7 @@ impl Installer { }, Message::Clibpboard(s) => clipboard::write(s), Message::Next => self.next(), - Message::Previous => { - self.previous(); - Command::none() - } + Message::Previous => self.previous(), Message::Install => { let _cmd = self .steps diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index b1b3fc954..89cf63613 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -54,26 +54,6 @@ pub trait Step { fn stop(&self) {} } -#[derive(Default)] -pub struct Welcome {} - -impl Step for Welcome { - fn view<'a>( - &'a self, - _hws: &'a HardwareWallets, - _progress: (usize, usize), - _email: Option<&'a str>, - ) -> Element { - view::welcome() - } -} - -impl From for Box { - fn from(s: Welcome) -> Box { - Box::new(s) - } -} - pub struct Final { generating: bool, internal_bitcoind: Option, diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 79b2fbbb6..ab6bbf7f2 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -42,102 +42,6 @@ use crate::{ }, }; -pub fn welcome<'a>() -> Element<'a, Message> { - Container::new( - Column::new() - .push( - Container::new( - Row::new() - .align_items(Alignment::Center) - .push( - Container::new(image::liana_brand_grey().width(Length::Fixed(200.0))) - .width(Length::Fill), - ) - .push( - Row::new() - .push( - button::secondary( - Some(icon::previous_icon()), - "Change network", - ) - .width(Length::Fixed(200.0)) - .on_press(Message::BackToLauncher), - ) - .push( - button::secondary(None, "Share Xpubs") - .width(Length::Fixed(200.0)) - .on_press(Message::ShareXpubs), - ) - .spacing(20), - ), - ) - .padding(100), - ) - .push( - Container::new( - Column::new() - .push( - Row::new() - .align_items(Alignment::End) - .spacing(20) - .push( - Container::new( - Column::new() - .spacing(20) - .align_items(Alignment::Center) - .push( - image::create_new_wallet_icon() - .width(Length::Fixed(100.0)), - ) - .push( - p1_regular("Create a new wallet") - .style(color::GREY_3), - ) - .push( - button::secondary(None, "Select") - .width(Length::Fixed(200.0)) - .on_press(Message::CreateWallet), - ) - .align_items(Alignment::Center), - ) - .padding(20), - ) - .push( - Container::new( - Column::new() - .spacing(20) - .align_items(Alignment::Center) - .push( - image::restore_wallet_icon() - .width(Length::Fixed(100.0)), - ) - .push( - p1_regular("Add an existing wallet") - .style(color::GREY_3), - ) - .push( - button::secondary(None, "Select") - .width(Length::Fixed(200.0)) - .on_press(Message::ImportWallet), - ) - .align_items(Alignment::Center), - ) - .padding(20), - ), - ) - .push(Space::with_height(Length::Fixed(100.0))) - .spacing(50) - .align_items(Alignment::Center), - ) - .center_y() - .center_x() - .width(Length::Fill) - .height(Length::Fill), - ), - ) - .into() -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DescriptorKind { P2WSH, diff --git a/gui/src/launcher.rs b/gui/src/launcher.rs index 85c31b6d2..937237d4d 100644 --- a/gui/src/launcher.rs +++ b/gui/src/launcher.rs @@ -2,61 +2,68 @@ use std::path::PathBuf; use iced::{ alignment::Horizontal, - widget::{scrollable, tooltip}, + widget::{pick_list, scrollable, Button, Space}, Alignment, Command, Length, Subscription, }; use liana::{config::ConfigError, miniscript::bitcoin::Network}; use liana_ui::{ color, - component::{badge, button, card, modal::Modal, notification, text::*}, + component::{button, card, modal::Modal, network_banner, notification, text::*}, icon, image, theme, widget::*, }; -use crate::app; +use crate::{app, installer::UserFlow}; -fn wallet_name(network: &Network) -> String { - match network { - Network::Bitcoin => "Bitcoin Mainnet", - Network::Testnet => "Bitcoin Testnet", - Network::Signet => "Bitcoin Signet", - Network::Regtest => "Bitcoin Regtest", - _ => "Bitcoin unknown", - } - .to_string() +const NETWORKS: [Network; 4] = [ + Network::Bitcoin, + Network::Testnet, + Network::Signet, + Network::Regtest, +]; + +#[derive(Debug, Clone)] +pub enum State { + Unchecked, + Wallet { + name: Option, + email: Option, + checksum: Option, + }, + NoWallet, } pub struct Launcher { - // true if installed - choices: Vec<(Network, bool)>, + state: State, + network: Network, datadir_path: PathBuf, error: Option, delete_wallet_modal: Option, - collapsed: bool, } impl Launcher { - pub fn new(datadir_path: PathBuf) -> Self { - Self { - choices: [ - Network::Bitcoin, - Network::Testnet, - Network::Signet, - Network::Regtest, - ] - .iter() - .map(|net| (*net, datadir_path.join(net.to_string()).exists())) - .collect(), - datadir_path, - error: None, - delete_wallet_modal: None, - collapsed: false, - } - } - - fn is_fresh_install(&self) -> bool { - !self.choices.iter().any(|(_, installed)| *installed) + pub fn new(datadir_path: PathBuf, network: Option) -> (Self, Command) { + let network = network.unwrap_or( + NETWORKS + .iter() + .find(|net| datadir_path.join(net.to_string()).exists()) + .cloned() + .unwrap_or(Network::Bitcoin), + ); + ( + Self { + state: State::Unchecked, + network, + datadir_path: datadir_path.clone(), + error: None, + delete_wallet_modal: None, + }, + Command::perform( + check_network_datadir(datadir_path.clone(), network), + Message::Checked, + ), + ) } pub fn stop(&mut self) {} @@ -67,22 +74,29 @@ impl Launcher { pub fn update(&mut self, message: Message) -> Command { match message { - Message::View(ViewMessage::ShowUninstalledNetworks) => { - self.collapsed = true; - Command::none() + Message::View(ViewMessage::ImportWallet) => { + let datadir_path = self.datadir_path.clone(); + let network = self.network; + Command::perform(async move { (datadir_path, network) }, |(d, n)| { + Message::Install(d, n, UserFlow::AddWallet) + }) } - Message::View(ViewMessage::StartInstall(net)) => { + Message::View(ViewMessage::CreateWallet) => { let datadir_path = self.datadir_path.clone(); - Command::perform(async move { (datadir_path, net) }, |(d, n)| { - Message::Install(d, n) + let network = self.network; + Command::perform(async move { (datadir_path, network) }, |(d, n)| { + Message::Install(d, n, UserFlow::CreateWallet) }) } - Message::View(ViewMessage::Check(network)) => Command::perform( - check_network_datadir(self.datadir_path.clone(), network), - Message::Checked, - ), - Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::ShowModal(network))) => { - let wallet_datadir = self.datadir_path.join(network.to_string()); + Message::View(ViewMessage::ShareXpubs) => { + let datadir_path = self.datadir_path.clone(); + let network = self.network; + Command::perform(async move { (datadir_path, network) }, |(d, n)| { + Message::Install(d, n, UserFlow::ShareXpubs) + }) + } + Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::ShowModal)) => { + let wallet_datadir = self.datadir_path.join(self.network.to_string()); let config_path = wallet_datadir.join(app::config::DEFAULT_FILE_NAME); let internal_bitcoind = if let Ok(cfg) = app::Config::from_file(&config_path) { Some(cfg.start_internal_bitcoind) @@ -90,18 +104,21 @@ impl Launcher { None }; self.delete_wallet_modal = Some(DeleteWalletModal::new( - network, + self.network, wallet_datadir, internal_bitcoind, )); Command::none() } + Message::View(ViewMessage::SelectNetwork(network)) => { + self.network = network; + Command::perform( + check_network_datadir(self.datadir_path.clone(), self.network), + Message::Checked, + ) + } Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Deleted)) => { - if let Some(modal) = &self.delete_wallet_modal { - if let Some(choice) = self.choices.iter_mut().find(|c| c.0 == modal.network) { - choice.1 = false; - } - } + self.state = State::NoWallet; Command::none() } Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::CloseModal)) => { @@ -113,17 +130,26 @@ impl Launcher { self.error = Some(e); Command::none() } - Ok(network) => { + Ok(state) => { + self.state = state; + Command::none() + } + }, + Message::View(ViewMessage::Run) => { + if matches!(self.state, State::Wallet { .. }) { let datadir_path = self.datadir_path.clone(); let mut path = self.datadir_path.clone(); - path.push(network.to_string()); + path.push(self.network.to_string()); path.push(app::config::DEFAULT_FILE_NAME); let cfg = app::Config::from_file(&path).expect("Already checked"); + let network = self.network; Command::perform(async move { (datadir_path.clone(), cfg, network) }, |m| { Message::Run(m.0, m.1, m.2) }) + } else { + Command::none() } - }, + } _ => { if let Some(modal) = &mut self.delete_wallet_modal { return modal.update(message); @@ -137,225 +163,153 @@ impl Launcher { let content = Into::>::into(scrollable( Column::new() .push( - Container::new(image::liana_brand_grey().width(Length::Fixed(200.0))) + Row::new() + .spacing(20) + .push( + Container::new(image::liana_brand_grey().width(Length::Fixed(200.0))) + .width(Length::Fill), + ) + .push( + button::secondary(None, "Share Xpubs") + .on_press(ViewMessage::ShareXpubs), + ) + .push( + pick_list( + &NETWORKS[..], + Some(self.network), + ViewMessage::SelectNetwork, + ) + .style(theme::PickList::Simple) + .padding(10), + ) + .align_items(Alignment::Center) .padding(100), ) .push( Container::new( Column::new() + .align_items(Alignment::Center) .spacing(30) - .push(if !self.is_fresh_install() { + .push(if matches!(self.state, State::Wallet { .. }) { text("Welcome back").size(50).bold() } else { text("Welcome").size(50).bold() }) .push_maybe(self.error.as_ref().map(|e| card::simple(text(e)))) - .push(if self.is_fresh_install() { - Column::new() - .spacing(10) - .push( - Button::new( - Row::new() - .spacing(20) - .align_items(Alignment::Center) - .push( - badge::Badge::new(icon::bitcoin_icon()) - .style(theme::Badge::Bitcoin), - ) - .push(text(format!( - "Create wallet on {}", - wallet_name(&Network::Bitcoin) - ))), - ) - .on_press(ViewMessage::StartInstall(Network::Bitcoin)) - .padding(10) - .width(Length::Fixed(400.0)) - .style(theme::Button::Border), - ) - .push(if !self.collapsed { - Column::new().push( + .push(match &self.state { + State::Unchecked => Column::new(), + State::Wallet { + name, + email, + checksum, + } => Column::new().push( + Row::new() + .align_items(Alignment::Center) + .spacing(20) + .push( Button::new( - Row::new() - .spacing(20) - .align_items(Alignment::Center) - .push(badge::Badge::new(icon::plus_icon())) - .push(text("Create wallet on another network")), - ) - .on_press(ViewMessage::ShowUninstalledNetworks) - .padding(10) - .width(Length::Fixed(400.0)) - .style(theme::Button::TransparentBorder), - ) - } else { - self.choices - .iter() - .filter_map(|(net, installed)| { - if *installed || *net == Network::Bitcoin { - None - } else { - Some(net) - } - }) - .fold(Column::new().spacing(10), |col, choice| { - col.push( - Button::new( + Column::new() + .push(p1_bold(if let Some(name) = name { + name + } else { + "Wallet" + })) + .push_maybe(checksum.as_ref().map(|checksum| { + p1_regular(format!("Liana-{}", checksum)) + .style(color::GREY_3) + })) + .push_maybe(email.as_ref().map(|email| { Row::new() - .spacing(20) - .align_items(Alignment::Center) + .push(Space::with_width(Length::Fill)) .push( - badge::Badge::new( - icon::bitcoin_icon(), - ) - .style(theme::Badge::Standard), + p1_regular(email) + .style(color::GREEN), ) - .push(text(format!( - "Create wallet on {}", - wallet_name(choice) - ))), - ) - .on_press(ViewMessage::StartInstall(*choice)) - .padding(10) - .width(Length::Fixed(400.0)) - .style(theme::Button::Border), - ) - }) - }) - } else { - Column::new() - .spacing(10) - .push( - self.choices - .iter() - .filter_map( - |(net, installed)| { - if *installed { - Some(net) - } else { - None - } - }, - ) - .fold( - Column::new() - .spacing(10), - |col, choice| { - col.push( - Row::new() - .spacing(10) - .push( - Button::new( - Row::new() - .spacing(20) - .align_items(Alignment::Center) - .push( - badge::Badge::new( - icon::bitcoin_icon(), - ) - .style(match choice { - Network::Bitcoin => { - theme::Badge::Bitcoin - } - _ => theme::Badge::Standard, - }), - ) - .push(text(format!("Open wallet on {}", choice))), - ) - .on_press(ViewMessage::Check(*choice)) - .padding(10) - .width(Length::Fixed(400.0)) - .style(theme::Button::Border), - ) - .push(tooltip::Tooltip::new( - Button::new(icon::trash_icon()) - .on_press(ViewMessage::DeleteWallet( - DeleteWalletMessage::ShowModal( - *choice, - ), - )) - .style( - theme::Button::SecondaryDestructive, - ), - "Delete wallet", - tooltip::Position::Right, - )) - .align_items(Alignment::Center), + })), ) - }, - ), - ) + .on_press(ViewMessage::Run) + .style(theme::Button::Border) + .padding(10) + .width(Length::Fill), + ) + .push( + Button::new(icon::trash_icon()) + .style(theme::Button::Secondary) + .padding(10) + .on_press(ViewMessage::DeleteWallet( + DeleteWalletMessage::ShowModal, + )), + ), + ), + State::NoWallet => Column::new() .push( - if !self.collapsed - && self.choices.iter().any(|(_, installed)| !installed) - { - Column::new().push( - Button::new( - Row::new() + Row::new() + .align_items(Alignment::End) + .spacing(20) + .push( + Container::new( + Column::new() .spacing(20) .align_items(Alignment::Center) - .push(badge::Badge::new(icon::plus_icon())) - .push(text("Create a new wallet")), + .push( + image::create_new_wallet_icon() + .width(Length::Fixed(100.0)), + ) + .push( + p1_regular("Create a new wallet") + .style(color::GREY_3), + ) + .push( + button::secondary(None, "Select") + .width(Length::Fixed(200.0)) + .on_press( + ViewMessage::CreateWallet, + ), + ) + .align_items(Alignment::Center), ) - .on_press(ViewMessage::ShowUninstalledNetworks) - .padding(10) - .width(Length::Fixed(400.0)) - .style(theme::Button::TransparentBorder), + .padding(20), ) - } else if self.collapsed { - self.choices - .iter() - .filter_map(|(net, installed)| { - if *installed { - None - } else { - Some(net) - } - }) - .fold( + .push( + Container::new( Column::new() - .spacing(10), - |col, choice| { - col.push( - Button::new( - Row::new() - .spacing(20) - .align_items(Alignment::Center) - .push( - badge::Badge::new( - icon::bitcoin_icon(), - ) - .style(match choice { - Network::Bitcoin => { - theme::Badge::Bitcoin - } - _ => theme::Badge::Standard, - }), - ) - .push(text(format!("Create wallet on {}", wallet_name(choice)))), - ) - .on_press(ViewMessage::StartInstall(*choice)) - .padding(10) - .width(Length::Fixed(400.0)) - .style(theme::Button::Border), - ) - }, + .spacing(20) + .align_items(Alignment::Center) + .push( + image::restore_wallet_icon() + .width(Length::Fixed(100.0)), + ) + .push( + p1_regular("Add an existing wallet") + .style(color::GREY_3), + ) + .push( + button::secondary(None, "Select") + .width(Length::Fixed(200.0)) + .on_press( + ViewMessage::ImportWallet, + ), + ) + .align_items(Alignment::Center), ) - } else { - Column::new() - }, + .padding(20), + ), ) - }) - .align_items(if self.is_fresh_install() { - Alignment::Center - } else { - Alignment::Start + .align_items(Alignment::Center), }) .max_width(500), ) .width(Length::Fill) .center_x(), - ), + ) + .push(Space::with_height(Length::Fixed(100.0))), )) .map(Message::View); + let content = if self.network != Network::Bitcoin { + Column::with_children(vec![network_banner(self.network).into(), content]).into() + } else { + content + }; if let Some(modal) = &self.delete_wallet_modal { Modal::new(Container::new(content).height(Length::Fill), modal.view()) .on_blur(Some(Message::View(ViewMessage::DeleteWallet( @@ -371,22 +325,26 @@ impl Launcher { #[derive(Debug, Clone)] pub enum Message { View(ViewMessage), - Install(PathBuf, Network), - Checked(Result), + Install(PathBuf, Network, UserFlow), + Checked(Result), Run(PathBuf, app::config::Config, Network), } #[derive(Debug, Clone)] pub enum ViewMessage { + ImportWallet, + CreateWallet, + ShareXpubs, + SelectNetwork(Network), StartInstall(Network), - ShowUninstalledNetworks, - Check(Network), + Check, + Run, DeleteWallet(DeleteWalletMessage), } #[derive(Debug, Clone)] pub enum DeleteWalletMessage { - ShowModal(Network), + ShowModal, CloseModal, Confirm, Deleted, @@ -436,8 +394,8 @@ impl DeleteWalletModal { } // Use separate `Row`s for help text in order to have better spacing. let help_text_1 = format!( - "Are you sure you want to delete the wallet and all associated data for {}?", - wallet_name(&self.network) + "Are you sure you want to delete the configuration and all associated data for the network {}?", + &self.network ); let help_text_2 = match self.internal_bitcoind { Some(true) => Some("(The Liana-managed Bitcoin node for this network will not be affected by this action.)"), @@ -451,7 +409,7 @@ impl DeleteWalletModal { Column::new() .spacing(10) .push(Container::new( - h4_bold(format!("Delete wallet for {}", wallet_name(&self.network))) + h4_bold(format!("Delete configuration for {}", &self.network)) .style(color::RED) .width(Length::Fill), )) @@ -483,16 +441,24 @@ impl DeleteWalletModal { } } -async fn check_network_datadir(mut path: PathBuf, network: Network) -> Result { - path.push(network.to_string()); - path.push(app::config::DEFAULT_FILE_NAME); +async fn check_network_datadir(path: PathBuf, network: Network) -> Result { + let mut config_path = path.clone(); + config_path.push(network.to_string()); + config_path.push(app::config::DEFAULT_FILE_NAME); - let cfg = app::Config::from_file(&path).map_err(|_| { - format!( - "Failed to read GUI configuration file in the directory: {}", - path.to_string_lossy() - ) - })?; + let cfg = match app::Config::from_file(&config_path) { + Ok(cfg) => cfg, + Err(e) => { + if e == app::config::ConfigError::NotFound { + return Ok(State::NoWallet); + } else { + return Err(format!( + "Failed to read GUI configuration file in the directory: {}", + path.to_string_lossy() + )); + } + } + }; if let Some(daemon_config_path) = cfg.daemon_config_path { liana::config::Config::from_file(Some(daemon_config_path.clone())).map_err(|e| match e { @@ -525,5 +491,18 @@ async fn check_network_datadir(mut path: PathBuf, network: Network) -> Result { - let launcher = Launcher::new(datadir_path); + let (launcher, command) = Launcher::new(datadir_path, None); + cmds.push(command.map(|msg| Message::Launch(Box::new(msg)))); State::Launcher(Box::new(launcher)) } - Config::Install(datadir_path, network) => { - if !datadir_path.exists() { - // datadir is created right before launching the installer - // so logs can go in /installer.log - if let Err(e) = create_directory(&datadir_path) { - error!("Failed to create datadir: {}", e); - } else { - info!( - "Created a fresh data directory at {}", - &datadir_path.to_string_lossy() - ); - } - } - logger.set_installer_mode( - datadir_path.clone(), - log_level.unwrap_or(LevelFilter::INFO), - ); - let (install, command) = Installer::new(datadir_path, network, None); - cmds.push(command.map(|msg| Message::Install(Box::new(msg)))); - State::Installer(Box::new(install)) - } Config::Run(datadir_path, cfg, network) => { logger.set_running_mode( datadir_path.clone(), @@ -229,7 +203,7 @@ impl Application for GUI { } } (State::Launcher(l), Message::Launch(msg)) => match *msg { - launcher::Message::Install(datadir_path, network) => { + launcher::Message::Install(datadir_path, network, init) => { if !datadir_path.exists() { // datadir is created right before launching the installer // so logs can go in /installer.log @@ -247,7 +221,7 @@ impl Application for GUI { self.log_level.unwrap_or(LevelFilter::INFO), ); - let (install, command) = Installer::new(datadir_path, network, None); + let (install, command) = Installer::new(datadir_path, network, None, init); self.state = State::Installer(Box::new(install)); command.map(|msg| Message::Install(Box::new(msg))) } @@ -285,14 +259,18 @@ impl Application for GUI { _ => l.update(*msg).map(|msg| Message::Launch(Box::new(msg))), }, (State::Login(l), Message::Login(msg)) => match *msg { - login::Message::View(login::ViewMessage::BackToLauncher) => { - let launcher = Launcher::new(l.datadir.clone()); + login::Message::View(login::ViewMessage::BackToLauncher(network)) => { + let (launcher, command) = Launcher::new(l.datadir.clone(), Some(network)); self.state = State::Launcher(Box::new(launcher)); - Command::none() + command.map(|msg| Message::Launch(Box::new(msg))) } login::Message::Install(remote_backend) => { - let (install, command) = - Installer::new(l.datadir.clone(), l.network, remote_backend); + let (install, command) = Installer::new( + l.datadir.clone(), + l.network, + remote_backend, + installer::UserFlow::CreateWallet, + ); self.state = State::Installer(Box::new(install)); command.map(|msg| Message::Install(Box::new(msg))) } @@ -362,19 +340,20 @@ impl Application for GUI { self.state = State::Loader(Box::new(loader)); command.map(|msg| Message::Load(Box::new(msg))) } - } else if let installer::Message::BackToLauncher = *msg { - let launcher = Launcher::new(i.destination_path()); + } else if let installer::Message::BackToLauncher(network) = *msg { + let (launcher, command) = Launcher::new(i.destination_path(), Some(network)); self.state = State::Launcher(Box::new(launcher)); - Command::none() + command.map(|msg| Message::Launch(Box::new(msg))) } else { i.update(*msg).map(|msg| Message::Install(Box::new(msg))) } } (State::Loader(loader), Message::Load(msg)) => match *msg { loader::Message::View(loader::ViewMessage::SwitchNetwork) => { - self.state = - State::Launcher(Box::new(Launcher::new(loader.datadir_path.clone()))); - Command::none() + let (launcher, command) = + Launcher::new(loader.datadir_path.clone(), Some(loader.network)); + self.state = State::Launcher(Box::new(launcher)); + command.map(|msg| Message::Launch(Box::new(msg))) } loader::Message::Synced(Ok((wallet, cache, daemon, bitcoind))) => { let (app, command) = App::new( @@ -501,7 +480,6 @@ pub fn create_app_with_remote_backend( pub enum Config { Run(PathBuf, app::Config, bitcoin::Network), Launcher(PathBuf), - Install(PathBuf, bitcoin::Network), } impl Config { @@ -515,7 +493,6 @@ impl Config { path.push(app::config::DEFAULT_FILE_NAME); match app::Config::from_file(&path) { Ok(cfg) => Ok(Config::Run(datadir_path, cfg, network)), - Err(ConfigError::NotFound) => Ok(Config::Install(datadir_path, network)), Err(e) => Err(format!("Failed to read configuration file: {}", e).into()), } } else {