feat: notification copy, error messages, animations, reduced-motion support (batch v0.2.2)
- Rewrite all notification titles/bodies + channel descriptions to warmer, partner-centric tone
- Update error messages across all screens for clarity and consistency
- Add AnimatedVisibility + reduced-motion detection (Settings.Global.ANIMATOR_DURATION_SCALE) to AnswerRevealScreen and LocalQuestionContent
- Polish settings copy (quiet hours, partner activity labels, info footer)
- Update all game error states with actionable language ('Go back and try again')
- Refresh docs screenshots
|
|
@ -98,28 +98,28 @@ class AppMessagingService : FirebaseMessagingService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveTitle(type: String): String? = when (type) {
|
private fun resolveTitle(type: String): String? = when (type) {
|
||||||
"daily_question" -> "Your daily question is waiting!"
|
"daily_question" -> "Today's question is here."
|
||||||
"partner_answered" -> "Your partner just answered!"
|
"partner_answered" -> "Your partner answered."
|
||||||
"partner_left" -> "Your partner has left"
|
"partner_left" -> "You've been unlinked."
|
||||||
"streak" -> "Keep your streak going — answer today's question!"
|
"streak" -> "A question is waiting for you."
|
||||||
"partner_started_game" -> "Partner is playing!"
|
"partner_started_game" -> "Your partner started a game."
|
||||||
"partner_finished_game" -> "Partner finished!"
|
"partner_finished_game" -> "Your partner finished the round."
|
||||||
"partner_waiting" -> "Partner waiting"
|
"partner_waiting" -> "Your partner is waiting."
|
||||||
"memory_capsule_unlocked" -> "Your memory capsule opened"
|
"memory_capsule_unlocked" -> "Your capsule just opened."
|
||||||
"challenge_day_ready" -> "Today's challenge is ready"
|
"challenge_day_ready" -> "A new connection moment is ready."
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveBody(type: String): String? = when (type) {
|
private fun resolveBody(type: String): String? = when (type) {
|
||||||
"daily_question" -> "Tap to answer today's question together."
|
"daily_question" -> "Take a moment to answer. Your partner's waiting too."
|
||||||
"partner_answered" -> "See what your partner shared."
|
"partner_answered" -> "See what they shared — then reveal when you're ready."
|
||||||
"partner_left" -> "You are no longer paired. Tap to create a new invite."
|
"partner_left" -> "Your shared space has been closed. Create a new invite whenever you're ready."
|
||||||
"streak" -> "Don't break the chain. Open the app now."
|
"streak" -> "Answer today's question to keep your shared rhythm going."
|
||||||
"partner_started_game" -> "Your partner has started a game. Tap to join!"
|
"partner_started_game" -> "They're in — tap to join them."
|
||||||
"partner_finished_game" -> "Your partner has finished. Tap to see results!"
|
"partner_finished_game" -> "Time to compare notes. See your results together."
|
||||||
"partner_waiting" -> "Your partner is waiting for you to finish."
|
"partner_waiting" -> "They finished their side. Whenever you're ready, complete yours."
|
||||||
"memory_capsule_unlocked" -> "Open Memory Lane to read it together."
|
"memory_capsule_unlocked" -> "Something you sealed together is ready to open."
|
||||||
"challenge_day_ready" -> "Open your connection challenge for today's prompt."
|
"challenge_day_ready" -> "Your next connection challenge is here — open it together."
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ object NotificationHelper {
|
||||||
"Daily reminders",
|
"Daily reminders",
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
).apply {
|
).apply {
|
||||||
description = "Daily question and streak reminders"
|
description = "Gentle nudges for today's question and shared moments"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
nm.createNotificationChannel(
|
nm.createNotificationChannel(
|
||||||
|
|
@ -33,16 +33,16 @@ object NotificationHelper {
|
||||||
"Partner activity",
|
"Partner activity",
|
||||||
NotificationManager.IMPORTANCE_HIGH
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
).apply {
|
).apply {
|
||||||
description = "When your partner answers a question or plays a game"
|
description = "When your partner answers, reveals, or opens a conversation"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
nm.createNotificationChannel(
|
nm.createNotificationChannel(
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_GAME_ACTIVITY,
|
CHANNEL_GAME_ACTIVITY,
|
||||||
"Game activity",
|
"Games",
|
||||||
NotificationManager.IMPORTANCE_HIGH
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
).apply {
|
).apply {
|
||||||
description = "When your partner starts or finishes a game session"
|
description = "When your partner starts or completes a game round"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
package app.closer.ui.answers
|
package app.closer.ui.answers
|
||||||
|
|
||||||
import app.closer.ui.theme.closerBackgroundBrush
|
import app.closer.ui.theme.closerBackgroundBrush
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -28,6 +33,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
|
@ -78,6 +84,11 @@ private fun AnswerRevealContent(
|
||||||
onHistory: () -> Unit,
|
onHistory: () -> Unit,
|
||||||
onHome: () -> Unit
|
onHome: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val reducedMotion = Settings.Global.getFloat(
|
||||||
|
context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f
|
||||||
|
) == 0f
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -119,13 +130,19 @@ private fun AnswerRevealContent(
|
||||||
onAnswerQuestion = onAnswerQuestion,
|
onAnswerQuestion = onAnswerQuestion,
|
||||||
onHome = onHome
|
onHome = onHome
|
||||||
)
|
)
|
||||||
state.answer.isRevealed -> RevealedState(
|
state.answer.isRevealed -> AnimatedVisibility(
|
||||||
answer = state.answer,
|
visible = true,
|
||||||
partnerAnswer = state.partnerAnswer,
|
enter = if (reducedMotion) fadeIn(tween(0))
|
||||||
question = state.question,
|
else fadeIn(tween(380)) + expandVertically(tween(380))
|
||||||
onHistory = onHistory,
|
) {
|
||||||
onHome = onHome
|
RevealedState(
|
||||||
)
|
answer = state.answer,
|
||||||
|
partnerAnswer = state.partnerAnswer,
|
||||||
|
question = state.question,
|
||||||
|
onHistory = onHistory,
|
||||||
|
onHome = onHome
|
||||||
|
)
|
||||||
|
}
|
||||||
else -> ReadyToRevealState(
|
else -> ReadyToRevealState(
|
||||||
answer = state.answer,
|
answer = state.answer,
|
||||||
partnerAnswer = state.partnerAnswer,
|
partnerAnswer = state.partnerAnswer,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ErrorState(
|
fun ErrorState(
|
||||||
title: String = "Something went wrong",
|
title: String = "Didn't quite load",
|
||||||
message: String,
|
message: String,
|
||||||
retryLabel: String = "Try again",
|
retryLabel: String = "Try again",
|
||||||
onRetry: (() -> Unit)? = null,
|
onRetry: (() -> Unit)? = null,
|
||||||
|
|
|
||||||
|
|
@ -128,8 +128,8 @@ private fun DateMatchContent(
|
||||||
|
|
||||||
state.error != null -> {
|
state.error != null -> {
|
||||||
ErrorState(
|
ErrorState(
|
||||||
title = "Could not load ideas",
|
title = "Couldn't load date ideas",
|
||||||
message = state.error ?: "Something went wrong.",
|
message = state.error ?: "Tap below to try again.",
|
||||||
onRetry = onRetry,
|
onRetry = onRetry,
|
||||||
modifier = Modifier.padding(top = 120.dp)
|
modifier = Modifier.padding(top = 120.dp)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,8 @@ private fun DateMatchesContent(
|
||||||
|
|
||||||
state.error != null -> item {
|
state.error != null -> item {
|
||||||
ErrorState(
|
ErrorState(
|
||||||
title = "Could not load matches",
|
title = "Couldn't load your matches",
|
||||||
message = state.error ?: "Something went wrong.",
|
message = state.error ?: "Pull down to try again.",
|
||||||
onRetry = onRetry,
|
onRetry = onRetry,
|
||||||
modifier = Modifier.padding(top = 80.dp)
|
modifier = Modifier.padding(top = 80.dp)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ class DesireSyncViewModel @Inject constructor(
|
||||||
sessionId = existingSessionId
|
sessionId = existingSessionId
|
||||||
val byId = loadNeutralQuestions().associateBy { it.id }
|
val byId = loadNeutralQuestions().associateBy { it.id }
|
||||||
val questions = questionIds.mapNotNull { byId[it] }
|
val questions = questionIds.mapNotNull { byId[it] }
|
||||||
if (questions.isEmpty()) return fail("Could not load this game.")
|
if (questions.isEmpty()) return fail("Couldn't load this round's questions.")
|
||||||
_uiState.update { it.copy(phase = DesireSyncPhase.INTRO, questions = questions) }
|
_uiState.update { it.copy(phase = DesireSyncPhase.INTRO, questions = questions) }
|
||||||
observeReveal()
|
observeReveal()
|
||||||
}
|
}
|
||||||
|
|
@ -374,7 +374,7 @@ fun DesireSyncScreen(
|
||||||
color = CloserPalette.Romantic
|
color = CloserPalette.Romantic
|
||||||
)
|
)
|
||||||
DesireSyncPhase.ERROR -> DSErrorScreen(
|
DesireSyncPhase.ERROR -> DSErrorScreen(
|
||||||
message = state.error ?: "Something went wrong.",
|
message = state.error ?: "Something didn't load. Go back and try again.",
|
||||||
onBack = viewModel::quit
|
onBack = viewModel::quit
|
||||||
)
|
)
|
||||||
DesireSyncPhase.SETUP -> DSSetupScreen(
|
DesireSyncPhase.SETUP -> DSSetupScreen(
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ class HomeViewModel @Inject constructor(
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
error = e.message ?: "Could not load your dashboard."
|
error = e.message ?: "Couldn't load your space right now."
|
||||||
).withHomeActions()
|
).withHomeActions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@ class HowWellViewModel @Inject constructor(
|
||||||
_uiState.update { it.copy(navigateTo = AppRoute.WAITING_FOR_PARTNER) }
|
_uiState.update { it.copy(navigateTo = AppRoute.WAITING_FOR_PARTNER) }
|
||||||
else -> {
|
else -> {
|
||||||
Log.w(TAG, "Could not start session", startResult.exceptionOrNull())
|
Log.w(TAG, "Could not start session", startResult.exceptionOrNull())
|
||||||
fail("Could not start the game.")
|
fail("Couldn't start this round. Go back and try again.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -254,7 +254,7 @@ class HowWellViewModel @Inject constructor(
|
||||||
sessionId = existingSessionId
|
sessionId = existingSessionId
|
||||||
startedByUserId = startedBy
|
startedByUserId = startedBy
|
||||||
val questions = questionIds.mapNotNull { repository.getQuestionById(it) }
|
val questions = questionIds.mapNotNull { repository.getQuestionById(it) }
|
||||||
if (questions.isEmpty()) return fail("Could not load this game.")
|
if (questions.isEmpty()) return fail("Couldn't load this round's questions.")
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(phase = HowWellPhase.INTRO, questions = questions, amSubject = startedBy == uid)
|
it.copy(phase = HowWellPhase.INTRO, questions = questions, amSubject = startedBy == uid)
|
||||||
}
|
}
|
||||||
|
|
@ -418,7 +418,7 @@ fun HowWellScreen(
|
||||||
color = CloserPalette.PurpleDeep
|
color = CloserPalette.PurpleDeep
|
||||||
)
|
)
|
||||||
HowWellPhase.ERROR -> HowWellErrorScreen(
|
HowWellPhase.ERROR -> HowWellErrorScreen(
|
||||||
message = state.error ?: "Something went wrong.",
|
message = state.error ?: "Something didn't load. Go back and try again.",
|
||||||
onBack = viewModel::quit
|
onBack = viewModel::quit
|
||||||
)
|
)
|
||||||
HowWellPhase.SETUP -> HWSetupScreen(
|
HowWellPhase.SETUP -> HWSetupScreen(
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,8 @@ fun PaywallScreen(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
uiState.error != null -> ErrorState(
|
uiState.error != null -> ErrorState(
|
||||||
title = "Could not load plans",
|
title = "Couldn't load plans",
|
||||||
message = uiState.error ?: "Something went wrong.",
|
message = uiState.error ?: "Check your connection and tap to try again.",
|
||||||
retryLabel = "Try again",
|
retryLabel = "Try again",
|
||||||
onRetry = { viewModel.retry() },
|
onRetry = { viewModel.retry() },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ class DailyQuestionViewModel @Inject constructor(
|
||||||
crashReporter.recordException(e)
|
crashReporter.recordException(e)
|
||||||
_uiState.value = LocalQuestionUiState(
|
_uiState.value = LocalQuestionUiState(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
error = e.message ?: "Could not load today's question."
|
error = e.message ?: "Couldn't open today's question."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
package app.closer.ui.questions
|
package app.closer.ui.questions
|
||||||
|
|
||||||
import app.closer.ui.theme.closerBackgroundBrush
|
import app.closer.ui.theme.closerBackgroundBrush
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -33,6 +38,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
|
@ -98,6 +104,10 @@ fun LocalQuestionContent(
|
||||||
)
|
)
|
||||||
else -> {
|
else -> {
|
||||||
val question = state.question
|
val question = state.question
|
||||||
|
val context = LocalContext.current
|
||||||
|
val reducedMotion = Settings.Global.getFloat(
|
||||||
|
context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f
|
||||||
|
) == 0f
|
||||||
var helpExpanded by remember(question.id) { mutableStateOf(false) }
|
var helpExpanded by remember(question.id) { mutableStateOf(false) }
|
||||||
QuestionMetaRow(question = question)
|
QuestionMetaRow(question = question)
|
||||||
QuestionHeader(
|
QuestionHeader(
|
||||||
|
|
@ -118,7 +128,11 @@ fun LocalQuestionContent(
|
||||||
isSubmitting = false
|
isSubmitting = false
|
||||||
)
|
)
|
||||||
|
|
||||||
if (state.submitted) {
|
AnimatedVisibility(
|
||||||
|
visible = state.submitted,
|
||||||
|
enter = if (reducedMotion) fadeIn(tween(0))
|
||||||
|
else fadeIn(tween(260)) + slideInVertically(tween(260)) { it / 3 }
|
||||||
|
) {
|
||||||
SubmittedAnswerCard(question = question, state = state)
|
SubmittedAnswerCard(question = question, state = state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,21 +130,21 @@ fun NotificationSettingsScreen(
|
||||||
Column {
|
Column {
|
||||||
NotifToggleRow(
|
NotifToggleRow(
|
||||||
label = "Daily question",
|
label = "Daily question",
|
||||||
description = "Remind me to answer today's question",
|
description = "A gentle nudge when today's question is ready",
|
||||||
checked = state.dailyReminderEnabled,
|
checked = state.dailyReminderEnabled,
|
||||||
onCheckedChange = viewModel::toggleDailyReminder
|
onCheckedChange = viewModel::toggleDailyReminder
|
||||||
)
|
)
|
||||||
Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 0.5.dp)
|
Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 0.5.dp)
|
||||||
NotifToggleRow(
|
NotifToggleRow(
|
||||||
label = "Partner answered",
|
label = "Partner answered",
|
||||||
description = "Notify me when my partner answers",
|
description = "Let me know when they're ready to reveal",
|
||||||
checked = state.partnerAnsweredEnabled,
|
checked = state.partnerAnsweredEnabled,
|
||||||
onCheckedChange = viewModel::togglePartnerAnswered
|
onCheckedChange = viewModel::togglePartnerAnswered
|
||||||
)
|
)
|
||||||
Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 0.5.dp)
|
Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 0.5.dp)
|
||||||
NotifToggleRow(
|
NotifToggleRow(
|
||||||
label = "Streak reminder",
|
label = "Shared rhythm reminder",
|
||||||
description = "Nudge me before the streak resets",
|
description = "Remind me to keep our shared rhythm going",
|
||||||
checked = state.streakReminderEnabled,
|
checked = state.streakReminderEnabled,
|
||||||
onCheckedChange = viewModel::toggleStreakReminder
|
onCheckedChange = viewModel::toggleStreakReminder
|
||||||
)
|
)
|
||||||
|
|
@ -167,8 +167,8 @@ fun NotificationSettingsScreen(
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
NotifToggleRow(
|
NotifToggleRow(
|
||||||
label = "Enable quiet hours",
|
label = "Quiet hours",
|
||||||
description = "Suppress notifications 10 PM – 8 AM",
|
description = "Pause all notifications from 10 PM to 8 AM",
|
||||||
checked = state.quietHoursEnabled,
|
checked = state.quietHoursEnabled,
|
||||||
onCheckedChange = viewModel::toggleQuietHours
|
onCheckedChange = viewModel::toggleQuietHours
|
||||||
)
|
)
|
||||||
|
|
@ -178,7 +178,7 @@ fun NotificationSettingsScreen(
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "These preferences shape the reminders you see from the app.",
|
text = "Notifications from Closer are gentle invitations, not alerts. You're always in control of when they arrive.",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = SettingsMuted,
|
color = SettingsMuted,
|
||||||
modifier = Modifier.padding(horizontal = 4.dp)
|
modifier = Modifier.padding(horizontal = 4.dp)
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ class ThisOrThatViewModel @Inject constructor(
|
||||||
_uiState.update { it.copy(navigateTo = AppRoute.WAITING_FOR_PARTNER) }
|
_uiState.update { it.copy(navigateTo = AppRoute.WAITING_FOR_PARTNER) }
|
||||||
else -> {
|
else -> {
|
||||||
Log.w(TAG, "Could not start session", startResult.exceptionOrNull())
|
Log.w(TAG, "Could not start session", startResult.exceptionOrNull())
|
||||||
fail("Could not start the game.")
|
fail("Couldn't start this round. Go back and try again.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +250,7 @@ class ThisOrThatViewModel @Inject constructor(
|
||||||
.getOrElse { emptyList() }
|
.getOrElse { emptyList() }
|
||||||
.associateBy { it.id }
|
.associateBy { it.id }
|
||||||
val questions = questionIds.mapNotNull { byId[it] }
|
val questions = questionIds.mapNotNull { byId[it] }
|
||||||
if (questions.isEmpty()) return fail("Could not load this game.")
|
if (questions.isEmpty()) return fail("Couldn't load this round's questions.")
|
||||||
_uiState.update { it.copy(phase = TotPhase.PLAYING, questions = questions) }
|
_uiState.update { it.copy(phase = TotPhase.PLAYING, questions = questions) }
|
||||||
observeReveal()
|
observeReveal()
|
||||||
}
|
}
|
||||||
|
|
@ -421,7 +421,7 @@ fun ThisOrThatScreen(
|
||||||
color = CloserPalette.PurpleDeep
|
color = CloserPalette.PurpleDeep
|
||||||
)
|
)
|
||||||
TotPhase.ERROR -> ErrorState(
|
TotPhase.ERROR -> ErrorState(
|
||||||
message = state.error ?: "Something went wrong.",
|
message = state.error ?: "Something didn't load. Go back and try again.",
|
||||||
onBack = viewModel::quit
|
onBack = viewModel::quit
|
||||||
)
|
)
|
||||||
TotPhase.WAITING -> WaitingForRevealScreen(
|
TotPhase.WAITING -> WaitingForRevealScreen(
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ class WheelHistoryViewModel @Inject constructor(
|
||||||
_uiState.update { it.copy(isLoading = false, sessions = sessions) }
|
_uiState.update { it.copy(isLoading = false, sessions = sessions) }
|
||||||
}
|
}
|
||||||
.onFailure { e ->
|
.onFailure { e ->
|
||||||
_uiState.update { it.copy(isLoading = false, error = e.message ?: "Failed to load history") }
|
_uiState.update { it.copy(isLoading = false, error = e.message ?: "Couldn't load your past games.") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 307 KiB After Width: | Height: | Size: 305 KiB |
|
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 289 KiB |
|
Before Width: | Height: | Size: 576 KiB After Width: | Height: | Size: 580 KiB |
|
Before Width: | Height: | Size: 318 KiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 458 KiB After Width: | Height: | Size: 444 KiB |
|
Before Width: | Height: | Size: 310 KiB After Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 261 KiB After Width: | Height: | Size: 245 KiB |