android: ResidenceViewModel.residenceTasksState derives from _allTasks

Same screen contract, but the data flows from DataManager.allTasks
through a combine(_allTasks, _currentResidenceId) into the existing
StateFlow. No per-residence network call needed; the upstream
getTasks() refresh propagates and the screen re-renders.

Eliminates the gitea#2 race window on Android — same fix as the iOS
TaskViewModel commit. Both platforms now react to _allTasks changes
without manual refresh.
This commit is contained in:
Trey t
2026-04-25 10:44:53 -05:00
parent ce25c80783
commit 1b001323e4
@@ -2,6 +2,7 @@ package com.tt.honeyDue.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tt.honeyDue.data.DataManager
import com.tt.honeyDue.models.Residence
import com.tt.honeyDue.models.ResidenceCreateRequest
import com.tt.honeyDue.models.TotalSummary
@@ -11,7 +12,10 @@ import com.tt.honeyDue.models.ContractorSummary
import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.network.APILayer
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class ResidenceViewModel : ViewModel() {
@@ -28,8 +32,24 @@ class ResidenceViewModel : ViewModel() {
private val _updateResidenceState = MutableStateFlow<ApiResult<Residence>>(ApiResult.Idle)
val updateResidenceState: StateFlow<ApiResult<Residence>> = _updateResidenceState
private val _residenceTasksState = MutableStateFlow<ApiResult<TaskColumnsResponse>>(ApiResult.Idle)
val residenceTasksState: StateFlow<ApiResult<TaskColumnsResponse>> = _residenceTasksState
/// Residence-scoped kanban derived from `DataManager.allTasks` filtered
/// by `_currentResidenceId`. Re-emits whenever either upstream changes,
/// so the residence detail screen reacts to new tasks (created or
/// completed elsewhere) without manual refresh. Replaces the previous
/// imperative `_residenceTasksState` that was only written by
/// loadResidenceTasks's API result and stayed stale otherwise.
private val _currentResidenceId = MutableStateFlow<Int?>(null)
val residenceTasksState: StateFlow<ApiResult<TaskColumnsResponse>> =
combine(DataManager.allTasks, _currentResidenceId) { all, id ->
when {
id == null -> ApiResult.Idle
all == null -> ApiResult.Loading
else -> {
val filtered = DataManager.getTasksForResidence(id)
if (filtered != null) ApiResult.Success(filtered) else ApiResult.Loading
}
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), ApiResult.Idle)
private val _myResidencesState = MutableStateFlow<ApiResult<MyResidencesResponse>>(ApiResult.Idle)
val myResidencesState: StateFlow<ApiResult<MyResidencesResponse>> = _myResidencesState
@@ -85,13 +105,16 @@ class ResidenceViewModel : ViewModel() {
}
fun resetResidenceTasksState() {
_residenceTasksState.value = ApiResult.Idle
_currentResidenceId.value = null
}
fun loadResidenceTasks(residenceId: Int) {
fun loadResidenceTasks(residenceId: Int, forceRefresh: Boolean = false) {
_currentResidenceId.value = residenceId
// Trigger an _allTasks refresh in the background. The combine flow
// above re-emits Success when allTasks lands, so the screen
// re-renders without needing the result here.
viewModelScope.launch {
_residenceTasksState.value = ApiResult.Loading
_residenceTasksState.value = APILayer.getTasksByResidence(residenceId)
APILayer.getTasks(forceRefresh = forceRefresh)
}
}