P1: Shared FixtureDataManager (empty + populated) for cross-platform snapshots

InMemoryDataManager + Fixtures with deterministic data (fixed clock 2026-04-15,
2 residences, 8 tasks, 3 contractors, 5 documents). FixtureDataManager.empty()
and .populated() factories. Exposed to Swift via SKIE.

Expanded IDataManager surface (5 -> 22 members) so fixtures cover every
StateFlow and lookup helper screens read: myResidences, allTasks,
tasksByResidence, documents, documentsByResidence, contractors, residenceTypes,
taskFrequencies, taskPriorities, taskCategories, contractorSpecialties,
taskTemplates, taskTemplatesGrouped, residenceSummaries, upgradeTriggers,
promotions, plus get{ResidenceType,TaskFrequency,TaskPriority,TaskCategory,
ContractorSpecialty}(id) lookup helpers. DataManager implementation is a pure
override-keyword addition — no behavior change.

Enables P2 (Android gallery) + P3 (iOS gallery) to render real screens against
identical inputs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-18 19:22:41 -05:00
parent c57743dca0
commit 47eaf5a0c0
6 changed files with 1263 additions and 31 deletions

View File

@@ -125,35 +125,35 @@ object DataManager : IDataManager {
override val residences: StateFlow<List<Residence>> = _residences.asStateFlow()
private val _myResidences = MutableStateFlow<MyResidencesResponse?>(null)
val myResidences: StateFlow<MyResidencesResponse?> = _myResidences.asStateFlow()
override val myResidences: StateFlow<MyResidencesResponse?> = _myResidences.asStateFlow()
private val _totalSummary = MutableStateFlow<TotalSummary?>(null)
override val totalSummary: StateFlow<TotalSummary?> = _totalSummary.asStateFlow()
private val _residenceSummaries = MutableStateFlow<Map<Int, ResidenceSummaryResponse>>(emptyMap())
val residenceSummaries: StateFlow<Map<Int, ResidenceSummaryResponse>> = _residenceSummaries.asStateFlow()
override val residenceSummaries: StateFlow<Map<Int, ResidenceSummaryResponse>> = _residenceSummaries.asStateFlow()
// ==================== TASKS ====================
private val _allTasks = MutableStateFlow<TaskColumnsResponse?>(null)
val allTasks: StateFlow<TaskColumnsResponse?> = _allTasks.asStateFlow()
override val allTasks: StateFlow<TaskColumnsResponse?> = _allTasks.asStateFlow()
private val _tasksByResidence = MutableStateFlow<Map<Int, TaskColumnsResponse>>(emptyMap())
val tasksByResidence: StateFlow<Map<Int, TaskColumnsResponse>> = _tasksByResidence.asStateFlow()
override val tasksByResidence: StateFlow<Map<Int, TaskColumnsResponse>> = _tasksByResidence.asStateFlow()
// ==================== DOCUMENTS ====================
private val _documents = MutableStateFlow<List<Document>>(emptyList())
val documents: StateFlow<List<Document>> = _documents.asStateFlow()
override val documents: StateFlow<List<Document>> = _documents.asStateFlow()
private val _documentsByResidence = MutableStateFlow<Map<Int, List<Document>>>(emptyMap())
val documentsByResidence: StateFlow<Map<Int, List<Document>>> = _documentsByResidence.asStateFlow()
override val documentsByResidence: StateFlow<Map<Int, List<Document>>> = _documentsByResidence.asStateFlow()
// ==================== CONTRACTORS ====================
// Stores ContractorSummary for list views (lighter weight than full Contractor)
private val _contractors = MutableStateFlow<List<ContractorSummary>>(emptyList())
val contractors: StateFlow<List<ContractorSummary>> = _contractors.asStateFlow()
override val contractors: StateFlow<List<ContractorSummary>> = _contractors.asStateFlow()
// ==================== SUBSCRIPTION ====================
@@ -161,39 +161,39 @@ object DataManager : IDataManager {
override val subscription: StateFlow<SubscriptionStatus?> = _subscription.asStateFlow()
private val _upgradeTriggers = MutableStateFlow<Map<String, UpgradeTriggerData>>(emptyMap())
val upgradeTriggers: StateFlow<Map<String, UpgradeTriggerData>> = _upgradeTriggers.asStateFlow()
override val upgradeTriggers: StateFlow<Map<String, UpgradeTriggerData>> = _upgradeTriggers.asStateFlow()
private val _featureBenefits = MutableStateFlow<List<FeatureBenefit>>(emptyList())
override val featureBenefits: StateFlow<List<FeatureBenefit>> = _featureBenefits.asStateFlow()
private val _promotions = MutableStateFlow<List<Promotion>>(emptyList())
val promotions: StateFlow<List<Promotion>> = _promotions.asStateFlow()
override val promotions: StateFlow<List<Promotion>> = _promotions.asStateFlow()
// ==================== LOOKUPS (Reference Data) ====================
// List-based for dropdowns/pickers
private val _residenceTypes = MutableStateFlow<List<ResidenceType>>(emptyList())
val residenceTypes: StateFlow<List<ResidenceType>> = _residenceTypes.asStateFlow()
override val residenceTypes: StateFlow<List<ResidenceType>> = _residenceTypes.asStateFlow()
private val _taskFrequencies = MutableStateFlow<List<TaskFrequency>>(emptyList())
val taskFrequencies: StateFlow<List<TaskFrequency>> = _taskFrequencies.asStateFlow()
override val taskFrequencies: StateFlow<List<TaskFrequency>> = _taskFrequencies.asStateFlow()
private val _taskPriorities = MutableStateFlow<List<TaskPriority>>(emptyList())
val taskPriorities: StateFlow<List<TaskPriority>> = _taskPriorities.asStateFlow()
override val taskPriorities: StateFlow<List<TaskPriority>> = _taskPriorities.asStateFlow()
private val _taskCategories = MutableStateFlow<List<TaskCategory>>(emptyList())
val taskCategories: StateFlow<List<TaskCategory>> = _taskCategories.asStateFlow()
override val taskCategories: StateFlow<List<TaskCategory>> = _taskCategories.asStateFlow()
private val _contractorSpecialties = MutableStateFlow<List<ContractorSpecialty>>(emptyList())
val contractorSpecialties: StateFlow<List<ContractorSpecialty>> = _contractorSpecialties.asStateFlow()
override val contractorSpecialties: StateFlow<List<ContractorSpecialty>> = _contractorSpecialties.asStateFlow()
// ==================== TASK TEMPLATES ====================
private val _taskTemplates = MutableStateFlow<List<TaskTemplate>>(emptyList())
val taskTemplates: StateFlow<List<TaskTemplate>> = _taskTemplates.asStateFlow()
override val taskTemplates: StateFlow<List<TaskTemplate>> = _taskTemplates.asStateFlow()
private val _taskTemplatesGrouped = MutableStateFlow<TaskTemplatesGroupedResponse?>(null)
val taskTemplatesGrouped: StateFlow<TaskTemplatesGroupedResponse?> = _taskTemplatesGrouped.asStateFlow()
override val taskTemplatesGrouped: StateFlow<TaskTemplatesGroupedResponse?> = _taskTemplatesGrouped.asStateFlow()
// Map-based for O(1) ID resolution
private val _residenceTypesMap = MutableStateFlow<Map<Int, ResidenceType>>(emptyMap())
@@ -261,11 +261,11 @@ object DataManager : IDataManager {
// ==================== O(1) LOOKUP HELPERS ====================
fun getResidenceType(id: Int?): ResidenceType? = id?.let { _residenceTypesMap.value[it] }
fun getTaskFrequency(id: Int?): TaskFrequency? = id?.let { _taskFrequenciesMap.value[it] }
fun getTaskPriority(id: Int?): TaskPriority? = id?.let { _taskPrioritiesMap.value[it] }
fun getTaskCategory(id: Int?): TaskCategory? = id?.let { _taskCategoriesMap.value[it] }
fun getContractorSpecialty(id: Int?): ContractorSpecialty? = id?.let { _contractorSpecialtiesMap.value[it] }
override fun getResidenceType(id: Int?): ResidenceType? = id?.let { _residenceTypesMap.value[it] }
override fun getTaskFrequency(id: Int?): TaskFrequency? = id?.let { _taskFrequenciesMap.value[it] }
override fun getTaskPriority(id: Int?): TaskPriority? = id?.let { _taskPrioritiesMap.value[it] }
override fun getTaskCategory(id: Int?): TaskCategory? = id?.let { _taskCategoriesMap.value[it] }
override fun getContractorSpecialty(id: Int?): ContractorSpecialty? = id?.let { _contractorSpecialtiesMap.value[it] }
// ==================== AUTH UPDATE METHODS ====================

View File

@@ -1,37 +1,110 @@
package com.tt.honeyDue.data
import com.tt.honeyDue.models.ContractorSpecialty
import com.tt.honeyDue.models.ContractorSummary
import com.tt.honeyDue.models.Document
import com.tt.honeyDue.models.FeatureBenefit
import com.tt.honeyDue.models.MyResidencesResponse
import com.tt.honeyDue.models.Promotion
import com.tt.honeyDue.models.Residence
import com.tt.honeyDue.models.ResidenceSummaryResponse
import com.tt.honeyDue.models.ResidenceType
import com.tt.honeyDue.models.SubscriptionStatus
import com.tt.honeyDue.models.TaskCategory
import com.tt.honeyDue.models.TaskColumnsResponse
import com.tt.honeyDue.models.TaskFrequency
import com.tt.honeyDue.models.TaskPriority
import com.tt.honeyDue.models.TaskTemplate
import com.tt.honeyDue.models.TaskTemplatesGroupedResponse
import com.tt.honeyDue.models.TotalSummary
import com.tt.honeyDue.models.UpgradeTriggerData
import com.tt.honeyDue.models.User
import kotlinx.coroutines.flow.StateFlow
/**
* Minimal contract covering the [DataManager] surface consumed by Compose screens.
* Contract covering the [DataManager] surface consumed by Compose screens
* and the parity-gallery fixture factories.
*
* This interface exists solely so screens can depend on an abstraction that tests,
* previews, and the parity-gallery can substitute via [LocalDataManager]. It is
* deliberately narrow — only members referenced from the ui/screens package tree
* are included.
* This interface exists so screens can depend on an abstraction that tests,
* previews, and the parity-gallery can substitute via [LocalDataManager].
* The member set intentionally mirrors every StateFlow and lookup helper a
* screen may read — [com.tt.honeyDue.testing.FixtureDataManager] produces
* fully-populated fakes so snapshot renders have the data every surface
* expects without reaching into the production singleton.
*
* ViewModels, [com.tt.honeyDue.network.APILayer], and [PersistenceManager] continue
* to use the concrete [DataManager] singleton directly; widening this interface to
* cover their surface is explicitly out of scope for the ambient refactor.
* ViewModels, [com.tt.honeyDue.network.APILayer], and [PersistenceManager]
* continue to use the concrete [DataManager] singleton directly; they are
* not covered by this interface.
*/
interface IDataManager {
// ==================== AUTH ====================
/** Observed by [com.tt.honeyDue.ui.screens.ProfileScreen], [com.tt.honeyDue.ui.screens.ResidenceDetailScreen], [com.tt.honeyDue.ui.screens.ResidenceFormScreen]. */
val currentUser: StateFlow<User?>
// ==================== RESIDENCES ====================
/** Observed by [com.tt.honeyDue.ui.screens.ContractorDetailScreen] and the onboarding first-task screen. */
val residences: StateFlow<List<Residence>>
/** Full my-residences API response (may include metadata beyond the list itself). */
val myResidences: StateFlow<MyResidencesResponse?>
/** Observed by [com.tt.honeyDue.ui.screens.HomeScreen] and [com.tt.honeyDue.ui.screens.ResidencesScreen]. */
val totalSummary: StateFlow<TotalSummary?>
/** Per-residence summary cache (keyed by residence id). */
val residenceSummaries: StateFlow<Map<Int, ResidenceSummaryResponse>>
// ==================== TASKS ====================
/** Kanban board covering all tasks across all residences. */
val allTasks: StateFlow<TaskColumnsResponse?>
/** Kanban board cache keyed by residence id. */
val tasksByResidence: StateFlow<Map<Int, TaskColumnsResponse>>
// ==================== DOCUMENTS ====================
val documents: StateFlow<List<Document>>
val documentsByResidence: StateFlow<Map<Int, List<Document>>>
// ==================== CONTRACTORS ====================
val contractors: StateFlow<List<ContractorSummary>>
// ==================== SUBSCRIPTION ====================
/** Observed by [com.tt.honeyDue.ui.screens.ProfileScreen]. */
val subscription: StateFlow<SubscriptionStatus?>
val upgradeTriggers: StateFlow<Map<String, UpgradeTriggerData>>
/** Observed by [com.tt.honeyDue.ui.screens.subscription.FeatureComparisonScreen]. */
val featureBenefits: StateFlow<List<FeatureBenefit>>
/** Observed by [com.tt.honeyDue.ui.screens.ProfileScreen]. */
val subscription: StateFlow<SubscriptionStatus?>
val promotions: StateFlow<List<Promotion>>
// ==================== LOOKUPS ====================
val residenceTypes: StateFlow<List<ResidenceType>>
val taskFrequencies: StateFlow<List<TaskFrequency>>
val taskPriorities: StateFlow<List<TaskPriority>>
val taskCategories: StateFlow<List<TaskCategory>>
val contractorSpecialties: StateFlow<List<ContractorSpecialty>>
// ==================== TASK TEMPLATES ====================
val taskTemplates: StateFlow<List<TaskTemplate>>
val taskTemplatesGrouped: StateFlow<TaskTemplatesGroupedResponse?>
// ==================== O(1) LOOKUP HELPERS ====================
fun getResidenceType(id: Int?): ResidenceType?
fun getTaskFrequency(id: Int?): TaskFrequency?
fun getTaskPriority(id: Int?): TaskPriority?
fun getTaskCategory(id: Int?): TaskCategory?
fun getContractorSpecialty(id: Int?): ContractorSpecialty?
}