@@ -32,6 +32,7 @@ pub enum InputMode {
3232 #[ default]
3333 None ,
3434 CreateProfile ,
35+ ConfirmDeleteProfile ,
3536}
3637
3738/// All state owned by the user interface.
@@ -70,6 +71,8 @@ pub struct App {
7071 pub ( crate ) send_area : Cell < Rect > ,
7172 pub ( crate ) jump_to_latest_area : Cell < Rect > ,
7273 pub ( crate ) max_chat_scroll : Cell < usize > ,
74+ pub ( crate ) delete_cancel_area : Cell < Rect > ,
75+ pub ( crate ) delete_ok_area : Cell < Rect > ,
7376 pub events : EventHandler ,
7477 message_cache : HashMap < ChatRef , Vec < Message > > ,
7578 simplex_events : mpsc:: Receiver < SimplexEvent > ,
@@ -113,6 +116,8 @@ impl Default for App {
113116 send_area : Cell :: new ( Rect :: default ( ) ) ,
114117 jump_to_latest_area : Cell :: new ( Rect :: default ( ) ) ,
115118 max_chat_scroll : Cell :: new ( 0 ) ,
119+ delete_cancel_area : Cell :: new ( Rect :: default ( ) ) ,
120+ delete_ok_area : Cell :: new ( Rect :: default ( ) ) ,
116121 events : EventHandler :: new ( ) ,
117122 message_cache : HashMap :: new ( ) ,
118123 simplex_events,
@@ -170,6 +175,14 @@ impl App {
170175 self . events . send ( AppEvent :: Quit ) ;
171176 return Ok ( ( ) ) ;
172177 }
178+ if self . input_mode == InputMode :: ConfirmDeleteProfile {
179+ match key. code {
180+ KeyCode :: Char ( 'y' ) => self . confirm_delete_profile ( ) ,
181+ KeyCode :: Enter | KeyCode :: Esc => self . input_mode = InputMode :: None ,
182+ _ => { }
183+ }
184+ return Ok ( ( ) ) ;
185+ }
173186 if self . input_mode == InputMode :: CreateProfile {
174187 match key. code {
175188 KeyCode :: Esc => {
@@ -245,6 +258,12 @@ impl App {
245258 self . input_mode = InputMode :: CreateProfile ;
246259 self . input . clear ( ) ;
247260 }
261+ KeyCode :: Char ( 'd' )
262+ if self . section == Section :: Profiles
263+ && self . selected_profile < self . profiles . len ( ) =>
264+ {
265+ self . input_mode = InputMode :: ConfirmDeleteProfile ;
266+ }
248267 KeyCode :: Enter | KeyCode :: Char ( ' ' ) if self . section == Section :: Settings => {
249268 self . activate_setting ( )
250269 }
@@ -314,6 +333,16 @@ impl App {
314333 }
315334
316335 fn handle_mouse_event ( & mut self , kind : MouseEventKind , column : u16 , row : u16 ) {
336+ if self . input_mode == InputMode :: ConfirmDeleteProfile
337+ && matches ! ( kind, MouseEventKind :: Down ( MouseButton :: Left ) )
338+ {
339+ if self . delete_ok_area . get ( ) . contains ( ( column, row) . into ( ) ) {
340+ self . confirm_delete_profile ( ) ;
341+ } else if self . delete_cancel_area . get ( ) . contains ( ( column, row) . into ( ) ) {
342+ self . input_mode = InputMode :: None ;
343+ }
344+ return ;
345+ }
317346 if matches ! ( kind, MouseEventKind :: Down ( MouseButton :: Left ) ) {
318347 if self
319348 . jump_to_latest_area
@@ -482,6 +511,26 @@ impl App {
482511 self . notice = Some ( "Profile created" . into ( ) ) ;
483512 self . sync_selected_profile ( ) ;
484513 }
514+ SimplexEvent :: ProfileDeleted {
515+ profiles,
516+ active_user,
517+ chats,
518+ } => {
519+ self . profiles = profiles;
520+ self . chats = chats;
521+ self . messages . clear ( ) ;
522+ self . message_cache . clear ( ) ;
523+ self . loaded_chat = None ;
524+ self . selected_chat = 0 ;
525+ self . startup = active_user
526+ . map ( StartupState :: Ready )
527+ . unwrap_or ( StartupState :: NoActiveUser ) ;
528+ self . notice = Some ( "Profile deleted" . into ( ) ) ;
529+ self . sync_selected_profile ( ) ;
530+ }
531+ SimplexEvent :: ProfileDeleteFailed ( error) => {
532+ self . notice = Some ( format ! ( "Could not delete profile: {error}" ) ) ;
533+ }
485534 SimplexEvent :: SettingChanged ( message) => self . notice = Some ( message) ,
486535 SimplexEvent :: AutoDeleteLoaded ( seconds) => self . auto_delete_seconds = seconds,
487536 SimplexEvent :: ServersLoaded ( servers) => self . smp_servers = servers,
@@ -734,6 +783,19 @@ impl App {
734783 . send ( SimplexCommand :: ActivateProfile ( profile. id ) ) ;
735784 }
736785
786+ fn confirm_delete_profile ( & mut self ) {
787+ let Some ( profile) = self . profiles . get ( self . selected_profile ) else {
788+ self . input_mode = InputMode :: None ;
789+ return ;
790+ } ;
791+ let user_id = profile. id ;
792+ self . input_mode = InputMode :: None ;
793+ self . notice = Some ( format ! ( "Deleting profile {}…" , profile. display_name) ) ;
794+ let _ = self
795+ . simplex_commands
796+ . send ( SimplexCommand :: DeleteProfile ( user_id) ) ;
797+ }
798+
737799 fn activate_setting ( & mut self ) {
738800 match self . selected_setting {
739801 1 => {
@@ -1059,4 +1121,37 @@ mod tests {
10591121 app. tick ( ) ;
10601122 assert_eq ! ( app. messages[ 0 ] . text, "from background" ) ;
10611123 }
1124+
1125+ #[ tokio:: test]
1126+ async fn profile_delete_requires_explicit_confirmation ( ) {
1127+ let ( commands, receiver) = mpsc:: channel ( ) ;
1128+ let mut app = App {
1129+ section : Section :: Profiles ,
1130+ profiles : vec ! [ Profile {
1131+ id: 9 ,
1132+ display_name: "work" . into( ) ,
1133+ notifications: true ,
1134+ active: true ,
1135+ } ] ,
1136+ simplex_commands : commands,
1137+ ..App :: default ( )
1138+ } ;
1139+
1140+ app. handle_key_events ( KeyEvent :: new ( KeyCode :: Char ( 'd' ) , KeyModifiers :: NONE ) )
1141+ . unwrap ( ) ;
1142+ assert_eq ! ( app. input_mode, InputMode :: ConfirmDeleteProfile ) ;
1143+ app. handle_key_events ( KeyEvent :: new ( KeyCode :: Enter , KeyModifiers :: NONE ) )
1144+ . unwrap ( ) ;
1145+ assert_eq ! ( app. input_mode, InputMode :: None ) ;
1146+ assert ! ( receiver. try_recv( ) . is_err( ) ) ;
1147+
1148+ app. handle_key_events ( KeyEvent :: new ( KeyCode :: Char ( 'd' ) , KeyModifiers :: NONE ) )
1149+ . unwrap ( ) ;
1150+ app. handle_key_events ( KeyEvent :: new ( KeyCode :: Char ( 'y' ) , KeyModifiers :: NONE ) )
1151+ . unwrap ( ) ;
1152+ let SimplexCommand :: DeleteProfile ( user_id) = receiver. try_recv ( ) . unwrap ( ) else {
1153+ panic ! ( "expected delete-profile command" )
1154+ } ;
1155+ assert_eq ! ( user_id, 9 ) ;
1156+ }
10621157}
0 commit comments