@@ -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,50 @@ 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) ;
485+
486+ // Restart sync service only when actually restoring from background
487+ if let Some ( sync_service) = crate :: sliding_sync:: get_sync_service ( ) {
488+ let _ = crate :: sliding_sync:: block_on_async_with_timeout (
489+ Some ( std:: time:: Duration :: from_secs ( 2 ) ) ,
490+ async move { sync_service. start ( ) . await } ,
491+ ) ;
492+ }
484493 }
494+
495+ // Perform full redraw to ensure UI is up-to-date
496+ self . ui . redraw ( cx) ;
497+
498+ // Mark that state has changed and may need saving again
499+ self . needs_save = true ;
485500 }
501+
502+ _ => { }
486503 }
487504
488505 // Forward events to the MatchEvent trait implementation.
@@ -525,6 +542,106 @@ impl AppMain for App {
525542}
526543
527544impl App {
545+ /// Saves all app state (window geometry, AppState, and TSP state if applicable).
546+ /// Uses a best-effort approach: continues saving other state even if one part fails.
547+ fn save_all_state ( & mut self , cx : & mut Cx ) {
548+ // Save window geometry
549+ let window_ref = self . ui . window ( ids ! ( main_window) ) ;
550+ if let Err ( e) = persistence:: save_window_state ( window_ref, cx) {
551+ error ! ( "Failed to save window state: {e}" ) ;
552+ }
553+
554+ // Save app state
555+ if let Some ( user_id) = current_user_id ( ) {
556+ if let Err ( e) = persistence:: save_app_state ( self . app_state . clone ( ) , user_id) {
557+ error ! ( "Failed to save app state: {e}" ) ;
558+ }
559+ }
560+
561+ // Save TSP state
562+ #[ cfg( feature = "tsp" ) ] {
563+ // Take the TSP state, but keep a backup in case saving fails
564+ let mut tsp_state = std:: mem:: take ( & mut * crate :: tsp:: tsp_state_ref ( ) . lock ( ) . unwrap ( ) ) ;
565+ let mut save_failed = false ;
566+ let res = crate :: sliding_sync:: block_on_async_with_timeout (
567+ Some ( std:: time:: Duration :: from_secs ( 3 ) ) ,
568+ async {
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+ // Mark as failed
574+ save_failed = true ;
575+ }
576+ }
577+ Err ( e) => {
578+ error ! ( "Failed to close and serialize TSP wallet state: {e}" ) ;
579+ save_failed = true ;
580+ }
581+ }
582+ } ,
583+ ) ;
584+ if let Err ( _) = res {
585+ error ! ( "Failed to save TSP wallet state: Timed Out" ) ;
586+ save_failed = true ;
587+ }
588+
589+ // If saving failed, restore the original TSP state
590+ if save_failed {
591+ * crate :: tsp:: tsp_state_ref ( ) . lock ( ) . unwrap ( ) = tsp_state;
592+ }
593+ }
594+
595+ // Mark that we no longer need to save
596+ self . needs_save = false ;
597+ }
598+
599+ /// Restores all app state (window geometry, AppState, and TSP state if applicable).
600+ /// Uses a best-effort approach: continues restoring other state even if one part fails.
601+ fn restore_all_state ( & mut self , cx : & mut Cx ) {
602+ // Restore window geometry
603+ let window_ref = self . ui . window ( ids ! ( main_window) ) ;
604+ if let Err ( e) = persistence:: load_window_state ( window_ref, cx) {
605+ error ! ( "Failed to restore window state: {e}" ) ;
606+ }
607+
608+ // Restore app state
609+ if let Some ( user_id) = current_user_id ( ) {
610+ match crate :: sliding_sync:: block_on_async_with_timeout (
611+ Some ( std:: time:: Duration :: from_secs ( 2 ) ) ,
612+ crate :: persistence:: load_app_state ( & user_id) ,
613+ ) {
614+ Ok ( Ok ( restored_state) ) => {
615+ // Preserve the logged_in state
616+ let logged_in = self . app_state . logged_in ;
617+ self . app_state = restored_state;
618+ self . app_state . logged_in = logged_in;
619+ cx. action ( crate :: home:: main_desktop_ui:: MainDesktopUiAction :: LoadDockFromAppState ) ;
620+ }
621+ Ok ( Err ( e) ) => error ! ( "Failed to restore app state: {e}" ) ,
622+ Err ( _) => error ! ( "Timeout while restoring app state" ) ,
623+ }
624+ }
625+
626+ // Restore TSP state
627+ #[ cfg( feature = "tsp" ) ] {
628+ if let Err ( e) = crate :: sliding_sync:: block_on_async_with_timeout (
629+ Some ( std:: time:: Duration :: from_secs ( 2 ) ) ,
630+ async {
631+ let loaded_state = persistence:: load_tsp_state_async ( ) . await ?;
632+ let mut tsp_state = crate :: tsp:: tsp_state_ref ( ) . lock ( ) . unwrap ( ) ;
633+ * tsp_state = loaded_state;
634+ Ok :: < ( ) , anyhow:: Error > ( ( ) )
635+ } ,
636+ ) {
637+ error ! ( "Failed to restore TSP wallet state: {e:?}" ) ;
638+ }
639+ }
640+
641+ // Mark that we no longer need to restore
642+ self . needs_restore = false ;
643+ }
644+
528645 fn update_login_visibility ( & self , cx : & mut Cx ) {
529646 let show_login = !self . app_state . logged_in ;
530647 if !show_login {
0 commit comments