Add document viewing, editing, and image deletion features
- Add DocumentDetailScreen and EditDocumentScreen for Compose (Android/Web) - Add DocumentDetailView and EditDocumentView for iOS SwiftUI - Add DocumentViewModelWrapper for iOS state management - Implement document image deletion API integration - Fix iOS navigation issues with edit button using hidden NavigationLink - Add clickable warranties in iOS with NavigationLink - Fix iOS build errors with proper type checking and state handling - Add support for viewing and managing warranty-specific fields 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
300
iosApp/iosApp/Documents/DocumentViewModelWrapper.swift
Normal file
300
iosApp/iosApp/Documents/DocumentViewModelWrapper.swift
Normal file
@@ -0,0 +1,300 @@
|
||||
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()
|
||||
|
||||
private let documentApi = DocumentApi(client: ApiClient_iosKt.createHttpClient())
|
||||
|
||||
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
|
||||
) {
|
||||
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,
|
||||
documentType: documentType,
|
||||
category: category,
|
||||
contractorId: contractorId != nil ? KotlinInt(integerLiteral: Int(contractorId!)) : nil,
|
||||
isActive: isActive != nil ? KotlinBoolean(bool: isActive!) : nil,
|
||||
expiringSoon: expiringSoon != nil ? KotlinInt(integerLiteral: Int(expiringSoon!)) : nil,
|
||||
tags: tags,
|
||||
search: search
|
||||
)
|
||||
|
||||
await MainActor.run {
|
||||
if let success = result as? ApiResultSuccess<DocumentListResponse> {
|
||||
let documents = success.data?.results 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) {
|
||||
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)
|
||||
|
||||
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
|
||||
) {
|
||||
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,
|
||||
id: id,
|
||||
title: title,
|
||||
documentType: documentType,
|
||||
description: description,
|
||||
category: category,
|
||||
tags: tags,
|
||||
notes: notes,
|
||||
contractorId: nil,
|
||||
isActive: KotlinBoolean(bool: 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
|
||||
)
|
||||
|
||||
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) {
|
||||
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)
|
||||
|
||||
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) {
|
||||
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)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user