// // ProjectListModel.swift // Snail // import Foundation import Observation /// Постраничная загрузка списка проектов: следующая страница подгружается при прокрутке. @Observable final class ProjectListModel { private static let pageSize = 20 private(set) var projects: [Project] = [] private(set) var isLoading = false private(set) var hasLoaded = false private(set) var error: APIError? private var loadedPage = 0 private var totalPages = 1 private let service: ProjectService init(service: ProjectService = ProjectService()) { self.service = service } var hasMorePages: Bool { loadedPage < totalPages } /// Загружает первую страницу заново (первый показ, pull-to-refresh, «Повторить»). func reload() async { await load(page: 1) } /// Вызывается при появлении строки: если это последняя строка, грузим следующую страницу. func loadMoreIfNeeded(after project: Project) async { guard project.id == projects.last?.id, hasMorePages, !isLoading else { return } await load(page: loadedPage + 1) } func create(key: String, name: String) async throws { let project = try await service.create(key: key, name: name) projects.insert(project, at: 0) } func rename(_ project: Project, to name: String) async throws { let updated = try await service.update(id: project.id, name: name) if let index = projects.firstIndex(where: { $0.id == updated.id }) { projects[index] = updated } } private func load(page: Int) async { isLoading = true defer { isLoading = false } do { let result = try await service.list(page: page, limit: Self.pageSize) if page == 1 { projects = result.list } else { // Список мог сдвинуться между запросами — отбрасываем дубликаты. let known = Set(projects.map(\.id)) projects += result.list.filter { !known.contains($0.id) } } loadedPage = page totalPages = result.meta.totalPages error = nil } catch let apiError as APIError { error = apiError } catch { self.error = .invalidResponse } hasLoaded = true } }