fix(wheel-reveal): error state with retry, null-safe uid/couple, close on snapshot error
This commit is contained in:
parent
acaa8e635c
commit
6977db7600
|
|
@ -77,7 +77,8 @@ class FirestoreWheelAnswerDataSource @Inject constructor(
|
|||
/** Live view of both partners' answers; emits whenever either side submits. */
|
||||
fun observe(coupleId: String, sessionId: String): Flow<WheelRevealDoc> = callbackFlow {
|
||||
val reg = doc(coupleId, sessionId).addSnapshotListener { snap, err ->
|
||||
if (err != null || snap == null) return@addSnapshotListener
|
||||
if (err != null) { close(err); return@addSnapshotListener }
|
||||
if (snap == null) return@addSnapshotListener
|
||||
trySend(parse(snap, coupleId))
|
||||
}
|
||||
awaitClose { reg.remove() }
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ import kotlinx.coroutines.launch
|
|||
|
||||
// ── ViewModel ──────────────────────────────────────────────────────────────────
|
||||
|
||||
enum class WheelRevealPhase { LOADING, WAITING, REVEAL }
|
||||
enum class WheelRevealPhase { LOADING, WAITING, REVEAL, ERROR }
|
||||
|
||||
/** One prompt with both partners' answers, ready to render side by side. */
|
||||
data class WheelRevealItem(
|
||||
|
|
@ -80,7 +80,8 @@ data class WheelCompleteUiState(
|
|||
val categoryName: String = "",
|
||||
val partnerName: String = "Your partner",
|
||||
val items: List<WheelRevealItem> = emptyList(),
|
||||
val navigateTo: String? = null
|
||||
val navigateTo: String? = null,
|
||||
val error: String? = null
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -105,8 +106,12 @@ class WheelCompleteViewModel @Inject constructor(
|
|||
|
||||
private fun observe() {
|
||||
viewModelScope.launch {
|
||||
val uid = gameSessionManager.currentUserId ?: return@launch
|
||||
val couple = gameSessionManager.getCoupleForUser(uid) ?: return@launch
|
||||
val uid = gameSessionManager.currentUserId
|
||||
val couple = if (uid != null) gameSessionManager.getCoupleForUser(uid) else null
|
||||
if (uid == null || couple == null) {
|
||||
_uiState.update { it.copy(phase = WheelRevealPhase.ERROR, error = "Couldn't load session. Try again.") }
|
||||
return@launch
|
||||
}
|
||||
userId = uid
|
||||
coupleId = couple.id
|
||||
partnerId = couple.userIds.firstOrNull { it != uid }
|
||||
|
|
@ -117,9 +122,21 @@ class WheelCompleteViewModel @Inject constructor(
|
|||
?.let { name -> _uiState.update { it.copy(partnerName = name) } }
|
||||
}
|
||||
|
||||
if (sessionId.isBlank()) return@launch
|
||||
answerDataSource.observe(couple.id, sessionId).collect { handle(it) }
|
||||
if (sessionId.isBlank()) {
|
||||
_uiState.update { it.copy(phase = WheelRevealPhase.ERROR, error = "Session not found. Return to Play.") }
|
||||
return@launch
|
||||
}
|
||||
runCatching {
|
||||
answerDataSource.observe(couple.id, sessionId).collect { handle(it) }
|
||||
}.onFailure {
|
||||
_uiState.update { s -> s.copy(phase = WheelRevealPhase.ERROR, error = "Couldn't load results. Check your connection.") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun retry() {
|
||||
_uiState.update { it.copy(phase = WheelRevealPhase.LOADING, error = null) }
|
||||
observe()
|
||||
}
|
||||
|
||||
private fun handle(doc: WheelRevealDoc) {
|
||||
|
|
@ -212,6 +229,11 @@ fun WheelCompleteScreen(
|
|||
onSpinAgain = { onNavigate(AppRoute.CATEGORY_PICKER) },
|
||||
onHome = { onNavigate(AppRoute.PLAY) }
|
||||
)
|
||||
WheelRevealPhase.ERROR -> WheelErrorContent(
|
||||
message = state.error ?: "Something went wrong.",
|
||||
onRetry = viewModel::retry,
|
||||
onHome = { onNavigate(AppRoute.PLAY) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -411,6 +433,53 @@ private fun AnswerBlock(label: String, text: String, accent: Color) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WheelErrorContent(
|
||||
message: String,
|
||||
onRetry: () -> Unit,
|
||||
onHome: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.safeDrawingPadding()
|
||||
.navigationBarsPadding()
|
||||
.padding(horizontal = 28.dp, vertical = 40.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(Modifier.weight(1f))
|
||||
Text(
|
||||
text = "Couldn't load results",
|
||||
style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.SemiBold),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Text(
|
||||
text = message,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Spacer(Modifier.weight(1f))
|
||||
Button(
|
||||
onClick = onRetry,
|
||||
modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp),
|
||||
shape = RoundedCornerShape(18.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = CloserPalette.PurpleDeep)
|
||||
) {
|
||||
Text("Try again", color = Color.White)
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = onHome,
|
||||
modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp),
|
||||
shape = RoundedCornerShape(18.dp)
|
||||
) {
|
||||
Text("Back to Play")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun WheelRevealPreview() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue