- 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>
260 lines
9.2 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|
|
}
|