import Foundation import UIKit @MainActor final class StubLeadDetailRemoteService: LeadDetailRemoteService { enum Scenario { case success, offline, timeout, http(Int), invalidFile } var detailScenario: Scenario = .success var fileScenario: Scenario = .success var delay: Duration private(set) var downloadCount = 0 private let leads: any LeadRemoteService init(leads: (any LeadRemoteService)? = nil, delay: Duration = .milliseconds(400)) { self.leads = leads ?? StubLeadRemoteService(delay: .zero) self.delay = delay } func fetchDetail(id: String, salesCode: String) async throws -> LeadDetailDTO { try await simulate(detailScenario) let lead = try await leads.fetchLead(id: id, salesCode: salesCode).toDomain() return LeadDetailDTO(detail: LeadDetailSampleData.make(for: lead)) } func download(_ reference: LeadFileReference, leadID: String, salesCode: String) async throws -> Data { downloadCount += 1 try await simulate(fileScenario) if case .invalidFile = fileScenario { return Data("not a valid file".utf8) } let lead = try await leads.fetchLead(id: leadID, salesCode: salesCode).toDomain() let detail = LeadDetailSampleData.make(for: lead) switch reference.kind { case .pdf: guard let document = detail.offers.flatMap(\.documents).first(where: { $0.file == reference }) else { throw LeadRemoteError.http(statusCode: 404) } return makePDF(document: document, customer: lead.displayName) case .image: guard let photo = detail.photos.first(where: { $0.thumbnail == reference || $0.original == reference }) else { throw LeadRemoteError.http(statusCode: 404) } return makeImage(photo: photo, thumbnail: reference == photo.thumbnail) } } private func simulate(_ scenario: Scenario) async throws { try await Task.sleep(for: delay) try Task.checkCancellation() switch scenario { case .success, .invalidFile: break case .offline: throw URLError(.notConnectedToInternet) case .timeout: throw URLError(.timedOut) case .http(let code): throw LeadRemoteError.http(statusCode: code) } } private func makePDF(document: LeadDocument, customer: String) -> Data { let renderer = UIGraphicsPDFRenderer(bounds: CGRect(x: 0, y: 0, width: 595, height: 842)) return renderer.pdfData { context in for page in 1...document.pageCount { context.beginPage() let title = document.title.replacingOccurrences(of: ".pdf", with: "") (title as NSString).draw(in: CGRect(x: 48, y: 56, width: 499, height: 100), withAttributes: [.font: UIFont.boldSystemFont(ofSize: 24)]) let body = """ DrachenGas – Beispieldokument \(customer) Seite \(page) von \(document.pageCount) Diese Datei dient ausschließlich zur Vorschau der Download- und PDF-Anzeige. Sie enthält keine verbindlichen Vertragsbedingungen. Bitte prüfen Sie alle Seiten vor der Unterschrift. """ (body as NSString).draw(in: CGRect(x: 48, y: 180, width: 499, height: 540), withAttributes: [.font: UIFont.systemFont(ofSize: 15), .foregroundColor: UIColor.darkGray]) if let placement = document.signaturePlacement, placement.pageIndex == page - 1 { let bounds = placement.bounds let field = CGRect(x: bounds.minX, y: 842 - bounds.maxY, width: bounds.width, height: bounds.height) UIColor.lightGray.setStroke() UIBezierPath(rect: field).stroke() ("Unterschrift Kunde" as NSString).draw( at: CGPoint(x: field.minX, y: field.maxY + 8), withAttributes: [.font: UIFont.systemFont(ofSize: 11), .foregroundColor: UIColor.darkGray] ) } } } } private func makeImage(photo: LeadPhoto, thumbnail: Bool) -> Data { let size = thumbnail ? CGSize(width: 360, height: 220) : CGSize(width: 1200, height: 800) let renderer = UIGraphicsImageRenderer(size: size) return renderer.pngData { context in UIColor.systemTeal.withAlphaComponent(0.14).setFill() context.fill(CGRect(origin: .zero, size: size)) let symbol = UIImage(systemName: photo.isAR ? "arkit" : "photo")? .withTintColor(.systemBlue, renderingMode: .alwaysOriginal) symbol?.draw(in: CGRect(x: size.width * 0.4, y: size.height * 0.2, width: size.width * 0.2, height: size.height * 0.35)) ("Beispiel · \(photo.title)" as NSString).draw( in: CGRect(x: 20, y: size.height * 0.72, width: size.width - 40, height: 60), withAttributes: [.font: UIFont.systemFont(ofSize: thumbnail ? 16 : 32), .foregroundColor: UIColor.darkGray] ) } } }