feat: add partner-waiting flow across all game screens with QuestionSession model updates
This commit is contained in:
parent
733c0967c2
commit
3ba6c659dd
|
|
@ -43,7 +43,8 @@ class QuestionSessionRepositoryImpl @Inject constructor(
|
||||||
"partnerCompletedAt" to session.partnerCompletedAt,
|
"partnerCompletedAt" to session.partnerCompletedAt,
|
||||||
"isPremium" to session.isPremium,
|
"isPremium" to session.isPremium,
|
||||||
"status" to session.status,
|
"status" to session.status,
|
||||||
"gameType" to session.gameType
|
"gameType" to session.gameType,
|
||||||
|
"completedByUsers" to session.completedByUsers
|
||||||
)
|
)
|
||||||
doc.set(data).await()
|
doc.set(data).await()
|
||||||
}
|
}
|
||||||
|
|
@ -152,4 +153,40 @@ class QuestionSessionRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
override suspend fun hasActiveSession(coupleId: String): Boolean =
|
override suspend fun hasActiveSession(coupleId: String): Boolean =
|
||||||
getActiveSessionForCouple(coupleId) != null
|
getActiveSessionForCouple(coupleId) != null
|
||||||
|
|
||||||
|
override suspend fun markUserComplete(
|
||||||
|
sessionId: String,
|
||||||
|
coupleId: String,
|
||||||
|
userId: String
|
||||||
|
): Result<Unit> = runCatching {
|
||||||
|
val docRef = firestore.collection(FirestoreCollections.COUPLES)
|
||||||
|
.document(coupleId)
|
||||||
|
.collection(FirestoreCollections.Couples.SESSIONS)
|
||||||
|
.document(sessionId)
|
||||||
|
|
||||||
|
firestore.runTransaction { tx ->
|
||||||
|
val snap = tx.get(docRef)
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val completedBy = (snap.get("completedByUsers") as? List<String>)
|
||||||
|
?.toMutableList() ?: mutableListOf()
|
||||||
|
if (userId !in completedBy) completedBy.add(userId)
|
||||||
|
|
||||||
|
val updates = mutableMapOf<String, Any>("completedByUsers" to completedBy)
|
||||||
|
if (completedBy.size >= 2) {
|
||||||
|
updates["status"] = "completed"
|
||||||
|
updates["completedAt"] = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
tx.update(docRef, updates)
|
||||||
|
}.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun abandonSession(coupleId: String): Result<Unit> = runCatching {
|
||||||
|
val activeSession = getActiveSessionForCouple(coupleId) ?: return@runCatching
|
||||||
|
saveSession(
|
||||||
|
activeSession.copy(
|
||||||
|
completedAt = System.currentTimeMillis(),
|
||||||
|
status = "completed"
|
||||||
|
)
|
||||||
|
).getOrThrow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,6 @@ data class QuestionSession(
|
||||||
val partnerCompletedAt: Long? = null,
|
val partnerCompletedAt: Long? = null,
|
||||||
val isPremium: Boolean = false,
|
val isPremium: Boolean = false,
|
||||||
val status: String = "active",
|
val status: String = "active",
|
||||||
val gameType: String = "wheel"
|
val gameType: String = "wheel",
|
||||||
|
val completedByUsers: List<String> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,10 @@ interface QuestionSessionRepository {
|
||||||
suspend fun getActiveSessionForCouple(coupleId: String): QuestionSession?
|
suspend fun getActiveSessionForCouple(coupleId: String): QuestionSession?
|
||||||
fun observeActiveSessionForCouple(coupleId: String): Flow<QuestionSession?>
|
fun observeActiveSessionForCouple(coupleId: String): Flow<QuestionSession?>
|
||||||
suspend fun hasActiveSession(coupleId: String): Boolean
|
suspend fun hasActiveSession(coupleId: String): Boolean
|
||||||
|
|
||||||
|
// Per-user completion: marks one user done; auto-completes when both users are recorded.
|
||||||
|
suspend fun markUserComplete(sessionId: String, coupleId: String, userId: String): Result<Unit>
|
||||||
|
|
||||||
|
// Force-complete the active session (escape hatch for stuck/abandoned games).
|
||||||
|
suspend fun abandonSession(coupleId: String): Result<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,24 @@ class GameSessionManager @Inject constructor(
|
||||||
suspend fun hasActiveSession(coupleId: String): Boolean =
|
suspend fun hasActiveSession(coupleId: String): Boolean =
|
||||||
sessionRepository.hasActiveSession(coupleId)
|
sessionRepository.hasActiveSession(coupleId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the current user's side of the session as complete.
|
||||||
|
* The session is automatically finished once both users have marked done.
|
||||||
|
* Safe to call concurrently — a Firestore transaction ensures idempotency.
|
||||||
|
*/
|
||||||
|
suspend fun markUserComplete(sessionId: String, coupleId: String): Result<Unit> {
|
||||||
|
val userId = authRepository.currentUserId
|
||||||
|
?: return Result.failure(Exception("Not signed in"))
|
||||||
|
return sessionRepository.markUserComplete(sessionId, coupleId, userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force-finish the couple's active session without requiring both users to mark done.
|
||||||
|
* Use when one partner wants to release the lock (e.g. partner never played).
|
||||||
|
*/
|
||||||
|
suspend fun abandonSession(coupleId: String): Result<Unit> =
|
||||||
|
sessionRepository.abandonSession(coupleId)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe active session changes for a couple.
|
* Observe active session changes for a couple.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -274,12 +274,12 @@ class DesireSyncViewModel @Inject constructor(
|
||||||
finishSession()
|
finishSession()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Mark the shared session completed (idempotent — fine if the partner already did). */
|
/** Both answered — mark this user done; session auto-completes once both sides recorded. */
|
||||||
private fun finishSession() {
|
private fun finishSession() {
|
||||||
val cId = coupleId ?: return
|
val cId = coupleId ?: return
|
||||||
val sId = sessionId ?: return
|
val sId = sessionId ?: return
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
runCatching { gameSessionManager.finishGame(sId, cId) }
|
gameSessionManager.markUserComplete(sId, cId)
|
||||||
.onFailure { Log.d(TAG, "finishSession no-op: ${it.message}") }
|
.onFailure { Log.d(TAG, "finishSession no-op: ${it.message}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -293,6 +293,16 @@ class DesireSyncViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Force-end the game from the waiting screen (partner never played). */
|
||||||
|
fun abandon() {
|
||||||
|
val cId = coupleId ?: return
|
||||||
|
viewModelScope.launch {
|
||||||
|
gameSessionManager.abandonSession(cId)
|
||||||
|
.onFailure { Log.d(TAG, "abandon no-op: ${it.message}") }
|
||||||
|
_uiState.update { it.copy(navigateTo = AppRoute.PLAY) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun restart() {
|
fun restart() {
|
||||||
observeJob?.cancel()
|
observeJob?.cancel()
|
||||||
sessionId = null
|
sessionId = null
|
||||||
|
|
@ -363,7 +373,8 @@ fun DesireSyncScreen(
|
||||||
}
|
}
|
||||||
DesireSyncPhase.WAITING -> DSWaitingScreen(
|
DesireSyncPhase.WAITING -> DSWaitingScreen(
|
||||||
partnerName = state.partnerName,
|
partnerName = state.partnerName,
|
||||||
onBack = viewModel::quit
|
onBack = viewModel::quit,
|
||||||
|
onAbandon = viewModel::abandon
|
||||||
)
|
)
|
||||||
DesireSyncPhase.REVEAL -> DSRevealScreen(
|
DesireSyncPhase.REVEAL -> DSRevealScreen(
|
||||||
matches = state.matches,
|
matches = state.matches,
|
||||||
|
|
@ -419,7 +430,7 @@ private fun DSIntroScreen(total: Int, onReady: () -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DSWaitingScreen(partnerName: String, onBack: () -> Unit) {
|
private fun DSWaitingScreen(partnerName: String, onBack: () -> Unit, onAbandon: () -> Unit = {}) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -455,6 +466,13 @@ private fun DSWaitingScreen(partnerName: String, onBack: () -> Unit) {
|
||||||
modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp),
|
modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp),
|
||||||
shape = RoundedCornerShape(18.dp)
|
shape = RoundedCornerShape(18.dp)
|
||||||
) { Text("Back to Play") }
|
) { Text("Back to Play") }
|
||||||
|
TextButton(onClick = onAbandon) {
|
||||||
|
Text(
|
||||||
|
"End this game",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
|
@ -55,10 +56,13 @@ class WaitingForPartnerViewModel @Inject constructor(
|
||||||
private val _uiState = MutableStateFlow(WaitingForPartnerUiState())
|
private val _uiState = MutableStateFlow(WaitingForPartnerUiState())
|
||||||
val uiState: StateFlow<WaitingForPartnerUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<WaitingForPartnerUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
private var coupleId: String? = null
|
||||||
|
|
||||||
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 = gameSessionManager.getCoupleForUser(userId) ?: return@launch
|
||||||
|
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"
|
val partnerName = partnerId?.let { gameSessionManager.getUser(it) }?.displayName ?: "Partner"
|
||||||
|
|
@ -79,6 +83,15 @@ class WaitingForPartnerViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Force-end the partner's active session so this user can start a new game. */
|
||||||
|
fun abandonPartnerGame() {
|
||||||
|
val cId = coupleId ?: return
|
||||||
|
viewModelScope.launch {
|
||||||
|
gameSessionManager.abandonSession(cId)
|
||||||
|
_uiState.update { it.copy(navigateTo = AppRoute.PLAY) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onNavigated() {
|
fun onNavigated() {
|
||||||
_uiState.update { it.copy(navigateTo = null) }
|
_uiState.update { it.copy(navigateTo = null) }
|
||||||
}
|
}
|
||||||
|
|
@ -154,6 +167,13 @@ fun WaitingForPartnerScreen(
|
||||||
) {
|
) {
|
||||||
Text("Back to Games")
|
Text("Back to Games")
|
||||||
}
|
}
|
||||||
|
TextButton(onClick = viewModel::abandonPartnerGame) {
|
||||||
|
Text(
|
||||||
|
"End their game",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -323,12 +323,12 @@ class HowWellViewModel @Inject constructor(
|
||||||
private fun HowWellRawAnswer?.toAnswer(): HowWellAnswer =
|
private fun HowWellRawAnswer?.toAnswer(): HowWellAnswer =
|
||||||
HowWellAnswer(this?.optionId, this?.scale)
|
HowWellAnswer(this?.optionId, this?.scale)
|
||||||
|
|
||||||
/** Mark the shared session completed (idempotent — fine if the partner already did). */
|
/** Both answered — mark this user done; session auto-completes once both sides recorded. */
|
||||||
private fun finishSession() {
|
private fun finishSession() {
|
||||||
val cId = coupleId ?: return
|
val cId = coupleId ?: return
|
||||||
val sId = sessionId ?: return
|
val sId = sessionId ?: return
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
runCatching { gameSessionManager.finishGame(sId, cId) }
|
gameSessionManager.markUserComplete(sId, cId)
|
||||||
.onFailure { Log.d(TAG, "finishSession no-op: ${it.message}") }
|
.onFailure { Log.d(TAG, "finishSession no-op: ${it.message}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -340,6 +340,16 @@ class HowWellViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Force-end the game from the waiting screen (partner never played). */
|
||||||
|
fun abandon() {
|
||||||
|
val cId = coupleId ?: return
|
||||||
|
viewModelScope.launch {
|
||||||
|
gameSessionManager.abandonSession(cId)
|
||||||
|
.onFailure { Log.d(TAG, "abandon no-op: ${it.message}") }
|
||||||
|
_uiState.update { it.copy(navigateTo = AppRoute.PLAY) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun restart() {
|
fun restart() {
|
||||||
observeJob?.cancel()
|
observeJob?.cancel()
|
||||||
sessionId = null
|
sessionId = null
|
||||||
|
|
@ -419,7 +429,8 @@ fun HowWellScreen(
|
||||||
HowWellPhase.WAITING -> HowWellWaitingScreen(
|
HowWellPhase.WAITING -> HowWellWaitingScreen(
|
||||||
amSubject = state.amSubject,
|
amSubject = state.amSubject,
|
||||||
partnerName = state.partnerName,
|
partnerName = state.partnerName,
|
||||||
onBack = viewModel::quit
|
onBack = viewModel::quit,
|
||||||
|
onAbandon = viewModel::abandon
|
||||||
)
|
)
|
||||||
HowWellPhase.COMPLETE -> CompleteScreen(
|
HowWellPhase.COMPLETE -> CompleteScreen(
|
||||||
score = state.score,
|
score = state.score,
|
||||||
|
|
@ -489,7 +500,7 @@ private fun PlayerIntroScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun HowWellWaitingScreen(amSubject: Boolean, partnerName: String, onBack: () -> Unit) {
|
private fun HowWellWaitingScreen(amSubject: Boolean, partnerName: String, onBack: () -> Unit, onAbandon: () -> Unit = {}) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -528,6 +539,13 @@ private fun HowWellWaitingScreen(amSubject: Boolean, partnerName: String, onBack
|
||||||
modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp),
|
modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp),
|
||||||
shape = RoundedCornerShape(18.dp)
|
shape = RoundedCornerShape(18.dp)
|
||||||
) { Text("Back to Play") }
|
) { Text("Back to Play") }
|
||||||
|
TextButton(onClick = onAbandon) {
|
||||||
|
Text(
|
||||||
|
"End this game",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -288,12 +288,12 @@ class ThisOrThatViewModel @Inject constructor(
|
||||||
else -> optionId
|
else -> optionId
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Mark the shared session completed (idempotent — fine if the partner already did). */
|
/** Both answered — mark this user done; session auto-completes once both sides recorded. */
|
||||||
private fun finishSession() {
|
private fun finishSession() {
|
||||||
val cId = coupleId ?: return
|
val cId = coupleId ?: return
|
||||||
val sId = sessionId ?: return
|
val sId = sessionId ?: return
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
runCatching { gameSessionManager.finishGame(sId, cId) }
|
gameSessionManager.markUserComplete(sId, cId)
|
||||||
.onFailure { Log.d(TAG, "finishSession no-op: ${it.message}") }
|
.onFailure { Log.d(TAG, "finishSession no-op: ${it.message}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -307,6 +307,16 @@ class ThisOrThatViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Force-end the game from the waiting screen (partner never played). */
|
||||||
|
fun abandon() {
|
||||||
|
val cId = coupleId ?: return
|
||||||
|
viewModelScope.launch {
|
||||||
|
gameSessionManager.abandonSession(cId)
|
||||||
|
.onFailure { Log.d(TAG, "abandon no-op: ${it.message}") }
|
||||||
|
_uiState.update { it.copy(navigateTo = AppRoute.PLAY) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun restart() {
|
fun restart() {
|
||||||
observeJob?.cancel()
|
observeJob?.cancel()
|
||||||
sessionId = null
|
sessionId = null
|
||||||
|
|
@ -362,7 +372,8 @@ fun ThisOrThatScreen(
|
||||||
)
|
)
|
||||||
TotPhase.WAITING -> WaitingForRevealScreen(
|
TotPhase.WAITING -> WaitingForRevealScreen(
|
||||||
partnerName = state.partnerName,
|
partnerName = state.partnerName,
|
||||||
onBack = viewModel::quit
|
onBack = viewModel::quit,
|
||||||
|
onAbandon = viewModel::abandon
|
||||||
)
|
)
|
||||||
TotPhase.REVEAL -> ThisOrThatReveal(
|
TotPhase.REVEAL -> ThisOrThatReveal(
|
||||||
matched = state.matchedCount,
|
matched = state.matchedCount,
|
||||||
|
|
@ -694,7 +705,8 @@ private fun VersusBadge(
|
||||||
@Composable
|
@Composable
|
||||||
private fun WaitingForRevealScreen(
|
private fun WaitingForRevealScreen(
|
||||||
partnerName: String,
|
partnerName: String,
|
||||||
onBack: () -> Unit
|
onBack: () -> Unit,
|
||||||
|
onAbandon: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -748,6 +760,13 @@ private fun WaitingForRevealScreen(
|
||||||
) {
|
) {
|
||||||
Text("Back to Play")
|
Text("Back to Play")
|
||||||
}
|
}
|
||||||
|
TextButton(onClick = onAbandon) {
|
||||||
|
Text(
|
||||||
|
"End this game",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
|
@ -146,15 +147,26 @@ class WheelCompleteViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Both answered — mark this user done; session auto-completes once both sides recorded. */
|
||||||
private fun finishSession() {
|
private fun finishSession() {
|
||||||
val cId = coupleId ?: return
|
val cId = coupleId ?: return
|
||||||
if (sessionId.isBlank()) return
|
if (sessionId.isBlank()) return
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
runCatching { gameSessionManager.finishGame(sessionId, cId) }
|
gameSessionManager.markUserComplete(sessionId, cId)
|
||||||
.onFailure { Log.d(TAG, "finishSession no-op: ${it.message}") }
|
.onFailure { Log.d(TAG, "finishSession no-op: ${it.message}") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Force-end the game from the waiting screen (partner never played). */
|
||||||
|
fun abandon() {
|
||||||
|
val cId = coupleId ?: return
|
||||||
|
viewModelScope.launch {
|
||||||
|
gameSessionManager.abandonSession(cId)
|
||||||
|
.onFailure { Log.d(TAG, "abandon no-op: ${it.message}") }
|
||||||
|
_uiState.update { it.copy(navigateTo = AppRoute.PLAY) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onNavigated() = _uiState.update { it.copy(navigateTo = null) }
|
fun onNavigated() = _uiState.update { it.copy(navigateTo = null) }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -191,7 +203,8 @@ fun WheelCompleteScreen(
|
||||||
)
|
)
|
||||||
WheelRevealPhase.WAITING -> WheelWaitingContent(
|
WheelRevealPhase.WAITING -> WheelWaitingContent(
|
||||||
partnerName = state.partnerName,
|
partnerName = state.partnerName,
|
||||||
onHome = { onNavigate(AppRoute.PLAY) }
|
onHome = { onNavigate(AppRoute.PLAY) },
|
||||||
|
onAbandon = viewModel::abandon
|
||||||
)
|
)
|
||||||
WheelRevealPhase.REVEAL -> WheelRevealContent(
|
WheelRevealPhase.REVEAL -> WheelRevealContent(
|
||||||
categoryName = state.categoryName,
|
categoryName = state.categoryName,
|
||||||
|
|
@ -207,7 +220,8 @@ fun WheelCompleteScreen(
|
||||||
@Composable
|
@Composable
|
||||||
private fun WheelWaitingContent(
|
private fun WheelWaitingContent(
|
||||||
partnerName: String,
|
partnerName: String,
|
||||||
onHome: () -> Unit
|
onHome: () -> Unit,
|
||||||
|
onAbandon: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -253,6 +267,13 @@ private fun WheelWaitingContent(
|
||||||
) {
|
) {
|
||||||
Text("Back to Play")
|
Text("Back to Play")
|
||||||
}
|
}
|
||||||
|
TextButton(onClick = onAbandon) {
|
||||||
|
Text(
|
||||||
|
"End this game",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue