1
1
use std:: path:: PathBuf ;
2
2
3
- use iced:: { widget:: Space , Alignment , Command , Length , Subscription } ;
3
+ use iced:: {
4
+ widget:: { tooltip, Space } ,
5
+ Alignment , Command , Length , Subscription ,
6
+ } ;
4
7
5
8
use liana:: { config:: ConfigError , miniscript:: bitcoin:: Network } ;
6
9
use liana_ui:: {
7
- component:: { badge, card, text:: * } ,
10
+ color,
11
+ component:: { badge, button, card, modal:: Modal , notification, text:: * } ,
8
12
icon, image, theme,
9
13
util:: * ,
10
14
widget:: * ,
11
15
} ;
12
16
13
17
use crate :: app;
14
18
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
+
15
30
pub struct Launcher {
16
31
choices : Vec < Network > ,
17
32
datadir_path : PathBuf ,
18
33
error : Option < String > ,
34
+ delete_wallet_modal : Option < DeleteWalletModal > ,
19
35
}
20
36
21
37
impl Launcher {
@@ -35,6 +51,7 @@ impl Launcher {
35
51
datadir_path,
36
52
choices,
37
53
error : None ,
54
+ delete_wallet_modal : None ,
38
55
}
39
56
}
40
57
@@ -54,6 +71,35 @@ impl Launcher {
54
71
check_network_datadir ( self . datadir_path . clone ( ) , network) ,
55
72
Message :: Checked ,
56
73
) ,
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
+ }
57
103
Message :: Checked ( res) => match res {
58
104
Err ( e) => {
59
105
self . error = Some ( e) ;
@@ -70,12 +116,17 @@ impl Launcher {
70
116
} )
71
117
}
72
118
} ,
73
- _ => Command :: none ( ) ,
119
+ _ => {
120
+ if let Some ( modal) = & mut self . delete_wallet_modal {
121
+ return modal. update ( message) ;
122
+ }
123
+ Command :: none ( )
124
+ }
74
125
}
75
126
}
76
127
77
128
pub fn view ( & self ) -> Element < Message > {
78
- Into :: < Element < ViewMessage > > :: into (
129
+ let content = Into :: < Element < ViewMessage > > :: into (
79
130
Column :: new ( )
80
131
. push (
81
132
Container :: new ( image:: liana_brand_grey ( ) . width ( Length :: Fixed ( 200.0 ) ) )
@@ -96,31 +147,43 @@ impl Launcher {
96
147
. spacing ( 10 ) ,
97
148
|col, choice| {
98
149
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) ) ) ,
111
169
)
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 ) ,
124
187
)
125
188
} ,
126
189
)
@@ -148,11 +211,20 @@ impl Launcher {
148
211
)
149
212
. push ( Space :: with_height ( Length :: Fixed ( 100.0 ) ) ) ,
150
213
)
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
+ }
152
224
}
153
225
}
154
226
155
- #[ derive( Debug ) ]
227
+ #[ derive( Debug , Clone ) ]
156
228
pub enum Message {
157
229
View ( ViewMessage ) ,
158
230
Install ( PathBuf ) ,
@@ -164,6 +236,106 @@ pub enum Message {
164
236
pub enum ViewMessage {
165
237
StartInstall ,
166
238
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
+ }
167
339
}
168
340
169
341
async fn check_network_datadir ( mut path : PathBuf , network : Network ) -> Result < Network , String > {
0 commit comments