diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index 45744183..744efd28 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 8d4c8157..ab0526bf 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 b1b3fc95..89cf6361 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 79b2fbbb..ab6bbf7f 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 85c31b6d..937237d4 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::StartInstall(net)) => { + Message::View(ViewMessage::ImportWallet) => { 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::AddWallet) }) } - 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::CreateWallet) => { + 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::CreateWallet) + }) + } + 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( - Row::new() - .spacing(20) - .align_items(Alignment::Center) - .push( - badge::Badge::new( - icon::bitcoin_icon(), - ) - .style(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), - ) - }) - }) - } 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), + .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() + .push(Space::with_width(Length::Fill)) + .push( + p1_regular(email) + .style(color::GREEN), + ) + })), ) - }, - ), - ) + .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 {