Add 1-hour cache timeout and fix pull-to-refresh across iOS

- 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>
This commit is contained in:
Trey t
2025-12-03 09:50:57 -06:00
parent cf0cd1cda2
commit 63a54434ed
29 changed files with 1284 additions and 1230 deletions

View File

@@ -56,8 +56,6 @@ class DocumentViewModelWrapper: ObservableObject {
@Published var deleteState: DeleteState = DeleteStateIdle()
@Published var deleteImageState: DeleteImageState = DeleteImageStateIdle()
private let documentApi = DocumentApi(client: ApiClient_iosKt.createHttpClient())
func loadDocuments(
residenceId: Int32? = nil,
documentType: String? = nil,
@@ -68,29 +66,22 @@ class DocumentViewModelWrapper: ObservableObject {
tags: String? = nil,
search: String? = nil
) {
guard let token = TokenStorage.shared.getToken() else {
DispatchQueue.main.async {
self.documentsState = DocumentStateError(message: "Not authenticated")
}
return
}
DispatchQueue.main.async {
self.documentsState = DocumentStateLoading()
}
Task {
do {
let result = try await documentApi.getDocuments(
token: token,
residenceId: residenceId != nil ? KotlinInt(integerLiteral: Int(residenceId!)) : nil,
let result = try await APILayer.shared.getDocuments(
residenceId: residenceId != nil ? KotlinInt(int: residenceId!) : nil,
documentType: documentType,
category: category,
contractorId: contractorId != nil ? KotlinInt(integerLiteral: Int(contractorId!)) : nil,
contractorId: contractorId != nil ? KotlinInt(int: contractorId!) : nil,
isActive: isActive != nil ? KotlinBoolean(bool: isActive!) : nil,
expiringSoon: expiringSoon != nil ? KotlinInt(integerLiteral: Int(expiringSoon!)) : nil,
expiringSoon: expiringSoon != nil ? KotlinInt(int: expiringSoon!) : nil,
tags: tags,
search: search
search: search,
forceRefresh: false
)
await MainActor.run {
@@ -110,20 +101,13 @@ class DocumentViewModelWrapper: ObservableObject {
}
func loadDocumentDetail(id: Int32) {
guard let token = TokenStorage.shared.getToken() else {
DispatchQueue.main.async {
self.documentDetailState = DocumentDetailStateError(message: "Not authenticated")
}
return
}
DispatchQueue.main.async {
self.documentDetailState = DocumentDetailStateLoading()
}
Task {
do {
let result = try await documentApi.getDocument(token: token, id: id)
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 {
@@ -161,21 +145,13 @@ class DocumentViewModelWrapper: ObservableObject {
startDate: String? = nil,
endDate: String? = nil
) {
guard let token = TokenStorage.shared.getToken() else {
DispatchQueue.main.async {
self.updateState = UpdateStateError(message: "Not authenticated")
}
return
}
DispatchQueue.main.async {
self.updateState = UpdateStateLoading()
}
Task {
do {
let result = try await documentApi.updateDocument(
token: token,
let result = try await APILayer.shared.updateDocument(
id: id,
title: title,
documentType: documentType,
@@ -184,7 +160,7 @@ class DocumentViewModelWrapper: ObservableObject {
tags: tags,
notes: notes,
contractorId: nil,
isActive: KotlinBoolean(bool: isActive),
isActive: isActive,
itemName: itemName,
modelNumber: modelNumber,
serialNumber: serialNumber,
@@ -195,10 +171,7 @@ class DocumentViewModelWrapper: ObservableObject {
claimWebsite: claimWebsite,
purchaseDate: purchaseDate,
startDate: startDate,
endDate: endDate,
fileBytes: nil,
fileName: nil,
mimeType: nil
endDate: endDate
)
await MainActor.run {
@@ -219,20 +192,13 @@ class DocumentViewModelWrapper: ObservableObject {
}
func deleteDocument(id: Int32) {
guard let token = TokenStorage.shared.getToken() else {
DispatchQueue.main.async {
self.deleteState = DeleteStateError(message: "Not authenticated")
}
return
}
DispatchQueue.main.async {
self.deleteState = DeleteStateLoading()
}
Task {
do {
let result = try await documentApi.deleteDocument(token: token, id: id)
let result = try await APILayer.shared.deleteDocument(id: id)
await MainActor.run {
if result is ApiResultSuccess<KotlinUnit> {
@@ -262,20 +228,13 @@ class DocumentViewModelWrapper: ObservableObject {
}
func deleteDocumentImage(imageId: Int32) {
guard let token = TokenStorage.shared.getToken() else {
DispatchQueue.main.async {
self.deleteImageState = DeleteImageStateError(message: "Not authenticated")
}
return
}
DispatchQueue.main.async {
self.deleteImageState = DeleteImageStateLoading()
}
Task {
do {
let result = try await documentApi.deleteDocumentImage(token: token, imageId: imageId)
let result = try await APILayer.shared.deleteDocumentImage(imageId: imageId)
await MainActor.run {
if result is ApiResultSuccess<KotlinUnit> {