Skip to content

Commit 417cc27

Browse files
Handle app lifecycle events: Pause, Resume, etc
1 parent c2ea9a1 commit 417cc27

File tree

1 file changed

+147
-30
lines changed

1 file changed

+147
-30
lines changed

src/app.rs

Lines changed: 147 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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

146150
impl 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

450457
impl 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

527544
impl 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

Comments
 (0)