157 lines
6.6 KiB
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) }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|