Skip to content

Commit 3e0f82a

Browse files
committed
Merge #864: gui: add option to delete wallet
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
2 parents 23e834a + 279c1c7 commit 3e0f82a

File tree

1 file changed

+202
-30
lines changed

1 file changed

+202
-30
lines changed

gui/src/launcher.rs

+202-30
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
11
use std::path::PathBuf;
22

3-
use iced::{widget::Space, Alignment, Command, Length, Subscription};
3+
use iced::{
4+
widget::{tooltip, Space},
5+
Alignment, Command, Length, Subscription,
6+
};
47

58
use liana::{config::ConfigError, miniscript::bitcoin::Network};
69
use liana_ui::{
7-
component::{badge, card, text::*},
10+
color,
11+
component::{badge, button, card, modal::Modal, notification, text::*},
812
icon, image, theme,
913
util::*,
1014
widget::*,
1115
};
1216

1317
use crate::app;
1418

19+
fn wallet_name(network: &Network) -> String {
20+
match network {
21+
Network::Bitcoin => "Bitcoin Mainnet",
22+
Network::Testnet => "Bitcoin Testnet",
23+
Network::Signet => "Bitcoin Signet",
24+
Network::Regtest => "Bitcoin Regtest",
25+
_ => "Bitcoin unknown",
26+
}
27+
.to_string()
28+
}
29+
1530
pub struct Launcher {
1631
choices: Vec<Network>,
1732
datadir_path: PathBuf,
1833
error: Option<String>,
34+
delete_wallet_modal: Option<DeleteWalletModal>,
1935
}
2036

2137
impl Launcher {
@@ -35,6 +51,7 @@ impl Launcher {
3551
datadir_path,
3652
choices,
3753
error: None,
54+
delete_wallet_modal: None,
3855
}
3956
}
4057

@@ -54,6 +71,35 @@ impl Launcher {
5471
check_network_datadir(self.datadir_path.clone(), network),
5572
Message::Checked,
5673
),
74+
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::ShowModal(network))) => {
75+
let wallet_datadir = self.datadir_path.join(network.to_string());
76+
let config_path = wallet_datadir.join(app::config::DEFAULT_FILE_NAME);
77+
let internal_bitcoind = if let Ok(cfg) = app::Config::from_file(&config_path) {
78+
Some(cfg.start_internal_bitcoind)
79+
} else {
80+
None
81+
};
82+
self.delete_wallet_modal = Some(DeleteWalletModal::new(
83+
network,
84+
wallet_datadir,
85+
internal_bitcoind,
86+
));
87+
Command::none()
88+
}
89+
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Deleted)) => {
90+
if let Some(modal) = &self.delete_wallet_modal {
91+
let choices = self.choices.clone();
92+
self.choices = choices
93+
.into_iter()
94+
.filter(|c| c != &modal.network)
95+
.collect();
96+
}
97+
Command::none()
98+
}
99+
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::CloseModal)) => {
100+
self.delete_wallet_modal = None;
101+
Command::none()
102+
}
57103
Message::Checked(res) => match res {
58104
Err(e) => {
59105
self.error = Some(e);
@@ -70,12 +116,17 @@ impl Launcher {
70116
})
71117
}
72118
},
73-
_ => Command::none(),
119+
_ => {
120+
if let Some(modal) = &mut self.delete_wallet_modal {
121+
return modal.update(message);
122+
}
123+
Command::none()
124+
}
74125
}
75126
}
76127

77128
pub fn view(&self) -> Element<Message> {
78-
Into::<Element<ViewMessage>>::into(
129+
let content = Into::<Element<ViewMessage>>::into(
79130
Column::new()
80131
.push(
81132
Container::new(image::liana_brand_grey().width(Length::Fixed(200.0)))
@@ -96,31 +147,43 @@ impl Launcher {
96147
.spacing(10),
97148
|col, choice| {
98149
col.push(
99-
Button::new(
100-
Row::new()
101-
.spacing(20)
102-
.align_items(Alignment::Center)
103-
.push(
104-
badge::Badge::new(icon::bitcoin_icon())
105-
.style(match choice {
106-
Network::Bitcoin => {
107-
theme::Badge::Bitcoin
108-
}
109-
_ => theme::Badge::Standard,
110-
}),
150+
Row::new()
151+
.spacing(10)
152+
.push(
153+
Button::new(
154+
Row::new()
155+
.spacing(20)
156+
.align_items(Alignment::Center)
157+
.push(
158+
badge::Badge::new(
159+
icon::bitcoin_icon(),
160+
)
161+
.style(match choice {
162+
Network::Bitcoin => {
163+
theme::Badge::Bitcoin
164+
}
165+
_ => theme::Badge::Standard,
166+
}),
167+
)
168+
.push(text(wallet_name(choice))),
111169
)
112-
.push(text(match choice {
113-
Network::Bitcoin => "Bitcoin Mainnet",
114-
Network::Testnet => "Bitcoin Testnet",
115-
Network::Signet => "Bitcoin Signet",
116-
Network::Regtest => "Bitcoin Regtest",
117-
_ => "Bitcoin unknown",
118-
})),
119-
)
120-
.on_press(ViewMessage::Check(*choice))
121-
.padding(10)
122-
.width(Length::Fill)
123-
.style(theme::Button::Border),
170+
.on_press(ViewMessage::Check(*choice))
171+
.padding(10)
172+
.width(Length::Fill)
173+
.style(theme::Button::Border),
174+
)
175+
.push(tooltip::Tooltip::new(
176+
Button::new(icon::trash_icon())
177+
.on_press(ViewMessage::DeleteWallet(
178+
DeleteWalletMessage::ShowModal(
179+
*choice,
180+
),
181+
))
182+
.style(theme::Button::Destructive),
183+
"Delete wallet",
184+
tooltip::Position::Right,
185+
))
186+
.align_items(Alignment::Center),
124187
)
125188
},
126189
)
@@ -148,11 +211,20 @@ impl Launcher {
148211
)
149212
.push(Space::with_height(Length::Fixed(100.0))),
150213
)
151-
.map(Message::View)
214+
.map(Message::View);
215+
if let Some(modal) = &self.delete_wallet_modal {
216+
Modal::new(content, modal.view())
217+
.on_blur(Some(Message::View(ViewMessage::DeleteWallet(
218+
DeleteWalletMessage::CloseModal,
219+
))))
220+
.into()
221+
} else {
222+
content
223+
}
152224
}
153225
}
154226

155-
#[derive(Debug)]
227+
#[derive(Debug, Clone)]
156228
pub enum Message {
157229
View(ViewMessage),
158230
Install(PathBuf),
@@ -164,6 +236,106 @@ pub enum Message {
164236
pub enum ViewMessage {
165237
StartInstall,
166238
Check(Network),
239+
DeleteWallet(DeleteWalletMessage),
240+
}
241+
242+
#[derive(Debug, Clone)]
243+
pub enum DeleteWalletMessage {
244+
ShowModal(Network),
245+
CloseModal,
246+
Confirm,
247+
Deleted,
248+
}
249+
250+
struct DeleteWalletModal {
251+
network: Network,
252+
wallet_datadir: PathBuf,
253+
warning: Option<std::io::Error>,
254+
deleted: bool,
255+
// `None` means we were not able to determine whether wallet uses internal bitcoind.
256+
internal_bitcoind: Option<bool>,
257+
}
258+
259+
impl DeleteWalletModal {
260+
fn new(network: Network, wallet_datadir: PathBuf, internal_bitcoind: Option<bool>) -> Self {
261+
Self {
262+
network,
263+
wallet_datadir,
264+
warning: None,
265+
deleted: false,
266+
internal_bitcoind,
267+
}
268+
}
269+
270+
fn update(&mut self, message: Message) -> Command<Message> {
271+
if let Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Confirm)) = message {
272+
self.warning = None;
273+
if let Err(e) = std::fs::remove_dir_all(&self.wallet_datadir) {
274+
self.warning = Some(e);
275+
} else {
276+
self.deleted = true;
277+
return Command::perform(async {}, |_| {
278+
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Deleted))
279+
});
280+
};
281+
}
282+
Command::none()
283+
}
284+
fn view(&self) -> Element<Message> {
285+
let mut confirm_button = button::primary(None, "Delete wallet")
286+
.width(Length::Fixed(200.0))
287+
.style(theme::Button::Destructive);
288+
if self.warning.is_none() {
289+
confirm_button =
290+
confirm_button.on_press(ViewMessage::DeleteWallet(DeleteWalletMessage::Confirm));
291+
}
292+
// Use separate `Row`s for help text in order to have better spacing.
293+
let help_text_1 = format!(
294+
"Are you sure you want to delete the wallet and all associated data for {}?",
295+
wallet_name(&self.network)
296+
);
297+
let help_text_2 = match self.internal_bitcoind {
298+
Some(true) => Some("(The Liana-managed Bitcoin node for this network will not be affected by this action.)"),
299+
Some(false) => None,
300+
None => Some("(If you are using a Liana-managed Bitcoin node, it will not be affected by this action.)"),
301+
};
302+
let help_text_3 = "WARNING: This cannot be undone.";
303+
304+
Into::<Element<ViewMessage>>::into(
305+
card::simple(
306+
Column::new()
307+
.spacing(10)
308+
.push(Container::new(
309+
h4_bold(format!("Delete wallet for {}", wallet_name(&self.network)))
310+
.style(color::RED)
311+
.width(Length::Fill),
312+
))
313+
.push(Row::new().push(text(help_text_1)))
314+
.push_maybe(
315+
help_text_2.map(|t| Row::new().push(p1_regular(t).style(color::GREY_3))),
316+
)
317+
.push(Row::new())
318+
.push(Row::new().push(text(help_text_3)))
319+
.push_maybe(self.warning.as_ref().map(|w| {
320+
notification::warning(w.to_string(), w.to_string()).width(Length::Fill)
321+
}))
322+
.push(
323+
Container::new(if !self.deleted {
324+
Row::new().push(confirm_button)
325+
} else {
326+
Row::new()
327+
.spacing(10)
328+
.push(icon::circle_check_icon().style(color::GREEN))
329+
.push(text("Wallet successfully deleted").style(color::GREEN))
330+
})
331+
.align_x(iced_native::alignment::Horizontal::Center)
332+
.width(Length::Fill),
333+
),
334+
)
335+
.width(Length::Fixed(700.0)),
336+
)
337+
.map(Message::View)
338+
}
167339
}
168340

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

0 commit comments

Comments
 (0)