@@ -141,6 +141,10 @@ pub struct App {
141141 /// The details of a room we're waiting on to be joined so that we can navigate to it.
142142 /// Also includes an optional room ID to be closed once the awaited room is joined.
143143 #[ rust] waiting_to_navigate_to_joined_room : Option < ( BasicRoomDetails , Option < OwnedRoomId > ) > ,
144+ /// Tracks whether app state needs to be saved on the next appropriate lifecycle event.
145+ #[ rust] needs_save : bool ,
146+ /// Tracks whether app state needs to be restored on the next appropriate lifecycle event.
147+ #[ rust] needs_restore : bool ,
144148}
145149
146150impl LiveRegister for App {
@@ -213,6 +217,9 @@ impl MatchEvent for App {
213217 log ! ( "App::Startup: initializing TSP (Trust Spanning Protocol) module." ) ;
214218 crate :: tsp:: tsp_init ( _tokio_rt_handle) . unwrap ( ) ;
215219 }
220+
221+ // Mark that state may have changed and should be saved on the next pause/background event
222+ self . needs_save = true ;
216223 }
217224
218225 fn handle_actions ( & mut self , cx : & mut Cx , actions : & Actions ) {
@@ -449,40 +456,52 @@ fn clear_all_app_state(cx: &mut Cx) {
449456
450457impl AppMain for App {
451458 fn handle_event ( & mut self , cx : & mut Cx , event : & Event ) {
452- // if let Event::WindowGeomChange(geom) = event {
453- // log!("App::handle_event(): Window geometry changed: {:?}", geom);
454- // }
455-
456- if let Event :: Shutdown = event {
457- let window_ref = self . ui . window ( ids ! ( main_window) ) ;
458- if let Err ( e) = persistence:: save_window_state ( window_ref, cx) {
459- error ! ( "Failed to save window state. Error: {e}" ) ;
460- }
461- if let Some ( user_id) = current_user_id ( ) {
462- let app_state = self . app_state . clone ( ) ;
463- if let Err ( e) = persistence:: save_app_state ( app_state, user_id) {
464- error ! ( "Failed to save app state. Error: {e}" ) ;
459+ // Handle app lifecycle events
460+ match event {
461+ Event :: Pause | Event :: Background | Event :: Shutdown => {
462+ // Save state if we haven't already saved since last restore
463+ if self . needs_save {
464+ self . save_all_state ( cx) ;
465+ }
466+
467+ // Stop sync service (only on Pause/Background, not Shutdown)
468+ if matches ! ( event, Event :: Pause | Event :: Background ) {
469+ if let Some ( sync_service) = crate :: sliding_sync:: get_sync_service ( ) {
470+ let _ = crate :: sliding_sync:: block_on_async_with_timeout (
471+ Some ( std:: time:: Duration :: from_secs ( 2 ) ) ,
472+ async move { sync_service. stop ( ) . await } ,
473+ ) ;
474+ }
475+
476+ // Mark that we'll need to restore when app comes back
477+ self . needs_restore = true ;
465478 }
466479 }
467- #[ cfg( feature = "tsp" ) ] {
468- // Save the TSP wallet state, if it exists, with a 3-second timeout.
469- let tsp_state = std:: mem:: take ( & mut * crate :: tsp:: tsp_state_ref ( ) . lock ( ) . unwrap ( ) ) ;
470- let res = crate :: sliding_sync:: block_on_async_with_timeout (
471- Some ( std:: time:: Duration :: from_secs ( 3 ) ) ,
472- async move {
473- match tsp_state. close_and_serialize ( ) . await {
474- Ok ( saved_state) => match persistence:: save_tsp_state_async ( saved_state) . await {
475- Ok ( _) => { }
476- Err ( e) => error ! ( "Failed to save TSP wallet state. Error: {e}" ) ,
477- }
478- Err ( e) => error ! ( "Failed to close and serialize TSP wallet state. Error: {e}" ) ,
479- }
480- } ,
481- ) ;
482- if let Err ( _e) = res {
483- error ! ( "Failed to save TSP wallet state before app shutdown. Error: Timed Out." ) ;
480+
481+ Event :: Foreground | Event :: Resume => {
482+ // Restore state if needed
483+ if self . needs_restore {
484+ self . restore_all_state ( cx) ;
484485 }
486+
487+ // Restart sync service (only on first incoming event)
488+ if matches ! ( event, Event :: Foreground ) || self . needs_restore {
489+ if let Some ( sync_service) = crate :: sliding_sync:: get_sync_service ( ) {
490+ let _ = crate :: sliding_sync:: block_on_async_with_timeout (
491+ Some ( std:: time:: Duration :: from_secs ( 2 ) ) ,
492+ async move { sync_service. start ( ) . await } ,
493+ ) ;
494+ }
495+ }
496+
497+ // Perform full redraw to ensure UI is up-to-date
498+ self . ui . redraw ( cx) ;
499+
500+ // Mark that state has changed and may need saving again
501+ self . needs_save = true ;
485502 }
503+
504+ _ => { }
486505 }
487506
488507 // Forward events to the MatchEvent trait implementation.
@@ -525,6 +544,93 @@ impl AppMain for App {
525544}
526545
527546impl App {
547+ /// Saves all app state (window geometry, AppState, and TSP state if applicable).
548+ /// Uses a best-effort approach: continues saving other state even if one part fails.
549+ fn save_all_state ( & mut self , cx : & mut Cx ) {
550+ // Save window geometry
551+ let window_ref = self . ui . window ( ids ! ( main_window) ) ;
552+ if let Err ( e) = persistence:: save_window_state ( window_ref, cx) {
553+ error ! ( "Failed to save window state: {e}" ) ;
554+ }
555+
556+ // Save app state
557+ if let Some ( user_id) = current_user_id ( ) {
558+ if let Err ( e) = persistence:: save_app_state ( self . app_state . clone ( ) , user_id) {
559+ error ! ( "Failed to save app state: {e}" ) ;
560+ }
561+ }
562+
563+ // Save TSP state
564+ #[ cfg( feature = "tsp" ) ] {
565+ let tsp_state = std:: mem:: take ( & mut * crate :: tsp:: tsp_state_ref ( ) . lock ( ) . unwrap ( ) ) ;
566+ let res = crate :: sliding_sync:: block_on_async_with_timeout (
567+ Some ( std:: time:: Duration :: from_secs ( 3 ) ) ,
568+ async move {
569+ match tsp_state. close_and_serialize ( ) . await {
570+ Ok ( saved_state) => {
571+ if let Err ( e) = persistence:: save_tsp_state_async ( saved_state) . await {
572+ error ! ( "Failed to save TSP wallet state: {e}" ) ;
573+ }
574+ }
575+ Err ( e) => error ! ( "Failed to close and serialize TSP wallet state: {e}" ) ,
576+ }
577+ } ,
578+ ) ;
579+ if let Err ( _) = res {
580+ error ! ( "Failed to save TSP wallet state: Timed Out" ) ;
581+ }
582+ }
583+
584+ // Mark that we no longer need to save
585+ self . needs_save = false ;
586+ }
587+
588+ /// Restores all app state (window geometry, AppState, and TSP state if applicable).
589+ /// Uses a best-effort approach: continues restoring other state even if one part fails.
590+ fn restore_all_state ( & mut self , cx : & mut Cx ) {
591+ // Restore window geometry
592+ let window_ref = self . ui . window ( ids ! ( main_window) ) ;
593+ if let Err ( e) = persistence:: load_window_state ( window_ref, cx) {
594+ error ! ( "Failed to restore window state: {e}" ) ;
595+ }
596+
597+ // Restore app state
598+ if let Some ( user_id) = current_user_id ( ) {
599+ match crate :: sliding_sync:: block_on_async_with_timeout (
600+ Some ( std:: time:: Duration :: from_secs ( 2 ) ) ,
601+ crate :: persistence:: load_app_state ( & user_id) ,
602+ ) {
603+ Ok ( Ok ( restored_state) ) => {
604+ // Preserve the logged_in state
605+ let logged_in = self . app_state . logged_in ;
606+ self . app_state = restored_state;
607+ self . app_state . logged_in = logged_in;
608+ cx. action ( crate :: home:: main_desktop_ui:: MainDesktopUiAction :: LoadDockFromAppState ) ;
609+ }
610+ Ok ( Err ( e) ) => error ! ( "Failed to restore app state: {e}" ) ,
611+ Err ( _) => error ! ( "Timeout while restoring app state" ) ,
612+ }
613+ }
614+
615+ // Restore TSP state
616+ #[ cfg( feature = "tsp" ) ] {
617+ if let Err ( e) = crate :: sliding_sync:: block_on_async_with_timeout (
618+ Some ( std:: time:: Duration :: from_secs ( 2 ) ) ,
619+ async {
620+ let loaded_state = persistence:: load_tsp_state_async ( ) . await ?;
621+ let mut tsp_state = crate :: tsp:: tsp_state_ref ( ) . lock ( ) . unwrap ( ) ;
622+ * tsp_state = loaded_state;
623+ Ok :: < ( ) , anyhow:: Error > ( ( ) )
624+ } ,
625+ ) {
626+ error ! ( "Failed to restore TSP wallet state: {e:?}" ) ;
627+ }
628+ }
629+
630+ // Mark that we no longer need to restore
631+ self . needs_restore = false ;
632+ }
633+
528634 fn update_login_visibility ( & self , cx : & mut Cx ) {
529635 let show_login = !self . app_state . logged_in ;
530636 if !show_login {
0 commit comments