fix(games): add 'Join the game' escape to WaitingForPartner screen (B-004)

The generic WaitingForPartner screen only exited when the session became null, so a partner who
landed there for an async game they could actually play (every current game is async — both play
on their own device) was stuck waiting forever, recoverable only via Back to Games. Now the screen
resolves the active session's game route and offers a primary 'Join the game' action that drops the
user into the game (which auto-joins the session). Deterministic repro: QA starts How Well, Sam
opens a different game -> one-game lock routes Sam to WaitingForPartner -> 'Join the game' -> How
Well guess intro. Verified live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
null 2026-06-25 12:07:48 -05:00
parent 23c99233b4
commit da7fc74faa
1 changed files with 42 additions and 7 deletions

View File

@ -47,7 +47,13 @@ data class WaitingForPartnerUiState(
val isLoading: Boolean = true,
val gameType: String = GameType.WHEEL,
val partnerName: String = "Partner",
val navigateTo: String? = null
val navigateTo: String? = null,
/**
* Route into the partner's active game so this user can play their part, instead of being
* stuck on this "waiting" screen. Every current game is async (both play on their own device),
* so the partner who lands here can always join. Null only for an unknown game type. B-004.
*/
val joinRoute: String? = null
)
@HiltViewModel
@ -81,7 +87,8 @@ class WaitingForPartnerViewModel @Inject constructor(
it.copy(
isLoading = false,
gameType = session.gameType,
partnerName = partnerName
partnerName = partnerName,
joinRoute = gameTypeRoute(session.gameType)
)
}
}
@ -173,11 +180,30 @@ fun WaitingForPartnerScreen(
style = MaterialTheme.typography.bodySmall
)
Button(
onClick = { onNavigate(AppRoute.PLAY) },
modifier = Modifier.fillMaxWidth()
) {
Text("Back to Games")
// Primary action: join the partner's game and play your part. Without this the
// screen was a dead-end for async games the user could actually play (B-004).
state.joinRoute?.let { route ->
Button(
onClick = { onNavigate(route) },
modifier = Modifier.fillMaxWidth()
) {
Text("Join the game")
}
}
if (state.joinRoute != null) {
TextButton(
onClick = { onNavigate(AppRoute.PLAY) },
modifier = Modifier.fillMaxWidth()
) {
Text("Back to Games")
}
} else {
Button(
onClick = { onNavigate(AppRoute.PLAY) },
modifier = Modifier.fillMaxWidth()
) {
Text("Back to Games")
}
}
TextButton(onClick = viewModel::abandonPartnerGame) {
Text(
@ -200,6 +226,15 @@ private fun gameTypeLabel(gameType: String): String = when (gameType) {
else -> gameType
}
/** Entry route that joins the partner's active game (each game screen auto-joins on open). B-004. */
private fun gameTypeRoute(gameType: String): String? = when (gameType) {
GameType.WHEEL -> AppRoute.SPIN_WHEEL_RANDOM
GameType.THIS_OR_THAT -> AppRoute.THIS_OR_THAT
GameType.HOW_WELL -> AppRoute.HOW_WELL
GameType.DESIRE_SYNC -> AppRoute.DESIRE_SYNC
else -> null
}
private fun gameTypeGlyphKey(gameType: String): String = when (gameType) {
GameType.WHEEL -> "play"
GameType.THIS_OR_THAT -> "question"