StanfordSpezi / SpeziOnboarding

Spezi Onboarding module to inform a user or retrieve consent for a study participation
https://swiftpackageindex.com/StanfordSpezi/SpeziOnboarding/documentation/
MIT License
11 stars 5 forks source link

Export Consent Forms (Markdown) #26

Closed philippzagar closed 8 months ago

philippzagar commented 9 months ago

Export Consent Forms (Markdown)

:recycle: Current situation & Problem

Until now, users could view and sign a consent document using the SpeziOnboarding package. However, the package did not offer any provision to save or export the signed consent document. In the medical domain, the consent form collection is especially important in order to serve as legal cover. See #7.

:gear: Release Notes

:books: Documentation

Documentation is provided via DocC in-line comments. Additional DocC documentation documents are adjusted and refer to the new export functionality.

:white_check_mark: Testing

UI Test cases written and adapted.

:pencil: Code of Conduct & Contributing Guidelines

By submitting creating this pull request, you agree to follow our Code of Conduct and Contributing Guidelines:

philippzagar commented 9 months ago

Hi @PSchmiedmayer,

here is the first draft of the consent export functionality PR. The overall export works reasonably well, see an example exported consent form. However, I'm a bit stuck about the integration in the overall Onboarding API. My ideal usage of the export functionality would be something like:

Export(size: .usLetter, destinationPath: ...) { render in
     ConsentView(
         ...,
         action: {
                render()
                onboardingNavigationPath.nextStep()
          }
     )
}

However, this is prohibited by a multitude of factors, such as the fact that all SwiftUI views are structs, the action closure of the ConsentView is escaping, all data is passed down to the "child" views of the ConsentView (such as the OnboardingActionView) directly in the initializer (including the closures) etc. This constraints the current export design by quite a lot. I don't want to tear down too much of the existing structure of the Onboarding module without consulting you first. The goal would be to have a simple usable API as explained above. At the moment it's implemented via a simple hardcoded Button in the ConsentView (to demonstrate the functionality) and only for MD as HTML is a bit trickier to deal with (but not impossible). Also, I plan to give the user the option to easily store the consent form via the adjustments made in the OnboardingActionView to enable horizontal button layouts. Do you have any design suggestions that would make this functionality nicely integrated, configurable, and extensible for future consent data types? I'm quite stuck here at the moment...

PSchmiedmayer commented 9 months ago

Thank you for the progress in this PR @philippzagar!

I would suggest two things:

  1. Let's first aim to directly integrate the export functionality in the Consent View before we extract it in a separate generic SwiftUI view that can export everything. That should make the process a bit easier and more targeted to the problem that we are facing there at the moment. You can still use that internally but that might be a bitter focus for a first PR. Getting this into a more generalized from would be amazing and then a thing that could be put into SpeziViews.
  2. Regarding the question where and how to enable the export functionality without the escaping closure: I would suggest that we use the Spezi Standard mechanisms here: https://swiftpackageindex.com/stanfordspezi/spezi/documentation/spezi/component#Component-Constraints. This would allow you to pass the finished PDF to the standard and a user can then decide where to put it (e.g. upload or store locally) and how that should be processed is then defined by the Standard instance, a unique environment object that is a reference type. You can see how we use that mechanism over in Spezi Questionnaire: https://github.com/StanfordSpezi/SpeziQuestionnaire

Hope this solves your questions and unblocks you from the next steps. Let me know if you have any questions! 🚀

codecov[bot] commented 9 months ago

Codecov Report

Merging #26 (75472f0) into main (13efb08) will increase coverage by 1.07%. The diff coverage is 81.09%.

Additional details and impacted files [![Impacted file tree graph](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26/graphs/tree.svg?width=650&height=150&src=pr&token=tVwIFVPdJG&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi)](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi) ```diff @@ Coverage Diff @@ ## main #26 +/- ## ========================================== + Coverage 72.65% 73.72% +1.07% ========================================== Files 14 22 +8 Lines 797 1050 +253 ========================================== + Hits 579 774 +195 - Misses 218 276 +58 ``` | [Files](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi) | Coverage Δ | | |---|---|---| | [...entView/ConsentDocument+LocalizationDefaults.swift](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aU9uYm9hcmRpbmcvQ29uc2VudFZpZXcvQ29uc2VudERvY3VtZW50K0xvY2FsaXphdGlvbkRlZmF1bHRzLnN3aWZ0) | `100.00% <100.00%> (ø)` | | | [...oarding/ConsentView/ConsentViewState+Binding.swift](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aU9uYm9hcmRpbmcvQ29uc2VudFZpZXcvQ29uc2VudFZpZXdTdGF0ZStCaW5kaW5nLnN3aWZ0) | `100.00% <100.00%> (ø)` | | | [Sources/SpeziOnboarding/OnboardingDataSource.swift](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aU9uYm9hcmRpbmcvT25ib2FyZGluZ0RhdGFTb3VyY2Uuc3dpZnQ=) | `100.00% <100.00%> (ø)` | | | [...eziOnboarding/OnboardingFlow/OnboardingStack.swift](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aU9uYm9hcmRpbmcvT25ib2FyZGluZ0Zsb3cvT25ib2FyZGluZ1N0YWNrLnN3aWZ0) | `83.34% <ø> (ø)` | | | [Sources/SpeziOnboarding/OnboardingTitleView.swift](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aU9uYm9hcmRpbmcvT25ib2FyZGluZ1RpdGxlVmlldy5zd2lmdA==) | `80.00% <100.00%> (+12.36%)` | :arrow_up: | | [...ces/SpeziOnboarding/SequentialOnboardingView.swift](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aU9uYm9hcmRpbmcvU2VxdWVudGlhbE9uYm9hcmRpbmdWaWV3LnN3aWZ0) | `72.35% <100.00%> (ø)` | | | [Sources/SpeziOnboarding/SignatureView.swift](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aU9uYm9hcmRpbmcvU2lnbmF0dXJlVmlldy5zd2lmdA==) | `87.18% <100.00%> (-4.19%)` | :arrow_down: | | [...rces/SpeziOnboarding/SignatureViewBackground.swift](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aU9uYm9hcmRpbmcvU2lnbmF0dXJlVmlld0JhY2tncm91bmQuc3dpZnQ=) | `100.00% <100.00%> (ø)` | | | [...ding/OnboardingFlow/OnboardingNavigationPath.swift](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aU9uYm9hcmRpbmcvT25ib2FyZGluZ0Zsb3cvT25ib2FyZGluZ05hdmlnYXRpb25QYXRoLnN3aWZ0) | `84.83% <0.00%> (ø)` | | | [...sentView/ConsentDocument+ExportConfiguration.swift](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aU9uYm9hcmRpbmcvQ29uc2VudFZpZXcvQ29uc2VudERvY3VtZW50K0V4cG9ydENvbmZpZ3VyYXRpb24uc3dpZnQ=) | `84.22% <84.22%> (ø)` | | | ... and [4 more](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi) | | ... and [1 file with indirect coverage changes](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi) ------ [Continue to review full report in Codecov by Sentry](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi). > **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi) > `Δ = absolute (impact)`, `ø = not affected`, `? = missing data` > Powered by [Codecov](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi). Last update [13efb08...75472f0](https://app.codecov.io/gh/StanfordSpezi/SpeziOnboarding/pull/26?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi).
philippzagar commented 9 months ago

Hi @PSchmiedmayer,

thanks for the input! 🚀

Regarding 1: Yeah, that probably makes sense, do a first working draft now and expand upon it later (including additions to SpeziViews). As mentioned, the consent export (especially with different formats) isn't a trivial topic and could become quite complex.

Regarding 2: Thanks for the input but I guess you misunderstood my issue a bit. I already use the Standard as well as Constraints in order to store the resulting PDF and let the user decide what to do with the rendered document, that's not the issue here. The main issue is that I'm not sure how to trigger the rendering process when the user proceeds to the next onboarding view. As mentioned, the closure (primaryAction) from the OnboardingActionView used for moving to the next onboarding step is escaping and created in the initializer of the ConsentView, meaning we don't have access to the respective ConsentView instance from inside that closure (which includes all the necessary data points). My current workaround is to use the .simultaneousGesture() modifier on the action subview in the ConsentView in order to detect taps on the action buttons in the OnboardingActionView but also the ConsentView. This works, but it is kind of an ugly workaround. Furthermore, it doesn't enable the implementation of features like a personal "Share" button for users directly on the consent page (e.g., via the secondaryAction of the OnboardingActionView). Any clues on how we could proceed here without tearing down too much of the existing onboarding infrastructure? (especially OnboardingActionView). For now, it is definitely an option to just merge as is (as we are under time pressure), but I would definitely suggest a follow-up PR that expands and refines the export functionality.

philippzagar commented 9 months ago

@PSchmiedmayer Thanks for the great feedback! I incorporated it, please review the current state of the PR 🚀

As mentioned yesterday, a couple of warnings are still thrown when using the iOS Share Sheet functionality (from UIKit):

Only support loading options for CKShare and SWY types.

error fetching item for URL:file:///Users/philippzagar/Library/Developer/CoreSimulator/Devices/B8A80EF1-2820-46D9-8A4D-B653F5FE4AB7/data/Containers/Data/Application/E0B221BD-0A7C-41B4-9EFF-14AD2FBF5FDD/tmp/Signed%20Consent%20Form.pdf :

error fetching file provider domain for URL:file:///Users/philippzagar/Library/Developer/CoreSimulator/Devices/B8A80EF1-2820-46D9-8A4D-B653F5FE4AB7/data/Containers/Data/Application/E0B221BD-0A7C-41B4-9EFF-14AD2FBF5FDD/tmp/Signed%20Consent%20Form.pdf :

Collaboration: error loading metadata for documentURL:file:///Users/philippzagar/Library/Developer/CoreSimulator/Devices/B8A80EF1-2820-46D9-8A4D-B653F5FE4AB7/data/Containers/Data/Application/E0B221BD-0A7C-41B4-9EFF-14AD2FBF5FDD/tmp/Signed%20Consent%20Form.pdf error:Error Domain=NSFileProviderInternalErrorDomain Code=0 "No valid file provider found from URL file:///Users/philippzagar/Library/Developer/CoreSimulator/Devices/B8A80EF1-2820-46D9-8A4D-B653F5FE4AB7/data/Containers/Data/Application/E0B221BD-0A7C-41B4-9EFF-14AD2FBF5FDD/tmp/Signed%20Consent%20Form.pdf." UserInfo={NSLocalizedDescription=No valid file provider found from URL file:///Users/philippzagar/Library/Developer/CoreSimulator/Devices/B8A80EF1-2820-46D9-8A4D-B653F5FE4AB7/data/Containers/Data/Application/E0B221BD-0A7C-41B4-9EFF-14AD2FBF5FDD/tmp/Signed%20Consent%20Form.pdf.}

Error acquiring assertion: <Error Domain=RBSServiceErrorDomain Code=1 "(originator doesn't have entitlement com.apple.runningboard.primitiveattribute AND originator doesn't have entitlement com.apple.runningboard.assertions.frontboard AND target is not running or doesn't have entitlement com.apple.runningboard.trustedtarget AND Target not hosted by originator)" UserInfo={NSLocalizedFailureReason=(originator doesn't have entitlement com.apple.runningboard.primitiveattribute AND originator doesn't have entitlement com.apple.runningboard.assertions.frontboard AND target is not running or doesn't have entitlement com.apple.runningboard.trustedtarget AND Target not hosted by originator)}>

Not sure if we can do smt about that, as it apparently has smt. to do with permissions set in the Info.plist file: https://stackoverflow.com/questions/76740143/sharesheet-fails-to-open-a-local-file Apparently (somehow related to this) it also may be (partly) a bug of iOS17: https://developer.apple.com/forums/thread/737721

Warnings under iOS16:

Only support loading options for CKShare and SWY types.

error fetching item for URL:file:///Users/philippzagar/Library/Developer/CoreSimulator/Devices/7F4D9524-B4DF-45C4-AB41-2A4BF0BBC85D/data/Containers/Data/Application/6604A4BA-C17B-45BE-9FAF-E9582788CCA3/tmp/Signed%20Consent%20Form.pdf : (null)

error fetching file provider domain for URL:file:///Users/philippzagar/Library/Developer/CoreSimulator/Devices/7F4D9524-B4DF-45C4-AB41-2A4BF0BBC85D/data/Containers/Data/Application/6604A4BA-C17B-45BE-9FAF-E9582788CCA3/tmp/Signed%20Consent%20Form.pdf : (null)

error loading metadata for documentURL:file:///Users/philippzagar/Library/Developer/CoreSimulator/Devices/7F4D9524-B4DF-45C4-AB41-2A4BF0BBC85D/data/Containers/Data/Application/6604A4BA-C17B-45BE-9FAF-E9582788CCA3/tmp/Signed%20Consent%20Form.pdf error:Error Domain=NSFileProviderInternalErrorDomain Code=0 "No valid file provider found from URL file:///Users/philippzagar/Library/Developer/CoreSimulator/Devices/7F4D9524-B4DF-45C4-AB41-2A4BF0BBC85D/data/Containers/Data/Application/6604A4BA-C17B-45BE-9FAF-E9582788CCA3/tmp/Signed%20Consent%20Form.pdf." UserInfo={NSLocalizedDescription=No valid file provider found from URL file:///Users/philippzagar/Library/Developer/CoreSimulator/Devices/7F4D9524-B4DF-45C4-AB41-2A4BF0BBC85D/data/Containers/Data/Application/6604A4BA-C17B-45BE-9FAF-E9582788CCA3/tmp/Signed%20Consent%20Form.pdf.}