diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b19013a..f08a5ee 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "com.crsmthw.phase10tracker" minSdk = 35 targetSdk = 37 - versionCode = 6 - versionName = "3.0.2" + versionCode = 7 + versionName = "3.0.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm index b5f8993..176f2fa 100644 Binary files a/app/release/baselineProfiles/0/app-release.dm and b/app/release/baselineProfiles/0/app-release.dm differ diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm index f7e2b7d..10b2e32 100644 Binary files a/app/release/baselineProfiles/1/app-release.dm and b/app/release/baselineProfiles/1/app-release.dm differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index 0ff5cd6..67329c3 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 6, - "versionName": "3.0.2", + "versionCode": 7, + "versionName": "3.0.3", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/java/com/crsmthw/phase10tracker/ui/Navigation.kt b/app/src/main/java/com/crsmthw/phase10tracker/ui/Navigation.kt index 60801a0..28403ac 100644 --- a/app/src/main/java/com/crsmthw/phase10tracker/ui/Navigation.kt +++ b/app/src/main/java/com/crsmthw/phase10tracker/ui/Navigation.kt @@ -2,8 +2,11 @@ package com.crsmthw.phase10tracker.ui import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.Lifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController import androidx.navigation.NavHostController +import androidx.navigation.NavOptionsBuilder import androidx.navigation.NavType import androidx.navigation.compose.* import androidx.navigation.navArgument @@ -27,6 +30,25 @@ object Routes { fun gameResults(id: Long) = "results/$id" } +// ── Safe navigation helpers ─────────────────────────────────────────────────── +// Guards every nav action behind a RESUMED check so rapid double-taps during +// transition animations can't pop extra destinations and cause a blank screen. + +private fun NavController.navigateSafe( + route: String, + builder: NavOptionsBuilder.() -> Unit = {} +) { + if (currentBackStackEntry?.lifecycle?.currentState == Lifecycle.State.RESUMED) { + navigate(route, builder) + } +} + +private fun NavController.popSafe() { + if (currentBackStackEntry?.lifecycle?.currentState == Lifecycle.State.RESUMED) { + popBackStack() + } +} + @Composable fun Phase10NavHost( navController: NavHostController, @@ -57,12 +79,12 @@ fun Phase10NavHost( amoledBlack = amoledBlack, onThemeModeChange = themeVm::setThemeMode, onAmoledBlackChange = themeVm::setAmoledBlack, - onContinueGame = { gameId -> navController.navigate(Routes.activeGame(gameId)) }, - onStartNew = { navController.navigate(Routes.GAME_SETUP) }, - onLeaderboard = { navController.navigate(Routes.LEADERBOARD) }, - onManagePlayers = { navController.navigate(Routes.PLAYER_ROSTER) }, - onCustomRules = { navController.navigate(Routes.CUSTOM_RULES) }, - onAbout = { navController.navigate(Routes.ABOUT) } + onContinueGame = { gameId -> navController.navigateSafe(Routes.activeGame(gameId)) }, + onStartNew = { navController.navigateSafe(Routes.GAME_SETUP) }, + onLeaderboard = { navController.navigateSafe(Routes.LEADERBOARD) }, + onManagePlayers = { navController.navigateSafe(Routes.PLAYER_ROSTER) }, + onCustomRules = { navController.navigateSafe(Routes.CUSTOM_RULES) }, + onAbout = { navController.navigateSafe(Routes.ABOUT) } ) } @@ -70,7 +92,7 @@ fun Phase10NavHost( val vm: PlayerRosterViewModel = viewModel(factory = factory) PlayerRosterScreen( vm = vm, - onBack = { navController.popBackStack() } + onBack = { navController.popSafe() } ) } @@ -79,11 +101,11 @@ fun Phase10NavHost( GameSetupScreen( vm = vm, onGameStarted = { gameId -> - navController.navigate(Routes.activeGame(gameId)) { + navController.navigateSafe(Routes.activeGame(gameId)) { popUpTo(Routes.HOME) } }, - onBack = { navController.popBackStack() } + onBack = { navController.popSafe() } ) } @@ -96,18 +118,18 @@ fun Phase10NavHost( val vm: ActiveGameViewModel = viewModel(factory = gameFactory) ActiveGameScreen( vm = vm, - onEnterRound = { navController.navigate(Routes.roundEntry(gameId)) }, + onEnterRound = { navController.navigateSafe(Routes.roundEntry(gameId)) }, onGameEnd = { - navController.navigate(Routes.gameResults(gameId)) { + navController.navigateSafe(Routes.gameResults(gameId)) { popUpTo(Routes.HOME) } }, onGameCancelled = { - navController.navigate(Routes.HOME) { + navController.navigateSafe(Routes.HOME) { popUpTo(Routes.HOME) { inclusive = true } } }, - onBack = { navController.popBackStack() } + onBack = { navController.popSafe() } ) } @@ -120,8 +142,8 @@ fun Phase10NavHost( val vm: RoundEntryViewModel = viewModel(factory = gameFactory) RoundEntryScreen( vm = vm, - onRoundSubmitted = { navController.popBackStack() }, - onBack = { navController.popBackStack() } + onRoundSubmitted = { navController.popSafe() }, + onBack = { navController.popSafe() } ) } @@ -135,7 +157,7 @@ fun Phase10NavHost( GameResultsScreen( vm = vm, onHome = { - navController.navigate(Routes.HOME) { + navController.navigateSafe(Routes.HOME) { popUpTo(Routes.HOME) { inclusive = true } } } @@ -146,7 +168,7 @@ fun Phase10NavHost( val vm: LeaderboardViewModel = viewModel(factory = factory) LeaderboardScreen( vm = vm, - onBack = { navController.popBackStack() } + onBack = { navController.popSafe() } ) } @@ -154,12 +176,12 @@ fun Phase10NavHost( val vm: CustomPhasesViewModel = viewModel(factory = factory) CustomPhasesScreen( vm = vm, - onBack = { navController.popBackStack() } + onBack = { navController.popSafe() } ) } composable(Routes.ABOUT) { - AboutScreen(onBack = { navController.popBackStack() }) + AboutScreen(onBack = { navController.popSafe() }) } } }