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
This commit is contained in:
WeshBg 2026-05-01 23:46:12 -04:00
parent 8a58fabfdd
commit 280f6c2aae
2 changed files with 77 additions and 26 deletions

View file

@ -6,6 +6,7 @@
<application
android:allowBackup="true"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:usesCleartextTraffic="true"

View file

@ -1005,15 +1005,21 @@ private fun MainAppContent(
}
composable<DetailRoute> { backStackEntry ->
val route = backStackEntry.toRoute<DetailRoute>()
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<PersonDetailRoute> { backStackEntry ->
val route = backStackEntry.toRoute<PersonDetailRoute>()
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<EntityBrowseRoute> { backStackEntry ->
val route = backStackEntry.toRoute<EntityBrowseRoute>()
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<StreamRoute> { backStackEntry ->
val route = backStackEntry.toRoute<StreamRoute>()
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<PlayerRoute>()
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<CatalogRoute> { backStackEntry ->
val route = backStackEntry.toRoute<CatalogRoute>()
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<CollectionEditorRoute> { backStackEntry ->
val route = backStackEntry.toRoute<CollectionEditorRoute>()
val onBack = rememberGuardedPopBackStack(
navController = navController,
backStackEntry = backStackEntry,
beforePop = { CollectionEditorRepository.clear() },
)
CollectionEditorScreen(
collectionId = route.collectionId,
onBack = {
CollectionEditorRepository.clear()
navController.popBackStack()
},
onBack = onBack,
)
}
composable<FolderDetailRoute> { backStackEntry ->
val route = backStackEntry.toRoute<FolderDetailRoute>()
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))