Revert "feat(date-memories): add Home nudge for pending date reflection + priority engine (batch 3/8)"
This reverts commit 4ecb1560cb.
This commit is contained in:
parent
c19e7ea711
commit
5905c2b2d0
|
|
@ -19,9 +19,8 @@ package app.closer.ui.home
|
||||||
* 9. Weekly recap ready
|
* 9. Weekly recap ready
|
||||||
* 10. Capsule unlocked
|
* 10. Capsule unlocked
|
||||||
* 11. Date reminder
|
* 11. Date reminder
|
||||||
* 12. Date reflection pending
|
* 12. Suggested pack
|
||||||
* 13. Suggested pack
|
* 13. Explore games
|
||||||
* 14. Explore games
|
|
||||||
*
|
*
|
||||||
* Rules:
|
* Rules:
|
||||||
* - Show one primary CTA.
|
* - Show one primary CTA.
|
||||||
|
|
@ -50,7 +49,6 @@ object HomePriorityEngine {
|
||||||
val weeklyRecapReady: Boolean = false,
|
val weeklyRecapReady: Boolean = false,
|
||||||
val capsuleUnlocked: Boolean = false,
|
val capsuleUnlocked: Boolean = false,
|
||||||
val dateReminder: Boolean = false,
|
val dateReminder: Boolean = false,
|
||||||
val dateReflectionPending: Boolean = false,
|
|
||||||
val suggestedPackAvailable: Boolean = false,
|
val suggestedPackAvailable: Boolean = false,
|
||||||
val exploreGamesAvailable: Boolean = false
|
val exploreGamesAvailable: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
@ -70,9 +68,6 @@ object HomePriorityEngine {
|
||||||
WEEKLY_RECAP_READY,
|
WEEKLY_RECAP_READY,
|
||||||
CAPSULE_UNLOCKED,
|
CAPSULE_UNLOCKED,
|
||||||
DATE_REMINDER,
|
DATE_REMINDER,
|
||||||
// You marked a date done but haven't reflected yet. Value action in the date band: an actionable
|
|
||||||
// shared-memory prompt, ranked above the daily-question closure card.
|
|
||||||
DATE_REFLECTION_PENDING,
|
|
||||||
// You already revealed today's question — a low-priority "keep the conversation going" closure card.
|
// You already revealed today's question — a low-priority "keep the conversation going" closure card.
|
||||||
DAILY_QUESTION_REVEALED,
|
DAILY_QUESTION_REVEALED,
|
||||||
SUGGESTED_PACK,
|
SUGGESTED_PACK,
|
||||||
|
|
@ -146,7 +141,6 @@ object HomePriorityEngine {
|
||||||
Priority.WEEKLY_RECAP_READY -> input.weeklyRecapReady
|
Priority.WEEKLY_RECAP_READY -> input.weeklyRecapReady
|
||||||
Priority.CAPSULE_UNLOCKED -> input.capsuleUnlocked
|
Priority.CAPSULE_UNLOCKED -> input.capsuleUnlocked
|
||||||
Priority.DATE_REMINDER -> input.dateReminder
|
Priority.DATE_REMINDER -> input.dateReminder
|
||||||
Priority.DATE_REFLECTION_PENDING -> input.dateReflectionPending
|
|
||||||
Priority.SUGGESTED_PACK -> input.suggestedPackAvailable
|
Priority.SUGGESTED_PACK -> input.suggestedPackAvailable
|
||||||
Priority.EXPLORE_GAMES -> input.exploreGamesAvailable
|
Priority.EXPLORE_GAMES -> input.exploreGamesAvailable
|
||||||
}
|
}
|
||||||
|
|
@ -172,8 +166,7 @@ object HomePriorityEngine {
|
||||||
Priority.DAILY_QUESTION_AWAITING_PARTNER,
|
Priority.DAILY_QUESTION_AWAITING_PARTNER,
|
||||||
Priority.DAILY_QUESTION_REVEALED,
|
Priority.DAILY_QUESTION_REVEALED,
|
||||||
Priority.WEEKLY_RECAP_READY,
|
Priority.WEEKLY_RECAP_READY,
|
||||||
Priority.DATE_REMINDER,
|
Priority.DATE_REMINDER -> true
|
||||||
Priority.DATE_REFLECTION_PENDING -> true
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,6 @@ private fun HomeCallbacks.toActionHandler(onNavigate: (String) -> Unit): (HomeAc
|
||||||
HomeActionTarget.Challenge -> onNavigate(AppRoute.CONNECTION_CHALLENGES)
|
HomeActionTarget.Challenge -> onNavigate(AppRoute.CONNECTION_CHALLENGES)
|
||||||
HomeActionTarget.DatePlan -> onNavigate(AppRoute.DATE_MATCHES)
|
HomeActionTarget.DatePlan -> onNavigate(AppRoute.DATE_MATCHES)
|
||||||
HomeActionTarget.MemoryCapsule -> onNavigate(AppRoute.MEMORY_LANE)
|
HomeActionTarget.MemoryCapsule -> onNavigate(AppRoute.MEMORY_LANE)
|
||||||
HomeActionTarget.DateMemories -> onNavigate(AppRoute.DATE_MEMORIES)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -417,7 +416,6 @@ private fun homeActionGlyph(target: HomeActionTarget): Int = when (target) {
|
||||||
HomeActionTarget.Challenge -> R.drawable.glyph_connection_challenge
|
HomeActionTarget.Challenge -> R.drawable.glyph_connection_challenge
|
||||||
HomeActionTarget.DatePlan -> R.drawable.glyph_date_card_heart
|
HomeActionTarget.DatePlan -> R.drawable.glyph_date_card_heart
|
||||||
HomeActionTarget.MemoryCapsule -> R.drawable.glyph_memory_capsule
|
HomeActionTarget.MemoryCapsule -> R.drawable.glyph_memory_capsule
|
||||||
HomeActionTarget.DateMemories -> R.drawable.glyph_date_replay
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -717,16 +715,6 @@ private fun PartnerQuickActionsSheet(
|
||||||
if (state.togetherSince > 0L) add("together since ${formatMonthYear(state.togetherSince)}")
|
if (state.togetherSince > 0L) add("together since ${formatMonthYear(state.togetherSince)}")
|
||||||
}.joinToString(" · ")
|
}.joinToString(" · ")
|
||||||
}
|
}
|
||||||
// Living "today" status — reflects where the couple is in the daily ritual. Hidden when there's no
|
|
||||||
// question assigned (no misleading "still open").
|
|
||||||
val todayStatus = state.dailyQuestion?.let {
|
|
||||||
when (state.dailyQuestionState) {
|
|
||||||
DailyQuestionState.BOTH_ANSWERED, DailyQuestionState.REVEALED -> "You've both answered today 💜"
|
|
||||||
DailyQuestionState.PARTNER_ANSWERED_USER_PENDING -> "They answered — your turn"
|
|
||||||
DailyQuestionState.USER_ANSWERED_PARTNER_PENDING -> "Waiting on their answer"
|
|
||||||
DailyQuestionState.UNANSWERED -> "Tonight's question is still open"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val openPartnerPage = { onNavigate(AppRoute.PARTNER_HOME); onDismiss() }
|
val openPartnerPage = { onNavigate(AppRoute.PARTNER_HOME); onDismiss() }
|
||||||
|
|
||||||
ModalBottomSheet(onDismissRequest = onDismiss, sheetState = sheetState) {
|
ModalBottomSheet(onDismissRequest = onDismiss, sheetState = sheetState) {
|
||||||
|
|
@ -769,36 +757,26 @@ private fun PartnerQuickActionsSheet(
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
todayStatus?.let {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
style = MaterialTheme.typography.bodySmall.copy(fontWeight = FontWeight.Medium),
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider(modifier = Modifier.padding(vertical = 4.dp), thickness = 0.5.dp)
|
Divider(modifier = Modifier.padding(vertical = 4.dp), thickness = 0.5.dp)
|
||||||
|
|
||||||
PartnerSheetAction(R.drawable.glyph_heart, "Thinking of you", enabled = !state.isSendingNudge, onClick = onThinkingOfYou)
|
PartnerSheetAction("💜", "Thinking of you", enabled = !state.isSendingNudge, onClick = onThinkingOfYou)
|
||||||
PartnerSheetAction(R.drawable.glyph_chat, "Message") { onNavigate(AppRoute.MESSAGES); onDismiss() }
|
PartnerSheetAction("💬", "Message") { onNavigate(AppRoute.MESSAGES); onDismiss() }
|
||||||
PartnerSheetAction(
|
PartnerSheetAction(
|
||||||
R.drawable.glyph_paired_cards,
|
"✨",
|
||||||
"Together",
|
"Together",
|
||||||
trailing = state.unreadActivityCount.takeIf { it > 0 }?.let { if (it > 9) "9+" else "$it" }
|
trailing = state.unreadActivityCount.takeIf { it > 0 }?.let { if (it > 9) "9+" else "$it" }
|
||||||
) { onNavigate(AppRoute.ACTIVITY); onDismiss() }
|
) { onNavigate(AppRoute.ACTIVITY); onDismiss() }
|
||||||
PartnerSheetAction(R.drawable.glyph_memory_capsule, "Our memories") { onNavigate(AppRoute.MEMORY_LANE); onDismiss() }
|
PartnerSheetAction("⚙️", "Your relationship") { onNavigate(AppRoute.RELATIONSHIP_SETTINGS); onDismiss() }
|
||||||
PartnerSheetAction(R.drawable.glyph_settings, "Your relationship") { onNavigate(AppRoute.RELATIONSHIP_SETTINGS); onDismiss() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PartnerSheetAction(
|
private fun PartnerSheetAction(
|
||||||
@DrawableRes iconRes: Int,
|
emoji: String,
|
||||||
label: String,
|
label: String,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
trailing: String? = null,
|
trailing: String? = null,
|
||||||
|
|
@ -813,12 +791,7 @@ private fun PartnerSheetAction(
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
HomeGlyphIcon(
|
Text(text = emoji, style = MaterialTheme.typography.titleMedium)
|
||||||
resId = iconRes,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
modifier = Modifier.size(22.dp)
|
|
||||||
)
|
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,7 @@ enum class HomeActionTarget {
|
||||||
Game,
|
Game,
|
||||||
Challenge,
|
Challenge,
|
||||||
DatePlan,
|
DatePlan,
|
||||||
MemoryCapsule,
|
MemoryCapsule
|
||||||
DateMemories
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class HomeActionTone {
|
enum class HomeActionTone {
|
||||||
|
|
@ -159,8 +158,6 @@ data class HomeUiState(
|
||||||
val hasActiveChallenge: Boolean = false,
|
val hasActiveChallenge: Boolean = false,
|
||||||
val hasUpcomingDatePlan: Boolean = false,
|
val hasUpcomingDatePlan: Boolean = false,
|
||||||
val hasUnlockedCapsule: Boolean = false,
|
val hasUnlockedCapsule: Boolean = false,
|
||||||
// A completed date this user hasn't reflected on yet (drives the Home "reflect on your date" nudge).
|
|
||||||
val hasPendingDateReflection: Boolean = false,
|
|
||||||
val weeklyRecapReady: Boolean = false,
|
val weeklyRecapReady: Boolean = false,
|
||||||
val reminderSentEvent: Boolean = false,
|
val reminderSentEvent: Boolean = false,
|
||||||
/** "Thinking of you" nudge: in-flight guard + one-shot snackbar message (success or friendly error). */
|
/** "Thinking of you" nudge: in-flight guard + one-shot snackbar message (success or friendly error). */
|
||||||
|
|
@ -195,9 +192,7 @@ class HomeViewModel @Inject constructor(
|
||||||
private val outcomeRepository: OutcomeRepository,
|
private val outcomeRepository: OutcomeRepository,
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val activityDataSource: app.closer.data.remote.FirestoreActivityDataSource,
|
private val activityDataSource: app.closer.data.remote.FirestoreActivityDataSource,
|
||||||
private val dailyQuestionResolver: app.closer.domain.usecase.DailyQuestionResolver,
|
private val dailyQuestionResolver: app.closer.domain.usecase.DailyQuestionResolver
|
||||||
private val dateMemoryDataSource: app.closer.data.remote.FirestoreDateMemoryDataSource,
|
|
||||||
private val dateReflectionDataSource: app.closer.data.remote.FirestoreDateReflectionDataSource
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(HomeUiState())
|
private val _uiState = MutableStateFlow(HomeUiState())
|
||||||
|
|
@ -294,7 +289,6 @@ class HomeViewModel @Inject constructor(
|
||||||
var hasActiveChallenge = false
|
var hasActiveChallenge = false
|
||||||
var hasUpcomingDatePlan = false
|
var hasUpcomingDatePlan = false
|
||||||
var hasUnlockedCapsule = false
|
var hasUnlockedCapsule = false
|
||||||
var hasPendingDateReflection = false
|
|
||||||
val coupleId = couple?.id
|
val coupleId = couple?.id
|
||||||
if (couple != null && coupleId != null && uid != null) {
|
if (couple != null && coupleId != null && uid != null) {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
|
|
@ -326,16 +320,6 @@ class HomeViewModel @Inject constructor(
|
||||||
.any { it.status == "sealed" && it.unlockAt in 1L..now }
|
.any { it.status == "sealed" && it.unlockAt in 1L..now }
|
||||||
}.getOrDefault(false)
|
}.getOrDefault(false)
|
||||||
}
|
}
|
||||||
// Pending date reflection: the most recent completed date this user hasn't
|
|
||||||
// reflected on yet. The nudge chases the latest date; older un-reflected dates
|
|
||||||
// remain reachable from the Replay timeline.
|
|
||||||
val reflectionJob = async {
|
|
||||||
runCatching {
|
|
||||||
val latest = dateMemoryDataSource.getHistoryOnce(coupleId).firstOrNull()
|
|
||||||
latest != null &&
|
|
||||||
!dateReflectionDataSource.hasReflected(coupleId, latest.id, uid)
|
|
||||||
}.getOrDefault(false)
|
|
||||||
}
|
|
||||||
val (waitingSession, waitingRoute) = gameJob.await()
|
val (waitingSession, waitingRoute) = gameJob.await()
|
||||||
hasWaitingGame = waitingSession != null
|
hasWaitingGame = waitingSession != null
|
||||||
waitingGameRoute = waitingRoute
|
waitingGameRoute = waitingRoute
|
||||||
|
|
@ -343,7 +327,6 @@ class HomeViewModel @Inject constructor(
|
||||||
hasActiveChallenge = challengeJob.await()
|
hasActiveChallenge = challengeJob.await()
|
||||||
hasUpcomingDatePlan = dateJob.await()
|
hasUpcomingDatePlan = dateJob.await()
|
||||||
hasUnlockedCapsule = capsuleJob.await()
|
hasUnlockedCapsule = capsuleJob.await()
|
||||||
hasPendingDateReflection = reflectionJob.await()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -366,7 +349,6 @@ class HomeViewModel @Inject constructor(
|
||||||
hasActiveChallenge = hasActiveChallenge,
|
hasActiveChallenge = hasActiveChallenge,
|
||||||
hasUpcomingDatePlan = hasUpcomingDatePlan,
|
hasUpcomingDatePlan = hasUpcomingDatePlan,
|
||||||
hasUnlockedCapsule = hasUnlockedCapsule,
|
hasUnlockedCapsule = hasUnlockedCapsule,
|
||||||
hasPendingDateReflection = hasPendingDateReflection,
|
|
||||||
showOutcomeBaselineDialog = showBaselineDialog,
|
showOutcomeBaselineDialog = showBaselineDialog,
|
||||||
showOutcomeFollowUpDialog = followUpDay != null,
|
showOutcomeFollowUpDialog = followUpDay != null,
|
||||||
outcomeFollowUpDay = followUpDay,
|
outcomeFollowUpDay = followUpDay,
|
||||||
|
|
@ -637,7 +619,6 @@ class HomeViewModel @Inject constructor(
|
||||||
weeklyRecapReady = weeklyRecapReady,
|
weeklyRecapReady = weeklyRecapReady,
|
||||||
capsuleUnlocked = hasUnlockedCapsule(),
|
capsuleUnlocked = hasUnlockedCapsule(),
|
||||||
dateReminder = hasUpcomingDate(),
|
dateReminder = hasUpcomingDate(),
|
||||||
dateReflectionPending = hasPendingDateReflection,
|
|
||||||
suggestedPackAvailable = categories.isNotEmpty(),
|
suggestedPackAvailable = categories.isNotEmpty(),
|
||||||
exploreGamesAvailable = categories.isNotEmpty()
|
exploreGamesAvailable = categories.isNotEmpty()
|
||||||
)
|
)
|
||||||
|
|
@ -778,16 +759,6 @@ class HomeViewModel @Inject constructor(
|
||||||
tone = HomeActionTone.Ritual
|
tone = HomeActionTone.Ritual
|
||||||
)
|
)
|
||||||
|
|
||||||
Priority.DATE_REFLECTION_PENDING -> HomeAction(
|
|
||||||
eyebrow = "Date replay",
|
|
||||||
title = partnerName?.let { "Reflect on your date with $it 💭" }
|
|
||||||
?: "Reflect on your date 💭",
|
|
||||||
body = "Capture what the night meant to you. You'll reveal your reflections together when you're both ready.",
|
|
||||||
cta = "Add your reflection",
|
|
||||||
target = HomeActionTarget.DateMemories,
|
|
||||||
tone = HomeActionTone.Reflection
|
|
||||||
)
|
|
||||||
|
|
||||||
Priority.SUGGESTED_PACK -> categories.firstOrNull()?.let { category ->
|
Priority.SUGGESTED_PACK -> categories.firstOrNull()?.let { category ->
|
||||||
HomeAction(
|
HomeAction(
|
||||||
eyebrow = "Suggested pack",
|
eyebrow = "Suggested pack",
|
||||||
|
|
@ -874,20 +845,11 @@ class HomeViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasPendingDateReflection) {
|
|
||||||
actions += PendingActionCard(
|
|
||||||
title = "Reflect on your date",
|
|
||||||
subtitle = "Capture the night, then reveal together.",
|
|
||||||
priority = 6,
|
|
||||||
target = HomeActionTarget.DateMemories
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasUnlockedCapsule()) {
|
if (hasUnlockedCapsule()) {
|
||||||
actions += PendingActionCard(
|
actions += PendingActionCard(
|
||||||
title = "Capsule unlocked",
|
title = "Capsule unlocked",
|
||||||
subtitle = "A saved memory is ready to open together.",
|
subtitle = "A saved memory is ready to open together.",
|
||||||
priority = 7,
|
priority = 6,
|
||||||
target = HomeActionTarget.MemoryCapsule
|
target = HomeActionTarget.MemoryCapsule
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -318,44 +318,6 @@ class HomePriorityEngineTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `date reflection pending is a value action that surfaces as a secondary card`() {
|
|
||||||
val input = Input(
|
|
||||||
isPaired = true,
|
|
||||||
dailyQuestionUnanswered = true,
|
|
||||||
dateReflectionPending = true,
|
|
||||||
suggestedPackAvailable = true,
|
|
||||||
exploreGamesAvailable = true
|
|
||||||
)
|
|
||||||
|
|
||||||
val output = HomePriorityEngine.compute(input)
|
|
||||||
|
|
||||||
// Daily question is the hero; the pending reflection rides along as a value-action card,
|
|
||||||
// while the generic browse items (pack/explore) are filtered out of the secondary band.
|
|
||||||
assertEquals(Priority.DAILY_QUESTION_UNANSWERED, output.primary?.priority)
|
|
||||||
assertEquals(
|
|
||||||
listOf(Priority.DATE_REFLECTION_PENDING),
|
|
||||||
output.secondary.map { it.priority }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `date reflection pending outranks the daily question closure card`() {
|
|
||||||
val input = Input(
|
|
||||||
isPaired = true,
|
|
||||||
dateReflectionPending = true,
|
|
||||||
dailyQuestionRevealed = true
|
|
||||||
)
|
|
||||||
|
|
||||||
val output = HomePriorityEngine.compute(input)
|
|
||||||
|
|
||||||
assertEquals(Priority.DATE_REFLECTION_PENDING, output.primary?.priority)
|
|
||||||
assertEquals(
|
|
||||||
listOf(Priority.DAILY_QUESTION_REVEALED),
|
|
||||||
output.secondary.map { it.priority }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `primary priority convenience returns pairing needed for default empty input`() {
|
fun `primary priority convenience returns pairing needed for default empty input`() {
|
||||||
assertEquals(Priority.PAIRING_NEEDED, HomePriorityEngine.primaryPriority(Input()))
|
assertEquals(Priority.PAIRING_NEEDED, HomePriorityEngine.primaryPriority(Input()))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue