// // 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 = [] /// 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 } }