From 280f6c2aae7ca00ff2dbe2f50958339da374c452 Mon Sep 17 00:00:00 2001 From: WeshBg <158100913+WeshBg@users.noreply.github.com> Date: Fri, 1 May 2026 23:46:12 -0400 Subject: [PATCH] fix(nav): prevent back-stack crash on rapid presses - Replace raw popBackStack() with rememberGuardedPopBackStack() on detail, person, browse, stream, player, catalog, collection, and folder routes - Add PlatformBackHandler to affected full-screen routes - Enable android:enableOnBackInvokedCallback in the manifest --- .../src/androidMain/AndroidManifest.xml | 1 + .../commonMain/kotlin/com/nuvio/app/App.kt | 102 +++++++++++++----- 2 files changed, 77 insertions(+), 26 deletions(-) diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml index dc8f0964..ecff53e4 100644 --- a/composeApp/src/androidMain/AndroidManifest.xml +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -6,6 +6,7 @@ { backStackEntry -> val route = backStackEntry.toRoute() + val onBack = rememberGuardedPopBackStack( + navController = navController, + backStackEntry = backStackEntry, + ) + PlatformBackHandler( + enabled = true, + onBack = onBack, + ) val directorRole = stringResource(Res.string.person_role_director) val writerRole = stringResource(Res.string.person_role_writer) val creatorRole = stringResource(Res.string.person_role_creator) MetaDetailsScreen( type = route.type, id = route.id, - onBack = { - navController.popBackStack() - }, + onBack = onBack, onPlay = onPlay, onPlayManually = onPlayManually, onOpenMeta = { preview -> @@ -1078,13 +1084,21 @@ private fun MainAppContent( } composable { backStackEntry -> val route = backStackEntry.toRoute() + val onBack = rememberGuardedPopBackStack( + navController = navController, + backStackEntry = backStackEntry, + ) + PlatformBackHandler( + enabled = true, + onBack = onBack, + ) PersonDetailScreen( personId = route.personId, personName = route.personName, initialProfilePhoto = route.personPhoto, avatarTransitionKey = route.castAvatarTransitionKey, preferCrew = route.preferCrew, - onBack = { navController.popBackStack() }, + onBack = onBack, onOpenMeta = { preview -> coroutineScope.launch { val resolvedId = if (preview.id.startsWith("tmdb:")) { @@ -1113,12 +1127,20 @@ private fun MainAppContent( } composable { backStackEntry -> val route = backStackEntry.toRoute() + val onBack = rememberGuardedPopBackStack( + navController = navController, + backStackEntry = backStackEntry, + ) + PlatformBackHandler( + enabled = true, + onBack = onBack, + ) TmdbEntityBrowseScreen( entityKind = TmdbEntityKind.fromRouteValue(route.entityKind), entityId = route.entityId, entityName = route.entityName, sourceType = route.sourceType, - onBack = { navController.popBackStack() }, + onBack = onBack, onOpenMeta = { preview -> coroutineScope.launch { val resolvedId = if (preview.id.startsWith("tmdb:")) { @@ -1145,6 +1167,15 @@ private fun MainAppContent( } composable { backStackEntry -> val route = backStackEntry.toRoute() + val onBack = rememberGuardedPopBackStack( + navController = navController, + backStackEntry = backStackEntry, + beforePop = { StreamsRepository.clear() }, + ) + PlatformBackHandler( + enabled = true, + onBack = onBack, + ) val launch = remember(route.launchId) { StreamLaunchStore.get(route.launchId) } @@ -1410,10 +1441,7 @@ private fun MainAppContent( ) } }, - onBack = { - StreamsRepository.clear() - navController.popBackStack() - }, + onBack = onBack, modifier = Modifier.fillMaxSize(), ) } @@ -1432,6 +1460,18 @@ private fun MainAppContent( }, ) { backStackEntry -> val route = backStackEntry.toRoute() + val onBack = rememberGuardedPopBackStack( + navController = navController, + backStackEntry = backStackEntry, + beforePop = { + ResumePromptRepository.markPlayerExitedNormally() + PlayerLaunchStore.remove(route.launchId) + }, + ) + PlatformBackHandler( + enabled = true, + onBack = onBack, + ) val launch = remember(route.launchId) { PlayerLaunchStore.get(route.launchId) } if (launch == null) { LaunchedEffect(route.launchId) { @@ -1468,16 +1508,21 @@ private fun MainAppContent( parentMetaType = launch.parentMetaType, initialPositionMs = launch.initialPositionMs, initialProgressFraction = launch.initialProgressFraction, - onBack = { - ResumePromptRepository.markPlayerExitedNormally() - PlayerLaunchStore.remove(route.launchId) - navController.popBackStack() - }, + onBack = onBack, modifier = Modifier.fillMaxSize(), ) } composable { backStackEntry -> val route = backStackEntry.toRoute() + val onBack = rememberGuardedPopBackStack( + navController = navController, + backStackEntry = backStackEntry, + beforePop = { CatalogRepository.clear() }, + ) + PlatformBackHandler( + enabled = true, + onBack = onBack, + ) CatalogScreen( title = route.title, subtitle = route.subtitle, @@ -1486,10 +1531,7 @@ private fun MainAppContent( catalogId = route.catalogId, supportsPagination = route.supportsPagination, genre = route.genre, - onBack = { - CatalogRepository.clear() - navController.popBackStack() - }, + onBack = onBack, onPosterClick = { meta -> navController.navigate(DetailRoute(type = meta.type, id = meta.id)) }, @@ -1618,24 +1660,32 @@ private fun MainAppContent( } composable { backStackEntry -> val route = backStackEntry.toRoute() + val onBack = rememberGuardedPopBackStack( + navController = navController, + backStackEntry = backStackEntry, + beforePop = { CollectionEditorRepository.clear() }, + ) CollectionEditorScreen( collectionId = route.collectionId, - onBack = { - CollectionEditorRepository.clear() - navController.popBackStack() - }, + onBack = onBack, ) } composable { backStackEntry -> val route = backStackEntry.toRoute() + val onBack = rememberGuardedPopBackStack( + navController = navController, + backStackEntry = backStackEntry, + beforePop = { FolderDetailRepository.clear() }, + ) + PlatformBackHandler( + enabled = true, + onBack = onBack, + ) LaunchedEffect(route.collectionId, route.folderId) { FolderDetailRepository.initialize(route.collectionId, route.folderId) } FolderDetailScreen( - onBack = { - FolderDetailRepository.clear() - navController.popBackStack() - }, + onBack = onBack, onCatalogClick = onCatalogClick, onPosterClick = { meta -> navController.navigate(DetailRoute(type = meta.type, id = meta.id))