Files
honeyDueKMP/iosApp/iosApp/Documents/DocumentViewModel.swift
Trey t 258ccf7354 Improve error message handling with user-friendly messages
- Add ErrorMessageParser in Kotlin and Swift to detect network errors
  and technical messages, replacing them with human-readable text
- Update all ViewModels to use ErrorMessageParser.parse() for error display
- Remove redundant error popup from LoginView (error shows inline only)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-11 20:46:43 -06:00

260 lines
9.2 KiB
Swift

import Foundation
import UIKit
import ComposeApp
import Combine
/// ViewModel for document management.
/// Observes DataManagerObservable for documents list.
/// Calls APILayer directly for all operations.
@MainActor
class DocumentViewModel: ObservableObject {
@Published var documents: [Document] = []
@Published var isLoading = false
@Published var errorMessage: String?
// MARK: - Private Properties
private var cancellables = Set<AnyCancellable>()
init() {
// Observe documents from DataManagerObservable
DataManagerObservable.shared.$documents
.receive(on: DispatchQueue.main)
.sink { [weak self] documents in
self?.documents = documents
}
.store(in: &cancellables)
}
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,
forceRefresh: Bool = false
) {
isLoading = true
errorMessage = nil
Task {
do {
let result = try await APILayer.shared.getDocuments(
residenceId: residenceId.asKotlin,
documentType: documentType,
category: category,
contractorId: contractorId.asKotlin,
isActive: isActive.asKotlin,
expiringSoon: expiringSoon.asKotlin,
tags: tags,
search: search,
forceRefresh: forceRefresh
)
// API updates DataManager on success, which triggers our observation
if result is ApiResultSuccess<NSArray> {
self.isLoading = false
} else if let error = result as? ApiResultError {
self.errorMessage = ErrorMessageParser.parse(error.message)
self.isLoading = false
}
} catch {
self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
self.isLoading = false
}
}
}
func createDocument(
title: String,
documentType: String,
residenceId: Int32,
description: String? = nil,
category: String? = nil,
tags: String? = nil,
notes: String? = nil,
contractorId: Int32? = 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,
images: [UIImage] = [],
completion: @escaping (Bool, String?) -> Void
) {
isLoading = true
errorMessage = nil
Task {
do {
let result = try await APILayer.shared.createDocument(
title: title,
documentType: documentType,
residenceId: residenceId,
description: description,
category: category,
tags: tags,
notes: notes,
contractorId: contractorId.asKotlin,
isActive: isActive,
itemName: itemName,
modelNumber: modelNumber,
serialNumber: serialNumber,
provider: provider,
providerContact: providerContact,
claimPhone: claimPhone,
claimEmail: claimEmail,
claimWebsite: claimWebsite,
purchaseDate: purchaseDate,
startDate: startDate,
endDate: endDate,
fileBytes: nil,
fileName: nil,
mimeType: nil,
fileBytesList: nil,
fileNamesList: nil,
mimeTypesList: nil
)
if result is ApiResultSuccess<Document> {
self.isLoading = false
// DataManager is updated by APILayer, view updates via observation
completion(true, nil)
} else if let error = result as? ApiResultError {
self.errorMessage = ErrorMessageParser.parse(error.message)
self.isLoading = false
completion(false, self.errorMessage)
}
} catch {
self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
self.isLoading = false
completion(false, self.errorMessage)
}
}
}
func updateDocument(
id: Int32,
title: String,
description: String? = nil,
category: String? = nil,
tags: String? = nil,
notes: String? = nil,
contractorId: Int32? = 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,
newImages: [UIImage] = [],
completion: @escaping (Bool, String?) -> Void
) {
isLoading = true
errorMessage = nil
Task {
do {
let result = try await APILayer.shared.updateDocument(
id: id,
title: title,
documentType: "", // Required but not changing
description: description,
category: category,
tags: tags,
notes: notes,
contractorId: contractorId.asKotlin,
isActive: isActive,
itemName: itemName,
modelNumber: modelNumber,
serialNumber: serialNumber,
provider: provider,
providerContact: providerContact,
claimPhone: claimPhone,
claimEmail: claimEmail,
claimWebsite: claimWebsite,
purchaseDate: purchaseDate,
startDate: startDate,
endDate: endDate
)
if result is ApiResultSuccess<Document> {
self.isLoading = false
// DataManager is updated by APILayer, view updates via observation
completion(true, nil)
} else if let error = result as? ApiResultError {
self.errorMessage = ErrorMessageParser.parse(error.message)
self.isLoading = false
completion(false, self.errorMessage)
}
} catch {
self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
self.isLoading = false
completion(false, self.errorMessage)
}
}
}
func deleteDocument(id: Int32, completion: @escaping (Bool) -> Void = { _ in }) {
isLoading = true
errorMessage = nil
Task {
do {
let result = try await APILayer.shared.deleteDocument(id: id)
if result is ApiResultSuccess<KotlinUnit> {
self.isLoading = false
// DataManager is updated by APILayer, view updates via observation
completion(true)
} else if let error = result as? ApiResultError {
self.errorMessage = ErrorMessageParser.parse(error.message)
self.isLoading = false
completion(false)
}
} catch {
self.errorMessage = ErrorMessageParser.parse(error.localizedDescription)
self.isLoading = false
completion(false)
}
}
}
func downloadDocument(url: String) -> Task<Data?, Error> {
return Task {
do {
let result = try await APILayer.shared.downloadDocument(url: url)
if let success = result as? ApiResultSuccess<KotlinByteArray>, let byteArray = success.data {
// Convert Kotlin ByteArray to Swift Data
var data = Data()
for i in 0..<byteArray.size {
data.append(UInt8(bitPattern: byteArray.get(index: i)))
}
return data
} else if let error = result as? ApiResultError {
throw NSError(domain: error.message, code: error.code?.intValue ?? 0)
}
return nil
} catch {
throw error
}
}
}
}