Move all network setup in launcher

This commit is contained in:
edouardparis 2024-05-29 12:19:35 +02:00
parent be768b1a67
commit 9ae33408cb
6 changed files with 293 additions and 402 deletions

View File

@ -1,7 +1,4 @@
use liana::miniscript::{
bitcoin::{bip32::Fingerprint, Network},
DescriptorPublicKey,
};
use liana::miniscript::{bitcoin::bip32::Fingerprint, DescriptorPublicKey};
use std::path::PathBuf;
use super::Error;
@ -29,7 +26,6 @@ pub enum Message {
Select(usize),
UseHotSigner,
Installed(Result<PathBuf, Error>),
Network(Network),
CreateTaprootDescriptor(bool),
SelectBitcoindType(SelectBitcoindTypeMsg),
InternalBitcoind(InternalBitcoindMsg),

View File

@ -29,6 +29,7 @@ use step::{
};
pub struct Installer {
network: bitcoin::Network,
current: usize,
steps: Vec<Box<dyn Step>>,
hws: HardwareWallets,
@ -61,6 +62,7 @@ impl Installer {
) -> (Installer, Command<Message>) {
(
Installer {
network,
current: 0,
hws: HardwareWallets::new(destination_path.clone(), network),
steps: vec![Welcome::default().into()],
@ -135,7 +137,7 @@ impl Installer {
Message::CreateWallet => {
self.steps = vec![
Welcome::default().into(),
DefineDescriptor::new(self.signer.clone()).into(),
DefineDescriptor::new(self.network, self.signer.clone()).into(),
BackupMnemonic::new(self.signer.clone()).into(),
BackupDescriptor::default().into(),
RegisterDescriptor::new_create_wallet().into(),
@ -149,8 +151,8 @@ impl Installer {
Message::ParticipateWallet => {
self.steps = vec![
Welcome::default().into(),
ParticipateXpub::new(self.signer.clone()).into(),
ImportDescriptor::new(false).into(),
ParticipateXpub::new(self.network, self.signer.clone()).into(),
ImportDescriptor::new(self.network).into(),
BackupMnemonic::new(self.signer.clone()).into(),
RegisterDescriptor::new_import_wallet().into(),
SelectBitcoindTypeStep::new().into(),
@ -163,7 +165,7 @@ impl Installer {
Message::ImportWallet => {
self.steps = vec![
Welcome::default().into(),
ImportDescriptor::new(true).into(),
ImportDescriptor::new(self.network).into(),
RecoverMnemonic::default().into(),
RegisterDescriptor::new_import_wallet().into(),
SelectBitcoindTypeStep::new().into(),

View File

@ -1,6 +1,5 @@
use std::collections::{BTreeMap, HashMap, HashSet};
use std::iter::FromIterator;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
@ -196,11 +195,9 @@ impl Setup {
}
pub struct DefineDescriptor {
data_dir: Option<PathBuf>,
setup: HashMap<Network, Setup>,
setup: Setup,
network: Network,
network_valid: bool,
use_taproot: bool,
modal: Option<Box<dyn DescriptorEditModal>>,
@ -210,13 +207,11 @@ pub struct DefineDescriptor {
}
impl DefineDescriptor {
pub fn new(signer: Arc<Mutex<Signer>>) -> Self {
pub fn new(network: Network, signer: Arc<Mutex<Signer>>) -> Self {
Self {
network: Network::Bitcoin,
network,
use_taproot: false,
setup: HashMap::from([(Network::Bitcoin, Setup::new())]),
data_dir: None,
network_valid: true,
setup: Setup::new(),
modal: None,
signer,
error: None,
@ -224,25 +219,10 @@ impl DefineDescriptor {
}
fn valid(&self) -> bool {
self.setup[&self.network].valid()
self.setup.valid()
}
fn setup_mut(&mut self) -> &mut Setup {
self.setup
.get_mut(&self.network)
.expect("There is always one")
}
fn set_network(&mut self, network: Network) {
self.network = network;
if self.setup.get(&self.network).is_none() {
self.setup.insert(self.network, Setup::new());
}
self.signer.lock().unwrap().set_network(network);
if let Some(mut network_datadir) = self.data_dir.clone() {
network_datadir.push(self.network.to_string());
self.network_valid = !network_datadir.exists();
}
self.check_setup()
&mut self.setup
}
fn check_setup(&mut self) {
@ -257,16 +237,11 @@ impl Step for DefineDescriptor {
// form value is set as valid each time it is edited.
// Verification of the values is happening when the user click on Next button.
fn update(&mut self, hws: &mut HardwareWallets, message: Message) -> Command<Message> {
let network = self.network;
self.error = None;
match message {
Message::Close => {
self.modal = None;
}
Message::Network(network) => {
hws.set_network(network);
self.set_network(network)
}
Message::CreateTaprootDescriptor(use_taproot) => {
self.use_taproot = use_taproot;
self.check_setup();
@ -313,6 +288,7 @@ impl Step for DefineDescriptor {
}
message::DefineKey::Edit => {
let use_taproot = self.use_taproot;
let network = self.network;
let setup = self.setup_mut();
let modal = EditXpubModal::new(
use_taproot,
@ -424,7 +400,7 @@ impl Step for DefineDescriptor {
j,
self.network,
self.signer.clone(),
self.setup[&self.network].keys.clone(),
self.setup.keys.clone(),
);
let cmd = modal.load();
self.modal = Some(Box::new(modal));
@ -459,11 +435,6 @@ impl Step for DefineDescriptor {
Command::none()
}
fn load_context(&mut self, ctx: &Context) {
self.data_dir = Some(ctx.data_dir.clone());
self.set_network(ctx.bitcoin_config.network)
}
fn subscription(&self, hws: &HardwareWallets) -> Subscription<Message> {
if let Some(modal) = &self.modal {
modal.subscription(hws)
@ -478,9 +449,10 @@ impl Step for DefineDescriptor {
let mut hw_is_used = false;
let mut spending_keys: Vec<DescriptorPublicKey> = Vec::new();
let mut key_derivation_index = HashMap::<Fingerprint, usize>::new();
for spending_key in self.setup[&self.network].spending_keys.iter().clone() {
for spending_key in self.setup.spending_keys.iter().clone() {
let fingerprint = spending_key.expect("Must be present at this step");
let key = self.setup[&self.network]
let key = self
.setup
.keys
.iter()
.find(|key| key.key.master_fingerprint() == fingerprint)
@ -506,11 +478,12 @@ impl Step for DefineDescriptor {
let mut recovery_paths = BTreeMap::new();
for path in &self.setup[&self.network].recovery_paths {
for path in &self.setup.recovery_paths {
let mut recovery_keys: Vec<DescriptorPublicKey> = Vec::new();
for recovery_key in path.keys.iter().clone() {
let fingerprint = recovery_key.expect("Must be present at this step");
let key = self.setup[&self.network]
let key = self
.setup
.keys
.iter()
.find(|key| key.key.master_fingerprint() == fingerprint)
@ -544,14 +517,14 @@ impl Step for DefineDescriptor {
recovery_paths.insert(path.sequence, recovery_keys);
}
if !self.network_valid || spending_keys.is_empty() {
if spending_keys.is_empty() {
return false;
}
let spending_keys = if spending_keys.len() == 1 {
PathInfo::Single(spending_keys[0].clone())
} else {
PathInfo::Multi(self.setup[&self.network].spending_threshold, spending_keys)
PathInfo::Multi(self.setup.spending_threshold, spending_keys)
};
let policy = match if self.use_taproot {
@ -576,13 +549,11 @@ impl Step for DefineDescriptor {
hws: &'a HardwareWallets,
progress: (usize, usize),
) -> Element<'a, Message> {
let aliases = self.setup[&self.network].keys_aliases();
let aliases = self.setup.keys_aliases();
let content = view::define_descriptor(
progress,
self.network,
self.network_valid,
self.use_taproot,
self.setup[&self.network]
self.setup
.spending_keys
.iter()
.enumerate()
@ -590,10 +561,8 @@ impl Step for DefineDescriptor {
if let Some(key) = key {
view::defined_descriptor_key(
aliases.get(key).unwrap().to_string(),
self.setup[&self.network].duplicate_name.contains(key),
self.setup[&self.network]
.incompatible_with_tapminiscript
.contains(key),
self.setup.duplicate_name.contains(key),
self.setup.incompatible_with_tapminiscript.contains(key),
)
} else {
view::undefined_descriptor_key()
@ -605,16 +574,16 @@ impl Step for DefineDescriptor {
})
})
.collect(),
self.setup[&self.network].spending_threshold,
self.setup[&self.network]
self.setup.spending_threshold,
self.setup
.recovery_paths
.iter()
.enumerate()
.map(|(i, path)| {
path.view(
&aliases,
&self.setup[&self.network].duplicate_name,
&self.setup[&self.network].incompatible_with_tapminiscript,
&self.setup.duplicate_name,
&self.setup.incompatible_with_tapminiscript,
)
.map(move |msg| {
Message::DefineDescriptor(message::DefineDescriptor::RecoveryPath(i, msg))
@ -1133,13 +1102,6 @@ pub struct HardwareWalletXpubs {
error: Option<Error>,
}
impl HardwareWalletXpubs {
fn reset(&mut self) {
self.error = None;
self.xpubs = Vec::new();
}
}
pub struct SignerXpubs {
signer: Arc<Mutex<Signer>>,
xpubs: Vec<String>,
@ -1155,11 +1117,6 @@ impl SignerXpubs {
}
}
fn reset(&mut self) {
self.xpubs = Vec::new();
self.next_account = ChildNumber::from_hardened_idx(0).unwrap();
}
fn select(&mut self, network: Network) {
self.next_account = self.next_account.increment().unwrap();
let signer = self.signer.lock().unwrap();
@ -1180,8 +1137,6 @@ impl SignerXpubs {
pub struct ParticipateXpub {
network: Network,
network_valid: bool,
data_dir: Option<PathBuf>,
shared: bool,
@ -1190,33 +1145,14 @@ pub struct ParticipateXpub {
}
impl ParticipateXpub {
pub fn new(signer: Arc<Mutex<Signer>>) -> Self {
pub fn new(network: Network, signer: Arc<Mutex<Signer>>) -> Self {
Self {
network: Network::Bitcoin,
network_valid: true,
data_dir: None,
network,
hw_xpubs: Vec::new(),
shared: false,
xpubs_signer: SignerXpubs::new(signer),
}
}
fn set_network(&mut self, network: Network) {
if network != self.network {
self.hw_xpubs.iter_mut().for_each(|hw| hw.reset());
self.xpubs_signer.reset();
}
self.network = network;
self.xpubs_signer
.signer
.lock()
.unwrap()
.set_network(network);
if let Some(mut network_datadir) = self.data_dir.clone() {
network_datadir.push(self.network.to_string());
self.network_valid = !network_datadir.exists();
}
}
}
impl Step for ParticipateXpub {
@ -1224,10 +1160,6 @@ impl Step for ParticipateXpub {
// Verification of the values is happening when the user click on Next button.
fn update(&mut self, hws: &mut HardwareWallets, message: Message) -> Command<Message> {
match message {
Message::Network(network) => {
hws.set_network(network);
self.set_network(network);
}
Message::UserActionDone(shared) => self.shared = shared,
Message::ImportXpub(fg, res) => {
if let Some(hw_xpubs) = self.hw_xpubs.iter_mut().find(|x| x.fingerprint == fg) {
@ -1292,11 +1224,6 @@ impl Step for ParticipateXpub {
hws.refresh().map(Message::HardwareWallets)
}
fn load_context(&mut self, ctx: &Context) {
self.data_dir = Some(ctx.data_dir.clone());
self.set_network(ctx.bitcoin_config.network);
}
fn apply(&mut self, ctx: &mut Context) -> bool {
ctx.bitcoin_config.network = self.network;
// Drop connections to hardware wallets.
@ -1307,8 +1234,6 @@ impl Step for ParticipateXpub {
fn view<'a>(&'a self, hws: &'a HardwareWallets, progress: (usize, usize)) -> Element<Message> {
view::participate_xpub(
progress,
self.network,
self.network_valid,
hws.list
.iter()
.enumerate()
@ -1344,21 +1269,15 @@ impl From<ParticipateXpub> for Box<dyn Step> {
pub struct ImportDescriptor {
network: Network,
network_valid: bool,
change_network: bool,
data_dir: Option<PathBuf>,
imported_descriptor: form::Value<String>,
wrong_network: bool,
error: Option<String>,
}
impl ImportDescriptor {
pub fn new(change_network: bool) -> Self {
pub fn new(network: Network) -> Self {
Self {
change_network,
network: Network::Bitcoin,
network_valid: true,
data_dir: None,
network,
imported_descriptor: form::Value::default(),
wrong_network: false,
error: None,
@ -1397,32 +1316,13 @@ impl Step for ImportDescriptor {
// form value is set as valid each time it is edited.
// Verification of the values is happening when the user click on Next button.
fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command<Message> {
match message {
Message::Network(network) => {
self.network = network;
let mut network_datadir = self.data_dir.clone().unwrap();
network_datadir.push(self.network.to_string());
self.network_valid = !network_datadir.exists();
self.check_descriptor(self.network);
}
Message::DefineDescriptor(message::DefineDescriptor::ImportDescriptor(desc)) => {
self.imported_descriptor.value = desc;
self.check_descriptor(self.network);
}
_ => {}
};
Command::none()
}
fn load_context(&mut self, ctx: &Context) {
if ctx.bitcoin_config.network != self.network {
self.check_descriptor(ctx.bitcoin_config.network);
if let Message::DefineDescriptor(message::DefineDescriptor::ImportDescriptor(desc)) =
message
{
self.imported_descriptor.value = desc;
self.check_descriptor(self.network);
}
self.network = ctx.bitcoin_config.network;
self.data_dir = Some(ctx.data_dir.clone());
let mut network_datadir = ctx.data_dir.clone();
network_datadir.push(self.network.to_string());
self.network_valid = !network_datadir.exists();
Command::none()
}
fn apply(&mut self, ctx: &mut Context) -> bool {
@ -1441,9 +1341,6 @@ impl Step for ImportDescriptor {
fn view(&self, _hws: &HardwareWallets, progress: (usize, usize)) -> Element<Message> {
view::import_descriptor(
progress,
self.change_network,
self.network,
self.network_valid,
&self.imported_descriptor,
self.wrong_network,
self.error.as_ref(),
@ -1650,6 +1547,7 @@ impl From<BackupDescriptor> for Box<dyn Step> {
mod tests {
use super::*;
use iced_runtime::command::Action;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
pub struct Sandbox<S: Step> {
@ -1686,9 +1584,10 @@ mod tests {
#[tokio::test]
async fn test_define_descriptor_use_hotkey() {
let mut ctx = Context::new(Network::Signet, PathBuf::from_str("/").unwrap());
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(Arc::new(
Mutex::new(Signer::generate(Network::Bitcoin).unwrap()),
)));
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(
Network::Bitcoin,
Arc::new(Mutex::new(Signer::generate(Network::Bitcoin).unwrap())),
));
// Edit primary key
sandbox
@ -1767,9 +1666,10 @@ mod tests {
#[tokio::test]
async fn test_define_descriptor_stores_if_hw_is_used() {
let mut ctx = Context::new(Network::Testnet, PathBuf::from_str("/").unwrap());
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(Arc::new(
Mutex::new(Signer::generate(Network::Testnet).unwrap()),
)));
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(
Network::Testnet,
Arc::new(Mutex::new(Signer::generate(Network::Testnet).unwrap())),
));
sandbox.load(&ctx).await;
let specter_key = message::DefinePath::Key(

View File

@ -32,55 +32,6 @@ use crate::{
},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Network {
Mainnet,
Testnet,
Regtest,
Signet,
}
impl From<bitcoin::Network> for Network {
fn from(n: bitcoin::Network) -> Self {
match n {
bitcoin::Network::Bitcoin => Network::Mainnet,
bitcoin::Network::Testnet => Network::Testnet,
bitcoin::Network::Regtest => Network::Regtest,
bitcoin::Network::Signet => Network::Signet,
_ => Network::Mainnet,
}
}
}
impl From<Network> for bitcoin::Network {
fn from(network: Network) -> bitcoin::Network {
match network {
Network::Mainnet => bitcoin::Network::Bitcoin,
Network::Testnet => bitcoin::Network::Testnet,
Network::Regtest => bitcoin::Network::Regtest,
Network::Signet => bitcoin::Network::Signet,
}
}
}
impl std::fmt::Display for Network {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Mainnet => write!(f, "Bitcoin mainnet"),
Self::Testnet => write!(f, "Bitcoin testnet"),
Self::Regtest => write!(f, "Bitcoin regtest"),
Self::Signet => write!(f, "Bitcoin signet"),
}
}
}
const NETWORKS: [Network; 4] = [
Network::Mainnet,
Network::Testnet,
Network::Signet,
Network::Regtest,
];
pub fn welcome<'a>() -> Element<'a, Message> {
Container::new(
Column::new()
@ -191,31 +142,7 @@ impl std::fmt::Display for DescriptorKind {
}
#[allow(clippy::too_many_arguments)]
pub fn define_descriptor_advanced_settings<'a>(
network: bitcoin::Network,
network_valid: bool,
use_taproot: bool,
) -> Element<'a, Message> {
let col_network = Column::new()
.spacing(10)
.push(text("Network").bold())
.push(container(
pick_list(&NETWORKS[..], Some(Network::from(network)), |net| {
Message::Network(net.into())
})
.style(if network_valid {
theme::PickList::Secondary
} else {
theme::PickList::Invalid
})
.padding(10),
))
.push_maybe(if network_valid {
None
} else {
Some(text("A data directory already exists for this network").style(color::RED))
});
pub fn define_descriptor_advanced_settings<'a>(use_taproot: bool) -> Element<'a, Message> {
let col_wallet = Column::new()
.spacing(10)
.push(text("Descriptor type").bold())
@ -238,12 +165,7 @@ pub fn define_descriptor_advanced_settings<'a>(
.spacing(20)
.push(Space::with_height(0))
.push(separation().width(500))
.push(
Row::new()
.push(col_network)
.push(Space::with_width(100))
.push(col_wallet),
)
.push(Row::new().push(col_wallet))
.push_maybe(if use_taproot {
Some(
p1_regular("Taproot is only supported by Liana version 5.0 and above")
@ -259,8 +181,6 @@ pub fn define_descriptor_advanced_settings<'a>(
#[allow(clippy::too_many_arguments)]
pub fn define_descriptor<'a>(
progress: (usize, usize),
network: bitcoin::Network,
network_valid: bool,
use_taproot: bool,
spending_keys: Vec<Element<'a, Message>>,
spending_threshold: usize,
@ -329,34 +249,29 @@ pub fn define_descriptor<'a>(
progress,
"Create the wallet",
Column::new()
.push(
collapse::Collapse::new(
|| {
Button::new(
Row::new()
.align_items(Alignment::Center)
.spacing(10)
.push(text("Advanced settings").small().bold())
.push(icon::collapse_icon()),
)
.style(theme::Button::Transparent)
},
|| {
Button::new(
Row::new()
.align_items(Alignment::Center)
.spacing(10)
.push(text("Advanced settings").small().bold())
.push(icon::collapsed_icon()),
)
.style(theme::Button::Transparent)
},
move || {
define_descriptor_advanced_settings(network, network_valid, use_taproot)
},
)
.collapsed(!network_valid),
)
.push(collapse::Collapse::new(
|| {
Button::new(
Row::new()
.align_items(Alignment::Center)
.spacing(10)
.push(text("Advanced settings").small().bold())
.push(icon::collapse_icon()),
)
.style(theme::Button::Transparent)
},
|| {
Button::new(
Row::new()
.align_items(Alignment::Center)
.spacing(10)
.push(text("Advanced settings").small().bold())
.push(icon::collapsed_icon()),
)
.style(theme::Button::Transparent)
},
move || define_descriptor_advanced_settings(use_taproot),
))
.push(
Column::new()
.width(Length::Fill)
@ -384,7 +299,7 @@ pub fn define_descriptor<'a>(
))
.width(Length::Fixed(200.0)),
)
.push(if !valid || !network_valid {
.push(if !valid {
button::primary(None, "Next").width(Length::Fixed(200.0))
} else {
button::primary(None, "Next")
@ -455,33 +370,10 @@ pub fn recovery_path_view(
pub fn import_descriptor<'a>(
progress: (usize, usize),
change_network: bool,
network: bitcoin::Network,
network_valid: bool,
imported_descriptor: &form::Value<String>,
wrong_network: bool,
error: Option<&String>,
) -> Element<'a, Message> {
let row_network = Row::new()
.spacing(10)
.align_items(Alignment::Center)
.push(text("Network:").bold())
.push(Container::new(
pick_list(&NETWORKS[..], Some(Network::from(network)), |net| {
Message::Network(net.into())
})
.style(if network_valid {
theme::PickList::Simple
} else {
theme::PickList::Invalid
})
.padding(10),
))
.push_maybe(if network_valid {
None
} else {
Some(text("A data directory already exists for this network").style(color::RED))
});
let col_descriptor = Column::new()
.push(text("Descriptor:").bold())
.push(
@ -501,28 +393,13 @@ pub fn import_descriptor<'a>(
progress,
"Import the wallet",
Column::new()
.push(
Column::new()
.spacing(20)
.push_maybe(if change_network {
Some(row_network)
} else {
None
})
.push(col_descriptor)
.push_maybe(if change_network {
// only show message when importing a descriptor
Some(text(
"After creating the wallet, \
.push(Column::new().spacing(20).push(col_descriptor).push(text(
"After creating the wallet, \
you will need to perform a rescan of \
the blockchain in order to see your \
coins and past transactions. This can \
be done in Settings > Bitcoin Core.",
))
} else {
None
}),
)
)))
.push(
if imported_descriptor.value.is_empty() || !imported_descriptor.valid {
button::primary(None, "Next").width(Length::Fixed(200.0))
@ -684,43 +561,14 @@ pub fn hardware_wallet_xpubs<'a>(
pub fn participate_xpub<'a>(
progress: (usize, usize),
network: bitcoin::Network,
network_valid: bool,
hws: Vec<Element<'a, Message>>,
signer: Element<'a, Message>,
shared: bool,
) -> Element<'a, Message> {
let row_network = Row::new()
.spacing(10)
.align_items(Alignment::Center)
.push(text("Network:").bold())
.push(Container::new(
pick_list(&NETWORKS[..], Some(Network::from(network)), |net| {
Message::Network(net.into())
})
.style(if network_valid {
theme::PickList::Simple
} else {
theme::PickList::Invalid
})
.padding(10),
))
.push_maybe(if network_valid {
None
} else {
Some(text("A data directory already exists for this network").style(color::RED))
});
layout(
progress,
"Share your public keys",
Column::new()
.push(
Column::new()
.spacing(20)
.width(Length::Fill)
.push(row_network),
)
.push(
Column::new()
.push(
@ -739,7 +587,7 @@ pub fn participate_xpub<'a>(
checkbox("I have shared my extended public key", shared)
.on_toggle(Message::UserActionDone),
)
.push(if shared && network_valid {
.push(if shared {
button::primary(None, "Next")
.width(Length::Fixed(200.0))
.on_press(Message::Next)

View File

@ -2,7 +2,7 @@ use std::path::PathBuf;
use iced::{
alignment::Horizontal,
widget::{tooltip, Space},
widget::{scrollable, tooltip},
Alignment, Command, Length, Subscription,
};
@ -28,33 +28,37 @@ fn wallet_name(network: &Network) -> String {
}
pub struct Launcher {
choices: Vec<Network>,
// true if installed
choices: Vec<(Network, bool)>,
datadir_path: PathBuf,
error: Option<String>,
delete_wallet_modal: Option<DeleteWalletModal>,
collapsed: bool,
}
impl Launcher {
pub fn new(datadir_path: PathBuf) -> Self {
let mut choices = Vec::new();
for network in [
Network::Bitcoin,
Network::Testnet,
Network::Signet,
Network::Regtest,
] {
if datadir_path.join(network.to_string()).exists() {
choices.push(network)
}
}
Self {
choices: [
Network::Bitcoin,
Network::Testnet,
Network::Signet,
Network::Regtest,
]
.iter()
.map(|net| (*net, datadir_path.join(net.to_string()).exists()))
.collect(),
datadir_path,
choices,
error: None,
delete_wallet_modal: None,
collapsed: false,
}
}
fn is_fresh_install(&self) -> bool {
!self.choices.iter().any(|(_, installed)| *installed)
}
pub fn stop(&mut self) {}
pub fn subscription(&self) -> Subscription<Message> {
@ -63,9 +67,15 @@ impl Launcher {
pub fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::View(ViewMessage::StartInstall) => {
Message::View(ViewMessage::ShowUninstalledNetworks) => {
self.collapsed = true;
Command::none()
}
Message::View(ViewMessage::StartInstall(net)) => {
let datadir_path = self.datadir_path.clone();
Command::perform(async move { datadir_path }, Message::Install)
Command::perform(async move { (datadir_path, net) }, |(d, n)| {
Message::Install(d, n)
})
}
Message::View(ViewMessage::Check(network)) => Command::perform(
check_network_datadir(self.datadir_path.clone(), network),
@ -88,11 +98,9 @@ impl Launcher {
}
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Deleted)) => {
if let Some(modal) = &self.delete_wallet_modal {
let choices = self.choices.clone();
self.choices = choices
.into_iter()
.filter(|c| c != &modal.network)
.collect();
if let Some(choice) = self.choices.iter_mut().find(|c| c.0 == modal.network) {
choice.1 = false;
}
}
Command::none()
}
@ -126,7 +134,7 @@ impl Launcher {
}
pub fn view(&self) -> Element<Message> {
let content = Into::<Element<ViewMessage>>::into(
let content = Into::<Element<ViewMessage>>::into(scrollable(
Column::new()
.push(
Container::new(image::liana_brand_grey().width(Length::Fixed(200.0)))
@ -136,17 +144,107 @@ impl Launcher {
Container::new(
Column::new()
.spacing(30)
.push(text("Welcome back").size(50).bold())
.push(if !self.is_fresh_install() {
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(
self.choices
.iter()
.fold(
Column::new()
.push(text("Select network:").small().bold())
.spacing(10),
|col, choice| {
col.push(
.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(
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()
.push(
text("Open an existing wallet:")
.small()
.bold(),
)
.spacing(10),
|col, choice| {
col.push(
Row::new()
.spacing(10)
.push(
@ -169,7 +267,7 @@ impl Launcher {
)
.on_press(ViewMessage::Check(*choice))
.padding(10)
.width(Length::Fill)
.width(Length::Fixed(400.0))
.style(theme::Button::Border),
)
.push(tooltip::Tooltip::new(
@ -187,35 +285,89 @@ impl Launcher {
))
.align_items(Alignment::Center),
)
},
},
),
)
.push(
Button::new(
Row::new()
.spacing(20)
.align_items(Alignment::Center)
.push(badge::Badge::new(icon::plus_icon()))
.push(text("Install Liana on another network")),
)
.on_press(ViewMessage::StartInstall)
.padding(10)
.width(Length::Fill)
.style(theme::Button::TransparentBorder),
),
)
.max_width(500)
.align_items(Alignment::Center),
if !self.collapsed
&& self.choices.iter().any(|(_, installed)| !installed)
{
Column::new().push(
Button::new(
Row::new()
.spacing(20)
.align_items(Alignment::Center)
.push(badge::Badge::new(icon::plus_icon()))
.push(text("Create a new wallet")),
)
.on_press(ViewMessage::ShowUninstalledNetworks)
.padding(10)
.width(Length::Fixed(400.0))
.style(theme::Button::TransparentBorder),
)
} else if self.collapsed {
self.choices
.iter()
.filter_map(|(net, installed)| {
if *installed {
None
} else {
Some(net)
}
})
.fold(
Column::new()
.push(
text("Create a new wallet:")
.small()
.bold(),
)
.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(wallet_name(choice))),
)
.on_press(ViewMessage::StartInstall(*choice))
.padding(10)
.width(Length::Fixed(400.0))
.style(theme::Button::Border),
)
},
)
} else {
Column::new()
},
)
})
.align_items(if self.is_fresh_install() {
Alignment::Center
} else {
Alignment::Start
})
.max_width(500),
)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y(),
)
.push(Space::with_height(Length::Fixed(100.0))),
)
.center_x(),
),
))
.map(Message::View);
if let Some(modal) = &self.delete_wallet_modal {
Modal::new(content, modal.view())
Modal::new(Container::new(content).height(Length::Fill), modal.view())
.on_blur(Some(Message::View(ViewMessage::DeleteWallet(
DeleteWalletMessage::CloseModal,
))))
@ -229,14 +381,15 @@ impl Launcher {
#[derive(Debug, Clone)]
pub enum Message {
View(ViewMessage),
Install(PathBuf),
Install(PathBuf, Network),
Checked(Result<Network, String>),
Run(PathBuf, app::config::Config, Network),
}
#[derive(Debug, Clone)]
pub enum ViewMessage {
StartInstall,
StartInstall(Network),
ShowUninstalledNetworks,
Check(Network),
DeleteWallet(DeleteWalletMessage),
}

View File

@ -198,13 +198,12 @@ impl Application for GUI {
}
}
(State::Launcher(l), Message::Launch(msg)) => match *msg {
launcher::Message::Install(datadir_path) => {
launcher::Message::Install(datadir_path, network) => {
self.logger.set_installer_mode(
datadir_path.clone(),
self.log_level.unwrap_or(LevelFilter::INFO),
);
let (install, command) =
Installer::new(datadir_path, bitcoin::Network::Bitcoin);
let (install, command) = Installer::new(datadir_path, network);
self.state = State::Installer(Box::new(install));
command.map(|msg| Message::Install(Box::new(msg)))
}
@ -364,13 +363,6 @@ impl Config {
Err(ConfigError::NotFound) => Ok(Config::Install(datadir_path, network)),
Err(e) => Err(format!("Failed to read configuration file: {}", e).into()),
}
} else if !datadir_path.exists()
|| (!datadir_path.join("bitcoin").exists()
&& !datadir_path.join("testnet").exists()
&& !datadir_path.join("signet").exists()
&& !datadir_path.join("regtest").exists())
{
Ok(Config::Install(datadir_path, bitcoin::Network::Bitcoin))
} else {
Ok(Config::Launcher(datadir_path))
}