From f1549c642cbc24cec2ff0bfd4e9316294f1b1477 Mon Sep 17 00:00:00 2001 From: null Date: Thu, 25 Jun 2026 12:07:48 -0500 Subject: [PATCH] fix(games): add 'Join the game' escape to WaitingForPartner screen (B-004) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../ui/games/WaitingForPartnerScreen.kt | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/closer/ui/games/WaitingForPartnerScreen.kt b/app/src/main/java/app/closer/ui/games/WaitingForPartnerScreen.kt index 3f3db6a4..1d728cce 100644 --- a/app/src/main/java/app/closer/ui/games/WaitingForPartnerScreen.kt +++ b/app/src/main/java/app/closer/ui/games/WaitingForPartnerScreen.kt @@ -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"