From a0ec123e0c19f8f1fa321cb0cc3d6d75c12a84a6 Mon Sep 17 00:00:00 2001 From: Barry Deen Date: Mon, 4 May 2026 07:44:39 -0400 Subject: [PATCH] fix: preserve bottom-tab state during tab switching Restore top-level tab destinations instead of recreating them on each tap, and avoid immediately resubscribing inbox streams when users bounce between tabs. This reduces navigation jank and repeated hangs on slower devices. --- .../main/kotlin/com/wisp/app/Navigation.kt | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/wisp/app/Navigation.kt b/app/src/main/kotlin/com/wisp/app/Navigation.kt index 4658784..394fd12 100644 --- a/app/src/main/kotlin/com/wisp/app/Navigation.kt +++ b/app/src/main/kotlin/com/wisp/app/Navigation.kt @@ -1,5 +1,6 @@ package com.wisp.app +import android.os.SystemClock import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -26,6 +27,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavType +import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState @@ -527,6 +529,16 @@ fun WispNavHost( } } + var lastInboxRefreshElapsedMs by rememberSaveable { mutableLongStateOf(0L) } + fun refreshInboxSubscriptionsIfStale() { + val now = SystemClock.elapsedRealtime() + // Inbox subscriptions already stay live in the background; avoid re-sending the + // same REQs every time the user bounces between top-level tabs. + if (now - lastInboxRefreshElapsedMs < 30_000L) return + lastInboxRefreshElapsedMs = now + feedViewModel.refreshDmsAndNotifications() + } + // Crash report dialog — check on launch if a crash log exists var showCrashDialog by remember { mutableStateOf(CrashHandler.hasCrashLog(context)) } if (showCrashDialog) { @@ -607,8 +619,11 @@ fun WispNavHost( } else { if (tab == BottomTab.WALLET) walletViewModel.navigateHome() navController.navigate(tab.route) { - popUpTo(Routes.FEED) { inclusive = false } + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } launchSingleTop = true + restoreState = true } } } @@ -1271,7 +1286,7 @@ fun WispNavHost( composable(Routes.DM_LIST) { LaunchedEffect(Unit) { - feedViewModel.refreshDmsAndNotifications() + refreshInboxSubscriptionsIfStale() // Decrypt pending gift wraps when signer is available activeSigner?.let { dmListViewModel.decryptPending(it) } dmListViewModel.markDmsRead() @@ -1331,7 +1346,7 @@ fun WispNavHost( val dmConvoViewModel: DmConversationViewModel = viewModel() val userPubkey = feedViewModel.getUserPubkey() LaunchedEffect(pubkey) { - feedViewModel.refreshDmsAndNotifications() + refreshInboxSubscriptionsIfStale() dmConvoViewModel.init( peerPubkeyHex = pubkey, dmRepository = feedViewModel.dmRepo, @@ -1413,7 +1428,7 @@ fun WispNavHost( val dmConvoViewModel: DmConversationViewModel = viewModel() val userPubkey = feedViewModel.getUserPubkey() LaunchedEffect(convKey) { - feedViewModel.refreshDmsAndNotifications() + refreshInboxSubscriptionsIfStale() dmConvoViewModel.init( peerPubkeyHex = participantList.firstOrNull() ?: "", dmRepository = feedViewModel.dmRepo, @@ -3034,7 +3049,7 @@ fun WispNavHost( onDispose { feedViewModel.notifRepo.isViewing = false } } LaunchedEffect(Unit) { - feedViewModel.refreshDmsAndNotifications() + refreshInboxSubscriptionsIfStale() notificationsViewModel.markRead() }