- Add configurable cache timeout (CACHE_TIMEOUT_MS) to DataManager - Fix cache to work with empty results (contractors, documents, residences) - Change Documents/Warranties view to use client-side filtering for cache efficiency - Add pull-to-refresh support for empty state views in ListAsyncContentView - Fix ContractorsListView to pass forceRefresh parameter correctly - Fix TaskViewModel loading spinner not stopping after refresh completes - Remove duplicate cache checks in iOS ViewModels, delegate to Kotlin APILayer 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
260 lines
8.9 KiB
Swift
260 lines
8.9 KiB
Swift
import Foundation
|
|
import ComposeApp
|
|
import SwiftUI
|
|
|
|
// State wrappers for SwiftUI
|
|
protocol DocumentState {}
|
|
struct DocumentStateIdle: DocumentState {}
|
|
struct DocumentStateLoading: DocumentState {}
|
|
struct DocumentStateSuccess: DocumentState {
|
|
let documents: [Document]
|
|
}
|
|
struct DocumentStateError: DocumentState {
|
|
let message: String
|
|
}
|
|
|
|
protocol DocumentDetailState {}
|
|
struct DocumentDetailStateIdle: DocumentDetailState {}
|
|
struct DocumentDetailStateLoading: DocumentDetailState {}
|
|
struct DocumentDetailStateSuccess: DocumentDetailState {
|
|
let document: Document
|
|
}
|
|
struct DocumentDetailStateError: DocumentDetailState {
|
|
let message: String
|
|
}
|
|
|
|
protocol UpdateState {}
|
|
struct UpdateStateIdle: UpdateState {}
|
|
struct UpdateStateLoading: UpdateState {}
|
|
struct UpdateStateSuccess: UpdateState {
|
|
let document: Document
|
|
}
|
|
struct UpdateStateError: UpdateState {
|
|
let message: String
|
|
}
|
|
|
|
protocol DeleteState {}
|
|
struct DeleteStateIdle: DeleteState {}
|
|
struct DeleteStateLoading: DeleteState {}
|
|
struct DeleteStateSuccess: DeleteState {}
|
|
struct DeleteStateError: DeleteState {
|
|
let message: String
|
|
}
|
|
|
|
protocol DeleteImageState {}
|
|
struct DeleteImageStateIdle: DeleteImageState {}
|
|
struct DeleteImageStateLoading: DeleteImageState {}
|
|
struct DeleteImageStateSuccess: DeleteImageState {}
|
|
struct DeleteImageStateError: DeleteImageState {
|
|
let message: String
|
|
}
|
|
|
|
class DocumentViewModelWrapper: ObservableObject {
|
|
@Published var documentsState: DocumentState = DocumentStateIdle()
|
|
@Published var documentDetailState: DocumentDetailState = DocumentDetailStateIdle()
|
|
@Published var updateState: UpdateState = UpdateStateIdle()
|
|
@Published var deleteState: DeleteState = DeleteStateIdle()
|
|
@Published var deleteImageState: DeleteImageState = DeleteImageStateIdle()
|
|
|
|
func loadDocuments(
|
|
residenceId: Int32? = nil,
|
|
documentType: String? = nil,
|
|
category: String? = nil,
|
|
contractorId: Int32? = nil,
|
|
isActive: Bool? = nil,
|
|
expiringSoon: Int32? = nil,
|
|
tags: String? = nil,
|
|
search: String? = nil
|
|
) {
|
|
DispatchQueue.main.async {
|
|
self.documentsState = DocumentStateLoading()
|
|
}
|
|
|
|
Task {
|
|
do {
|
|
let result = try await APILayer.shared.getDocuments(
|
|
residenceId: residenceId != nil ? KotlinInt(int: residenceId!) : nil,
|
|
documentType: documentType,
|
|
category: category,
|
|
contractorId: contractorId != nil ? KotlinInt(int: contractorId!) : nil,
|
|
isActive: isActive != nil ? KotlinBoolean(bool: isActive!) : nil,
|
|
expiringSoon: expiringSoon != nil ? KotlinInt(int: expiringSoon!) : nil,
|
|
tags: tags,
|
|
search: search,
|
|
forceRefresh: false
|
|
)
|
|
|
|
await MainActor.run {
|
|
if let success = result as? ApiResultSuccess<NSArray> {
|
|
let documents = success.data as? [Document] ?? []
|
|
self.documentsState = DocumentStateSuccess(documents: documents)
|
|
} else if let error = result as? ApiResultError {
|
|
self.documentsState = DocumentStateError(message: error.message)
|
|
}
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
self.documentsState = DocumentStateError(message: error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func loadDocumentDetail(id: Int32) {
|
|
DispatchQueue.main.async {
|
|
self.documentDetailState = DocumentDetailStateLoading()
|
|
}
|
|
|
|
Task {
|
|
do {
|
|
let result = try await APILayer.shared.getDocument(id: id, forceRefresh: false)
|
|
|
|
await MainActor.run {
|
|
if let success = result as? ApiResultSuccess<Document>, let document = success.data {
|
|
self.documentDetailState = DocumentDetailStateSuccess(document: document)
|
|
} else if let error = result as? ApiResultError {
|
|
self.documentDetailState = DocumentDetailStateError(message: error.message)
|
|
}
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
self.documentDetailState = DocumentDetailStateError(message: error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateDocument(
|
|
id: Int32,
|
|
title: String,
|
|
documentType: String,
|
|
description: String? = nil,
|
|
category: String? = nil,
|
|
tags: String? = nil,
|
|
notes: String? = nil,
|
|
isActive: Bool = true,
|
|
itemName: String? = nil,
|
|
modelNumber: String? = nil,
|
|
serialNumber: String? = nil,
|
|
provider: String? = nil,
|
|
providerContact: String? = nil,
|
|
claimPhone: String? = nil,
|
|
claimEmail: String? = nil,
|
|
claimWebsite: String? = nil,
|
|
purchaseDate: String? = nil,
|
|
startDate: String? = nil,
|
|
endDate: String? = nil
|
|
) {
|
|
DispatchQueue.main.async {
|
|
self.updateState = UpdateStateLoading()
|
|
}
|
|
|
|
Task {
|
|
do {
|
|
let result = try await APILayer.shared.updateDocument(
|
|
id: id,
|
|
title: title,
|
|
documentType: documentType,
|
|
description: description,
|
|
category: category,
|
|
tags: tags,
|
|
notes: notes,
|
|
contractorId: nil,
|
|
isActive: isActive,
|
|
itemName: itemName,
|
|
modelNumber: modelNumber,
|
|
serialNumber: serialNumber,
|
|
provider: provider,
|
|
providerContact: providerContact,
|
|
claimPhone: claimPhone,
|
|
claimEmail: claimEmail,
|
|
claimWebsite: claimWebsite,
|
|
purchaseDate: purchaseDate,
|
|
startDate: startDate,
|
|
endDate: endDate
|
|
)
|
|
|
|
await MainActor.run {
|
|
if let success = result as? ApiResultSuccess<Document>, let document = success.data {
|
|
self.updateState = UpdateStateSuccess(document: document)
|
|
// Also refresh the detail state
|
|
self.documentDetailState = DocumentDetailStateSuccess(document: document)
|
|
} else if let error = result as? ApiResultError {
|
|
self.updateState = UpdateStateError(message: error.message)
|
|
}
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
self.updateState = UpdateStateError(message: error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func deleteDocument(id: Int32) {
|
|
DispatchQueue.main.async {
|
|
self.deleteState = DeleteStateLoading()
|
|
}
|
|
|
|
Task {
|
|
do {
|
|
let result = try await APILayer.shared.deleteDocument(id: id)
|
|
|
|
await MainActor.run {
|
|
if result is ApiResultSuccess<KotlinUnit> {
|
|
self.deleteState = DeleteStateSuccess()
|
|
} else if let error = result as? ApiResultError {
|
|
self.deleteState = DeleteStateError(message: error.message)
|
|
}
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
self.deleteState = DeleteStateError(message: error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func resetUpdateState() {
|
|
DispatchQueue.main.async {
|
|
self.updateState = UpdateStateIdle()
|
|
}
|
|
}
|
|
|
|
func resetDeleteState() {
|
|
DispatchQueue.main.async {
|
|
self.deleteState = DeleteStateIdle()
|
|
}
|
|
}
|
|
|
|
func deleteDocumentImage(imageId: Int32) {
|
|
DispatchQueue.main.async {
|
|
self.deleteImageState = DeleteImageStateLoading()
|
|
}
|
|
|
|
Task {
|
|
do {
|
|
let result = try await APILayer.shared.deleteDocumentImage(imageId: imageId)
|
|
|
|
await MainActor.run {
|
|
if result is ApiResultSuccess<KotlinUnit> {
|
|
self.deleteImageState = DeleteImageStateSuccess()
|
|
} else if let error = result as? ApiResultError {
|
|
self.deleteImageState = DeleteImageStateError(message: error.message)
|
|
}
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
self.deleteImageState = DeleteImageStateError(message: error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func resetDeleteImageState() {
|
|
DispatchQueue.main.async {
|
|
self.deleteImageState = DeleteImageStateIdle()
|
|
}
|
|
}
|
|
}
|