Add unified DataManager as single source of truth for all app data
- Create DataManager.kt with StateFlows for all cached data: - Authentication (token, user) - Residences, tasks, documents, contractors - Subscription status and upgrade triggers - All lookup data (residence types, task categories, etc.) - Theme preferences and state metadata - Add PersistenceManager with platform-specific implementations: - Android: SharedPreferences - iOS: NSUserDefaults - JVM: Properties file - WasmJS: localStorage - Migrate APILayer to update DataManager on every API response - Update Kotlin ViewModels to use DataManager for token access - Deprecate LookupsRepository (delegates to DataManager) - Create iOS DataManagerObservable Swift wrapper for SwiftUI - Update iOS auth flow to use DataManager.isAuthenticated() Data flow: User Action → API Call → DataManager Updated → All Screens React 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,175 +1,62 @@
|
||||
package com.example.casera.repository
|
||||
|
||||
import com.example.casera.cache.SubscriptionCache
|
||||
import com.example.casera.data.DataManager
|
||||
import com.example.casera.models.*
|
||||
import com.example.casera.network.ApiResult
|
||||
import com.example.casera.network.LookupsApi
|
||||
import com.example.casera.network.SubscriptionApi
|
||||
import com.example.casera.storage.TokenStorage
|
||||
import com.example.casera.storage.TaskCacheStorage
|
||||
import com.example.casera.network.APILayer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Singleton repository for managing lookup data across the entire app.
|
||||
* Fetches data once on initialization and caches it for the app session.
|
||||
*
|
||||
* @deprecated Use DataManager directly. This class is kept for backwards compatibility
|
||||
* and simply delegates to DataManager.
|
||||
*/
|
||||
object LookupsRepository {
|
||||
private val lookupsApi = LookupsApi()
|
||||
private val subscriptionApi = SubscriptionApi()
|
||||
private val scope = CoroutineScope(Dispatchers.Default)
|
||||
|
||||
private val _residenceTypes = MutableStateFlow<List<ResidenceType>>(emptyList())
|
||||
val residenceTypes: StateFlow<List<ResidenceType>> = _residenceTypes
|
||||
|
||||
private val _taskFrequencies = MutableStateFlow<List<TaskFrequency>>(emptyList())
|
||||
val taskFrequencies: StateFlow<List<TaskFrequency>> = _taskFrequencies
|
||||
|
||||
private val _taskPriorities = MutableStateFlow<List<TaskPriority>>(emptyList())
|
||||
val taskPriorities: StateFlow<List<TaskPriority>> = _taskPriorities
|
||||
|
||||
private val _taskStatuses = MutableStateFlow<List<TaskStatus>>(emptyList())
|
||||
val taskStatuses: StateFlow<List<TaskStatus>> = _taskStatuses
|
||||
|
||||
private val _taskCategories = MutableStateFlow<List<TaskCategory>>(emptyList())
|
||||
val taskCategories: StateFlow<List<TaskCategory>> = _taskCategories
|
||||
|
||||
private val _contractorSpecialties = MutableStateFlow<List<ContractorSpecialty>>(emptyList())
|
||||
val contractorSpecialties: StateFlow<List<ContractorSpecialty>> = _contractorSpecialties
|
||||
|
||||
private val _allTasks = MutableStateFlow<List<CustomTask>>(emptyList())
|
||||
val allTasks: StateFlow<List<CustomTask>> = _allTasks
|
||||
|
||||
private val _isLoading = MutableStateFlow(false)
|
||||
val isLoading: StateFlow<Boolean> = _isLoading
|
||||
|
||||
private val _isInitialized = MutableStateFlow(false)
|
||||
val isInitialized: StateFlow<Boolean> = _isInitialized
|
||||
// Delegate to DataManager
|
||||
val residenceTypes: StateFlow<List<ResidenceType>> = DataManager.residenceTypes
|
||||
val taskFrequencies: StateFlow<List<TaskFrequency>> = DataManager.taskFrequencies
|
||||
val taskPriorities: StateFlow<List<TaskPriority>> = DataManager.taskPriorities
|
||||
val taskStatuses: StateFlow<List<TaskStatus>> = DataManager.taskStatuses
|
||||
val taskCategories: StateFlow<List<TaskCategory>> = DataManager.taskCategories
|
||||
val contractorSpecialties: StateFlow<List<ContractorSpecialty>> = DataManager.contractorSpecialties
|
||||
val isInitialized: StateFlow<Boolean> = DataManager.lookupsInitialized
|
||||
|
||||
/**
|
||||
* Load all lookups from the API.
|
||||
* Load all lookups from the API via DataManager.
|
||||
* This should be called once when the user logs in.
|
||||
*/
|
||||
fun initialize() {
|
||||
// Only initialize once per app session
|
||||
if (_isInitialized.value) {
|
||||
// DataManager handles initialization via APILayer.initializeLookups()
|
||||
if (DataManager.lookupsInitialized.value) {
|
||||
return
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
_isLoading.value = true
|
||||
|
||||
// Load cached tasks from disk immediately for offline access
|
||||
val cachedTasks = TaskCacheStorage.getTasks()
|
||||
if (cachedTasks != null) {
|
||||
_allTasks.value = cachedTasks
|
||||
println("Loaded ${cachedTasks.size} tasks from cache")
|
||||
}
|
||||
|
||||
val token = TokenStorage.getToken()
|
||||
|
||||
if (token != null) {
|
||||
// Load all static data in a single API call
|
||||
launch {
|
||||
when (val result = lookupsApi.getStaticData(token)) {
|
||||
is ApiResult.Success -> {
|
||||
_residenceTypes.value = result.data.residenceTypes
|
||||
_taskFrequencies.value = result.data.taskFrequencies
|
||||
_taskPriorities.value = result.data.taskPriorities
|
||||
_taskStatuses.value = result.data.taskStatuses
|
||||
_taskCategories.value = result.data.taskCategories
|
||||
_contractorSpecialties.value = result.data.contractorSpecialties
|
||||
println("Loaded all static data successfully")
|
||||
}
|
||||
else -> {
|
||||
println("Failed to fetch static data")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
when (val result = lookupsApi.getAllTasks(token)) {
|
||||
is ApiResult.Success -> {
|
||||
_allTasks.value = result.data
|
||||
// Save to disk cache for offline access
|
||||
TaskCacheStorage.saveTasks(result.data)
|
||||
println("Fetched and cached ${result.data.size} tasks from API")
|
||||
}
|
||||
else -> {
|
||||
println("Failed to fetch tasks from API, using cached data if available")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load subscription status for limitation checks
|
||||
launch {
|
||||
println("🔄 [LookupsRepository] Fetching subscription status...")
|
||||
when (val result = subscriptionApi.getSubscriptionStatus(token)) {
|
||||
is ApiResult.Success -> {
|
||||
println("✅ [LookupsRepository] Subscription status loaded: limitationsEnabled=${result.data.limitationsEnabled}")
|
||||
println(" Limits: ${result.data.limits}")
|
||||
SubscriptionCache.updateSubscriptionStatus(result.data)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
println("❌ [LookupsRepository] Failed to fetch subscription status: ${result.message}")
|
||||
}
|
||||
else -> {
|
||||
println("❌ [LookupsRepository] Unexpected subscription result")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load upgrade triggers for subscription prompts
|
||||
launch {
|
||||
println("🔄 [LookupsRepository] Fetching upgrade triggers...")
|
||||
when (val result = subscriptionApi.getUpgradeTriggers(token)) {
|
||||
is ApiResult.Success -> {
|
||||
println("✅ [LookupsRepository] Upgrade triggers loaded: ${result.data.size} triggers")
|
||||
SubscriptionCache.updateUpgradeTriggers(result.data)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
println("❌ [LookupsRepository] Failed to fetch upgrade triggers: ${result.message}")
|
||||
}
|
||||
else -> {
|
||||
println("❌ [LookupsRepository] Unexpected upgrade triggers result")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_isInitialized.value = true
|
||||
_isLoading.value = false
|
||||
APILayer.initializeLookups()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached data.
|
||||
* Clear all cached data via DataManager.
|
||||
* This should be called when the user logs out.
|
||||
*/
|
||||
fun clear() {
|
||||
_residenceTypes.value = emptyList()
|
||||
_taskFrequencies.value = emptyList()
|
||||
_taskPriorities.value = emptyList()
|
||||
_taskStatuses.value = emptyList()
|
||||
_taskCategories.value = emptyList()
|
||||
_contractorSpecialties.value = emptyList()
|
||||
_allTasks.value = emptyList()
|
||||
// Clear disk cache on logout
|
||||
TaskCacheStorage.clearTasks()
|
||||
// Clear subscription cache on logout
|
||||
SubscriptionCache.clear()
|
||||
_isInitialized.value = false
|
||||
_isLoading.value = false
|
||||
// DataManager.clear() is called by APILayer.logout()
|
||||
// This method is kept for backwards compatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Force refresh all lookups from the API.
|
||||
*/
|
||||
fun refresh() {
|
||||
_isInitialized.value = false
|
||||
initialize()
|
||||
scope.launch {
|
||||
APILayer.initializeLookups()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user