diff --git a/iosApp/iosApp/Task/TaskViewModel.swift b/iosApp/iosApp/Task/TaskViewModel.swift index 312ddb7..dc7ed24 100644 --- a/iosApp/iosApp/Task/TaskViewModel.swift +++ b/iosApp/iosApp/Task/TaskViewModel.swift @@ -42,33 +42,24 @@ class TaskViewModel: ObservableObject { // MARK: - Initialization init() { - // Observe DataManagerObservable for all tasks data + // Single source of truth = DataManager._allTasks. When this VM is + // residence-scoped (currentResidenceId set), filter in-memory by + // residence id. Eliminates the gitea#2 race window where the + // per-residence cache slot could be empty while _allTasks was + // populated. Phase 3 deletes the per-residence cache entirely. DataManagerObservable.shared.$allTasks .receive(on: DispatchQueue.main) .sink { [weak self] allTasks in - // Skip DataManager updates during completion animation to prevent - // the task from being moved out of its column before the animation finishes - guard self?.isAnimatingCompletion != true else { return } - // Only update if we're showing all tasks (no residence filter) - if self?.currentResidenceId == nil { - self?.tasksResponse = allTasks - if allTasks != nil { - self?.isLoadingTasks = false - } - } - } - .store(in: &cancellables) + guard let self else { return } + guard !self.isAnimatingCompletion else { return } - // Observe tasks by residence - DataManagerObservable.shared.$tasksByResidence - .receive(on: DispatchQueue.main) - .sink { [weak self] tasksByResidence in - guard self?.isAnimatingCompletion != true else { return } - // Only update if we're filtering by residence - if let resId = self?.currentResidenceId, - let tasks = tasksByResidence[resId] { - self?.tasksResponse = tasks - self?.isLoadingTasks = false + if let allTasks { + if let resId = self.currentResidenceId { + self.tasksResponse = self.filterTasks(allTasks, residenceId: resId) + } else { + self.tasksResponse = allTasks + } + self.isLoadingTasks = false } } .store(in: &cancellables) @@ -382,6 +373,28 @@ class TaskViewModel: ObservableObject { } } + /// Filter the all-tasks kanban down to a single residence in-memory. + /// Mirrors `DataManager.getTasksForResidence` on the Kotlin side. + private func filterTasks(_ response: TaskColumnsResponse, residenceId: Int32) -> TaskColumnsResponse { + let filteredColumns = response.columns.map { column -> TaskColumn in + let filteredTasks = column.tasks.filter { Int32($0.residenceId) == residenceId } + return TaskColumn( + name: column.name, + displayName: column.displayName, + buttonTypes: column.buttonTypes, + icons: column.icons, + color: column.color, + tasks: filteredTasks, + count: Int32(filteredTasks.count) + ) + } + return TaskColumnsResponse( + columns: filteredColumns, + daysThreshold: response.daysThreshold, + residenceId: String(residenceId) + ) + } + /// Updates a task in the kanban board by moving it to the correct column based on kanban_column func updateTaskInKanban(_ updatedTask: TaskResponse) { guard let currentResponse = tasksResponse else { return }