112 lines
5.3 KiB
Swift
112 lines
5.3 KiB
Swift
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]
|
||
)
|
||
}
|
||
}
|
||
}
|