Files
Gas-Tank-AR-App/GasTankAR/Views/Leads/Signing/LeadSigningSheet.swift

157 lines
6.6 KiB
Swift

import SwiftUI
import PencilKit
struct LeadSigningSheet: View {
@State private var model: LeadSigningViewModel
@State private var confirmDiscard = false
@Environment(\.dismiss) private var dismiss
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
let onSigned: (LeadDocument, URL) -> Void
init(request: LeadSigningRequest, leadID: String, salesCode: String,
repository: any LeadDetailRepository, onSigned: @escaping (LeadDocument, URL) -> Void) {
_model = State(initialValue: LeadSigningViewModel(
request: request, leadID: leadID, salesCode: salesCode, repository: repository
))
self.onSigned = onSigned
}
init(model: LeadSigningViewModel, onSigned: @escaping (LeadDocument, URL) -> Void) {
_model = State(initialValue: model)
self.onSigned = onSigned
}
var body: some View {
NavigationStack {
GeometryReader { geometry in
if geometry.size.width >= 700 && !dynamicTypeSize.isAccessibilitySize {
HStack(alignment: .top, spacing: 0) {
ScrollView {
LeadSigningDocumentListView(
documents: model.request.documents, currentID: model.currentDocument?.id,
signedIDs: Set(model.signedURLs.keys)
)
}
.frame(width: 240)
Divider()
ScrollView {
LeadSigningContentView(model: model, onSigned: onSigned)
.padding(16)
}
}
} else {
ScrollView {
VStack(spacing: 0) {
LeadSigningDocumentListView(
documents: model.request.documents, currentID: model.currentDocument?.id,
signedIDs: Set(model.signedURLs.keys)
)
LeadSigningContentView(model: model, onSigned: onSigned)
.padding(16)
}
}
}
}
.background(Color(uiColor: .systemGroupedBackground))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
LeadSigningHeaderView(
offerID: model.request.offerID, current: model.currentDocumentNumber,
total: model.request.documents.count, isComplete: model.isComplete,
isSigning: model.isBusy, canConfirm: model.canFinish
) {
if model.drawing.strokes.isEmpty { dismiss() } else { confirmDiscard = true }
}
}
}
.tint(LeadDetailStyle.blue)
.frame(idealWidth: 1000, idealHeight: 790)
.presentationSizing(.fitted)
.presentationCornerRadius(20)
.interactiveDismissDisabled(model.isBusy || !model.drawing.strokes.isEmpty)
.confirmationDialog("Unterschrift verwerfen?", isPresented: $confirmDiscard, titleVisibility: .visible) {
Button("Unterschrift verwerfen", role: .destructive) { dismiss() }
Button("Weiter unterschreiben", role: .cancel) {}
} message: {
Text("Bereits gespeicherte PDFs bleiben erhalten. Nur die aktuelle Eingabe wird verworfen.")
}
.task {
if model.sourceURL == nil { await model.loadCurrentDocument() }
}
}
}
#Preview("Unterschreiben", traits: .fixedLayout(width: 1000, height: 790), .modifier(LeadSigningPreviewData())) {
LeadSigningPreviewHost()
}
private struct LeadSigningPreviewHost: View {
@Environment(LeadSigningViewModel.self) private var model
var body: some View {
LeadSigningSheet(model: model, onSigned: { _, _ in })
}
}
private struct LeadSigningPreviewData: PreviewModifier {
static func makeSharedContext() async throws -> LeadSigningViewModel {
let repository = DefaultLeadDetailRepository(
remoteService: StubLeadDetailRemoteService(delay: .zero)
)
let detail = try await repository.detail(id: "L-240318", salesCode: "MKR")
guard let offer = detail.offers.first else { throw PDFSigningError.invalidDocument }
let request = LeadSigningRequest(offerID: offer.id, signerName: "Klaus Hoffmann",
documents: offer.documents)
let model = LeadSigningViewModel(request: request, leadID: detail.lead.id,
salesCode: "MKR", repository: repository)
await model.loadCurrentDocument()
return model
}
func body(content: Content, context: LeadSigningViewModel) -> some View {
content.environment(context)
}
}
private struct LeadSigningContentView: View {
@Bindable var model: LeadSigningViewModel
let onSigned: (LeadDocument, URL) -> Void
var body: some View {
VStack(spacing: 20) {
if let url = model.sourceURL {
LeadSigningPreviewView(
offerID: model.request.offerID, url: url,
placement: model.isComplete ? nil : model.currentDocument?.signaturePlacement
)
} else if model.isLoading {
ProgressView("PDF wird geladen …")
.frame(maxWidth: .infinity, minHeight: 310)
}
if model.showsCompletion {
LeadSigningCompletionView(model: model)
} else {
LeadSigningSignatureView(
signerName: model.request.signerName, drawing: $model.drawing,
isEnabled: model.sourceURL != nil && !model.isSigning
)
.padding(.top, 16)
if let message = model.message {
Text(message).font(.callout).foregroundStyle(.red)
if model.sourceURL == nil {
Button("Erneut versuchen") { Task { await model.loadCurrentDocument() } }
.disabled(model.isLoading)
}
}
LeadSigningFooterView(
title: model.currentDocument?.title.replacingOccurrences(of: ".pdf", with: "") ?? "",
canSign: model.canSign, isSigning: model.isSigning,
isLast: model.isLastPendingDocument
) {
Task { await model.signAndAdvance(onSigned: onSigned) }
}
}
}
}
}