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 colors = action.tone.actionColors()
|
||||||
val isDark = isCloserDarkTheme()
|
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
|
// 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,
|
// 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.UNANSWERED -> "Tonight's question is ready."
|
||||||
DailyQuestionState.USER_ANSWERED_PARTNER_PENDING -> "You showed up tonight. Waiting for your partner."
|
DailyQuestionState.USER_ANSWERED_PARTNER_PENDING -> "You showed up tonight. Waiting for your partner."
|
||||||
DailyQuestionState.PARTNER_ANSWERED_USER_PENDING -> "Your partner answered. Your turn."
|
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."
|
DailyQuestionState.REVEALED -> "You opened a conversation tonight."
|
||||||
}
|
}
|
||||||
else -> action.title
|
else -> action.title
|
||||||
|
|
@ -1042,7 +1052,7 @@ private fun PrimaryHomeActionCard(
|
||||||
DailyQuestionState.PARTNER_ANSWERED_USER_PENDING ->
|
DailyQuestionState.PARTNER_ANSWERED_USER_PENDING ->
|
||||||
"Answer to unlock the reveal. Your response stays private until you are ready."
|
"Answer to unlock the reveal. Your response stays private until you are ready."
|
||||||
DailyQuestionState.BOTH_ANSWERED ->
|
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 ->
|
DailyQuestionState.REVEALED ->
|
||||||
"You revealed an answer together. What comes next is up to both of you."
|
"You revealed an answer together. What comes next is up to both of you."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,39 @@ enum class DailyQuestionState {
|
||||||
REVEALED
|
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(
|
data class HomeUiState(
|
||||||
val isLoading: Boolean = true,
|
val isLoading: Boolean = true,
|
||||||
val error: String? = null,
|
val error: String? = null,
|
||||||
|
|
@ -600,7 +633,7 @@ class HomeViewModel @Inject constructor(
|
||||||
partnerAnswerListener?.remove()
|
partnerAnswerListener?.remove()
|
||||||
partnerAnswerListener = null
|
partnerAnswerListener = null
|
||||||
val cId = coupleId ?: return
|
val cId = coupleId ?: return
|
||||||
val qId = dailyQuestionId ?: return
|
dailyQuestionId ?: return
|
||||||
val uid = authRepository.currentUserId ?: return
|
val uid = authRepository.currentUserId ?: return
|
||||||
val partnerId = coupleUserIds?.firstOrNull { it != uid } ?: return
|
val partnerId = coupleUserIds?.firstOrNull { it != uid } ?: return
|
||||||
|
|
||||||
|
|
@ -617,33 +650,30 @@ class HomeViewModel @Inject constructor(
|
||||||
return@addSnapshotListener
|
return@addSnapshotListener
|
||||||
}
|
}
|
||||||
val hasPartnerAnswer = snapshot?.exists() == true
|
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 {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
hasPartnerAnsweredToday = hasPartnerAnswer,
|
hasPartnerAnsweredToday = hasPartnerAnswer,
|
||||||
partnerAnsweredQuestionId = if (hasPartnerAnswer) qId else null
|
partnerAnsweredQuestionId = if (hasPartnerAnswer) partnerQuestionId else null
|
||||||
).refreshDailyQuestionState().withHomeActions()
|
).refreshDailyQuestionState().withHomeActions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun HomeUiState.refreshDailyQuestionState(): HomeUiState {
|
private fun HomeUiState.refreshDailyQuestionState(): HomeUiState {
|
||||||
val questionId = dailyQuestion?.id
|
val state = computeDailyQuestionState(
|
||||||
val userAnswered = questionId != null && questionId in answerStats.answeredQuestionIds
|
questionId = dailyQuestion?.id,
|
||||||
val userRevealed = questionId != null && answerStats.latest?.let { latest ->
|
answeredQuestionIds = answerStats.answeredQuestionIds,
|
||||||
latest.questionId == questionId && latest.isRevealed
|
latestAnswer = answerStats.latest,
|
||||||
} == true
|
hasPartnerAnsweredToday = hasPartnerAnsweredToday,
|
||||||
|
partnerAnsweredQuestionId = partnerAnsweredQuestionId
|
||||||
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
|
|
||||||
}
|
|
||||||
return copy(
|
return copy(
|
||||||
dailyQuestionState = state,
|
dailyQuestionState = state,
|
||||||
hasRevealedToday = userRevealed
|
hasRevealedToday = state == DailyQuestionState.REVEALED
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -720,8 +750,8 @@ class HomeViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
Priority.REVEAL_READY -> buildDailyQuestionAction(
|
Priority.REVEAL_READY -> buildDailyQuestionAction(
|
||||||
title = "Reveal is ready.",
|
title = "Your reveal is waiting",
|
||||||
body = "Both of you answered. Open it together when you are both in the right headspace.",
|
body = "You both answered — open it together when you're ready.",
|
||||||
cta = "Reveal together"
|
cta = "Reveal together"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -860,7 +890,7 @@ class HomeViewModel @Inject constructor(
|
||||||
|
|
||||||
if (dailyQuestionState == DailyQuestionState.BOTH_ANSWERED) {
|
if (dailyQuestionState == DailyQuestionState.BOTH_ANSWERED) {
|
||||||
actions += PendingActionCard(
|
actions += PendingActionCard(
|
||||||
title = "Reveal is ready",
|
title = "Your reveal is waiting",
|
||||||
subtitle = "Both of you answered tonight. Open it together.",
|
subtitle = "Both of you answered tonight. Open it together.",
|
||||||
priority = 1,
|
priority = 1,
|
||||||
target = HomeActionTarget.AnswerReveal
|
target = HomeActionTarget.AnswerReveal
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue