Add lead workflows, PDF signing, AR projects and photo gallery integration
This commit is contained in:
111
GasTankAR/Data/Leads/StubLeadDetailRemoteService.swift
Normal file
111
GasTankAR/Data/Leads/StubLeadDetailRemoteService.swift
Normal file
@ -0,0 +1,111 @@
|
||||
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]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user