Skip to content

Commit

Permalink
Merge #864: gui: add option to delete wallet
Browse files Browse the repository at this point in the history
279c1c7 gui: add option to delete wallet (jp1ac4)

Pull request description:

  This is to resolve #543.

  I've used a trash icon instead of a cross, but can change it to a cross if preferred.

ACKs for top commit:
  edouardparis:
    ACK 279c1c7

Tree-SHA512: d6bdbf7894726be5e1eb0b6d62d3689d1828222deba3ae84814396ae7e2b1aa144ff12908ecc1d8e1f26fc3cc29e0d137200d3039eeb870226feabb7a8aec428
  • Loading branch information
darosior committed Dec 11, 2023
2 parents 23e834a + 279c1c7 commit 3e0f82a
Showing 1 changed file with 202 additions and 30 deletions.
232 changes: 202 additions & 30 deletions gui/src/launcher.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
use std::path::PathBuf;

use iced::{widget::Space, Alignment, Command, Length, Subscription};
use iced::{
widget::{tooltip, Space},
Alignment, Command, Length, Subscription,
};

use liana::{config::ConfigError, miniscript::bitcoin::Network};
use liana_ui::{
component::{badge, card, text::*},
color,
component::{badge, button, card, modal::Modal, notification, text::*},
icon, image, theme,
util::*,
widget::*,
};

use crate::app;

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()
}

pub struct Launcher {
choices: Vec<Network>,
datadir_path: PathBuf,
error: Option<String>,
delete_wallet_modal: Option<DeleteWalletModal>,
}

impl Launcher {
Expand All @@ -35,6 +51,7 @@ impl Launcher {
datadir_path,
choices,
error: None,
delete_wallet_modal: None,
}
}

Expand All @@ -54,6 +71,35 @@ impl Launcher {
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());
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)
} else {
None
};
self.delete_wallet_modal = Some(DeleteWalletModal::new(
network,
wallet_datadir,
internal_bitcoind,
));
Command::none()
}
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();
}
Command::none()
}
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::CloseModal)) => {
self.delete_wallet_modal = None;
Command::none()
}
Message::Checked(res) => match res {
Err(e) => {
self.error = Some(e);
Expand All @@ -70,12 +116,17 @@ impl Launcher {
})
}
},
_ => Command::none(),
_ => {
if let Some(modal) = &mut self.delete_wallet_modal {
return modal.update(message);
}
Command::none()
}
}
}

pub fn view(&self) -> Element<Message> {
Into::<Element<ViewMessage>>::into(
let content = Into::<Element<ViewMessage>>::into(
Column::new()
.push(
Container::new(image::liana_brand_grey().width(Length::Fixed(200.0)))
Expand All @@ -96,31 +147,43 @@ impl Launcher {
.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,
}),
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(wallet_name(choice))),
)
.push(text(match choice {
Network::Bitcoin => "Bitcoin Mainnet",
Network::Testnet => "Bitcoin Testnet",
Network::Signet => "Bitcoin Signet",
Network::Regtest => "Bitcoin Regtest",
_ => "Bitcoin unknown",
})),
)
.on_press(ViewMessage::Check(*choice))
.padding(10)
.width(Length::Fill)
.style(theme::Button::Border),
.on_press(ViewMessage::Check(*choice))
.padding(10)
.width(Length::Fill)
.style(theme::Button::Border),
)
.push(tooltip::Tooltip::new(
Button::new(icon::trash_icon())
.on_press(ViewMessage::DeleteWallet(
DeleteWalletMessage::ShowModal(
*choice,
),
))
.style(theme::Button::Destructive),
"Delete wallet",
tooltip::Position::Right,
))
.align_items(Alignment::Center),
)
},
)
Expand Down Expand Up @@ -148,11 +211,20 @@ impl Launcher {
)
.push(Space::with_height(Length::Fixed(100.0))),
)
.map(Message::View)
.map(Message::View);
if let Some(modal) = &self.delete_wallet_modal {
Modal::new(content, modal.view())
.on_blur(Some(Message::View(ViewMessage::DeleteWallet(
DeleteWalletMessage::CloseModal,
))))
.into()
} else {
content
}
}
}

#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Message {
View(ViewMessage),
Install(PathBuf),
Expand All @@ -164,6 +236,106 @@ pub enum Message {
pub enum ViewMessage {
StartInstall,
Check(Network),
DeleteWallet(DeleteWalletMessage),
}

#[derive(Debug, Clone)]
pub enum DeleteWalletMessage {
ShowModal(Network),
CloseModal,
Confirm,
Deleted,
}

struct DeleteWalletModal {
network: Network,
wallet_datadir: PathBuf,
warning: Option<std::io::Error>,
deleted: bool,
// `None` means we were not able to determine whether wallet uses internal bitcoind.
internal_bitcoind: Option<bool>,
}

impl DeleteWalletModal {
fn new(network: Network, wallet_datadir: PathBuf, internal_bitcoind: Option<bool>) -> Self {
Self {
network,
wallet_datadir,
warning: None,
deleted: false,
internal_bitcoind,
}
}

fn update(&mut self, message: Message) -> Command<Message> {
if let Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Confirm)) = message {
self.warning = None;
if let Err(e) = std::fs::remove_dir_all(&self.wallet_datadir) {
self.warning = Some(e);
} else {
self.deleted = true;
return Command::perform(async {}, |_| {
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Deleted))
});
};
}
Command::none()
}
fn view(&self) -> Element<Message> {
let mut confirm_button = button::primary(None, "Delete wallet")
.width(Length::Fixed(200.0))
.style(theme::Button::Destructive);
if self.warning.is_none() {
confirm_button =
confirm_button.on_press(ViewMessage::DeleteWallet(DeleteWalletMessage::Confirm));
}
// 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)
);
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.)"),
Some(false) => None,
None => Some("(If you are using a Liana-managed Bitcoin node, it will not be affected by this action.)"),
};
let help_text_3 = "WARNING: This cannot be undone.";

Into::<Element<ViewMessage>>::into(
card::simple(
Column::new()
.spacing(10)
.push(Container::new(
h4_bold(format!("Delete wallet for {}", wallet_name(&self.network)))
.style(color::RED)
.width(Length::Fill),
))
.push(Row::new().push(text(help_text_1)))
.push_maybe(
help_text_2.map(|t| Row::new().push(p1_regular(t).style(color::GREY_3))),
)
.push(Row::new())
.push(Row::new().push(text(help_text_3)))
.push_maybe(self.warning.as_ref().map(|w| {
notification::warning(w.to_string(), w.to_string()).width(Length::Fill)
}))
.push(
Container::new(if !self.deleted {
Row::new().push(confirm_button)
} else {
Row::new()
.spacing(10)
.push(icon::circle_check_icon().style(color::GREEN))
.push(text("Wallet successfully deleted").style(color::GREEN))
})
.align_x(iced_native::alignment::Horizontal::Center)
.width(Length::Fill),
),
)
.width(Length::Fixed(700.0)),
)
.map(Message::View)
}
}

async fn check_network_datadir(mut path: PathBuf, network: Network) -> Result<Network, String> {
Expand Down

0 comments on commit 3e0f82a

Please sign in to comment.