Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3cdf7df
commit b02f0da
Showing
10 changed files
with
385 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
// | ||
// This source file is part of the CardinalKit open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
import PencilKit | ||
import SwiftUI | ||
import Views | ||
|
||
|
||
/// The ``ConsentView`` allows the display of markdown-based documents that can be signed using a family and given name and a hand drawn signature. | ||
/// | ||
/// The ``ConsentView`` provides a convenience initalizer with a provided action view using an ``OnboardingActionsView`` (``ConsentView/init(header:asyncMarkdown:footer:action:)``) | ||
/// or a more customized ``ConsentView/init(contentView:actionView:)`` initializer with a custom-provided content and action view. | ||
/// | ||
/// ``` | ||
/// ConsentView( | ||
/// asyncMarkdown: { | ||
/// Data("This is a *markdown* **example**".utf8) | ||
/// }, | ||
/// action: { | ||
/// // The action that should be performed once the user has providided their consent. | ||
/// } | ||
/// ) | ||
/// ``` | ||
public struct ConsentView<ContentView: View, Action: View>: View { | ||
private let contentView: ContentView | ||
private let action: Action | ||
@State private var name = PersonNameComponents() | ||
@State private var showSignatureView = false | ||
@State private var isSigning = false | ||
@State private var signature = PKDrawing() | ||
|
||
|
||
public var body: some View { | ||
ScrollViewReader { proxy in | ||
OnboardingView( | ||
contentView: { | ||
contentView | ||
}, | ||
actionView: { | ||
VStack { | ||
Divider() | ||
NameFields(name: $name) | ||
if showSignatureView { | ||
Divider() | ||
SignatureView(signature: $signature, isSigning: $isSigning, name: name) | ||
.padding(.vertical, 4) | ||
} | ||
Divider() | ||
action | ||
.disabled(buttonDisabled) | ||
.animation(.easeInOut, value: buttonDisabled) | ||
.id("ActionButton") | ||
.onChange(of: showSignatureView) { _ in | ||
proxy.scrollTo("ActionButton") | ||
} | ||
} | ||
.transition(.opacity) | ||
.animation(.easeInOut, value: showSignatureView) | ||
} | ||
) | ||
.scrollDisabled(isSigning) | ||
} | ||
} | ||
|
||
var buttonDisabled: Bool { | ||
let showSignatureView = !(name.givenName?.isEmpty ?? true) && !(name.familyName?.isEmpty ?? true) | ||
if !self.showSignatureView && showSignatureView { | ||
Task { @MainActor in | ||
self.showSignatureView = showSignatureView | ||
} | ||
} | ||
|
||
return signature.strokes.isEmpty || (name.givenName?.isEmpty ?? true) || (name.familyName?.isEmpty ?? true) | ||
} | ||
|
||
|
||
/// Creates a ``ConsentView`` with a provided action view using an``OnboardingActionsView`` and renders a markdown view. | ||
/// - Parameters: | ||
/// - header: The header view will be displayed above the markdown content. | ||
/// - asyncMarkdown: The markdown content provided as an UTF8 encoded `Data` instance that can be provided asynchronously. | ||
/// - footer: The footer view will be displayed above the markdown content. | ||
/// - action: The action that should be performed once the consent has been given. | ||
public init( | ||
@ViewBuilder header: () -> (some View) = { EmptyView() }, | ||
asyncMarkdown: @escaping () async -> Data, | ||
@ViewBuilder footer: () -> (some View) = { EmptyView() }, | ||
action: @escaping () -> Void | ||
) where ContentView == MarkdownView<AnyView, AnyView>, Action == OnboardingActionsView { | ||
self.init( | ||
contentView: { | ||
MarkdownView( | ||
asyncMarkdown: asyncMarkdown, | ||
header: { AnyView(header()) }, | ||
footer: { AnyView(footer()) } | ||
) | ||
}, | ||
actionView: { | ||
OnboardingActionsView(String(localized: "CONSENT_ACTION", bundle: .module)) { | ||
action() | ||
} | ||
} | ||
) | ||
} | ||
|
||
/// Creates a ``ConsentView`` with a custom-provided action view. | ||
/// - Parameters: | ||
/// - contentView: The content view providing context about the consent view. | ||
/// - actionView: The action view that should be displayed under the name and signature boxes. | ||
public init( | ||
@ViewBuilder contentView: () -> (ContentView), | ||
@ViewBuilder actionView: () -> (Action) | ||
) { | ||
self.contentView = contentView() | ||
self.action = actionView() | ||
} | ||
} | ||
|
||
|
||
struct ConsentView_Previews: PreviewProvider { | ||
static var previews: some View { | ||
NavigationStack { | ||
ConsentView( | ||
asyncMarkdown: { | ||
Data("This is a *markdown* **example**".utf8) | ||
}, | ||
action: { | ||
print("Next step ...") | ||
} | ||
) | ||
.navigationTitle("Consent") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// | ||
// This source file is part of the CardinalKit open-source project | ||
// | ||
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) | ||
// | ||
// SPDX-License-Identifier: MIT | ||
// | ||
|
||
import PencilKit | ||
import SwiftUI | ||
import Views | ||
|
||
|
||
/// A ``SignatureView`` enables the collection of signatures using a view that allows freeform signatures using a finger and the Apple Pencil. | ||
/// | ||
/// Use SwiftUI `Bindings` to obtain information like the content of the signature and if the user is currently signing: | ||
/// ``` | ||
/// @State var signature = PKDrawing() | ||
/// @State var isSigning = false | ||
/// | ||
/// | ||
/// SignatureView( | ||
/// signature: $signature, | ||
/// isSigning: $isSigning, | ||
/// name: name | ||
/// ) | ||
/// ``` | ||
public struct SignatureView: View { | ||
@Environment(\.undoManager) private var undoManager | ||
@Binding private var signature: PKDrawing | ||
@Binding private var isSigning: Bool | ||
@State private var canUndo = false | ||
private let name: PersonNameComponents | ||
private let lineOffset: CGFloat | ||
|
||
|
||
public var body: some View { | ||
VStack { | ||
ZStack(alignment: .bottomLeading) { | ||
Color(.secondarySystemBackground) | ||
Rectangle() | ||
.fill(.secondary) | ||
.frame(maxWidth: .infinity, maxHeight: 1) | ||
.padding(.horizontal, 20) | ||
.padding(.bottom, lineOffset) | ||
Text("X") | ||
.font(.title2) | ||
.foregroundColor(.secondary) | ||
.padding(.horizontal, 20) | ||
.padding(.bottom, lineOffset + 2) | ||
Text(name.formatted(.name(style: .long))) | ||
.font(.subheadline) | ||
.foregroundColor(.secondary) | ||
.padding(.horizontal, 20) | ||
.padding(.bottom, lineOffset - 18) | ||
CanvasView(drawing: $signature, isDrawing: $isSigning, showToolPicker: .constant(false)) | ||
} | ||
.frame(height: 120) | ||
Button(String(localized: "SIGNATURE_VIEW_UNDO", bundle: .module)) { | ||
undoManager?.undo() | ||
canUndo = undoManager?.canUndo ?? false | ||
} | ||
.disabled(!canUndo) | ||
} | ||
.onChange(of: isSigning) { _ in | ||
Task { @MainActor in | ||
canUndo = undoManager?.canUndo ?? false | ||
} | ||
} | ||
.transition(.opacity) | ||
.animation(.easeInOut, value: canUndo) | ||
} | ||
|
||
|
||
/// Creates a new instance of an ``SignatureView``. | ||
/// - Parameters: | ||
/// - signature: A `Binding` containing the current signature as an `PKDrawing`. | ||
/// - isSigning: A `Binding` indicating if the user is currently signing. | ||
/// - name: The name that is deplayed under the signature line. | ||
/// - lineOffset: Defines the distance of the signature line from the bottom of the view. The default value is 30. | ||
init( | ||
signature: Binding<PKDrawing> = .constant(PKDrawing()), | ||
isSigning: Binding<Bool> = .constant(false), | ||
name: PersonNameComponents = PersonNameComponents(), | ||
lineOffset: CGFloat = 30 | ||
) { | ||
self._signature = signature | ||
self._isSigning = isSigning | ||
self.name = name | ||
self.lineOffset = lineOffset | ||
} | ||
} | ||
|
||
|
||
struct SignatureView_Previews: PreviewProvider { | ||
static var previews: some View { | ||
SignatureView() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.