Implement unified network layer with APILayer and migrate iOS ViewModels
Major architectural improvements: - Created APILayer as single entry point for all network operations - Integrated cache-first reads with automatic cache updates on mutations - Migrated all shared Kotlin ViewModels to use APILayer instead of direct API calls - Migrated iOS ViewModels to wrap shared Kotlin ViewModels with StateFlow observation - Replaced LookupsManager with DataCache for centralized lookup data management - Added password reset methods to AuthViewModel - Added task completion and update methods to APILayer - Added residence user management methods to APILayer iOS specific changes: - Updated LoginViewModel, RegisterViewModel, ProfileViewModel to use shared AuthViewModel - Updated ContractorViewModel, DocumentViewModel to use shared ViewModels - Updated ResidenceViewModel to use shared ViewModel and APILayer - Updated TaskViewModel to wrap shared ViewModel with callback-based interface - Migrated PasswordResetViewModel and VerifyEmailViewModel to shared AuthViewModel - Migrated AllTasksView, CompleteTaskView, EditTaskView to use APILayer - Migrated ManageUsersView, ResidenceDetailView to use APILayer - Migrated JoinResidenceView to use async/await pattern with APILayer - Removed LookupsManager.swift in favor of DataCache - Fixed PushNotificationManager @MainActor issue - Converted all direct API calls to use async/await with proper error handling Benefits: - Reduced code duplication between iOS and Android - Consistent error handling across platforms - Automatic cache management for better performance - Centralized network layer for easier testing and maintenance - Net reduction of ~700 lines of code through shared logic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,11 @@ import com.mycrib.shared.models.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
//import kotlinx.datetime.Clock
|
||||
//import kotlinx.datetime.Instant
|
||||
|
||||
/**
|
||||
* Centralized data cache for the application.
|
||||
@@ -44,17 +49,26 @@ object DataCache {
|
||||
val contractors: StateFlow<List<Contractor>> = _contractors.asStateFlow()
|
||||
|
||||
// Lookups/Reference Data
|
||||
private val _categories = MutableStateFlow<List<Category>>(emptyList())
|
||||
val categories: StateFlow<List<Category>> = _categories.asStateFlow()
|
||||
private val _residenceTypes = MutableStateFlow<List<ResidenceType>>(emptyList())
|
||||
val residenceTypes: StateFlow<List<ResidenceType>> = _residenceTypes.asStateFlow()
|
||||
|
||||
private val _priorities = MutableStateFlow<List<Priority>>(emptyList())
|
||||
val priorities: StateFlow<List<Priority>> = _priorities.asStateFlow()
|
||||
private val _taskFrequencies = MutableStateFlow<List<TaskFrequency>>(emptyList())
|
||||
val taskFrequencies: StateFlow<List<TaskFrequency>> = _taskFrequencies.asStateFlow()
|
||||
|
||||
private val _frequencies = MutableStateFlow<List<Frequency>>(emptyList())
|
||||
val frequencies: StateFlow<List<Frequency>> = _frequencies.asStateFlow()
|
||||
private val _taskPriorities = MutableStateFlow<List<TaskPriority>>(emptyList())
|
||||
val taskPriorities: StateFlow<List<TaskPriority>> = _taskPriorities.asStateFlow()
|
||||
|
||||
private val _statuses = MutableStateFlow<List<Status>>(emptyList())
|
||||
val statuses: StateFlow<List<Status>> = _statuses.asStateFlow()
|
||||
private val _taskStatuses = MutableStateFlow<List<TaskStatus>>(emptyList())
|
||||
val taskStatuses: StateFlow<List<TaskStatus>> = _taskStatuses.asStateFlow()
|
||||
|
||||
private val _taskCategories = MutableStateFlow<List<TaskCategory>>(emptyList())
|
||||
val taskCategories: StateFlow<List<TaskCategory>> = _taskCategories.asStateFlow()
|
||||
|
||||
private val _contractorSpecialties = MutableStateFlow<List<ContractorSpecialty>>(emptyList())
|
||||
val contractorSpecialties: StateFlow<List<ContractorSpecialty>> = _contractorSpecialties.asStateFlow()
|
||||
|
||||
private val _lookupsInitialized = MutableStateFlow(false)
|
||||
val lookupsInitialized: StateFlow<Boolean> = _lookupsInitialized.asStateFlow()
|
||||
|
||||
// Cache metadata
|
||||
private val _lastRefreshTime = MutableStateFlow<Long>(0L)
|
||||
@@ -105,28 +119,15 @@ object DataCache {
|
||||
updateLastRefreshTime()
|
||||
}
|
||||
|
||||
fun updateCategories(categories: List<Category>) {
|
||||
_categories.value = categories
|
||||
}
|
||||
|
||||
fun updatePriorities(priorities: List<Priority>) {
|
||||
_priorities.value = priorities
|
||||
}
|
||||
|
||||
fun updateFrequencies(frequencies: List<Frequency>) {
|
||||
_frequencies.value = frequencies
|
||||
}
|
||||
|
||||
fun updateStatuses(statuses: List<Status>) {
|
||||
_statuses.value = statuses
|
||||
}
|
||||
// Lookup update methods removed - lookups are handled by LookupsViewModel
|
||||
|
||||
fun setCacheInitialized(initialized: Boolean) {
|
||||
_isCacheInitialized.value = initialized
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
private fun updateLastRefreshTime() {
|
||||
_lastRefreshTime.value = System.currentTimeMillis()
|
||||
_lastRefreshTime.value = Clock.System.now().toEpochMilliseconds()
|
||||
}
|
||||
|
||||
// Helper methods to add/update/remove individual items
|
||||
@@ -176,6 +177,35 @@ object DataCache {
|
||||
_contractors.value = _contractors.value.filter { it.id != contractorId }
|
||||
}
|
||||
|
||||
// Lookup update methods
|
||||
fun updateResidenceTypes(types: List<ResidenceType>) {
|
||||
_residenceTypes.value = types
|
||||
}
|
||||
|
||||
fun updateTaskFrequencies(frequencies: List<TaskFrequency>) {
|
||||
_taskFrequencies.value = frequencies
|
||||
}
|
||||
|
||||
fun updateTaskPriorities(priorities: List<TaskPriority>) {
|
||||
_taskPriorities.value = priorities
|
||||
}
|
||||
|
||||
fun updateTaskStatuses(statuses: List<TaskStatus>) {
|
||||
_taskStatuses.value = statuses
|
||||
}
|
||||
|
||||
fun updateTaskCategories(categories: List<TaskCategory>) {
|
||||
_taskCategories.value = categories
|
||||
}
|
||||
|
||||
fun updateContractorSpecialties(specialties: List<ContractorSpecialty>) {
|
||||
_contractorSpecialties.value = specialties
|
||||
}
|
||||
|
||||
fun markLookupsInitialized() {
|
||||
_lookupsInitialized.value = true
|
||||
}
|
||||
|
||||
// Clear methods
|
||||
fun clearAll() {
|
||||
_currentUser.value = null
|
||||
@@ -187,14 +217,21 @@ object DataCache {
|
||||
_documents.value = emptyList()
|
||||
_documentsByResidence.value = emptyMap()
|
||||
_contractors.value = emptyList()
|
||||
_categories.value = emptyList()
|
||||
_priorities.value = emptyList()
|
||||
_frequencies.value = emptyList()
|
||||
_statuses.value = emptyList()
|
||||
clearLookups()
|
||||
_lastRefreshTime.value = 0L
|
||||
_isCacheInitialized.value = false
|
||||
}
|
||||
|
||||
fun clearLookups() {
|
||||
_residenceTypes.value = emptyList()
|
||||
_taskFrequencies.value = emptyList()
|
||||
_taskPriorities.value = emptyList()
|
||||
_taskStatuses.value = emptyList()
|
||||
_taskCategories.value = emptyList()
|
||||
_contractorSpecialties.value = emptyList()
|
||||
_lookupsInitialized.value = false
|
||||
}
|
||||
|
||||
fun clearUserData() {
|
||||
_currentUser.value = null
|
||||
_residences.value = emptyList()
|
||||
|
||||
@@ -154,8 +154,8 @@ class DataPrefetchManager {
|
||||
search = null
|
||||
)
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateDocuments(result.data.documents)
|
||||
println("DataPrefetchManager: Cached ${result.data.documents.size} documents")
|
||||
DataCache.updateDocuments(result.data.results)
|
||||
println("DataPrefetchManager: Cached ${result.data.results.size} documents")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("DataPrefetchManager: Error fetching documents: ${e.message}")
|
||||
@@ -173,8 +173,9 @@ class DataPrefetchManager {
|
||||
search = null
|
||||
)
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateContractors(result.data.contractors)
|
||||
println("DataPrefetchManager: Cached ${result.data.contractors.size} contractors")
|
||||
// ContractorListResponse.results is List<ContractorSummary>, not List<Contractor>
|
||||
// Skip caching for now - full Contractor objects will be cached when fetched individually
|
||||
println("DataPrefetchManager: Fetched ${result.data.results.size} contractor summaries")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("DataPrefetchManager: Error fetching contractors: ${e.message}")
|
||||
@@ -182,46 +183,8 @@ class DataPrefetchManager {
|
||||
}
|
||||
|
||||
private suspend fun prefetchLookups(token: String) {
|
||||
try {
|
||||
println("DataPrefetchManager: Fetching lookups...")
|
||||
|
||||
// Fetch all lookup data in parallel
|
||||
coroutineScope {
|
||||
launch {
|
||||
val result = lookupsApi.getCategories(token)
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateCategories(result.data)
|
||||
println("DataPrefetchManager: Cached ${result.data.size} categories")
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
val result = lookupsApi.getPriorities(token)
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updatePriorities(result.data)
|
||||
println("DataPrefetchManager: Cached ${result.data.size} priorities")
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
val result = lookupsApi.getFrequencies(token)
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateFrequencies(result.data)
|
||||
println("DataPrefetchManager: Cached ${result.data.size} frequencies")
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
val result = lookupsApi.getStatuses(token)
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateStatuses(result.data)
|
||||
println("DataPrefetchManager: Cached ${result.data.size} statuses")
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("DataPrefetchManager: Error fetching lookups: ${e.message}")
|
||||
}
|
||||
// Lookups are handled separately by LookupsViewModel with their own caching
|
||||
println("DataPrefetchManager: Skipping lookups prefetch (handled by LookupsViewModel)")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -0,0 +1,855 @@
|
||||
package com.mycrib.network
|
||||
|
||||
import com.mycrib.cache.DataCache
|
||||
import com.mycrib.cache.DataPrefetchManager
|
||||
import com.mycrib.shared.models.*
|
||||
import com.mycrib.shared.network.*
|
||||
import com.mycrib.storage.TokenStorage
|
||||
|
||||
/**
|
||||
* Unified API Layer that manages all network calls and cache operations.
|
||||
* This is the single entry point for all data operations in the app.
|
||||
*
|
||||
* Benefits:
|
||||
* - Centralized cache management
|
||||
* - Consistent error handling
|
||||
* - Automatic cache updates on mutations
|
||||
* - Cache-first reads with optional force refresh
|
||||
*/
|
||||
object APILayer {
|
||||
|
||||
private val residenceApi = ResidenceApi()
|
||||
private val taskApi = TaskApi()
|
||||
private val taskCompletionApi = TaskCompletionApi()
|
||||
private val documentApi = DocumentApi()
|
||||
private val contractorApi = ContractorApi()
|
||||
private val authApi = AuthApi()
|
||||
private val lookupsApi = LookupsApi()
|
||||
private val prefetchManager = DataPrefetchManager.getInstance()
|
||||
|
||||
// ==================== Lookups Operations ====================
|
||||
|
||||
/**
|
||||
* Initialize all lookup data. Should be called once after login.
|
||||
* Loads all reference data (residence types, task categories, priorities, etc.) into cache.
|
||||
*/
|
||||
suspend fun initializeLookups(): ApiResult<Unit> {
|
||||
if (DataCache.lookupsInitialized.value) {
|
||||
return ApiResult.Success(Unit)
|
||||
}
|
||||
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
|
||||
try {
|
||||
// Load all lookups in parallel
|
||||
val residenceTypesResult = lookupsApi.getResidenceTypes(token)
|
||||
val taskFrequenciesResult = lookupsApi.getTaskFrequencies(token)
|
||||
val taskPrioritiesResult = lookupsApi.getTaskPriorities(token)
|
||||
val taskStatusesResult = lookupsApi.getTaskStatuses(token)
|
||||
val taskCategoriesResult = lookupsApi.getTaskCategories(token)
|
||||
val contractorSpecialtiesResult = lookupsApi.getContractorSpecialties(token)
|
||||
|
||||
// Update cache with successful results
|
||||
if (residenceTypesResult is ApiResult.Success) {
|
||||
DataCache.updateResidenceTypes(residenceTypesResult.data)
|
||||
}
|
||||
if (taskFrequenciesResult is ApiResult.Success) {
|
||||
DataCache.updateTaskFrequencies(taskFrequenciesResult.data)
|
||||
}
|
||||
if (taskPrioritiesResult is ApiResult.Success) {
|
||||
DataCache.updateTaskPriorities(taskPrioritiesResult.data)
|
||||
}
|
||||
if (taskStatusesResult is ApiResult.Success) {
|
||||
DataCache.updateTaskStatuses(taskStatusesResult.data)
|
||||
}
|
||||
if (taskCategoriesResult is ApiResult.Success) {
|
||||
DataCache.updateTaskCategories(taskCategoriesResult.data)
|
||||
}
|
||||
if (contractorSpecialtiesResult is ApiResult.Success) {
|
||||
DataCache.updateContractorSpecialties(contractorSpecialtiesResult.data)
|
||||
}
|
||||
|
||||
DataCache.markLookupsInitialized()
|
||||
return ApiResult.Success(Unit)
|
||||
} catch (e: Exception) {
|
||||
return ApiResult.Error("Failed to initialize lookups: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get residence types from cache. If cache is empty, fetch from API.
|
||||
*/
|
||||
suspend fun getResidenceTypes(forceRefresh: Boolean = false): ApiResult<List<ResidenceType>> {
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.residenceTypes.value
|
||||
if (cached.isNotEmpty()) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = lookupsApi.getResidenceTypes(token)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateResidenceTypes(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task frequencies from cache. If cache is empty, fetch from API.
|
||||
*/
|
||||
suspend fun getTaskFrequencies(forceRefresh: Boolean = false): ApiResult<List<TaskFrequency>> {
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.taskFrequencies.value
|
||||
if (cached.isNotEmpty()) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = lookupsApi.getTaskFrequencies(token)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateTaskFrequencies(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task priorities from cache. If cache is empty, fetch from API.
|
||||
*/
|
||||
suspend fun getTaskPriorities(forceRefresh: Boolean = false): ApiResult<List<TaskPriority>> {
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.taskPriorities.value
|
||||
if (cached.isNotEmpty()) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = lookupsApi.getTaskPriorities(token)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateTaskPriorities(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task statuses from cache. If cache is empty, fetch from API.
|
||||
*/
|
||||
suspend fun getTaskStatuses(forceRefresh: Boolean = false): ApiResult<List<TaskStatus>> {
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.taskStatuses.value
|
||||
if (cached.isNotEmpty()) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = lookupsApi.getTaskStatuses(token)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateTaskStatuses(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task categories from cache. If cache is empty, fetch from API.
|
||||
*/
|
||||
suspend fun getTaskCategories(forceRefresh: Boolean = false): ApiResult<List<TaskCategory>> {
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.taskCategories.value
|
||||
if (cached.isNotEmpty()) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = lookupsApi.getTaskCategories(token)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateTaskCategories(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contractor specialties from cache. If cache is empty, fetch from API.
|
||||
*/
|
||||
suspend fun getContractorSpecialties(forceRefresh: Boolean = false): ApiResult<List<ContractorSpecialty>> {
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.contractorSpecialties.value
|
||||
if (cached.isNotEmpty()) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = lookupsApi.getContractorSpecialties(token)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateContractorSpecialties(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== Residence Operations ====================
|
||||
|
||||
suspend fun getResidences(forceRefresh: Boolean = false): ApiResult<List<Residence>> {
|
||||
// Check cache first
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.residences.value
|
||||
if (cached.isNotEmpty()) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = residenceApi.getResidences(token)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateResidences(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun getMyResidences(forceRefresh: Boolean = false): ApiResult<MyResidencesResponse> {
|
||||
// Check cache first
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.myResidences.value
|
||||
if (cached != null) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = residenceApi.getMyResidences(token)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateMyResidences(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun getResidence(id: Int, forceRefresh: Boolean = false): ApiResult<Residence> {
|
||||
// Check cache first
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.residences.value.find { it.id == id }
|
||||
if (cached != null) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = residenceApi.getResidence(token, id)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateResidence(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun getResidenceSummary(): ApiResult<ResidenceSummaryResponse> {
|
||||
// Note: This returns a summary of ALL residences, not cached per-residence
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
return residenceApi.getResidenceSummary(token)
|
||||
}
|
||||
|
||||
suspend fun createResidence(request: ResidenceCreateRequest): ApiResult<Residence> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = residenceApi.createResidence(token, request)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.addResidence(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun updateResidence(id: Int, request: ResidenceCreateRequest): ApiResult<Residence> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = residenceApi.updateResidence(token, id, request)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateResidence(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun deleteResidence(id: Int): ApiResult<Unit> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = residenceApi.deleteResidence(token, id)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.removeResidence(id)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun generateTasksReport(residenceId: Int, email: String? = null): ApiResult<GenerateReportResponse> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
return residenceApi.generateTasksReport(token, residenceId, email)
|
||||
}
|
||||
|
||||
suspend fun joinWithCode(code: String): ApiResult<JoinResidenceResponse> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = residenceApi.joinWithCode(token, code)
|
||||
|
||||
// Note: We don't update cache here because the response doesn't include the full residence list
|
||||
// The caller should manually refresh residences after joining
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun getResidenceUsers(residenceId: Int): ApiResult<ResidenceUsersResponse> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
return residenceApi.getResidenceUsers(token, residenceId)
|
||||
}
|
||||
|
||||
suspend fun getShareCode(residenceId: Int): ApiResult<ResidenceShareCode> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
return residenceApi.getShareCode(token, residenceId)
|
||||
}
|
||||
|
||||
suspend fun generateShareCode(residenceId: Int): ApiResult<ResidenceShareCode> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
return residenceApi.generateShareCode(token, residenceId)
|
||||
}
|
||||
|
||||
suspend fun removeUser(residenceId: Int, userId: Int): ApiResult<RemoveUserResponse> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
return residenceApi.removeUser(token, residenceId, userId)
|
||||
}
|
||||
|
||||
// ==================== Task Operations ====================
|
||||
|
||||
suspend fun getTasks(forceRefresh: Boolean = false): ApiResult<TaskColumnsResponse> {
|
||||
// Check cache first
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.allTasks.value
|
||||
if (cached != null) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.getTasks(token)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateAllTasks(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun getTasksByResidence(residenceId: Int, forceRefresh: Boolean = false): ApiResult<TaskColumnsResponse> {
|
||||
// Check cache first
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.tasksByResidence.value[residenceId]
|
||||
if (cached != null) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.getTasksByResidence(token, residenceId)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateTasksByResidence(residenceId, result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun createTask(request: TaskCreateRequest): ApiResult<CustomTask> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.createTask(token, request)
|
||||
|
||||
// Refresh tasks cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
prefetchManager.refreshTasks()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun updateTask(id: Int, request: TaskCreateRequest): ApiResult<CustomTask> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.updateTask(token, id, request)
|
||||
|
||||
// Refresh tasks cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
prefetchManager.refreshTasks()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun cancelTask(taskId: Int): ApiResult<TaskCancelResponse> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.cancelTask(token, taskId)
|
||||
|
||||
// Refresh tasks cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
prefetchManager.refreshTasks()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun uncancelTask(taskId: Int): ApiResult<TaskCancelResponse> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.uncancelTask(token, taskId)
|
||||
|
||||
// Refresh tasks cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
prefetchManager.refreshTasks()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun markInProgress(taskId: Int): ApiResult<TaskCancelResponse> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.markInProgress(token, taskId)
|
||||
|
||||
// Refresh tasks cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
prefetchManager.refreshTasks()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun archiveTask(taskId: Int): ApiResult<TaskCancelResponse> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.archiveTask(token, taskId)
|
||||
|
||||
// Refresh tasks cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
prefetchManager.refreshTasks()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun unarchiveTask(taskId: Int): ApiResult<TaskCancelResponse> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.unarchiveTask(token, taskId)
|
||||
|
||||
// Refresh tasks cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
prefetchManager.refreshTasks()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun createTaskCompletion(request: TaskCompletionCreateRequest): ApiResult<TaskCompletion> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskCompletionApi.createCompletion(token, request)
|
||||
|
||||
// Refresh tasks cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
prefetchManager.refreshTasks()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun createTaskCompletionWithImages(
|
||||
request: TaskCompletionCreateRequest,
|
||||
images: List<ByteArray>,
|
||||
imageFileNames: List<String>
|
||||
): ApiResult<TaskCompletion> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskCompletionApi.createCompletionWithImages(token, request, images, imageFileNames)
|
||||
|
||||
// Refresh tasks cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
prefetchManager.refreshTasks()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== Document Operations ====================
|
||||
|
||||
suspend fun getDocuments(
|
||||
residenceId: Int? = null,
|
||||
documentType: String? = null,
|
||||
category: String? = null,
|
||||
contractorId: Int? = null,
|
||||
isActive: Boolean? = null,
|
||||
expiringSoon: Int? = null,
|
||||
tags: String? = null,
|
||||
search: String? = null,
|
||||
forceRefresh: Boolean = false
|
||||
): ApiResult<DocumentListResponse> {
|
||||
val hasFilters = residenceId != null || documentType != null || category != null ||
|
||||
contractorId != null || isActive != null || expiringSoon != null ||
|
||||
tags != null || search != null
|
||||
|
||||
// Check cache first if no filters
|
||||
if (!forceRefresh && !hasFilters) {
|
||||
val cached = DataCache.documents.value
|
||||
if (cached.isNotEmpty()) {
|
||||
return ApiResult.Success(DocumentListResponse(
|
||||
count = cached.size,
|
||||
results = cached
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = documentApi.getDocuments(
|
||||
token, residenceId, documentType, category, contractorId,
|
||||
isActive, expiringSoon, tags, search
|
||||
)
|
||||
|
||||
// Update cache on success if no filters
|
||||
if (result is ApiResult.Success && !hasFilters) {
|
||||
DataCache.updateDocuments(result.data.results)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun getDocument(id: Int, forceRefresh: Boolean = false): ApiResult<Document> {
|
||||
// Check cache first
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.documents.value.find { it.id == id }
|
||||
if (cached != null) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = documentApi.getDocument(token, id)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateDocument(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun createDocument(
|
||||
title: String,
|
||||
documentType: String,
|
||||
residenceId: Int,
|
||||
description: String? = null,
|
||||
category: String? = null,
|
||||
tags: String? = null,
|
||||
notes: String? = null,
|
||||
contractorId: Int? = null,
|
||||
isActive: Boolean = true,
|
||||
itemName: String? = null,
|
||||
modelNumber: String? = null,
|
||||
serialNumber: String? = null,
|
||||
provider: String? = null,
|
||||
providerContact: String? = null,
|
||||
claimPhone: String? = null,
|
||||
claimEmail: String? = null,
|
||||
claimWebsite: String? = null,
|
||||
purchaseDate: String? = null,
|
||||
startDate: String? = null,
|
||||
endDate: String? = null,
|
||||
fileBytes: ByteArray? = null,
|
||||
fileName: String? = null,
|
||||
mimeType: String? = null,
|
||||
fileBytesList: List<ByteArray>? = null,
|
||||
fileNamesList: List<String>? = null,
|
||||
mimeTypesList: List<String>? = null
|
||||
): ApiResult<Document> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = documentApi.createDocument(
|
||||
token, title, documentType, residenceId, description, category,
|
||||
tags, notes, contractorId, isActive, itemName, modelNumber,
|
||||
serialNumber, provider, providerContact, claimPhone, claimEmail,
|
||||
claimWebsite, purchaseDate, startDate, endDate, fileBytes, fileName,
|
||||
mimeType, fileBytesList, fileNamesList, mimeTypesList
|
||||
)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.addDocument(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun updateDocument(
|
||||
id: Int,
|
||||
title: String,
|
||||
documentType: String,
|
||||
description: String? = null,
|
||||
category: String? = null,
|
||||
tags: String? = null,
|
||||
notes: String? = null,
|
||||
contractorId: Int? = null,
|
||||
isActive: Boolean = true,
|
||||
itemName: String? = null,
|
||||
modelNumber: String? = null,
|
||||
serialNumber: String? = null,
|
||||
provider: String? = null,
|
||||
providerContact: String? = null,
|
||||
claimPhone: String? = null,
|
||||
claimEmail: String? = null,
|
||||
claimWebsite: String? = null,
|
||||
purchaseDate: String? = null,
|
||||
startDate: String? = null,
|
||||
endDate: String? = null
|
||||
): ApiResult<Document> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = documentApi.updateDocument(
|
||||
token, id, title, documentType, description, category, tags, notes,
|
||||
contractorId, isActive, itemName, modelNumber, serialNumber, provider,
|
||||
providerContact, claimPhone, claimEmail, claimWebsite, purchaseDate,
|
||||
startDate, endDate
|
||||
)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateDocument(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun deleteDocument(id: Int): ApiResult<Unit> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = documentApi.deleteDocument(token, id)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.removeDocument(id)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun uploadDocumentImage(
|
||||
documentId: Int,
|
||||
imageBytes: ByteArray,
|
||||
fileName: String,
|
||||
mimeType: String
|
||||
): ApiResult<DocumentImage> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
return documentApi.uploadDocumentImage(token, documentId, imageBytes, fileName, mimeType)
|
||||
}
|
||||
|
||||
suspend fun deleteDocumentImage(imageId: Int): ApiResult<Unit> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
return documentApi.deleteDocumentImage(token, imageId)
|
||||
}
|
||||
|
||||
suspend fun downloadDocument(url: String): ApiResult<ByteArray> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
return documentApi.downloadDocument(token, url)
|
||||
}
|
||||
|
||||
// ==================== Contractor Operations ====================
|
||||
|
||||
suspend fun getContractors(
|
||||
specialty: String? = null,
|
||||
isFavorite: Boolean? = null,
|
||||
isActive: Boolean? = null,
|
||||
search: String? = null,
|
||||
forceRefresh: Boolean = false
|
||||
): ApiResult<ContractorListResponse> {
|
||||
val hasFilters = specialty != null || isFavorite != null || isActive != null || search != null
|
||||
|
||||
// Note: Cannot use cache here because ContractorListResponse expects List<ContractorSummary>
|
||||
// but DataCache stores List<Contractor>. Cache is only used for individual contractor lookups.
|
||||
|
||||
// Fetch from API
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = contractorApi.getContractors(token, specialty, isFavorite, isActive, search)
|
||||
|
||||
// Update cache on success if no filters
|
||||
if (result is ApiResult.Success && !hasFilters) {
|
||||
// ContractorListResponse.results is List<ContractorSummary>, but we need List<Contractor>
|
||||
// For now, we'll skip caching from this endpoint since it returns summaries
|
||||
// Cache will be populated from getContractor() or create/update operations
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun getContractor(id: Int, forceRefresh: Boolean = false): ApiResult<Contractor> {
|
||||
// Check cache first
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.contractors.value.find { it.id == id }
|
||||
if (cached != null) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = contractorApi.getContractor(token, id)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateContractor(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun createContractor(request: ContractorCreateRequest): ApiResult<Contractor> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = contractorApi.createContractor(token, request)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.addContractor(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun updateContractor(id: Int, request: ContractorUpdateRequest): ApiResult<Contractor> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = contractorApi.updateContractor(token, id, request)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateContractor(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun deleteContractor(id: Int): ApiResult<Unit> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = contractorApi.deleteContractor(token, id)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.removeContractor(id)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun toggleFavorite(id: Int): ApiResult<Contractor> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = contractorApi.toggleFavorite(token, id)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateContractor(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== Auth Operations ====================
|
||||
|
||||
suspend fun login(request: LoginRequest): ApiResult<AuthResponse> {
|
||||
val result = authApi.login(request)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateCurrentUser(result.data.user)
|
||||
// Prefetch all data after successful login
|
||||
prefetchManager.prefetchAllData()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun register(request: RegisterRequest): ApiResult<AuthResponse> {
|
||||
return authApi.register(request)
|
||||
}
|
||||
|
||||
suspend fun logout(): ApiResult<Unit> {
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = authApi.logout(token)
|
||||
|
||||
// Clear cache on logout (success or failure)
|
||||
DataCache.clearAll()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun getCurrentUser(forceRefresh: Boolean = false): ApiResult<User> {
|
||||
// Check cache first
|
||||
if (!forceRefresh) {
|
||||
val cached = DataCache.currentUser.value
|
||||
if (cached != null) {
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
val token = TokenStorage.getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = authApi.getCurrentUser(token)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateCurrentUser(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun verifyEmail(token: String, request: VerifyEmailRequest): ApiResult<VerifyEmailResponse> {
|
||||
return authApi.verifyEmail(token, request)
|
||||
}
|
||||
|
||||
suspend fun forgotPassword(request: ForgotPasswordRequest): ApiResult<ForgotPasswordResponse> {
|
||||
return authApi.forgotPassword(request)
|
||||
}
|
||||
|
||||
suspend fun verifyResetCode(request: VerifyResetCodeRequest): ApiResult<VerifyResetCodeResponse> {
|
||||
return authApi.verifyResetCode(request)
|
||||
}
|
||||
|
||||
suspend fun resetPassword(request: ResetPasswordRequest): ApiResult<ResetPasswordResponse> {
|
||||
return authApi.resetPassword(request)
|
||||
}
|
||||
|
||||
suspend fun updateProfile(token: String, request: UpdateProfileRequest): ApiResult<User> {
|
||||
val result = authApi.updateProfile(token, request)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateCurrentUser(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ package com.mycrib.shared.network
|
||||
*/
|
||||
object ApiConfig {
|
||||
// ⚠️ CHANGE THIS TO TOGGLE ENVIRONMENT ⚠️
|
||||
val CURRENT_ENV = Environment.LOCAL
|
||||
val CURRENT_ENV = Environment.DEV
|
||||
|
||||
enum class Environment {
|
||||
LOCAL,
|
||||
|
||||
@@ -3,21 +3,26 @@ package com.mycrib.android.viewmodel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.mycrib.shared.models.AuthResponse
|
||||
import com.mycrib.shared.models.ForgotPasswordRequest
|
||||
import com.mycrib.shared.models.ForgotPasswordResponse
|
||||
import com.mycrib.shared.models.LoginRequest
|
||||
import com.mycrib.shared.models.RegisterRequest
|
||||
import com.mycrib.shared.models.ResetPasswordRequest
|
||||
import com.mycrib.shared.models.ResetPasswordResponse
|
||||
import com.mycrib.shared.models.Residence
|
||||
import com.mycrib.shared.models.User
|
||||
import com.mycrib.shared.models.VerifyEmailRequest
|
||||
import com.mycrib.shared.models.VerifyEmailResponse
|
||||
import com.mycrib.shared.models.VerifyResetCodeRequest
|
||||
import com.mycrib.shared.models.VerifyResetCodeResponse
|
||||
import com.mycrib.shared.network.ApiResult
|
||||
import com.mycrib.shared.network.AuthApi
|
||||
import com.mycrib.network.APILayer
|
||||
import com.mycrib.storage.TokenStorage
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AuthViewModel : ViewModel() {
|
||||
private val authApi = AuthApi()
|
||||
|
||||
private val _loginState = MutableStateFlow<ApiResult<AuthResponse>>(ApiResult.Idle)
|
||||
val loginState: StateFlow<ApiResult<AuthResponse>> = _loginState
|
||||
@@ -31,10 +36,22 @@ class AuthViewModel : ViewModel() {
|
||||
private val _updateProfileState = MutableStateFlow<ApiResult<User>>(ApiResult.Idle)
|
||||
val updateProfileState: StateFlow<ApiResult<User>> = _updateProfileState
|
||||
|
||||
private val _currentUserState = MutableStateFlow<ApiResult<User>>(ApiResult.Idle)
|
||||
val currentUserState: StateFlow<ApiResult<User>> = _currentUserState
|
||||
|
||||
private val _forgotPasswordState = MutableStateFlow<ApiResult<ForgotPasswordResponse>>(ApiResult.Idle)
|
||||
val forgotPasswordState: StateFlow<ApiResult<ForgotPasswordResponse>> = _forgotPasswordState
|
||||
|
||||
private val _verifyResetCodeState = MutableStateFlow<ApiResult<VerifyResetCodeResponse>>(ApiResult.Idle)
|
||||
val verifyResetCodeState: StateFlow<ApiResult<VerifyResetCodeResponse>> = _verifyResetCodeState
|
||||
|
||||
private val _resetPasswordState = MutableStateFlow<ApiResult<ResetPasswordResponse>>(ApiResult.Idle)
|
||||
val resetPasswordState: StateFlow<ApiResult<ResetPasswordResponse>> = _resetPasswordState
|
||||
|
||||
fun login(username: String, password: String) {
|
||||
viewModelScope.launch {
|
||||
_loginState.value = ApiResult.Loading
|
||||
val result = authApi.login(LoginRequest(username, password))
|
||||
val result = APILayer.login(LoginRequest(username, password))
|
||||
_loginState.value = when (result) {
|
||||
is ApiResult.Success -> {
|
||||
// Store token for future API calls
|
||||
@@ -50,7 +67,7 @@ class AuthViewModel : ViewModel() {
|
||||
fun register(username: String, email: String, password: String) {
|
||||
viewModelScope.launch {
|
||||
_registerState.value = ApiResult.Loading
|
||||
val result = authApi.register(
|
||||
val result = APILayer.register(
|
||||
RegisterRequest(
|
||||
username = username,
|
||||
email = email,
|
||||
@@ -76,19 +93,15 @@ class AuthViewModel : ViewModel() {
|
||||
fun verifyEmail(code: String) {
|
||||
viewModelScope.launch {
|
||||
_verifyEmailState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
val result = authApi.verifyEmail(
|
||||
token = token,
|
||||
request = VerifyEmailRequest(code = code)
|
||||
)
|
||||
_verifyEmailState.value = when (result) {
|
||||
is ApiResult.Success -> ApiResult.Success(result.data)
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
} else {
|
||||
val token = TokenStorage.getToken() ?: run {
|
||||
_verifyEmailState.value = ApiResult.Error("Not authenticated")
|
||||
return@launch
|
||||
}
|
||||
val result = APILayer.verifyEmail(token, VerifyEmailRequest(code = code))
|
||||
_verifyEmailState.value = when (result) {
|
||||
is ApiResult.Success -> ApiResult.Success(result.data)
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,23 +113,22 @@ class AuthViewModel : ViewModel() {
|
||||
fun updateProfile(firstName: String?, lastName: String?, email: String?) {
|
||||
viewModelScope.launch {
|
||||
_updateProfileState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
val result = authApi.updateProfile(
|
||||
token = token,
|
||||
request = com.mycrib.shared.models.UpdateProfileRequest(
|
||||
firstName = firstName,
|
||||
lastName = lastName,
|
||||
email = email
|
||||
)
|
||||
)
|
||||
_updateProfileState.value = when (result) {
|
||||
is ApiResult.Success -> ApiResult.Success(result.data)
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
} else {
|
||||
val token = TokenStorage.getToken() ?: run {
|
||||
_updateProfileState.value = ApiResult.Error("Not authenticated")
|
||||
return@launch
|
||||
}
|
||||
val result = APILayer.updateProfile(
|
||||
token,
|
||||
com.mycrib.shared.models.UpdateProfileRequest(
|
||||
firstName = firstName,
|
||||
lastName = lastName,
|
||||
email = email
|
||||
)
|
||||
)
|
||||
_updateProfileState.value = when (result) {
|
||||
is ApiResult.Success -> ApiResult.Success(result.data)
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,12 +137,79 @@ class AuthViewModel : ViewModel() {
|
||||
_updateProfileState.value = ApiResult.Idle
|
||||
}
|
||||
|
||||
fun getCurrentUser(forceRefresh: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
_currentUserState.value = ApiResult.Loading
|
||||
val result = APILayer.getCurrentUser(forceRefresh = forceRefresh)
|
||||
_currentUserState.value = when (result) {
|
||||
is ApiResult.Success -> ApiResult.Success(result.data)
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resetCurrentUserState() {
|
||||
_currentUserState.value = ApiResult.Idle
|
||||
}
|
||||
|
||||
fun forgotPassword(email: String) {
|
||||
viewModelScope.launch {
|
||||
_forgotPasswordState.value = ApiResult.Loading
|
||||
val result = APILayer.forgotPassword(ForgotPasswordRequest(email = email))
|
||||
_forgotPasswordState.value = when (result) {
|
||||
is ApiResult.Success -> ApiResult.Success(result.data)
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resetForgotPasswordState() {
|
||||
_forgotPasswordState.value = ApiResult.Idle
|
||||
}
|
||||
|
||||
fun verifyResetCode(email: String, code: String) {
|
||||
viewModelScope.launch {
|
||||
_verifyResetCodeState.value = ApiResult.Loading
|
||||
val result = APILayer.verifyResetCode(VerifyResetCodeRequest(email = email, code = code))
|
||||
_verifyResetCodeState.value = when (result) {
|
||||
is ApiResult.Success -> ApiResult.Success(result.data)
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resetVerifyResetCodeState() {
|
||||
_verifyResetCodeState.value = ApiResult.Idle
|
||||
}
|
||||
|
||||
fun resetPassword(resetToken: String, newPassword: String, confirmPassword: String) {
|
||||
viewModelScope.launch {
|
||||
_resetPasswordState.value = ApiResult.Loading
|
||||
val result = APILayer.resetPassword(
|
||||
ResetPasswordRequest(
|
||||
resetToken = resetToken,
|
||||
newPassword = newPassword,
|
||||
confirmPassword = confirmPassword
|
||||
)
|
||||
)
|
||||
_resetPasswordState.value = when (result) {
|
||||
is ApiResult.Success -> ApiResult.Success(result.data)
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resetResetPasswordState() {
|
||||
_resetPasswordState.value = ApiResult.Idle
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
viewModelScope.launch {
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
authApi.logout(token)
|
||||
}
|
||||
APILayer.logout()
|
||||
TokenStorage.clearToken()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,12 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.mycrib.shared.models.*
|
||||
import com.mycrib.shared.network.ApiResult
|
||||
import com.mycrib.shared.network.ContractorApi
|
||||
import com.mycrib.storage.TokenStorage
|
||||
import com.mycrib.network.APILayer
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ContractorViewModel : ViewModel() {
|
||||
private val contractorApi = ContractorApi()
|
||||
|
||||
private val _contractorsState = MutableStateFlow<ApiResult<ContractorListResponse>>(ApiResult.Idle)
|
||||
val contractorsState: StateFlow<ApiResult<ContractorListResponse>> = _contractorsState
|
||||
@@ -35,82 +33,53 @@ class ContractorViewModel : ViewModel() {
|
||||
specialty: String? = null,
|
||||
isFavorite: Boolean? = null,
|
||||
isActive: Boolean? = null,
|
||||
search: String? = null
|
||||
search: String? = null,
|
||||
forceRefresh: Boolean = false
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
_contractorsState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_contractorsState.value = contractorApi.getContractors(
|
||||
token = token,
|
||||
specialty = specialty,
|
||||
isFavorite = isFavorite,
|
||||
isActive = isActive,
|
||||
search = search
|
||||
)
|
||||
} else {
|
||||
_contractorsState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_contractorsState.value = APILayer.getContractors(
|
||||
specialty = specialty,
|
||||
isFavorite = isFavorite,
|
||||
isActive = isActive,
|
||||
search = search,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadContractorDetail(id: Int) {
|
||||
viewModelScope.launch {
|
||||
_contractorDetailState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_contractorDetailState.value = contractorApi.getContractor(token, id)
|
||||
} else {
|
||||
_contractorDetailState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_contractorDetailState.value = APILayer.getContractor(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun createContractor(request: ContractorCreateRequest) {
|
||||
viewModelScope.launch {
|
||||
_createState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_createState.value = contractorApi.createContractor(token, request)
|
||||
} else {
|
||||
_createState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_createState.value = APILayer.createContractor(request)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateContractor(id: Int, request: ContractorUpdateRequest) {
|
||||
viewModelScope.launch {
|
||||
_updateState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_updateState.value = contractorApi.updateContractor(token, id, request)
|
||||
} else {
|
||||
_updateState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_updateState.value = APILayer.updateContractor(id, request)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteContractor(id: Int) {
|
||||
viewModelScope.launch {
|
||||
_deleteState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_deleteState.value = contractorApi.deleteContractor(token, id)
|
||||
} else {
|
||||
_deleteState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_deleteState.value = APILayer.deleteContractor(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleFavorite(id: Int) {
|
||||
viewModelScope.launch {
|
||||
_toggleFavoriteState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_toggleFavoriteState.value = contractorApi.toggleFavorite(token, id)
|
||||
} else {
|
||||
_toggleFavoriteState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_toggleFavoriteState.value = APILayer.toggleFavorite(id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,13 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.mycrib.shared.models.*
|
||||
import com.mycrib.shared.network.ApiResult
|
||||
import com.mycrib.shared.network.DocumentApi
|
||||
import com.mycrib.storage.TokenStorage
|
||||
import com.mycrib.network.APILayer
|
||||
import com.mycrib.util.ImageCompressor
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class DocumentViewModel : ViewModel() {
|
||||
private val documentApi = DocumentApi()
|
||||
|
||||
private val _documentsState = MutableStateFlow<ApiResult<DocumentListResponse>>(ApiResult.Idle)
|
||||
val documentsState: StateFlow<ApiResult<DocumentListResponse>> = _documentsState
|
||||
@@ -43,38 +41,29 @@ class DocumentViewModel : ViewModel() {
|
||||
isActive: Boolean? = null,
|
||||
expiringSoon: Int? = null,
|
||||
tags: String? = null,
|
||||
search: String? = null
|
||||
search: String? = null,
|
||||
forceRefresh: Boolean = false
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
_documentsState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_documentsState.value = documentApi.getDocuments(
|
||||
token = token,
|
||||
residenceId = residenceId,
|
||||
documentType = documentType,
|
||||
category = category,
|
||||
contractorId = contractorId,
|
||||
isActive = isActive,
|
||||
expiringSoon = expiringSoon,
|
||||
tags = tags,
|
||||
search = search
|
||||
)
|
||||
} else {
|
||||
_documentsState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_documentsState.value = APILayer.getDocuments(
|
||||
residenceId = residenceId,
|
||||
documentType = documentType,
|
||||
category = category,
|
||||
contractorId = contractorId,
|
||||
isActive = isActive,
|
||||
expiringSoon = expiringSoon,
|
||||
tags = tags,
|
||||
search = search,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadDocumentDetail(id: Int) {
|
||||
viewModelScope.launch {
|
||||
_documentDetailState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_documentDetailState.value = documentApi.getDocument(token, id)
|
||||
} else {
|
||||
_documentDetailState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_documentDetailState.value = APILayer.getDocument(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,63 +94,57 @@ class DocumentViewModel : ViewModel() {
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
_createState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
// Compress images and convert to ByteArrays
|
||||
val fileBytesList = if (images.isNotEmpty()) {
|
||||
images.map { ImageCompressor.compressImage(it) }
|
||||
} else null
|
||||
// Compress images and convert to ByteArrays
|
||||
val fileBytesList = if (images.isNotEmpty()) {
|
||||
images.map { ImageCompressor.compressImage(it) }
|
||||
} else null
|
||||
|
||||
val fileNamesList = if (images.isNotEmpty()) {
|
||||
images.mapIndexed { index, image ->
|
||||
// Always use .jpg extension since we compress to JPEG
|
||||
val baseName = image.fileName.ifBlank { "image_$index" }
|
||||
if (baseName.endsWith(".jpg", ignoreCase = true) ||
|
||||
baseName.endsWith(".jpeg", ignoreCase = true)) {
|
||||
baseName
|
||||
} else {
|
||||
// Remove any existing extension and add .jpg
|
||||
baseName.substringBeforeLast('.', baseName) + ".jpg"
|
||||
}
|
||||
val fileNamesList = if (images.isNotEmpty()) {
|
||||
images.mapIndexed { index, image ->
|
||||
// Always use .jpg extension since we compress to JPEG
|
||||
val baseName = image.fileName.ifBlank { "image_$index" }
|
||||
if (baseName.endsWith(".jpg", ignoreCase = true) ||
|
||||
baseName.endsWith(".jpeg", ignoreCase = true)) {
|
||||
baseName
|
||||
} else {
|
||||
// Remove any existing extension and add .jpg
|
||||
baseName.substringBeforeLast('.', baseName) + ".jpg"
|
||||
}
|
||||
} else null
|
||||
}
|
||||
} else null
|
||||
|
||||
val mimeTypesList = if (images.isNotEmpty()) {
|
||||
images.map { "image/jpeg" }
|
||||
} else null
|
||||
val mimeTypesList = if (images.isNotEmpty()) {
|
||||
images.map { "image/jpeg" }
|
||||
} else null
|
||||
|
||||
_createState.value = documentApi.createDocument(
|
||||
token = token,
|
||||
title = title,
|
||||
documentType = documentType,
|
||||
residenceId = residenceId,
|
||||
description = description,
|
||||
category = category,
|
||||
tags = tags,
|
||||
notes = notes,
|
||||
contractorId = contractorId,
|
||||
isActive = isActive,
|
||||
itemName = itemName,
|
||||
modelNumber = modelNumber,
|
||||
serialNumber = serialNumber,
|
||||
provider = provider,
|
||||
providerContact = providerContact,
|
||||
claimPhone = claimPhone,
|
||||
claimEmail = claimEmail,
|
||||
claimWebsite = claimWebsite,
|
||||
purchaseDate = purchaseDate,
|
||||
startDate = startDate,
|
||||
endDate = endDate,
|
||||
fileBytes = null,
|
||||
fileName = null,
|
||||
mimeType = null,
|
||||
fileBytesList = fileBytesList,
|
||||
fileNamesList = fileNamesList,
|
||||
mimeTypesList = mimeTypesList
|
||||
)
|
||||
} else {
|
||||
_createState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_createState.value = APILayer.createDocument(
|
||||
title = title,
|
||||
documentType = documentType,
|
||||
residenceId = residenceId,
|
||||
description = description,
|
||||
category = category,
|
||||
tags = tags,
|
||||
notes = notes,
|
||||
contractorId = contractorId,
|
||||
isActive = isActive,
|
||||
itemName = itemName,
|
||||
modelNumber = modelNumber,
|
||||
serialNumber = serialNumber,
|
||||
provider = provider,
|
||||
providerContact = providerContact,
|
||||
claimPhone = claimPhone,
|
||||
claimEmail = claimEmail,
|
||||
claimWebsite = claimWebsite,
|
||||
purchaseDate = purchaseDate,
|
||||
startDate = startDate,
|
||||
endDate = endDate,
|
||||
fileBytes = null,
|
||||
fileName = null,
|
||||
mimeType = null,
|
||||
fileBytesList = fileBytesList,
|
||||
fileNamesList = fileNamesList,
|
||||
mimeTypesList = mimeTypesList
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,80 +175,73 @@ class DocumentViewModel : ViewModel() {
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
_updateState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
// First, update the document metadata
|
||||
val updateResult = documentApi.updateDocument(
|
||||
token = token,
|
||||
id = id,
|
||||
title = title,
|
||||
documentType = documentType,
|
||||
description = description,
|
||||
category = category,
|
||||
tags = tags,
|
||||
notes = notes,
|
||||
contractorId = contractorId,
|
||||
isActive = isActive,
|
||||
itemName = itemName,
|
||||
modelNumber = modelNumber,
|
||||
serialNumber = serialNumber,
|
||||
provider = provider,
|
||||
providerContact = providerContact,
|
||||
claimPhone = claimPhone,
|
||||
claimEmail = claimEmail,
|
||||
claimWebsite = claimWebsite,
|
||||
purchaseDate = purchaseDate,
|
||||
startDate = startDate,
|
||||
endDate = endDate
|
||||
)
|
||||
// First, update the document metadata
|
||||
val updateResult = APILayer.updateDocument(
|
||||
id = id,
|
||||
title = title,
|
||||
documentType = documentType,
|
||||
description = description,
|
||||
category = category,
|
||||
tags = tags,
|
||||
notes = notes,
|
||||
contractorId = contractorId,
|
||||
isActive = isActive,
|
||||
itemName = itemName,
|
||||
modelNumber = modelNumber,
|
||||
serialNumber = serialNumber,
|
||||
provider = provider,
|
||||
providerContact = providerContact,
|
||||
claimPhone = claimPhone,
|
||||
claimEmail = claimEmail,
|
||||
claimWebsite = claimWebsite,
|
||||
purchaseDate = purchaseDate,
|
||||
startDate = startDate,
|
||||
endDate = endDate
|
||||
)
|
||||
|
||||
// If update succeeded and there are new images, upload them
|
||||
if (updateResult is ApiResult.Success && images.isNotEmpty()) {
|
||||
var uploadFailed = false
|
||||
for ((index, image) in images.withIndex()) {
|
||||
// Compress the image
|
||||
val compressedBytes = ImageCompressor.compressImage(image)
|
||||
// If update succeeded and there are new images, upload them
|
||||
if (updateResult is ApiResult.Success && images.isNotEmpty()) {
|
||||
var uploadFailed = false
|
||||
for ((index, image) in images.withIndex()) {
|
||||
// Compress the image
|
||||
val compressedBytes = ImageCompressor.compressImage(image)
|
||||
|
||||
// Determine filename with .jpg extension
|
||||
val fileName = if (image.fileName.isNotBlank()) {
|
||||
val baseName = image.fileName
|
||||
if (baseName.endsWith(".jpg", ignoreCase = true) ||
|
||||
baseName.endsWith(".jpeg", ignoreCase = true)) {
|
||||
baseName
|
||||
} else {
|
||||
baseName.substringBeforeLast('.', baseName) + ".jpg"
|
||||
}
|
||||
// Determine filename with .jpg extension
|
||||
val fileName = if (image.fileName.isNotBlank()) {
|
||||
val baseName = image.fileName
|
||||
if (baseName.endsWith(".jpg", ignoreCase = true) ||
|
||||
baseName.endsWith(".jpeg", ignoreCase = true)) {
|
||||
baseName
|
||||
} else {
|
||||
"image_$index.jpg"
|
||||
baseName.substringBeforeLast('.', baseName) + ".jpg"
|
||||
}
|
||||
} else {
|
||||
"image_$index.jpg"
|
||||
}
|
||||
|
||||
val uploadResult = documentApi.uploadDocumentImage(
|
||||
token = token,
|
||||
documentId = id,
|
||||
imageBytes = compressedBytes,
|
||||
fileName = fileName,
|
||||
mimeType = "image/jpeg"
|
||||
val uploadResult = APILayer.uploadDocumentImage(
|
||||
documentId = id,
|
||||
imageBytes = compressedBytes,
|
||||
fileName = fileName,
|
||||
mimeType = "image/jpeg"
|
||||
)
|
||||
|
||||
if (uploadResult is ApiResult.Error) {
|
||||
uploadFailed = true
|
||||
_updateState.value = ApiResult.Error(
|
||||
"Document updated but failed to upload image: ${uploadResult.message}",
|
||||
uploadResult.code
|
||||
)
|
||||
|
||||
if (uploadResult is ApiResult.Error) {
|
||||
uploadFailed = true
|
||||
_updateState.value = ApiResult.Error(
|
||||
"Document updated but failed to upload image: ${uploadResult.message}",
|
||||
uploadResult.code
|
||||
)
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If all uploads succeeded, set success state
|
||||
if (!uploadFailed) {
|
||||
_updateState.value = updateResult
|
||||
}
|
||||
} else {
|
||||
// If all uploads succeeded, set success state
|
||||
if (!uploadFailed) {
|
||||
_updateState.value = updateResult
|
||||
}
|
||||
} else {
|
||||
_updateState.value = ApiResult.Error("Not authenticated", 401)
|
||||
_updateState.value = updateResult
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,24 +249,14 @@ class DocumentViewModel : ViewModel() {
|
||||
fun deleteDocument(id: Int) {
|
||||
viewModelScope.launch {
|
||||
_deleteState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_deleteState.value = documentApi.deleteDocument(token, id)
|
||||
} else {
|
||||
_deleteState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_deleteState.value = APILayer.deleteDocument(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadDocument(url: String) {
|
||||
viewModelScope.launch {
|
||||
_downloadState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_downloadState.value = documentApi.downloadDocument(token, url)
|
||||
} else {
|
||||
_downloadState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_downloadState.value = APILayer.downloadDocument(url)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,12 +279,7 @@ class DocumentViewModel : ViewModel() {
|
||||
fun deleteDocumentImage(imageId: Int) {
|
||||
viewModelScope.launch {
|
||||
_deleteImageState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_deleteImageState.value = documentApi.deleteDocumentImage(token, imageId)
|
||||
} else {
|
||||
_deleteImageState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_deleteImageState.value = APILayer.deleteDocumentImage(imageId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,25 +2,18 @@ package com.mycrib.android.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.mycrib.cache.DataCache
|
||||
import com.mycrib.cache.DataPrefetchManager
|
||||
import com.mycrib.shared.models.Residence
|
||||
import com.mycrib.shared.models.ResidenceCreateRequest
|
||||
import com.mycrib.shared.models.ResidenceSummaryResponse
|
||||
import com.mycrib.shared.models.MyResidencesResponse
|
||||
import com.mycrib.shared.models.TaskColumnsResponse
|
||||
import com.mycrib.shared.network.ApiResult
|
||||
import com.mycrib.shared.network.ResidenceApi
|
||||
import com.mycrib.shared.network.TaskApi
|
||||
import com.mycrib.storage.TokenStorage
|
||||
import com.mycrib.network.APILayer
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ResidenceViewModel : ViewModel() {
|
||||
private val residenceApi = ResidenceApi()
|
||||
private val taskApi = TaskApi()
|
||||
private val prefetchManager = DataPrefetchManager.getInstance()
|
||||
|
||||
private val _residencesState = MutableStateFlow<ApiResult<List<Residence>>>(ApiResult.Idle)
|
||||
val residencesState: StateFlow<ApiResult<List<Residence>>> = _residencesState
|
||||
@@ -61,68 +54,29 @@ class ResidenceViewModel : ViewModel() {
|
||||
*/
|
||||
fun loadResidences(forceRefresh: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
// Check if cache is initialized and we have data
|
||||
val cachedResidences = DataCache.residences.value
|
||||
if (!forceRefresh && cachedResidences.isNotEmpty()) {
|
||||
// Use cached data
|
||||
_residencesState.value = ApiResult.Success(cachedResidences)
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
_residencesState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
val result = residenceApi.getResidences(token)
|
||||
_residencesState.value = result
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateResidences(result.data)
|
||||
}
|
||||
} else {
|
||||
_residencesState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_residencesState.value = APILayer.getResidences(forceRefresh = forceRefresh)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadResidenceSummary() {
|
||||
viewModelScope.launch {
|
||||
_residenceSummaryState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_residenceSummaryState.value = residenceApi.getResidenceSummary(token)
|
||||
} else {
|
||||
_residenceSummaryState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_residenceSummaryState.value = APILayer.getResidenceSummary()
|
||||
}
|
||||
}
|
||||
|
||||
fun getResidence(id: Int, onResult: (ApiResult<Residence>) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
val result = residenceApi.getResidence(token, id)
|
||||
onResult(result)
|
||||
} else {
|
||||
onResult(ApiResult.Error("Not authenticated", 401))
|
||||
}
|
||||
val result = APILayer.getResidence(id)
|
||||
onResult(result)
|
||||
}
|
||||
}
|
||||
|
||||
fun createResidence(request: ResidenceCreateRequest) {
|
||||
viewModelScope.launch {
|
||||
_createResidenceState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
val result = residenceApi.createResidence(token, request)
|
||||
_createResidenceState.value = result
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.addResidence(result.data)
|
||||
}
|
||||
} else {
|
||||
_createResidenceState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_createResidenceState.value = APILayer.createResidence(request)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,29 +87,14 @@ class ResidenceViewModel : ViewModel() {
|
||||
fun loadResidenceTasks(residenceId: Int) {
|
||||
viewModelScope.launch {
|
||||
_residenceTasksState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_residenceTasksState.value = taskApi.getTasksByResidence(token, residenceId)
|
||||
} else {
|
||||
_residenceTasksState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_residenceTasksState.value = APILayer.getTasksByResidence(residenceId)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateResidence(residenceId: Int, request: ResidenceCreateRequest) {
|
||||
viewModelScope.launch {
|
||||
_updateResidenceState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
val result = residenceApi.updateResidence(token, residenceId, request)
|
||||
_updateResidenceState.value = result
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateResidence(result.data)
|
||||
}
|
||||
} else {
|
||||
_updateResidenceState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_updateResidenceState.value = APILayer.updateResidence(residenceId, request)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,61 +108,29 @@ class ResidenceViewModel : ViewModel() {
|
||||
|
||||
fun loadMyResidences(forceRefresh: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
// Check cache first
|
||||
val cachedData = DataCache.myResidences.value
|
||||
if (!forceRefresh && cachedData != null) {
|
||||
_myResidencesState.value = ApiResult.Success(cachedData)
|
||||
return@launch
|
||||
}
|
||||
|
||||
_myResidencesState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
val result = residenceApi.getMyResidences(token)
|
||||
_myResidencesState.value = result
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateMyResidences(result.data)
|
||||
}
|
||||
} else {
|
||||
_myResidencesState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_myResidencesState.value = APILayer.getMyResidences(forceRefresh = forceRefresh)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelTask(taskId: Int) {
|
||||
viewModelScope.launch {
|
||||
_cancelTaskState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_cancelTaskState.value = taskApi.cancelTask(token, taskId)
|
||||
} else {
|
||||
_cancelTaskState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_cancelTaskState.value = APILayer.cancelTask(taskId)
|
||||
}
|
||||
}
|
||||
|
||||
fun uncancelTask(taskId: Int) {
|
||||
viewModelScope.launch {
|
||||
_uncancelTaskState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_uncancelTaskState.value = taskApi.uncancelTask(token, taskId)
|
||||
} else {
|
||||
_uncancelTaskState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_uncancelTaskState.value = APILayer.uncancelTask(taskId)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTask(taskId: Int, request: com.mycrib.shared.models.TaskCreateRequest) {
|
||||
viewModelScope.launch {
|
||||
_updateTaskState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_updateTaskState.value = taskApi.updateTask(token, taskId, request)
|
||||
} else {
|
||||
_updateTaskState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_updateTaskState.value = APILayer.updateTask(taskId, request)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,12 +149,7 @@ class ResidenceViewModel : ViewModel() {
|
||||
fun generateTasksReport(residenceId: Int, email: String? = null) {
|
||||
viewModelScope.launch {
|
||||
_generateReportState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
_generateReportState.value = residenceApi.generateTasksReport(token, residenceId, email)
|
||||
} else {
|
||||
_generateReportState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_generateReportState.value = APILayer.generateTasksReport(residenceId, email)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,21 +160,25 @@ class ResidenceViewModel : ViewModel() {
|
||||
fun deleteResidence(residenceId: Int) {
|
||||
viewModelScope.launch {
|
||||
_deleteResidenceState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
val result = residenceApi.deleteResidence(token, residenceId)
|
||||
_deleteResidenceState.value = result
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.removeResidence(residenceId)
|
||||
}
|
||||
} else {
|
||||
_deleteResidenceState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_deleteResidenceState.value = APILayer.deleteResidence(residenceId)
|
||||
}
|
||||
}
|
||||
|
||||
fun resetDeleteResidenceState() {
|
||||
_deleteResidenceState.value = ApiResult.Idle
|
||||
}
|
||||
|
||||
private val _joinResidenceState = MutableStateFlow<ApiResult<com.mycrib.shared.models.JoinResidenceResponse>>(ApiResult.Idle)
|
||||
val joinResidenceState: StateFlow<ApiResult<com.mycrib.shared.models.JoinResidenceResponse>> = _joinResidenceState
|
||||
|
||||
fun joinWithCode(code: String) {
|
||||
viewModelScope.launch {
|
||||
_joinResidenceState.value = ApiResult.Loading
|
||||
_joinResidenceState.value = APILayer.joinWithCode(code)
|
||||
}
|
||||
}
|
||||
|
||||
fun resetJoinResidenceState() {
|
||||
_joinResidenceState.value = ApiResult.Idle
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,16 @@ package com.mycrib.android.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.mycrib.cache.DataCache
|
||||
import com.mycrib.cache.DataPrefetchManager
|
||||
import com.mycrib.shared.models.TaskColumnsResponse
|
||||
import com.mycrib.shared.models.CustomTask
|
||||
import com.mycrib.shared.models.TaskCreateRequest
|
||||
import com.mycrib.shared.network.ApiResult
|
||||
import com.mycrib.shared.network.TaskApi
|
||||
import com.mycrib.storage.TokenStorage
|
||||
import com.mycrib.network.APILayer
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class TaskViewModel : ViewModel() {
|
||||
private val taskApi = TaskApi()
|
||||
private val prefetchManager = DataPrefetchManager.getInstance()
|
||||
|
||||
private val _tasksState = MutableStateFlow<ApiResult<TaskColumnsResponse>>(ApiResult.Idle)
|
||||
val tasksState: StateFlow<ApiResult<TaskColumnsResponse>> = _tasksState
|
||||
@@ -30,51 +25,19 @@ class TaskViewModel : ViewModel() {
|
||||
fun loadTasks(forceRefresh: Boolean = false) {
|
||||
println("TaskViewModel: loadTasks called")
|
||||
viewModelScope.launch {
|
||||
// Check cache first
|
||||
val cachedTasks = DataCache.allTasks.value
|
||||
if (!forceRefresh && cachedTasks != null) {
|
||||
println("TaskViewModel: Using cached tasks")
|
||||
_tasksState.value = ApiResult.Success(cachedTasks)
|
||||
return@launch
|
||||
}
|
||||
|
||||
_tasksState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
val result = taskApi.getTasks(token)
|
||||
println("TaskViewModel: loadTasks result: $result")
|
||||
_tasksState.value = result
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateAllTasks(result.data)
|
||||
}
|
||||
} else {
|
||||
_tasksState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_tasksState.value = APILayer.getTasks(forceRefresh = forceRefresh)
|
||||
println("TaskViewModel: loadTasks result: ${_tasksState.value}")
|
||||
}
|
||||
}
|
||||
|
||||
fun loadTasksByResidence(residenceId: Int, forceRefresh: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
// Check cache first
|
||||
val cachedTasks = DataCache.tasksByResidence.value[residenceId]
|
||||
if (!forceRefresh && cachedTasks != null) {
|
||||
_tasksByResidenceState.value = ApiResult.Success(cachedTasks)
|
||||
return@launch
|
||||
}
|
||||
|
||||
_tasksByResidenceState.value = ApiResult.Loading
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
val result = taskApi.getTasksByResidence(token, residenceId)
|
||||
_tasksByResidenceState.value = result
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateTasksByResidence(residenceId, result.data)
|
||||
}
|
||||
} else {
|
||||
_tasksByResidenceState.value = ApiResult.Error("Not authenticated", 401)
|
||||
}
|
||||
_tasksByResidenceState.value = APILayer.getTasksByResidence(
|
||||
residenceId = residenceId,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,15 +46,9 @@ class TaskViewModel : ViewModel() {
|
||||
viewModelScope.launch {
|
||||
println("TaskViewModel: Setting state to Loading")
|
||||
_taskAddNewCustomTaskState.value = ApiResult.Loading
|
||||
try {
|
||||
val result = taskApi.createTask(TokenStorage.getToken()!!, request)
|
||||
println("TaskViewModel: API result: $result")
|
||||
_taskAddNewCustomTaskState.value = result
|
||||
} catch (e: Exception) {
|
||||
println("TaskViewModel: Exception: ${e.message}")
|
||||
e.printStackTrace()
|
||||
_taskAddNewCustomTaskState.value = ApiResult.Error(e.message ?: "Unknown error")
|
||||
}
|
||||
val result = APILayer.createTask(request)
|
||||
println("TaskViewModel: API result: $result")
|
||||
_taskAddNewCustomTaskState.value = result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,107 +57,98 @@ class TaskViewModel : ViewModel() {
|
||||
_taskAddNewCustomTaskState.value = ApiResult.Idle
|
||||
}
|
||||
|
||||
fun updateTask(taskId: Int, request: TaskCreateRequest, onComplete: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
when (val result = APILayer.updateTask(taskId, request)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelTask(taskId: Int, onComplete: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
when (val result = taskApi.cancelTask(token, taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
when (val result = APILayer.cancelTask(taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
} else {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun uncancelTask(taskId: Int, onComplete: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
when (val result = taskApi.uncancelTask(token, taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
when (val result = APILayer.uncancelTask(taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
} else {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun markInProgress(taskId: Int, onComplete: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
when (val result = taskApi.markInProgress(token, taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
when (val result = APILayer.markInProgress(taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
} else {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun archiveTask(taskId: Int, onComplete: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
when (val result = taskApi.archiveTask(token, taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
when (val result = APILayer.archiveTask(taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
} else {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unarchiveTask(taskId: Int, onComplete: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val token = TokenStorage.getToken()
|
||||
if (token != null) {
|
||||
when (val result = taskApi.unarchiveTask(token, taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
when (val result = APILayer.unarchiveTask(taskId)) {
|
||||
is ApiResult.Success -> {
|
||||
onComplete(true)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
onComplete(false)
|
||||
}
|
||||
else -> {
|
||||
onComplete(false)
|
||||
}
|
||||
} else {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user