Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup, Swift 6, Consent Contraint and SignatureView improvements #60

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@ let package = Package(
.library(name: "SpeziOnboarding", targets: ["SpeziOnboarding"])
],
dependencies: [
.package(url: "https://github.com/StanfordSpezi/Spezi.git", from: "1.8.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziViews.git", from: "1.8.0"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.4"),
.package(url: "https://github.com/techprimate/TPPDF.git", from: "2.6.1")
.package(url: "https://github.com/StanfordSpezi/Spezi", from: "1.8.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziFoundation", from: "2.1.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziViews", from: "1.9.0"),
.package(url: "https://github.com/apple/swift-collections", from: "1.1.4"),
.package(url: "https://github.com/techprimate/TPPDF", from: "2.6.1")
] + swiftLintPackage(),
targets: [
.target(
name: "SpeziOnboarding",
dependencies: [
.product(name: "Spezi", package: "Spezi"),
.product(name: "SpeziFoundation", package: "SpeziFoundation"),
.product(name: "SpeziViews", package: "SpeziViews"),
.product(name: "SpeziPersonalInfo", package: "SpeziViews"),
.product(name: "OrderedCollections", package: "swift-collections"),
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ struct ConsentViewExample: View {
action: {
// Action to perform once the user has given their consent
},
exportConfiguration: .init(paperSize: .usLetter) // Configure the properties of the exported consent form
exportConfiguration: .init(paperSize: .usLetter), // Configure the properties of the exported consent form
currentDateInSignature: true // Indicates if the consent signature should include the current date.
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ public enum ConsentViewState: Equatable {
/// The `signed` state indicates that the ``ConsentDocument`` is signed by the user.
case signed
/// The `export` state can be set by an outside view
/// encapsulating the ``ConsentDocument`` to trigger the export of the consent document as a PDF.
/// encapsulating the ``ConsentDocument`` to trigger the export of the consent document as a ``ConsentDocumentExportRepresentation``.
///
/// The previous state must be ``ConsentViewState/signed``, indicating that the consent document is signed.
case export
/// The `exported` state indicates that the
/// ``ConsentDocument`` has been successfully exported. The rendered `PDFDocument` can be found as the associated value of the state.
/// ``ConsentDocumentExportRepresentation`` has been successfully created.
/// The ``ConsentDocumentExportRepresentation`` can then be rendered to a PDF via ``ConsentDocumentExportRepresentation/render()``.
///
/// The export procedure (resulting in the ``ConsentViewState/exported(document:)`` state) can be triggered via setting the ``ConsentViewState/export`` state of the ``ConsentDocument`` .
case exported(document: PDFDocument, export: ConsentDocumentExport)
/// The `storing` state indicates that the ``ConsentDocument`` is currently being stored to the Standard.
case storing
/// The export representation creation (resulting in the ``ConsentViewState/exported(representation:)`` state) can be triggered
/// via setting the ``ConsentViewState/export`` state of the ``ConsentDocument`` .
case exported(representation: ConsentDocumentExportRepresentation)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import PDFKit
import PencilKit
import SwiftUI
import TPPDF

/// Extension of `ConsentDocument` enabling the export of the signed consent page.
extension ConsentDocument {
/// Creates the export representation of the ``ConsentDocument`` including all necessary content.
var exportRepresentation: ConsentDocumentExportRepresentation {
get async {
#if !os(macOS)
.init(
markdown: await self.markdown(),
signature: signatureImage,
name: self.name,
formattedSignatureDate: self.formattedConsentSignatureDate,
configuration: self.exportConfiguration
)
#else
.init(
markdown: await self.markdown(),
signature: self.signature,
name: self.name,
formattedSignatureDate: self.formattedConsentSignatureDate,
configuration: self.exportConfiguration
)

Check warning on line 34 in Sources/SpeziOnboarding/Consent/Export/ConsentDocument+Export.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziOnboarding/Consent/Export/ConsentDocument+Export.swift#L28-L34

Added lines #L28 - L34 were not covered by tests
#endif
}
}

#if !os(macOS)
private var signatureImage: UIImage {
var updatedDrawing = PKDrawing()

for stroke in signature.strokes {
// As the `PKDrawing.image()` function automatically converts the ink color dependent on the used color scheme (light or dark mode),
// force the ink used in the `UIImage` of the `PKDrawing` to always be black by adjusting the signature ink according to the color scheme.
let blackStroke = PKStroke(
ink: PKInk(stroke.ink.inkType, color: colorScheme == .light ? .black : .white),
path: stroke.path,
transform: stroke.transform,
mask: stroke.mask
)

updatedDrawing.strokes.append(blackStroke)
}

#if os(iOS)
let scale = UIScreen.main.scale
#else
let scale = 3.0 // retina scale is default
#endif

return updatedDrawing.image(
from: .init(x: 0, y: 0, width: signatureSize.width, height: signatureSize.height),
scale: scale
)
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@

import Foundation
import SwiftUI
import TPPDF


extension ConsentDocument {
/// The ``ExportConfiguration`` enables developers to define the properties of the exported consent form.
public struct ExportConfiguration: Sendable {
extension ConsentDocumentExportRepresentation {
/// The ``Configuration`` enables developers to define the properties of the exported consent form.
public struct Configuration: Equatable, Sendable {
/// Represents common paper sizes with their dimensions.
///
/// You can use the `dimensions` property to get the width and height of each paper size in points.
///
/// - Note: The dimensions are calculated based on the standard DPI (dots per inch) of 72 for print.
public enum PaperSize: Sendable {
public enum PaperSize: Equatable, Sendable {
/// Standard US Letter paper size.
case usLetter
/// Standard DIN A4 paper size.
Expand All @@ -42,14 +43,22 @@ extension ConsentDocument {
return (widthInInches * pointsPerInch, heightInInches * pointsPerInch)
}
}

/// `TPPDF/PDFPageFormat` which corresponds to SpeziOnboarding's `PaperSize`.
var pdfPageFormat: PDFPageFormat {
switch self {
case .usLetter: .usLetter
case .dinA4: .a4
}
}
}

#if !os(macOS)
/// The ``FontSettings`` store configuration of the fonts used to render the exported
/// consent document, i.e., fonts for the content, title and signature.
public struct FontSettings: Sendable {
/// The font of the name rendered below the signature line.
public let signatureNameFont: UIFont
public struct FontSettings: Equatable, Sendable {
/// The font of the caption rendered below the signature line.
public let signatureCaptionFont: UIFont
/// The font of the prefix of the signature ("X" in most cases).
public let signaturePrefixFont: UIFont
/// The font of the content of the document (i.e., the rendered markdown text)
Expand All @@ -63,19 +72,19 @@ extension ConsentDocument {
/// Creates an instance`FontSettings` specifying the fonts of various components of the exported document
///
/// - Parameters:
/// - signatureNameFont: The font used for the signature name.
/// - signatureCaptionFont: The font used for the signature caption.
/// - signaturePrefixFont: The font used for the signature prefix text.
/// - documentContentFont: The font used for the main content of the document.
/// - headerTitleFont: The font used for the header title.
/// - headerExportTimeStampFont: The font used for the header timestamp.
public init(
signatureNameFont: UIFont,
signatureCaptionFont: UIFont,
signaturePrefixFont: UIFont,
documentContentFont: UIFont,
headerTitleFont: UIFont,
headerExportTimeStampFont: UIFont
) {
self.signatureNameFont = signatureNameFont
self.signatureCaptionFont = signatureCaptionFont
self.signaturePrefixFont = signaturePrefixFont
self.documentContentFont = documentContentFont
self.headerTitleFont = headerTitleFont
Expand All @@ -85,9 +94,9 @@ extension ConsentDocument {
#else
/// The ``FontSettings`` store configuration of the fonts used to render the exported
/// consent document, i.e., fonts for the content, title and signature.
public struct FontSettings: @unchecked Sendable {
/// The font of the name rendered below the signature line.
public let signatureNameFont: NSFont
public struct FontSettings: Equatable, @unchecked Sendable {
/// The font of the caption rendered below the signature line.
public let signatureCaptionFont: NSFont
/// The font of the prefix of the signature ("X" in most cases).
public let signaturePrefixFont: NSFont
/// The font of the content of the document (i.e., the rendered markdown text)
Expand All @@ -101,19 +110,19 @@ extension ConsentDocument {
/// Creates an instance`FontSettings` specifying the fonts of various components of the exported document
///
/// - Parameters:
/// - signatureNameFont: The font used for the signature name.
/// - signatureCaptionFont: The font used for the signature caption.
/// - signaturePrefixFont: The font used for the signature prefix text.
/// - documentContentFont: The font used for the main content of the document.
/// - headerTitleFont: The font used for the header title.
/// - headerExportTimeStampFont: The font used for the header timestamp.
public init(
signatureNameFont: NSFont,
signatureCaptionFont: NSFont,
signaturePrefixFont: NSFont,
documentContentFont: NSFont,
headerTitleFont: NSFont,
headerExportTimeStampFont: NSFont
) {
self.signatureNameFont = signatureNameFont
self.signatureCaptionFont = signatureCaptionFont
self.signaturePrefixFont = signaturePrefixFont
self.documentContentFont = documentContentFont
self.headerTitleFont = headerTitleFont
Expand All @@ -122,23 +131,25 @@ extension ConsentDocument {
}
#endif


let consentTitle: LocalizedStringResource
let paperSize: PaperSize
let includingTimestamp: Bool
let fontSettings: FontSettings


/// Creates an `ExportConfiguration` specifying the properties of the exported consent form.
/// Creates an ``ConsentDocumentExportRepresentation/Configuration`` specifying the properties of the exported consent form.
///
/// - Parameters:
/// - paperSize: The page size of the exported form represented by ``ConsentDocument/ExportConfiguration/PaperSize``.
/// - paperSize: The page size of the exported form represented by ``ConsentDocumentExportRepresentation/Configuration/PaperSize``.
/// - consentTitle: The title of the exported consent form.
/// - includingTimestamp: Indicates if the exported form includes a timestamp.
/// - fontSettings: Font settings for the exported form.
public init(
paperSize: PaperSize = .usLetter,
consentTitle: LocalizedStringResource = LocalizationDefaults.exportedConsentFormTitle,
consentTitle: LocalizedStringResource = Configuration.Defaults.exportedConsentFormTitle,
includingTimestamp: Bool = true,
fontSettings: FontSettings = ExportConfiguration.Defaults.defaultExportFontSettings
fontSettings: FontSettings = Configuration.Defaults.defaultExportFontSettings
) {
self.paperSize = paperSize
self.consentTitle = consentTitle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ import Foundation
import SwiftUI


extension ConsentDocument.ExportConfiguration {
/// Provides default values for fields related to the `ConsentDocumentExportConfiguration`.
extension ConsentDocumentExportRepresentation.Configuration {
/// Provides default values for fields related to the ``ConsentDocumentExportRepresentation/Configuration``.
public enum Defaults {
/// Default localized value for the title of the exported consent form.
public static let exportedConsentFormTitle = LocalizedStringResource("CONSENT_TITLE", bundle: .atURL(from: .module))

#if !os(macOS)
/// Default export font settings with fixed font sizes, ensuring a consistent appearance across platforms.
///
/// This configuration uses `systemFont` and `boldSystemFont` with absolute font sizes to achieve uniform font sizes
/// on different operating systems such as macOS, iOS, and visionOS.
public static let defaultExportFontSettings = FontSettings(
signatureNameFont: UIFont.systemFont(ofSize: 10),
signatureCaptionFont: UIFont.systemFont(ofSize: 10),
signaturePrefixFont: UIFont.boldSystemFont(ofSize: 12),
documentContentFont: UIFont.systemFont(ofSize: 12),
headerTitleFont: UIFont.boldSystemFont(ofSize: 28),
Expand All @@ -30,7 +33,7 @@ extension ConsentDocument.ExportConfiguration {
/// the font sizes might change according to the system settings, potentially leading to varying exported PDF documents
/// on devices with different system settings (e.g., larger default font size).
public static let defaultSystemDefaultFontSettings = FontSettings(
signatureNameFont: UIFont.preferredFont(forTextStyle: .subheadline),
signatureCaptionFont: UIFont.preferredFont(forTextStyle: .subheadline),
signaturePrefixFont: UIFont.preferredFont(forTextStyle: .title2),
documentContentFont: UIFont.preferredFont(forTextStyle: .body),
headerTitleFont: UIFont.boldSystemFont(ofSize: UIFont.preferredFont(forTextStyle: .largeTitle).pointSize),
Expand All @@ -42,7 +45,7 @@ extension ConsentDocument.ExportConfiguration {
/// This configuration uses `systemFont` and `boldSystemFont` with absolute font sizes to achieve uniform font sizes
/// on different operating systems such as macOS, iOS, and visionOS.
public static let defaultExportFontSettings = FontSettings(
signatureNameFont: NSFont.systemFont(ofSize: 10),
signatureCaptionFont: NSFont.systemFont(ofSize: 10),
signaturePrefixFont: NSFont.boldSystemFont(ofSize: 12),
documentContentFont: NSFont.systemFont(ofSize: 12),
headerTitleFont: NSFont.boldSystemFont(ofSize: 28),
Expand All @@ -53,7 +56,7 @@ extension ConsentDocument.ExportConfiguration {
/// the font sizes might change according to the system settings, potentially leading to varying exported PDF documents
/// on devices with different system settings (e.g., larger default font size).
public static let defaultSystemDefaultFontSettings = FontSettings(
signatureNameFont: NSFont.preferredFont(forTextStyle: .subheadline),
signatureCaptionFont: NSFont.preferredFont(forTextStyle: .subheadline),
signaturePrefixFont: NSFont.preferredFont(forTextStyle: .title2),
documentContentFont: NSFont.preferredFont(forTextStyle: .body),
headerTitleFont: NSFont.boldSystemFont(ofSize: NSFont.preferredFont(forTextStyle: .largeTitle).pointSize),
Expand Down
Loading
Loading