v3.0.3 - fixed double back navigation bug

This commit is contained in:
2026-05-17 11:31:34 +03:00
parent 3637a1e236
commit d7ccc2b858
5 changed files with 45 additions and 23 deletions
+2 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId = "com.crsmthw.phase10tracker" applicationId = "com.crsmthw.phase10tracker"
minSdk = 35 minSdk = 35
targetSdk = 37 targetSdk = 37
versionCode = 6 versionCode = 7
versionName = "3.0.2" versionName = "3.0.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -11,8 +11,8 @@
"type": "SINGLE", "type": "SINGLE",
"filters": [], "filters": [],
"attributes": [], "attributes": [],
"versionCode": 6, "versionCode": 7,
"versionName": "3.0.2", "versionName": "3.0.3",
"outputFile": "app-release.apk" "outputFile": "app-release.apk"
} }
], ],
@@ -2,8 +2,11 @@ package com.crsmthw.phase10tracker.ui
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavOptionsBuilder
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.* import androidx.navigation.compose.*
import androidx.navigation.navArgument import androidx.navigation.navArgument
@@ -27,6 +30,25 @@ object Routes {
fun gameResults(id: Long) = "results/$id" 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 @Composable
fun Phase10NavHost( fun Phase10NavHost(
navController: NavHostController, navController: NavHostController,
@@ -57,12 +79,12 @@ fun Phase10NavHost(
amoledBlack = amoledBlack, amoledBlack = amoledBlack,
onThemeModeChange = themeVm::setThemeMode, onThemeModeChange = themeVm::setThemeMode,
onAmoledBlackChange = themeVm::setAmoledBlack, onAmoledBlackChange = themeVm::setAmoledBlack,
onContinueGame = { gameId -> navController.navigate(Routes.activeGame(gameId)) }, onContinueGame = { gameId -> navController.navigateSafe(Routes.activeGame(gameId)) },
onStartNew = { navController.navigate(Routes.GAME_SETUP) }, onStartNew = { navController.navigateSafe(Routes.GAME_SETUP) },
onLeaderboard = { navController.navigate(Routes.LEADERBOARD) }, onLeaderboard = { navController.navigateSafe(Routes.LEADERBOARD) },
onManagePlayers = { navController.navigate(Routes.PLAYER_ROSTER) }, onManagePlayers = { navController.navigateSafe(Routes.PLAYER_ROSTER) },
onCustomRules = { navController.navigate(Routes.CUSTOM_RULES) }, onCustomRules = { navController.navigateSafe(Routes.CUSTOM_RULES) },
onAbout = { navController.navigate(Routes.ABOUT) } onAbout = { navController.navigateSafe(Routes.ABOUT) }
) )
} }
@@ -70,7 +92,7 @@ fun Phase10NavHost(
val vm: PlayerRosterViewModel = viewModel(factory = factory) val vm: PlayerRosterViewModel = viewModel(factory = factory)
PlayerRosterScreen( PlayerRosterScreen(
vm = vm, vm = vm,
onBack = { navController.popBackStack() } onBack = { navController.popSafe() }
) )
} }
@@ -79,11 +101,11 @@ fun Phase10NavHost(
GameSetupScreen( GameSetupScreen(
vm = vm, vm = vm,
onGameStarted = { gameId -> onGameStarted = { gameId ->
navController.navigate(Routes.activeGame(gameId)) { navController.navigateSafe(Routes.activeGame(gameId)) {
popUpTo(Routes.HOME) popUpTo(Routes.HOME)
} }
}, },
onBack = { navController.popBackStack() } onBack = { navController.popSafe() }
) )
} }
@@ -96,18 +118,18 @@ fun Phase10NavHost(
val vm: ActiveGameViewModel = viewModel(factory = gameFactory) val vm: ActiveGameViewModel = viewModel(factory = gameFactory)
ActiveGameScreen( ActiveGameScreen(
vm = vm, vm = vm,
onEnterRound = { navController.navigate(Routes.roundEntry(gameId)) }, onEnterRound = { navController.navigateSafe(Routes.roundEntry(gameId)) },
onGameEnd = { onGameEnd = {
navController.navigate(Routes.gameResults(gameId)) { navController.navigateSafe(Routes.gameResults(gameId)) {
popUpTo(Routes.HOME) popUpTo(Routes.HOME)
} }
}, },
onGameCancelled = { onGameCancelled = {
navController.navigate(Routes.HOME) { navController.navigateSafe(Routes.HOME) {
popUpTo(Routes.HOME) { inclusive = true } popUpTo(Routes.HOME) { inclusive = true }
} }
}, },
onBack = { navController.popBackStack() } onBack = { navController.popSafe() }
) )
} }
@@ -120,8 +142,8 @@ fun Phase10NavHost(
val vm: RoundEntryViewModel = viewModel(factory = gameFactory) val vm: RoundEntryViewModel = viewModel(factory = gameFactory)
RoundEntryScreen( RoundEntryScreen(
vm = vm, vm = vm,
onRoundSubmitted = { navController.popBackStack() }, onRoundSubmitted = { navController.popSafe() },
onBack = { navController.popBackStack() } onBack = { navController.popSafe() }
) )
} }
@@ -135,7 +157,7 @@ fun Phase10NavHost(
GameResultsScreen( GameResultsScreen(
vm = vm, vm = vm,
onHome = { onHome = {
navController.navigate(Routes.HOME) { navController.navigateSafe(Routes.HOME) {
popUpTo(Routes.HOME) { inclusive = true } popUpTo(Routes.HOME) { inclusive = true }
} }
} }
@@ -146,7 +168,7 @@ fun Phase10NavHost(
val vm: LeaderboardViewModel = viewModel(factory = factory) val vm: LeaderboardViewModel = viewModel(factory = factory)
LeaderboardScreen( LeaderboardScreen(
vm = vm, vm = vm,
onBack = { navController.popBackStack() } onBack = { navController.popSafe() }
) )
} }
@@ -154,12 +176,12 @@ fun Phase10NavHost(
val vm: CustomPhasesViewModel = viewModel(factory = factory) val vm: CustomPhasesViewModel = viewModel(factory = factory)
CustomPhasesScreen( CustomPhasesScreen(
vm = vm, vm = vm,
onBack = { navController.popBackStack() } onBack = { navController.popSafe() }
) )
} }
composable(Routes.ABOUT) { composable(Routes.ABOUT) {
AboutScreen(onBack = { navController.popBackStack() }) AboutScreen(onBack = { navController.popSafe() })
} }
} }
} }