Skip to content

Commit 660c6fc

Browse files
Handle app lifecycle events: Pause, Resume, etc
1 parent c2ea9a1 commit 660c6fc

File tree

1 file changed

+136
-30
lines changed

1 file changed

+136
-30
lines changed

src/app.rs

Lines changed: 136 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,52 @@ 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);
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

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

Comments
 (0)