feat(home): reveal-waiting art swap, copy polish, extracted computeDailyQuestionState
This commit is contained in:
parent
b15e696388
commit
0feb72eaf0
|
|
@ -1007,7 +1007,17 @@ private fun PrimaryHomeActionCard(
|
|||
) {
|
||||
val colors = action.tone.actionColors()
|
||||
val isDark = isCloserDarkTheme()
|
||||
val artRes = homePrimaryArt(action.target)
|
||||
// The reveal-waiting moment (you answered, then your partner did) gets its own warm couple art so
|
||||
// "your reveal is waiting" reads as the night's focus. Every other daily-question state keeps the
|
||||
// default target-based art.
|
||||
val artRes = if (
|
||||
action.target == HomeActionTarget.DailyQuestion &&
|
||||
dailyQuestionState == DailyQuestionState.BOTH_ANSWERED
|
||||
) {
|
||||
R.drawable.illustration_tonight_partner_prompt
|
||||
} else {
|
||||
homePrimaryArt(action.target)
|
||||
}
|
||||
|
||||
// For daily-question actions, route the CTA through the explicit state handlers
|
||||
// so the same button label maps to the correct next step (answer, remind,
|
||||
|
|
@ -1027,7 +1037,7 @@ private fun PrimaryHomeActionCard(
|
|||
DailyQuestionState.UNANSWERED -> "Tonight's question is ready."
|
||||
DailyQuestionState.USER_ANSWERED_PARTNER_PENDING -> "You showed up tonight. Waiting for your partner."
|
||||
DailyQuestionState.PARTNER_ANSWERED_USER_PENDING -> "Your partner answered. Your turn."
|
||||
DailyQuestionState.BOTH_ANSWERED -> "Reveal is ready."
|
||||
DailyQuestionState.BOTH_ANSWERED -> "Your reveal is waiting"
|
||||
DailyQuestionState.REVEALED -> "You opened a conversation tonight."
|
||||
}
|
||||
else -> action.title
|
||||
|
|
@ -1042,7 +1052,7 @@ private fun PrimaryHomeActionCard(
|
|||
DailyQuestionState.PARTNER_ANSWERED_USER_PENDING ->
|
||||
"Answer to unlock the reveal. Your response stays private until you are ready."
|
||||
DailyQuestionState.BOTH_ANSWERED ->
|
||||
"Both of you answered. Open it together when you are both in the right headspace."
|
||||
"You both answered — open it together when you're ready."
|
||||
DailyQuestionState.REVEALED ->
|
||||
"You revealed an answer together. What comes next is up to both of you."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,39 @@ enum class DailyQuestionState {
|
|||
REVEALED
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure derivation of the daily-question home state — extracted so it can be unit-tested directly,
|
||||
* mirroring the pure-logic style of [HomePriorityEngine].
|
||||
*
|
||||
* The partner is only counted as "answered" when their answer is for THIS question. The partner-answer
|
||||
* listener keys off *today's date*, not the question id, so a rotated daily question (e.g. across
|
||||
* midnight while Home is open) could otherwise mark the pair as both-answered for a question the partner
|
||||
* never answered. A blank [partnerAnsweredQuestionId] (legacy answer docs written before the field
|
||||
* existed) falls back to plain existence so the common case never regresses.
|
||||
*/
|
||||
@androidx.annotation.VisibleForTesting
|
||||
internal fun computeDailyQuestionState(
|
||||
questionId: String?,
|
||||
answeredQuestionIds: Set<String>,
|
||||
latestAnswer: LocalAnswer?,
|
||||
hasPartnerAnsweredToday: Boolean,
|
||||
partnerAnsweredQuestionId: String?
|
||||
): DailyQuestionState {
|
||||
val userAnswered = questionId != null && questionId in answeredQuestionIds
|
||||
val userRevealed = questionId != null &&
|
||||
latestAnswer?.let { it.questionId == questionId && it.isRevealed } == true
|
||||
val partnerAnswered = hasPartnerAnsweredToday &&
|
||||
(partnerAnsweredQuestionId == questionId || partnerAnsweredQuestionId.isNullOrBlank())
|
||||
return when {
|
||||
questionId == null -> DailyQuestionState.UNANSWERED
|
||||
userRevealed -> DailyQuestionState.REVEALED
|
||||
userAnswered && partnerAnswered -> DailyQuestionState.BOTH_ANSWERED
|
||||
userAnswered -> DailyQuestionState.USER_ANSWERED_PARTNER_PENDING
|
||||
partnerAnswered -> DailyQuestionState.PARTNER_ANSWERED_USER_PENDING
|
||||
else -> DailyQuestionState.UNANSWERED
|
||||
}
|
||||
}
|
||||
|
||||
data class HomeUiState(
|
||||
val isLoading: Boolean = true,
|
||||
val error: String? = null,
|
||||
|
|
@ -600,7 +633,7 @@ class HomeViewModel @Inject constructor(
|
|||
partnerAnswerListener?.remove()
|
||||
partnerAnswerListener = null
|
||||
val cId = coupleId ?: return
|
||||
val qId = dailyQuestionId ?: return
|
||||
dailyQuestionId ?: return
|
||||
val uid = authRepository.currentUserId ?: return
|
||||
val partnerId = coupleUserIds?.firstOrNull { it != uid } ?: return
|
||||
|
||||
|
|
@ -617,33 +650,30 @@ class HomeViewModel @Inject constructor(
|
|||
return@addSnapshotListener
|
||||
}
|
||||
val hasPartnerAnswer = snapshot?.exists() == true
|
||||
// Capture WHICH question the partner answered (a plaintext routing field on the doc),
|
||||
// so reveal-ready reflects this question rather than "any answer exists for today" —
|
||||
// the daily question can rotate. See refreshDailyQuestionState().
|
||||
val partnerQuestionId = snapshot?.getString("questionId")
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
hasPartnerAnsweredToday = hasPartnerAnswer,
|
||||
partnerAnsweredQuestionId = if (hasPartnerAnswer) qId else null
|
||||
partnerAnsweredQuestionId = if (hasPartnerAnswer) partnerQuestionId else null
|
||||
).refreshDailyQuestionState().withHomeActions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun HomeUiState.refreshDailyQuestionState(): HomeUiState {
|
||||
val questionId = dailyQuestion?.id
|
||||
val userAnswered = questionId != null && questionId in answerStats.answeredQuestionIds
|
||||
val userRevealed = questionId != null && answerStats.latest?.let { latest ->
|
||||
latest.questionId == questionId && latest.isRevealed
|
||||
} == true
|
||||
|
||||
val state = when {
|
||||
questionId == null -> DailyQuestionState.UNANSWERED
|
||||
userRevealed -> DailyQuestionState.REVEALED
|
||||
userAnswered && hasPartnerAnsweredToday -> DailyQuestionState.BOTH_ANSWERED
|
||||
userAnswered -> DailyQuestionState.USER_ANSWERED_PARTNER_PENDING
|
||||
hasPartnerAnsweredToday -> DailyQuestionState.PARTNER_ANSWERED_USER_PENDING
|
||||
else -> DailyQuestionState.UNANSWERED
|
||||
}
|
||||
val state = computeDailyQuestionState(
|
||||
questionId = dailyQuestion?.id,
|
||||
answeredQuestionIds = answerStats.answeredQuestionIds,
|
||||
latestAnswer = answerStats.latest,
|
||||
hasPartnerAnsweredToday = hasPartnerAnsweredToday,
|
||||
partnerAnsweredQuestionId = partnerAnsweredQuestionId
|
||||
)
|
||||
return copy(
|
||||
dailyQuestionState = state,
|
||||
hasRevealedToday = userRevealed
|
||||
hasRevealedToday = state == DailyQuestionState.REVEALED
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -720,8 +750,8 @@ class HomeViewModel @Inject constructor(
|
|||
)
|
||||
|
||||
Priority.REVEAL_READY -> buildDailyQuestionAction(
|
||||
title = "Reveal is ready.",
|
||||
body = "Both of you answered. Open it together when you are both in the right headspace.",
|
||||
title = "Your reveal is waiting",
|
||||
body = "You both answered — open it together when you're ready.",
|
||||
cta = "Reveal together"
|
||||
)
|
||||
|
||||
|
|
@ -860,7 +890,7 @@ class HomeViewModel @Inject constructor(
|
|||
|
||||
if (dailyQuestionState == DailyQuestionState.BOTH_ANSWERED) {
|
||||
actions += PendingActionCard(
|
||||
title = "Reveal is ready",
|
||||
title = "Your reveal is waiting",
|
||||
subtitle = "Both of you answered tonight. Open it together.",
|
||||
priority = 1,
|
||||
target = HomeActionTarget.AnswerReveal
|
||||
|
|
|
|||
Loading…
Reference in New Issue