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) } } } } } }