Files
Gas-Tank-AR-App/GasTankAR/Views/Docuframe/DocuframeViewModel.swift
2026-09-02 20:54:36 +02:00

119 lines
3.6 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// DocuframeViewModel.swift
// GasTankAR
//
// Created by Dennis Nemec on 28.08.26.
//
import Foundation
import SwiftData
/// Präsentationslogik für den DOCUframe-Tab: verwaltet die Fotoauswahl und stößt Upload bzw.
/// Mailversand über den injizierten ``DocuframeService`` an. Kennt keine UI-Details.
@MainActor
@Observable
final class DocuframeViewModel {
/// Auswahl anhand der stabilen SwiftData-Identität der Fotos.
var selectedIDs: Set<PersistentIdentifier> = []
/// Läuft gerade ein Upload/Mailversand?
private(set) var isProcessing = false
/// Ergebnis des letzten Vorgangs für die Anzeige (nil = kein Hinweis).
var result: DocuframeActionResult?
@ObservationIgnored private let service: DocuframeService
/// Der Service wird injiziert (Default: Stub) – austauschbar für echte Anbindung oder Tests.
init(service: DocuframeService = StubDocuframeService()) {
self.service = service
}
var hasSelection: Bool { !selectedIDs.isEmpty }
var selectionCount: Int { selectedIDs.count }
func isSelected(_ photo: ProjectPhoto) -> Bool {
selectedIDs.contains(photo.persistentModelID)
}
func toggleSelection(_ photo: ProjectPhoto) {
let id = photo.persistentModelID
if selectedIDs.contains(id) {
selectedIDs.remove(id)
} else {
selectedIDs.insert(id)
}
}
func upload(from photos: [ProjectPhoto], leadID: String) async {
await perform(from: photos, leadID: leadID, successText: "in DOCUframe hochgeladen") {
try await service.uploadPhotos($0, projectLeadID: $1)
}
}
func sendEmail(from photos: [ProjectPhoto], leadID: String) async {
await perform(from: photos, leadID: leadID, successText: "per E-Mail versendet") {
try await service.sendEmail($0, projectLeadID: $1)
}
}
// MARK: - Gemeinsame Ausführung
private func perform(
from photos: [ProjectPhoto],
leadID: String,
successText: String,
action: ([DocuframePhoto], String) async throws -> Void
) async {
let selected = photos.filter { selectedIDs.contains($0.persistentModelID) }
guard !selected.isEmpty else {
result = .failure(message: DocuframeError.noPhotosSelected.localizedDescription)
return
}
isProcessing = true
defer { isProcessing = false }
let payload = selected.map(Self.makeDTO)
do {
try await action(payload, leadID)
result = .success(message: "\(selected.count) Foto(s) \(successText).")
} catch {
// Bei HTTP 4xx/5xx (und anderen Fehlern) den Nutzer informieren.
result = .failure(message: error.localizedDescription)
}
}
/// Bildet das Persistenz-Model auf ein persistenzunabhängiges Domänen-DTO ab.
private static func makeDTO(_ photo: ProjectPhoto) -> DocuframePhoto {
DocuframePhoto(
imageData: photo.imageData,
creationDate: photo.creationDate,
latitude: photo.latitude,
longitude: photo.longitude
)
}
}
/// Ergebnis eines DOCUframe-Vorgangs für die Anzeige (Erfolg oder Fehler).
enum DocuframeActionResult {
case success(message: String)
case failure(message: String)
var title: String {
switch self {
case .success: "Erledigt"
case .failure: "Fehler"
}
}
var message: String {
switch self {
case .success(let message), .failure(let message): message
}
}
var isError: Bool {
if case .failure = self { return true }
return false
}
}