2026-06-16 20:03:58 -05:00
|
|
|
package app.closer.ui.home
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
|
2026-06-17 20:51:18 -05:00
|
|
|
import android.util.Log
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
import androidx.lifecycle.ViewModel
|
|
|
|
|
import androidx.lifecycle.viewModelScope
|
2026-06-16 20:03:58 -05:00
|
|
|
import app.closer.domain.model.LocalAnswer
|
|
|
|
|
import app.closer.domain.model.Question
|
|
|
|
|
import app.closer.domain.model.QuestionCategory
|
|
|
|
|
import app.closer.domain.repository.AuthRepository
|
|
|
|
|
import app.closer.domain.repository.CoupleRepository
|
|
|
|
|
import app.closer.domain.repository.LocalAnswerRepository
|
|
|
|
|
import app.closer.domain.repository.QuestionRepository
|
|
|
|
|
import app.closer.domain.repository.UserRepository
|
2026-06-18 00:25:52 -05:00
|
|
|
import com.google.firebase.firestore.FirebaseFirestore
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
|
|
|
import javax.inject.Inject
|
|
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
|
|
|
import kotlinx.coroutines.flow.asStateFlow
|
|
|
|
|
import kotlinx.coroutines.flow.update
|
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
|
|
|
|
|
data class HomeCategorySummary(
|
|
|
|
|
val category: QuestionCategory,
|
|
|
|
|
val questionCount: Int
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
data class HomeAnswerStats(
|
|
|
|
|
val total: Int = 0,
|
|
|
|
|
val revealed: Int = 0,
|
|
|
|
|
val private: Int = 0,
|
2026-06-16 03:25:03 -05:00
|
|
|
val latest: LocalAnswer? = null,
|
|
|
|
|
val answeredQuestionIds: Set<String> = emptySet()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
enum class HomeActionTarget {
|
|
|
|
|
InvitePartner,
|
|
|
|
|
DailyQuestion,
|
|
|
|
|
AnswerHistory,
|
|
|
|
|
QuestionPacks,
|
|
|
|
|
Settings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum class HomeActionTone {
|
|
|
|
|
Invite,
|
|
|
|
|
Daily,
|
|
|
|
|
Reflection,
|
|
|
|
|
Ritual,
|
|
|
|
|
Starter,
|
|
|
|
|
Pack,
|
|
|
|
|
Utility
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data class HomeAction(
|
|
|
|
|
val eyebrow: String,
|
|
|
|
|
val title: String,
|
|
|
|
|
val body: String,
|
|
|
|
|
val cta: String,
|
|
|
|
|
val target: HomeActionTarget,
|
|
|
|
|
val tone: HomeActionTone,
|
|
|
|
|
val metric: String? = null,
|
|
|
|
|
val categoryId: String? = null
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
data class HomeUiState(
|
|
|
|
|
val isLoading: Boolean = true,
|
|
|
|
|
val error: String? = null,
|
|
|
|
|
val dailyQuestion: Question? = null,
|
|
|
|
|
val categories: List<HomeCategorySummary> = emptyList(),
|
2026-06-16 00:50:13 -05:00
|
|
|
val answerStats: HomeAnswerStats = HomeAnswerStats(),
|
|
|
|
|
val partnerName: String? = null,
|
2026-06-16 01:57:48 -05:00
|
|
|
val streakCount: Int = 0,
|
2026-06-16 03:25:03 -05:00
|
|
|
val isPaired: Boolean = false,
|
|
|
|
|
val primaryAction: HomeAction? = null,
|
2026-06-18 00:25:52 -05:00
|
|
|
val secondaryActions: List<HomeAction> = emptyList(),
|
|
|
|
|
val partnerLeftEvent: Boolean = false
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@HiltViewModel
|
|
|
|
|
class HomeViewModel @Inject constructor(
|
|
|
|
|
private val questionRepository: QuestionRepository,
|
2026-06-16 00:50:13 -05:00
|
|
|
private val localAnswerRepository: LocalAnswerRepository,
|
|
|
|
|
private val authRepository: AuthRepository,
|
|
|
|
|
private val coupleRepository: CoupleRepository,
|
2026-06-18 00:25:52 -05:00
|
|
|
private val userRepository: UserRepository,
|
|
|
|
|
private val db: FirebaseFirestore
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
) : ViewModel() {
|
|
|
|
|
|
|
|
|
|
private val _uiState = MutableStateFlow(HomeUiState())
|
|
|
|
|
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
|
|
|
|
|
|
2026-06-18 00:25:52 -05:00
|
|
|
private var coupleStateListener: com.google.firebase.firestore.ListenerRegistration? = null
|
|
|
|
|
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
init {
|
|
|
|
|
loadHome()
|
|
|
|
|
observeAnswers()
|
2026-06-18 00:25:52 -05:00
|
|
|
observeCoupleState()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onCleared() {
|
|
|
|
|
super.onCleared()
|
|
|
|
|
coupleStateListener?.remove()
|
|
|
|
|
coupleStateListener = null
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun loadHome() {
|
|
|
|
|
viewModelScope.launch {
|
|
|
|
|
_uiState.update { it.copy(isLoading = true, error = null) }
|
|
|
|
|
try {
|
|
|
|
|
val dailyQuestion = questionRepository.getDailyQuestion()
|
|
|
|
|
val categories = questionRepository.getCategories()
|
|
|
|
|
.take(6)
|
|
|
|
|
.map { category ->
|
|
|
|
|
HomeCategorySummary(
|
|
|
|
|
category = category,
|
|
|
|
|
questionCount = questionRepository.getQuestionCountByCategory(category.id)
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-06-16 00:50:13 -05:00
|
|
|
val uid = authRepository.currentUserId
|
|
|
|
|
val couple = uid?.let { runCatching { coupleRepository.getCoupleForUser(it) }.getOrNull() }
|
|
|
|
|
val partnerName = couple?.userIds?.firstOrNull { it != uid }?.let { partnerId ->
|
2026-06-17 20:51:18 -05:00
|
|
|
runCatching { userRepository.getUser(partnerId)?.displayName }
|
|
|
|
|
.onFailure { Log.w(TAG, "Could not load partner display name", it) }
|
|
|
|
|
.getOrNull()
|
2026-06-16 00:50:13 -05:00
|
|
|
}
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
_uiState.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
isLoading = false,
|
|
|
|
|
dailyQuestion = dailyQuestion,
|
2026-06-16 00:50:13 -05:00
|
|
|
categories = categories,
|
|
|
|
|
partnerName = partnerName,
|
2026-06-16 01:57:48 -05:00
|
|
|
streakCount = couple?.streakCount ?: 0,
|
2026-06-18 00:25:52 -05:00
|
|
|
isPaired = couple != null,
|
|
|
|
|
partnerLeftEvent = false
|
2026-06-16 03:25:03 -05:00
|
|
|
).withHomeActions()
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
}
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
_uiState.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
isLoading = false,
|
2026-06-16 02:55:16 -05:00
|
|
|
error = e.message ?: "Could not load your dashboard."
|
2026-06-16 03:25:03 -05:00
|
|
|
).withHomeActions()
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-18 00:25:52 -05:00
|
|
|
private fun observeCoupleState() {
|
|
|
|
|
val uid = authRepository.currentUserId ?: return
|
|
|
|
|
coupleStateListener?.remove()
|
|
|
|
|
coupleStateListener = db.collection("users").document(uid)
|
|
|
|
|
.addSnapshotListener(com.google.firebase.firestore.MetadataChanges.INCLUDE) { snapshot, error ->
|
|
|
|
|
if (error != null || snapshot == null || !snapshot.exists()) {
|
|
|
|
|
return@addSnapshotListener
|
|
|
|
|
}
|
|
|
|
|
val newCoupleId = snapshot.getString("coupleId")
|
|
|
|
|
val oldIsPaired = _uiState.value.isPaired
|
|
|
|
|
val oldPartnerName = _uiState.value.partnerName
|
|
|
|
|
val newIsPaired = !newCoupleId.isNullOrBlank()
|
|
|
|
|
|
|
|
|
|
if (newIsPaired != oldIsPaired) {
|
|
|
|
|
// Capture partner name before reload so the banner can reference it.
|
|
|
|
|
val showPartnerLeftBanner = oldIsPaired && !newIsPaired
|
|
|
|
|
if (showPartnerLeftBanner) {
|
|
|
|
|
_uiState.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
isPaired = false,
|
|
|
|
|
partnerName = null,
|
|
|
|
|
partnerLeftEvent = true
|
|
|
|
|
).withHomeActions()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
loadHome()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Consumes the partner-left banner event. Call from the UI after showing it.
|
|
|
|
|
*/
|
|
|
|
|
fun consumePartnerLeftEvent() {
|
|
|
|
|
_uiState.update { it.copy(partnerLeftEvent = false) }
|
|
|
|
|
}
|
|
|
|
|
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
private fun observeAnswers() {
|
|
|
|
|
viewModelScope.launch {
|
|
|
|
|
localAnswerRepository.observeAnswers().collect { answers ->
|
|
|
|
|
val sorted = answers.sortedByDescending { it.updatedAt }
|
|
|
|
|
_uiState.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
answerStats = HomeAnswerStats(
|
|
|
|
|
total = answers.size,
|
|
|
|
|
revealed = answers.count { answer -> answer.isRevealed },
|
|
|
|
|
private = answers.count { answer -> !answer.isRevealed },
|
2026-06-16 03:25:03 -05:00
|
|
|
latest = sorted.firstOrNull(),
|
|
|
|
|
answeredQuestionIds = answers.map { answer -> answer.questionId }.toSet()
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
)
|
2026-06-16 03:25:03 -05:00
|
|
|
).withHomeActions()
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-16 03:25:03 -05:00
|
|
|
|
|
|
|
|
private fun HomeUiState.withHomeActions(): HomeUiState {
|
|
|
|
|
if (isLoading || error != null) {
|
|
|
|
|
return copy(primaryAction = null, secondaryActions = emptyList())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val primary = buildPrimaryAction()
|
|
|
|
|
return copy(
|
|
|
|
|
primaryAction = primary,
|
|
|
|
|
secondaryActions = buildSecondaryActions(primary)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun HomeUiState.buildPrimaryAction(): HomeAction {
|
|
|
|
|
val dailyQuestionIsUnanswered = dailyQuestion
|
|
|
|
|
?.id
|
|
|
|
|
?.let { questionId -> questionId !in answerStats.answeredQuestionIds }
|
|
|
|
|
?: false
|
|
|
|
|
|
|
|
|
|
return when {
|
|
|
|
|
!isPaired -> HomeAction(
|
|
|
|
|
eyebrow = "Next best action",
|
|
|
|
|
title = "Invite your partner into tonight.",
|
|
|
|
|
body = "The app works best as a shared ritual. Send a private invite and make the next prompt something you can both answer.",
|
|
|
|
|
cta = "Invite partner",
|
|
|
|
|
target = HomeActionTarget.InvitePartner,
|
|
|
|
|
tone = HomeActionTone.Invite
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
dailyQuestionIsUnanswered -> HomeAction(
|
|
|
|
|
eyebrow = "Tonight's prompt",
|
|
|
|
|
title = dailyQuestion?.text ?: "Answer tonight's question.",
|
|
|
|
|
body = "Start with one honest answer. You can keep it private or reveal it when the moment feels right.",
|
|
|
|
|
cta = "Answer now",
|
|
|
|
|
target = HomeActionTarget.DailyQuestion,
|
|
|
|
|
tone = HomeActionTone.Daily,
|
|
|
|
|
metric = dailyQuestion?.category?.takeIf { category -> category.isNotBlank() }?.toHomeLabel()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
answerStats.private > 0 -> HomeAction(
|
|
|
|
|
eyebrow = "Saved privately",
|
|
|
|
|
title = "You have ${answerStats.private} reflection${if (answerStats.private == 1) "" else "s"} waiting.",
|
|
|
|
|
body = "Review what you saved and choose whether tonight is the right time to open one up.",
|
|
|
|
|
cta = "Review reflections",
|
|
|
|
|
target = HomeActionTarget.AnswerHistory,
|
|
|
|
|
tone = HomeActionTone.Reflection,
|
|
|
|
|
metric = "${answerStats.revealed} revealed"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
streakCount > 0 -> HomeAction(
|
|
|
|
|
eyebrow = "Shared ritual",
|
|
|
|
|
title = "$streakCount night${if (streakCount == 1) "" else "s"} showing up.",
|
|
|
|
|
body = "Keep it light: answer one prompt, revisit a saved reflection, or choose a pack that fits tonight.",
|
|
|
|
|
cta = "Keep going",
|
|
|
|
|
target = HomeActionTarget.DailyQuestion,
|
|
|
|
|
tone = HomeActionTone.Ritual,
|
|
|
|
|
metric = "${answerStats.total} saved"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
else -> HomeAction(
|
|
|
|
|
eyebrow = "Gentle start",
|
|
|
|
|
title = "Start with one question worth answering.",
|
|
|
|
|
body = "A small prompt is enough. Build the habit around attention, not pressure.",
|
|
|
|
|
cta = "Start tonight",
|
|
|
|
|
target = HomeActionTarget.DailyQuestion,
|
|
|
|
|
tone = HomeActionTone.Starter
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun HomeUiState.buildSecondaryActions(primary: HomeAction): List<HomeAction> {
|
|
|
|
|
val actions = mutableListOf<HomeAction>()
|
|
|
|
|
|
|
|
|
|
answerStats.latest?.let { latest ->
|
|
|
|
|
if (primary.target != HomeActionTarget.AnswerHistory) {
|
|
|
|
|
actions += HomeAction(
|
|
|
|
|
eyebrow = if (latest.isRevealed) "Revealed" else "Private",
|
|
|
|
|
title = "Return to your latest reflection.",
|
|
|
|
|
body = latest.questionText,
|
|
|
|
|
cta = "Open history",
|
|
|
|
|
target = HomeActionTarget.AnswerHistory,
|
|
|
|
|
tone = HomeActionTone.Reflection
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
categories.firstOrNull()?.let { category ->
|
|
|
|
|
actions += HomeAction(
|
|
|
|
|
eyebrow = "Suggested pack",
|
|
|
|
|
title = category.category.displayName.ifBlank { "Question pack" },
|
|
|
|
|
body = "${category.questionCount} prompts for when you want a different doorway into the conversation.",
|
|
|
|
|
cta = "Open pack",
|
|
|
|
|
target = HomeActionTarget.QuestionPacks,
|
|
|
|
|
tone = HomeActionTone.Pack,
|
|
|
|
|
categoryId = category.category.id
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actions += HomeAction(
|
|
|
|
|
eyebrow = "Tune the ritual",
|
|
|
|
|
title = "Adjust your space.",
|
|
|
|
|
body = "Manage reminders, partner state, privacy, and account details when you need to.",
|
|
|
|
|
cta = "Settings",
|
|
|
|
|
target = HomeActionTarget.Settings,
|
|
|
|
|
tone = HomeActionTone.Utility
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return actions.take(3)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun String.toHomeLabel(): String =
|
|
|
|
|
split("_", "-")
|
|
|
|
|
.filter { part -> part.isNotBlank() }
|
|
|
|
|
.joinToString(" ") { part -> part.replaceFirstChar { it.uppercaseChar() } }
|
2026-06-17 20:51:18 -05:00
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
private const val TAG = "HomeViewModel"
|
|
|
|
|
}
|
feat(ui): navigation refactor, screen wiring, local answer persistence
- Refactored AppNavigation with route-based screen graph
- Wired all screens (auth, onboarding, pairing, home, wheel, questions, settings)
- Added local answer repository (SharedPreferences-based)
- Added HomeViewModel, AnswerHistoryViewModel, AnswerRevealViewModel
- Added question category browsing, pack library, composer screens
- Cleaned up DailyQuestionScreen/QuestionThreadScreen to use shared components
- Projected route docs, account/settings screens, new drawable resources
2026-06-15 23:48:55 -05:00
|
|
|
}
|