From c3c5438bcc5c0b700f88fba0c40b74d0913b33b4 Mon Sep 17 00:00:00 2001 From: null Date: Thu, 25 Jun 2026 09:58:24 -0500 Subject: [PATCH] fix(home): 'Play now' resumes the waiting game, not the generic hub (B-002 P2) Resolve the active session's gameType to its resume route (gameRouteFor) and carry it on HomeAction.gameRoute / HomeUiState.waitingGameRoute; HomeActionTarget.Game now navigates there (fallback Play hub). Each game screen auto-joins the couple's active session on open, so the Home 'Play now' CTA drops the user straight into the actual waiting game. Co-Authored-By: Claude Opus 4.8 --- .../java/app/closer/ui/home/HomeScreen.kt | 3 +- .../java/app/closer/ui/home/HomeViewModel.kt | 37 ++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/app/closer/ui/home/HomeScreen.kt b/app/src/main/java/app/closer/ui/home/HomeScreen.kt index d4df5276..823ea2d8 100644 --- a/app/src/main/java/app/closer/ui/home/HomeScreen.kt +++ b/app/src/main/java/app/closer/ui/home/HomeScreen.kt @@ -216,7 +216,8 @@ private fun HomeCallbacks.toActionHandler(onNavigate: (String) -> Unit): (HomeAc HomeActionTarget.QuestionPacks -> action.categoryId?.let(onCategory) ?: onPacks() HomeActionTarget.Settings -> onSettings() HomeActionTarget.AnswerReveal -> onReveal() - HomeActionTarget.Game -> onNavigate(AppRoute.PLAY) + // Resume the specific waiting game when known (B-002); fall back to the Play hub. + HomeActionTarget.Game -> onNavigate(action.gameRoute ?: AppRoute.PLAY) HomeActionTarget.Challenge -> onNavigate(AppRoute.CONNECTION_CHALLENGES) HomeActionTarget.DatePlan -> onNavigate(AppRoute.DATE_MATCHES) HomeActionTarget.MemoryCapsule -> onNavigate(AppRoute.MEMORY_LANE) diff --git a/app/src/main/java/app/closer/ui/home/HomeViewModel.kt b/app/src/main/java/app/closer/ui/home/HomeViewModel.kt index fb26e035..6133db74 100644 --- a/app/src/main/java/app/closer/ui/home/HomeViewModel.kt +++ b/app/src/main/java/app/closer/ui/home/HomeViewModel.kt @@ -3,9 +3,11 @@ package app.closer.ui.home import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import app.closer.core.navigation.AppRoute import app.closer.crypto.CoupleEncryptionManager import app.closer.crypto.EncryptionStatus import app.closer.crypto.SealedRevealManager +import app.closer.domain.model.GameType import app.closer.data.remote.FirestoreAnswerDataSource import app.closer.data.remote.FirestoreCapsuleDataSource import app.closer.data.remote.FirestoreChallengeDataSource @@ -89,7 +91,10 @@ data class HomeAction( val target: HomeActionTarget, val tone: HomeActionTone, val metric: String? = null, - val categoryId: String? = null + val categoryId: String? = null, + // For the "your partner is waiting to play" CTA: the specific game route to resume + // (so "Play now" jumps into the actual waiting game, not the generic Play hub). B-002. + val gameRoute: String? = null ) data class PendingActionCard( @@ -99,6 +104,19 @@ data class PendingActionCard( val target: HomeActionTarget ) +/** + * The entry route that resumes an in-progress game of [gameType]. Each game screen + * detects the couple's active session on open and joins it, so navigating here lets the + * Home "Play now" CTA drop the user straight back into the waiting game (B-002). + */ +private fun gameRouteFor(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 +} + enum class DailyQuestionState { UNANSWERED, USER_ANSWERED_PARTNER_PENDING, @@ -128,6 +146,9 @@ data class HomeUiState( val pendingActions: List = emptyList(), // Retention signals — populated in loadHome() and observeAnswers() val hasWaitingGame: Boolean = false, + // The route of the active game waiting for this user, so the Home "Play now" CTA + // resumes that specific game instead of dumping on the generic Play hub (B-002). + val waitingGameRoute: String? = null, val hasActiveChallenge: Boolean = false, val hasUpcomingDatePlan: Boolean = false, val hasUnlockedCapsule: Boolean = false, @@ -243,6 +264,7 @@ class HomeViewModel @Inject constructor( // Retention signal fetches — run in parallel, failures silently default to false. var hasWaitingGame = false + var waitingGameRoute: String? = null var hasActiveChallenge = false var hasUpcomingDatePlan = false var hasUnlockedCapsule = false @@ -252,8 +274,9 @@ class HomeViewModel @Inject constructor( val gameJob = async { runCatching { val session = questionSessionRepository.getActiveSessionForCouple(coupleId) - session != null && uid !in session.completedByUsers - }.getOrDefault(false) + ?.takeIf { uid !in it.completedByUsers } + session to gameRouteFor(session?.gameType) + }.getOrDefault(null to null) } val challengeJob = async { runCatching { @@ -276,7 +299,9 @@ class HomeViewModel @Inject constructor( .any { it.status == "sealed" && it.unlockAt in 1L..now } }.getOrDefault(false) } - hasWaitingGame = gameJob.await() + val (waitingSession, waitingRoute) = gameJob.await() + hasWaitingGame = waitingSession != null + waitingGameRoute = waitingRoute hasActiveChallenge = challengeJob.await() hasUpcomingDatePlan = dateJob.await() hasUnlockedCapsule = capsuleJob.await() @@ -295,6 +320,7 @@ class HomeViewModel @Inject constructor( partnerLeftEvent = false, needsRecovery = needsRecovery, hasWaitingGame = hasWaitingGame, + waitingGameRoute = waitingGameRoute, hasActiveChallenge = hasActiveChallenge, hasUpcomingDatePlan = hasUpcomingDatePlan, hasUnlockedCapsule = hasUnlockedCapsule, @@ -598,7 +624,8 @@ class HomeViewModel @Inject constructor( body = "A game is ready for the two of you. Jump back in and keep the ritual going.", cta = "Play now", target = HomeActionTarget.Game, - tone = HomeActionTone.Ritual + tone = HomeActionTone.Ritual, + gameRoute = waitingGameRoute ) Priority.CHALLENGE_WAITING -> HomeAction(