fix(nav): tab-switch routing prevents stacking tabs; fix(crash): runCatching around getUser/getCoupleForUser across 6 screens
This commit is contained in:
parent
fe1808b36c
commit
17c7ed60b9
|
|
@ -95,22 +95,32 @@ fun AppNavigation(
|
||||||
val shellTitle = currentRoute
|
val shellTitle = currentRoute
|
||||||
?.takeIf { it in shellBackRoutes }
|
?.takeIf { it in shellBackRoutes }
|
||||||
?.let(AppRoute::titleFor)
|
?.let(AppRoute::titleFor)
|
||||||
|
// Tab-switch semantics: pop to the graph start, keep a single instance, and
|
||||||
|
// save/restore each tab's own back stack. Every navigation to a top-level
|
||||||
|
// route must go through this so a tab is never pushed on top of another tab.
|
||||||
|
val selectTab: (String) -> Unit = { route ->
|
||||||
|
navController.navigate(route) {
|
||||||
|
popUpTo(navController.graph.findStartDestination().id) {
|
||||||
|
saveState = true
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
restoreState = true
|
||||||
|
}
|
||||||
|
}
|
||||||
val navigateBackOrHome: () -> Unit = {
|
val navigateBackOrHome: () -> Unit = {
|
||||||
if (!navController.popBackStack()) {
|
if (!navController.popBackStack()) {
|
||||||
navController.navigate(AppRoute.HOME) {
|
selectTab(AppRoute.HOME)
|
||||||
popUpTo(navController.graph.findStartDestination().id) {
|
|
||||||
saveState = true
|
|
||||||
}
|
|
||||||
launchSingleTop = true
|
|
||||||
restoreState = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val navigateRoute: (String) -> Unit = { route ->
|
val navigateRoute: (String) -> Unit = { route ->
|
||||||
if (route == "back") {
|
when {
|
||||||
navigateBackOrHome()
|
route == "back" -> navigateBackOrHome()
|
||||||
} else {
|
// Top-level tabs must use tab-switch semantics. Plain-navigating to a
|
||||||
navController.navigate(route)
|
// tab (e.g. a "Game waiting" card → PLAY) would stack it on the current
|
||||||
|
// tab; the bottom bar then saves that substack and `restoreState` later
|
||||||
|
// lands the user on the wrong tab (Home → Play was the symptom).
|
||||||
|
route in AppRoute.topLevelRoutes -> selectTab(route)
|
||||||
|
else -> navController.navigate(route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,15 +153,7 @@ fun AppNavigation(
|
||||||
if (currentRoute in bottomRoutes) {
|
if (currentRoute in bottomRoutes) {
|
||||||
AppBottomNavigation(
|
AppBottomNavigation(
|
||||||
currentRoute = currentRoute,
|
currentRoute = currentRoute,
|
||||||
onRouteSelected = { route ->
|
onRouteSelected = selectTab
|
||||||
navController.navigate(route) {
|
|
||||||
popUpTo(navController.graph.findStartDestination().id) {
|
|
||||||
saveState = true
|
|
||||||
}
|
|
||||||
launchSingleTop = true
|
|
||||||
restoreState = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,9 @@ class GameSessionManager @Inject constructor(
|
||||||
val activeSession = sessionRepository.getActiveSessionForCouple(couple.id)
|
val activeSession = sessionRepository.getActiveSessionForCouple(couple.id)
|
||||||
if (activeSession != null) {
|
if (activeSession != null) {
|
||||||
val partnerId = couple.userIds.firstOrNull { it != userId }
|
val partnerId = couple.userIds.firstOrNull { it != userId }
|
||||||
val partnerName = partnerId?.let { userRepository.getUser(it) }?.displayName ?: "Partner"
|
val partnerName = partnerId
|
||||||
|
?.let { runCatching { userRepository.getUser(it) }.getOrNull() }
|
||||||
|
?.displayName ?: "Partner"
|
||||||
return Result.failure(
|
return Result.failure(
|
||||||
Exception("partner_active_session|$partnerName|${gameTypeLabel(gameType)}")
|
Exception("partner_active_session|$partnerName|${gameTypeLabel(gameType)}")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1008,8 +1008,9 @@ class DSReplayViewModel @Inject constructor(
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val partnerId = couple.userIds.firstOrNull { it != uid }
|
val partnerId = couple.userIds.firstOrNull { it != uid }
|
||||||
val partnerName = partnerId?.let { gameSessionManager.getUser(it) }?.displayName
|
val partnerName = partnerId
|
||||||
?: "Your partner"
|
?.let { runCatching { gameSessionManager.getUser(it) }.getOrNull() }
|
||||||
|
?.displayName ?: "Your partner"
|
||||||
_uiState.update { it.copy(partnerName = partnerName) }
|
_uiState.update { it.copy(partnerName = partnerName) }
|
||||||
|
|
||||||
val session = sessionRepository.getSessionById(couple.id, sessionId) ?: run {
|
val session = sessionRepository.getSessionById(couple.id, sessionId) ?: run {
|
||||||
|
|
|
||||||
|
|
@ -62,11 +62,16 @@ class WaitingForPartnerViewModel @Inject constructor(
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val userId = gameSessionManager.currentUserId ?: return@launch
|
val userId = gameSessionManager.currentUserId ?: return@launch
|
||||||
val couple = gameSessionManager.getCoupleForUser(userId) ?: return@launch
|
val couple = runCatching { gameSessionManager.getCoupleForUser(userId) }.getOrNull()
|
||||||
|
?: return@launch
|
||||||
coupleId = couple.id
|
coupleId = couple.id
|
||||||
|
|
||||||
val partnerId = couple.userIds.firstOrNull { it != userId }
|
val partnerId = couple.userIds.firstOrNull { it != userId }
|
||||||
val partnerName = partnerId?.let { gameSessionManager.getUser(it) }?.displayName ?: "Partner"
|
// getUser() throws on Firestore failure (e.g. PERMISSION_DENIED); never let
|
||||||
|
// that escape this launch or the whole app crashes on the waiting screen.
|
||||||
|
val partnerName = partnerId
|
||||||
|
?.let { runCatching { gameSessionManager.getUser(it) }.getOrNull() }
|
||||||
|
?.displayName ?: "Partner"
|
||||||
|
|
||||||
gameSessionManager.observeActiveSession(couple.id).collect { session ->
|
gameSessionManager.observeActiveSession(couple.id).collect { session ->
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
|
|
|
||||||
|
|
@ -1179,8 +1179,9 @@ class HowWellReplayViewModel @Inject constructor(
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val partnerId = couple.userIds.firstOrNull { it != uid }
|
val partnerId = couple.userIds.firstOrNull { it != uid }
|
||||||
val partnerName = partnerId?.let { gameSessionManager.getUser(it) }?.displayName
|
val partnerName = partnerId
|
||||||
?: "Your partner"
|
?.let { runCatching { gameSessionManager.getUser(it) }.getOrNull() }
|
||||||
|
?.displayName ?: "Your partner"
|
||||||
_uiState.update { it.copy(partnerName = partnerName) }
|
_uiState.update { it.copy(partnerName = partnerName) }
|
||||||
|
|
||||||
val session = sessionRepository.getSessionById(couple.id, sessionId) ?: run {
|
val session = sessionRepository.getSessionById(couple.id, sessionId) ?: run {
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ class PairingSuccessViewModel @Inject constructor(
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val myId = authRepository.currentUserId ?: return@launch
|
val myId = authRepository.currentUserId ?: return@launch
|
||||||
val me = userRepository.getUser(myId)
|
val me = runCatching { userRepository.getUser(myId) }.getOrNull()
|
||||||
val couple = coupleRepository.getCoupleForUser(myId)
|
val couple = coupleRepository.getCoupleForUser(myId)
|
||||||
val partnerId = couple?.userIds?.firstOrNull { it != myId }
|
val partnerId = couple?.userIds?.firstOrNull { it != myId }
|
||||||
val partner = partnerId?.let { runCatching { userRepository.getUser(it) }.getOrNull() }
|
val partner = partnerId?.let { runCatching { userRepository.getUser(it) }.getOrNull() }
|
||||||
|
|
|
||||||
|
|
@ -1249,8 +1249,9 @@ class ThisOrThatReplayViewModel @Inject constructor(
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val partnerId = couple.userIds.firstOrNull { it != uid }
|
val partnerId = couple.userIds.firstOrNull { it != uid }
|
||||||
val partnerName = partnerId?.let { gameSessionManager.getUser(it) }?.displayName
|
val partnerName = partnerId
|
||||||
?: "Your partner"
|
?.let { runCatching { gameSessionManager.getUser(it) }.getOrNull() }
|
||||||
|
?.displayName ?: "Your partner"
|
||||||
_uiState.update { it.copy(partnerName = partnerName) }
|
_uiState.update { it.copy(partnerName = partnerName) }
|
||||||
|
|
||||||
val session = sessionRepository.getSessionById(couple.id, sessionId) ?: run {
|
val session = sessionRepository.getSessionById(couple.id, sessionId) ?: run {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue