From 67b2354329ea60a93a527e24692d7e728292553c Mon Sep 17 00:00:00 2001 From: Paul Schmiedmayer Date: Thu, 18 Jan 2024 12:29:30 -0800 Subject: [PATCH 1/6] Update to Spezi 1.0 --- .firebaserc | 2 +- .firebaserc.license | 5 +- .gitattributes | 7 - .github/workflows/beta-deployment.yml | 25 +- .github/workflows/build-and-test.yml | 21 +- .../monthly-markdown-link-check.yml.yml | 21 + .gitignore | 14 +- .swiftlint.yml | 5 +- CITATION.cff | 5 +- CONTRIBUTORS.md | 7 +- Gemfile | 2 +- Gemfile.lock | 67 +- Gemfile.lock.license | 2 +- PAWS.xcodeproj/project.pbxproj | 824 +++++++++++++----- PAWS.xcodeproj/project.pbxproj.license | 2 +- .../contents.xcworkspacedata | 7 + .../contents.xcworkspacedata.license | 6 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../IDEWorkspaceChecks.plist.license | 6 + .../xcshareddata/swiftpm/Package.resolved | 320 +++++++ .../swiftpm/Package.resolved.license | 6 + .../xcshareddata/xcschemes/PAWS.xcscheme | 104 +-- .../xcschemes/PAWS.xcscheme.license | 2 +- PAWS.xctestplan | 39 - PAWS.xctestplan.license | 2 +- PAWS/Account/AccountButton.swift | 38 + PAWS/Account/AccountSetupHeader.swift | 43 + PAWS/Account/AccountSheet.swift | 91 ++ PAWS/Contacts/Contacts.swift | 107 +++ PAWS/Contributions/ContributionsList.swift | 53 ++ PAWS/Contributions/Package+LicenseType.swift | 104 +++ PAWS/Contributions/PackageCell.swift | 73 ++ PAWS/Contributions/PackageHelper.swift | 26 + .../Helper}/Binding+Negate.swift | 4 +- .../Helper}/Bundle+Image.swift | 4 +- .../CodableArray+RawRepresentable.swift | 2 +- PAWS/Home.swift | 92 ++ PAWS/MockUpload/MockUpload.swift | 41 + PAWS/Onboarding/AccountOnboarding.swift | 64 ++ PAWS/Onboarding/Consent.swift | 49 ++ PAWS/Onboarding/HealthKitPermissions.swift | 77 ++ PAWS/Onboarding/InterestingModules.swift | 54 ++ PAWS/Onboarding/NotificationPermissions.swift | 77 ++ PAWS/Onboarding/OnboardingFlow.swift | 79 ++ PAWS/Onboarding/Welcome.swift | 63 ++ PAWS/PAWS.swift | 30 +- PAWS/PAWSAppDelegate.swift | 75 -- PAWS/PAWSDelegate.swift | 84 ++ PAWS/PAWSStandard.swift | 203 +++++ ...tingSetup.swift => PAWSTestingSetup.swift} | 7 +- .../AccentColor.colorset/Contents.json | 20 + .../Contents.json.license | 6 + .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/paws-logo.png | Bin .../AydinSquarePhoto.png | Bin .../AydinSquarePhoto.imageset}/Contents.json | 3 +- .../Assets.xcassets/Contents.json | 0 .../Assets.xcassets/Contents.json.license | 6 + .../NotificationImage.imageset/Contents.json | 12 + .../NotificationImage.png | Bin .../ScottSquarePhoto.imageset/Contents.json | 12 + .../ScottSquarePhoto.png | Bin .../Resources}/ConsentDocument.md | 0 PAWS/Resources/ConsentDocument.md.license | 6 + PAWS/Resources/Localizable.xcstrings | 789 +++++++++++++++++ PAWS/Resources/Localizable.xcstrings.license | 6 + .../Resources/SocialSupportQuestionnaire.json | 387 ++++++++ .../SocialSupportQuestionnaire.json.license | 6 + PAWS/Schedule/Bundle+Questionnaire.swift | 26 + PAWS/Schedule/EventContext.swift | 25 + PAWS/Schedule/EventContextView.swift | 81 ++ PAWS/Schedule/ModalView.swift | 47 + PAWS/Schedule/PAWSScheduler.swift | 49 ++ PAWS/Schedule/PAWSTaskContext.swift | 40 + PAWS/Schedule/ScheduleView.swift | 131 +++ .../SharedContext}/FeatureFlags.swift | 18 +- PAWS/SharedContext/PAWSCard.swift | 52 ++ PAWS/SharedContext/StorageKeys.swift | 21 + PAWS/Study Information/FAQ.swift | 74 ++ PAWS/Study Information/StudyDescription.swift | 33 + PAWS/Study Information/StudyInformation.swift | 41 + .../Contents.json.license | 6 - .../AppIcon.appiconset/Contents.json.license | 6 - .../AppIcon.appiconset/paws-logo.png.license | 6 - .../AydinSquarePhoto.png.license | 6 - .../AydinSquarePhoto.imageset/Contents.json | 21 - .../Contents.json.license | 6 - .../Assets.xcassets/Contents.json.license | 6 - .../NotificationImage.imageset/Contents.json | 21 - .../Contents.json.license | 6 - .../NotificationImage.png.license | 6 - .../ScottSquarePhoto.imageset/Contents.json | 21 - .../Contents.json.license | 7 - .../ScottSquarePhoto.png.license | 7 - .../Supporting Files/GoogleService-Info.plist | 24 +- .../GoogleService-Info.plist.license | 5 +- PAWS/Supporting Files/Info.plist | 8 +- PAWS/Supporting Files/Info.plist.license | 2 +- PAWS/Supporting Files/Localizable.strings | 22 - PAWS/Supporting Files/PAWS.entitlements | 4 + .../PAWS.entitlements.license | 2 +- PAWSModules/Package.swift | 84 -- .../Sources/PAWSContacts/Contacts.swift | 463 ---------- .../Sources/PAWSContacts/Description.swift | 56 -- .../PAWSContacts/Resources/Question1.md | 1 - .../Resources/Question1.md.license | 6 - .../PAWSContacts/Resources/Question2.md | 1 - .../Resources/Question2.md.license | 6 - .../PAWSContacts/Resources/Question3.md | 1 - .../Resources/Question3.md.license | 6 - .../PAWSContacts/Resources/Question4.md | 1 - .../Resources/Question4.md.license | 6 - .../PAWSContacts/Resources/Question5.md | 1 - .../Resources/Question5.md.license | 6 - .../PAWSContacts/Resources/Question6.md | 1 - .../Resources/Question6.md.license | 6 - .../PAWSContacts/Resources/Question7.md | 1 - .../Resources/Question7.md.license | 6 - .../PAWSContacts/Resources/Question8.md | 4 - .../Resources/Question8.md.license | 6 - .../PAWSContacts/Resources/Question9.md | 1 - .../Resources/Question9.md.license | 6 - .../Resources/StudyDescription.md | 9 - .../Resources/StudyDescription.md.license | 6 - .../Resources/en.lproj/Localizable.strings | 12 - .../PAWSLandingScreen/Bundle+Image.swift | 24 - .../PAWSLandingScreen/LandingScreen.swift | 67 -- .../AboutStudy.swift | 65 -- .../AboutStudyNav.swift | 44 - .../Bundle+ECGTracing.swift | 26 - .../PAWSMockDataStorageProvider/Home.swift | 51 -- .../HomeScreen.swift | 116 --- .../LatestRecording.swift | 126 --- .../LazyText.swift | 54 -- .../MockDataStorageProvider.swift | 103 --- .../MockUpload.swift | 46 - .../MockUploadDetailView.swift | 28 - .../MockUploadHeader.swift | 140 --- .../MockUploadList-filter.swift | 77 -- .../MockUploadList.swift | 66 -- .../RecordingsNav.swift | 44 - .../Resources/ECGObservation.json | 165 ---- .../Resources/ECGObservation.json.license | 6 - .../Resources/en.lproj/Localizable.strings | 13 - .../PAWSNotificationScreen/Notification.swift | 61 -- .../NotificationScreen.swift | 37 - .../AccountSetup/AccountSetup.swift | 153 ---- .../AccountSetup/IconView.swift | 44 - .../AccountSetup/PAWSLogin.swift | 37 - .../AccountSetup/PAWSSignUp.swift | 36 - .../AccountSetup/UserView.swift | 69 -- .../Sources/PAWSOnboardingFlow/Consent.swift | 71 -- .../HealthKitPermissions.swift | 69 -- .../InterestingModules.swift | 69 -- .../NotificationSetup.swift | 94 -- .../PAWSOnboardingFlow/OnboardingFlow.swift | 71 -- .../Resources/en.lproj/AppIcon.png | Bin 152328 -> 0 bytes .../Resources/en.lproj/AppIcon.png.license | 6 - .../en.lproj/ConsentDocument.md.license | 6 - .../Resources/en.lproj/Localizable.strings | 83 -- .../String+ModuleLocalized.swift | 13 - .../Sources/PAWSOnboardingFlow/Welcome.swift | 59 -- .../PAWSSharedContext/StorageKeys.swift | 21 - PAWSTests/PAWSTests.swift | 2 +- PAWSUITests/ContactsTests.swift | 19 +- PAWSUITests/ContributionsTest.swift | 41 + PAWSUITests/MockUploadTests.swift | 32 + PAWSUITests/NotificationsTests.swift | 32 - PAWSUITests/OnboardingTests.swift | 221 +++-- PAWSUITests/SchedulerTests.swift | 36 + README.md | 57 +- codecov.yml | 40 + fastlane/.gitignore | 2 +- fastlane/Appfile | 2 +- fastlane/Fastfile | 43 +- fastlane/Gymfile | 25 - fastlane/README.md | 8 + fastlane/README.md.license | 2 +- fastlane/Scanfile | 18 - firebase.json | 8 +- firebase.json.license | 5 +- firebasestorage.rules | 12 + firebasestorage.rules.license | 5 + firestore.rules | 3 +- firestore.rules.license | 5 +- 185 files changed, 4827 insertions(+), 3879 deletions(-) delete mode 100644 .gitattributes create mode 100644 .github/workflows/monthly-markdown-link-check.yml.yml create mode 100644 PAWS.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 PAWS.xcodeproj/project.xcworkspace/contents.xcworkspacedata.license create mode 100644 PAWS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 PAWS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist.license create mode 100644 PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved.license create mode 100644 PAWS/Account/AccountButton.swift create mode 100644 PAWS/Account/AccountSetupHeader.swift create mode 100644 PAWS/Account/AccountSheet.swift create mode 100644 PAWS/Contacts/Contacts.swift create mode 100644 PAWS/Contributions/ContributionsList.swift create mode 100644 PAWS/Contributions/Package+LicenseType.swift create mode 100644 PAWS/Contributions/PackageCell.swift create mode 100644 PAWS/Contributions/PackageHelper.swift rename {PAWSModules/Sources/PAWSSharedContext => PAWS/Helper}/Binding+Negate.swift (63%) rename {PAWSModules/Sources/PAWSSharedContext => PAWS/Helper}/Bundle+Image.swift (82%) rename {PAWSModules/Sources/PAWSSharedContext => PAWS/Helper}/CodableArray+RawRepresentable.swift (85%) create mode 100644 PAWS/Home.swift create mode 100644 PAWS/MockUpload/MockUpload.swift create mode 100644 PAWS/Onboarding/AccountOnboarding.swift create mode 100644 PAWS/Onboarding/Consent.swift create mode 100644 PAWS/Onboarding/HealthKitPermissions.swift create mode 100644 PAWS/Onboarding/InterestingModules.swift create mode 100644 PAWS/Onboarding/NotificationPermissions.swift create mode 100644 PAWS/Onboarding/OnboardingFlow.swift create mode 100644 PAWS/Onboarding/Welcome.swift delete mode 100644 PAWS/PAWSAppDelegate.swift create mode 100644 PAWS/PAWSDelegate.swift create mode 100644 PAWS/PAWSStandard.swift rename PAWS/{PAWSAppTestingSetup.swift => PAWSTestingSetup.swift} (75%) create mode 100644 PAWS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 PAWS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json.license rename PAWS/{Supporting Files => Resources}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename PAWS/{Supporting Files => Resources}/Assets.xcassets/AppIcon.appiconset/paws-logo.png (100%) rename PAWS/{Supporting Files => Resources}/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png (100%) rename PAWS/{Supporting Files/Assets.xcassets/AccentColor.colorset => Resources/Assets.xcassets/AydinSquarePhoto.imageset}/Contents.json (65%) rename PAWS/{Supporting Files => Resources}/Assets.xcassets/Contents.json (100%) create mode 100644 PAWS/Resources/Assets.xcassets/Contents.json.license create mode 100644 PAWS/Resources/Assets.xcassets/NotificationImage.imageset/Contents.json rename PAWS/{Supporting Files => Resources}/Assets.xcassets/NotificationImage.imageset/NotificationImage.png (100%) create mode 100644 PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json rename PAWS/{Supporting Files => Resources}/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png (100%) rename {PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj => PAWS/Resources}/ConsentDocument.md (100%) create mode 100644 PAWS/Resources/ConsentDocument.md.license create mode 100644 PAWS/Resources/Localizable.xcstrings create mode 100644 PAWS/Resources/Localizable.xcstrings.license create mode 100644 PAWS/Resources/SocialSupportQuestionnaire.json create mode 100644 PAWS/Resources/SocialSupportQuestionnaire.json.license create mode 100644 PAWS/Schedule/Bundle+Questionnaire.swift create mode 100644 PAWS/Schedule/EventContext.swift create mode 100644 PAWS/Schedule/EventContextView.swift create mode 100644 PAWS/Schedule/ModalView.swift create mode 100644 PAWS/Schedule/PAWSScheduler.swift create mode 100644 PAWS/Schedule/PAWSTaskContext.swift create mode 100644 PAWS/Schedule/ScheduleView.swift rename {PAWSModules/Sources/PAWSSharedContext => PAWS/SharedContext}/FeatureFlags.swift (55%) create mode 100644 PAWS/SharedContext/PAWSCard.swift create mode 100644 PAWS/SharedContext/StorageKeys.swift create mode 100644 PAWS/Study Information/FAQ.swift create mode 100644 PAWS/Study Information/StudyDescription.swift create mode 100644 PAWS/Study Information/StudyInformation.swift delete mode 100644 PAWS/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json.license delete mode 100644 PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json.license delete mode 100644 PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/paws-logo.png.license delete mode 100644 PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png.license delete mode 100644 PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json delete mode 100644 PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json.license delete mode 100644 PAWS/Supporting Files/Assets.xcassets/Contents.json.license delete mode 100644 PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/Contents.json delete mode 100644 PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/Contents.json.license delete mode 100644 PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/NotificationImage.png.license delete mode 100644 PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json delete mode 100644 PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json.license delete mode 100644 PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png.license delete mode 100644 PAWS/Supporting Files/Localizable.strings delete mode 100644 PAWSModules/Package.swift delete mode 100644 PAWSModules/Sources/PAWSContacts/Contacts.swift delete mode 100644 PAWSModules/Sources/PAWSContacts/Description.swift delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question1.md delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question1.md.license delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question2.md delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question2.md.license delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question3.md delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question3.md.license delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question4.md delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question4.md.license delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question5.md delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question5.md.license delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question6.md delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question6.md.license delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question7.md delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question7.md.license delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question8.md delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question8.md.license delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question9.md delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/Question9.md.license delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/StudyDescription.md delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/StudyDescription.md.license delete mode 100644 PAWSModules/Sources/PAWSContacts/Resources/en.lproj/Localizable.strings delete mode 100644 PAWSModules/Sources/PAWSLandingScreen/Bundle+Image.swift delete mode 100644 PAWSModules/Sources/PAWSLandingScreen/LandingScreen.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/AboutStudy.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/AboutStudyNav.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/Bundle+ECGTracing.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/Home.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/HomeScreen.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/LatestRecording.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/LazyText.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/MockDataStorageProvider.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/MockUpload.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadDetailView.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadHeader.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadList-filter.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadList.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/RecordingsNav.swift delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/ECGObservation.json delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/ECGObservation.json.license delete mode 100644 PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/en.lproj/Localizable.strings delete mode 100644 PAWSModules/Sources/PAWSNotificationScreen/Notification.swift delete mode 100644 PAWSModules/Sources/PAWSNotificationScreen/NotificationScreen.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/AccountSetup.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/IconView.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/PAWSLogin.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/PAWSSignUp.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/UserView.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/Consent.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/HealthKitPermissions.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/InterestingModules.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/NotificationSetup.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/OnboardingFlow.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/AppIcon.png delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/AppIcon.png.license delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/ConsentDocument.md.license delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/Localizable.strings delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/String+ModuleLocalized.swift delete mode 100644 PAWSModules/Sources/PAWSOnboardingFlow/Welcome.swift delete mode 100644 PAWSModules/Sources/PAWSSharedContext/StorageKeys.swift create mode 100644 PAWSUITests/ContributionsTest.swift create mode 100644 PAWSUITests/MockUploadTests.swift delete mode 100644 PAWSUITests/NotificationsTests.swift create mode 100644 PAWSUITests/SchedulerTests.swift create mode 100644 codecov.yml delete mode 100644 fastlane/Gymfile delete mode 100644 fastlane/Scanfile create mode 100644 firebasestorage.rules create mode 100644 firebasestorage.rules.license diff --git a/.firebaserc b/.firebaserc index 39b8d8b5..5d910ff7 100644 --- a/.firebaserc +++ b/.firebaserc @@ -2,4 +2,4 @@ "projects": { "default": "cs342-2023-paws" } -} +} diff --git a/.firebaserc.license b/.firebaserc.license index 9137ea9d..c310108a 100644 --- a/.firebaserc.license +++ b/.firebaserc.license @@ -1,6 +1,5 @@ +This source file is part of the PAWS application based on the Stanford Spezi Template Application project -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University +SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) SPDX-License-Identifier: MIT diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 77c2e811..00000000 --- a/.gitattributes +++ /dev/null @@ -1,7 +0,0 @@ -# -# This source file is part of the CS342 2023 PAWS Team Application project -# -# SPDX-FileCopyrightText: 2023 Stanford University -# -# SPDX-License-Identifier: MIT -# diff --git a/.github/workflows/beta-deployment.yml b/.github/workflows/beta-deployment.yml index 7b8e00cb..89f94b9f 100644 --- a/.github/workflows/beta-deployment.yml +++ b/.github/workflows/beta-deployment.yml @@ -1,5 +1,5 @@ # -# This source file is part of the CS342 2023 PAWS Team Application project +# This source file is part of the PAWS application based on the Stanford Spezi Template Application project # # SPDX-FileCopyrightText: 2023 Stanford University # @@ -18,12 +18,27 @@ jobs: buildandtest: name: Build and Test uses: ./.github/workflows/build-and-test.yml + permissions: + contents: read + secrets: inherit iosapptestflightdeployment: name: iOS App TestFlight Deployment needs: buildandtest - uses: CS342/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2 - secrets: inherit + uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2 + permissions: + contents: read with: - artifactname: PAWS.xcresult - fastlanelane: beta + googleserviceinfoplistpath: 'PAWS/Supporting Files/GoogleService-Info.plist' setupsigning: true + fastlanelane: beta + secrets: inherit + deployfirebase: + name: Deploy Firebase Project + needs: iosapptestflightdeployment + uses: StanfordBDHG/.github/.github/workflows/firebase-deploy.yml@v2 + permissions: + contents: read + with: + arguments: '--debug' + secrets: + GOOGLE_APPLICATION_CREDENTIALS_BASE64: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_BASE64 }} diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f05987ff..e44a1956 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -1,5 +1,5 @@ # -# This source file is part of the CS342 2023 PAWS Team Application project +# This source file is part of the PAWS application based on the Stanford Spezi Template Application project # # SPDX-FileCopyrightText: 2023 Stanford University # @@ -17,20 +17,35 @@ jobs: reuse_action: name: REUSE Compliance Check uses: StanfordBDHG/.github/.github/workflows/reuse.yml@v2 + permissions: + contents: read swiftlint: name: SwiftLint uses: StanfordBDHG/.github/.github/workflows/swiftlint.yml@v2 + permissions: + contents: read + markdownlinkcheck: + name: Markdown Link Check + uses: StanfordBDHG/.github/.github/workflows/markdown-link-check.yml@v2 + permissions: + contents: read buildandtest: name: Build and Test - uses: CS342/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2 + uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2 + permissions: + contents: read with: - codeql: true artifactname: PAWS.xcresult + runsonlabels: '["macOS", "self-hosted"]' setupfirebaseemulator: true customcommand: "firebase emulators:exec 'fastlane test'" uploadcoveragereport: name: Upload Coverage Report needs: buildandtest uses: StanfordBDHG/.github/.github/workflows/create-and-upload-coverage-report.yml@v2 + permissions: + contents: read with: coveragereports: PAWS.xcresult + secrets: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/monthly-markdown-link-check.yml.yml b/.github/workflows/monthly-markdown-link-check.yml.yml new file mode 100644 index 00000000..9b749c97 --- /dev/null +++ b/.github/workflows/monthly-markdown-link-check.yml.yml @@ -0,0 +1,21 @@ +# +# This source file is part of the PAWS application based on the Stanford Spezi Template Application project +# +# SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md) +# +# SPDX-License-Identifier: MIT +# + +name: Monthly Markdown Link Check + +on: + # Runs at midnight on the first of every month + schedule: + - cron: "0 0 1 * *" + +jobs: + markdown_link_check: + name: Markdown Link Check + uses: StanfordBDHG/.github/.github/workflows/markdown-link-check.yml@v2 + permissions: + contents: read diff --git a/.gitignore b/.gitignore index 24f2a14b..176cbf40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # -# This source file is part of the CS342 2023 PAWS Team Application project +# This source file is part of the PAWS application based on the Stanford Spezi Template Application project # # SPDX-FileCopyrightText: 2023 Stanford University # @@ -9,11 +9,11 @@ # Swift Package Manager *.xcodeproj .swiftpm -.build/ +.derivedData +.build +.docs !PAWS.xcodeproj -project.xcworkspace/ - # IDE related folders .idea @@ -32,7 +32,6 @@ report.junit report.html PAWS.xcresult - # Logs logs *.log @@ -43,4 +42,7 @@ firebase-debug.log* firebase-debug.*.log* # Firebase cache -.firebase/ \ No newline at end of file +.firebase/ + +# Swift Package List +PAWS/package-list.json diff --git a/.swiftlint.yml b/.swiftlint.yml index 87c022c1..3aae2ae7 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,11 +1,11 @@ # -# This source file is part of the CS342 2023 PAWS Team Application project +# This source file is part of the PAWS application based on the Stanford Spezi Template Application project # # SPDX-FileCopyrightText: 2023 Stanford University # # SPDX-License-Identifier: MIT # - + # The whitelist_rules configuration also includes rules that are enabled by default to provide a good overview of all rules. only_rules: # All Images that provide context should have an accessibility label. Purely decorative images can be hidden from accessibility. @@ -379,6 +379,7 @@ deployment_target: # Availability checks or attributes shouldn’t be using olde excluded: # paths to ignore during linting. Takes precedence over `included`. - .build - .swiftpm + - .codeql - .derivedData closure_body_length: # Closure bodies should not span too many lines. diff --git a/CITATION.cff b/CITATION.cff index 4967af21..41211df1 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,5 +1,5 @@ # -# This source file is part of the CS342 2023 PAWS Team Application project +# This source file is part of the PAWS application based on the Stanford Spezi Template Application project # # SPDX-FileCopyrightText: 2023 Stanford University # @@ -18,5 +18,4 @@ authors: - family-names: "Aalami" given-names: "Oliver" orcid: "https://orcid.org/0000-0002-7799-2429" -title: "CS342 2023 PAWS Team Application" -url: "https://github.com/CS342/2023-PAWS" +title: "PAWS" diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c5a43f76..6f474450 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -CS342 2023 PAWS Team Application Contributors +PAWS Contributors ================================= * [Paul Schmiedmayer](https://github.com/PSchmiedmayer) +* [Andreas Bauer](https://github.com/Supereg) +* [Philipp Zagar](https://github.com/philippzagar) +* [Nikolai Madlener](https://github.com/NikolaiMadlener) \ No newline at end of file diff --git a/Gemfile b/Gemfile index 2e284a7f..39505584 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ # -# This source file is part of the CS342 2023 PAWS Team Application project +# This source file is part of the PAWS application based on the Stanford Spezi Template Application project # # SPDX-FileCopyrightText: 2023 Stanford University # diff --git a/Gemfile.lock b/Gemfile.lock index 0a30d2bc..8f90740b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,27 +1,27 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.5) + CFPropertyList (3.0.6) rexml - addressable (2.8.1) + addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.699.0) - aws-sdk-core (3.169.0) + aws-partitions (1.809.0) + aws-sdk-core (3.181.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.62.0) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-kms (1.71.0) + aws-sdk-core (~> 3, >= 3.177.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.118.0) - aws-sdk-core (~> 3, >= 3.165.0) + aws-sdk-s3 (1.133.0) + aws-sdk-core (~> 3, >= 3.181.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.2) + aws-sigv4 (~> 1.6) + aws-sigv4 (1.6.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -30,13 +30,13 @@ GEM commander (4.6.0) highline (~> 2.0.0) declarative (0.0.20) - digest-crc (0.6.4) + digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.97.2) + excon (0.100.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -65,8 +65,8 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.6) - fastlane (2.211.0) + fastimage (2.2.7) + fastlane (2.214.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -90,7 +90,7 @@ GEM json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) + multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) optparse (~> 0.1.1) plist (>= 3.1.0, < 4.0.0) @@ -106,9 +106,9 @@ GEM xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.32.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-core (0.9.5) + google-apis-androidpublisher_v3 (0.48.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.1) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -117,10 +117,10 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.16.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-playcustomapp_v1 (0.12.0) - google-apis-core (>= 0.9.1, < 2.a) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) google-apis-storage_v1 (0.19.0) google-apis-core (>= 0.9.0, < 2.a) google-cloud-core (1.6.0) @@ -128,7 +128,7 @@ GEM google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.0) + google-cloud-errors (1.3.1) google-cloud-storage (1.44.0) addressable (~> 2.8) digest-crc (~> 0.4) @@ -137,7 +137,7 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.3.0) + googleauth (1.7.0) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -150,25 +150,25 @@ GEM httpclient (2.8.3) jmespath (1.6.2) json (2.6.3) - jwt (2.6.0) + jwt (2.7.1) memoist (0.16.2) mini_magick (4.12.0) - mini_mime (1.1.2) + mini_mime (1.1.5) multi_json (1.15.0) - multipart-post (2.0.0) + multipart-post (2.3.0) nanaimo (0.3.0) naturally (2.2.1) optparse (0.1.1) os (1.1.4) - plist (3.6.0) - public_suffix (5.0.1) + plist (3.7.0) + public_suffix (5.0.3) rake (13.0.6) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.5) + rexml (3.2.6) rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) @@ -178,7 +178,7 @@ GEM faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.8) + simctl (1.6.10) CFPropertyList naturally terminal-notifier (2.0.0) @@ -194,7 +194,7 @@ GEM unf_ext unf_ext (0.0.8.2) unicode-display_width (1.8.0) - webrick (1.7.0) + webrick (1.8.1) word_wrap (1.0.0) xcodeproj (1.22.0) CFPropertyList (>= 2.3.3, < 4.0) @@ -209,6 +209,7 @@ GEM xcpretty (~> 0.2, >= 0.0.7) PLATFORMS + arm64-darwin-22 universal-darwin-21 x86_64-darwin-20 @@ -216,4 +217,4 @@ DEPENDENCIES fastlane BUNDLED WITH - 2.3.23 + 2.4.10 diff --git a/Gemfile.lock.license b/Gemfile.lock.license index 9137ea9d..4f324d88 100644 --- a/Gemfile.lock.license +++ b/Gemfile.lock.license @@ -1,5 +1,5 @@ -This source file is part of the CS342 2023 PAWS Team Application project +This source file is part of the PAWS application based on the Stanford Spezi Template Application project SPDX-FileCopyrightText: 2023 Stanford University diff --git a/PAWS.xcodeproj/project.pbxproj b/PAWS.xcodeproj/project.pbxproj index a878c5b8..c3cb6b98 100644 --- a/PAWS.xcodeproj/project.pbxproj +++ b/PAWS.xcodeproj/project.pbxproj @@ -7,37 +7,79 @@ objects = { /* Begin PBXBuildFile section */ - 2F36AD24299D93E200B1077C /* NotificationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F36AD23299D93E200B1077C /* NotificationsTests.swift */; }; - 2F36AD27299D989F00B1077C /* XCTestExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = 2F36AD26299D989F00B1077C /* XCTestExtensions */; }; - 2F49B7762980407C00BCB272 /* CardinalKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7752980407B00BCB272 /* CardinalKit */; }; - 2F49B7782980407C00BCB272 /* FHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7772980407C00BCB272 /* FHIR */; }; - 2F49B77A2980407C00BCB272 /* HealthKitDataSource in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7792980407C00BCB272 /* HealthKitDataSource */; }; - 2F49B77E2980407C00BCB272 /* Scheduler in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B77D2980407C00BCB272 /* Scheduler */; }; - 2F49B7802980418400BCB272 /* Onboarding in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B77F2980418400BCB272 /* Onboarding */; }; - 2F49B7822980419C00BCB272 /* Questionnaires in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7812980419C00BCB272 /* Questionnaires */; }; - 2F49B784298041F300BCB272 /* Contact in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B783298041F300BCB272 /* Contact */; }; + 27FA29902A388E9B009CAC45 /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FA298F2A388E9B009CAC45 /* ModalView.swift */; }; + 2F1B52CE2A4F5CCE003AE151 /* MockUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F1B52CD2A4F5CCE003AE151 /* MockUploadTests.swift */; }; + 2F3D4ABC2A4E7C290068FB2F /* SpeziScheduler in Frameworks */ = {isa = PBXBuildFile; productRef = 2F3D4ABB2A4E7C290068FB2F /* SpeziScheduler */; }; + 2F49B7762980407C00BCB272 /* Spezi in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7752980407B00BCB272 /* Spezi */; }; 2F4E237E2989A2FE0013F3D9 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */; }; - 2F4E23812989C5930013F3D9 /* XCTHealthKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2F4E23802989C5930013F3D9 /* XCTHealthKit */; }; - 2F4E23832989D51F0013F3D9 /* PAWSAppTestingSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E23822989D51F0013F3D9 /* PAWSAppTestingSetup.swift */; }; + 2F4E23832989D51F0013F3D9 /* PAWSTestingSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E23822989D51F0013F3D9 /* PAWSTestingSetup.swift */; }; 2F4E23872989DB360013F3D9 /* ContactsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E23862989DB360013F3D9 /* ContactsTests.swift */; }; - 2F59B93D298C628800C5107F /* PAWSContacts in Frameworks */ = {isa = PBXBuildFile; productRef = 2F59B93C298C628800C5107F /* PAWSContacts */; }; - 2F59B93F298C628800C5107F /* PAWSMockDataStorageProvider in Frameworks */ = {isa = PBXBuildFile; productRef = 2F59B93E298C628800C5107F /* PAWSMockDataStorageProvider */; }; - 2F59B941298C628800C5107F /* PAWSOnboardingFlow in Frameworks */ = {isa = PBXBuildFile; productRef = 2F59B940298C628800C5107F /* PAWSOnboardingFlow */; }; - 2F59B945298C628800C5107F /* PAWSSharedContext in Frameworks */ = {isa = PBXBuildFile; productRef = 2F59B944298C628800C5107F /* PAWSSharedContext */; }; - 2F5E32BD297E05EA003432F8 /* PAWSAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F5E32BC297E05EA003432F8 /* PAWSAppDelegate.swift */; }; - 2F9056D3299E1B9C003D3802 /* FHIRToFirestoreAdapter in Frameworks */ = {isa = PBXBuildFile; productRef = 2F9056D2299E1B9C003D3802 /* FHIRToFirestoreAdapter */; }; - 2F9056D5299E1B9C003D3802 /* FirestoreDataStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 2F9056D4299E1B9C003D3802 /* FirestoreDataStorage */; }; - 2F9056D7299E1B9C003D3802 /* FirestoreStoragePrefixUserIdAdapter in Frameworks */ = {isa = PBXBuildFile; productRef = 2F9056D6299E1B9C003D3802 /* FirestoreStoragePrefixUserIdAdapter */; }; - 2F9056DB299E1C97003D3802 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2F9056D9299E1C97003D3802 /* GoogleService-Info.plist */; }; - 2F9F4D8329B7FFAE00ABE259 /* CardinalKitHealthKitToFHIRAdapter in Frameworks */ = {isa = PBXBuildFile; productRef = 2F9F4D8229B7FFAE00ABE259 /* CardinalKitHealthKitToFHIRAdapter */; }; - 2FABBC5929A8975900DF3BBC /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 2FABBC5829A8975900DF3BBC /* FirebaseFirestore */; }; - 2FABBC5B29A8975900DF3BBC /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 2FABBC5A29A8975900DF3BBC /* FirebaseFirestoreSwift */; }; - 2FC9759F2978E39600BA99FE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2FC9759E2978E39600BA99FE /* Localizable.strings */; }; - 3637947E2992F3B000B3C006 /* PAWSLandingScreen in Frameworks */ = {isa = PBXBuildFile; productRef = 3637947D2992F3B000B3C006 /* PAWSLandingScreen */; }; - 36379481299308AA00B3C006 /* PAWSNotificationScreen in Frameworks */ = {isa = PBXBuildFile; productRef = 36379480299308AA00B3C006 /* PAWSNotificationScreen */; }; + 2F4FC8D729EE69D300BFFE26 /* MockUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4FC8D629EE69D300BFFE26 /* MockUpload.swift */; }; + 2F5E32BD297E05EA003432F8 /* PAWSDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F5E32BC297E05EA003432F8 /* PAWSDelegate.swift */; }; + 2F6025CB29BBE70F0045459E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2F6025CA29BBE70F0045459E /* GoogleService-Info.plist */; }; + 2F65B44E2A3B8B0600A36932 /* NotificationPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F65B44D2A3B8B0600A36932 /* NotificationPermissions.swift */; }; + 2FA0BFED2ACC977500E0EF83 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 2FA0BFEC2ACC977500E0EF83 /* Localizable.xcstrings */; }; + 2FB099AF2A875DF100B20952 /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 2FB099AE2A875DF100B20952 /* FirebaseAuth */; }; + 2FB099B12A875DF100B20952 /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 2FB099B02A875DF100B20952 /* FirebaseFirestore */; }; + 2FB099B32A875DF100B20952 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 2FB099B22A875DF100B20952 /* FirebaseFirestoreSwift */; }; + 2FB099B62A875E2B00B20952 /* HealthKitOnFHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 2FB099B52A875E2B00B20952 /* HealthKitOnFHIR */; }; + 2FBD738C2A3BD150004228E7 /* SpeziScheduler in Frameworks */ = {isa = PBXBuildFile; productRef = 2FBD738B2A3BD150004228E7 /* SpeziScheduler */; }; + 2FC3439029EE6346002D773C /* SocialSupportQuestionnaire.json in Resources */ = {isa = PBXBuildFile; fileRef = 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */; }; + 2FC3439229EE634B002D773C /* ConsentDocument.md in Resources */ = {isa = PBXBuildFile; fileRef = 2FE5DC2C29EDD78E004B9AB4 /* ConsentDocument.md */; }; + 2FC975A82978F11A00BA99FE /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FC975A72978F11A00BA99FE /* Home.swift */; }; + 2FE5DC2629EDD38A004B9AB4 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC2529EDD38A004B9AB4 /* Contacts.swift */; }; + 2FE5DC3529EDD7CA004B9AB4 /* Consent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC2F29EDD7CA004B9AB4 /* Consent.swift */; }; + 2FE5DC3629EDD7CA004B9AB4 /* HealthKitPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC3029EDD7CA004B9AB4 /* HealthKitPermissions.swift */; }; + 2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC3129EDD7CA004B9AB4 /* OnboardingFlow.swift */; }; + 2FE5DC3829EDD7CA004B9AB4 /* InterestingModules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC3229EDD7CA004B9AB4 /* InterestingModules.swift */; }; + 2FE5DC3A29EDD7CA004B9AB4 /* Welcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC3429EDD7CA004B9AB4 /* Welcome.swift */; }; + 2FE5DC4029EDD7EE004B9AB4 /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC3E29EDD7ED004B9AB4 /* FeatureFlags.swift */; }; + 2FE5DC4129EDD7EE004B9AB4 /* StorageKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC3F29EDD7EE004B9AB4 /* StorageKeys.swift */; }; + 2FE5DC4529EDD7F2004B9AB4 /* Binding+Negate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4229EDD7F2004B9AB4 /* Binding+Negate.swift */; }; + 2FE5DC4629EDD7F2004B9AB4 /* Bundle+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4329EDD7F2004B9AB4 /* Bundle+Image.swift */; }; + 2FE5DC4729EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4429EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift */; }; + 2FE5DC4E29EDD7FA004B9AB4 /* ScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4829EDD7FA004B9AB4 /* ScheduleView.swift */; }; + 2FE5DC4F29EDD7FA004B9AB4 /* EventContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4929EDD7FA004B9AB4 /* EventContext.swift */; }; + 2FE5DC5029EDD7FA004B9AB4 /* EventContextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4A29EDD7FA004B9AB4 /* EventContextView.swift */; }; + 2FE5DC5129EDD7FA004B9AB4 /* PAWSTaskContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4B29EDD7FA004B9AB4 /* PAWSTaskContext.swift */; }; + 2FE5DC5229EDD7FA004B9AB4 /* PAWSScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4C29EDD7FA004B9AB4 /* PAWSScheduler.swift */; }; + 2FE5DC5329EDD7FA004B9AB4 /* Bundle+Questionnaire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4D29EDD7FA004B9AB4 /* Bundle+Questionnaire.swift */; }; + 2FE5DC6429EDD883004B9AB4 /* SpeziAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC6329EDD883004B9AB4 /* SpeziAccount */; }; + 2FE5DC6729EDD894004B9AB4 /* SpeziContact in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC6629EDD894004B9AB4 /* SpeziContact */; }; + 2FE5DC7229EDD8D3004B9AB4 /* SpeziHealthKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC7129EDD8D3004B9AB4 /* SpeziHealthKit */; }; + 2FE5DC7529EDD8E6004B9AB4 /* SpeziFirebaseAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC7429EDD8E6004B9AB4 /* SpeziFirebaseAccount */; }; + 2FE5DC7729EDD8E6004B9AB4 /* SpeziFirebaseConfiguration in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC7629EDD8E6004B9AB4 /* SpeziFirebaseConfiguration */; }; + 2FE5DC7929EDD8E6004B9AB4 /* SpeziFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC7829EDD8E6004B9AB4 /* SpeziFirestore */; }; + 2FE5DC8429EDD934004B9AB4 /* SpeziQuestionnaire in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8329EDD934004B9AB4 /* SpeziQuestionnaire */; }; + 2FE5DC8A29EDD972004B9AB4 /* SpeziLocalStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8929EDD972004B9AB4 /* SpeziLocalStorage */; }; + 2FE5DC8C29EDD972004B9AB4 /* SpeziSecureStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8B29EDD972004B9AB4 /* SpeziSecureStorage */; }; + 2FE5DC8F29EDD980004B9AB4 /* SpeziViews in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8E29EDD980004B9AB4 /* SpeziViews */; }; + 2FE5DC9929EDD9D9004B9AB4 /* XCTestExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC9829EDD9D9004B9AB4 /* XCTestExtensions */; }; + 2FE5DC9C29EDD9EF004B9AB4 /* XCTHealthKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC9B29EDD9EF004B9AB4 /* XCTHealthKit */; }; + 2FE5DCB129EE6107004B9AB4 /* AccountOnboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */; }; + 2FF53D8B2A8725DE00042B76 /* SpeziMockWebService in Frameworks */ = {isa = PBXBuildFile; productRef = 2FF53D8A2A8725DE00042B76 /* SpeziMockWebService */; }; + 2FF53D8D2A8729D600042B76 /* PAWSStandard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FF53D8C2A8729D600042B76 /* PAWSStandard.swift */; }; + 2FFD22F62B59ABE2005DD268 /* PAWSCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FFD22F52B59ABE2005DD268 /* PAWSCard.swift */; }; + 2FFD22FC2B59AED8005DD268 /* FAQ.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FFD22FB2B59AED8005DD268 /* FAQ.swift */; }; + 2FFD22FF2B59B158005DD268 /* StudyDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FFD22FE2B59B158005DD268 /* StudyDescription.swift */; }; + 2FFD23022B59B22C005DD268 /* StudyInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FFD23012B59B22C005DD268 /* StudyInformation.swift */; }; + 5661551D2AB8384200209B80 /* SwiftPackageList in Frameworks */ = {isa = PBXBuildFile; productRef = 5661551C2AB8384200209B80 /* SwiftPackageList */; }; + 566155292AB8447C00209B80 /* Package+LicenseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566155282AB8447C00209B80 /* Package+LicenseType.swift */; }; + 5661552E2AB854C000209B80 /* PackageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5661552D2AB854C000209B80 /* PackageHelper.swift */; }; + 5680DD392AB8983D004E6D4A /* PackageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5680DD382AB8983D004E6D4A /* PackageCell.swift */; }; + 5680DD3E2AB8CD84004E6D4A /* ContributionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */; }; + 56F6F2A02AB441930022FE5A /* ContributionsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56F6F29F2AB441930022FE5A /* ContributionsList.swift */; }; 653A2551283387FE005D4D48 /* PAWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A2550283387FE005D4D48 /* PAWS.swift */; }; 653A255528338800005D4D48 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 653A255428338800005D4D48 /* Assets.xcassets */; }; 653A256228338800005D4D48 /* PAWSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A256128338800005D4D48 /* PAWSTests.swift */; }; + 653A256C28338800005D4D48 /* SchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A256B28338800005D4D48 /* SchedulerTests.swift */; }; + 9733CFC62A8066DE001B7ABC /* SpeziOnboarding in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8029EDD91D004B9AB4 /* SpeziOnboarding */; }; + 9739A0C62AD7B5730084BEA5 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 9739A0C52AD7B5730084BEA5 /* FirebaseStorage */; }; + 97D73D6A2AD860AD00B47FA0 /* SpeziFirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */; }; + A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */; }; + A9D83F962B083794000D0C78 /* SpeziFirebaseAccountStorage in Frameworks */ = {isa = PBXBuildFile; productRef = A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */; }; + A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DFE8A82ABE551400428242 /* AccountButton.swift */; }; + A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -46,35 +88,72 @@ containerPortal = 653A2545283387FE005D4D48 /* Project object */; proxyType = 1; remoteGlobalIDString = 653A254C283387FE005D4D48; - remoteInfo = Blance; + remoteInfo = PAWS; }; 653A256828338800005D4D48 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 653A2545283387FE005D4D48 /* Project object */; proxyType = 1; remoteGlobalIDString = 653A254C283387FE005D4D48; - remoteInfo = Blance; + remoteInfo = PAWS; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 2F36AD23299D93E200B1077C /* NotificationsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsTests.swift; sourceTree = ""; }; - 2F49B77329803E8F00BCB272 /* PAWSModules */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = PAWSModules; sourceTree = ""; }; + 27FA298F2A388E9B009CAC45 /* ModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalView.swift; sourceTree = ""; }; + 2F1B52CD2A4F5CCE003AE151 /* MockUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUploadTests.swift; sourceTree = ""; }; 2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = ""; }; - 2F4E23822989D51F0013F3D9 /* PAWSAppTestingSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSAppTestingSetup.swift; sourceTree = ""; }; + 2F4E23822989D51F0013F3D9 /* PAWSTestingSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSTestingSetup.swift; sourceTree = ""; }; 2F4E23862989DB360013F3D9 /* ContactsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsTests.swift; sourceTree = ""; }; - 2F5E32BC297E05EA003432F8 /* PAWSAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSAppDelegate.swift; sourceTree = ""; }; - 2F9056D9299E1C97003D3802 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 2F4FC8D629EE69D300BFFE26 /* MockUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUpload.swift; sourceTree = ""; }; + 2F5E32BC297E05EA003432F8 /* PAWSDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSDelegate.swift; sourceTree = ""; }; + 2F6025CA29BBE70F0045459E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 2F65B44D2A3B8B0600A36932 /* NotificationPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissions.swift; sourceTree = ""; }; + 2FA0BFEC2ACC977500E0EF83 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; 2FAEC07F297F583900C11C42 /* PAWS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PAWS.entitlements; sourceTree = ""; }; 2FC94CD4298B0A1D009C8209 /* PAWS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = PAWS.xctestplan; sourceTree = ""; }; - 2FC9759E2978E39600BA99FE /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; + 2FC975A72978F11A00BA99FE /* Home.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Home.swift; sourceTree = ""; }; + 2FE5DC2529EDD38A004B9AB4 /* Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = ""; }; + 2FE5DC2C29EDD78E004B9AB4 /* ConsentDocument.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ConsentDocument.md; sourceTree = ""; }; + 2FE5DC2F29EDD7CA004B9AB4 /* Consent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Consent.swift; sourceTree = ""; }; + 2FE5DC3029EDD7CA004B9AB4 /* HealthKitPermissions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HealthKitPermissions.swift; sourceTree = ""; }; + 2FE5DC3129EDD7CA004B9AB4 /* OnboardingFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingFlow.swift; sourceTree = ""; }; + 2FE5DC3229EDD7CA004B9AB4 /* InterestingModules.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterestingModules.swift; sourceTree = ""; }; + 2FE5DC3429EDD7CA004B9AB4 /* Welcome.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Welcome.swift; sourceTree = ""; }; + 2FE5DC3E29EDD7ED004B9AB4 /* FeatureFlags.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureFlags.swift; sourceTree = ""; }; + 2FE5DC3F29EDD7EE004B9AB4 /* StorageKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageKeys.swift; sourceTree = ""; }; + 2FE5DC4229EDD7F2004B9AB4 /* Binding+Negate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Binding+Negate.swift"; sourceTree = ""; }; + 2FE5DC4329EDD7F2004B9AB4 /* Bundle+Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Image.swift"; sourceTree = ""; }; + 2FE5DC4429EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CodableArray+RawRepresentable.swift"; sourceTree = ""; }; + 2FE5DC4829EDD7FA004B9AB4 /* ScheduleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduleView.swift; sourceTree = ""; }; + 2FE5DC4929EDD7FA004B9AB4 /* EventContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventContext.swift; sourceTree = ""; }; + 2FE5DC4A29EDD7FA004B9AB4 /* EventContextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventContextView.swift; sourceTree = ""; }; + 2FE5DC4B29EDD7FA004B9AB4 /* PAWSTaskContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PAWSTaskContext.swift; sourceTree = ""; }; + 2FE5DC4C29EDD7FA004B9AB4 /* PAWSScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PAWSScheduler.swift; sourceTree = ""; }; + 2FE5DC4D29EDD7FA004B9AB4 /* Bundle+Questionnaire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Questionnaire.swift"; sourceTree = ""; }; + 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SocialSupportQuestionnaire.json; sourceTree = ""; }; + 2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountOnboarding.swift; sourceTree = ""; }; + 2FF53D8C2A8729D600042B76 /* PAWSStandard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PAWSStandard.swift; sourceTree = ""; }; + 2FFD22F52B59ABE2005DD268 /* PAWSCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSCard.swift; sourceTree = ""; }; + 2FFD22FB2B59AED8005DD268 /* FAQ.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAQ.swift; sourceTree = ""; }; + 2FFD22FE2B59B158005DD268 /* StudyDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyDescription.swift; sourceTree = ""; }; + 2FFD23012B59B22C005DD268 /* StudyInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyInformation.swift; sourceTree = ""; }; + 566155282AB8447C00209B80 /* Package+LicenseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Package+LicenseType.swift"; sourceTree = ""; }; + 5661552D2AB854C000209B80 /* PackageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageHelper.swift; sourceTree = ""; }; + 5680DD382AB8983D004E6D4A /* PackageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageCell.swift; sourceTree = ""; }; + 5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributionsTest.swift; sourceTree = ""; }; + 56F6F29F2AB441930022FE5A /* ContributionsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributionsList.swift; sourceTree = ""; }; 653A254D283387FE005D4D48 /* PAWS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PAWS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 653A2550283387FE005D4D48 /* PAWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWS.swift; sourceTree = ""; }; 653A255428338800005D4D48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 653A255D28338800005D4D48 /* PAWSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PAWSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 653A256128338800005D4D48 /* PAWSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSTests.swift; sourceTree = ""; }; 653A256728338800005D4D48 /* PAWSUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PAWSUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 653A256B28338800005D4D48 /* SchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchedulerTests.swift; sourceTree = ""; }; 653A258928339462005D4D48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSetupHeader.swift; sourceTree = ""; }; + A9DFE8A82ABE551400428242 /* AccountButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountButton.swift; sourceTree = ""; }; + A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSheet.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -82,25 +161,29 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 36379481299308AA00B3C006 /* PAWSNotificationScreen in Frameworks */, - 2F59B941298C628800C5107F /* PAWSOnboardingFlow in Frameworks */, - 2F9F4D8329B7FFAE00ABE259 /* CardinalKitHealthKitToFHIRAdapter in Frameworks */, - 2F49B7822980419C00BCB272 /* Questionnaires in Frameworks */, - 2F49B77E2980407C00BCB272 /* Scheduler in Frameworks */, - 2F9056D3299E1B9C003D3802 /* FHIRToFirestoreAdapter in Frameworks */, - 2FABBC5B29A8975900DF3BBC /* FirebaseFirestoreSwift in Frameworks */, - 2FABBC5929A8975900DF3BBC /* FirebaseFirestore in Frameworks */, - 3637947E2992F3B000B3C006 /* PAWSLandingScreen in Frameworks */, - 2F9056D5299E1B9C003D3802 /* FirestoreDataStorage in Frameworks */, - 2F59B93F298C628800C5107F /* PAWSMockDataStorageProvider in Frameworks */, - 2F49B77A2980407C00BCB272 /* HealthKitDataSource in Frameworks */, - 2F59B945298C628800C5107F /* PAWSSharedContext in Frameworks */, - 2F49B784298041F300BCB272 /* Contact in Frameworks */, - 2F9056D7299E1B9C003D3802 /* FirestoreStoragePrefixUserIdAdapter in Frameworks */, - 2F49B7782980407C00BCB272 /* FHIR in Frameworks */, - 2F49B7802980418400BCB272 /* Onboarding in Frameworks */, - 2F49B7762980407C00BCB272 /* CardinalKit in Frameworks */, - 2F59B93D298C628800C5107F /* PAWSContacts in Frameworks */, + 9733CFC62A8066DE001B7ABC /* SpeziOnboarding in Frameworks */, + 2FE5DC6429EDD883004B9AB4 /* SpeziAccount in Frameworks */, + 2FB099AF2A875DF100B20952 /* FirebaseAuth in Frameworks */, + 97D73D6A2AD860AD00B47FA0 /* SpeziFirebaseStorage in Frameworks */, + 2FE5DC6729EDD894004B9AB4 /* SpeziContact in Frameworks */, + 2FE5DC8429EDD934004B9AB4 /* SpeziQuestionnaire in Frameworks */, + 2FB099B32A875DF100B20952 /* FirebaseFirestoreSwift in Frameworks */, + 5661551D2AB8384200209B80 /* SwiftPackageList in Frameworks */, + 2FB099B12A875DF100B20952 /* FirebaseFirestore in Frameworks */, + A9D83F962B083794000D0C78 /* SpeziFirebaseAccountStorage in Frameworks */, + 2FB099B62A875E2B00B20952 /* HealthKitOnFHIR in Frameworks */, + 2FE5DC8A29EDD972004B9AB4 /* SpeziLocalStorage in Frameworks */, + 2FE5DC8C29EDD972004B9AB4 /* SpeziSecureStorage in Frameworks */, + 2FE5DC7529EDD8E6004B9AB4 /* SpeziFirebaseAccount in Frameworks */, + 9739A0C62AD7B5730084BEA5 /* FirebaseStorage in Frameworks */, + 2FF53D8B2A8725DE00042B76 /* SpeziMockWebService in Frameworks */, + 2FE5DC7229EDD8D3004B9AB4 /* SpeziHealthKit in Frameworks */, + 2F49B7762980407C00BCB272 /* Spezi in Frameworks */, + 2FE5DC8F29EDD980004B9AB4 /* SpeziViews in Frameworks */, + 2F3D4ABC2A4E7C290068FB2F /* SpeziScheduler in Frameworks */, + 2FBD738C2A3BD150004228E7 /* SpeziScheduler in Frameworks */, + 2FE5DC7929EDD8E6004B9AB4 /* SpeziFirestore in Frameworks */, + 2FE5DC7729EDD8E6004B9AB4 /* SpeziFirebaseConfiguration in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -115,31 +198,124 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 2F4E23812989C5930013F3D9 /* XCTHealthKit in Frameworks */, - 2F36AD27299D989F00B1077C /* XCTestExtensions in Frameworks */, + 2FE5DC9929EDD9D9004B9AB4 /* XCTestExtensions in Frameworks */, + 2FE5DC9C29EDD9EF004B9AB4 /* XCTHealthKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2F4FC8D529EE69BE00BFFE26 /* MockUpload */ = { + isa = PBXGroup; + children = ( + 2F4FC8D629EE69D300BFFE26 /* MockUpload.swift */, + ); + path = MockUpload; + sourceTree = ""; + }; 2FC9759D2978E30800BA99FE /* Supporting Files */ = { isa = PBXGroup; children = ( 2FAEC07F297F583900C11C42 /* PAWS.entitlements */, 653A258928339462005D4D48 /* Info.plist */, - 2F9056D9299E1C97003D3802 /* GoogleService-Info.plist */, - 653A255428338800005D4D48 /* Assets.xcassets */, - 2FC9759E2978E39600BA99FE /* Localizable.strings */, + 2F6025CA29BBE70F0045459E /* GoogleService-Info.plist */, ); path = "Supporting Files"; sourceTree = ""; }; + 2FE5DC2729EDD38D004B9AB4 /* Contacts */ = { + isa = PBXGroup; + children = ( + 2FE5DC2529EDD38A004B9AB4 /* Contacts.swift */, + ); + path = Contacts; + sourceTree = ""; + }; + 2FE5DC2829EDD398004B9AB4 /* Onboarding */ = { + isa = PBXGroup; + children = ( + 2FE5DC3129EDD7CA004B9AB4 /* OnboardingFlow.swift */, + 2FE5DC3429EDD7CA004B9AB4 /* Welcome.swift */, + 2FE5DC3229EDD7CA004B9AB4 /* InterestingModules.swift */, + 2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */, + 2FE5DC2F29EDD7CA004B9AB4 /* Consent.swift */, + 2FE5DC3029EDD7CA004B9AB4 /* HealthKitPermissions.swift */, + 2F65B44D2A3B8B0600A36932 /* NotificationPermissions.swift */, + ); + path = Onboarding; + sourceTree = ""; + }; + 2FE5DC2D29EDD792004B9AB4 /* Resources */ = { + isa = PBXGroup; + children = ( + 653A255428338800005D4D48 /* Assets.xcassets */, + 2FA0BFEC2ACC977500E0EF83 /* Localizable.xcstrings */, + 2FE5DC2C29EDD78E004B9AB4 /* ConsentDocument.md */, + 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */, + ); + path = Resources; + sourceTree = ""; + }; + 2FE5DC3B29EDD7D0004B9AB4 /* Schedule */ = { + isa = PBXGroup; + children = ( + 2FE5DC4D29EDD7FA004B9AB4 /* Bundle+Questionnaire.swift */, + 2FE5DC4929EDD7FA004B9AB4 /* EventContext.swift */, + 2FE5DC4A29EDD7FA004B9AB4 /* EventContextView.swift */, + 2FE5DC4829EDD7FA004B9AB4 /* ScheduleView.swift */, + 2FE5DC4C29EDD7FA004B9AB4 /* PAWSScheduler.swift */, + 2FE5DC4B29EDD7FA004B9AB4 /* PAWSTaskContext.swift */, + 27FA298F2A388E9B009CAC45 /* ModalView.swift */, + ); + path = Schedule; + sourceTree = ""; + }; + 2FE5DC3C29EDD7DA004B9AB4 /* SharedContext */ = { + isa = PBXGroup; + children = ( + 2FE5DC3E29EDD7ED004B9AB4 /* FeatureFlags.swift */, + 2FE5DC3F29EDD7EE004B9AB4 /* StorageKeys.swift */, + 2FFD22F52B59ABE2005DD268 /* PAWSCard.swift */, + ); + path = SharedContext; + sourceTree = ""; + }; + 2FE5DC3D29EDD7E4004B9AB4 /* Helper */ = { + isa = PBXGroup; + children = ( + 2FE5DC4229EDD7F2004B9AB4 /* Binding+Negate.swift */, + 2FE5DC4329EDD7F2004B9AB4 /* Bundle+Image.swift */, + 2FE5DC4429EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift */, + ); + path = Helper; + sourceTree = ""; + }; + 2FFD22F82B59AECC005DD268 /* Study Information */ = { + isa = PBXGroup; + children = ( + 2FFD22FB2B59AED8005DD268 /* FAQ.swift */, + 2FFD22FE2B59B158005DD268 /* StudyDescription.swift */, + 2FFD23012B59B22C005DD268 /* StudyInformation.swift */, + ); + path = "Study Information"; + sourceTree = ""; + }; + 56F6F29E2AB441640022FE5A /* Contributions */ = { + isa = PBXGroup; + children = ( + 56F6F29F2AB441930022FE5A /* ContributionsList.swift */, + 5680DD382AB8983D004E6D4A /* PackageCell.swift */, + 566155282AB8447C00209B80 /* Package+LicenseType.swift */, + 5661552D2AB854C000209B80 /* PackageHelper.swift */, + ); + path = Contributions; + sourceTree = ""; + }; 653A2544283387FE005D4D48 = { isa = PBXGroup; children = ( 2FC94CD4298B0A1D009C8209 /* PAWS.xctestplan */, - 2F49B77329803E8F00BCB272 /* PAWSModules */, 653A254F283387FE005D4D48 /* PAWS */, 653A256028338800005D4D48 /* PAWSTests */, 653A256A28338800005D4D48 /* PAWSUITests */, @@ -162,8 +338,20 @@ isa = PBXGroup; children = ( 653A2550283387FE005D4D48 /* PAWS.swift */, - 2F5E32BC297E05EA003432F8 /* PAWSAppDelegate.swift */, - 2F4E23822989D51F0013F3D9 /* PAWSAppTestingSetup.swift */, + 2F5E32BC297E05EA003432F8 /* PAWSDelegate.swift */, + 2FF53D8C2A8729D600042B76 /* PAWSStandard.swift */, + 2F4E23822989D51F0013F3D9 /* PAWSTestingSetup.swift */, + 2FC975A72978F11A00BA99FE /* Home.swift */, + A9720E412ABB68B300872D23 /* Account */, + 2FE5DC2829EDD398004B9AB4 /* Onboarding */, + 2FE5DC3B29EDD7D0004B9AB4 /* Schedule */, + 2FFD22F82B59AECC005DD268 /* Study Information */, + 2FE5DC2729EDD38D004B9AB4 /* Contacts */, + 56F6F29E2AB441640022FE5A /* Contributions */, + 2F4FC8D529EE69BE00BFFE26 /* MockUpload */, + 2FE5DC3C29EDD7DA004B9AB4 /* SharedContext */, + 2FE5DC3D29EDD7E4004B9AB4 /* Helper */, + 2FE5DC2D29EDD792004B9AB4 /* Resources */, 2FC9759D2978E30800BA99FE /* Supporting Files */, ); path = PAWS; @@ -181,8 +369,10 @@ isa = PBXGroup; children = ( 2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */, + 653A256B28338800005D4D48 /* SchedulerTests.swift */, 2F4E23862989DB360013F3D9 /* ContactsTests.swift */, - 2F36AD23299D93E200B1077C /* NotificationsTests.swift */, + 2F1B52CD2A4F5CCE003AE151 /* MockUploadTests.swift */, + 5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */, ); path = PAWSUITests; sourceTree = ""; @@ -194,6 +384,16 @@ name = Frameworks; sourceTree = ""; }; + A9720E412ABB68B300872D23 /* Account */ = { + isa = PBXGroup; + children = ( + A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */, + A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */, + A9DFE8A82ABE551400428242 /* AccountButton.swift */, + ); + path = Account; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -204,35 +404,40 @@ 653A2549283387FE005D4D48 /* Sources */, 653A254A283387FE005D4D48 /* Frameworks */, 653A254B283387FE005D4D48 /* Resources */, - 2FDB275429AEB6DC0084C7DF /* ShellScript */, + 2F5B528D29BD237B002020B7 /* ShellScript */, ); buildRules = ( ); dependencies = ( + 566155222AB83CF200209B80 /* PBXTargetDependency */, ); name = PAWS; packageProductDependencies = ( - 2F49B7752980407B00BCB272 /* CardinalKit */, - 2F49B7772980407C00BCB272 /* FHIR */, - 2F49B7792980407C00BCB272 /* HealthKitDataSource */, - 2F49B77D2980407C00BCB272 /* Scheduler */, - 2F49B77F2980418400BCB272 /* Onboarding */, - 2F49B7812980419C00BCB272 /* Questionnaires */, - 2F49B783298041F300BCB272 /* Contact */, - 2F59B93C298C628800C5107F /* PAWSContacts */, - 2F59B93E298C628800C5107F /* PAWSMockDataStorageProvider */, - 2F59B940298C628800C5107F /* PAWSOnboardingFlow */, - 2F59B944298C628800C5107F /* PAWSSharedContext */, - 3637947D2992F3B000B3C006 /* PAWSLandingScreen */, - 36379480299308AA00B3C006 /* PAWSNotificationScreen */, - 2F9056D2299E1B9C003D3802 /* FHIRToFirestoreAdapter */, - 2F9056D4299E1B9C003D3802 /* FirestoreDataStorage */, - 2F9056D6299E1B9C003D3802 /* FirestoreStoragePrefixUserIdAdapter */, - 2FABBC5829A8975900DF3BBC /* FirebaseFirestore */, - 2FABBC5A29A8975900DF3BBC /* FirebaseFirestoreSwift */, - 2F9F4D8229B7FFAE00ABE259 /* CardinalKitHealthKitToFHIRAdapter */, + 2F49B7752980407B00BCB272 /* Spezi */, + 2FE5DC6329EDD883004B9AB4 /* SpeziAccount */, + 2FE5DC6629EDD894004B9AB4 /* SpeziContact */, + 2FE5DC7129EDD8D3004B9AB4 /* SpeziHealthKit */, + 2FE5DC7429EDD8E6004B9AB4 /* SpeziFirebaseAccount */, + 2FE5DC7629EDD8E6004B9AB4 /* SpeziFirebaseConfiguration */, + 2FE5DC7829EDD8E6004B9AB4 /* SpeziFirestore */, + 2FE5DC8329EDD934004B9AB4 /* SpeziQuestionnaire */, + 2FE5DC8929EDD972004B9AB4 /* SpeziLocalStorage */, + 2FE5DC8B29EDD972004B9AB4 /* SpeziSecureStorage */, + 2FE5DC8E29EDD980004B9AB4 /* SpeziViews */, + 2FBD738B2A3BD150004228E7 /* SpeziScheduler */, + 2F3D4ABB2A4E7C290068FB2F /* SpeziScheduler */, + 2FE5DC8029EDD91D004B9AB4 /* SpeziOnboarding */, + 2FF53D8A2A8725DE00042B76 /* SpeziMockWebService */, + 2FB099AE2A875DF100B20952 /* FirebaseAuth */, + 2FB099B02A875DF100B20952 /* FirebaseFirestore */, + 2FB099B22A875DF100B20952 /* FirebaseFirestoreSwift */, + 2FB099B52A875E2B00B20952 /* HealthKitOnFHIR */, + 5661551C2AB8384200209B80 /* SwiftPackageList */, + 9739A0C52AD7B5730084BEA5 /* FirebaseStorage */, + 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */, + A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */, ); - productName = TemplateApplication; + productName = PAWS; productReference = 653A254D283387FE005D4D48 /* PAWS.app */; productType = "com.apple.product-type.application"; }; @@ -250,7 +455,7 @@ 653A255F28338800005D4D48 /* PBXTargetDependency */, ); name = PAWSTests; - productName = TemplateApplicationTests; + productName = PAWSTests; productReference = 653A255D28338800005D4D48 /* PAWSTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; @@ -269,10 +474,10 @@ ); name = PAWSUITests; packageProductDependencies = ( - 2F4E23802989C5930013F3D9 /* XCTHealthKit */, - 2F36AD26299D989F00B1077C /* XCTestExtensions */, + 2FE5DC9829EDD9D9004B9AB4 /* XCTestExtensions */, + 2FE5DC9B29EDD9EF004B9AB4 /* XCTHealthKit */, ); - productName = TemplateApplicationUITests; + productName = PAWSUITests; productReference = 653A256728338800005D4D48 /* PAWSUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; @@ -284,7 +489,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1340; - LastUpgradeCheck = 1340; + LastUpgradeCheck = 1520; TargetAttributes = { 653A254C283387FE005D4D48 = { CreatedOnToolsVersion = 13.4; @@ -309,11 +514,22 @@ ); mainGroup = 653A2544283387FE005D4D48; packageReferences = ( - 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */, - 2F4E237F2989C5930013F3D9 /* XCRemoteSwiftPackageReference "XCTHealthKit" */, - 2F36AD25299D989F00B1077C /* XCRemoteSwiftPackageReference "XCTestExtensions" */, - 2FABBC5729A8975900DF3BBC /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, - 2F9F4D8129B7FFAE00ABE259 /* XCRemoteSwiftPackageReference "CardinalKitHealthKitToFHIRAdapter" */, + 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "Spezi" */, + 2FE5DC6229EDD883004B9AB4 /* XCRemoteSwiftPackageReference "SpeziAccount" */, + 2FE5DC6529EDD894004B9AB4 /* XCRemoteSwiftPackageReference "SpeziContact" */, + 2FE5DC7029EDD8D3004B9AB4 /* XCRemoteSwiftPackageReference "SpeziHealthKit" */, + 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */, + 2FE5DC8229EDD934004B9AB4 /* XCRemoteSwiftPackageReference "SpeziQuestionnaire" */, + 2FE5DC8829EDD972004B9AB4 /* XCRemoteSwiftPackageReference "SpeziStorage" */, + 2FE5DC8D29EDD980004B9AB4 /* XCRemoteSwiftPackageReference "SpeziViews" */, + 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + 2FE5DC9729EDD9D9004B9AB4 /* XCRemoteSwiftPackageReference "XCTestExtensions" */, + 2FE5DC9A29EDD9EF004B9AB4 /* XCRemoteSwiftPackageReference "XCTHealthKit" */, + 2F3D4ABA2A4E7C290068FB2F /* XCRemoteSwiftPackageReference "SpeziScheduler" */, + 97F466E62A76BBEE005DC9B4 /* XCRemoteSwiftPackageReference "SpeziOnboarding" */, + 2FE750CA2A87240100723EAE /* XCRemoteSwiftPackageReference "SpeziMockWebService" */, + 2FB099B42A875E2B00B20952 /* XCRemoteSwiftPackageReference "HealthKitOnFHIR" */, + 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */, ); productRefGroup = 653A254E283387FE005D4D48 /* Products */; projectDirPath = ""; @@ -331,9 +547,11 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 2F9056DB299E1C97003D3802 /* GoogleService-Info.plist in Resources */, + 2FC3439229EE634B002D773C /* ConsentDocument.md in Resources */, 653A255528338800005D4D48 /* Assets.xcassets in Resources */, - 2FC9759F2978E39600BA99FE /* Localizable.strings in Resources */, + 2FC3439029EE6346002D773C /* SocialSupportQuestionnaire.json in Resources */, + 2FA0BFED2ACC977500E0EF83 /* Localizable.xcstrings in Resources */, + 2F6025CB29BBE70F0045459E /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -354,7 +572,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 2FDB275429AEB6DC0084C7DF /* ShellScript */ = { + 2F5B528D29BD237B002020B7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; @@ -379,9 +597,43 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 2F4E23832989D51F0013F3D9 /* PAWSAppTestingSetup.swift in Sources */, - 2F5E32BD297E05EA003432F8 /* PAWSAppDelegate.swift in Sources */, + 2FE5DC4129EDD7EE004B9AB4 /* StorageKeys.swift in Sources */, + 2FE5DCB129EE6107004B9AB4 /* AccountOnboarding.swift in Sources */, + 2F4FC8D729EE69D300BFFE26 /* MockUpload.swift in Sources */, + 2FE5DC3A29EDD7CA004B9AB4 /* Welcome.swift in Sources */, + 2FE5DC3829EDD7CA004B9AB4 /* InterestingModules.swift in Sources */, + 2FE5DC3529EDD7CA004B9AB4 /* Consent.swift in Sources */, + 2FE5DC4529EDD7F2004B9AB4 /* Binding+Negate.swift in Sources */, + 2FFD23022B59B22C005DD268 /* StudyInformation.swift in Sources */, + 2FC975A82978F11A00BA99FE /* Home.swift in Sources */, + 2FE5DC4E29EDD7FA004B9AB4 /* ScheduleView.swift in Sources */, + A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */, + 2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */, + 2FF53D8D2A8729D600042B76 /* PAWSStandard.swift in Sources */, + 2FE5DC4729EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift in Sources */, + A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */, + 2FE5DC4029EDD7EE004B9AB4 /* FeatureFlags.swift in Sources */, + 2FE5DC4629EDD7F2004B9AB4 /* Bundle+Image.swift in Sources */, + 2FE5DC4F29EDD7FA004B9AB4 /* EventContext.swift in Sources */, + 2FE5DC5029EDD7FA004B9AB4 /* EventContextView.swift in Sources */, + 2FFD22FC2B59AED8005DD268 /* FAQ.swift in Sources */, + 2F4E23832989D51F0013F3D9 /* PAWSTestingSetup.swift in Sources */, + 2FE5DC5329EDD7FA004B9AB4 /* Bundle+Questionnaire.swift in Sources */, + 2FE5DC5129EDD7FA004B9AB4 /* PAWSTaskContext.swift in Sources */, + 56F6F2A02AB441930022FE5A /* ContributionsList.swift in Sources */, + 566155292AB8447C00209B80 /* Package+LicenseType.swift in Sources */, + 2FFD22FF2B59B158005DD268 /* StudyDescription.swift in Sources */, + 5680DD392AB8983D004E6D4A /* PackageCell.swift in Sources */, + 2F5E32BD297E05EA003432F8 /* PAWSDelegate.swift in Sources */, + 2FE5DC5229EDD7FA004B9AB4 /* PAWSScheduler.swift in Sources */, + A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */, 653A2551283387FE005D4D48 /* PAWS.swift in Sources */, + 2FFD22F62B59ABE2005DD268 /* PAWSCard.swift in Sources */, + 2FE5DC3629EDD7CA004B9AB4 /* HealthKitPermissions.swift in Sources */, + 2F65B44E2A3B8B0600A36932 /* NotificationPermissions.swift in Sources */, + 5661552E2AB854C000209B80 /* PackageHelper.swift in Sources */, + 27FA29902A388E9B009CAC45 /* ModalView.swift in Sources */, + 2FE5DC2629EDD38A004B9AB4 /* Contacts.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -397,15 +649,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5680DD3E2AB8CD84004E6D4A /* ContributionsTest.swift in Sources */, 2F4E23872989DB360013F3D9 /* ContactsTests.swift in Sources */, - 2F36AD24299D93E200B1077C /* NotificationsTests.swift in Sources */, 2F4E237E2989A2FE0013F3D9 /* OnboardingTests.swift in Sources */, + 2F1B52CE2A4F5CCE003AE151 /* MockUploadTests.swift in Sources */, + 653A256C28338800005D4D48 /* SchedulerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 566155222AB83CF200209B80 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = 566155212AB83CF200209B80 /* SwiftPackageListJSONPlugin */; + }; 653A255F28338800005D4D48 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 653A254C283387FE005D4D48 /* PAWS */; @@ -419,10 +677,11 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 2F82DB262A80C04F0029346E /* Test */ = { + 2FEE10302998C89C000822E1 /* Test */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -455,6 +714,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -469,17 +729,18 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = TEST; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Test; }; - 2F82DB272A80C04F0029346E /* Test */ = { + 2FEE10312998C89C000822E1 /* Test */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -493,21 +754,20 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "PAWS/Supporting Files/Info.plist"; - INFOPLIST_KEY_CFBundleDisplayName = PAWS; INFOPLIST_KEY_NSCameraUsageDescription = "This message should never appear. Please adjust this when you start using camera information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; - INFOPLIST_KEY_NSHealthShareUsageDescription = "The PAWS Application uses the heart rate information to extract ECG data and provide it to the researchers. "; - INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The PAWS Application uses the heart rate information to extract ECG data and provide it to the researchers. "; + INFOPLIST_KEY_NSHealthShareUsageDescription = "The PAWS uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The PAWS uses the step count to demonstrate Spezi's integration with HealthKit."; INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSMicrophoneUsageDescription = "This message should never appear. Please adjust this when you start using microphone information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSMotionUsageDescription = "This message should never appear. Please adjust this when you start using motion information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "This message should never appear. Please adjust this when you start using speecg information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UIPAWSlicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIPAWSlicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - INFOPLIST_KEY_UITemplateApplicationlicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UITemplateApplicationlicationSupportsIndirectInputEvents = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -519,13 +779,14 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - "SWIFT_ELicenseRef-TemplateApplication_LOC_STRINGS" = YES; + "SWIFT_ELicenseRef-PAWS_LOC_STRINGS" = YES; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Test; }; - 2F82DB282A80C04F0029346E /* Test */ = { + 2FEE10322998C89C000822E1 /* Test */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; @@ -534,18 +795,18 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 637867499T; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.2023.paws.tests; PRODUCT_NAME = "$(TARGET_NAME)"; - "SWIFT_ELicenseRef-TemplateApplication_LOC_STRINGS" = NO; + "SWIFT_ELicenseRef-PAWS_LOC_STRINGS" = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PAWS.app/PAWS"; }; name = Test; }; - 2F82DB292A80C04F0029346E /* Test */ = { + 2FEE10332998C89C000822E1 /* Test */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; @@ -553,11 +814,11 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 637867499T; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.2023.paws.uitests; + PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.2023.pawsuitests; PRODUCT_NAME = "$(TARGET_NAME)"; - "SWIFT_ELicenseRef-TemplateApplication_LOC_STRINGS" = NO; + "SWIFT_ELicenseRef-PAWS_LOC_STRINGS" = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = PAWS; @@ -568,6 +829,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -600,6 +862,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -614,7 +877,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -628,6 +892,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -660,6 +925,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -668,7 +934,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -692,21 +959,20 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "PAWS/Supporting Files/Info.plist"; - INFOPLIST_KEY_CFBundleDisplayName = PAWS; INFOPLIST_KEY_NSCameraUsageDescription = "This message should never appear. Please adjust this when you start using camera information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; - INFOPLIST_KEY_NSHealthShareUsageDescription = "The PAWS Application uses the heart rate information to extract ECG data and provide it to the researchers. "; - INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The PAWS Application uses the heart rate information to extract ECG data and provide it to the researchers. "; + INFOPLIST_KEY_NSHealthShareUsageDescription = "The PAWS uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The PAWS uses the step count to demonstrate Spezi's integration with HealthKit."; INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSMicrophoneUsageDescription = "This message should never appear. Please adjust this when you start using microphone information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSMotionUsageDescription = "This message should never appear. Please adjust this when you start using motion information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "This message should never appear. Please adjust this when you start using speecg information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UIPAWSlicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIPAWSlicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - INFOPLIST_KEY_UITemplateApplicationlicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UITemplateApplicationlicationSupportsIndirectInputEvents = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -718,7 +984,8 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - "SWIFT_ELicenseRef-TemplateApplication_LOC_STRINGS" = YES; + "SWIFT_ELicenseRef-PAWS_LOC_STRINGS" = YES; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; @@ -730,6 +997,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "PAWS/Supporting Files/PAWS.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; @@ -739,21 +1007,20 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "PAWS/Supporting Files/Info.plist"; - INFOPLIST_KEY_CFBundleDisplayName = PAWS; INFOPLIST_KEY_NSCameraUsageDescription = "This message should never appear. Please adjust this when you start using camera information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; - INFOPLIST_KEY_NSHealthShareUsageDescription = "The PAWS Application uses the heart rate information to extract ECG data and provide it to the researchers. "; - INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The PAWS Application uses the heart rate information to extract ECG data and provide it to the researchers. "; + INFOPLIST_KEY_NSHealthShareUsageDescription = "The PAWS uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The PAWS uses the step count to demonstrate Spezi's integration with HealthKit."; INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "This message should never appear. Please adjust this when you start using location information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSMicrophoneUsageDescription = "This message should never appear. Please adjust this when you start using microphone information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSMotionUsageDescription = "This message should never appear. Please adjust this when you start using motion information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "This message should never appear. Please adjust this when you start using speecg information. We have to put this in here as ResearchKit has the possibility to use it and not putting it here returns an error on AppStore Connect."; INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UIPAWSlicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIPAWSlicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - INFOPLIST_KEY_UITemplateApplicationlicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UITemplateApplicationlicationSupportsIndirectInputEvents = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -766,7 +1033,8 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - "SWIFT_ELicenseRef-TemplateApplication_LOC_STRINGS" = YES; + "SWIFT_ELicenseRef-PAWS_LOC_STRINGS" = YES; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; @@ -781,11 +1049,11 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 637867499T; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.2023.paws.tests; PRODUCT_NAME = "$(TARGET_NAME)"; - "SWIFT_ELicenseRef-TemplateApplication_LOC_STRINGS" = NO; + "SWIFT_ELicenseRef-PAWS_LOC_STRINGS" = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PAWS.app/PAWS"; @@ -801,11 +1069,11 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 637867499T; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.2023.paws.tests; PRODUCT_NAME = "$(TARGET_NAME)"; - "SWIFT_ELicenseRef-TemplateApplication_LOC_STRINGS" = NO; + "SWIFT_ELicenseRef-PAWS_LOC_STRINGS" = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PAWS.app/PAWS"; @@ -820,11 +1088,11 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 637867499T; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.2023.paws.uitests; + PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.2023.pawsuitests; PRODUCT_NAME = "$(TARGET_NAME)"; - "SWIFT_ELicenseRef-TemplateApplication_LOC_STRINGS" = NO; + "SWIFT_ELicenseRef-PAWS_LOC_STRINGS" = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = PAWS; @@ -839,11 +1107,11 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 637867499T; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.2023.paws.uitests; + PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.2023.pawsuitests; PRODUCT_NAME = "$(TARGET_NAME)"; - "SWIFT_ELicenseRef-TemplateApplication_LOC_STRINGS" = NO; + "SWIFT_ELicenseRef-PAWS_LOC_STRINGS" = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = PAWS; @@ -857,7 +1125,7 @@ isa = XCConfigurationList; buildConfigurations = ( 653A256F28338800005D4D48 /* Debug */, - 2F82DB262A80C04F0029346E /* Test */, + 2FEE10302998C89C000822E1 /* Test */, 653A257028338800005D4D48 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -867,7 +1135,7 @@ isa = XCConfigurationList; buildConfigurations = ( 653A257228338800005D4D48 /* Debug */, - 2F82DB272A80C04F0029346E /* Test */, + 2FEE10312998C89C000822E1 /* Test */, 653A257328338800005D4D48 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -877,7 +1145,7 @@ isa = XCConfigurationList; buildConfigurations = ( 653A257528338800005D4D48 /* Debug */, - 2F82DB282A80C04F0029346E /* Test */, + 2FEE10322998C89C000822E1 /* Test */, 653A257628338800005D4D48 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -887,7 +1155,7 @@ isa = XCConfigurationList; buildConfigurations = ( 653A257828338800005D4D48 /* Debug */, - 2F82DB292A80C04F0029346E /* Test */, + 2FEE10332998C89C000822E1 /* Test */, 653A257928338800005D4D48 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -896,147 +1164,265 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 2F36AD25299D989F00B1077C /* XCRemoteSwiftPackageReference "XCTestExtensions" */ = { + 2F3D4ABA2A4E7C290068FB2F /* XCRemoteSwiftPackageReference "SpeziScheduler" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/StanfordBDHG/XCTestExtensions"; + repositoryURL = "https://github.com/StanfordSpezi/SpeziScheduler.git"; requirement = { kind = upToNextMinorVersion; - minimumVersion = 0.4.6; + minimumVersion = 0.8.0; }; }; - 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */ = { + 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "Spezi" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/StanfordBDHG/CardinalKit"; + repositoryURL = "https://github.com/StanfordSpezi/Spezi"; requirement = { - kind = upToNextMinorVersion; - minimumVersion = 0.3.5; + kind = upToNextMajorVersion; + minimumVersion = 1.1.0; }; }; - 2F4E237F2989C5930013F3D9 /* XCRemoteSwiftPackageReference "XCTHealthKit" */ = { + 2FB099B42A875E2B00B20952 /* XCRemoteSwiftPackageReference "HealthKitOnFHIR" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/StanfordBDHG/XCTHealthKit"; + repositoryURL = "https://github.com/StanfordBDHG/HealthKitOnFHIR.git"; requirement = { kind = upToNextMinorVersion; - minimumVersion = 0.3.5; + minimumVersion = 0.2.4; + }; + }; + 2FE5DC6229EDD883004B9AB4 /* XCRemoteSwiftPackageReference "SpeziAccount" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/SpeziAccount.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.0; + }; + }; + 2FE5DC6529EDD894004B9AB4 /* XCRemoteSwiftPackageReference "SpeziContact" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/SpeziContact.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; }; }; - 2F9F4D8129B7FFAE00ABE259 /* XCRemoteSwiftPackageReference "CardinalKitHealthKitToFHIRAdapter" */ = { + 2FE5DC7029EDD8D3004B9AB4 /* XCRemoteSwiftPackageReference "SpeziHealthKit" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/StanfordBDHG/CardinalKitHealthKitToFHIRAdapter"; + repositoryURL = "https://github.com/StanfordSpezi/SpeziHealthKit.git"; requirement = { kind = upToNextMinorVersion; - minimumVersion = 0.1.1; + minimumVersion = 0.5.0; + }; + }; + 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/SpeziFirebase.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; + 2FE5DC8229EDD934004B9AB4 /* XCRemoteSwiftPackageReference "SpeziQuestionnaire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/SpeziQuestionnaire.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; + 2FE5DC8829EDD972004B9AB4 /* XCRemoteSwiftPackageReference "SpeziStorage" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/SpeziStorage.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; }; }; - 2FABBC5729A8975900DF3BBC /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + 2FE5DC8D29EDD980004B9AB4 /* XCRemoteSwiftPackageReference "SpeziViews" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/SpeziViews.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; + 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 10.5.0; + minimumVersion = 10.17.0; + }; + }; + 2FE5DC9729EDD9D9004B9AB4 /* XCRemoteSwiftPackageReference "XCTestExtensions" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordBDHG/XCTestExtensions.git"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 0.4.7; + }; + }; + 2FE5DC9A29EDD9EF004B9AB4 /* XCRemoteSwiftPackageReference "XCTHealthKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordBDHG/XCTHealthKit.git"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 0.3.5; + }; + }; + 2FE750CA2A87240100723EAE /* XCRemoteSwiftPackageReference "SpeziMockWebService" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/SpeziMockWebService.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; + 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/FelixHerrmann/swift-package-list"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.0.10; + }; + }; + 97F466E62A76BBEE005DC9B4 /* XCRemoteSwiftPackageReference "SpeziOnboarding" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/SpeziOnboarding"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 2F36AD26299D989F00B1077C /* XCTestExtensions */ = { + 2F3D4ABB2A4E7C290068FB2F /* SpeziScheduler */ = { isa = XCSwiftPackageProductDependency; - package = 2F36AD25299D989F00B1077C /* XCRemoteSwiftPackageReference "XCTestExtensions" */; - productName = XCTestExtensions; + package = 2F3D4ABA2A4E7C290068FB2F /* XCRemoteSwiftPackageReference "SpeziScheduler" */; + productName = SpeziScheduler; }; - 2F49B7752980407B00BCB272 /* CardinalKit */ = { + 2F49B7752980407B00BCB272 /* Spezi */ = { isa = XCSwiftPackageProductDependency; - package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */; - productName = CardinalKit; + package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "Spezi" */; + productName = Spezi; }; - 2F49B7772980407C00BCB272 /* FHIR */ = { + 2FB099AE2A875DF100B20952 /* FirebaseAuth */ = { isa = XCSwiftPackageProductDependency; - package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */; - productName = FHIR; + package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAuth; }; - 2F49B7792980407C00BCB272 /* HealthKitDataSource */ = { + 2FB099B02A875DF100B20952 /* FirebaseFirestore */ = { isa = XCSwiftPackageProductDependency; - package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */; - productName = HealthKitDataSource; + package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseFirestore; }; - 2F49B77D2980407C00BCB272 /* Scheduler */ = { + 2FB099B22A875DF100B20952 /* FirebaseFirestoreSwift */ = { isa = XCSwiftPackageProductDependency; - package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */; - productName = Scheduler; + package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseFirestoreSwift; }; - 2F49B77F2980418400BCB272 /* Onboarding */ = { + 2FB099B52A875E2B00B20952 /* HealthKitOnFHIR */ = { isa = XCSwiftPackageProductDependency; - package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */; - productName = Onboarding; + package = 2FB099B42A875E2B00B20952 /* XCRemoteSwiftPackageReference "HealthKitOnFHIR" */; + productName = HealthKitOnFHIR; }; - 2F49B7812980419C00BCB272 /* Questionnaires */ = { + 2FBD738B2A3BD150004228E7 /* SpeziScheduler */ = { isa = XCSwiftPackageProductDependency; - package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */; - productName = Questionnaires; + productName = SpeziScheduler; }; - 2F49B783298041F300BCB272 /* Contact */ = { + 2FE5DC6329EDD883004B9AB4 /* SpeziAccount */ = { isa = XCSwiftPackageProductDependency; - package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */; - productName = Contact; + package = 2FE5DC6229EDD883004B9AB4 /* XCRemoteSwiftPackageReference "SpeziAccount" */; + productName = SpeziAccount; }; - 2F4E23802989C5930013F3D9 /* XCTHealthKit */ = { + 2FE5DC6629EDD894004B9AB4 /* SpeziContact */ = { isa = XCSwiftPackageProductDependency; - package = 2F4E237F2989C5930013F3D9 /* XCRemoteSwiftPackageReference "XCTHealthKit" */; - productName = XCTHealthKit; + package = 2FE5DC6529EDD894004B9AB4 /* XCRemoteSwiftPackageReference "SpeziContact" */; + productName = SpeziContact; }; - 2F59B93C298C628800C5107F /* PAWSContacts */ = { + 2FE5DC7129EDD8D3004B9AB4 /* SpeziHealthKit */ = { isa = XCSwiftPackageProductDependency; - productName = PAWSContacts; + package = 2FE5DC7029EDD8D3004B9AB4 /* XCRemoteSwiftPackageReference "SpeziHealthKit" */; + productName = SpeziHealthKit; }; - 2F59B93E298C628800C5107F /* PAWSMockDataStorageProvider */ = { + 2FE5DC7429EDD8E6004B9AB4 /* SpeziFirebaseAccount */ = { isa = XCSwiftPackageProductDependency; - productName = PAWSMockDataStorageProvider; + package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; + productName = SpeziFirebaseAccount; }; - 2F59B940298C628800C5107F /* PAWSOnboardingFlow */ = { + 2FE5DC7629EDD8E6004B9AB4 /* SpeziFirebaseConfiguration */ = { isa = XCSwiftPackageProductDependency; - productName = PAWSOnboardingFlow; + package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; + productName = SpeziFirebaseConfiguration; }; - 2F59B944298C628800C5107F /* PAWSSharedContext */ = { + 2FE5DC7829EDD8E6004B9AB4 /* SpeziFirestore */ = { isa = XCSwiftPackageProductDependency; - productName = PAWSSharedContext; + package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; + productName = SpeziFirestore; }; - 2F9056D2299E1B9C003D3802 /* FHIRToFirestoreAdapter */ = { + 2FE5DC8029EDD91D004B9AB4 /* SpeziOnboarding */ = { isa = XCSwiftPackageProductDependency; - package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */; - productName = FHIRToFirestoreAdapter; + package = 97F466E62A76BBEE005DC9B4 /* XCRemoteSwiftPackageReference "SpeziOnboarding" */; + productName = SpeziOnboarding; }; - 2F9056D4299E1B9C003D3802 /* FirestoreDataStorage */ = { + 2FE5DC8329EDD934004B9AB4 /* SpeziQuestionnaire */ = { isa = XCSwiftPackageProductDependency; - package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */; - productName = FirestoreDataStorage; + package = 2FE5DC8229EDD934004B9AB4 /* XCRemoteSwiftPackageReference "SpeziQuestionnaire" */; + productName = SpeziQuestionnaire; }; - 2F9056D6299E1B9C003D3802 /* FirestoreStoragePrefixUserIdAdapter */ = { + 2FE5DC8929EDD972004B9AB4 /* SpeziLocalStorage */ = { isa = XCSwiftPackageProductDependency; - package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "CardinalKit" */; - productName = FirestoreStoragePrefixUserIdAdapter; + package = 2FE5DC8829EDD972004B9AB4 /* XCRemoteSwiftPackageReference "SpeziStorage" */; + productName = SpeziLocalStorage; }; - 2F9F4D8229B7FFAE00ABE259 /* CardinalKitHealthKitToFHIRAdapter */ = { + 2FE5DC8B29EDD972004B9AB4 /* SpeziSecureStorage */ = { isa = XCSwiftPackageProductDependency; - package = 2F9F4D8129B7FFAE00ABE259 /* XCRemoteSwiftPackageReference "CardinalKitHealthKitToFHIRAdapter" */; - productName = CardinalKitHealthKitToFHIRAdapter; + package = 2FE5DC8829EDD972004B9AB4 /* XCRemoteSwiftPackageReference "SpeziStorage" */; + productName = SpeziSecureStorage; }; - 2FABBC5829A8975900DF3BBC /* FirebaseFirestore */ = { + 2FE5DC8E29EDD980004B9AB4 /* SpeziViews */ = { isa = XCSwiftPackageProductDependency; - package = 2FABBC5729A8975900DF3BBC /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebaseFirestore; + package = 2FE5DC8D29EDD980004B9AB4 /* XCRemoteSwiftPackageReference "SpeziViews" */; + productName = SpeziViews; }; - 2FABBC5A29A8975900DF3BBC /* FirebaseFirestoreSwift */ = { + 2FE5DC9829EDD9D9004B9AB4 /* XCTestExtensions */ = { isa = XCSwiftPackageProductDependency; - package = 2FABBC5729A8975900DF3BBC /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebaseFirestoreSwift; + package = 2FE5DC9729EDD9D9004B9AB4 /* XCRemoteSwiftPackageReference "XCTestExtensions" */; + productName = XCTestExtensions; + }; + 2FE5DC9B29EDD9EF004B9AB4 /* XCTHealthKit */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC9A29EDD9EF004B9AB4 /* XCRemoteSwiftPackageReference "XCTHealthKit" */; + productName = XCTHealthKit; + }; + 2FF53D8A2A8725DE00042B76 /* SpeziMockWebService */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE750CA2A87240100723EAE /* XCRemoteSwiftPackageReference "SpeziMockWebService" */; + productName = SpeziMockWebService; + }; + 5661551C2AB8384200209B80 /* SwiftPackageList */ = { + isa = XCSwiftPackageProductDependency; + package = 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */; + productName = SwiftPackageList; + }; + 566155212AB83CF200209B80 /* SwiftPackageListJSONPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */; + productName = "plugin:SwiftPackageListJSONPlugin"; + }; + 9739A0C52AD7B5730084BEA5 /* FirebaseStorage */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseStorage; }; - 3637947D2992F3B000B3C006 /* PAWSLandingScreen */ = { + 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */ = { isa = XCSwiftPackageProductDependency; - productName = PAWSLandingScreen; + package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; + productName = SpeziFirebaseStorage; }; - 36379480299308AA00B3C006 /* PAWSNotificationScreen */ = { + A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */ = { isa = XCSwiftPackageProductDependency; - productName = PAWSNotificationScreen; + package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; + productName = SpeziFirebaseAccountStorage; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/PAWS.xcodeproj/project.pbxproj.license b/PAWS.xcodeproj/project.pbxproj.license index 9137ea9d..4f324d88 100644 --- a/PAWS.xcodeproj/project.pbxproj.license +++ b/PAWS.xcodeproj/project.pbxproj.license @@ -1,5 +1,5 @@ -This source file is part of the CS342 2023 PAWS Team Application project +This source file is part of the PAWS application based on the Stanford Spezi Template Application project SPDX-FileCopyrightText: 2023 Stanford University diff --git a/PAWS.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/PAWS.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/PAWS.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/PAWS.xcodeproj/project.xcworkspace/contents.xcworkspacedata.license b/PAWS.xcodeproj/project.xcworkspace/contents.xcworkspacedata.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS.xcodeproj/project.xcworkspace/contents.xcworkspacedata.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/PAWS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist.license b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..c6a59aec --- /dev/null +++ b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,320 @@ +{ + "pins" : [ + { + "identity" : "abseil-cpp-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/abseil-cpp-binary.git", + "state" : { + "revision" : "bfc0b6f81adc06ce5121eb23f628473638d67c5c", + "version" : "1.2022062300.0" + } + }, + { + "identity" : "app-check", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/app-check.git", + "state" : { + "revision" : "5746b2d35c91c50581590ed97abe4c06b5037274", + "version" : "10.18.0" + } + }, + { + "identity" : "fhirmodels", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/FHIRModels", + "state" : { + "revision" : "861afd5816a98d38f86220eab2f812d76cad84a0", + "version" : "0.5.0" + } + }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk.git", + "state" : { + "revision" : "c60c958e707c50a9cf8bcb7cfd7d51c566d726c5", + "version" : "10.19.1" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "6b332152355c372ace9966d8ee76ed191f97025e", + "version" : "10.17.0" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "a732a4b47f59e4f725a2ea10f0c77e93a7131117", + "version" : "9.3.0" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "bc27fad73504f3d4af235de451f02ee22586ebd3", + "version" : "7.12.1" + } + }, + { + "identity" : "grpc-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/grpc-binary.git", + "state" : { + "revision" : "a673bc2937fbe886dd1f99c401b01b6d977a9c98", + "version" : "1.49.1" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "115f75e43851774934d695449a4836123c3246e1", + "version" : "3.2.0" + } + }, + { + "identity" : "healthkitonfhir", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordBDHG/HealthKitOnFHIR.git", + "state" : { + "revision" : "825e96007d83ed83f81ee49eb3ebab29d7b7ba2f", + "version" : "0.2.5" + } + }, + { + "identity" : "interop-ios-for-google-sdks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/interop-ios-for-google-sdks.git", + "state" : { + "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", + "version" : "100.0.0" + } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "9d108e9112aa1d65ce508facf804674546116d9c", + "version" : "1.22.3" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692", + "version" : "2.30909.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "e70e889c0196c76d22759eb50d6a0270ca9f1d9e", + "version" : "2.3.1" + } + }, + { + "identity" : "researchkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordBDHG/ResearchKit", + "state" : { + "revision" : "cf79a15c7d8c436f98937fe93e72e880dd2f73e4", + "version" : "2.2.20" + } + }, + { + "identity" : "researchkitonfhir", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordBDHG/ResearchKitOnFHIR", + "state" : { + "revision" : "7dc09f7acd7fb19673594e0fdd4d72d0869ee006", + "version" : "1.0.0" + } + }, + { + "identity" : "spezi", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/Spezi", + "state" : { + "revision" : "c4bf0e99de40acfdd2baf0fa02769f06a4c3f0eb", + "version" : "1.1.0" + } + }, + { + "identity" : "speziaccount", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziAccount.git", + "state" : { + "revision" : "714f01ae1e67bf9c1c0e7c07624380f9bea772b7", + "version" : "1.1.0" + } + }, + { + "identity" : "spezicontact", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziContact.git", + "state" : { + "revision" : "494b776f8c98d771e4a609a1fb706097dba4c030", + "version" : "1.0.0" + } + }, + { + "identity" : "spezifirebase", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziFirebase.git", + "state" : { + "revision" : "ca1edf678ec59e76c9869ee3448e6e165d9c2789", + "version" : "1.0.0" + } + }, + { + "identity" : "spezifoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziFoundation.git", + "state" : { + "revision" : "683c66f922a4cfe0882c4a86a43854f613b48541", + "version" : "1.0.0" + } + }, + { + "identity" : "spezihealthkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziHealthKit.git", + "state" : { + "revision" : "d882734a4ed31fce1bffd7b9977e2669080f21de", + "version" : "0.5.0" + } + }, + { + "identity" : "spezimockwebservice", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziMockWebService.git", + "state" : { + "revision" : "b18067d3499e630bbd995ef05a296ef8fdd42528", + "version" : "1.0.0" + } + }, + { + "identity" : "spezionboarding", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziOnboarding", + "state" : { + "revision" : "ae7b18a18453557cd95c7adeb8f75846f48c343c", + "version" : "1.0.0" + } + }, + { + "identity" : "speziquestionnaire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziQuestionnaire.git", + "state" : { + "revision" : "930a4099db1aca9db0b6ed4e77687141c4780052", + "version" : "1.0.0" + } + }, + { + "identity" : "spezischeduler", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziScheduler.git", + "state" : { + "revision" : "adf793cb47dc199f8ae88f5c719f4d3ba06a4c4e", + "version" : "0.8.0" + } + }, + { + "identity" : "spezistorage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziStorage.git", + "state" : { + "revision" : "eaed2220375c35400aa69d1f96a8d32b7e66b1c7", + "version" : "1.0.0" + } + }, + { + "identity" : "speziviews", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziViews.git", + "state" : { + "revision" : "0137e69d156bf4001a8d6bf5661c9a37b2bbd0aa", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "c8ed701b513cf5177118a175d85fbbbcd707ab41", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "d029d9d39c87bed85b1c50adee7c41795261a192", + "version" : "1.0.6" + } + }, + { + "identity" : "swift-package-list", + "kind" : "remoteSourceControl", + "location" : "https://github.com/FelixHerrmann/swift-package-list", + "state" : { + "revision" : "412180a72b9a1f8262213c16459e3533b0385ea5", + "version" : "3.1.0" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "65e8f29b2d63c4e38e736b25c27b83e012159be8", + "version" : "1.25.2" + } + }, + { + "identity" : "xctestextensions", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordBDHG/XCTestExtensions.git", + "state" : { + "revision" : "388a6d6a5be48eff5d98a2c45e0b50f30ed21dc3", + "version" : "0.4.7" + } + }, + { + "identity" : "xcthealthkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordBDHG/XCTHealthKit.git", + "state" : { + "revision" : "6e9344a2d632b801d94fe3bbd1d891817e032103", + "version" : "0.3.5" + } + }, + { + "identity" : "xctruntimeassertions", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordBDHG/XCTRuntimeAssertions", + "state" : { + "revision" : "bb2a287c2544aa846e53670d1ece35e5949567be", + "version" : "1.0.0" + } + } + ], + "version" : 2 +} diff --git a/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved.license b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS.xcodeproj/xcshareddata/xcschemes/PAWS.xcscheme b/PAWS.xcodeproj/xcshareddata/xcschemes/PAWS.xcscheme index 81291d56..dc7a2bb2 100644 --- a/PAWS.xcodeproj/xcshareddata/xcschemes/PAWS.xcscheme +++ b/PAWS.xcodeproj/xcshareddata/xcschemes/PAWS.xcscheme @@ -1,95 +1,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + ) { + self._isPresented = isPresented + } +} + + +#if DEBUG +#Preview(traits: .sizeThatFitsLayout) { + AccountButton(isPresented: .constant(false)) +} +#endif diff --git a/PAWS/Account/AccountSetupHeader.swift b/PAWS/Account/AccountSetupHeader.swift new file mode 100644 index 00000000..08a10cf3 --- /dev/null +++ b/PAWS/Account/AccountSetupHeader.swift @@ -0,0 +1,43 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziAccount +import SwiftUI + + +struct AccountSetupHeader: View { + @Environment(Account.self) private var account + @Environment(\._accountSetupState) private var setupState + + + var body: some View { + VStack { + Text("ACCOUNT_TITLE") + .font(.largeTitle) + .bold() + .padding(.bottom) + .padding(.top, 30) + Text("ACCOUNT_SUBTITLE") + .padding(.bottom, 8) + if account.signedIn, case .generic = setupState { + Text("ACCOUNT_SIGNED_IN_DESCRIPTION") + } else { + Text("ACCOUNT_SETUP_DESCRIPTION") + } + } + .multilineTextAlignment(.center) + } +} + + +#if DEBUG +#Preview { + AccountSetupHeader() + .environment(Account()) +} +#endif diff --git a/PAWS/Account/AccountSheet.swift b/PAWS/Account/AccountSheet.swift new file mode 100644 index 00000000..b0691693 --- /dev/null +++ b/PAWS/Account/AccountSheet.swift @@ -0,0 +1,91 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziAccount +import SwiftUI + + +struct AccountSheet: View { + @Environment(\.dismiss) var dismiss + + @Environment(Account.self) private var account + @Environment(\.accountRequired) var accountRequired + + @State var isInSetup = false + @State var overviewIsEditing = false + + + var body: some View { + NavigationStack { + ZStack { + if account.signedIn && !isInSetup { + AccountOverview(isEditing: $overviewIsEditing) { + NavigationLink { + ContributionsList() + } label: { + Text("LICENSE_INFO_TITLE") + } + } + .onDisappear { + overviewIsEditing = false + } + .toolbar { + if !overviewIsEditing { + closeButton + } + } + } else { + AccountSetup { _ in + dismiss() // we just signed in, dismiss the account setup sheet + } header: { + AccountSetupHeader() + } + .onAppear { + isInSetup = true + } + .toolbar { + if !accountRequired { + closeButton + } + } + } + } + } + } + + var closeButton: some ToolbarContent { + ToolbarItem(placement: .cancellationAction) { + Button("CLOSE") { + dismiss() + } + } + } +} + + +#if DEBUG +#Preview("AccountSheet") { + let details = AccountDetails.Builder() + .set(\.userId, value: "lelandstanford@stanford.edu") + .set(\.name, value: PersonNameComponents(givenName: "Leland", familyName: "Stanford")) + + return AccountSheet() + .previewWith { + AccountConfiguration(building: details, active: MockUserIdPasswordAccountService()) + } +} + +#Preview("AccountSheet SignIn") { + AccountSheet() + .previewWith { + AccountConfiguration { + MockUserIdPasswordAccountService() + } + } +} +#endif diff --git a/PAWS/Contacts/Contacts.swift b/PAWS/Contacts/Contacts.swift new file mode 100644 index 00000000..00149bc5 --- /dev/null +++ b/PAWS/Contacts/Contacts.swift @@ -0,0 +1,107 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SpeziContact +import SwiftUI + + +/// Displays the contacts for the PAWS. +struct Contacts: View { + let contacts = [ + Contact( + name: PersonNameComponents( + givenName: "Scott", + familyName: "Ceresnak" + ), + image: Image("ScottSquarePhoto"), // swiftlint:disable:this accessibility_label_for_image + title: "Professor of Pediatrics (Cardiology)", + description: String(localized: "SCOTT_CERESNAK_BIO"), + organization: "Stanford University", + address: { + let address = CNMutablePostalAddress() + address.country = "USA" + address.state = "CA" + address.postalCode = "94304" + address.city = "Palo Alto" + address.street = "725 Welch Rd" + return address + }(), + contactOptions: [ + .email(addresses: ["ceresnak@stanford.edu"]), + ContactOption( + image: Image(systemName: "safari.fill"), // swiftlint:disable:this accessibility_label_for_image + title: "Website", + action: { + if let url = URL(string: "https://profiles.stanford.edu/intranet/scott-ceresnak?tab=bio") { + UIApplication.shared.open(url) + } + } + ) + ] + ), + Contact( + name: PersonNameComponents( + givenName: "Aydin", + familyName: "Zahedivash" + ), + image: Image("AydinSquarePhoto"), // swiftlint:disable:this accessibility_label_for_image + title: "Pediatric Stanford Cardiology Fellow", + description: String(localized: "AYDIN_ZAHEDIVASH_BIO"), + organization: "Stanford University", + address: { + let address = CNMutablePostalAddress() + address.country = "USA" + address.state = "CA" + address.postalCode = "94304" + address.city = "Palo Alto" + address.street = "725 Welch Rd" + return address + }(), + contactOptions: [ + .email(addresses: ["aydinz@stanford.edu"]), + ContactOption( + image: Image(systemName: "safari.fill"), // swiftlint:disable:this accessibility_label_for_image + title: "Website", + action: { + if let url = URL(string: "https://profiles.stanford.edu/intranet/scott-ceresnak?tab=bio") { + UIApplication.shared.open(url) + } + } + ) + ] + ) + ] + + @Binding var presentingAccount: Bool + + + var body: some View { + NavigationStack { + ContactsList(contacts: contacts) + .navigationTitle(String(localized: "CONTACTS_NAVIGATION_TITLE")) + .toolbar { + if AccountButton.shouldDisplay { + AccountButton(isPresented: $presentingAccount) + } + } + } + } + + + init(presentingAccount: Binding) { + self._presentingAccount = presentingAccount + } +} + + +#if DEBUG +#Preview { + Contacts(presentingAccount: .constant(false)) +} +#endif diff --git a/PAWS/Contributions/ContributionsList.swift b/PAWS/Contributions/ContributionsList.swift new file mode 100644 index 00000000..409989bc --- /dev/null +++ b/PAWS/Contributions/ContributionsList.swift @@ -0,0 +1,53 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftPackageList +import SwiftUI + + +struct ContributionsList: View { + var packages: [Package] = PackageHelper.getPackageList() + + var body: some View { + List { + Section(footer: Text("PROJECT_LICENSE_DESCRIPTION")) { + Text("CONTRIBUTIONS_LIST_DESCRIPTION") + } + Section( + header: Text("CONTRIBUTIONS_LIST_HEADER"), + footer: Text("CONTRIBUTIONS_LIST_FOOTER") + ) { + ForEach(packages.sorted(by: { $0.name < $1.name }), id: \.name) { package in + PackageCell(package: package) + } + } + } + .navigationTitle("LICENSE_INFO_TITLE") + .navigationBarTitleDisplayMode(.inline) + } +} + + +#if DEBUG +#Preview { + let mockPackages = [ + Package( + name: "MockPackage", + version: "1.0", + branch: nil, + revision: "0", + // We use a force unwrap in the preview as we can not recover from an error here + // and the code will never end up in a production environment. + // swiftlint:disable:next force_unwrapping + repositoryURL: URL(string: "github.com")!, + license: "MIT License" + ) + ] + return ContributionsList(packages: mockPackages) +} +#endif diff --git a/PAWS/Contributions/Package+LicenseType.swift b/PAWS/Contributions/Package+LicenseType.swift new file mode 100644 index 00000000..302dac67 --- /dev/null +++ b/PAWS/Contributions/Package+LicenseType.swift @@ -0,0 +1,104 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SwiftPackageList + + +// This section of code is based on the SwiftPackageList package: +// - Original code: https://github.com/FelixHerrmann/swift-package-list/issues/43 +enum LicenseType { + case mit + case apachev2 + case gplv2 + case gplv3 + case bsd2 + case bsd3 + case bsd4 + case zlib + + /// SPDX-License-Identifier for the UI + var spdxIdentifier: String { + switch self { + case .mit: return "MIT" + case .apachev2: return "Apache-2.0" + case .gplv2: return "GPL-2.0" + case .gplv3: return "GPL-3.0" + case .bsd2: return "BSD-2-Clause" + case .bsd3: return "BSD-3-Clause" + case .bsd4: return "BSD-4-Clause" + case .zlib: return "Zlib" + } + } + + /// Initializer that scans the license document for common licenses and versions + init?(license: String) { + let license = license + .replacingOccurrences(of: "\\s+|\\n", with: " ", options: .regularExpression) + + if license.contains(mitText) { + self = .mit + } else if license.contains(apacheText) && license.contains("Version 2.0") { + self = .apachev2 + } else if license.contains(gnuText) && license.contains("Version 2") { + self = .gplv2 + } else if license.contains(gnuText) && license.contains("Version 3") { + self = .gplv3 + } else if license.contains(bsdFourClauseText) { + self = .bsd4 + } else if license.range(of: bsdThreeClausePattern, options: .regularExpression) != nil { + self = .bsd3 + } else if license.contains(bsdTwoClauseText) { + self = .bsd2 + } else if license.range(of: zlibPattern, options: .regularExpression) != nil { + self = .zlib + } else { + return nil + } + } +} + + +// Constants representing typical text and regular expression patterns often found in license files. +// They are used for matching and identifying different types of licenses within text documents. +private let mitText = "MIT License" +private let apacheText = "Apache License" +private let gnuText = "GNU GENERAL PUBLIC LICENSE" +private let bsdTwoClauseText = + """ + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met + """ +private let bsdThreeClausePattern = + """ + Neither the name of (.+) nor the names of (.+) may be used to endorse or promote products derived from this software \ + without specific prior written permission + """ +private let bsdFourClauseText = + """ + All advertising materials mentioning features or use of this software must display the following acknowledgement: \ + this product includes software developed by + """ +private let zlibPattern = + """ + The origin of this software must not be misrepresented; you must not claim that you wrote the original software. \ + If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.(.*) \ + Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.(.*) \ + This notice may not be removed or altered from any source distribution. + """ + + +extension Package { + /// Generates the `LicenseType` from a license document of `String` + func getLicenseType(license: String?) -> LicenseType? { + if let license = license { + let licenseType = LicenseType(license: license) + return licenseType + } + return nil + } +} diff --git a/PAWS/Contributions/PackageCell.swift b/PAWS/Contributions/PackageCell.swift new file mode 100644 index 00000000..da60b26f --- /dev/null +++ b/PAWS/Contributions/PackageCell.swift @@ -0,0 +1,73 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftPackageList +import SwiftUI + + +struct PackageCell: View { + let package: Package + + var body: some View { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text(package.name).font(.headline) + HStack { + Text(getPackageDetails(package: package)) + .font(.caption) + if let licenseType = package.getLicenseType(license: package.license) { + Text(licenseType.spdxIdentifier) + .font(.caption) + .fontWeight(.semibold) + .padding(2) + .background(Color(.systemGray5)) + .cornerRadius(4) + } + } + } + Spacer() + Button(action: { + UIApplication.shared.open(package.repositoryURL) + }) { + Image(systemName: "safari.fill") + .imageScale(.large) + }.buttonStyle(PlainButtonStyle()) + .foregroundColor(.blue) + .accessibilityLabel(Text("Repository Link")) + } + } + + func getPackageDetails(package: Package) -> String { + if let branch = package.branch { + return "Branch: \(branch)" + } else if let version = package.version { + return "Version: \(version)" + } else { + return "Revision: \(package.revision)" + } + } +} + + +#if DEBUG +#Preview(traits: .sizeThatFitsLayout) { + let mockPackage = Package( + name: "MockPackage", + version: "1.0", + branch: nil, + revision: "0", + // We use a force unwrap in the preview as we can not recover from an error here + // and the code will never end up in a production environment. + // swiftlint:disable:next force_unwrapping + repositoryURL: URL(string: "github.com")!, + license: "MIT License" + ) + + return PackageCell(package: mockPackage) +} +#endif diff --git a/PAWS/Contributions/PackageHelper.swift b/PAWS/Contributions/PackageHelper.swift new file mode 100644 index 00000000..6558a442 --- /dev/null +++ b/PAWS/Contributions/PackageHelper.swift @@ -0,0 +1,26 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SwiftPackageList + + +enum PackageHelper { + /// Helper function that calls the corresponding API of `SwiftPackageList`to fetch the list of packages + static func getPackageList() -> [Package] { + do { + let packages = try packageList() + return packages + } catch PackageListError.noPackageList { + print("There is no package-list file") + } catch { + print(error) + } + return [] + } +} diff --git a/PAWSModules/Sources/PAWSSharedContext/Binding+Negate.swift b/PAWS/Helper/Binding+Negate.swift similarity index 63% rename from PAWSModules/Sources/PAWSSharedContext/Binding+Negate.swift rename to PAWS/Helper/Binding+Negate.swift index 484bad68..63e5dbd0 100644 --- a/PAWSModules/Sources/PAWSSharedContext/Binding+Negate.swift +++ b/PAWS/Helper/Binding+Negate.swift @@ -1,5 +1,5 @@ // -// This source file is part of the CS342 2023 PAWS Team Application project +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // @@ -11,7 +11,7 @@ import SwiftUI extension Binding where Value == Bool { /// Negates a `Binding`. - public prefix static func ! (value: Binding) -> Binding { + prefix static func ! (value: Binding) -> Binding { Binding( get: { !value.wrappedValue }, set: { value.wrappedValue = !$0 } diff --git a/PAWSModules/Sources/PAWSSharedContext/Bundle+Image.swift b/PAWS/Helper/Bundle+Image.swift similarity index 82% rename from PAWSModules/Sources/PAWSSharedContext/Bundle+Image.swift rename to PAWS/Helper/Bundle+Image.swift index 431b155e..448cebc4 100644 --- a/PAWSModules/Sources/PAWSSharedContext/Bundle+Image.swift +++ b/PAWS/Helper/Bundle+Image.swift @@ -1,5 +1,5 @@ // -// This source file is part of the Stanford CardinalKit PAWS Application project +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // @@ -15,7 +15,7 @@ extension Foundation.Bundle { /// - name: The name of the image. /// - fileExtension: The file extension of the image. /// - Returns: Returns the `UIImage` loaded from the `Bundle` instance. - public func image(withName name: String, fileExtension: String) -> UIImage { + func image(withName name: String, fileExtension: String) -> UIImage { guard let resourceURL = self.url(forResource: name, withExtension: fileExtension) else { fatalError("Could not find the file \"\(name).\(fileExtension)\" in the bundle.") } diff --git a/PAWSModules/Sources/PAWSSharedContext/CodableArray+RawRepresentable.swift b/PAWS/Helper/CodableArray+RawRepresentable.swift similarity index 85% rename from PAWSModules/Sources/PAWSSharedContext/CodableArray+RawRepresentable.swift rename to PAWS/Helper/CodableArray+RawRepresentable.swift index fd9fa003..154f19ca 100644 --- a/PAWSModules/Sources/PAWSSharedContext/CodableArray+RawRepresentable.swift +++ b/PAWS/Helper/CodableArray+RawRepresentable.swift @@ -1,5 +1,5 @@ // -// This source file is part of the CS342 2023 PAWS Team Application project +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // diff --git a/PAWS/Home.swift b/PAWS/Home.swift new file mode 100644 index 00000000..d4a355de --- /dev/null +++ b/PAWS/Home.swift @@ -0,0 +1,92 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziAccount +import SpeziMockWebService +import SwiftUI + + +struct HomeView: View { + enum Tabs: String { + case schedule + case contact + case studyInformation + case mockUpload + } + + static var accountEnabled: Bool { + !FeatureFlags.disableFirebase && !FeatureFlags.skipOnboarding + } + + + @AppStorage(StorageKeys.homeTabSelection) private var selectedTab = Tabs.schedule + @State private var presentingAccount = false + + + var body: some View { + TabView(selection: $selectedTab) { + ScheduleView(presentingAccount: $presentingAccount) + .tag(Tabs.schedule) + .tabItem { + Label("SCHEDULE_TAB_TITLE", systemImage: "list.clipboard") + } + StudyInformation(presentingAccount: $presentingAccount) + .tag(Tabs.studyInformation) + .tabItem { + Label("Infos", systemImage: "i.circle") + } + Contacts(presentingAccount: $presentingAccount) + .tag(Tabs.contact) + .tabItem { + Label("CONTACTS_TAB_TITLE", systemImage: "person.fill") + } + if FeatureFlags.disableFirebase { + MockUpload(presentingAccount: $presentingAccount) + .tag(Tabs.mockUpload) + .tabItem { + Label("MOCK_WEB_SERVICE_TAB_TITLE", systemImage: "server.rack") + } + } + } + .sheet(isPresented: $presentingAccount) { + AccountSheet() + } + .accountRequired(Self.accountEnabled) { + AccountSheet() + } + .verifyRequiredAccountDetails(Self.accountEnabled) + } +} + + +#if DEBUG +#Preview { + let details = AccountDetails.Builder() + .set(\.userId, value: "lelandstanford@stanford.edu") + .set(\.name, value: PersonNameComponents(givenName: "Leland", familyName: "Stanford")) + + return HomeView() + .previewWith(standard: PAWSStandard()) { + PAWSScheduler() + MockWebService() + AccountConfiguration(building: details, active: MockUserIdPasswordAccountService()) + } +} + +#Preview { + CommandLine.arguments.append("--disableFirebase") // make sure the MockWebService is displayed + return HomeView() + .previewWith(standard: PAWSStandard()) { + PAWSScheduler() + MockWebService() + AccountConfiguration { + MockUserIdPasswordAccountService() + } + } +} +#endif diff --git a/PAWS/MockUpload/MockUpload.swift b/PAWS/MockUpload/MockUpload.swift new file mode 100644 index 00000000..6ecad118 --- /dev/null +++ b/PAWS/MockUpload/MockUpload.swift @@ -0,0 +1,41 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziMockWebService +import SwiftUI + + +struct MockUpload: View { + @Binding var presentingAccount: Bool + + var body: some View { + NavigationStack { + RequestList() + .toolbar { + if AccountButton.shouldDisplay { + AccountButton(isPresented: $presentingAccount) + } + } + } + } + + + init(presentingAccount: Binding) { + self._presentingAccount = presentingAccount + } +} + + +#if DEBUG +#Preview { + MockUpload(presentingAccount: .constant(false)) + .previewWith { + MockWebService() + } +} +#endif diff --git a/PAWS/Onboarding/AccountOnboarding.swift b/PAWS/Onboarding/AccountOnboarding.swift new file mode 100644 index 00000000..eec7441d --- /dev/null +++ b/PAWS/Onboarding/AccountOnboarding.swift @@ -0,0 +1,64 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziAccount +import SpeziOnboarding +import SwiftUI + + +struct AccountOnboarding: View { + @Environment(Account.self) private var account + @Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath + + + var body: some View { + AccountSetup { _ in + Task { + // Placing the nextStep() call inside this task will ensure that the sheet dismiss animation is + // played till the end before we navigate to the next step. + onboardingNavigationPath.nextStep() + } + } header: { + AccountSetupHeader() + } continue: { + OnboardingActionsView( + "ACCOUNT_NEXT", + action: { + onboardingNavigationPath.nextStep() + } + ) + } + } +} + + +#if DEBUG +#Preview("Account Onboarding SignIn") { + OnboardingStack { + AccountOnboarding() + } + .previewWith { + AccountConfiguration { + MockUserIdPasswordAccountService() + } + } +} + +#Preview("Account Onboarding") { + let details = AccountDetails.Builder() + .set(\.userId, value: "lelandstanford@stanford.edu") + .set(\.name, value: PersonNameComponents(givenName: "Leland", familyName: "Stanford")) + + return OnboardingStack { + AccountOnboarding() + } + .previewWith { + AccountConfiguration(building: details, active: MockUserIdPasswordAccountService()) + } +} +#endif diff --git a/PAWS/Onboarding/Consent.swift b/PAWS/Onboarding/Consent.swift new file mode 100644 index 00000000..c19312da --- /dev/null +++ b/PAWS/Onboarding/Consent.swift @@ -0,0 +1,49 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziOnboarding +import SwiftUI + + +/// - Note: The `OnboardingConsentView` exports the signed consent form as PDF to the Spezi `Standard`, necessitating the conformance of the `Standard` to the `OnboardingConstraint`. +struct Consent: View { + @Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath + + + private var consentDocument: Data { + guard let path = Bundle.main.url(forResource: "ConsentDocument", withExtension: "md"), + let data = try? Data(contentsOf: path) else { + return Data(String(localized: "CONSENT_LOADING_ERROR").utf8) + } + return data + } + + + var body: some View { + OnboardingConsentView( + markdown: { + consentDocument + }, + action: { + onboardingNavigationPath.nextStep() + } + ) + } +} + + +#if DEBUG +#Preview { + OnboardingStack { + Consent() + } + .previewWith(standard: PAWSStandard()) { + OnboardingDataSource() + } +} +#endif diff --git a/PAWS/Onboarding/HealthKitPermissions.swift b/PAWS/Onboarding/HealthKitPermissions.swift new file mode 100644 index 00000000..94212932 --- /dev/null +++ b/PAWS/Onboarding/HealthKitPermissions.swift @@ -0,0 +1,77 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziHealthKit +import SpeziOnboarding +import SwiftUI + + +struct HealthKitPermissions: View { + @Environment(HealthKit.self) private var healthKitDataSource + @Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath + + @State private var healthKitProcessing = false + + + var body: some View { + OnboardingView( + contentView: { + VStack { + OnboardingTitleView( + title: "HEALTHKIT_PERMISSIONS_TITLE", + subtitle: "HEALTHKIT_PERMISSIONS_SUBTITLE" + ) + Spacer() + Image(systemName: "heart.text.square.fill") + .font(.system(size: 150)) + .foregroundColor(.accentColor) + .accessibilityHidden(true) + Text("HEALTHKIT_PERMISSIONS_DESCRIPTION") + .multilineTextAlignment(.center) + .padding(.vertical, 16) + Spacer() + } + }, actionView: { + OnboardingActionsView( + "HEALTHKIT_PERMISSIONS_BUTTON", + action: { + do { + healthKitProcessing = true + // HealthKit is not available in the preview simulator. + if ProcessInfo.processInfo.isPreviewSimulator { + try await _Concurrency.Task.sleep(for: .seconds(5)) + } else { + try await healthKitDataSource.askForAuthorization() + } + } catch { + print("Could not request HealthKit permissions.") + } + healthKitProcessing = false + + onboardingNavigationPath.nextStep() + } + ) + } + ) + .navigationBarBackButtonHidden(healthKitProcessing) + // Small fix as otherwise "Login" or "Sign up" is still shown in the nav bar + .navigationTitle(Text(verbatim: "")) + } +} + + +#if DEBUG +#Preview { + OnboardingStack { + HealthKitPermissions() + } + .previewWith(standard: PAWSStandard()) { + HealthKit() + } +} +#endif diff --git a/PAWS/Onboarding/InterestingModules.swift b/PAWS/Onboarding/InterestingModules.swift new file mode 100644 index 00000000..392a9418 --- /dev/null +++ b/PAWS/Onboarding/InterestingModules.swift @@ -0,0 +1,54 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziOnboarding +import SwiftUI + + +struct InterestingModules: View { + @Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath + + + var body: some View { + SequentialOnboardingView( + title: "INTERESTING_MODULES_TITLE", + subtitle: "INTERESTING_MODULES_SUBTITLE", + content: [ + SequentialOnboardingView.Content( + title: "INTERESTING_MODULES_AREA1_TITLE", + description: "INTERESTING_MODULES_AREA1_DESCRIPTION" + ), + SequentialOnboardingView.Content( + title: "INTERESTING_MODULES_AREA2_TITLE", + description: "INTERESTING_MODULES_AREA2_DESCRIPTION" + ), + SequentialOnboardingView.Content( + title: "INTERESTING_MODULES_AREA3_TITLE", + description: "INTERESTING_MODULES_AREA3_DESCRIPTION" + ), + SequentialOnboardingView.Content( + title: "INTERESTING_MODULES_AREA4_TITLE", + description: "INTERESTING_MODULES_AREA4_DESCRIPTION" + ) + ], + actionText: "INTERESTING_MODULES_BUTTON", + action: { + onboardingNavigationPath.nextStep() + } + ) + } +} + + +#if DEBUG +#Preview { + OnboardingStack { + InterestingModules() + } +} +#endif diff --git a/PAWS/Onboarding/NotificationPermissions.swift b/PAWS/Onboarding/NotificationPermissions.swift new file mode 100644 index 00000000..d997875c --- /dev/null +++ b/PAWS/Onboarding/NotificationPermissions.swift @@ -0,0 +1,77 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziOnboarding +import SpeziScheduler +import SwiftUI + + +struct NotificationPermissions: View { + @Environment(PAWSScheduler.self) private var scheduler + @Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath + + @State private var notificationProcessing = false + + + var body: some View { + OnboardingView( + contentView: { + VStack { + OnboardingTitleView( + title: "NOTIFICATION_PERMISSIONS_TITLE", + subtitle: "NOTIFICATION_PERMISSIONS_SUBTITLE" + ) + Spacer() + Image(systemName: "bell.square.fill") + .font(.system(size: 150)) + .foregroundColor(.accentColor) + .accessibilityHidden(true) + Text("NOTIFICATION_PERMISSIONS_DESCRIPTION") + .multilineTextAlignment(.center) + .padding(.vertical, 16) + Spacer() + } + }, actionView: { + OnboardingActionsView( + "NOTIFICATION_PERMISSIONS_BUTTON", + action: { + do { + notificationProcessing = true + // Notification Authorization is not available in the preview simulator. + if ProcessInfo.processInfo.isPreviewSimulator { + try await _Concurrency.Task.sleep(for: .seconds(5)) + } else { + try await scheduler.requestLocalNotificationAuthorization() + } + } catch { + print("Could not request notification permissions.") + } + notificationProcessing = false + + onboardingNavigationPath.nextStep() + } + ) + } + ) + .navigationBarBackButtonHidden(notificationProcessing) + // Small fix as otherwise "Login" or "Sign up" is still shown in the nav bar + .navigationTitle(Text(verbatim: "")) + } +} + + +#if DEBUG +#Preview { + OnboardingStack { + NotificationPermissions() + } + .previewWith { + PAWSScheduler() + } +} +#endif diff --git a/PAWS/Onboarding/OnboardingFlow.swift b/PAWS/Onboarding/OnboardingFlow.swift new file mode 100644 index 00000000..e0221f0e --- /dev/null +++ b/PAWS/Onboarding/OnboardingFlow.swift @@ -0,0 +1,79 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziAccount +import SpeziFirebaseAccount +import SpeziHealthKit +import SpeziOnboarding +import SwiftUI + + +/// Displays an multi-step onboarding flow for the PAWS. +struct OnboardingFlow: View { + @Environment(HealthKit.self) private var healthKitDataSource + @Environment(PAWSScheduler.self) private var scheduler + + @AppStorage(StorageKeys.onboardingFlowComplete) private var completedOnboardingFlow = false + + @State private var localNotificationAuthorization = false + + + private var healthKitAuthorization: Bool { + // As HealthKit not available in preview simulator + if ProcessInfo.processInfo.isPreviewSimulator { + return false + } + + return healthKitDataSource.authorized + } + + + var body: some View { + OnboardingStack(onboardingFlowComplete: $completedOnboardingFlow) { + Welcome() + InterestingModules() + + if !FeatureFlags.disableFirebase { + AccountOnboarding() + } + + #if !(targetEnvironment(simulator) && (arch(i386) || arch(x86_64))) + Consent() + #endif + + if HKHealthStore.isHealthDataAvailable() && !healthKitAuthorization { + HealthKitPermissions() + } + + if !localNotificationAuthorization { + NotificationPermissions() + } + } + .task { + localNotificationAuthorization = await scheduler.localNotificationAuthorization + } + .interactiveDismissDisabled(!completedOnboardingFlow) + } +} + + +#if DEBUG +#Preview { + OnboardingFlow() + .environment(Account(MockUserIdPasswordAccountService())) + .previewWith(standard: PAWSStandard()) { + OnboardingDataSource() + HealthKit() + AccountConfiguration { + MockUserIdPasswordAccountService() + } + + PAWSScheduler() + } +} +#endif diff --git a/PAWS/Onboarding/Welcome.swift b/PAWS/Onboarding/Welcome.swift new file mode 100644 index 00000000..1d410725 --- /dev/null +++ b/PAWS/Onboarding/Welcome.swift @@ -0,0 +1,63 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziOnboarding +import SwiftUI + + +struct Welcome: View { + @Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath + + + var body: some View { + OnboardingView( + title: "WELCOME_TITLE", + subtitle: "WELCOME_SUBTITLE", + areas: [ + OnboardingInformationView.Content( + icon: { + Image(systemName: "apps.iphone") + .accessibilityHidden(true) + }, + title: "WELCOME_AREA1_TITLE", + description: "WELCOME_AREA1_DESCRIPTION" + ), + OnboardingInformationView.Content( + icon: { + Image(systemName: "shippingbox.fill") + .accessibilityHidden(true) + }, + title: "WELCOME_AREA2_TITLE", + description: "WELCOME_AREA2_DESCRIPTION" + ), + OnboardingInformationView.Content( + icon: { + Image(systemName: "list.bullet.clipboard.fill") + .accessibilityHidden(true) + }, + title: "WELCOME_AREA3_TITLE", + description: "WELCOME_AREA3_DESCRIPTION" + ) + ], + actionText: "WELCOME_BUTTON", + action: { + onboardingNavigationPath.nextStep() + } + ) + .padding(.top, 24) + } +} + + +#if DEBUG +#Preview { + OnboardingStack { + Welcome() + } +} +#endif diff --git a/PAWS/PAWS.swift b/PAWS/PAWS.swift index 706b2a19..4c26c317 100644 --- a/PAWS/PAWS.swift +++ b/PAWS/PAWS.swift @@ -1,48 +1,36 @@ // -// This source file is part of the CS342 2023 PAWS Team Application project +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // // SPDX-License-Identifier: MIT // -import CardinalKit -import PAWSLandingScreen -import PAWSMockDataStorageProvider -import PAWSOnboardingFlow -import PAWSSharedContext +import Spezi +import SpeziFirebaseAccount import SwiftUI + @main struct PAWS: App { - @UIApplicationDelegateAdaptor(PAWSAppDelegate.self) var appDelegate + @UIApplicationDelegateAdaptor(PAWSDelegate.self) var appDelegate @AppStorage(StorageKeys.onboardingFlowComplete) var completedOnboardingFlow = false - @State var pressedStart = false - - var isSheetPresented: Binding { - Binding( - get: { - !completedOnboardingFlow && pressedStart - }, set: { _ in } - ) - } var body: some Scene { WindowGroup { - Group { + ZStack { if completedOnboardingFlow { HomeView() } else { - LandingScreen(pressedStart: $pressedStart) + EmptyView() } } - .sheet(isPresented: isSheetPresented) { + .sheet(isPresented: !$completedOnboardingFlow) { OnboardingFlow() - .interactiveDismissDisabled(true) } .testingSetup() - .cardinalKit(appDelegate) + .spezi(appDelegate) } } } diff --git a/PAWS/PAWSAppDelegate.swift b/PAWS/PAWSAppDelegate.swift deleted file mode 100644 index b0ea2a2e..00000000 --- a/PAWS/PAWSAppDelegate.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import CardinalKit -import FHIR -import FHIRToFirestoreAdapter -import FirebaseAccount -import class FirebaseFirestore.FirestoreSettings -import CardinalKitHealthKitToFHIRAdapter -import FirestoreDataStorage -import FirestoreStoragePrefixUserIdAdapter -import HealthKit -import HealthKitDataSource -import PAWSMockDataStorageProvider -import PAWSSharedContext -import Questionnaires -import Scheduler -import SwiftUI - - -class PAWSAppDelegate: CardinalKitAppDelegate { - override var configuration: Configuration { - Configuration(standard: FHIR()) { - if !FeatureFlags.disableFirebase { - if FeatureFlags.useFirebaseEmulator { - FirebaseAccountConfiguration(emulatorSettings: (host: "localhost", port: 9099)) - } else { - FirebaseAccountConfiguration() - } - firestore - } - if HKHealthStore.isHealthDataAvailable() { - healthKit - } - QuestionnaireDataSource() - MockDataStorageProvider() - } - } - - - private var firestore: Firestore { - let settings = FirestoreSettings() - if FeatureFlags.useFirebaseEmulator { - settings.host = "localhost:8080" - settings.isPersistenceEnabled = false - settings.isSSLEnabled = false - } - - return Firestore( - adapter: { - FHIRToFirestoreAdapter() - FirestoreStoragePrefixUserIdAdapter() - }, - settings: settings - ) - } - - - private var healthKit: HealthKit { - HealthKit { - CollectSample( - HKQuantityType.electrocardiogramType(), - deliverySetting: .background(.afterAuthorizationAndApplicationWillLaunch) - ) - CollectSamples(Set(HKElectrocardiogram.correlatedSymptomTypes)) - } adapter: { - HealthKitToFHIRAdapter() - } - } -} diff --git a/PAWS/PAWSDelegate.swift b/PAWS/PAWSDelegate.swift new file mode 100644 index 00000000..4515e1fd --- /dev/null +++ b/PAWS/PAWSDelegate.swift @@ -0,0 +1,84 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Spezi +import SpeziAccount +import SpeziFirebaseAccount +import SpeziFirebaseStorage +import SpeziFirestore +import SpeziHealthKit +import SpeziMockWebService +import SpeziOnboarding +import SpeziScheduler +import SwiftUI + + +class PAWSDelegate: SpeziAppDelegate { + override var configuration: Configuration { + Configuration(standard: PAWSStandard()) { + if !FeatureFlags.disableFirebase { + AccountConfiguration(configuration: [ + .requires(\.userId), + .requires(\.name), + + // additional values stored using the `FirestoreAccountStorage` within our Standard implementation + .collects(\.genderIdentity), + .collects(\.dateOfBirth) + ]) + + if FeatureFlags.useFirebaseEmulator { + FirebaseAccountConfiguration( + authenticationMethods: [.emailAndPassword, .signInWithApple], + emulatorSettings: (host: "localhost", port: 9099) + ) + } else { + FirebaseAccountConfiguration(authenticationMethods: [.emailAndPassword, .signInWithApple]) + } + firestore + if FeatureFlags.useFirebaseEmulator { + FirebaseStorageConfiguration(emulatorSettings: (host: "localhost", port: 9199)) + } else { + FirebaseStorageConfiguration() + } + } else { + MockWebService() + } + + if HKHealthStore.isHealthDataAvailable() { + healthKit + } + + PAWSScheduler() + OnboardingDataSource() + } + } + + + private var firestore: Firestore { + let settings = FirestoreSettings() + if FeatureFlags.useFirebaseEmulator { + settings.host = "localhost:8080" + settings.cacheSettings = MemoryCacheSettings() + settings.isSSLEnabled = false + } + + return Firestore( + settings: settings + ) + } + + + private var healthKit: HealthKit { + HealthKit { + CollectSample( + HKQuantityType(.stepCount), + deliverySetting: .anchorQuery(.afterAuthorizationAndApplicationWillLaunch) + ) + } + } +} diff --git a/PAWS/PAWSStandard.swift b/PAWS/PAWSStandard.swift new file mode 100644 index 00000000..1570b75a --- /dev/null +++ b/PAWS/PAWSStandard.swift @@ -0,0 +1,203 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import FirebaseFirestore +import FirebaseStorage +import HealthKitOnFHIR +import OSLog +import PDFKit +import Spezi +import SpeziAccount +import SpeziFirebaseAccountStorage +import SpeziFirestore +import SpeziHealthKit +import SpeziMockWebService +import SpeziOnboarding +import SpeziQuestionnaire +import SwiftUI + + +actor PAWSStandard: Standard, EnvironmentAccessible, HealthKitConstraint, OnboardingConstraint, AccountStorageConstraint { + enum PAWSStandardError: Error { + case userNotAuthenticatedYet + } + + private static var userCollection: CollectionReference { + Firestore.firestore().collection("users") + } + + @Dependency var mockWebService: MockWebService? + @Dependency var accountStorage: FirestoreAccountStorage? + + @AccountReference var account: Account + + private let logger = Logger(subsystem: "PAWS", category: "Standard") + + + private var userDocumentReference: DocumentReference { + get async throws { + guard let details = await account.details else { + throw PAWSStandardError.userNotAuthenticatedYet + } + + return Self.userCollection.document(details.accountId) + } + } + + private var userBucketReference: StorageReference { + get async throws { + guard let details = await account.details else { + throw PAWSStandardError.userNotAuthenticatedYet + } + + return Storage.storage().reference().child("users/\(details.accountId)") + } + } + + + init() { + if !FeatureFlags.disableFirebase { + _accountStorage = Dependency(wrappedValue: FirestoreAccountStorage(storeIn: PAWSStandard.userCollection)) + } + } + + + func add(sample: HKSample) async { + if let mockWebService { + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes] + let jsonRepresentation = (try? String(data: encoder.encode(sample.resource), encoding: .utf8)) ?? "" + try? await mockWebService.upload(path: "healthkit/\(sample.uuid.uuidString)", body: jsonRepresentation) + return + } + + do { + try await healthKitDocument(id: sample.id).setData(from: sample.resource) + } catch { + logger.error("Could not store HealthKit sample: \(error)") + } + } + + func remove(sample: HKDeletedObject) async { + if let mockWebService { + try? await mockWebService.remove(path: "healthkit/\(sample.uuid.uuidString)") + return + } + + do { + try await healthKitDocument(id: sample.uuid).delete() + } catch { + logger.error("Could not remove HealthKit sample: \(error)") + } + } + + func add(response: ModelsR4.QuestionnaireResponse) async { + let id = response.identifier?.value?.value?.string ?? UUID().uuidString + + if let mockWebService { + let jsonRepresentation = (try? String(data: JSONEncoder().encode(response), encoding: .utf8)) ?? "" + try? await mockWebService.upload(path: "questionnaireResponse/\(id)", body: jsonRepresentation) + return + } + + do { + try await userDocumentReference + .collection("QuestionnaireResponse") // Add all HealthKit sources in a /QuestionnaireResponse collection. + .document(id) // Set the document identifier to the id of the response. + .setData(from: response) + } catch { + logger.error("Could not store questionnaire response: \(error)") + } + } + + + private func healthKitDocument(id uuid: UUID) async throws -> DocumentReference { + try await userDocumentReference + .collection("HealthKit") // Add all HealthKit sources in a /HealthKit collection. + .document(uuid.uuidString) // Set the document identifier to the UUID of the document. + } + + func deletedAccount() async throws { + // delete all user associated data + do { + try await userDocumentReference.delete() + } catch { + logger.error("Could not delete user document: \(error)") + } + } + + /// Stores the given consent form in the user's document directory with a unique timestamped filename. + /// + /// - Parameter consent: The consent form's data to be stored as a `PDFDocument`. + func store(consent: PDFDocument) async { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd_HHmmss" + let dateString = formatter.string(from: Date()) + + guard !FeatureFlags.disableFirebase else { + guard let basePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + logger.error("Could not create path for writing consent form to user document directory.") + return + } + + let filePath = basePath.appending(path: "consentForm_\(dateString).pdf") + consent.write(to: filePath) + + return + } + + do { + guard let consentData = consent.dataRepresentation() else { + logger.error("Could not store consent form.") + return + } + + let metadata = StorageMetadata() + metadata.contentType = "application/pdf" + _ = try await userBucketReference.child("consent/\(dateString).pdf").putDataAsync(consentData, metadata: metadata) + } catch { + logger.error("Could not store consent form: \(error)") + } + } + + + func create(_ identifier: AdditionalRecordId, _ details: SignupDetails) async throws { + guard let accountStorage else { + preconditionFailure("Account Storage was requested although not enabled in current configuration.") + } + try await accountStorage.create(identifier, details) + } + + func load(_ identifier: AdditionalRecordId, _ keys: [any AccountKey.Type]) async throws -> PartialAccountDetails { + guard let accountStorage else { + preconditionFailure("Account Storage was requested although not enabled in current configuration.") + } + return try await accountStorage.load(identifier, keys) + } + + func modify(_ identifier: AdditionalRecordId, _ modifications: AccountModifications) async throws { + guard let accountStorage else { + preconditionFailure("Account Storage was requested although not enabled in current configuration.") + } + try await accountStorage.modify(identifier, modifications) + } + + func clear(_ identifier: AdditionalRecordId) async { + guard let accountStorage else { + preconditionFailure("Account Storage was requested although not enabled in current configuration.") + } + await accountStorage.clear(identifier) + } + + func delete(_ identifier: AdditionalRecordId) async throws { + guard let accountStorage else { + preconditionFailure("Account Storage was requested although not enabled in current configuration.") + } + try await accountStorage.delete(identifier) + } +} diff --git a/PAWS/PAWSAppTestingSetup.swift b/PAWS/PAWSTestingSetup.swift similarity index 75% rename from PAWS/PAWSAppTestingSetup.swift rename to PAWS/PAWSTestingSetup.swift index 43df0098..4495236f 100644 --- a/PAWS/PAWSAppTestingSetup.swift +++ b/PAWS/PAWSTestingSetup.swift @@ -1,16 +1,15 @@ // -// This source file is part of the CS342 2023 PAWS Team Application project +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // // SPDX-License-Identifier: MIT // -import PAWSSharedContext import SwiftUI -private struct PAWSTestingSetup: ViewModifier { +private struct PAWSAppTestingSetup: ViewModifier { @AppStorage(StorageKeys.onboardingFlowComplete) var completedOnboardingFlow = false @@ -30,6 +29,6 @@ private struct PAWSTestingSetup: ViewModifier { extension View { func testingSetup() -> some View { - self.modifier(PAWSTestingSetup()) + self.modifier(PAWSAppTestingSetup()) } } diff --git a/PAWS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/PAWS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..63ed2ec7 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.349", + "green" : "0.330", + "red" : "0.850" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PAWS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json.license b/PAWS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/AccentColor.colorset/Contents.json.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json b/PAWS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json rename to PAWS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/paws-logo.png b/PAWS/Resources/Assets.xcassets/AppIcon.appiconset/paws-logo.png similarity index 100% rename from PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/paws-logo.png rename to PAWS/Resources/Assets.xcassets/AppIcon.appiconset/paws-logo.png diff --git a/PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png b/PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png similarity index 100% rename from PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png rename to PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png diff --git a/PAWS/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json b/PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json similarity index 65% rename from PAWS/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json rename to PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json index eb878970..20ccc4dc 100644 --- a/PAWS/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json @@ -1,6 +1,7 @@ { - "colors" : [ + "images" : [ { + "filename" : "AydinSquarePhoto.png", "idiom" : "universal" } ], diff --git a/PAWS/Supporting Files/Assets.xcassets/Contents.json b/PAWS/Resources/Assets.xcassets/Contents.json similarity index 100% rename from PAWS/Supporting Files/Assets.xcassets/Contents.json rename to PAWS/Resources/Assets.xcassets/Contents.json diff --git a/PAWS/Resources/Assets.xcassets/Contents.json.license b/PAWS/Resources/Assets.xcassets/Contents.json.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/Contents.json.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Resources/Assets.xcassets/NotificationImage.imageset/Contents.json b/PAWS/Resources/Assets.xcassets/NotificationImage.imageset/Contents.json new file mode 100644 index 00000000..2bd22f78 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/NotificationImage.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "NotificationImage.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/NotificationImage.png b/PAWS/Resources/Assets.xcassets/NotificationImage.imageset/NotificationImage.png similarity index 100% rename from PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/NotificationImage.png rename to PAWS/Resources/Assets.xcassets/NotificationImage.imageset/NotificationImage.png diff --git a/PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json b/PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json new file mode 100644 index 00000000..553b2510 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ScottSquarePhoto.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png b/PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png similarity index 100% rename from PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png rename to PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/ConsentDocument.md b/PAWS/Resources/ConsentDocument.md similarity index 100% rename from PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/ConsentDocument.md rename to PAWS/Resources/ConsentDocument.md diff --git a/PAWS/Resources/ConsentDocument.md.license b/PAWS/Resources/ConsentDocument.md.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/ConsentDocument.md.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Resources/Localizable.xcstrings b/PAWS/Resources/Localizable.xcstrings new file mode 100644 index 00000000..c14bd0d7 --- /dev/null +++ b/PAWS/Resources/Localizable.xcstrings @@ -0,0 +1,789 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "ACCOUNT_NEXT" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Next" + } + } + } + }, + "ACCOUNT_SETUP_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You may login to your existing account. Or create a new one if you don't have one already." + } + } + } + }, + "ACCOUNT_SIGNED_IN_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You are already logged in with the account shown below. Continue or change your account by logging out." + } + } + } + }, + "ACCOUNT_SUBTITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The PAWS demonstrates the usage of the Firebase Account Module." + } + } + } + }, + "ACCOUNT_TITLE" : { + "comment" : "MARK: Account", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your Account" + } + } + } + }, + "ANSWER_1" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Don’t be concerned, simply start another ECG recording on the Apple Watch as soon as you can." + } + } + } + }, + "ANSWER_2" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The PAWS app will store your ECG file directly on your iPhone and upload it to the research database when you have access to the internet again." + } + } + } + }, + "ANSWER_3" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The research team will review your Apple Watch ECG data periodically but will not use it in place of traditional diagnostic methods to change the course of your care plan." + } + } + } + }, + "ANSWER_4" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If you are experiencing symptoms that require immediate attention, please call 9-1-1. Neither the PAWS app nor the Apple Watch will call the emergency services for you." + } + } + } + }, + "ANSWER_5" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You will not get sick or hurt because of anything in this study. We will be studying your health information and all of your information will be kept secure." + } + } + } + }, + "ANSWER_6" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nothing good will directly happen to you but, by participating in this study, you will help us understand if the Apple Watch can be used to monitor heart problems in other kids. We hope that someday we can just give an Apple Watch to kids needing to have their heart tested." + } + } + } + }, + "ANSWER_7" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Only the researchers in charge of the study will know that you are in it. No one else will know, and your information will be kept secure." + } + } + } + }, + "ANSWER_8" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If you have any questions about the study or any problems to do with the study you can contact the Protocol Director Dr. Scott Ceresnak. You can call him at (650)-723-7913.\n\nIf you have questions about the study but want to talk to someone else who is not a part of the study, you can call the Stanford Institutional Review Board (IRB) at (650)-723-5244 or toll free at 1-866-680-2906." + } + } + } + }, + "ANSWER_9" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If you do not want to participate in the study, just tell the researchers or your parents. You don’t need to be a part of the study and you won’t get in trouble if you decide if you don’t want to. Your doctor will still treat you the same even if you don’t participate." + } + } + } + }, + "AYDIN_ZAHEDIVASH_BIO" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dr. Zahedivash is a resident physician in the Stanford Department of Pediatrics and will serve as a chief resident during the 2023-2024 academic year. His academic interests focus on the development and translation of innovative technologies and services to address diagnostic and health equity challenges. During his educational career as an engineer and budding physician, he has co-invented a series of computational techniques and devices. Previous inventions include diagnostic deep-learning algorithms to detect unstable coronary artery plaque on intravascular imaging and a surgical probe for intraoperative margin detection during cancer resection. He is passionate about using his medical, technical, and business backgrounds to develop digital health solutions and equitable technology translation strategies for children." + } + } + } + }, + "CLOSE" : { + "comment" : "MARK: General", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Close" + } + } + } + }, + "COMPLETED_TASK_LABEL %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Completed Task: %@" + } + } + } + }, + "CONSENT_LOADING_ERROR" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Please include a markdown based document named \"ConsentDocument\" in your module Bundle." + } + } + } + }, + "CONTACTS_NAVIGATION_TITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contacts" + } + } + } + }, + "CONTACTS_TAB_TITLE" : { + "comment" : "MARK: - Contacts", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contacts" + } + } + } + }, + "CONTRIBUTIONS_LIST_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The following list contains all Swift Package dependencies of the SpeziPAWS." + } + } + } + }, + "CONTRIBUTIONS_LIST_FOOTER" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Please refer to the individual repository links for packages without license labels." + } + } + } + }, + "CONTRIBUTIONS_LIST_HEADER" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Packages" + } + } + } + }, + "FAQ" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "FAQ" + } + } + } + }, + "HEALTHKIT_PERMISSIONS_BUTTON" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Grant Access" + } + } + } + }, + "HEALTHKIT_PERMISSIONS_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This onboarding step allows you to customize the onboarding flow to explain how the application uses the HealhtKit data and allows a user to cusomize the selection." + } + } + } + }, + "HEALTHKIT_PERMISSIONS_SUBTITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spezi can access data from HealthKit using the HealthKitDataSource module." + } + } + } + }, + "HEALTHKIT_PERMISSIONS_TITLE" : { + "comment" : "MARK: HealthKit", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "HealthKit Access" + } + } + } + }, + "Infos" : { + + }, + "INTERESTING_MODULES_AREA1_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The onboarding module allows you to build an onboarding flow like this one." + } + } + } + }, + "INTERESTING_MODULES_AREA1_TITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Onboarding" + } + } + } + }, + "INTERESTING_MODULES_AREA2_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The application uses HL7 FHIR to provide a common standard to encode data gathered by the application." + } + } + } + }, + "INTERESTING_MODULES_AREA2_TITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "HL7 FHIR" + } + } + } + }, + "INTERESTING_MODULES_AREA3_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The contact module allows you to display contact information in your application." + } + } + } + }, + "INTERESTING_MODULES_AREA3_TITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contact" + } + } + } + }, + "INTERESTING_MODULES_AREA4_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The HealthKit data source module allows you to fetch data from HealthKit and e.g. transform it to FHIR resources." + } + } + } + }, + "INTERESTING_MODULES_AREA4_TITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "HealthKit Data Source" + } + } + } + }, + "INTERESTING_MODULES_BUTTON" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Next" + } + } + } + }, + "INTERESTING_MODULES_SUBTITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Here are a few Spezi modules that are interesting to know about ..." + } + } + } + }, + "INTERESTING_MODULES_TITLE" : { + "comment" : "MARK: Interesting Modules", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Interesting Modules" + } + } + } + }, + "LICENSE_INFO_TITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "License Information" + } + } + } + }, + "MOCK_WEB_SERVICE_TAB_TITLE" : { + "comment" : "MARK: - Mock Upload Data Storage Provider", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mock Web Service" + } + } + } + }, + "NOTIFICATION_PERMISSIONS_BUTTON" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Allow Notifications" + } + } + } + }, + "NOTIFICATION_PERMISSIONS_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The Spezi Scheduler module enables to send out local notifications when a new event of a task is scheduled." + } + } + } + }, + "NOTIFICATION_PERMISSIONS_SUBTITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spezi Scheduler Notifications." + } + } + } + }, + "NOTIFICATION_PERMISSIONS_TITLE" : { + "comment" : "MARK: Notifications", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notifications" + } + } + } + }, + "PROJECT_LICENSE_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This project is licensed under the MIT License." + } + } + } + }, + "QUESTION_1" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "1. After pressing the event button on the Zio Patch, what do I do if I don’t get a successful recording on my Apple Watch?" + } + } + } + }, + "QUESTION_2" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "2. What happens if I don’t have internet access when I record the Apple Watch ECG?" + } + } + } + }, + "QUESTION_3" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "3. Will the research team use my Apple Watch ECG data to provide care or change my treatment plan?" + } + } + } + }, + "QUESTION_4" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "4. If I am experiencing symptoms that need immediate medical attention, will the PAWS app or Apple Watch call 9-1-1 for me?" + } + } + } + }, + "QUESTION_5" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "5. Can anything bad happen to me by participating in this study?" + } + } + } + }, + "QUESTION_6" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "6. Can anything good happen to me by participating in this study?" + } + } + } + }, + "QUESTION_7" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "7. Will anyone know I am in the study?" + } + } + } + }, + "QUESTION_8" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "8. Who can I talk to about the study?" + } + } + } + }, + "QUESTION_9" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "9. What if I do not want to be in the study?" + } + } + } + }, + "Repository Link" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Repository Link" + } + } + } + }, + "SCHEDULE_LIST_TITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schedule" + } + } + } + }, + "SCHEDULE_TAB_TITLE" : { + "comment" : "MARK: - Schedule", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schedule" + } + } + } + }, + "SCOTT_CERESNAK_BIO" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dr. Ceresnak is a Professor of Pediatrics at Stanford University in the Division of Pediatric Cardiology. He is Director of the Pediatric Electrophysiology and Arrhythmia Program at Lucile Packard Children’s Hospital. His primary clinical and research interests involve the care of children and adults with congenital heart disease, with special attention to patients with arrhythmia disorders and inherited arrhythmia syndromes. He has special research interest and experience in advancing technological growth with the goal of creating improved diagnostic and therapeutic options for children with arrhythmia disorders." + } + } + } + }, + "Study Information" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Study Information" + } + } + } + }, + "STUDY_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This study will explore how the Apple Watch® can work as a heart monitor for kids. As a part of the study, you will wear the Apple Watch for as long as 6 months, and any time you feel like your heart is beating fast or differently, you will record an ECG on the watch using the instructions below. Other than the fact that you are using an Apple Watch, there is no difference between being a part of the study and everything else about seeing your doctor.\n\nAny time that you feel symptoms that feel like an arrhythmia (i.e., palpitations, skipped beats, fast heart rate):\n\n1. First, press the event button on your Zio® Patch Monitor.\n2. Second, initiate a recording on the Apple Watch by following these steps:\n • Open the ECG App.\n • Hold your finger to the Digital Crown for 30 seconds.\n • Follow the on-screen prompts on the Apple Watch." + } + } + } + }, + "TASK_CONTEXT_ACTION_QUESTIONNAIRE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start Questionnaire" + } + } + } + }, + "TASK_CONTEXT_ACTION_TEST" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start Test" + } + } + } + }, + "TASK_LABEL %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Task: %@" + } + } + } + }, + "TASK_SOCIAL_SUPPORT_QUESTIONNAIRE_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Please fill out the Social Support Questionnaire every day." + } + } + } + }, + "TASK_SOCIAL_SUPPORT_QUESTIONNAIRE_TITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Social Support Questionnaire" + } + } + } + }, + "Test ..." : { + + }, + "WELCOME_AREA1_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The Spezi Framework builds the foundation of this application." + } + } + } + }, + "WELCOME_AREA1_TITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The Spezi Framework" + } + } + } + }, + "WELCOME_AREA2_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spezi uses the Swift Package Manager to import it as a dependency." + } + } + } + }, + "WELCOME_AREA2_TITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Swift Package Manager" + } + } + } + }, + "WELCOME_AREA3_DESCRIPTION" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spezi offers several modules including HealthKit integration, questionnaires, and more ..." + } + } + } + }, + "WELCOME_AREA3_TITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spezi Modules" + } + } + } + }, + "WELCOME_BUTTON" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Learn More" + } + } + } + }, + "WELCOME_SUBTITLE" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This application demonstrates several Spezi features & modules." + } + } + } + }, + "WELCOME_TITLE" : { + "comment" : "MARK: Welcome", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spezi\nPAWS" + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/PAWS/Resources/Localizable.xcstrings.license b/PAWS/Resources/Localizable.xcstrings.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/Localizable.xcstrings.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Resources/SocialSupportQuestionnaire.json b/PAWS/Resources/SocialSupportQuestionnaire.json new file mode 100644 index 00000000..d3c584df --- /dev/null +++ b/PAWS/Resources/SocialSupportQuestionnaire.json @@ -0,0 +1,387 @@ +{ + "resourceType": "Questionnaire", + "language": "en-US", + "id": "socialsupport", + "name": "SocialSupport", + "title": "Social Support", + "description": "This survey measures tangible social support plus a couple of demographic questions.", + "version": "1", + "status": "draft", + "publisher": "RAND Corp", + "meta": { + "profile": [ + "http://spezi.stanford.edu/fhir/StructureDefinition/sdf-Questionnaire" + ], + "tag": [ + { + "system": "urn:ietf:bcp:47", + "code": "en-US", + "display": "English" + } + ] + }, + "useContext": [ + { + "code": { + "system": "http://hl7.org/fhir/ValueSet/usage-context-type", + "code": "focus", + "display": "Clinical Focus" + }, + "valueCodeableConcept": { + "coding": [ + { + "system": "urn:oid:2.16.578.1.12.4.1.1.8655", + "display": "Social Support" + } + ] + } + } + ], + "contact": [ + { + "name": "https://www.rand.org/health-care/surveys_tools/mos/social-support/survey-instrument.html" + } + ], + "subjectType": [ + "Patient" + ], + "purpose": "The RAND Medical Outcomes Social Support survey is a 4-item questionnaire that measures social support.", + "copyright": "RAND Corp surveys are open-source and free to use.", + "date": "2023-01-23T00:00:00-08:00", + "url": "http://spezi.stanford.edu/fhir/questionnaire/32f43c8e-93e9-4c70-97a0-e716f8030073", + "item": [ + { + "linkId": "dcea2683-9815-4505-b240-e75b502b29ef", + "type": "choice", + "text": "How often do you need someone to help you if you were confined to bed?", + "required": false, + "answerOption": [ + { + "valueCoding": { + "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", + "code": "1", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "None of the time" + } + }, + { + "valueCoding": { + "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", + "code": "2", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "A little of the time" + } + }, + { + "valueCoding": { + "id": "e32f7952-e280-48d7-9746-c13dbb26638f", + "code": "3", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "Some of the time" + } + }, + { + "valueCoding": { + "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", + "code": "4", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "Most of the time" + } + }, + { + "valueCoding": { + "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", + "code": "5", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "All of the time" + } + } + ] + }, + { + "linkId": "ce09d701-7b93-4150-defb-51825e05ade9", + "type": "choice", + "text": "How often do you need someone to take you to the doctor if you needed it?", + "required": false, + "answerOption": [ + { + "valueCoding": { + "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", + "code": "1", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "None of the time" + } + }, + { + "valueCoding": { + "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", + "code": "2", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "A little of the time" + } + }, + { + "valueCoding": { + "id": "e32f7952-e280-48d7-9746-c13dbb26638f", + "code": "3", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "Some of the time" + } + }, + { + "valueCoding": { + "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", + "code": "4", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "Most of the time" + } + }, + { + "valueCoding": { + "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", + "code": "5", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "All of the time" + } + } + ] + }, + { + "linkId": "58e97564-5f4d-4d4b-86d5-6429cbbc7a8e", + "type": "choice", + "text": "How often do you need someone to prepare your meals if you were unable to do it yourself?", + "required": false, + "answerOption": [ + { + "valueCoding": { + "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", + "code": "1", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "None of the time" + } + }, + { + "valueCoding": { + "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", + "code": "2", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "A little of the time" + } + }, + { + "valueCoding": { + "id": "e32f7952-e280-48d7-9746-c13dbb26638f", + "code": "3", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "Some of the time" + } + }, + { + "valueCoding": { + "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", + "code": "4", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "Most of the time" + } + }, + { + "valueCoding": { + "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", + "code": "5", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "All of the time" + } + } + ] + }, + { + "linkId": "ad161c49-e8a6-4d31-90e8-02b2887a765f", + "type": "choice", + "text": "How often do you need someone to help with daily chores if you were sick", + "required": false, + "answerOption": [ + { + "valueCoding": { + "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", + "code": "1", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "None of the time" + } + }, + { + "valueCoding": { + "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", + "code": "2", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "A little of the time" + } + }, + { + "valueCoding": { + "id": "e32f7952-e280-48d7-9746-c13dbb26638f", + "code": "3", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "Some of the time" + } + }, + { + "valueCoding": { + "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", + "code": "4", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "Most of the time" + } + }, + { + "valueCoding": { + "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", + "code": "5", + "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", + "display": "All of the time" + } + } + ] + }, + { + "linkId": "ba518851-2843-4bbd-c0f7-5b5692d542e0", + "type": "integer", + "text": "What is your age?", + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/minValue", + "valueInteger": 18 + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/maxValue", + "valueInteger": 120 + }, + { + "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", + "valueString": "Please enter a valid age." + } + ], + "required": false + }, + { + "linkId": "695525f3-3e89-4455-8e25-878171c596da", + "type": "choice", + "text": "What is your preferred contact method?", + "required": false, + "answerOption": [ + { + "valueCoding": { + "id": "b7a3d7a5-52b9-49b1-8b59-7a3885483f1c", + "code": "phone-call", + "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", + "display": "Phone call" + } + }, + { + "valueCoding": { + "id": "3d42dde0-8e60-4832-bd46-bd06de28cbf2", + "code": "text-message", + "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", + "display": "Text message" + } + }, + { + "valueCoding": { + "id": "e672cfc6-118f-4a2d-aafd-02722ff876b9", + "code": "e-mail", + "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", + "display": "E-mail" + } + } + ] + }, + { + "linkId": "c3bea33d-4c50-4f4a-8ae4-1a52be326b19", + "type": "string", + "text": "What is your phone number? Ex. (555) 555-5555", + "required": false, + "enableWhen": [ + { + "question": "695525f3-3e89-4455-8e25-878171c596da", + "operator": "=", + "answerCoding": { + "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", + "code": "phone-call" + } + } + ], + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/regex", + "valueString": "^(\\([0-9]{3}\\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$" + }, + { + "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", + "valueString": "Please enter a valid phone number." + } + ] + }, + { + "linkId": "8e906a39-5fd0-42a8-f42c-bd96d719dd13", + "type": "string", + "text": "What is your text number? Ex. (555) 555-5555", + "required": false, + "enableWhen": [ + { + "question": "695525f3-3e89-4455-8e25-878171c596da", + "operator": "=", + "answerCoding": { + "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", + "code": "text-message" + } + } + ], + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/regex", + "valueString": "^(\\([0-9]{3}\\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$" + }, + { + "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", + "valueString": "Please enter a valid phone number." + } + ] + }, + { + "linkId": "86290b0a-017e-4193-8707-dc0c2146f0eb", + "type": "string", + "text": "What is your e-mail?", + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/regex", + "valueString": ".*@.+" + }, + { + "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", + "valueString": "Please enter a valid email" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/minLength", + "valueInteger": 1 + } + ], + "required": false, + "maxLength": 50, + "enableWhen": [ + { + "question": "695525f3-3e89-4455-8e25-878171c596da", + "operator": "=", + "answerCoding": { + "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", + "code": "e-mail" + } + } + ] + }, + { + "linkId": "305f5381-2d8b-4b98-bc04-5a39bee2f7ec", + "type": "display", + "text": "Thank you for taking the survey!", + "required": false + } + ] +} diff --git a/PAWS/Resources/SocialSupportQuestionnaire.json.license b/PAWS/Resources/SocialSupportQuestionnaire.json.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/SocialSupportQuestionnaire.json.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Schedule/Bundle+Questionnaire.swift b/PAWS/Schedule/Bundle+Questionnaire.swift new file mode 100644 index 00000000..d7784d35 --- /dev/null +++ b/PAWS/Schedule/Bundle+Questionnaire.swift @@ -0,0 +1,26 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SpeziQuestionnaire + + +extension Foundation.Bundle { + func questionnaire(withName name: String) -> Questionnaire { + guard let resourceURL = self.url(forResource: name, withExtension: "json") else { + fatalError("Could not find the questionnaire \"\(name).json\" in the bundle.") + } + + do { + let resourceData = try Data(contentsOf: resourceURL) + return try JSONDecoder().decode(Questionnaire.self, from: resourceData) + } catch { + fatalError("Could not decode the FHIR questionnaire named \"\(name).json\": \(error)") + } + } +} diff --git a/PAWS/Schedule/EventContext.swift b/PAWS/Schedule/EventContext.swift new file mode 100644 index 00000000..932ac529 --- /dev/null +++ b/PAWS/Schedule/EventContext.swift @@ -0,0 +1,25 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziScheduler + + +struct EventContext: Comparable, Identifiable { + let event: Event + let task: Task + + + var id: Event.ID { + event.id + } + + + static func < (lhs: EventContext, rhs: EventContext) -> Bool { + lhs.event.scheduledAt < rhs.event.scheduledAt + } +} diff --git a/PAWS/Schedule/EventContextView.swift b/PAWS/Schedule/EventContextView.swift new file mode 100644 index 00000000..c909cca6 --- /dev/null +++ b/PAWS/Schedule/EventContextView.swift @@ -0,0 +1,81 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziScheduler +import SwiftUI + + +struct EventContextView: View { + let eventContext: EventContext + + + var body: some View { + HStack { + VStack(alignment: .leading) { + HStack { + if eventContext.event.complete { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.accentColor) + .font(.system(size: 30)) + .accessibilityHidden(true) + } + VStack(alignment: .leading, spacing: 8) { + Text(verbatim: eventContext.task.title) + .font(.headline) + .accessibilityLabel( + eventContext.event.complete + ? "COMPLETED_TASK_LABEL \(eventContext.task.title)" + : "TASK_LABEL \(eventContext.task.title)" + ) + Text(verbatim: format(eventDate: eventContext.event.scheduledAt)) + .font(.subheadline) + } + } + Divider() + Text(eventContext.task.description) + .font(.callout) + if !eventContext.event.complete { + Text(eventContext.task.context.actionType) + .frame(maxWidth: .infinity, minHeight: 50) + .foregroundColor(.white) + .background(Color.accentColor) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .padding(.top, 8) + } + } + } + .disabled(eventContext.event.complete) + .contentShape(Rectangle()) + } + + + private func format(eventDate: Date) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .none + dateFormatter.timeStyle = .short + return dateFormatter.string(from: eventDate) + } +} + + +#if DEBUG +#Preview(traits: .sizeThatFitsLayout) { + let task = PAWSScheduler.socialSupportTask + + return EventContextView( + eventContext: EventContext( + // We use a force unwrap in the preview as we can not recover from an error here + // and the code will never end up in a production environment. + // swiftlint:disable:next force_unwrapping + event: task.events(from: .now.addingTimeInterval(-60 * 60 * 24)).first!, + task: task + ) + ) + .padding() +} +#endif diff --git a/PAWS/Schedule/ModalView.swift b/PAWS/Schedule/ModalView.swift new file mode 100644 index 00000000..4fd26b44 --- /dev/null +++ b/PAWS/Schedule/ModalView.swift @@ -0,0 +1,47 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziViews +import SwiftUI + + +struct ModalView: View { + @Environment(\.dismiss) private var dismiss + + let text: String + let buttonText: String + let onClose: () async -> Void + + + var body: some View { + VStack { + Spacer() + Text(text) + .padding() + Spacer() + AsyncButton { + self.dismiss() + await self.onClose() + } label: { + Text(buttonText) + .frame(maxWidth: .infinity, minHeight: 38) + } + .padding() + .buttonStyle(.borderedProminent) + } + } +} + + +#if DEBUG +#Preview { + ModalView(text: "Preview Modal", buttonText: "Close") { + print("Preview Modal closed.") + } +} +#endif diff --git a/PAWS/Schedule/PAWSScheduler.swift b/PAWS/Schedule/PAWSScheduler.swift new file mode 100644 index 00000000..9e58e713 --- /dev/null +++ b/PAWS/Schedule/PAWSScheduler.swift @@ -0,0 +1,49 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SpeziScheduler + + +/// A `Scheduler` using the ``PAWSTaskContext`` to schedule and manage tasks and events in the +/// PAWS. +typealias PAWSScheduler = Scheduler + + +extension PAWSScheduler { + static var socialSupportTask: SpeziScheduler.Task { + let dateComponents: DateComponents + if FeatureFlags.testSchedule { + // Adds a task at the current time for UI testing if the `--testSchedule` feature flag is set + dateComponents = DateComponents( + hour: Calendar.current.component(.hour, from: .now), + minute: Calendar.current.component(.minute, from: .now) + ) + } else { + // For the normal app usage, we schedule the task for every day at 8:00 AM + dateComponents = DateComponents(hour: 8, minute: 0) + } + + return Task( + title: String(localized: "TASK_SOCIAL_SUPPORT_QUESTIONNAIRE_TITLE"), + description: String(localized: "TASK_SOCIAL_SUPPORT_QUESTIONNAIRE_DESCRIPTION"), + schedule: Schedule( + start: Calendar.current.startOfDay(for: Date()), + repetition: .matching(dateComponents), + end: .numberOfEvents(365) + ), + notifications: true, + context: PAWSTaskContext.questionnaire(Bundle.main.questionnaire(withName: "SocialSupportQuestionnaire")) + ) + } + + /// Creates a default instance of the ``PAWSScheduler`` by scheduling the tasks listed below. + convenience init() { + self.init(tasks: [Self.socialSupportTask]) + } +} diff --git a/PAWS/Schedule/PAWSTaskContext.swift b/PAWS/Schedule/PAWSTaskContext.swift new file mode 100644 index 00000000..4eb69db1 --- /dev/null +++ b/PAWS/Schedule/PAWSTaskContext.swift @@ -0,0 +1,40 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import ModelsR4 + + +/// The context attached to each task in the PAWS. +/// +/// We currently only support `Questionnaire`s, more cases can be added in the future. +enum PAWSTaskContext: Codable, Identifiable { + /// The task should display a `Questionnaire`. + case questionnaire(Questionnaire) + /// The task is used for UI testing. + case test(String) + + + var id: FHIRPrimitive? { + switch self { + case let .questionnaire(questionnaire): + return questionnaire.id + case .test: + return FHIRPrimitive(FHIRString(UUID().uuidString)) + } + } + + var actionType: LocalizedStringResource { + switch self { + case .questionnaire: + return LocalizedStringResource("TASK_CONTEXT_ACTION_QUESTIONNAIRE") + case .test: + return LocalizedStringResource("TASK_CONTEXT_ACTION_TEST") + } + } +} diff --git a/PAWS/Schedule/ScheduleView.swift b/PAWS/Schedule/ScheduleView.swift new file mode 100644 index 00000000..9516deb7 --- /dev/null +++ b/PAWS/Schedule/ScheduleView.swift @@ -0,0 +1,131 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziAccount +import SpeziQuestionnaire +import SpeziScheduler +import SwiftUI + + +struct ScheduleView: View { + @Environment(PAWSStandard.self) private var standard + @Environment(PAWSScheduler.self) private var scheduler + @State private var eventContextsByDate: [Date: [EventContext]] = [:] + @State private var presentedContext: EventContext? + + + @Binding private var presentingAccount: Bool + + + private var startOfDays: [Date] { + Array(eventContextsByDate.keys) + } + + + var body: some View { + NavigationStack { + List(startOfDays, id: \.timeIntervalSinceNow) { startOfDay in + Section(format(startOfDay: startOfDay)) { + ForEach(eventContextsByDate[startOfDay] ?? [], id: \.event) { eventContext in + EventContextView(eventContext: eventContext) + .onTapGesture { + if !eventContext.event.complete { + presentedContext = eventContext + } + } + } + } + } + .onChange(of: scheduler) { + calculateEventContextsByDate() + } + .task { + calculateEventContextsByDate() + } + .sheet(item: $presentedContext) { presentedContext in + destination(withContext: presentedContext) + } + .toolbar { + if AccountButton.shouldDisplay { + AccountButton(isPresented: $presentingAccount) + } + } + .navigationTitle("SCHEDULE_LIST_TITLE") + } + } + + + init(presentingAccount: Binding) { + self._presentingAccount = presentingAccount + } + + + private func destination(withContext eventContext: EventContext) -> some View { + @ViewBuilder var destination: some View { + switch eventContext.task.context { + case let .questionnaire(questionnaire): + QuestionnaireView(questionnaire: questionnaire) { result in + presentedContext = nil + + guard case let .completed(response) = result else { + return // user cancelled the task + } + + eventContext.event.complete(true) + await standard.add(response: response) + } + case let .test(string): + ModalView(text: string, buttonText: String(localized: "CLOSE")) { + await eventContext.event.complete(true) + } + } + } + return destination + } + + + private func format(startOfDay: Date) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .long + dateFormatter.timeStyle = .none + return dateFormatter.string(from: startOfDay) + } + + private func calculateEventContextsByDate() { + let eventContexts = scheduler.tasks.flatMap { task in + task + .events( + from: Calendar.current.startOfDay(for: .now), + to: .numberOfEventsOrEndDate(100, .now) + ) + .map { event in + EventContext(event: event, task: task) + } + } + .sorted() + + let newEventContextsByDate = Dictionary(grouping: eventContexts) { eventContext in + Calendar.current.startOfDay(for: eventContext.event.scheduledAt) + } + + eventContextsByDate = newEventContextsByDate + } +} + + +#if DEBUG +#Preview("ScheduleView") { + ScheduleView(presentingAccount: .constant(false)) + .previewWith(standard: PAWSStandard()) { + PAWSScheduler() + AccountConfiguration { + MockUserIdPasswordAccountService() + } + } +} +#endif diff --git a/PAWSModules/Sources/PAWSSharedContext/FeatureFlags.swift b/PAWS/SharedContext/FeatureFlags.swift similarity index 55% rename from PAWSModules/Sources/PAWSSharedContext/FeatureFlags.swift rename to PAWS/SharedContext/FeatureFlags.swift index 68fe73c9..388d2cfb 100644 --- a/PAWSModules/Sources/PAWSSharedContext/FeatureFlags.swift +++ b/PAWS/SharedContext/FeatureFlags.swift @@ -1,24 +1,26 @@ // -// This source file is part of the CS342 2023 PAWS Team Application project +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // // SPDX-License-Identifier: MIT // -/// A collection of feature flags for the PAWS app. -public enum FeatureFlags { +/// A collection of feature flags for the PAWS. +enum FeatureFlags { /// Skips the onboarding flow to enable easier development of features in the application and to allow UI tests to skip the onboarding flow. - public static let skipOnboarding = CommandLine.arguments.contains("--skipOnboarding") + static let skipOnboarding = CommandLine.arguments.contains("--skipOnboarding") /// Always show the onboarding when the application is launched. Makes it easy to modify and test the onboarding flow without the need to manually remove the application or reset the simulator. - public static let showOnboarding = CommandLine.arguments.contains("--showOnboarding") + static let showOnboarding = CommandLine.arguments.contains("--showOnboarding") /// Disables the Firebase interactions, including the login/sign-up step and the Firebase Firestore upload. - public static let disableFirebase = CommandLine.arguments.contains("--disableFirebase") + static let disableFirebase = CommandLine.arguments.contains("--disableFirebase") #if targetEnvironment(simulator) /// Defines if the application should connect to the local firebase emulator. Always set to true when using the iOS simulator. - public static let useFirebaseEmulator = true + static let useFirebaseEmulator = true #else /// Defines if the application should connect to the local firebase emulator. Always set to true when using the iOS simulator. - public static let useFirebaseEmulator = CommandLine.arguments.contains("--useFirebaseEmulator") + static let useFirebaseEmulator = CommandLine.arguments.contains("--useFirebaseEmulator") #endif + /// Adds a test task to the schedule at the current time + static let testSchedule = CommandLine.arguments.contains("--testSchedule") } diff --git a/PAWS/SharedContext/PAWSCard.swift b/PAWS/SharedContext/PAWSCard.swift new file mode 100644 index 00000000..6eb54cd7 --- /dev/null +++ b/PAWS/SharedContext/PAWSCard.swift @@ -0,0 +1,52 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +struct PAWSCard: View { + private let content: Content + + + var body: some View { + Group { + content + } + .cornerRadius(5) + .padding(3) + .background( + ZStack { + RoundedRectangle(cornerRadius: 5) + .stroke( + LinearGradient( + colors: [.red, .pink, .yellow], + startPoint: .leading, + endPoint: .trailing + ), + lineWidth: 4 + ) + .shadow(radius: 5) + RoundedRectangle(cornerRadius: 5) + .foregroundStyle(.background) + } + ) + .padding(.horizontal) + } + + + init(@ViewBuilder content: () -> Content) { + self.content = content() + } +} + + +#Preview { + PAWSCard { + Text("Test ...") + } +} diff --git a/PAWS/SharedContext/StorageKeys.swift b/PAWS/SharedContext/StorageKeys.swift new file mode 100644 index 00000000..16834bde --- /dev/null +++ b/PAWS/SharedContext/StorageKeys.swift @@ -0,0 +1,21 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +/// Constants shared across the Spezi Teamplate Application to access storage information including the `AppStorage` and `SceneStorage` +enum StorageKeys { + // MARK: - Onboarding + /// A `Bool` flag indicating of the onboarding was completed. + static let onboardingFlowComplete = "onboardingFlow.complete" + /// A `Step` flag indicating the current step in the onboarding process. + static let onboardingFlowStep = "onboardingFlow.step" + + + // MARK: - Home + /// The currently selected home tab. + static let homeTabSelection = "home.tabselection" +} diff --git a/PAWS/Study Information/FAQ.swift b/PAWS/Study Information/FAQ.swift new file mode 100644 index 00000000..aa58d6e9 --- /dev/null +++ b/PAWS/Study Information/FAQ.swift @@ -0,0 +1,74 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +struct FAQ: View { + private struct Question: Identifiable { + let question: LocalizedStringResource + let answer: LocalizedStringResource + + + var id: String { + question.localizedString() + } + } + + + private static var questions: [Question] = { + (1...9).map { index in + Question( + question: "QUESTION_\(index)".localized(), + answer: "ANSWER_\(index)".localized() + ) + } + }() + + + var body: some View { + HStack { + Text("FAQ") + .font(.title.bold()) + Spacer() + } + .padding(.horizontal) + .padding(.top, 24) + .padding(.bottom, 8) + ForEach(Self.questions) { question in + PAWSCard { + VStack(alignment: .leading) { + HStack { + Text(question.question) + .font(.title3.bold()) + .multilineTextAlignment(.leading) + .padding(.bottom, 4) + Spacer() + } + HStack { + Text(question.answer) + .multilineTextAlignment(.leading) + Spacer() + } + } + .padding() + .frame(maxWidth: .infinity) + } + .padding(.vertical, 8) + } + } +} + + +#Preview { + NavigationStack { + ScrollView { + FAQ() + } + } +} diff --git a/PAWS/Study Information/StudyDescription.swift b/PAWS/Study Information/StudyDescription.swift new file mode 100644 index 00000000..30cb0a4a --- /dev/null +++ b/PAWS/Study Information/StudyDescription.swift @@ -0,0 +1,33 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + +struct StudyDescription: View { + var body: some View { + PAWSCard { + VStack(alignment: .leading) { + HStack { + Text("STUDY_DESCRIPTION") + .multilineTextAlignment(.leading) + Spacer() + } + } + .padding() + .frame(maxWidth: .infinity) + } + .padding(.vertical, 8) + } +} + + +#Preview { + NavigationStack { + StudyDescription() + } +} diff --git a/PAWS/Study Information/StudyInformation.swift b/PAWS/Study Information/StudyInformation.swift new file mode 100644 index 00000000..240cd95c --- /dev/null +++ b/PAWS/Study Information/StudyInformation.swift @@ -0,0 +1,41 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +struct StudyInformation: View { + @Binding var presentingAccount: Bool + + + var body: some View { + NavigationStack { + ScrollView { + VStack(spacing: 0) { + StudyDescription() + FAQ() + } + } + .toolbar { + if AccountButton.shouldDisplay { + AccountButton(isPresented: $presentingAccount) + } + } + .navigationTitle(String(localized: "Study Information")) + } + } + + + init(presentingAccount: Binding) { + self._presentingAccount = presentingAccount + } +} + +#Preview { + StudyInformation(presentingAccount: .constant(false)) +} diff --git a/PAWS/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json.license b/PAWS/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json.license deleted file mode 100644 index 9137ea9d..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json.license b/PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json.license deleted file mode 100644 index 9137ea9d..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/paws-logo.png.license b/PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/paws-logo.png.license deleted file mode 100644 index 9137ea9d..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/AppIcon.appiconset/paws-logo.png.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png.license b/PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png.license deleted file mode 100644 index 9137ea9d..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json b/PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json deleted file mode 100644 index 2df9cc96..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "AydinSquarePhoto.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json.license b/PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json.license deleted file mode 100644 index 9137ea9d..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWS/Supporting Files/Assets.xcassets/Contents.json.license b/PAWS/Supporting Files/Assets.xcassets/Contents.json.license deleted file mode 100644 index 9137ea9d..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/Contents.json.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/Contents.json b/PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/Contents.json deleted file mode 100644 index bc46caed..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "NotificationImage.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/Contents.json.license b/PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/Contents.json.license deleted file mode 100644 index 9137ea9d..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/Contents.json.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/NotificationImage.png.license b/PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/NotificationImage.png.license deleted file mode 100644 index 9137ea9d..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/NotificationImage.imageset/NotificationImage.png.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json b/PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json deleted file mode 100644 index d2705d74..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "ScottSquarePhoto.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json.license b/PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json.license deleted file mode 100644 index 358a2369..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json.license +++ /dev/null @@ -1,7 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT - diff --git a/PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png.license b/PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png.license deleted file mode 100644 index 358a2369..00000000 --- a/PAWS/Supporting Files/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png.license +++ /dev/null @@ -1,7 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT - diff --git a/PAWS/Supporting Files/GoogleService-Info.plist b/PAWS/Supporting Files/GoogleService-Info.plist index 62410148..5a975050 100644 --- a/PAWS/Supporting Files/GoogleService-Info.plist +++ b/PAWS/Supporting Files/GoogleService-Info.plist @@ -3,13 +3,13 @@ CLIENT_ID - 750636343858-kev1gbnfovu771ri9hqlh33kuh6reqcg.apps.googleusercontent.com + CLIENT_ID REVERSED_CLIENT_ID - com.googleusercontent.apps.750636343858-kev1gbnfovu771ri9hqlh33kuh6reqcg + REVERSED_CLIENT_ID API_KEY - AIzaSyAkafWtgHHisoqURUyyYDW9Oj3gBSUaefQ + API_KEY GCM_SENDER_ID - 750636343858 + GCM_SENDER_ID PLIST_VERSION 1 BUNDLE_ID @@ -17,18 +17,18 @@ PROJECT_ID cs342-2023-paws STORAGE_BUCKET - cs342-2023-paws.appspot.com + STORAGE_BUCKET IS_ADS_ENABLED - + IS_ANALYTICS_ENABLED - + IS_APPINVITE_ENABLED - + IS_GCM_ENABLED - + IS_SIGNIN_ENABLED - + GOOGLE_APP_ID - 1:750636343858:ios:2d29a3cdbe7e71868e122b + 1:123456789012:ios:1234567890123456789012 - \ No newline at end of file + diff --git a/PAWS/Supporting Files/GoogleService-Info.plist.license b/PAWS/Supporting Files/GoogleService-Info.plist.license index 9137ea9d..c310108a 100644 --- a/PAWS/Supporting Files/GoogleService-Info.plist.license +++ b/PAWS/Supporting Files/GoogleService-Info.plist.license @@ -1,6 +1,5 @@ +This source file is part of the PAWS application based on the Stanford Spezi Template Application project -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University +SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) SPDX-License-Identifier: MIT diff --git a/PAWS/Supporting Files/Info.plist b/PAWS/Supporting Files/Info.plist index 24f5161d..5dc3321f 100644 --- a/PAWS/Supporting Files/Info.plist +++ b/PAWS/Supporting Files/Info.plist @@ -2,14 +2,8 @@ - INIntentsSupported - - Intent - ITSAppUsesNonExemptEncryption - NSUserNotificationsUsageDescription - Reminders to record ECG UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -17,5 +11,7 @@ UISceneConfigurations + CFBundleAllowMixedLocalizations + diff --git a/PAWS/Supporting Files/Info.plist.license b/PAWS/Supporting Files/Info.plist.license index 9137ea9d..4f324d88 100644 --- a/PAWS/Supporting Files/Info.plist.license +++ b/PAWS/Supporting Files/Info.plist.license @@ -1,5 +1,5 @@ -This source file is part of the CS342 2023 PAWS Team Application project +This source file is part of the PAWS application based on the Stanford Spezi Template Application project SPDX-FileCopyrightText: 2023 Stanford University diff --git a/PAWS/Supporting Files/Localizable.strings b/PAWS/Supporting Files/Localizable.strings deleted file mode 100644 index 98ad3022..00000000 --- a/PAWS/Supporting Files/Localizable.strings +++ /dev/null @@ -1,22 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - - -// MARK: - Notifications -"NOTIFICATIONS_TAB_TITLE" = "Notifications"; - -// MARK: - Contacts -"CONTACTS_TAB_TITLE" = "Contacts"; - - -// MARK: - Mock Upload Data Storage Provider -"MOCK_UPLOAD_TAB_TITLE" = "Reports"; - - -// MARK: - Schedule -"SCHEDULE_TAB_TITLE" = "Schedule"; diff --git a/PAWS/Supporting Files/PAWS.entitlements b/PAWS/Supporting Files/PAWS.entitlements index dab226cc..c541ce6f 100644 --- a/PAWS/Supporting Files/PAWS.entitlements +++ b/PAWS/Supporting Files/PAWS.entitlements @@ -2,6 +2,10 @@ + com.apple.developer.applesignin + + Default + com.apple.developer.healthkit com.apple.developer.healthkit.access diff --git a/PAWS/Supporting Files/PAWS.entitlements.license b/PAWS/Supporting Files/PAWS.entitlements.license index 9137ea9d..4f324d88 100644 --- a/PAWS/Supporting Files/PAWS.entitlements.license +++ b/PAWS/Supporting Files/PAWS.entitlements.license @@ -1,5 +1,5 @@ -This source file is part of the CS342 2023 PAWS Team Application project +This source file is part of the PAWS application based on the Stanford Spezi Template Application project SPDX-FileCopyrightText: 2023 Stanford University diff --git a/PAWSModules/Package.swift b/PAWSModules/Package.swift deleted file mode 100644 index b4358d9f..00000000 --- a/PAWSModules/Package.swift +++ /dev/null @@ -1,84 +0,0 @@ -// swift-tools-version: 5.7 - -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import PackageDescription - - -let package = Package( - name: "PAWSModules", - defaultLocalization: "en", - platforms: [ - .iOS(.v16) - ], - products: [ - .library(name: "PAWSContacts", targets: ["PAWSContacts"]), - .library(name: "PAWSMockDataStorageProvider", targets: ["PAWSMockDataStorageProvider"]), - .library(name: "PAWSOnboardingFlow", targets: ["PAWSOnboardingFlow"]), - .library(name: "PAWSSharedContext", targets: ["PAWSSharedContext"]), - .library(name: "PAWSLandingScreen", targets: ["PAWSLandingScreen"]), - .library(name: "PAWSNotificationScreen", targets: ["PAWSNotificationScreen"]) - ], - dependencies: [ - .package(url: "https://github.com/StanfordBDHG/CardinalKit.git", .upToNextMinor(from: "0.3.5")), - .package(url: "https://github.com/firebase/firebase-ios-sdk.git", from: "10.5.0"), - .package(url: "https://github.com/apple/FHIRModels.git", .upToNextMajor(from: "0.4.0")) - ], - targets: [ - .target( - name: "PAWSContacts", - dependencies: [ - .target(name: "PAWSSharedContext"), - .product(name: "Contact", package: "CardinalKit") - ], - resources: [ - .process("Resources") - ] - ), - .target( - name: "PAWSMockDataStorageProvider", - dependencies: [ - .target(name: "PAWSSharedContext"), - .product(name: "CardinalKit", package: "CardinalKit"), - .product(name: "FHIR", package: "CardinalKit"), - .product(name: "FirebaseAccount", package: "CardinalKit"), - .product(name: "FirestoreDataStorage", package: "firebase-ios-sdk"), - .product(name: "FirebaseFirestore", package: "firebase-ios-sdk"), - .product(name: "FirebaseFirestoreSwift", package: "firebase-ios-sdk"), - .product(name: "FirebaseAuth", package: "firebase-ios-sdk"), - .product(name: "ModelsR4", package: "fhirmodels") - ], - resources: [ - .process("Resources") - ] - ), - .target( - name: "PAWSOnboardingFlow", - dependencies: [ - .target(name: "PAWSSharedContext"), - .product(name: "FHIR", package: "CardinalKit"), - .product(name: "FirebaseAccount", package: "CardinalKit"), - .product(name: "HealthKitDataSource", package: "CardinalKit"), - .product(name: "Onboarding", package: "CardinalKit") - ], - resources: [ - .process("Resources") - ] - ), - .target( - name: "PAWSSharedContext" - ), - .target( - name: "PAWSLandingScreen" - ), - .target( - name: "PAWSNotificationScreen" - ) - ] -) diff --git a/PAWSModules/Sources/PAWSContacts/Contacts.swift b/PAWSModules/Sources/PAWSContacts/Contacts.swift deleted file mode 100644 index 0b9a8ac5..00000000 --- a/PAWSModules/Sources/PAWSContacts/Contacts.swift +++ /dev/null @@ -1,463 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Contact -import Foundation -import SwiftUI - -struct Question1234View: View { - var body: some View { - VStack(spacing: 1) { - Text("FAQ") - .font(.custom("Gill Sans", fixedSize: 28)) - } - VStack(spacing: 1) { - Text("1. After pressing the event button on the Zio Patch, what do I do if I don’t get a successful recording on my Apple Watch?") - .font(.custom("Gill Sans SemiBold", fixedSize: 19)) - .padding() - Text(question1) - .font(.custom("GillSans", fixedSize: 18)) - .padding() - } - .padding(.vertical, 20) - .padding(.horizontal, 10) - .background { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ), width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - .padding(.horizontal) - .padding(.vertical, 2) - - VStack(spacing: 1) { - Text("2. What happens if I don’t have internet access when I record the Apple Watch ECG?") - .font(.custom("Gill Sans SemiBold", fixedSize: 19)) - .padding() - Text(question2) - .font(.custom("GillSans", fixedSize: 18)) - .padding() - } - .padding(.vertical, 20) - .padding(.horizontal, 10) - .background { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ), width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - .padding(.horizontal) - .padding(.vertical, 2) - - VStack(spacing: 1) { - Text("3. Will the research team use my Apple Watch ECG data to provide care or change my treatment plan?") - .font(.custom("Gill Sans SemiBold", fixedSize: 19)) - .padding() - Text(question3) - .font(.custom("GillSans", fixedSize: 18)) - .padding() - } - .padding(.vertical, 20) - .padding(.horizontal, 10) - .background { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ), width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - .padding(.horizontal) - .padding(.vertical, 2) - - VStack(spacing: 1) { - Text("4. If I am experiencing symptoms that need immediate medical attention, will the PAWS app or Apple Watch call 9-1-1 for me?") - .font(.custom("Gill Sans SemiBold", fixedSize: 19)) - .padding() - Text(question4) - .font(.custom("GillSans", fixedSize: 18)) - .padding() - } - .padding(.vertical, 20) - .padding(.horizontal, 10) - .background { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ), width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - .padding(.horizontal) - .padding(.vertical, 2) - } - - private var question1: String { - guard let question1Path = Bundle.module.path(forResource: "Question1", ofType: "md"), - let question1 = try? String(contentsOfFile: question1Path) else { - return "" - } - - return question1 - } - - private var question2: String { - guard let question2Path = Bundle.module.path(forResource: "Question2", ofType: "md"), - let question2 = try? String(contentsOfFile: question2Path) else { - return "" - } - - return question2 - } - - private var question3: String { - guard let question3Path = Bundle.module.path(forResource: "Question3", ofType: "md"), - let question3 = try? String(contentsOfFile: question3Path) else { - return "" - } - - return question3 - } - - private var question4: String { - guard let question4Path = Bundle.module.path(forResource: "Question4", ofType: "md"), - let question4 = try? String(contentsOfFile: question4Path) else { - return "" - } - - return question4 - } -} - -struct Question56789View: View { - var body: some View { - VStack(spacing: 1) { - Text("5. Can anything bad happen to me by participating in this study?") - .font(.custom("Gill Sans SemiBold", fixedSize: 19)) - .padding() - Text(question5) - .font(.custom("GillSans", fixedSize: 18)) - .padding() - } - .padding(.vertical, 20) - .padding(.horizontal, 10) - .background { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ), width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - .padding(.horizontal) - .padding(.vertical, 2) - - VStack(spacing: 1) { - Text("6. Can anything good happen to me by participating in this study?") - .font(.custom("Gill Sans SemiBold", fixedSize: 19)) - .padding() - Text(question6) - .font(.custom("GillSans", fixedSize: 18)) - .padding() - } - .padding(.vertical, 20) - .padding(.horizontal, 10) - .background { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ), width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - .padding(.horizontal) - .padding(.vertical, 2) - - VStack(spacing: 1) { - Text("7. Will anyone know I am in the study?") - .font(.custom("Gill Sans SemiBold", fixedSize: 19)) - .padding() - Text(question7) - .font(.custom("GillSans", fixedSize: 18)) - .padding() - } - .padding(.vertical, 20) - .padding(.horizontal, 10) - .background { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ), width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - .padding(.horizontal) - .padding(.vertical, 2) - - VStack(spacing: 1) { - Text("8. Who can I talk to about the study?") - .font(.custom("Gill Sans SemiBold", fixedSize: 19)) - .padding() - Text(question8) - .font(.custom("GillSans", fixedSize: 18)) - .padding() - } - .padding(.vertical, 20) - .padding(.horizontal, 10) - .background { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ), width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - .padding(.horizontal) - .padding(.vertical, 2) - - VStack(spacing: 1) { - Text("9. What if I do not want to be in the study?") - .font(.custom("Gill Sans SemiBold", fixedSize: 19)) - .padding() - Text(question9) - .font(.custom("GillSans", fixedSize: 18)) - .padding() - } - .padding(.vertical, 20) - .padding(.horizontal, 10) - .background { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ), width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - .padding(.horizontal) - .padding(.vertical, 2) - } - - private var question5: String { - guard let question5Path = Bundle.module.path(forResource: "Question5", ofType: "md"), - let question5 = try? String(contentsOfFile: question5Path) else { - return "" - } - - return question5 - } - - private var question6: String { - guard let question6Path = Bundle.module.path(forResource: "Question6", ofType: "md"), - let question6 = try? String(contentsOfFile: question6Path) else { - return "" - } - - return question6 - } - - private var question7: String { - guard let question7Path = Bundle.module.path(forResource: "Question7", ofType: "md"), - let question7 = try? String(contentsOfFile: question7Path) else { - return "" - } - - return question7 - } - - private var question8: String { - guard let question8Path = Bundle.module.path(forResource: "Question8", ofType: "md"), - let question8 = try? String(contentsOfFile: question8Path) else { - return "" - } - - return question8 - } - - private var question9: String { - guard let question9Path = Bundle.module.path(forResource: "Question9", ofType: "md"), - let question9 = try? String(contentsOfFile: question9Path) else { - return "" - } - - return question9 - } -} - -public struct Contacts: View { - let contacts = [ - Contact( - name: PersonNameComponents( - givenName: "Scott", - familyName: "Ceresnak" - ), - image: Image(decorative: "ScottSquarePhoto"), - title: "Professor of Pediatrics (Cardiology)", - description: String(localized: "SCOTT_CERESNAK_BIO", bundle: .module), - organization: "Stanford University", - address: { - let address = CNMutablePostalAddress() - address.country = "USA" - address.state = "CA" - address.postalCode = "94304" - address.city = "Palo Alto" - address.street = "725 Welch Rd" - return address - }(), - contactOptions: [ - .email(addresses: ["ceresnak@stanford.edu"]), - ContactOption( - image: Image(systemName: "safari.fill"), - title: "Website", - action: { - if let url = URL(string: "https://profiles.stanford.edu/intranet/scott-ceresnak?tab=bio") { - UIApplication.shared.open(url) - } - } - ) - ] - ), - Contact( - name: PersonNameComponents( - givenName: "Aydin", - familyName: "Zahedivash" - ), - image: Image(decorative: "AydinSquarePhoto"), - title: "Pediatric Stanford Cardiology Fellow", - description: String(localized: "AYDIN_ZAHEDIVASH_BIO", bundle: .module), - organization: "Stanford University", - address: { - let address = CNMutablePostalAddress() - address.country = "USA" - address.state = "CA" - address.postalCode = "94304" - address.city = "Palo Alto" - address.street = "725 Welch Rd" - return address - }(), - contactOptions: [ - .email(addresses: ["aydinz@stanford.edu"]), - ContactOption( - image: Image(systemName: "safari.fill"), - title: "Website", - action: { - if let url = URL(string: "https://profiles.stanford.edu/intranet/scott-ceresnak?tab=bio") { - UIApplication.shared.open(url) - } - } - ) - ] - ) - ] - - - public var body: some View { - NavigationStack { - ScrollView(.vertical) { - DescriptionView() - .padding(.vertical, 12) - Question1234View() - .padding(.vertical, 12) - Question56789View() - .padding(.vertical, 12) - Text("CONTACTS") - .font(.custom("Gill Sans", fixedSize: 28)) - ForEach(contacts, id: \.name) { contact in - ContactView(contact: contact) - .padding() - .padding() - .background { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ), width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - .padding(.horizontal) - .padding(.vertical, 6) - } - .padding(.vertical, 6) - } - .background(Color(.systemGroupedBackground)) - .navigationTitle(String(localized: "CONTACTS_NAVIGATION_TITLE", bundle: .module)) - } - } - - public init() {} -} - - -struct Contacts_Previews: PreviewProvider { - static var previews: some View { - Contacts() - } -} diff --git a/PAWSModules/Sources/PAWSContacts/Description.swift b/PAWSModules/Sources/PAWSContacts/Description.swift deleted file mode 100644 index 4baf0cb5..00000000 --- a/PAWSModules/Sources/PAWSContacts/Description.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SwiftUI - -struct DescriptionView: View { - private let backgroundGradient = LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ) - - var body: some View { - VStack(spacing: 1) { - Text("DESCRIPTION") - .font(.custom("Gill Sans", fixedSize: 28)) - .padding() - Text(description) - .font(.custom("GillSans", fixedSize: 18)) - .padding() - .padding() - .background { - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(backgroundGradient, width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - .padding(.horizontal) - .padding(.vertical, 2) - } - } - - private var description: String { - guard let descriptionPath = Bundle.module.path(forResource: "StudyDescription", ofType: "md"), - let description = try? String(contentsOfFile: descriptionPath) else { - return "" - } - - return description - } -} - -struct DescriptionView_Previews: PreviewProvider { - static var previews: some View { - DescriptionView() - } -} diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question1.md b/PAWSModules/Sources/PAWSContacts/Resources/Question1.md deleted file mode 100644 index cec0d483..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question1.md +++ /dev/null @@ -1 +0,0 @@ -Don’t be concerned, simply start another ECG recording on the Apple Watch as soon as you can. diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question1.md.license b/PAWSModules/Sources/PAWSContacts/Resources/Question1.md.license deleted file mode 100644 index 7a306f20..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question1.md.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question2.md b/PAWSModules/Sources/PAWSContacts/Resources/Question2.md deleted file mode 100644 index 508d01ae..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question2.md +++ /dev/null @@ -1 +0,0 @@ -The PAWS app will store your ECG file directly on your iPhone and upload it to the research database when you have access to the internet again. diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question2.md.license b/PAWSModules/Sources/PAWSContacts/Resources/Question2.md.license deleted file mode 100644 index 7a306f20..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question2.md.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question3.md b/PAWSModules/Sources/PAWSContacts/Resources/Question3.md deleted file mode 100644 index 70d9b201..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question3.md +++ /dev/null @@ -1 +0,0 @@ -The research team will review your Apple Watch ECG data periodically but will not use it in place of traditional diagnostic methods to change the course of your care plan. diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question3.md.license b/PAWSModules/Sources/PAWSContacts/Resources/Question3.md.license deleted file mode 100644 index 7a306f20..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question3.md.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question4.md b/PAWSModules/Sources/PAWSContacts/Resources/Question4.md deleted file mode 100644 index b73ad2bf..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question4.md +++ /dev/null @@ -1 +0,0 @@ -If you are experiencing symptoms that require immediate attention, please call 9-1-1. Neither the PAWS app nor the Apple Watch will call the emergency services for you. diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question4.md.license b/PAWSModules/Sources/PAWSContacts/Resources/Question4.md.license deleted file mode 100644 index 7a306f20..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question4.md.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question5.md b/PAWSModules/Sources/PAWSContacts/Resources/Question5.md deleted file mode 100644 index 6a4a0edd..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question5.md +++ /dev/null @@ -1 +0,0 @@ -You will not get sick or hurt because of anything in this study. We will be studying your health information and all of your information will be kept secure. diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question5.md.license b/PAWSModules/Sources/PAWSContacts/Resources/Question5.md.license deleted file mode 100644 index 7a306f20..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question5.md.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question6.md b/PAWSModules/Sources/PAWSContacts/Resources/Question6.md deleted file mode 100644 index 4e4284d1..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question6.md +++ /dev/null @@ -1 +0,0 @@ -Nothing good will directly happen to you but, by participating in this study, you will help us understand if the Apple Watch can be used to monitor heart problems in other kids. We hope that someday we can just give an Apple Watch to kids needing to have their heart tested. diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question6.md.license b/PAWSModules/Sources/PAWSContacts/Resources/Question6.md.license deleted file mode 100644 index 7a306f20..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question6.md.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question7.md b/PAWSModules/Sources/PAWSContacts/Resources/Question7.md deleted file mode 100644 index a14056ab..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question7.md +++ /dev/null @@ -1 +0,0 @@ -Only the researchers in charge of the study will know that you are in it. No one else will know, and your information will be kept secure. diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question7.md.license b/PAWSModules/Sources/PAWSContacts/Resources/Question7.md.license deleted file mode 100644 index 7a306f20..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question7.md.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question8.md b/PAWSModules/Sources/PAWSContacts/Resources/Question8.md deleted file mode 100644 index 56e06fde..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question8.md +++ /dev/null @@ -1,4 +0,0 @@ -If you have any questions about the study or any problems to do with the study you can contact the Protocol Director Dr. Scott Ceresnak. You can call him at (650)-723-7913. - - -If you have questions about the study but want to talk to someone else who is not a part of the study, you can call the Stanford Institutional Review Board (IRB) at (650)-723-5244 or toll free at 1-866-680-2906. diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question8.md.license b/PAWSModules/Sources/PAWSContacts/Resources/Question8.md.license deleted file mode 100644 index 7a306f20..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question8.md.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question9.md b/PAWSModules/Sources/PAWSContacts/Resources/Question9.md deleted file mode 100644 index 7a179c99..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question9.md +++ /dev/null @@ -1 +0,0 @@ -If you do not want to participate in the study, just tell the researchers or your parents. You don’t need to be a part of the study and you won’t get in trouble if you decide if you don’t want to. Your doctor will still treat you the same even if you don’t participate. diff --git a/PAWSModules/Sources/PAWSContacts/Resources/Question9.md.license b/PAWSModules/Sources/PAWSContacts/Resources/Question9.md.license deleted file mode 100644 index 7a306f20..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/Question9.md.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSContacts/Resources/StudyDescription.md b/PAWSModules/Sources/PAWSContacts/Resources/StudyDescription.md deleted file mode 100644 index 763f98ee..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/StudyDescription.md +++ /dev/null @@ -1,9 +0,0 @@ -This study will explore how the Apple Watch® can work as a heart monitor for kids. As a part of the study, you will wear the Apple Watch for as long as 6 months, and any time you feel like your heart is beating fast or differently, you will record an ECG on the watch using the instructions below. Other than the fact that you are using an Apple Watch, there is no difference between being a part of the study and everything else about seeing your doctor. - -Any time that you feel symptoms that feel like an arrhythmia (i.e., palpitations, skipped beats, fast heart rate): - -1. First, press the event button on your Zio® Patch Monitor. -2. Second, initiate a recording on the Apple Watch by following these steps: - • Open the ECG App. - • Hold your finger to the Digital Crown for 30 seconds. - • Follow the on-screen prompts on the Apple Watch. diff --git a/PAWSModules/Sources/PAWSContacts/Resources/StudyDescription.md.license b/PAWSModules/Sources/PAWSContacts/Resources/StudyDescription.md.license deleted file mode 100644 index 7a306f20..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/StudyDescription.md.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSContacts/Resources/en.lproj/Localizable.strings b/PAWSModules/Sources/PAWSContacts/Resources/en.lproj/Localizable.strings deleted file mode 100644 index 0a2645a3..00000000 --- a/PAWSModules/Sources/PAWSContacts/Resources/en.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -// MARK: - Contacts -"CONTACTS_NAVIGATION_TITLE" = "Study Information"; -"SCOTT_CERESNAK_BIO" = "Dr. Ceresnak is a Professor of Pediatrics at Stanford University in the Division of Pediatric Cardiology. He is Director of the Pediatric Electrophysiology and Arrhythmia Program at Lucile Packard Children’s Hospital. His primary clinical and research interests involve the care of children and adults with congenital heart disease, with special attention to patients with arrhythmia disorders and inherited arrhythmia syndromes. He has special research interest and experience in advancing technological growth with the goal of creating improved diagnostic and therapeutic options for children with arrhythmia disorders."; -"AYDIN_ZAHEDIVASH_BIO" = "Dr. Zahedivash is a resident physician in the Stanford Department of Pediatrics and will serve as a chief resident during the 2023-2024 academic year. His academic interests focus on the development and translation of innovative technologies and services to address diagnostic and health equity challenges. During his educational career as an engineer and budding physician, he has co-invented a series of computational techniques and devices. Previous inventions include diagnostic deep-learning algorithms to detect unstable coronary artery plaque on intravascular imaging and a surgical probe for intraoperative margin detection during cancer resection. He is passionate about using his medical, technical, and business backgrounds to develop digital health solutions and equitable technology translation strategies for children."; diff --git a/PAWSModules/Sources/PAWSLandingScreen/Bundle+Image.swift b/PAWSModules/Sources/PAWSLandingScreen/Bundle+Image.swift deleted file mode 100644 index 2b20a775..00000000 --- a/PAWSModules/Sources/PAWSLandingScreen/Bundle+Image.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// This source file is part of the CS342 2023 Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SwiftUI - - -extension Bundle { - /// Loads an image from the `Bundle` and returns the content as a SwiftUI `Image`. - /// - Parameter name: The name of the image file. - /// - Returns: SwiftUI `Image` instance loaded from the `Bundle`. - public func image(fromFileNamed name: String, type: String = "jpeg") -> Image { - guard let imagePath = self.path(forResource: name, ofType: type), - let image = UIImage(contentsOfFile: imagePath) else { - return Image(systemName: "person.fill") - } - - return Image(uiImage: image) - } -} diff --git a/PAWSModules/Sources/PAWSLandingScreen/LandingScreen.swift b/PAWSModules/Sources/PAWSLandingScreen/LandingScreen.swift deleted file mode 100644 index 8d83667a..00000000 --- a/PAWSModules/Sources/PAWSLandingScreen/LandingScreen.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SwiftUI - - -public struct LandingScreen: View { - private let backgroundGradient = LinearGradient( - colors: [.red, .pink, .orange, .yellow], - startPoint: .leading, - endPoint: .trailing - ) - @Binding private var launchStatus: Bool - - - public var body: some View { - VStack { - backgroundGradient - .mask( - VStack { - Image(systemName: "pawprint.circle.fill") - .resizable() - .scaledToFill() - .frame(width: 200, height: 200) - .foregroundColor(.red) - Text("PAWS") - .font(.custom("GillSans-Bold", fixedSize: 30)) - Text("The Pediatric Apple Watch Study") - .font(.custom("GillSans", fixedSize: 15)) - .offset(y: 50) - } - ) - Button( - action: { - launchStatus = true - }, label: { - Text("Tap to get started") - .fontWeight(.bold) - .padding() - .foregroundColor(Color.red) - .border(backgroundGradient, width: 3) - .cornerRadius(5) - .offset(y: -30) - } - ) - } - } - - - public init(pressedStart: Binding) { - self._launchStatus = pressedStart - } -} - - -struct LandingScreen_Previews: PreviewProvider { - @State private static var pressedStart = false - - static var previews: some View { - LandingScreen(pressedStart: $pressedStart) - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/AboutStudy.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/AboutStudy.swift deleted file mode 100644 index 83aac34d..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/AboutStudy.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SwiftUI - - -struct AboutStudy: View { - private let backgroundGradient = LinearGradient( - colors: [.red, .pink, .orange], - startPoint: .leading, - endPoint: .trailing - ) - - - var body: some View { - VStack(alignment: .leading, spacing: 8) { - VStack { - Image(systemName: "pawprint.circle.fill") - .resizable() - // .aspectRatio(contentMode: .fit) - .scaledToFill() - .frame(width: 115, height: 75) - .foregroundColor(.red) - .padding([.top], 20) - // .offset(x: 8) - // .offset(x:80, y:-5) - } - .frame(maxWidth: .infinity, alignment: .center) - .padding([.top, .leading, .trailing, .bottom], 20) - VStack(alignment: .leading, spacing: 8) { - Text("Contribute to Health Research") - .font(.headline) - .fontWeight(.bold) - .frame(maxWidth: .infinity, alignment: .center) - - Text("Find out how you're helping the Stanford Pediatric Apple Watch Study (PAWS).") - .font(.subheadline) - .frame(maxWidth: .infinity, alignment: .leading) - .padding([.top, .bottom, .leading, .trailing], 10) - // .offset(y:-10) - } - .offset(y: 10) - .frame(maxWidth: .infinity, alignment: .center) - .padding([.top], 1) - .padding([.bottom], 20) - AboutStudyNav().padding([.leading, .trailing], 10) - } - .frame(maxWidth: .infinity) - .overlay( - RoundedRectangle(cornerRadius: 10).strokeBorder(.gray, lineWidth: 1) - ) - } -} - - -struct AboutStudy_Previews: PreviewProvider { - static var previews: some View { - AboutStudy() - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/AboutStudyNav.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/AboutStudyNav.swift deleted file mode 100644 index 1672b5ef..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/AboutStudyNav.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import PAWSSharedContext -import SwiftUI - - -struct AboutStudyNav: View { - private let backgroundGradient = LinearGradient( - colors: [.red, .pink, .orange], - startPoint: .leading, - endPoint: .trailing - ) - - @AppStorage(StorageKeys.homeTabSelection) var selectedTab = Tabs.home - - var body: some View { - Button( - action: { - selectedTab = Tabs.contact - }, label: { - Text("Learn More ") - .fontWeight(.bold) - .padding() - .foregroundColor(Color.white) - .frame(maxWidth: .infinity, alignment: .center) - .background(backgroundGradient) - .cornerRadius(10) - .padding(.bottom, 10) - } - ) - } -} - -struct AboutStudyNav_Previews: PreviewProvider { - static var previews: some View { - AboutStudyNav() - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/Bundle+ECGTracing.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/Bundle+ECGTracing.swift deleted file mode 100644 index 600a2d01..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/Bundle+ECGTracing.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Foundation -import ModelsR4 - -extension Foundation.Bundle { - func ecgTracing(withName name: String) -> Observation { - guard let resourceURL = self.url(forResource: name, withExtension: "json") else { - print(self.bundleURL) - fatalError("Could not find the ecgTracing \"\(name).json\" in Bundle.") - } - - do { - let resourceData = try Data(contentsOf: resourceURL) - return try JSONDecoder().decode(Observation.self, from: resourceData) - } catch { - fatalError("Could not decode the FHIR ECG data named \"\(name).json\": \(error)") - } - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/Home.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/Home.swift deleted file mode 100644 index f0595f73..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/Home.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import PAWSContacts -import PAWSNotificationScreen -import PAWSSharedContext -import SwiftUI - - -enum Tabs: String { - case home - case contact - case mockUpload -} - -public struct HomeView: View { - @AppStorage(StorageKeys.homeTabSelection) var selectedTab = Tabs.home - - public var body: some View { - TabView(selection: $selectedTab) { - HomeScreen() - .tag(Tabs.home) - .tabItem { - Label("Home", systemImage: "house.fill") - } - MockUploadList() - .tag(Tabs.mockUpload) - .tabItem { - Label("Reports", systemImage: "heart.text.square.fill") - } - Contacts() - .tag(Tabs.contact) - .tabItem { - Label("Study Information", systemImage: "doc.text.fill") - } - } - } - public init() {} -} - - -struct MainView_Previews: PreviewProvider { - static var previews: some View { - HomeView() - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/HomeScreen.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/HomeScreen.swift deleted file mode 100644 index 8d386903..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/HomeScreen.swift +++ /dev/null @@ -1,116 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Account -import class FHIR.FHIR -import FirebaseAccount -import Foundation -import SwiftUI -import Views - - -public struct HomeScreen: View { - private let backgroundGradient = LinearGradient( - colors: [.red, .pink, .orange, .yellow], - startPoint: .leading, - endPoint: .trailing - ) - - @EnvironmentObject var account: Account - @EnvironmentObject var firebaseAccountConfiguration: FirebaseAccountConfiguration - @EnvironmentObject var mockDataStorageProvider: MockDataStorageProvider - - - public var body: some View { - let name = firebaseAccountConfiguration.user?.displayName ?? "Name Needed" - - ScrollView(.vertical) { - VStack(alignment: .leading) { - HStack(alignment: .top) { - VStack(alignment: .leading) { - Text("Hello,") - .padding([.top], 10) - .font(.title) - .fontWeight(.bold) - Text(firstName(fullName: name).capitalized) - .font(.title2) - .fontWeight(.bold) - .padding([.top], 0) - .padding([.bottom], 10) - } - .padding() - Image(systemName: "pawprint.circle.fill") - .resizable() - .scaledToFill() - .frame(width: 60, height: 60) - .foregroundColor(.black) - .padding([.top], 20) - .padding([.leading], 190) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding([.top, .leading, .trailing], 10) - Divider().frame(maxWidth: .infinity) - recording - aboutstudy - }.frame(maxHeight: .infinity, alignment: .top) - } - } - @ViewBuilder var recording: some View { - if mockDataStorageProvider.mockUploads.isEmpty { - VStack(spacing: 32) { - Text("No Recordings Uploaded") - .multilineTextAlignment(.center) - .fontWeight(.bold) - } - .frame(maxWidth: .infinity) - .padding(32) - } else { - VStack(alignment: .leading) { - Text("Latest Recording") - .font(.title3) - .fontWeight(.bold) - .padding([.top], 10) - .padding(.leading, 10) - .foregroundColor(.gray) - let uploadList = mockDataStorageProvider.mockUploads - let latest = uploadList[0] - LatestRecording(mockUpload: latest) - } - .padding([.top, .leading, .trailing], 10) - .frame(maxWidth: .infinity, alignment: .topLeading) - } - } - @ViewBuilder var aboutstudy: some View { - VStack(alignment: .leading) { - Text("About The Study") - .font(.title3) - .fontWeight(.bold) - .padding([.top], 10) - .padding(.leading, 10) - .foregroundColor(.gray) - AboutStudy() - } - .padding([.top, .leading, .trailing], 10) - .frame(maxWidth: .infinity, alignment: .topLeading) - } - - public init() {} - - func firstName(fullName: String) -> String { - var names = fullName.components(separatedBy: " ") - let first = names.removeFirst() - return first + "!" - } -} - - -struct HomeScreen_Previews: PreviewProvider { - static var previews: some View { - HomeScreen() - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/LatestRecording.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/LatestRecording.swift deleted file mode 100644 index ecc162c7..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/LatestRecording.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -// swiftlint:disable all - -import FirebaseAuth -import FirebaseCore -import FirebaseFirestore -import FirestoreDataStorage -import Foundation -import SwiftUI - - -struct LatestRecording: View { - let mockUpload: MockUpload - @State var status: MockUpload.UploadStatus? - - private let backgroundGradient = LinearGradient( - colors: [.white], - startPoint: .leading, - endPoint: .trailing - ) - - var body: some View { - let date = "\(format(mockUpload.date))" - let time = date.range(of: "at")!.lowerBound - VStack(alignment: .leading, spacing: 8) { - HStack(alignment: .top, spacing: 8) { - Text("ECG Recording") - .font(.headline) - .frame(maxWidth: .infinity, alignment: .leading) - .padding([.top, .bottom, .leading, .trailing], 10) - .fontWeight(.bold) - Text(date[date.index(time, offsetBy: 2)...]) - .font(.subheadline) - .frame(maxWidth: .infinity, alignment: .trailing) - .padding([.top, .bottom, .leading, .trailing], 10) - - } - Text(date[.. String { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .long - dateFormatter.timeStyle = .long - return dateFormatter.string(from: date) - } - - private func checkStatus() { - let user = Auth.auth().currentUser - let uid = user?.uid ?? "" - let id = mockUpload.identifier - - let dbs = Firestore.firestore() - let uploadRef = dbs.collection("users").document(uid).collection("Observation").document(id) - - uploadRef.getDocument { document, _ in - if let document = document, document.exists { - self.status = .success - } else { - self.status = .failure - } - } - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/LazyText.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/LazyText.swift deleted file mode 100644 index 88b9ba58..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/LazyText.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import FHIR -import SwiftUI - - -struct LazyText: View { - private let text: String - @State private var lines: [(linenumber: Int, text: String)] = [] - - - var body: some View { - LazyVStack(alignment: .leading) { - ForEach(lines, id: \.linenumber) { line in - Text(line.text) - .multilineTextAlignment(.leading) - } - } - .onAppear { - var lineNumber = 0 - text.enumerateLines { line, _ in - lines.append((lineNumber, line)) - lineNumber += 1 - } - } - } - - - init(text: String) { - self.text = text - } -} - - -struct LazyText_Previews: PreviewProvider { - static let mappingJSONString = { - let observation = Bundle.module.ecgTracing(withName: "ECGObservation") - let jsonEncoder = JSONEncoder() - jsonEncoder.outputFormatting = [.prettyPrinted, .sortedKeys] - let jsonData = (try? jsonEncoder.encode(observation)) ?? Data() - return String(data: jsonData, encoding: .utf8) ?? "" - }() - static var previews: some View { - ScrollView { - LazyText(text: mappingJSONString) - } - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockDataStorageProvider.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/MockDataStorageProvider.swift deleted file mode 100644 index 091269f4..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockDataStorageProvider.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import CardinalKit -import FHIR -import FirebaseAuth -import FirebaseCore -import FirebaseFirestore -import FirestoreDataStorage -import Foundation -import HealthKitUI - -/// A data storage provider that collects all uploads and displays them in a user interface using the ``MockUploadList``. -public actor MockDataStorageProvider: DataStorageProvider, ObservableObjectProvider, ObservableObject { - public typealias ComponentStandard = FHIR - - - let encoder: JSONEncoder = { - let encoder = JSONEncoder() - encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes] - return encoder - }() - @MainActor @Published private (set) var mockUploads: [MockUpload] = [] - - - public init() { } - - - public func process(_ element: DataChange) async throws { - switch element { - case let .addition(element): - let data = try encoder.encode(element) - let json = String(decoding: data, as: UTF8.self) - print(json) - - // let tracing = try JSONDecoder().decode(HKElectrocardiogramMapping.self, from: data) - // print(tracing) - // Bundle.module.ecgTracing(withName: json) - // let symptoms = tracing.symptomsStatus.codings.description - let symptoms = getSymptoms(tracing: json) - - _Concurrency.Task { @MainActor in - mockUploads.insert( - MockUpload( - id: element.id.description, - type: .add, - path: ResourceProxy(with: element).resourceType.description, - body: json, - symptoms: symptoms - ), - at: 0 - ) - } - case let .removal(removalContext): - _Concurrency.Task { @MainActor in - mockUploads.insert( - MockUpload( - id: removalContext.id.description, - type: .delete, - path: removalContext.resourceType.rawValue - ), - at: 0 - ) - } - } - } - - private func getSymptoms(tracing: String) -> String { - var symptoms = "" - - if tracing.contains("Fatigue") { - symptoms += "Fatigue; " - } - if tracing.contains("Dizziness") { - symptoms += "Dizziness; " - } - if tracing.contains("Rapid") { - symptoms += "Rapid, pounding or fluttering heartbeat; " - } - if tracing.contains("Skipped") { - symptoms += "Skipped heartbeat; " - } - if tracing.contains("Shortness") { - symptoms += "Shortness of breath; " - } - if tracing.contains("Chest tightness or pain") { - symptoms += "Chest tightness or pain; " - } - if tracing.contains("Fainting") { - symptoms += "Fainting; " - } - if tracing.contains("Other") { - symptoms += "Other; " - } - - return symptoms - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUpload.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUpload.swift deleted file mode 100644 index 41af6aee..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUpload.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import FirebaseAuth -import FirebaseCore -import FirebaseFirestore -import FirestoreDataStorage -import Foundation - - -struct MockUpload: Identifiable, Hashable { - enum UploadType { - case add - case delete - } - - enum UploadStatus { - case success - case failure - } - - let identifier: String - let date = Date() - let type: UploadType - let path: String - let body: String? - var symptoms: String? - - var id: String { - "\(type): \(path)/\(identifier) at \(date.debugDescription)" - } - - - init(id: String, type: UploadType, path: String, body: String? = nil, symptoms: String? = "") { - self.identifier = id - self.type = type - self.path = path - self.body = body - self.symptoms = symptoms - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadDetailView.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadDetailView.swift deleted file mode 100644 index 58a17191..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadDetailView.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SwiftUI - - -struct MockUploadDetailView: View { - let mockUpload: MockUpload - - - var body: some View { - List { - Section(String(localized: "MOCK_UPLOAD_DETAIL_HEADER", bundle: .module)) { - MockUploadHeader(mockUpload: mockUpload) - } - Section(String(localized: "MOCK_UPLOAD_DETAIL_BODY", bundle: .module)) { - LazyText(text: mockUpload.body ?? "") - } - } - .listStyle(.insetGrouped) - .navigationBarTitleDisplayMode(.inline) - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadHeader.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadHeader.swift deleted file mode 100644 index d901f123..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadHeader.swift +++ /dev/null @@ -1,140 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -// swiftlint:disable all - -import FirebaseAuth -import FirebaseCore -import FirebaseFirestore -import FirestoreDataStorage -import SwiftUI - -struct MockUploadHeader: View { - let mockUpload: MockUpload - @State var status: MockUpload.UploadStatus? - - private let backgroundGradient = LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ) - - var body: some View { - let date = "\(format(mockUpload.date))" - let time = date.range(of: "at")!.lowerBound - //let symptomNum = seperateSymptoms(allSymptoms: mockUpload.symptoms ?? "").count - - ZStack{ - RoundedRectangle(cornerRadius: 10) - .foregroundColor(Color(.systemBackground)) - .frame(width: 360) - .shadow(radius: 5) - .opacity(0.9) - .border(backgroundGradient, width: 2) - .cornerRadius(10) - .shadow(radius: 10) - VStack(alignment: .leading, spacing: 4) { - Text(date[.. String { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .long - dateFormatter.timeStyle = .long - return dateFormatter.string(from: date) - } - - private func checkStatus() { - let user = Auth.auth().currentUser - let uid = user?.uid ?? "" - let id = mockUpload.identifier - - let dbs = Firestore.firestore() - let uploadRef = dbs.collection("users").document(uid).collection("Observation").document(id) - - uploadRef.getDocument { document, _ in - if let document = document, document.exists { - self.status = .success - } else { - self.status = .failure - } - } - } - - private func seperateSymptoms(allSymptoms: String) -> [String] { - var symptoms = allSymptoms.components(separatedBy: "; ") - symptoms.removeLast() - return symptoms - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadList-filter.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadList-filter.swift deleted file mode 100644 index c54384a1..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadList-filter.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SwiftUI - - -/// Displays the recoded uploads collected by the ``MockDataStorageProvider``. -public struct MockUploadListFilter: View { - @EnvironmentObject var mockDataStorageProvider: MockDataStorageProvider - let button1: some View = - Text("Past week") - .fontWeight(.bold) - .font(.system(size: 8)) - .padding() - .background(Color.purple) - .cornerRadius(10) - .foregroundColor(.white) - .padding(5) - - public var body: some View { - VStack(alignment: .leading) { - Text(String(localized: "MOCK_UPLOAD_LIST_TITLE", bundle: .module)) - .font(.title) - .fontWeight(.bold) - .padding([.top, .bottom], 20) - HStack(spacing: 3) { - button1 - .frame(maxWidth: .infinity) - button1 - .frame(maxWidth: .infinity) - button1 - .frame(maxWidth: .infinity) - } - - Group { - if mockDataStorageProvider.mockUploads.isEmpty { - VStack(spacing: 32) { - Image(systemName: "pawprint.circle") - .font(.system(size: 100)) - .opacity(0.2) - Text(String(localized: "MOCK_UPLOAD_LIST_PLACEHOLDER", bundle: .module)) - .multilineTextAlignment(.center) - } - .padding(32) - } else { - List(mockDataStorageProvider.mockUploads) { mockUpload in - MockUploadHeader(mockUpload: mockUpload) - } - } - } - } - } - - - public init() {} - - - private func format(_ date: Date) -> String { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .long - dateFormatter.timeStyle = .long - return dateFormatter.string(from: date) - } -} - - -struct MockUploadsListFilter_Previews: PreviewProvider { - static var previews: some View { - MockUploadListFilter() - .environmentObject(MockDataStorageProvider()) - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadList.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadList.swift deleted file mode 100644 index 0aeb4796..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/MockUploadList.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SwiftUI - - -/// Displays the recoded uploads collected by the ``MockDataStorageProvider``. -public struct MockUploadList: View { - @EnvironmentObject var mockDataStorageProvider: MockDataStorageProvider - - public var body: some View { - ZStack { - Color.white - .ignoresSafeArea() - // VStack(alignment: .leading) { - // Text("Notifications") - // .font(.largeTitle) - // .fontWeight(.bold) - // .padding([.top, .leading, .bottom], 20) - // .offset(y: -150) - NavigationStack { - Group { - if mockDataStorageProvider.mockUploads.isEmpty { - VStack(spacing: 32) { - Image(systemName: "pawprint.circle") - .font(.system(size: 100)) - .opacity(0.2) - Text(String(localized: "MOCK_UPLOAD_LIST_PLACEHOLDER", bundle: .module)) - .multilineTextAlignment(.center) - .fontWeight(.bold) - } - .padding(32) - } else { - List(mockDataStorageProvider.mockUploads) { mockUpload in - MockUploadHeader(mockUpload: mockUpload) - } - } - } - .navigationTitle(String(localized: "MOCK_UPLOAD_LIST_TITLE", bundle: .module)) - } - } - } - - public init() {} - - - private func format(_ date: Date) -> String { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .long - dateFormatter.timeStyle = .long - return dateFormatter.string(from: date) - } -} - - -struct MockUploadsList_Previews: PreviewProvider { - static var previews: some View { - MockUploadList() - .environmentObject(MockDataStorageProvider()) - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/RecordingsNav.swift b/PAWSModules/Sources/PAWSMockDataStorageProvider/RecordingsNav.swift deleted file mode 100644 index f93273c8..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/RecordingsNav.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import PAWSSharedContext -import SwiftUI - - -struct RecordingsNav: View { - private let backgroundGradient = LinearGradient( - colors: [.red, .pink, .orange], - startPoint: .leading, - endPoint: .trailing - ) - - @AppStorage(StorageKeys.homeTabSelection) var selectedTab = Tabs.home - - var body: some View { - Button( - action: { - selectedTab = Tabs.mockUpload - }, label: { - Text("View More Recordings ") - .fontWeight(.bold) - .padding() - .foregroundColor(Color.white) - .frame(maxWidth: .infinity, alignment: .center) - .background(backgroundGradient) - .cornerRadius(10) - .padding(.bottom, 10) - } - ) - } -} - -struct RecordingsNav_Previews: PreviewProvider { - static var previews: some View { - RecordingsNav() - } -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/ECGObservation.json b/PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/ECGObservation.json deleted file mode 100644 index f18fb46e..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/ECGObservation.json +++ /dev/null @@ -1,165 +0,0 @@ -{ - "category" : [ - { - "coding" : [ - { - "code" : "procedure", - "display" : "Procedure", - "system" : "http://terminology.hl7.org/CodeSystem/observation-category" - } - ] - } - ], - "code" : { - "coding" : [ - { - "code" : "HKElectrocardiogram", - "display" : "Electrocardiogram", - "system" : "http://developer.apple.com/documentation/healthkit" - }, - { - "code" : "131329", - "display" : "MDC_ECG_ELEC_POTL_I", - "system" : "urn:oid:2.16.840.1.113883.6.24" - } - ] - }, - "component" : [ - { - "code" : { - "coding" : [ - { - "code" : "HKElectrocardiogram.NumberOfVoltageMeasurements", - "display" : "Electrocardiogram Number of Voltage Measurements", - "system" : "http://developer.apple.com/documentation/healthkit" - } - ] - }, - "valueQuantity" : { - "unit" : "measurements", - "value" : 22112 - } - }, - { - "code" : { - "coding" : [ - { - "code" : "HKElectrocardiogram.SamplingFrequency", - "display" : "Sampling Frequency", - "system" : "http://developer.apple.com/documentation/healthkit" - } - ] - }, - "valueQuantity" : { - "code" : "hertz", - "system" : "http://unitsofmeasure.org", - "unit" : "hertz", - "value" : 512 - } - }, - { - "code" : { - "coding" : [ - { - "code" : "HKElectrocardiogram.Classification", - "display" : "Electrocardiogram Classification", - "system" : "http://developer.apple.com/documentation/healthkit" - } - ] - }, - "valueString" : "sinusRhythm" - }, - { - "code" : { - "coding" : [ - { - "code" : "8867-4", - "display" : "Heart rate", - "system" : "http://loinc.org" - }, - { - "code" : "HKQuantityTypeIdentifierHeartRate", - "display" : "Heart Rate", - "system" : "http://developer.apple.com/documentation/healthkit" - } - ] - }, - "valueQuantity" : { - "code" : "/min", - "system" : "http://unitsofmeasure.org", - "unit" : "beats/minute", - "value" : 140 - } - }, - { - "code" : { - "coding" : [ - { - "code" : "HKElectrocardiogram.SymptomsStatus", - "display" : "Electrocardiogram Symptoms Status", - "system" : "http://developer.apple.com/documentation/healthkit" - } - ] - }, - "valueString" : "present" - }, - { - "code" : { - "coding" : [ - { - "code" : "HKCategoryTypeIdentifierRapidPoundingOrFlutteringHeartbeat", - "display" : "Rapid Pounding Or Fluttering Heartbeat", - "system" : "http://developer.apple.com/documentation/healthkit" - } - ] - }, - "valueString" : "unspecified" - }, - { - "code" : { - "coding" : [ - { - "code" : "HKCategoryTypeIdentifierFatigue", - "display" : "Fatigue", - "system" : "http://developer.apple.com/documentation/healthkit" - } - ] - }, - "valueString" : "unspecified" - }, - { - "code" : { - "coding" : [ - { - "code" : "131329", - "display" : "MDC_ECG_ELEC_POTL_I", - "system" : "urn:oid:2.16.840.1.113883.6.24" - } - ] - }, - "valueSampledData" : { - "data" : "-122.59088134765625 -262.6523132324219 -398.10791015625 -517.7991333007812 ....", - "dimensions" : 1, - "origin" : { - "code" : "uV", - "system" : "http://unitsofmeasure.org", - "unit" : "uV", - "value" : 0 - }, - "period" : 0.00195303667126447232 - } - } - ], - "effectivePeriod" : { - "end" : "2022-12-20T01:41:00-08:00", - "start" : "2022-12-20T01:41:43.1875-08:00" - }, - "identifier" : [ - { - "id" : "E3E98DA2-7124-4567-93CB-0B52647EA332" - } - ], - "issued" : "2022-12-20T19:14:52.072656035-08:00", - "resourceType" : "Observation", - "status" : "final" -} diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/ECGObservation.json.license b/PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/ECGObservation.json.license deleted file mode 100644 index 9137ea9d..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/ECGObservation.json.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/en.lproj/Localizable.strings b/PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/en.lproj/Localizable.strings deleted file mode 100644 index 07ef31cd..00000000 --- a/PAWSModules/Sources/PAWSMockDataStorageProvider/Resources/en.lproj/Localizable.strings +++ /dev/null @@ -1,13 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -// MARK: - Mock Upload Data Storage Provider -"MOCK_UPLOAD_LIST_PLACEHOLDER" = "No recordings have been uploaded yet."; -"MOCK_UPLOAD_LIST_TITLE" = "Reports"; -"MOCK_UPLOAD_DETAIL_HEADER" = "Upload Header"; -"MOCK_UPLOAD_DETAIL_BODY" = "Upload Details"; diff --git a/PAWSModules/Sources/PAWSNotificationScreen/Notification.swift b/PAWSModules/Sources/PAWSNotificationScreen/Notification.swift deleted file mode 100644 index f12baefb..00000000 --- a/PAWSModules/Sources/PAWSNotificationScreen/Notification.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SwiftUI - - -struct Notification: View { - private let backgroundGradient = LinearGradient( - colors: [.red, .pink, .yellow], - startPoint: .leading, - endPoint: .trailing - ) - - - var body: some View { - VStack(alignment: .leading) { - Text("February 8th, 2023") - .font(.title3) - .fontWeight(.bold) - .foregroundColor(.gray) - VStack(alignment: .leading) { - VStack(alignment: .leading, spacing: 8) { - Text("ECG Recording") - .font(.headline) - .padding([.leading, .trailing, .top], 20) - .fontWeight(.bold) - - Text("12:30 PM") - .font(.subheadline) - .padding([.leading, .trailing], 20) - } - - Divider() - HStack { - Text("Recording successfully uploaded") - .font(.callout) - .padding() - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.green) - } - } - .frame(width: 320) - .opacity(0.9) - .border(backgroundGradient, width: 5) - .cornerRadius(10) - .shadow(radius: 10) - } - } -} - - -struct Notification_Previews: PreviewProvider { - static var previews: some View { - Notification() - } -} diff --git a/PAWSModules/Sources/PAWSNotificationScreen/NotificationScreen.swift b/PAWSModules/Sources/PAWSNotificationScreen/NotificationScreen.swift deleted file mode 100644 index 310a8802..00000000 --- a/PAWSModules/Sources/PAWSNotificationScreen/NotificationScreen.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SwiftUI - - -public struct NotificationScreen: View { - public var body: some View { - ScrollView(.vertical) { - VStack(alignment: .leading) { - Text("Notifications") - .font(.title) - .fontWeight(.bold) - .padding([.top, .bottom], 20) - Notification() - Notification() - Notification() - Notification() - } - } - } - - - public init() {} -} - - -struct NotificationScreen_Previews: PreviewProvider { - static var previews: some View { - NotificationScreen() - } -} diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/AccountSetup.swift b/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/AccountSetup.swift deleted file mode 100644 index b07c76dd..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/AccountSetup.swift +++ /dev/null @@ -1,153 +0,0 @@ -// -// This source file is part of the Stanford CardinalKit PAWS Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Account -import class FHIR.FHIR -import FirebaseAccount -import FirebaseAuth -import FirebaseFirestore -import Onboarding -import SwiftUI - - -struct AccountSetup: View { - @Binding private var onboardingSteps: [OnboardingFlow.Step] - @EnvironmentObject var account: Account - private let backgroundGradient = LinearGradient( - colors: [.red, .pink, .orange, .yellow], - startPoint: .leading, - endPoint: .trailing - ) - - var body: some View { - OnboardingView( - contentView: { - VStack { - OnboardingTitleView( - title: "ACCOUNT_TITLE".moduleLocalized, - subtitle: "ACCOUNT_SUBTITLE".moduleLocalized - ) - Spacer(minLength: 0) - accountImage - accountDescription - Spacer(minLength: 0) - } - }, actionView: { - actionView - } - ) - .onReceive(account.objectWillChange) { - if account.signedIn { - onboardingSteps.append(.healthKitPermissions) - - if let user = Auth.auth().currentUser { - let uid = user.uid - let name = user.displayName?.components(separatedBy: " ") - let firstName = name?[0] ?? "" - let lastName = name?[1] ?? "" - let data: [String: Any] = ["firstName": firstName, "id": uid, "lastName": lastName] - Firestore.firestore().collection("users").document(uid).setData(data) { err in - if let err = err { - print("Error updating document: \(err)") - } - } - } - // Unfortunately, SwiftUI currently animates changes in the navigation path that do not change - // the current top view. Therefore we need to do the following async procedure to remove the - // `.login` and `.signUp` steps while disabling the animations before and re-enabling them - // after the elements have been changed. - Task { @MainActor in - try? await Task.sleep(for: .seconds(1.0)) - UIView.setAnimationsEnabled(false) - onboardingSteps.removeAll(where: { $0 == .login || $0 == .signUp }) - try? await Task.sleep(for: .seconds(1.0)) - UIView.setAnimationsEnabled(true) - } - } - } - } - - @ViewBuilder private var accountImage: some View { - Group { - if account.signedIn { - backgroundGradient - .mask( - Image(systemName: "pawprint.circle.fill") - ) - } else { - backgroundGradient - .mask( - VStack { - Image(systemName: "pawprint.circle") - } - ) - } - } - .font(.system(size: 150)) - .foregroundColor(.accentColor) - } - - @ViewBuilder private var accountDescription: some View { - VStack { - Group { - if account.signedIn { - Text("ACCOUNT_SIGNED_IN_DESCRIPTION", bundle: .module) - } else { - Text("ACCOUNT_SETUP_DESCRIPTION", bundle: .module) - } - } - .multilineTextAlignment(.center) - .padding(.vertical, 16) - if account.signedIn { - UserView() - .padding() - } - } - } - - @ViewBuilder private var actionView: some View { - if account.signedIn { - OnboardingActionsView( - "ACCOUNT_NEXT".moduleLocalized, - action: { - onboardingSteps.append(.healthKitPermissions) - } - ) - } else { - OnboardingActionsView( - primaryText: "ACCOUNT_SIGN_UP".moduleLocalized, - primaryAction: { - onboardingSteps.append(.signUp) - }, - secondaryText: "ACCOUNT_LOGIN".moduleLocalized, - secondaryAction: { - onboardingSteps.append(.login) - } - ) - } - } - - - init(onboardingSteps: Binding<[OnboardingFlow.Step]>) { - self._onboardingSteps = onboardingSteps - } -} - - -#if DEBUG -struct AccountSetup_Previews: PreviewProvider { - @State private static var path: [OnboardingFlow.Step] = [] - - - static var previews: some View { - AccountSetup(onboardingSteps: $path) - .environmentObject(Account(accountServices: [])) - .environmentObject(FirebaseAccountConfiguration(emulatorSettings: (host: "localhost", port: 9099))) - } -} -#endif diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/IconView.swift b/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/IconView.swift deleted file mode 100644 index 2216d8d0..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/IconView.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// This source file is part of the Stanford CardinalKit PAWS Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import PAWSSharedContext -import SwiftUI - - -struct IconView: View { - let size: Double - - - var body: some View { - Image(uiImage: Bundle.module.image(withName: "AppIcon", fileExtension: "png")) - .resizable() - .scaledToFill() - .accessibilityLabel(Text("App Icon")) - .frame(width: size, height: size) - .clipShape(RoundedRectangle(cornerRadius: size / 5)) - .shadow(color: Color(.systemGray4), radius: 4, x: 0, y: 4) - .padding(.bottom) - } - - - init(size: Double = 150) { - self.size = size - } -} - - -#if DEBUG -struct IconView_Previews: PreviewProvider { - static var previews: some View { - ZStack { - Color(.secondarySystemBackground) - IconView(size: 100) - } - } -} -#endif diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/PAWSLogin.swift b/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/PAWSLogin.swift deleted file mode 100644 index 39a711e8..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/PAWSLogin.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// This source file is part of the Stanford CardinalKit PAWS Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Account -import Onboarding -import SwiftUI - - -struct PAWSLogin: View { - var body: some View { - Login { - IconView() - .padding(.top, 32) - Text("LOGIN_SUBTITLE", bundle: .module) - .multilineTextAlignment(.center) - .padding() - .padding() - Spacer(minLength: 0) - } - .navigationBarTitleDisplayMode(.large) - } -} - - -#if DEBUG -struct PAWSLogin_Previews: PreviewProvider { - static var previews: some View { - PAWSLogin() - .environmentObject(Account(accountServices: [])) - } -} -#endif diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/PAWSSignUp.swift b/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/PAWSSignUp.swift deleted file mode 100644 index 914a7c93..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/PAWSSignUp.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// This source file is part of the Stanford CardinalKit PAWS Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Account -import Onboarding -import SwiftUI - - -struct PAWSSignUp: View { - var body: some View { - SignUp { - IconView() - .padding(.top, 32) - Text("SIGN_UP_SUBTITLE", bundle: .module) - .multilineTextAlignment(.center) - .padding() - Spacer(minLength: 0) - } - .navigationBarTitleDisplayMode(.large) - } -} - - -#if DEBUG -struct PAWSSignUp_Previews: PreviewProvider { - static var previews: some View { - PAWSSignUp() - .environmentObject(Account(accountServices: [])) - } -} -#endif diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/UserView.swift b/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/UserView.swift deleted file mode 100644 index 89abc2e7..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/AccountSetup/UserView.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// This source file is part of the Stanford CardinalKit PAWS Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Account -import class FHIR.FHIR -import FirebaseAccount -import Foundation -import SwiftUI -import Views - - -struct UserView: View { - @EnvironmentObject var account: Account - @EnvironmentObject var firebaseAccountConfiguration: FirebaseAccountConfiguration - - var body: some View { - userInformation - .padding() - .background( - RoundedRectangle(cornerRadius: 8) - .fill(Color(.systemBackground)) - .shadow(color: .gray, radius: 2) - ) - } - - - @ViewBuilder private var userInformation: some View { - HStack(spacing: 16) { - if account.signedIn, - let user = firebaseAccountConfiguration.user, - let displayName = user.displayName, - let name = try? PersonNameComponents(displayName) { - UserProfileView(name: name) - .frame(height: 30) - VStack(alignment: .leading, spacing: 4) { - Text(name.formatted(.name(style: .medium))) - if let email = user.email { - Text(email) - } - } - Spacer() - } else { - Spacer() - VStack(spacing: 16) { - ProgressView() - Text("USER_VIEW_LOADING", bundle: .module) - .multilineTextAlignment(.center) - } - Spacer() - } - } - } -} - - -#if DEBUG -struct SwiftUIView_Previews: PreviewProvider { - static var previews: some View { - UserView() - .padding() - .environmentObject(FirebaseAccountConfiguration(emulatorSettings: (host: "localhost", port: 9099))) - } -} -#endif diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/Consent.swift b/PAWSModules/Sources/PAWSOnboardingFlow/Consent.swift deleted file mode 100644 index 38aa36ac..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/Consent.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Onboarding -import PAWSSharedContext -import SwiftUI - - -struct Consent: View { - @Binding private var onboardingSteps: [OnboardingFlow.Step] - - - private var consentDocument: Data { - guard let path = Bundle.module.url(forResource: "ConsentDocument", withExtension: "md"), - let data = try? Data(contentsOf: path) else { - return Data("CONSENT_LOADING_ERROR".moduleLocalized.utf8) - } - return data - } - - var body: some View { - VStack { - Image(systemName: "pawprint.circle.fill") - .resizable() - .scaledToFill() - .frame(width: 80, height: 80) - .foregroundColor(.red) - .offset(y: 10) - ConsentView( - header: { - OnboardingTitleView( - title: "CONSENT_TITLE".moduleLocalized, - subtitle: "CONSENT_SUBTITLE".moduleLocalized - ) - }, - - asyncMarkdown: { - consentDocument - }, - action: { - if !FeatureFlags.disableFirebase { - onboardingSteps.append(.accountSetup) - } else { - onboardingSteps.append(.healthKitPermissions) - } - } - ) - .offset(y: -20) - } - } - - - init(onboardingSteps: Binding<[OnboardingFlow.Step]>) { - self._onboardingSteps = onboardingSteps - } -} - - -struct Consent_Previews: PreviewProvider { - @State private static var path: [OnboardingFlow.Step] = [] - - - static var previews: some View { - Consent(onboardingSteps: $path) - } -} diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/HealthKitPermissions.swift b/PAWSModules/Sources/PAWSOnboardingFlow/HealthKitPermissions.swift deleted file mode 100644 index 192e170c..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/HealthKitPermissions.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import FHIR -import HealthKitDataSource -import Onboarding -import PAWSSharedContext -import SwiftUI - - -struct HealthKitPermissions: View { - @EnvironmentObject var healthKitDataSource: HealthKit - @Binding var onboardingSteps: [OnboardingFlow.Step] - - - var body: some View { - OnboardingView( - contentView: { - VStack { - Image(systemName: "pawprint.circle.fill") - .resizable() - .scaledToFill() - .frame(width: 40, height: 40) - .foregroundColor(.red) - .offset(y: 20) - OnboardingTitleView( - title: "HEALTHKIT_PERMISSIONS_TITLE".moduleLocalized, - subtitle: "HEALTHKIT_PERMISSIONS_SUBTITLE".moduleLocalized - ) - Spacer() - Image(systemName: "heart.text.square.fill") - .font(.system(size: 150)) - .foregroundColor(.accentColor) - Text("HEALTHKIT_PERMISSIONS_DESCRIPTION", bundle: .module) - .multilineTextAlignment(.center) - .padding(.vertical, 16) - Spacer() - } - }, actionView: { - OnboardingActionsView( - "HEALTHKIT_PERMISSIONS_BUTTON".moduleLocalized, - action: { - do { - try await healthKitDataSource.askForAuthorization() - } catch { - print("Could not request HealthKit permissions.") - } - onboardingSteps.append(.notificationSetup) - } - ) - } - ) - } -} - - -struct HealthKitPermissions_Previews: PreviewProvider { - @State private static var path: [OnboardingFlow.Step] = [] - - - static var previews: some View { - HealthKitPermissions(onboardingSteps: $path) - } -} diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/InterestingModules.swift b/PAWSModules/Sources/PAWSOnboardingFlow/InterestingModules.swift deleted file mode 100644 index 00165057..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/InterestingModules.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Onboarding -import SwiftUI - - -struct InterestingModules: View { - @Binding private var onboardingSteps: [OnboardingFlow.Step] - - - var body: some View { - VStack { - Image(systemName: "pawprint.circle.fill") - .resizable() - .scaledToFill() - .frame(width: 40, height: 40) - .foregroundColor(.red) - .offset(y: 20) - SequentialOnboardingView( - title: "INTERESTING_MODULES_TITLE".moduleLocalized, - subtitle: "INTERESTING_MODULES_SUBTITLE".moduleLocalized, - content: [ - .init( - title: "INTERESTING_MODULES_AREA1_TITLE".moduleLocalized, - description: "INTERESTING_MODULES_AREA1_DESCRIPTION".moduleLocalized - ), - .init( - title: "INTERESTING_MODULES_AREA2_TITLE".moduleLocalized, - description: "INTERESTING_MODULES_AREA2_DESCRIPTION".moduleLocalized - ), - .init( - title: "INTERESTING_MODULES_AREA3_TITLE".moduleLocalized, - description: "INTERESTING_MODULES_AREA3_DESCRIPTION".moduleLocalized - ) - ], - actionText: "INTERESTING_MODULES_BUTTON".moduleLocalized, - action: { - #if targetEnvironment(simulator) && (arch(i386) || arch(x86_64)) - print("PKCanvas view-related views are currently skipped on Intel-based iOS simulators due to a metal bug on the simulator.") - onboardingSteps.append(.accountSetup) - #else - onboardingSteps.append(.consent) - #endif - } - ) - } - } - - - init(onboardingSteps: Binding<[OnboardingFlow.Step]>) { - self._onboardingSteps = onboardingSteps - } -} - - -struct ThingsToKnow_Previews: PreviewProvider { - @State private static var path: [OnboardingFlow.Step] = [] - - - static var previews: some View { - InterestingModules(onboardingSteps: $path) - } -} diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/NotificationSetup.swift b/PAWSModules/Sources/PAWSOnboardingFlow/NotificationSetup.swift deleted file mode 100644 index 9d159402..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/NotificationSetup.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -// swiftlint:disable all - -import Onboarding -import PAWSSharedContext -import SwiftUI - -struct NotificationSetup: View { - @Binding var onboardingSteps: [OnboardingFlow.Step] - @AppStorage(StorageKeys.onboardingFlowComplete) var completedOnboardingFlow = false - @State private var date = Date() - @State private var selectedTime = Calendar.current.date(bySettingHour: 19, minute: 0, second: 0, of: Date())! - - var body: some View { - OnboardingView( - contentView: { - VStack { - Image(systemName: "pawprint.circle.fill") - .resizable() - .scaledToFill() - .frame(width: 40, height: 40) - .foregroundColor(.red) - .offset(y: 20) - OnboardingTitleView( - title: "NOTIFICATION_SETUP_TITLE".moduleLocalized, - subtitle: "NOTIFICATION_SETUP_SUBTITLE".moduleLocalized - ) - DatePicker("Select a time", selection: $selectedTime, displayedComponents: .hourAndMinute) - Image("NotificationImage") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 275, height: 275) - .padding(.vertical) - - } - }, actionView: { - OnboardingActionsView( - "ALLOW_NOTIFICATIONS_BUTTON".moduleLocalized, - action: { - notificationTrigger() - completedOnboardingFlow = true - } - ) - } - ) - } - - func notificationTrigger() { - let center = UNUserNotificationCenter.current() - center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in - if let error { - print("Error scheduling notification: \(error)") - } else { - let content = UNMutableNotificationContent() - content.title = "Friendly reminder to record your ECG!" - content.body = "Thank you for participating in the PAWS study!" - - // Configure the recurring date. - - let dateComponents = Calendar.current.dateComponents([.hour, .minute], from: selectedTime) - - for index in 0...6 { - let scheduleDate = Date.now.addingTimeInterval(86400 * Double(index)) - guard let notificationDate = Calendar.current.nextDate(after: scheduleDate, matching: dateComponents, matchingPolicy: .nextTime) else { - continue - } - - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: notificationDate.timeIntervalSince(.now), repeats: false) - - - // Create the request - let uuidString = UUID().uuidString - let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger) - - // Schedule the request with the system. - let notificationCenter = UNUserNotificationCenter.current() - notificationCenter.add(request) { (error) in - if error != nil { - // Handle any errors. - } - } - } - } - } - } - -} diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/OnboardingFlow.swift b/PAWSModules/Sources/PAWSOnboardingFlow/OnboardingFlow.swift deleted file mode 100644 index c6aa9085..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/OnboardingFlow.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import PAWSSharedContext -import SwiftUI - - -/// Displays an multi-step onboarding flow for the CS342 2023 PAWS Team Application. -public struct OnboardingFlow: View { - enum Step: String, Codable { - case interestingModules - case consent - case accountSetup - case login - case signUp - case healthKitPermissions - case notificationSetup - } - - - @SceneStorage(StorageKeys.onboardingFlowStep) private var onboardingSteps: [Step] = [] - - - public var body: some View { - VStack { - NavigationStack(path: $onboardingSteps) { - Image(systemName: "pawprint.circle.fill") - .resizable() - .scaledToFill() - .frame(width: 40, height: 40) - .foregroundColor(.red) - .offset(y: 20) - Welcome(onboardingSteps: $onboardingSteps) - .navigationDestination(for: Step.self) { onboardingStep in - switch onboardingStep { - case .interestingModules: - InterestingModules(onboardingSteps: $onboardingSteps) - case .accountSetup: - AccountSetup(onboardingSteps: $onboardingSteps) - case .login: - PAWSLogin() - case .signUp: - PAWSSignUp() - case .consent: - Consent(onboardingSteps: $onboardingSteps) - case .healthKitPermissions: - HealthKitPermissions( onboardingSteps: $onboardingSteps) - case .notificationSetup: - NotificationSetup( onboardingSteps: $onboardingSteps) - } - } - .navigationBarTitleDisplayMode(.inline) - } - } - } - - - public init() {} -} - - -struct OnboardingFlow_Previews: PreviewProvider { - static var previews: some View { - OnboardingFlow() - } -} diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/AppIcon.png b/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/AppIcon.png deleted file mode 100644 index b2123dd17a5dc49226638a2fd19c1e68e338e468..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 152328 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 z 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 diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/AppIcon.png.license b/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/AppIcon.png.license deleted file mode 100644 index 9137ea9d..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/AppIcon.png.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/ConsentDocument.md.license b/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/ConsentDocument.md.license deleted file mode 100644 index 9137ea9d..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/ConsentDocument.md.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/Localizable.strings b/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/Localizable.strings deleted file mode 100644 index b54998d7..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/Resources/en.lproj/Localizable.strings +++ /dev/null @@ -1,83 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -// MARK: - Onboarding - -// MARK: Welcome -"WELCOME_TITLE" = "Welcome to PAWS!"; -"WELCOME_SUBTITLE" = "We are excited to have you join the Pediatric Apple Watch Study (PAWS) to improve diagnosis of arrhythmias in patients like you! - -On this page, we'll walk through the app's main features. Next, you'll complete important onboarding information to confirm your participation in the study."; - -"WELCOME_AREA1_TITLE" = "Home Page"; -"WELCOME_AREA1_DESCRIPTION" = "On this page you'll be able to see your patient information and a summary of the ECG recordings uploaded from your Apple Watch "; - -"WELCOME_AREA2_TITLE" = "Reports"; -"WELCOME_AREA2_DESCRIPTION" = "On this page you'll be able to check if an ECG recording from your Apple Watch was successfully uploaded to the research server. "; - -"WELCOME_AREA3_TITLE" = "Study Information"; -"WELCOME_AREA3_DESCRIPTION" = "Learn more about the PAWS study and contact the lead researchers if you have any questions or concerns"; - -"WELCOME_BUTTON" = "Begin onboarding"; - -// MARK: Interesting Modules -"INTERESTING_MODULES_TITLE" = "PAWS Onboarding"; -"INTERESTING_MODULES_SUBTITLE" = "You'll need to complete a few more steps -to get started..."; - -"INTERESTING_MODULES_AREA1_TITLE" = "Create an account"; -"INTERESTING_MODULES_AREA1_DESCRIPTION" = "Create an account so researchers can identify you as a patient"; - -"INTERESTING_MODULES_AREA2_TITLE" = "Consent"; -"INTERESTING_MODULES_AREA2_DESCRIPTION" = "Give researchers consent to use your health data as part of the PAWS study"; - -"INTERESTING_MODULES_AREA3_TITLE" = "Grant access to health data "; -"INTERESTING_MODULES_AREA3_DESCRIPTION" = "Allow the PAWS app to access the health data recorded on your Apple Watch and iPhone"; - - - -"INTERESTING_MODULES_BUTTON" = "Next"; - - -// MARK: Consent -"CONSENT_TITLE" = "Consent Page"; -"CONSENT_SUBTITLE" = ""; - -"CONSENT_LOADING_ERROR" = "Please include a markdown based document named \"ConsentDocument\" in your module Bundle."; - - -// MARK: Account -"ACCOUNT_TITLE" = "Your PAWS Account"; -"ACCOUNT_SUBTITLE" = "Sign up for a new account or sign in to an existing account"; -"ACCOUNT_SETUP_DESCRIPTION" = "Select an option below"; -"ACCOUNT_SIGNED_IN_DESCRIPTION" = "You are successfully signed in with the following account:"; -"ACCOUNT_NEXT" = "Next"; -"ACCOUNT_SIGN_UP" = "Sign Up"; -"ACCOUNT_LOGIN" = "Login"; - -"USER_VIEW_LOADING" = "Loading user information ..."; - -"LOGIN_TITLE" = "Login"; -"LOGIN_SUBTITLE" = "Login to you existing PAWS account with a valid email and password."; - -"SIGN_UP_TITLE" = "Sign Up"; -"SIGN_UP_SUBTITLE" = "Sign up for a new PAWS account using a valid email and password"; - - -// MARK: HealthKit -"HEALTHKIT_PERMISSIONS_TITLE" = "Health Data Access"; -"HEALTHKIT_PERMISSIONS_SUBTITLE" = "PAWS requires access to your Health app data to collect ECG information including date of recording, average bpm, -reported symptoms, and any detected abnormalities. "; -"HEALTHKIT_PERMISSIONS_DESCRIPTION" = "On the next page you will be asked to grant access to the heart data from your Health app."; -"HEALTHKIT_PERMISSIONS_BUTTON" = "Grant Access"; - -// MARK: NOTIFICATIONS VIEW -"ALLOW_NOTIFICATIONS_BUTTON" = "Allow Notifications"; -"NOTIFICATION_SETUP_TITLE" = "Notifications"; -"NOTIFICATION_SETUP_SUBTITLE" = "Want to receive friendly reminders for the first week of the PAWS study? Choose your preferred time of day to receive notifications below and click allow."; - diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/String+ModuleLocalized.swift b/PAWSModules/Sources/PAWSOnboardingFlow/String+ModuleLocalized.swift deleted file mode 100644 index 08c953ed..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/String+ModuleLocalized.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -extension String { - var moduleLocalized: String { - String(localized: LocalizationValue(self), bundle: .module) - } -} diff --git a/PAWSModules/Sources/PAWSOnboardingFlow/Welcome.swift b/PAWSModules/Sources/PAWSOnboardingFlow/Welcome.swift deleted file mode 100644 index b1c5a623..00000000 --- a/PAWSModules/Sources/PAWSOnboardingFlow/Welcome.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Onboarding -import SwiftUI - - -struct Welcome: View { - @Binding private var onboardingSteps: [OnboardingFlow.Step] - - - var body: some View { - OnboardingView( - title: "WELCOME_TITLE".moduleLocalized, - subtitle: "WELCOME_SUBTITLE".moduleLocalized, - areas: [ - .init( - icon: Image(systemName: "house.fill"), - title: "WELCOME_AREA1_TITLE".moduleLocalized, - description: "WELCOME_AREA1_DESCRIPTION".moduleLocalized - ), - .init( - icon: Image(systemName: "bell.badge"), - title: "WELCOME_AREA2_TITLE".moduleLocalized, - description: "WELCOME_AREA2_DESCRIPTION".moduleLocalized - ), - .init( - icon: Image(systemName: "list.bullet.clipboard.fill"), - title: "WELCOME_AREA3_TITLE".moduleLocalized, - description: "WELCOME_AREA3_DESCRIPTION".moduleLocalized - ) - ], - actionText: "WELCOME_BUTTON".moduleLocalized, - action: { - onboardingSteps.append(.interestingModules) - } - ) - } - - - init(onboardingSteps: Binding<[OnboardingFlow.Step]>) { - self._onboardingSteps = onboardingSteps - } -} - - -struct Welcome_Previews: PreviewProvider { - @State private static var path: [OnboardingFlow.Step] = [] - - - static var previews: some View { - Welcome(onboardingSteps: $path) - } -} diff --git a/PAWSModules/Sources/PAWSSharedContext/StorageKeys.swift b/PAWSModules/Sources/PAWSSharedContext/StorageKeys.swift deleted file mode 100644 index 1ce9eb77..00000000 --- a/PAWSModules/Sources/PAWSSharedContext/StorageKeys.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -/// Constants shared across the CardinalKit Teamplate Application to access storage information including the `AppStorage` and `SceneStorage` -public enum StorageKeys { - // MARK: - Onboarding - /// A `Bool` flag indicating of the onboarding was completed. - public static let onboardingFlowComplete = "onboardingFlow.complete" - /// A `Step` flag indicating the current step in the onboarding process. - public static let onboardingFlowStep = "onboardingFlow.step" - - - // MARK: - Home - /// The currently selected home tab. - public static let homeTabSelection = "home.tabselection" -} diff --git a/PAWSTests/PAWSTests.swift b/PAWSTests/PAWSTests.swift index c889a557..4322299c 100644 --- a/PAWSTests/PAWSTests.swift +++ b/PAWSTests/PAWSTests.swift @@ -1,5 +1,5 @@ // -// This source file is part of the CS342 2023 PAWS Team Application project +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // diff --git a/PAWSUITests/ContactsTests.swift b/PAWSUITests/ContactsTests.swift index 791734a9..f32543d0 100644 --- a/PAWSUITests/ContactsTests.swift +++ b/PAWSUITests/ContactsTests.swift @@ -1,5 +1,5 @@ // -// This source file is part of the CS342 2023 PAWS Team Application project +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // @@ -9,7 +9,7 @@ import XCTest -class StudyInformationTests: XCTestCase { +class ContactsTests: XCTestCase { override func setUpWithError() throws { try super.setUpWithError() @@ -21,10 +21,19 @@ class StudyInformationTests: XCTestCase { } - func testStudyInformation() throws { + func testContacts() throws { let app = XCUIApplication() - XCTAssertTrue(app.tabBars["Tab Bar"].buttons["Study Information"].waitForExistence(timeout: 2)) - app.tabBars["Tab Bar"].buttons["Study Information"].tap() + XCTAssertTrue(app.tabBars["Tab Bar"].buttons["Contacts"].waitForExistence(timeout: 2)) + app.tabBars["Tab Bar"].buttons["Contacts"].tap() + + XCTAssertTrue(app.staticTexts["Contact: Leland Stanford"].waitForExistence(timeout: 2)) + + XCTAssertTrue(app.buttons["Call"].waitForExistence(timeout: 2)) + XCTAssertTrue(app.buttons["Text"].waitForExistence(timeout: 2)) + XCTAssertTrue(app.buttons["Email"].waitForExistence(timeout: 2)) + XCTAssertTrue(app.buttons["Website"].waitForExistence(timeout: 2)) + + XCTAssertTrue(app.buttons["Address: 450 Serra Mall\nStanford CA 94305\nUSA"].waitForExistence(timeout: 2)) } } diff --git a/PAWSUITests/ContributionsTest.swift b/PAWSUITests/ContributionsTest.swift new file mode 100644 index 00000000..a1e5c2d8 --- /dev/null +++ b/PAWSUITests/ContributionsTest.swift @@ -0,0 +1,41 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import XCTest + + +final class ContributionsTest: XCTestCase { + override func setUpWithError() throws { + try super.setUpWithError() + + try disablePasswordAutofill() + + continueAfterFailure = false + + let app = XCUIApplication() + app.launchArguments = ["--showOnboarding"] + app.deleteAndLaunch(withSpringboardAppName: "PAWS") + } + + func testLicenseInformationPage() throws { + let app = XCUIApplication() + + // complete onboarding so user is logged in + try app.conductOnboardingIfNeeded(email: "leland@contributions.stanford.edu") + + + XCTAssertTrue(app.buttons["Your Account"].waitForExistence(timeout: 6.0)) + app.buttons["Your Account"].tap() + + XCTAssertTrue(app.buttons["License Information"].waitForExistence(timeout: 2)) + app.buttons["License Information"].tap() + // Test if the sheet opens by checking if the title of the sheet is present + XCTAssertTrue(app.staticTexts["This project is licensed under the MIT License."].waitForExistence(timeout: 2)) + XCTAssertTrue(app.buttons["Repository Link"].waitForExistence(timeout: 2)) + } +} diff --git a/PAWSUITests/MockUploadTests.swift b/PAWSUITests/MockUploadTests.swift new file mode 100644 index 00000000..c12c029e --- /dev/null +++ b/PAWSUITests/MockUploadTests.swift @@ -0,0 +1,32 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import XCTest + + +class MockUploadTestsTests: XCTestCase { + override func setUpWithError() throws { + try super.setUpWithError() + + continueAfterFailure = false + + let app = XCUIApplication() + app.launchArguments = ["--skipOnboarding", "--disableFirebase"] + app.launch() + } + + + func testMockUpload() throws { + let app = XCUIApplication() + + XCTAssertTrue(app.tabBars["Tab Bar"].buttons["Mock Web Service"].waitForExistence(timeout: 2)) + app.tabBars["Tab Bar"].buttons["Mock Web Service"].tap() + + XCTAssertTrue(app.staticTexts["Mock Requests"].waitForExistence(timeout: 2)) + } +} diff --git a/PAWSUITests/NotificationsTests.swift b/PAWSUITests/NotificationsTests.swift deleted file mode 100644 index 70fe1e13..00000000 --- a/PAWSUITests/NotificationsTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// This source file is part of the CS342 2023 PAWS Team Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import XCTest - - -class NotificationsTests: XCTestCase { - override func setUpWithError() throws { - try super.setUpWithError() - - continueAfterFailure = false - - let app = XCUIApplication() - app.launchArguments = ["--skipOnboarding"] - app.launch() - } - - - func testNotifications() throws { - let app = XCUIApplication() - - XCTAssertTrue(app.tabBars["Tab Bar"].buttons["Reports"].waitForExistence(timeout: 2)) - app.tabBars["Tab Bar"].buttons["Reports"].tap() - - app.swipeUp(velocity: .fast) - } -} diff --git a/PAWSUITests/OnboardingTests.swift b/PAWSUITests/OnboardingTests.swift index 1b4c2edd..19355e12 100644 --- a/PAWSUITests/OnboardingTests.swift +++ b/PAWSUITests/OnboardingTests.swift @@ -1,5 +1,5 @@ // -// This source file is part of the CS342 2023 PAWS Team Application project +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // @@ -16,7 +16,7 @@ class OnboardingTests: XCTestCase { try super.setUpWithError() try disablePasswordAutofill() - + continueAfterFailure = false let app = XCUIApplication() @@ -27,52 +27,73 @@ class OnboardingTests: XCTestCase { func testOnboardingFlow() throws { let app = XCUIApplication() + let email = "leland@onboarding.stanford.edu" - try app.navigateOnboardingFlow(assertThatHealthKitConsentIsShown: true) + try app.navigateOnboardingFlow(email: email) - XCTAssertTrue(app.tabBars["Tab Bar"].waitForExistence(timeout: 2)) - XCTAssertTrue(app.tabBars["Tab Bar"].isHittable) + app.assertOnboardingComplete() + try app.assertAccountInformation(email: email) + } + + func testOnboardingFlowRepeated() throws { + let app = XCUIApplication() + app.launchArguments = ["--showOnboarding", "--disableFirebase"] + app.terminate() + app.launch() + + try app.navigateOnboardingFlow() + app.assertOnboardingComplete() + + app.terminate() + + // Second onboarding round shouldn't display HealthKit and Notification authorizations anymore + app.activate() + + try app.navigateOnboardingFlow(repeated: true) + // Do not show HealthKit and Notification authorization view again + app.assertOnboardingComplete() } } extension XCUIApplication { - func conductOnboardingIfNeeded() throws { - if buttons["Tap to get started"].waitForExistence(timeout: 2) { - try navigateOnboardingFlow(assertThatHealthKitConsentIsShown: false) + func conductOnboardingIfNeeded(email: String = "leland@stanford.edu") throws { + let app = XCUIApplication() + + if app.staticTexts["Spezi\nPAWS"].waitForExistence(timeout: 5) { + try app.navigateOnboardingFlow(email: email) } } - func navigateOnboardingFlow(assertThatHealthKitConsentIsShown: Bool = true) throws { - try navigateOnboardingGetStarted() + fileprivate func navigateOnboardingFlow( + email: String = "leland@stanford.edu", + repeated skippedIfRepeated: Bool = false + ) throws { try navigateOnboardingFlowWelcome() try navigateOnboardingFlowInterestingModules() - if staticTexts["Consent Page"].waitForExistence(timeout: 2) { + if staticTexts["Your Account"].waitForExistence(timeout: 5) { + try navigateOnboardingAccount(email: email) + } + if staticTexts["Consent"].waitForExistence(timeout: 5) { try navigateOnboardingFlowConsent() } - try navigateOnboardingAccount() - try healthKitPermissions() - try notificationPermissions() - } - - private func navigateOnboardingGetStarted() throws { - XCTAssertTrue(buttons["Tap to get started"].waitForExistence(timeout: 2)) - buttons["Tap to get started"].tap() + if !skippedIfRepeated { + try navigateOnboardingFlowHealthKitAccess() + try navigateOnboardingFlowNotification() + } } private func navigateOnboardingFlowWelcome() throws { - XCTAssertTrue(staticTexts["Welcome to PAWS!"].waitForExistence(timeout: 2)) + XCTAssertTrue(staticTexts["Spezi\nPAWS"].waitForExistence(timeout: 5)) - swipeUp() - - XCTAssertTrue(buttons["Begin onboarding"].waitForExistence(timeout: 2)) - buttons["Begin onboarding"].tap() + XCTAssertTrue(buttons["Learn More"].waitForExistence(timeout: 2)) + buttons["Learn More"].tap() } private func navigateOnboardingFlowInterestingModules() throws { - XCTAssertTrue(staticTexts["PAWS Onboarding"].waitForExistence(timeout: 2)) + XCTAssertTrue(staticTexts["Interesting Modules"].waitForExistence(timeout: 5)) - for _ in 1..<3 { + for _ in 1..<4 { XCTAssertTrue(buttons["Next"].waitForExistence(timeout: 2)) buttons["Next"].tap() } @@ -81,84 +102,126 @@ extension XCUIApplication { buttons["Next"].tap() } - private func navigateOnboardingFlowConsent() throws { - XCTAssertTrue(staticTexts["Consent Page"].waitForExistence(timeout: 2)) - - swipeUp(velocity: .fast) - - XCTAssertTrue(staticTexts["First Name"].waitForExistence(timeout: 2)) - try textFields["Enter your first name ..."].enter(value: "Leland") - - XCTAssertTrue(staticTexts["Last Name"].waitForExistence(timeout: 2)) - try textFields["Enter your last name ..."].enter(value: "Stanford") - swipeUp() - - XCTAssertTrue(staticTexts["Leland Stanford"].waitForExistence(timeout: 2)) - staticTexts["Leland Stanford"].firstMatch.swipeUp() - - XCTAssertTrue(buttons["I Consent"].waitForExistence(timeout: 2)) - buttons["I Consent"].tap() - } - - private func navigateOnboardingAccount() throws { - XCTAssertTrue(staticTexts["Your PAWS Account"].waitForExistence(timeout: 2)) - + private func navigateOnboardingAccount(email: String) throws { guard !buttons["Next"].waitForExistence(timeout: 5) else { buttons["Next"].tap() return } - XCTAssertTrue(buttons["Sign Up"].waitForExistence(timeout: 2)) - buttons["Sign Up"].tap() - - XCTAssertTrue(navigationBars.staticTexts["Sign Up"].waitForExistence(timeout: 2)) - XCTAssertTrue(images["App Icon"].waitForExistence(timeout: 2)) - XCTAssertTrue(buttons["Email and Password"].waitForExistence(timeout: 2)) - - buttons["Email and Password"].tap() - - try textFields["Enter your email ..."].enter(value: "leland@stanford.edu") - swipeUp() + XCTAssertTrue(buttons["Signup"].waitForExistence(timeout: 2)) + buttons["Signup"].tap() + + XCTAssertTrue(staticTexts["Create a new Account"].waitForExistence(timeout: 2)) - try secureTextFields["Enter your password ..."].enter(value: "StanfordRocks") - swipeUp() - try secureTextFields["Repeat your password ..."].enter(value: "StanfordRocks") - swipeUp() + try collectionViews.textFields["E-Mail Address"].enter(value: email) + try collectionViews.secureTextFields["Password"].enter(value: "StanfordRocks") + try textFields["enter first name"].enter(value: "Leland") + try textFields["enter last name"].enter(value: "Stanford") + + XCTAssertTrue(collectionViews.buttons["Signup"].waitForExistence(timeout: 2)) + collectionViews.buttons["Signup"].tap() + + sleep(3) + + if staticTexts["HealthKit Access"].waitForExistence(timeout: 5) && navigationBars.buttons["Back"].waitForExistence(timeout: 5) { + navigationBars.buttons["Back"].tap() + + XCTAssertTrue(staticTexts["Leland Stanford"].waitForExistence(timeout: 2)) + XCTAssertTrue(staticTexts[email].waitForExistence(timeout: 2)) + + XCTAssertTrue(buttons["Next"].waitForExistence(timeout: 2)) + buttons["Next"].tap() + } + } + + private func navigateOnboardingFlowConsent() throws { + XCTAssertTrue(staticTexts["Consent"].waitForExistence(timeout: 5)) + XCTAssertTrue(staticTexts["First Name"].waitForExistence(timeout: 2)) try textFields["Enter your first name ..."].enter(value: "Leland") - staticTexts["Repeat\nPassword"].swipeUp() + XCTAssertTrue(staticTexts["Last Name"].waitForExistence(timeout: 2)) try textFields["Enter your last name ..."].enter(value: "Stanford") - staticTexts["Repeat\nPassword"].swipeUp() - - XCTAssertTrue(collectionViews.buttons["Sign Up"].waitForExistence(timeout: 2)) - collectionViews.buttons["Sign Up"].tap() - - sleep(5) + + XCTAssertTrue(scrollViews["Signature Field"].waitForExistence(timeout: 2)) + scrollViews["Signature Field"].swipeRight() + + XCTAssertTrue(buttons["I Consent"].waitForExistence(timeout: 2)) + buttons["I Consent"].tap() } - - private func healthKitPermissions() throws { + + private func navigateOnboardingFlowHealthKitAccess() throws { + XCTAssertTrue(staticTexts["HealthKit Access"].waitForExistence(timeout: 5)) + XCTAssertTrue(buttons["Grant Access"].waitForExistence(timeout: 2)) buttons["Grant Access"].tap() try handleHealthKitAuthorization() } - private func notificationPermissions() throws { + private func navigateOnboardingFlowNotification() throws { XCTAssertTrue(staticTexts["Notifications"].waitForExistence(timeout: 5)) - swipeUp() - XCTAssertTrue(buttons["Allow Notifications"].waitForExistence(timeout: 2)) buttons["Allow Notifications"].tap() - let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") let alertAllowButton = springboard.buttons["Allow"] if alertAllowButton.waitForExistence(timeout: 5) { - alertAllowButton.tap() - } else { - print("Did not observe the notification permissions alert. Permissions might have already been provided.") + alertAllowButton.tap() } } + + fileprivate func assertOnboardingComplete() { + let tabBar = tabBars["Tab Bar"] + XCTAssertTrue(tabBar.buttons["Schedule"].waitForExistence(timeout: 2)) + XCTAssertTrue(tabBar.buttons["Contacts"].waitForExistence(timeout: 2)) + } + + fileprivate func assertAccountInformation(email: String) throws { + XCTAssertTrue(navigationBars.buttons["Your Account"].waitForExistence(timeout: 2)) + navigationBars.buttons["Your Account"].tap() + + XCTAssertTrue(staticTexts["Account Overview"].waitForExistence(timeout: 5.0)) + XCTAssertTrue(staticTexts["Leland Stanford"].exists) + XCTAssertTrue(staticTexts[email].exists) + XCTAssertTrue(staticTexts["Gender Identity, Choose not to answer"].exists) + + + XCTAssertTrue(navigationBars.buttons["Close"].waitForExistence(timeout: 0.5)) + navigationBars.buttons["Close"].tap() + + XCTAssertTrue(navigationBars.buttons["Your Account"].waitForExistence(timeout: 2)) + navigationBars.buttons["Your Account"].tap() + + XCTAssertTrue(navigationBars.buttons["Edit"].waitForExistence(timeout: 2)) + navigationBars.buttons["Edit"].tap() + + usleep(500_00) + XCTAssertFalse(navigationBars.buttons["Close"].exists) + + XCTAssertTrue(buttons["Delete Account"].waitForExistence(timeout: 2)) + buttons["Delete Account"].tap() + + let alert = "Are you sure you want to delete your account?" + XCTAssertTrue(alerts[alert].waitForExistence(timeout: 6.0)) + alerts[alert].buttons["Delete"].tap() + + XCTAssertTrue(alerts["Authentication Required"].waitForExistence(timeout: 2.0)) + XCTAssertTrue(alerts["Authentication Required"].secureTextFields["Password"].waitForExistence(timeout: 0.5)) + typeText("StanfordRocks") // the password field has focus already + XCTAssertTrue(alerts["Authentication Required"].buttons["Login"].waitForExistence(timeout: 0.5)) + alerts["Authentication Required"].buttons["Login"].tap() + + sleep(2) + + // Login + try textFields["E-Mail Address"].enter(value: email) + try secureTextFields["Password"].enter(value: "StanfordRocks") + + XCTAssertTrue(buttons["Login"].waitForExistence(timeout: 0.5)) + buttons["Login"].tap() + + XCTAssertTrue(alerts["Invalid Credentials"].waitForExistence(timeout: 2.0)) + } } diff --git a/PAWSUITests/SchedulerTests.swift b/PAWSUITests/SchedulerTests.swift new file mode 100644 index 00000000..6442f473 --- /dev/null +++ b/PAWSUITests/SchedulerTests.swift @@ -0,0 +1,36 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import XCTest +import XCTestExtensions + + +class SchedulerTests: XCTestCase { + override func setUpWithError() throws { + try super.setUpWithError() + + continueAfterFailure = false + + let app = XCUIApplication() + app.launchArguments = ["--skipOnboarding", "--testSchedule"] + app.deleteAndLaunch(withSpringboardAppName: "PAWS") + } + + + func testScheduler() throws { + let app = XCUIApplication() + + XCTAssertTrue(app.tabBars["Tab Bar"].buttons["Schedule"].waitForExistence(timeout: 2)) + app.tabBars["Tab Bar"].buttons["Schedule"].tap() + + XCTAssertTrue(app.staticTexts["Start Questionnaire"].waitForExistence(timeout: 2)) + app.staticTexts["Start Questionnaire"].tap() + + XCTAssertTrue(app.staticTexts["Social Support"].waitForExistence(timeout: 2)) + } +} diff --git a/README.md b/README.md index 4de5285f..320d4422 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# CS342 2023 PAWS Team Application +# PAWS -This repository contains the CS342 2023 PAWS Team Application. +This repository contains the PAWS. +The PAWS is using the [Spezi](https://github.com/StanfordSpezi/Spezi) ecosystem and builds on top of the [Stanford Spezi Template Application](https://github.com/StanfordSpezi/SpeziTemplateApplication). -It demonstrates using the [CardinalKit](https://github.com/StanfordBDHG/CardinalKit) framework template and builds on top of the [StanfordBDHG Template Application](https://github.com/StanfordBDHG/PAWS) and [StanfordBDHG CardinalKit Template Application](https://github.com/StanfordBDHG/CardinalKitPAWS). +> [!NOTE]  +> Do you want to learn more about the Stanford Spezi Template Application and how to use, extend, and modify this application? Check out the [Stanford Spezi Template Application documentation](https://stanfordspezi.github.io/SpeziTemplateApplication) -## Application Structure +## PAWS Features -The application uses a modularized structure enabled by using the Swift Package Manager. +*Provide a comprehensive description of your application, including figures showing the application. You can learn more on how to structure a README in the [Stanford Spezi Documentation Guide](https://swiftpackageindex.com/stanfordspezi/spezi/documentation/spezi/documentation-guide)* -The application uses the CardinalKit `FHIR` standard to provide a shared repository for data exchanged between different modules using the `FHIR` standard. -You can learn more about the CardinalKit standards-based software architecture in the [CardinalKit documentation](https://github.com/StanfordBDHG/CardinalKit). +## Contributing -## Build and Run the Application +*Ensure that you add an adequate contribution section to this README.* -You can build and run the application using [Xcode](https://developer.apple.com/xcode/) by opening up the **PAWS.xcodeproj**. -The application provides a [Firebase Firestore](https://firebase.google.com/docs/firestore)-based data upload and [Firebase Authentication](https://firebase.google.com/docs/auth) login & sign-up. -It is required to have the [Firebase Emulator Suite](https://firebase.google.com/docs/emulator-suite) to be up and running to use these features to build and test the application locally. Please follow the [installation instructions](https://firebase.google.com/docs/emulator-suite/install_and_configure). +## License -You do not have to make any modifications to the Firebase configuration, login into the `firebase` CLI using your Google account, or create a project in firebase to run, build, and test the application! - -Startup the [Firebase Emulator Suite](https://firebase.google.com/docs/emulator-suite) using -``` -$ firebase emulators:start --only auth,firestore -``` - -After the emulators have started up, you can run the application in your simulator to build, test, and run the application. - -The application includes the following feature flags that can be configured in the [scheme editor in Xcode](https://help.apple.com/xcode/mac/11.4/index.html?localePath=en.lproj#/dev0bee46f46) and selecting the **PAWS** scheme, the **Run** configuration, and to switch to the **Arguments** tab to add, enable, disable, or remove the following arguments passed on launch: -- ``--skipOnboarding``: Skips the onboarding flow to enable easier development of features in the application and to allow UI tests to skip the onboarding flow. -- ``--showOnboarding``: Always show the onboarding when the application is launched. Makes it easy to modify and test the onboarding flow without the need to manually remove the application or reset the simulator. -- ``--disableFirebase``: Disables the Firebase interactions, including the login/sign-up step and the Firebase Firestore upload. -- ``--useFirebaseEmulator``: Defines if the application should connect to the local firebase emulator. Always set to true when using the iOS simulator. - - -## Continous Delivery Workflows - -The application includes continuous integration (CI) and continuous delivery (CD) setup. -- Automatically build and test the application on every pull request before deploying it. -- An automated setup to deploy the application to TestFlight every time there is a new commit on the repository's main branch. -- Ensure a coherent code style by checking the conformance to the SwiftLint rules defined in `.swiftlint.yml` on every pull request and commit. -- Ensure conformance to the [REUSE Spacification]() to property license the application and all related code. - -Please refer to the [StanfordBDHG Template Application](https://github.com/StanfordBDHG/PAWS) and the [ContinousDelivery Example by Paul Schmiedmayer](https://github.com/PSchmiedmayer/ContinousDelivery) for more background about the CI and CD setup for the CardinalKit Template Application. - - -## Contributors & License - -This project is based on [ContinousDelivery Example by Paul Schmiedmayer](https://github.com/PSchmiedmayer/ContinousDelivery), [StanfordBDHG Template Application](https://github.com/StanfordBDHG/PAWS), and the [StanfordBDHG CardinalKit Template Application](https://github.com/StanfordBDHG/CardinalKitPAWS) provided using the MIT license. -You can find a list of contributors in the `CONTRIBUTORS.md` file. - -The CS342 2023 PAWS Team Application is licensed under the MIT license. +This project is licensed under the MIT License. See [Licenses](LICENSES) for more information. diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..c971049f --- /dev/null +++ b/codecov.yml @@ -0,0 +1,40 @@ +# +# This source file is part of the PAWS application based on the Stanford Spezi Template Application project +# +# SPDX-FileCopyrightText: 2023 Stanford University +# +# SPDX-License-Identifier: MIT +# + +codecov: + branch: main + require_ci_to_pass: true +comment: + behavior: default + layout: reach,diff,flags,files,footer + require_changes: false +coverage: + precision: 2 + range: + - 70.0 + - 90.0 + round: up + status: + patch: + default: + target: auto + threshold: 5.0 + project: + default: + target: auto + threshold: 5.0 +ignore: +- ^PAWSUITests.* +- ^PAWSTests.* +parsers: + gcov: + branch_detection: + conditional: true + loop: true + macro: false + method: false diff --git a/fastlane/.gitignore b/fastlane/.gitignore index 2ed5baae..f7cdaa40 100644 --- a/fastlane/.gitignore +++ b/fastlane/.gitignore @@ -1,5 +1,5 @@ # -# This source file is part of the CS342 2023 PAWS Team Application project +# This source file is part of the PAWS application based on the Stanford Spezi Template Application project # # SPDX-FileCopyrightText: 2023 Stanford University # diff --git a/fastlane/Appfile b/fastlane/Appfile index 8ed7f70f..1acbe6c3 100644 --- a/fastlane/Appfile +++ b/fastlane/Appfile @@ -1,5 +1,5 @@ # -# This source file is part of the CS342 2023 PAWS Team Application project +# This source file is part of the PAWS application based on the Stanford Spezi Template Application project # # SPDX-FileCopyrightText: 2023 Stanford University # diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 42547fac..884fb0e0 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,5 +1,5 @@ # -# This source file is part of the CS342 2023 PAWS Team Application project +# This source file is part of the PAWS application based on the Stanford Spezi Template Application project # # SPDX-FileCopyrightText: 2023 Stanford University # @@ -17,17 +17,41 @@ platform :ios do desc "Build and test" lane :test do run_tests( - code_coverage: true, + skip_build: true, derived_data_path: ".derivedData", + code_coverage: true, + devices: ["iPhone 15 Pro"], + force_quit_simulator: true, + reset_simulator: true, + prelaunch_simulator: false, + concurrent_workers: 1, + max_concurrent_simulators: 1, result_bundle: true, - output_directory: "." + output_directory: ".", + xcargs: "-skipPackagePluginValidation" + ) + end + + desc "CodeQL" + lane :codeql do + build_app( + skip_archive: true, + skip_codesigning: true, + derived_data_path: ".derivedData", + xcargs: "-skipPackagePluginValidation" ) end desc "Build app" lane :build do build_app( - derived_data_path: ".derivedData" + derived_data_path: ".derivedData", + xcargs: "-skipPackagePluginValidation", + export_options: { + provisioningProfiles: { + "edu.stanford.cs342.2023.paws" => "CS342 2023 PAWS Team Application" + } + } ) end @@ -44,16 +68,17 @@ platform :ios do desc "Publish a beta release to internal TestFlight testers" lane :beta do signin - increment_build_number({ - build_number: latest_testflight_build_number + 1, - xcodeproj: "PAWS.xcodeproj" - }) + increment_build_number( + { + build_number: latest_testflight_build_number + 1 + } + ) build commit = last_git_commit upload_to_testflight( distribute_external: true, groups: [ - "CS342 2023" + "External Testers" ], submit_beta_review: true, notify_external_testers: true, diff --git a/fastlane/Gymfile b/fastlane/Gymfile deleted file mode 100644 index 928c1c10..00000000 --- a/fastlane/Gymfile +++ /dev/null @@ -1,25 +0,0 @@ -# -# This source file is part of the CS342 2023 PAWS Team Application project -# -# SPDX-FileCopyrightText: 2023 Stanford University -# -# SPDX-License-Identifier: MIT -# - -# For more information about this configuration visit -# https://docs.fastlane.tools/actions/gym/#gymfile - -# In general, you can use the options available -# fastlane gym --help - -scheme("PAWS") - -project("PAWS.xcodeproj") - -export_options( - { - provisioningProfiles: { - "edu.stanford.cs342.2023.paws" => "CS342 2023 PAWS Team Application" - } - } -) \ No newline at end of file diff --git a/fastlane/README.md b/fastlane/README.md index c13a4f42..140582f7 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -23,6 +23,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do Build and test +### ios codeql + +```sh +[bundle exec] fastlane ios codeql +``` + +CodeQL + ### ios build ```sh diff --git a/fastlane/README.md.license b/fastlane/README.md.license index 9137ea9d..4f324d88 100644 --- a/fastlane/README.md.license +++ b/fastlane/README.md.license @@ -1,5 +1,5 @@ -This source file is part of the CS342 2023 PAWS Team Application project +This source file is part of the PAWS application based on the Stanford Spezi Template Application project SPDX-FileCopyrightText: 2023 Stanford University diff --git a/fastlane/Scanfile b/fastlane/Scanfile deleted file mode 100644 index e93c505a..00000000 --- a/fastlane/Scanfile +++ /dev/null @@ -1,18 +0,0 @@ -# -# This source file is part of the CS342 2023 PAWS Team Application project -# -# SPDX-FileCopyrightText: 2023 Stanford University -# -# SPDX-License-Identifier: MIT -# - -# For more information about this configuration visit -# https://docs.fastlane.tools/actions/scan/#scanfile - -# In general, you can use the options available -# fastlane scan --help - -project("PAWS.xcodeproj") -scheme("PAWS") -devices(["iPhone 14 Pro"]) -skip_build(true) diff --git a/firebase.json b/firebase.json index 74e41027..0b85064f 100644 --- a/firebase.json +++ b/firebase.json @@ -2,6 +2,9 @@ "firestore": { "rules": "firestore.rules" }, + "storage": { + "rules": "firebasestorage.rules" + }, "emulators": { "auth": { "port": 9099 @@ -13,6 +16,9 @@ "enabled": true, "port": 4000 }, + "storage": { + "port": 9199 + }, "singleProjectMode": true } -} +} \ No newline at end of file diff --git a/firebase.json.license b/firebase.json.license index 9137ea9d..c310108a 100644 --- a/firebase.json.license +++ b/firebase.json.license @@ -1,6 +1,5 @@ +This source file is part of the PAWS application based on the Stanford Spezi Template Application project -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University +SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) SPDX-License-Identifier: MIT diff --git a/firebasestorage.rules b/firebasestorage.rules new file mode 100644 index 00000000..18a3bd29 --- /dev/null +++ b/firebasestorage.rules @@ -0,0 +1,12 @@ +rules_version = '2'; +service firebase.storage { + match /b/{bucket}/o { + match /users/{userId}/{allPaths=**} { + allow read, write: if request.auth != null && request.auth.uid == userId; + } + + match /{allPaths=**} { + allow read, write: if false; + } + } +} diff --git a/firebasestorage.rules.license b/firebasestorage.rules.license new file mode 100644 index 00000000..c310108a --- /dev/null +++ b/firebasestorage.rules.license @@ -0,0 +1,5 @@ +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) + +SPDX-License-Identifier: MIT diff --git a/firestore.rules b/firestore.rules index 6b4d87de..5a233b0d 100644 --- a/firestore.rules +++ b/firestore.rules @@ -1,8 +1,9 @@ +rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // Allow only authenticated content owners access match /users/{userId}/{documents=**} { - allow read, write: if request.auth != null && request.auth.uid == userId + allow read, write: if request.auth != null && request.auth.uid == userId; } } } diff --git a/firestore.rules.license b/firestore.rules.license index 9137ea9d..c310108a 100644 --- a/firestore.rules.license +++ b/firestore.rules.license @@ -1,6 +1,5 @@ +This source file is part of the PAWS application based on the Stanford Spezi Template Application project -This source file is part of the CS342 2023 PAWS Team Application project - -SPDX-FileCopyrightText: 2023 Stanford University +SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) SPDX-License-Identifier: MIT From 6b588cd77fdf737c689244b8962ebe7548d3e8fd Mon Sep 17 00:00:00 2001 From: Paul Schmiedmayer Date: Tue, 30 Jan 2024 23:26:46 -0800 Subject: [PATCH 2/6] Update to Spezi 1.0 --- PAWS.xcodeproj/project.pbxproj | 99 ++++---- .../xcshareddata/swiftpm/Package.resolved | 88 ++++++- PAWS/ECGRecordings/ECGModule.swift | 20 ++ PAWS/ECGRecordings/ECGRecording.swift | 42 ++++ PAWS/ECGRecordings/ECGRecordingsList.swift | 56 +++++ PAWS/Helper/Date+RawRepresentable.swift | 20 ++ PAWS/Home.swift | 8 +- PAWS/Onboarding/Consent.swift | 2 + PAWS/Onboarding/InterestingModules.swift | 4 - PAWS/Onboarding/NotificationPermissions.swift | 30 ++- PAWS/PAWSDelegate.swift | 27 ++- PAWS/PAWSStandard.swift | 26 +- PAWS/Reminders/PAWSScheduler.swift | 37 +++ PAWS/Reminders/PAWSTaskContext.swift | 16 ++ PAWS/Resources/Localizable.xcstrings | 222 ++++++------------ PAWS/Schedule/Bundle+Questionnaire.swift | 26 -- PAWS/Schedule/EventContext.swift | 25 -- PAWS/Schedule/EventContextView.swift | 81 ------- PAWS/Schedule/ModalView.swift | 47 ---- PAWS/Schedule/PAWSScheduler.swift | 49 ---- PAWS/Schedule/PAWSTaskContext.swift | 40 ---- PAWS/Schedule/ScheduleView.swift | 131 ----------- PAWS/SharedContext/PAWSCard.swift | 2 +- PAWS/SharedContext/StorageKeys.swift | 5 + 24 files changed, 456 insertions(+), 647 deletions(-) create mode 100644 PAWS/ECGRecordings/ECGModule.swift create mode 100644 PAWS/ECGRecordings/ECGRecording.swift create mode 100644 PAWS/ECGRecordings/ECGRecordingsList.swift create mode 100644 PAWS/Helper/Date+RawRepresentable.swift create mode 100644 PAWS/Reminders/PAWSScheduler.swift create mode 100644 PAWS/Reminders/PAWSTaskContext.swift delete mode 100644 PAWS/Schedule/Bundle+Questionnaire.swift delete mode 100644 PAWS/Schedule/EventContext.swift delete mode 100644 PAWS/Schedule/EventContextView.swift delete mode 100644 PAWS/Schedule/ModalView.swift delete mode 100644 PAWS/Schedule/PAWSScheduler.swift delete mode 100644 PAWS/Schedule/PAWSTaskContext.swift delete mode 100644 PAWS/Schedule/ScheduleView.swift diff --git a/PAWS.xcodeproj/project.pbxproj b/PAWS.xcodeproj/project.pbxproj index c3cb6b98..63d4b680 100644 --- a/PAWS.xcodeproj/project.pbxproj +++ b/PAWS.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 27FA29902A388E9B009CAC45 /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FA298F2A388E9B009CAC45 /* ModalView.swift */; }; 2F1B52CE2A4F5CCE003AE151 /* MockUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F1B52CD2A4F5CCE003AE151 /* MockUploadTests.swift */; }; 2F3D4ABC2A4E7C290068FB2F /* SpeziScheduler in Frameworks */ = {isa = PBXBuildFile; productRef = 2F3D4ABB2A4E7C290068FB2F /* SpeziScheduler */; }; 2F49B7762980407C00BCB272 /* Spezi in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7752980407B00BCB272 /* Spezi */; }; @@ -27,6 +26,11 @@ 2FC3439029EE6346002D773C /* SocialSupportQuestionnaire.json in Resources */ = {isa = PBXBuildFile; fileRef = 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */; }; 2FC3439229EE634B002D773C /* ConsentDocument.md in Resources */ = {isa = PBXBuildFile; fileRef = 2FE5DC2C29EDD78E004B9AB4 /* ConsentDocument.md */; }; 2FC975A82978F11A00BA99FE /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FC975A72978F11A00BA99FE /* Home.swift */; }; + 2FCC1DBE2B6A1C9000C686BE /* ECGRecordingsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FCC1DBD2B6A1C9000C686BE /* ECGRecordingsList.swift */; }; + 2FCC1DC12B6A1D4C00C686BE /* ECGModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FCC1DC02B6A1D4C00C686BE /* ECGModule.swift */; }; + 2FCC1DC52B6A1F0600C686BE /* ECGRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FCC1DC42B6A1F0600C686BE /* ECGRecording.swift */; }; + 2FCC1DC92B6A258A00C686BE /* PAWSScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FCC1DC82B6A258A00C686BE /* PAWSScheduler.swift */; }; + 2FCC1DCB2B6A2B2700C686BE /* Date+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FCC1DCA2B6A2B2700C686BE /* Date+RawRepresentable.swift */; }; 2FE5DC2629EDD38A004B9AB4 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC2529EDD38A004B9AB4 /* Contacts.swift */; }; 2FE5DC3529EDD7CA004B9AB4 /* Consent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC2F29EDD7CA004B9AB4 /* Consent.swift */; }; 2FE5DC3629EDD7CA004B9AB4 /* HealthKitPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC3029EDD7CA004B9AB4 /* HealthKitPermissions.swift */; }; @@ -38,12 +42,7 @@ 2FE5DC4529EDD7F2004B9AB4 /* Binding+Negate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4229EDD7F2004B9AB4 /* Binding+Negate.swift */; }; 2FE5DC4629EDD7F2004B9AB4 /* Bundle+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4329EDD7F2004B9AB4 /* Bundle+Image.swift */; }; 2FE5DC4729EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4429EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift */; }; - 2FE5DC4E29EDD7FA004B9AB4 /* ScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4829EDD7FA004B9AB4 /* ScheduleView.swift */; }; - 2FE5DC4F29EDD7FA004B9AB4 /* EventContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4929EDD7FA004B9AB4 /* EventContext.swift */; }; - 2FE5DC5029EDD7FA004B9AB4 /* EventContextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4A29EDD7FA004B9AB4 /* EventContextView.swift */; }; 2FE5DC5129EDD7FA004B9AB4 /* PAWSTaskContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4B29EDD7FA004B9AB4 /* PAWSTaskContext.swift */; }; - 2FE5DC5229EDD7FA004B9AB4 /* PAWSScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4C29EDD7FA004B9AB4 /* PAWSScheduler.swift */; }; - 2FE5DC5329EDD7FA004B9AB4 /* Bundle+Questionnaire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4D29EDD7FA004B9AB4 /* Bundle+Questionnaire.swift */; }; 2FE5DC6429EDD883004B9AB4 /* SpeziAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC6329EDD883004B9AB4 /* SpeziAccount */; }; 2FE5DC6729EDD894004B9AB4 /* SpeziContact in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC6629EDD894004B9AB4 /* SpeziContact */; }; 2FE5DC7229EDD8D3004B9AB4 /* SpeziHealthKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC7129EDD8D3004B9AB4 /* SpeziHealthKit */; }; @@ -100,7 +99,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 27FA298F2A388E9B009CAC45 /* ModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalView.swift; sourceTree = ""; }; 2F1B52CD2A4F5CCE003AE151 /* MockUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUploadTests.swift; sourceTree = ""; }; 2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = ""; }; 2F4E23822989D51F0013F3D9 /* PAWSTestingSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSTestingSetup.swift; sourceTree = ""; }; @@ -113,6 +111,11 @@ 2FAEC07F297F583900C11C42 /* PAWS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PAWS.entitlements; sourceTree = ""; }; 2FC94CD4298B0A1D009C8209 /* PAWS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = PAWS.xctestplan; sourceTree = ""; }; 2FC975A72978F11A00BA99FE /* Home.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Home.swift; sourceTree = ""; }; + 2FCC1DBD2B6A1C9000C686BE /* ECGRecordingsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECGRecordingsList.swift; sourceTree = ""; }; + 2FCC1DC02B6A1D4C00C686BE /* ECGModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECGModule.swift; sourceTree = ""; }; + 2FCC1DC42B6A1F0600C686BE /* ECGRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECGRecording.swift; sourceTree = ""; }; + 2FCC1DC82B6A258A00C686BE /* PAWSScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSScheduler.swift; sourceTree = ""; }; + 2FCC1DCA2B6A2B2700C686BE /* Date+RawRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+RawRepresentable.swift"; sourceTree = ""; }; 2FE5DC2529EDD38A004B9AB4 /* Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = ""; }; 2FE5DC2C29EDD78E004B9AB4 /* ConsentDocument.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ConsentDocument.md; sourceTree = ""; }; 2FE5DC2F29EDD7CA004B9AB4 /* Consent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Consent.swift; sourceTree = ""; }; @@ -125,12 +128,7 @@ 2FE5DC4229EDD7F2004B9AB4 /* Binding+Negate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Binding+Negate.swift"; sourceTree = ""; }; 2FE5DC4329EDD7F2004B9AB4 /* Bundle+Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Image.swift"; sourceTree = ""; }; 2FE5DC4429EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CodableArray+RawRepresentable.swift"; sourceTree = ""; }; - 2FE5DC4829EDD7FA004B9AB4 /* ScheduleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduleView.swift; sourceTree = ""; }; - 2FE5DC4929EDD7FA004B9AB4 /* EventContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventContext.swift; sourceTree = ""; }; - 2FE5DC4A29EDD7FA004B9AB4 /* EventContextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventContextView.swift; sourceTree = ""; }; 2FE5DC4B29EDD7FA004B9AB4 /* PAWSTaskContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PAWSTaskContext.swift; sourceTree = ""; }; - 2FE5DC4C29EDD7FA004B9AB4 /* PAWSScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PAWSScheduler.swift; sourceTree = ""; }; - 2FE5DC4D29EDD7FA004B9AB4 /* Bundle+Questionnaire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Questionnaire.swift"; sourceTree = ""; }; 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SocialSupportQuestionnaire.json; sourceTree = ""; }; 2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountOnboarding.swift; sourceTree = ""; }; 2FF53D8C2A8729D600042B76 /* PAWSStandard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PAWSStandard.swift; sourceTree = ""; }; @@ -224,6 +222,16 @@ path = "Supporting Files"; sourceTree = ""; }; + 2FCC1DBC2B6A1C7400C686BE /* ECGRecordings */ = { + isa = PBXGroup; + children = ( + 2FCC1DBD2B6A1C9000C686BE /* ECGRecordingsList.swift */, + 2FCC1DC42B6A1F0600C686BE /* ECGRecording.swift */, + 2FCC1DC02B6A1D4C00C686BE /* ECGModule.swift */, + ); + path = ECGRecordings; + sourceTree = ""; + }; 2FE5DC2729EDD38D004B9AB4 /* Contacts */ = { isa = PBXGroup; children = ( @@ -257,18 +265,13 @@ path = Resources; sourceTree = ""; }; - 2FE5DC3B29EDD7D0004B9AB4 /* Schedule */ = { + 2FE5DC3B29EDD7D0004B9AB4 /* Reminders */ = { isa = PBXGroup; children = ( - 2FE5DC4D29EDD7FA004B9AB4 /* Bundle+Questionnaire.swift */, - 2FE5DC4929EDD7FA004B9AB4 /* EventContext.swift */, - 2FE5DC4A29EDD7FA004B9AB4 /* EventContextView.swift */, - 2FE5DC4829EDD7FA004B9AB4 /* ScheduleView.swift */, - 2FE5DC4C29EDD7FA004B9AB4 /* PAWSScheduler.swift */, 2FE5DC4B29EDD7FA004B9AB4 /* PAWSTaskContext.swift */, - 27FA298F2A388E9B009CAC45 /* ModalView.swift */, + 2FCC1DC82B6A258A00C686BE /* PAWSScheduler.swift */, ); - path = Schedule; + path = Reminders; sourceTree = ""; }; 2FE5DC3C29EDD7DA004B9AB4 /* SharedContext */ = { @@ -285,6 +288,7 @@ isa = PBXGroup; children = ( 2FE5DC4229EDD7F2004B9AB4 /* Binding+Negate.swift */, + 2FCC1DCA2B6A2B2700C686BE /* Date+RawRepresentable.swift */, 2FE5DC4329EDD7F2004B9AB4 /* Bundle+Image.swift */, 2FE5DC4429EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift */, ); @@ -344,7 +348,8 @@ 2FC975A72978F11A00BA99FE /* Home.swift */, A9720E412ABB68B300872D23 /* Account */, 2FE5DC2829EDD398004B9AB4 /* Onboarding */, - 2FE5DC3B29EDD7D0004B9AB4 /* Schedule */, + 2FCC1DBC2B6A1C7400C686BE /* ECGRecordings */, + 2FE5DC3B29EDD7D0004B9AB4 /* Reminders */, 2FFD22F82B59AECC005DD268 /* Study Information */, 2FE5DC2729EDD38D004B9AB4 /* Contacts */, 56F6F29E2AB441640022FE5A /* Contributions */, @@ -404,11 +409,11 @@ 653A2549283387FE005D4D48 /* Sources */, 653A254A283387FE005D4D48 /* Frameworks */, 653A254B283387FE005D4D48 /* Resources */, - 2F5B528D29BD237B002020B7 /* ShellScript */, ); buildRules = ( ); dependencies = ( + 2FCC1DD02B6A2CF000C686BE /* PBXTargetDependency */, 566155222AB83CF200209B80 /* PBXTargetDependency */, ); name = PAWS; @@ -530,6 +535,7 @@ 2FE750CA2A87240100723EAE /* XCRemoteSwiftPackageReference "SpeziMockWebService" */, 2FB099B42A875E2B00B20952 /* XCRemoteSwiftPackageReference "HealthKitOnFHIR" */, 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */, + 2FCC1DCE2B6A2CE000C686BE /* XCRemoteSwiftPackageReference "SwiftLint" */, ); productRefGroup = 653A254E283387FE005D4D48 /* Products */; projectDirPath = ""; @@ -571,27 +577,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 2F5B528D29BD237B002020B7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\n export PATH=\"$PATH:/opt/homebrew/bin\"\n if which swiftlint > /dev/null; then\n swiftlint\n else\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n fi\nfi\n"; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 653A2549283387FE005D4D48 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -605,8 +590,10 @@ 2FE5DC3529EDD7CA004B9AB4 /* Consent.swift in Sources */, 2FE5DC4529EDD7F2004B9AB4 /* Binding+Negate.swift in Sources */, 2FFD23022B59B22C005DD268 /* StudyInformation.swift in Sources */, + 2FCC1DBE2B6A1C9000C686BE /* ECGRecordingsList.swift in Sources */, + 2FCC1DCB2B6A2B2700C686BE /* Date+RawRepresentable.swift in Sources */, + 2FCC1DC12B6A1D4C00C686BE /* ECGModule.swift in Sources */, 2FC975A82978F11A00BA99FE /* Home.swift in Sources */, - 2FE5DC4E29EDD7FA004B9AB4 /* ScheduleView.swift in Sources */, A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */, 2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */, 2FF53D8D2A8729D600042B76 /* PAWSStandard.swift in Sources */, @@ -614,25 +601,22 @@ A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */, 2FE5DC4029EDD7EE004B9AB4 /* FeatureFlags.swift in Sources */, 2FE5DC4629EDD7F2004B9AB4 /* Bundle+Image.swift in Sources */, - 2FE5DC4F29EDD7FA004B9AB4 /* EventContext.swift in Sources */, - 2FE5DC5029EDD7FA004B9AB4 /* EventContextView.swift in Sources */, 2FFD22FC2B59AED8005DD268 /* FAQ.swift in Sources */, 2F4E23832989D51F0013F3D9 /* PAWSTestingSetup.swift in Sources */, - 2FE5DC5329EDD7FA004B9AB4 /* Bundle+Questionnaire.swift in Sources */, 2FE5DC5129EDD7FA004B9AB4 /* PAWSTaskContext.swift in Sources */, + 2FCC1DC92B6A258A00C686BE /* PAWSScheduler.swift in Sources */, 56F6F2A02AB441930022FE5A /* ContributionsList.swift in Sources */, 566155292AB8447C00209B80 /* Package+LicenseType.swift in Sources */, 2FFD22FF2B59B158005DD268 /* StudyDescription.swift in Sources */, 5680DD392AB8983D004E6D4A /* PackageCell.swift in Sources */, + 2FCC1DC52B6A1F0600C686BE /* ECGRecording.swift in Sources */, 2F5E32BD297E05EA003432F8 /* PAWSDelegate.swift in Sources */, - 2FE5DC5229EDD7FA004B9AB4 /* PAWSScheduler.swift in Sources */, A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */, 653A2551283387FE005D4D48 /* PAWS.swift in Sources */, 2FFD22F62B59ABE2005DD268 /* PAWSCard.swift in Sources */, 2FE5DC3629EDD7CA004B9AB4 /* HealthKitPermissions.swift in Sources */, 2F65B44E2A3B8B0600A36932 /* NotificationPermissions.swift in Sources */, 5661552E2AB854C000209B80 /* PackageHelper.swift in Sources */, - 27FA29902A388E9B009CAC45 /* ModalView.swift in Sources */, 2FE5DC2629EDD38A004B9AB4 /* Contacts.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -660,6 +644,10 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 2FCC1DD02B6A2CF000C686BE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = 2FCC1DCF2B6A2CF000C686BE /* SwiftLintPlugin */; + }; 566155222AB83CF200209B80 /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = 566155212AB83CF200209B80 /* SwiftPackageListJSONPlugin */; @@ -1188,6 +1176,14 @@ minimumVersion = 0.2.4; }; }; + 2FCC1DCE2B6A2CE000C686BE /* XCRemoteSwiftPackageReference "SwiftLint" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/realm/SwiftLint.git"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 0.54.0; + }; + }; 2FE5DC6229EDD883004B9AB4 /* XCRemoteSwiftPackageReference "SpeziAccount" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/StanfordSpezi/SpeziAccount.git"; @@ -1329,6 +1325,11 @@ isa = XCSwiftPackageProductDependency; productName = SpeziScheduler; }; + 2FCC1DCF2B6A2CF000C686BE /* SwiftLintPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = 2FCC1DCE2B6A2CE000C686BE /* XCRemoteSwiftPackageReference "SwiftLint" */; + productName = "plugin:SwiftLintPlugin"; + }; 2FE5DC6329EDD883004B9AB4 /* SpeziAccount */ = { isa = XCSwiftPackageProductDependency; package = 2FE5DC6229EDD883004B9AB4 /* XCRemoteSwiftPackageReference "SpeziAccount" */; diff --git a/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c6a59aec..6e8a9546 100644 --- a/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,6 +18,24 @@ "version" : "10.18.0" } }, + { + "identity" : "collectionconcurrencykit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git", + "state" : { + "revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95", + "version" : "0.2.0" + } + }, + { + "identity" : "cryptoswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", + "state" : { + "revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c", + "version" : "1.8.1" + } + }, { "identity" : "fhirmodels", "kind" : "remoteSourceControl", @@ -131,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/StanfordBDHG/ResearchKit", "state" : { - "revision" : "cf79a15c7d8c436f98937fe93e72e880dd2f73e4", - "version" : "2.2.20" + "revision" : "209164ed20592a2213c4bd69cefcb078d9de0692", + "version" : "2.2.21" } }, { @@ -140,8 +158,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/StanfordBDHG/ResearchKitOnFHIR", "state" : { - "revision" : "7dc09f7acd7fb19673594e0fdd4d72d0869ee006", - "version" : "1.0.0" + "revision" : "ea4d9691591594177e7dfbc8c246324855d73eb5", + "version" : "1.0.1" + } + }, + { + "identity" : "sourcekitten", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/SourceKitten.git", + "state" : { + "revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56", + "version" : "0.34.1" } }, { @@ -257,8 +284,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser", "state" : { - "revision" : "c8ed701b513cf5177118a175d85fbbbcd707ab41", - "version" : "1.3.0" + "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", + "version" : "1.2.3" } }, { @@ -288,10 +315,46 @@ "version" : "1.25.2" } }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", + "version" : "509.0.2" + } + }, + { + "identity" : "swiftlint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/realm/SwiftLint.git", + "state" : { + "revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee", + "version" : "0.54.0" + } + }, + { + "identity" : "swiftytexttable", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scottrhoyt/SwiftyTextTable.git", + "state" : { + "revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", + "version" : "0.9.0" + } + }, + { + "identity" : "swxmlhash", + "kind" : "remoteSourceControl", + "location" : "https://github.com/drmohundro/SWXMLHash.git", + "state" : { + "revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f", + "version" : "7.0.2" + } + }, { "identity" : "xctestextensions", "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordBDHG/XCTestExtensions.git", + "location" : "https://github.com/StanfordBDHG/XCTestExtensions", "state" : { "revision" : "388a6d6a5be48eff5d98a2c45e0b50f30ed21dc3", "version" : "0.4.7" @@ -300,7 +363,7 @@ { "identity" : "xcthealthkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordBDHG/XCTHealthKit.git", + "location" : "https://github.com/StanfordBDHG/XCTHealthKit", "state" : { "revision" : "6e9344a2d632b801d94fe3bbd1d891817e032103", "version" : "0.3.5" @@ -314,6 +377,15 @@ "revision" : "bb2a287c2544aa846e53670d1ece35e5949567be", "version" : "1.0.0" } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams.git", + "state" : { + "revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3", + "version" : "5.0.6" + } } ], "version" : 2 diff --git a/PAWS/ECGRecordings/ECGModule.swift b/PAWS/ECGRecordings/ECGModule.swift new file mode 100644 index 00000000..49b5e144 --- /dev/null +++ b/PAWS/ECGRecordings/ECGModule.swift @@ -0,0 +1,20 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import HealthKit +import Spezi + + +@Observable +class ECGModule: Module, DefaultInitializable, EnvironmentAccessible { + var hkElectrocardiograms: [HKElectrocardiogram] = [] + + + /// Creates an instance of a ``MockWebService``. + required init() { } +} diff --git a/PAWS/ECGRecordings/ECGRecording.swift b/PAWS/ECGRecordings/ECGRecording.swift new file mode 100644 index 00000000..b1156b86 --- /dev/null +++ b/PAWS/ECGRecordings/ECGRecording.swift @@ -0,0 +1,42 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import HealthKit +import SwiftUI + + +struct ECGRecording: View { + let hkElectrocardiogram: HKElectrocardiogram + @State var symptoms: HKElectrocardiogram.Symptoms = [:] + + + var body: some View { + PAWSCard { + HStack { + Text("EEG Recording") + .font(.title) + Text(hkElectrocardiogram.endDate.formatted()) + .font(.subheadline) + .foregroundStyle(.secondary) + Divider() + if symptoms.isEmpty { + Text("Recorded no symptoms") + } else { + Text("Recorded \(symptoms.count) symptoms.") + } + } + } + .task { + guard let symptoms = try? await hkElectrocardiogram.symptoms(from: HKHealthStore()) else { + return + } + + self.symptoms = symptoms + } + } +} diff --git a/PAWS/ECGRecordings/ECGRecordingsList.swift b/PAWS/ECGRecordings/ECGRecordingsList.swift new file mode 100644 index 00000000..491acf6a --- /dev/null +++ b/PAWS/ECGRecordings/ECGRecordingsList.swift @@ -0,0 +1,56 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +struct ECGRecordingsList: View { + @Environment(ECGModule.self) var ecgModule + @Binding var presentingAccount: Bool + + + var body: some View { + NavigationStack { + GeometryReader { geometry in + ScrollView { + if ecgModule.hkElectrocardiograms.isEmpty { + ContentUnavailableView { + Label("No Recordings", systemImage: "waveform.path.ecg") + } description: { + Text("New ECG Recordings will be displayed here.") + } + .frame(minHeight: geometry.size.height) + } + ForEach(ecgModule.hkElectrocardiograms) { hkElectrocardiogram in + ECGRecording(hkElectrocardiogram: hkElectrocardiogram) + } + } + .scrollBounceBehavior(.basedOnSize) + } + .toolbar { + if AccountButton.shouldDisplay { + AccountButton(isPresented: $presentingAccount) + } + } + .navigationTitle(String(localized: "ECG Recordings")) + } + } + + + init(presentingAccount: Binding) { + self._presentingAccount = presentingAccount + } +} + + +#Preview { + ECGRecordingsList(presentingAccount: .constant(false)) + .previewWith { + ECGModule() + } +} diff --git a/PAWS/Helper/Date+RawRepresentable.swift b/PAWS/Helper/Date+RawRepresentable.swift new file mode 100644 index 00000000..761b54b2 --- /dev/null +++ b/PAWS/Helper/Date+RawRepresentable.swift @@ -0,0 +1,20 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation + + +extension Date: RawRepresentable { + public var rawValue: String { + self.timeIntervalSinceReferenceDate.description + } + + public init?(rawValue: String) { + self = Date(timeIntervalSinceReferenceDate: Double(rawValue) ?? 0.0) + } +} diff --git a/PAWS/Home.swift b/PAWS/Home.swift index d4a355de..954a5b4e 100644 --- a/PAWS/Home.swift +++ b/PAWS/Home.swift @@ -30,10 +30,10 @@ struct HomeView: View { var body: some View { TabView(selection: $selectedTab) { - ScheduleView(presentingAccount: $presentingAccount) + ECGRecordingsList(presentingAccount: $presentingAccount) .tag(Tabs.schedule) .tabItem { - Label("SCHEDULE_TAB_TITLE", systemImage: "list.clipboard") + Label("ECG Recordings", systemImage: "waveform.path.ecg") } StudyInformation(presentingAccount: $presentingAccount) .tag(Tabs.studyInformation) @@ -43,13 +43,13 @@ struct HomeView: View { Contacts(presentingAccount: $presentingAccount) .tag(Tabs.contact) .tabItem { - Label("CONTACTS_TAB_TITLE", systemImage: "person.fill") + Label("Contacts", systemImage: "person.fill") } if FeatureFlags.disableFirebase { MockUpload(presentingAccount: $presentingAccount) .tag(Tabs.mockUpload) .tabItem { - Label("MOCK_WEB_SERVICE_TAB_TITLE", systemImage: "server.rack") + Label("Mock Web Service", systemImage: "server.rack") } } } diff --git a/PAWS/Onboarding/Consent.swift b/PAWS/Onboarding/Consent.swift index c19312da..2e7e7524 100644 --- a/PAWS/Onboarding/Consent.swift +++ b/PAWS/Onboarding/Consent.swift @@ -13,6 +13,7 @@ import SwiftUI /// - Note: The `OnboardingConsentView` exports the signed consent form as PDF to the Spezi `Standard`, necessitating the conformance of the `Standard` to the `OnboardingConstraint`. struct Consent: View { @Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath + @AppStorage(StorageKeys.healthKitStartDate) var healthKitStartDate: Date? private var consentDocument: Data { @@ -30,6 +31,7 @@ struct Consent: View { consentDocument }, action: { + healthKitStartDate = Date.now onboardingNavigationPath.nextStep() } ) diff --git a/PAWS/Onboarding/InterestingModules.swift b/PAWS/Onboarding/InterestingModules.swift index 392a9418..08d5d703 100644 --- a/PAWS/Onboarding/InterestingModules.swift +++ b/PAWS/Onboarding/InterestingModules.swift @@ -30,10 +30,6 @@ struct InterestingModules: View { SequentialOnboardingView.Content( title: "INTERESTING_MODULES_AREA3_TITLE", description: "INTERESTING_MODULES_AREA3_DESCRIPTION" - ), - SequentialOnboardingView.Content( - title: "INTERESTING_MODULES_AREA4_TITLE", - description: "INTERESTING_MODULES_AREA4_DESCRIPTION" ) ], actionText: "INTERESTING_MODULES_BUTTON", diff --git a/PAWS/Onboarding/NotificationPermissions.swift b/PAWS/Onboarding/NotificationPermissions.swift index d997875c..bc9d4e5d 100644 --- a/PAWS/Onboarding/NotificationPermissions.swift +++ b/PAWS/Onboarding/NotificationPermissions.swift @@ -16,25 +16,31 @@ struct NotificationPermissions: View { @Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath @State private var notificationProcessing = false + @State private var selectedTime = Calendar.current.date(bySettingHour: 19, minute: 0, second: 0, of: .now) ?? .now var body: some View { OnboardingView( contentView: { VStack { + Image(systemName: "pawprint.circle.fill") + .resizable() + .scaledToFill() + .frame(width: 40, height: 40) + .foregroundStyle(Color.accentColor) + .offset(y: 20) + .accessibilityHidden(true) OnboardingTitleView( - title: "NOTIFICATION_PERMISSIONS_TITLE", - subtitle: "NOTIFICATION_PERMISSIONS_SUBTITLE" + title: "NOTIFICATIONS_TITLE", + subtitle: "NOTIFICATIONS_SUBTITLE" ) - Spacer() - Image(systemName: "bell.square.fill") - .font(.system(size: 150)) - .foregroundColor(.accentColor) + DatePicker("Select a time", selection: $selectedTime, displayedComponents: .hourAndMinute) + Image("NotificationImage") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 275, height: 275) + .padding(.vertical) .accessibilityHidden(true) - Text("NOTIFICATION_PERMISSIONS_DESCRIPTION") - .multilineTextAlignment(.center) - .padding(.vertical, 16) - Spacer() } }, actionView: { OnboardingActionsView( @@ -42,6 +48,10 @@ struct NotificationPermissions: View { action: { do { notificationProcessing = true + + let dateComponents = Calendar.current.dateComponents([.hour, .minute], from: selectedTime) + try await scheduler.scheduleReminders(time: dateComponents) + // Notification Authorization is not available in the preview simulator. if ProcessInfo.processInfo.isPreviewSimulator { try await _Concurrency.Task.sleep(for: .seconds(5)) diff --git a/PAWS/PAWSDelegate.swift b/PAWS/PAWSDelegate.swift index 4515e1fd..7966a1b9 100644 --- a/PAWS/PAWSDelegate.swift +++ b/PAWS/PAWSDelegate.swift @@ -25,10 +25,8 @@ class PAWSDelegate: SpeziAppDelegate { AccountConfiguration(configuration: [ .requires(\.userId), .requires(\.name), - - // additional values stored using the `FirestoreAccountStorage` within our Standard implementation - .collects(\.genderIdentity), - .collects(\.dateOfBirth) + .requires(\.dateOfBirth), + .collects(\.genderIdentity) ]) if FeatureFlags.useFirebaseEmulator { @@ -74,10 +72,25 @@ class PAWSDelegate: SpeziAppDelegate { private var healthKit: HealthKit { - HealthKit { + @AppStorage(StorageKeys.healthKitStartDate) var healthKitStartDate: Date = .now + + // Collection starts at the time the user consents and lasts for 1 month. + let sharedPredicate = HKQuery.predicateForSamples( + withStart: healthKitStartDate, + end: Calendar.current.date(byAdding: DateComponents(month: 1), to: healthKitStartDate), + options: .strictEndDate + ) + + return HealthKit { CollectSample( - HKQuantityType(.stepCount), - deliverySetting: .anchorQuery(.afterAuthorizationAndApplicationWillLaunch) + HKQuantityType.electrocardiogramType(), + predicate: sharedPredicate, + deliverySetting: .background(saveAnchor: false) + ) + CollectSamples( + Set(HKElectrocardiogram.correlatedSymptomTypes), + predicate: sharedPredicate, + deliverySetting: .background(saveAnchor: false) ) } } diff --git a/PAWS/PAWSStandard.swift b/PAWS/PAWSStandard.swift index 1570b75a..ba106021 100644 --- a/PAWS/PAWSStandard.swift +++ b/PAWS/PAWSStandard.swift @@ -33,6 +33,7 @@ actor PAWSStandard: Standard, EnvironmentAccessible, HealthKitConstraint, Onboar @Dependency var mockWebService: MockWebService? @Dependency var accountStorage: FirestoreAccountStorage? + @Dependency var ecgStorage: ECGModule @AccountReference var account: Account @@ -68,6 +69,10 @@ actor PAWSStandard: Standard, EnvironmentAccessible, HealthKitConstraint, Onboar func add(sample: HKSample) async { + if let hkElectrocardiogram = sample as? HKElectrocardiogram { + ecgStorage.hkElectrocardiograms.append(hkElectrocardiogram) + } + if let mockWebService { let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes] @@ -84,6 +89,8 @@ actor PAWSStandard: Standard, EnvironmentAccessible, HealthKitConstraint, Onboar } func remove(sample: HKDeletedObject) async { + ecgStorage.hkElectrocardiograms.removeAll(where: { $0.id == sample.uuid }) + if let mockWebService { try? await mockWebService.remove(path: "healthkit/\(sample.uuid.uuidString)") return @@ -96,25 +103,6 @@ actor PAWSStandard: Standard, EnvironmentAccessible, HealthKitConstraint, Onboar } } - func add(response: ModelsR4.QuestionnaireResponse) async { - let id = response.identifier?.value?.value?.string ?? UUID().uuidString - - if let mockWebService { - let jsonRepresentation = (try? String(data: JSONEncoder().encode(response), encoding: .utf8)) ?? "" - try? await mockWebService.upload(path: "questionnaireResponse/\(id)", body: jsonRepresentation) - return - } - - do { - try await userDocumentReference - .collection("QuestionnaireResponse") // Add all HealthKit sources in a /QuestionnaireResponse collection. - .document(id) // Set the document identifier to the id of the response. - .setData(from: response) - } catch { - logger.error("Could not store questionnaire response: \(error)") - } - } - private func healthKitDocument(id uuid: UUID) async throws -> DocumentReference { try await userDocumentReference diff --git a/PAWS/Reminders/PAWSScheduler.swift b/PAWS/Reminders/PAWSScheduler.swift new file mode 100644 index 00000000..aaa8bc97 --- /dev/null +++ b/PAWS/Reminders/PAWSScheduler.swift @@ -0,0 +1,37 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SpeziScheduler + + +/// A `Scheduler` using the ``PAWSTaskContext`` to schedule and manage tasks and events in the +/// PAWS. +typealias PAWSScheduler = Scheduler + + +extension PAWSScheduler { + func scheduleReminders(time: DateComponents) async throws { + // We discard all other date components: + let dateComponents = DateComponents(hour: time.hour, minute: time.minute) + + await schedule( + task: Task( + title: String(localized: "Friendly reminder to record your ECG!"), + description: String(localized: "Thank you for participating in the PAWS study!"), + schedule: Schedule( + start: Calendar.current.startOfDay(for: .now), + repetition: .matching(dateComponents), + end: .numberOfEvents(7) + ), + notifications: true, + context: PAWSTaskContext.reminder + ) + ) + } +} diff --git a/PAWS/Reminders/PAWSTaskContext.swift b/PAWS/Reminders/PAWSTaskContext.swift new file mode 100644 index 00000000..b92560bf --- /dev/null +++ b/PAWS/Reminders/PAWSTaskContext.swift @@ -0,0 +1,16 @@ +// +// This source file is part of the PAWS application based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation + + +/// The context attached to each task in the PAWS app. +enum PAWSTaskContext: Codable { + /// Reminder Notificaiton to do a ECG recording + case reminder +} diff --git a/PAWS/Resources/Localizable.xcstrings b/PAWS/Resources/Localizable.xcstrings index c14bd0d7..3a13a556 100644 --- a/PAWS/Resources/Localizable.xcstrings +++ b/PAWS/Resources/Localizable.xcstrings @@ -26,7 +26,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "You are already logged in with the account shown below. Continue or change your account by logging out." + "value" : "You are successfully signed in with the following account:" } } } @@ -36,7 +36,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "The PAWS demonstrates the usage of the Firebase Account Module." + "value" : "Sign up for a new account or sign in to an existing account" } } } @@ -47,7 +47,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Your Account" + "value" : "Your PAWS Account" } } } @@ -172,16 +172,6 @@ } } }, - "COMPLETED_TASK_LABEL %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Completed Task: %@" - } - } - } - }, "CONSENT_LOADING_ERROR" : { "localizations" : { "en" : { @@ -192,18 +182,10 @@ } } }, - "CONTACTS_NAVIGATION_TITLE" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Contacts" - } - } - } + "Contacts" : { + }, - "CONTACTS_TAB_TITLE" : { - "comment" : "MARK: - Contacts", + "CONTACTS_NAVIGATION_TITLE" : { "localizations" : { "en" : { "stringUnit" : { @@ -218,7 +200,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "The following list contains all Swift Package dependencies of the SpeziPAWS." + "value" : "The following list contains all Swift Package dependencies of the PAWS Application." } } } @@ -242,6 +224,12 @@ } } } + }, + "ECG Recordings" : { + + }, + "EEG Recording" : { + }, "FAQ" : { "localizations" : { @@ -252,13 +240,16 @@ } } } + }, + "Friendly reminder to record your ECG!" : { + }, "HEALTHKIT_PERMISSIONS_BUTTON" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Grant Access" + "value" : "Health Data Access" } } } @@ -268,7 +259,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "This onboarding step allows you to customize the onboarding flow to explain how the application uses the HealhtKit data and allows a user to cusomize the selection." + "value" : "On the next page you will be asked to grant access to the heart data from your Health app." } } } @@ -278,7 +269,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Spezi can access data from HealthKit using the HealthKitDataSource module." + "value" : "PAWS requires access to your Health app data to collect ECG information including date of recording, average bpm,\nreported symptoms, and any detected abnormalities." } } } @@ -302,7 +293,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "The onboarding module allows you to build an onboarding flow like this one." + "value" : "Create an account so researchers can identify you as a patient" } } } @@ -312,7 +303,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Onboarding" + "value" : "Create an account" } } } @@ -322,7 +313,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "The application uses HL7 FHIR to provide a common standard to encode data gathered by the application." + "value" : "Give researchers consent to use your health data as part of the PAWS study" } } } @@ -332,7 +323,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "HL7 FHIR" + "value" : "Consent" } } } @@ -342,7 +333,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "The contact module allows you to display contact information in your application." + "value" : "Allow the PAWS app to access the health data recorded on your Apple Watch and iPhone" } } } @@ -352,120 +343,121 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Contact" + "value" : "Grant access to health data" } } } }, - "INTERESTING_MODULES_AREA4_DESCRIPTION" : { + "INTERESTING_MODULES_BUTTON" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "The HealthKit data source module allows you to fetch data from HealthKit and e.g. transform it to FHIR resources." + "value" : "Next" } } } }, - "INTERESTING_MODULES_AREA4_TITLE" : { + "INTERESTING_MODULES_SUBTITLE" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "HealthKit Data Source" + "value" : "You'll need to complete a few more steps\nto get started..." } } } }, - "INTERESTING_MODULES_BUTTON" : { + "INTERESTING_MODULES_TITLE" : { + "comment" : "MARK: Interesting Modules", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Next" + "value" : "Interesting Modules" } } } }, - "INTERESTING_MODULES_SUBTITLE" : { + "LICENSE_INFO_TITLE" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Here are a few Spezi modules that are interesting to know about ..." + "value" : "License Information" } } } }, - "INTERESTING_MODULES_TITLE" : { - "comment" : "MARK: Interesting Modules", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interesting Modules" - } - } - } + "Mock Web Service" : { + }, - "LICENSE_INFO_TITLE" : { + "New ECG Recordings will be displayed here." : { + + }, + "No Recordings" : { + + }, + "NOTIFICATION_PERMISSIONS_BUTTON" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "License Information" + "value" : "Allow Notifications" } } } }, - "MOCK_WEB_SERVICE_TAB_TITLE" : { - "comment" : "MARK: - Mock Upload Data Storage Provider", + "NOTIFICATION_PERMISSIONS_DESCRIPTION" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Mock Web Service" + "value" : "Do you want to receive friendly reminders for the first week of the PAWS study? Choose your preferred time of day to receive notifications below and click allow." } } } }, - "NOTIFICATION_PERMISSIONS_BUTTON" : { + "NOTIFICATION_PERMISSIONS_SUBTITLE" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Allow Notifications" + "value" : "PAWS Study Reminder" } } } }, - "NOTIFICATION_PERMISSIONS_DESCRIPTION" : { + "NOTIFICATION_PERMISSIONS_TITLE" : { + "comment" : "MARK: Notifications", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "The Spezi Scheduler module enables to send out local notifications when a new event of a task is scheduled." + "value" : "Notifications" } } } }, - "NOTIFICATION_PERMISSIONS_SUBTITLE" : { + "NOTIFICATIONS_SUBTITLE" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Spezi Scheduler Notifications." + "value" : "Do you want to receive friendly reminders for the first week of the PAWS study? Choose your preferred time of day to receive notifications below and click allow." } } } }, - "NOTIFICATION_PERMISSIONS_TITLE" : { - "comment" : "MARK: Notifications", + "NOTIFICATIONS_TITLE" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Notifications" + "value" : "PAWS Reminders" } } } @@ -579,33 +571,18 @@ } } }, - "Repository Link" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Repository Link" - } - } - } + "Recorded %lld symptoms." : { + }, - "SCHEDULE_LIST_TITLE" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schedule" - } - } - } + "Recorded no symptoms" : { + }, - "SCHEDULE_TAB_TITLE" : { - "comment" : "MARK: - Schedule", + "Repository Link" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Schedule" + "value" : "Repository Link" } } } @@ -619,6 +596,9 @@ } } } + }, + "Select a time" : { + }, "Study Information" : { "localizations" : { @@ -640,57 +620,7 @@ } } }, - "TASK_CONTEXT_ACTION_QUESTIONNAIRE" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Start Questionnaire" - } - } - } - }, - "TASK_CONTEXT_ACTION_TEST" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Start Test" - } - } - } - }, - "TASK_LABEL %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Task: %@" - } - } - } - }, - "TASK_SOCIAL_SUPPORT_QUESTIONNAIRE_DESCRIPTION" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Please fill out the Social Support Questionnaire every day." - } - } - } - }, - "TASK_SOCIAL_SUPPORT_QUESTIONNAIRE_TITLE" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Social Support Questionnaire" - } - } - } - }, - "Test ..." : { + "Thank you for participating in the PAWS study!" : { }, "WELCOME_AREA1_DESCRIPTION" : { @@ -698,7 +628,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "The Spezi Framework builds the foundation of this application." + "value" : "You'll be able to see your patient information and a summary of the ECG recordings uploaded from your Apple Watch." } } } @@ -708,7 +638,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "The Spezi Framework" + "value" : "Your ECG Recordings" } } } @@ -718,7 +648,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Spezi uses the Swift Package Manager to import it as a dependency." + "value" : "Check if an ECG recording from your Apple Watch was successfully uploaded to the research server. " } } } @@ -728,7 +658,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Swift Package Manager" + "value" : "Study Status" } } } @@ -738,7 +668,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Spezi offers several modules including HealthKit integration, questionnaires, and more ..." + "value" : "Learn more about the PAWS study and contact the lead researchers if you have any questions or concerns." } } } @@ -748,7 +678,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Spezi Modules" + "value" : "Study Information" } } } @@ -768,7 +698,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "This application demonstrates several Spezi features & modules." + "value" : "We are excited to have you join the Pediatric Apple Watch Study (PAWS) to improve diagnosis of arrhythmias in patients like you!" } } } @@ -779,7 +709,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Spezi\nPAWS" + "value" : "Welcome to PAWS!" } } } diff --git a/PAWS/Schedule/Bundle+Questionnaire.swift b/PAWS/Schedule/Bundle+Questionnaire.swift deleted file mode 100644 index d7784d35..00000000 --- a/PAWS/Schedule/Bundle+Questionnaire.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// This source file is part of the PAWS application based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Foundation -import SpeziQuestionnaire - - -extension Foundation.Bundle { - func questionnaire(withName name: String) -> Questionnaire { - guard let resourceURL = self.url(forResource: name, withExtension: "json") else { - fatalError("Could not find the questionnaire \"\(name).json\" in the bundle.") - } - - do { - let resourceData = try Data(contentsOf: resourceURL) - return try JSONDecoder().decode(Questionnaire.self, from: resourceData) - } catch { - fatalError("Could not decode the FHIR questionnaire named \"\(name).json\": \(error)") - } - } -} diff --git a/PAWS/Schedule/EventContext.swift b/PAWS/Schedule/EventContext.swift deleted file mode 100644 index 932ac529..00000000 --- a/PAWS/Schedule/EventContext.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// This source file is part of the PAWS application based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SpeziScheduler - - -struct EventContext: Comparable, Identifiable { - let event: Event - let task: Task - - - var id: Event.ID { - event.id - } - - - static func < (lhs: EventContext, rhs: EventContext) -> Bool { - lhs.event.scheduledAt < rhs.event.scheduledAt - } -} diff --git a/PAWS/Schedule/EventContextView.swift b/PAWS/Schedule/EventContextView.swift deleted file mode 100644 index c909cca6..00000000 --- a/PAWS/Schedule/EventContextView.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// This source file is part of the PAWS application based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SpeziScheduler -import SwiftUI - - -struct EventContextView: View { - let eventContext: EventContext - - - var body: some View { - HStack { - VStack(alignment: .leading) { - HStack { - if eventContext.event.complete { - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.accentColor) - .font(.system(size: 30)) - .accessibilityHidden(true) - } - VStack(alignment: .leading, spacing: 8) { - Text(verbatim: eventContext.task.title) - .font(.headline) - .accessibilityLabel( - eventContext.event.complete - ? "COMPLETED_TASK_LABEL \(eventContext.task.title)" - : "TASK_LABEL \(eventContext.task.title)" - ) - Text(verbatim: format(eventDate: eventContext.event.scheduledAt)) - .font(.subheadline) - } - } - Divider() - Text(eventContext.task.description) - .font(.callout) - if !eventContext.event.complete { - Text(eventContext.task.context.actionType) - .frame(maxWidth: .infinity, minHeight: 50) - .foregroundColor(.white) - .background(Color.accentColor) - .clipShape(RoundedRectangle(cornerRadius: 8)) - .padding(.top, 8) - } - } - } - .disabled(eventContext.event.complete) - .contentShape(Rectangle()) - } - - - private func format(eventDate: Date) -> String { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .none - dateFormatter.timeStyle = .short - return dateFormatter.string(from: eventDate) - } -} - - -#if DEBUG -#Preview(traits: .sizeThatFitsLayout) { - let task = PAWSScheduler.socialSupportTask - - return EventContextView( - eventContext: EventContext( - // We use a force unwrap in the preview as we can not recover from an error here - // and the code will never end up in a production environment. - // swiftlint:disable:next force_unwrapping - event: task.events(from: .now.addingTimeInterval(-60 * 60 * 24)).first!, - task: task - ) - ) - .padding() -} -#endif diff --git a/PAWS/Schedule/ModalView.swift b/PAWS/Schedule/ModalView.swift deleted file mode 100644 index 4fd26b44..00000000 --- a/PAWS/Schedule/ModalView.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// This source file is part of the PAWS application based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SpeziViews -import SwiftUI - - -struct ModalView: View { - @Environment(\.dismiss) private var dismiss - - let text: String - let buttonText: String - let onClose: () async -> Void - - - var body: some View { - VStack { - Spacer() - Text(text) - .padding() - Spacer() - AsyncButton { - self.dismiss() - await self.onClose() - } label: { - Text(buttonText) - .frame(maxWidth: .infinity, minHeight: 38) - } - .padding() - .buttonStyle(.borderedProminent) - } - } -} - - -#if DEBUG -#Preview { - ModalView(text: "Preview Modal", buttonText: "Close") { - print("Preview Modal closed.") - } -} -#endif diff --git a/PAWS/Schedule/PAWSScheduler.swift b/PAWS/Schedule/PAWSScheduler.swift deleted file mode 100644 index 9e58e713..00000000 --- a/PAWS/Schedule/PAWSScheduler.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// This source file is part of the PAWS application based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Foundation -import SpeziScheduler - - -/// A `Scheduler` using the ``PAWSTaskContext`` to schedule and manage tasks and events in the -/// PAWS. -typealias PAWSScheduler = Scheduler - - -extension PAWSScheduler { - static var socialSupportTask: SpeziScheduler.Task { - let dateComponents: DateComponents - if FeatureFlags.testSchedule { - // Adds a task at the current time for UI testing if the `--testSchedule` feature flag is set - dateComponents = DateComponents( - hour: Calendar.current.component(.hour, from: .now), - minute: Calendar.current.component(.minute, from: .now) - ) - } else { - // For the normal app usage, we schedule the task for every day at 8:00 AM - dateComponents = DateComponents(hour: 8, minute: 0) - } - - return Task( - title: String(localized: "TASK_SOCIAL_SUPPORT_QUESTIONNAIRE_TITLE"), - description: String(localized: "TASK_SOCIAL_SUPPORT_QUESTIONNAIRE_DESCRIPTION"), - schedule: Schedule( - start: Calendar.current.startOfDay(for: Date()), - repetition: .matching(dateComponents), - end: .numberOfEvents(365) - ), - notifications: true, - context: PAWSTaskContext.questionnaire(Bundle.main.questionnaire(withName: "SocialSupportQuestionnaire")) - ) - } - - /// Creates a default instance of the ``PAWSScheduler`` by scheduling the tasks listed below. - convenience init() { - self.init(tasks: [Self.socialSupportTask]) - } -} diff --git a/PAWS/Schedule/PAWSTaskContext.swift b/PAWS/Schedule/PAWSTaskContext.swift deleted file mode 100644 index 4eb69db1..00000000 --- a/PAWS/Schedule/PAWSTaskContext.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// This source file is part of the PAWS application based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Foundation -import ModelsR4 - - -/// The context attached to each task in the PAWS. -/// -/// We currently only support `Questionnaire`s, more cases can be added in the future. -enum PAWSTaskContext: Codable, Identifiable { - /// The task should display a `Questionnaire`. - case questionnaire(Questionnaire) - /// The task is used for UI testing. - case test(String) - - - var id: FHIRPrimitive? { - switch self { - case let .questionnaire(questionnaire): - return questionnaire.id - case .test: - return FHIRPrimitive(FHIRString(UUID().uuidString)) - } - } - - var actionType: LocalizedStringResource { - switch self { - case .questionnaire: - return LocalizedStringResource("TASK_CONTEXT_ACTION_QUESTIONNAIRE") - case .test: - return LocalizedStringResource("TASK_CONTEXT_ACTION_TEST") - } - } -} diff --git a/PAWS/Schedule/ScheduleView.swift b/PAWS/Schedule/ScheduleView.swift deleted file mode 100644 index 9516deb7..00000000 --- a/PAWS/Schedule/ScheduleView.swift +++ /dev/null @@ -1,131 +0,0 @@ -// -// This source file is part of the PAWS application based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SpeziAccount -import SpeziQuestionnaire -import SpeziScheduler -import SwiftUI - - -struct ScheduleView: View { - @Environment(PAWSStandard.self) private var standard - @Environment(PAWSScheduler.self) private var scheduler - @State private var eventContextsByDate: [Date: [EventContext]] = [:] - @State private var presentedContext: EventContext? - - - @Binding private var presentingAccount: Bool - - - private var startOfDays: [Date] { - Array(eventContextsByDate.keys) - } - - - var body: some View { - NavigationStack { - List(startOfDays, id: \.timeIntervalSinceNow) { startOfDay in - Section(format(startOfDay: startOfDay)) { - ForEach(eventContextsByDate[startOfDay] ?? [], id: \.event) { eventContext in - EventContextView(eventContext: eventContext) - .onTapGesture { - if !eventContext.event.complete { - presentedContext = eventContext - } - } - } - } - } - .onChange(of: scheduler) { - calculateEventContextsByDate() - } - .task { - calculateEventContextsByDate() - } - .sheet(item: $presentedContext) { presentedContext in - destination(withContext: presentedContext) - } - .toolbar { - if AccountButton.shouldDisplay { - AccountButton(isPresented: $presentingAccount) - } - } - .navigationTitle("SCHEDULE_LIST_TITLE") - } - } - - - init(presentingAccount: Binding) { - self._presentingAccount = presentingAccount - } - - - private func destination(withContext eventContext: EventContext) -> some View { - @ViewBuilder var destination: some View { - switch eventContext.task.context { - case let .questionnaire(questionnaire): - QuestionnaireView(questionnaire: questionnaire) { result in - presentedContext = nil - - guard case let .completed(response) = result else { - return // user cancelled the task - } - - eventContext.event.complete(true) - await standard.add(response: response) - } - case let .test(string): - ModalView(text: string, buttonText: String(localized: "CLOSE")) { - await eventContext.event.complete(true) - } - } - } - return destination - } - - - private func format(startOfDay: Date) -> String { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .long - dateFormatter.timeStyle = .none - return dateFormatter.string(from: startOfDay) - } - - private func calculateEventContextsByDate() { - let eventContexts = scheduler.tasks.flatMap { task in - task - .events( - from: Calendar.current.startOfDay(for: .now), - to: .numberOfEventsOrEndDate(100, .now) - ) - .map { event in - EventContext(event: event, task: task) - } - } - .sorted() - - let newEventContextsByDate = Dictionary(grouping: eventContexts) { eventContext in - Calendar.current.startOfDay(for: eventContext.event.scheduledAt) - } - - eventContextsByDate = newEventContextsByDate - } -} - - -#if DEBUG -#Preview("ScheduleView") { - ScheduleView(presentingAccount: .constant(false)) - .previewWith(standard: PAWSStandard()) { - PAWSScheduler() - AccountConfiguration { - MockUserIdPasswordAccountService() - } - } -} -#endif diff --git a/PAWS/SharedContext/PAWSCard.swift b/PAWS/SharedContext/PAWSCard.swift index 6eb54cd7..77bbcf24 100644 --- a/PAWS/SharedContext/PAWSCard.swift +++ b/PAWS/SharedContext/PAWSCard.swift @@ -47,6 +47,6 @@ struct PAWSCard: View { #Preview { PAWSCard { - Text("Test ...") + Text(verbatim: "This is an example content ...") } } diff --git a/PAWS/SharedContext/StorageKeys.swift b/PAWS/SharedContext/StorageKeys.swift index 16834bde..6758e0d6 100644 --- a/PAWS/SharedContext/StorageKeys.swift +++ b/PAWS/SharedContext/StorageKeys.swift @@ -18,4 +18,9 @@ enum StorageKeys { // MARK: - Home /// The currently selected home tab. static let homeTabSelection = "home.tabselection" + + + // MARK: - HealthKit + /// Start date of the HealthKit data collection + static let healthKitStartDate = "healthkit.startdate" } From d06338841488f4c76b2eef3bda5c64f3462dc560 Mon Sep 17 00:00:00 2001 From: Paul Schmiedmayer Date: Tue, 30 Jan 2024 23:31:03 -0800 Subject: [PATCH 3/6] Update License --- .../AppIcon.appiconset/Contents.json.license | 6 ++++++ .../AppIcon.appiconset/paws-logo.png.license | 6 ++++++ .../AydinSquarePhoto.imageset/AydinSquarePhoto.png.license | 6 ++++++ .../AydinSquarePhoto.imageset/Contents.json.license | 6 ++++++ .../NotificationImage.imageset/Contents.json.license | 6 ++++++ .../NotificationImage.png.license | 6 ++++++ .../ScottSquarePhoto.imageset/Contents.json.license | 6 ++++++ .../ScottSquarePhoto.imageset/ScottSquarePhoto.png.license | 6 ++++++ 8 files changed, 48 insertions(+) create mode 100644 PAWS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json.license create mode 100644 PAWS/Resources/Assets.xcassets/AppIcon.appiconset/paws-logo.png.license create mode 100644 PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png.license create mode 100644 PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json.license create mode 100644 PAWS/Resources/Assets.xcassets/NotificationImage.imageset/Contents.json.license create mode 100644 PAWS/Resources/Assets.xcassets/NotificationImage.imageset/NotificationImage.png.license create mode 100644 PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json.license create mode 100644 PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png.license diff --git a/PAWS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json.license b/PAWS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Resources/Assets.xcassets/AppIcon.appiconset/paws-logo.png.license b/PAWS/Resources/Assets.xcassets/AppIcon.appiconset/paws-logo.png.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/AppIcon.appiconset/paws-logo.png.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png.license b/PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/AydinSquarePhoto.png.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json.license b/PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/AydinSquarePhoto.imageset/Contents.json.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Resources/Assets.xcassets/NotificationImage.imageset/Contents.json.license b/PAWS/Resources/Assets.xcassets/NotificationImage.imageset/Contents.json.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/NotificationImage.imageset/Contents.json.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Resources/Assets.xcassets/NotificationImage.imageset/NotificationImage.png.license b/PAWS/Resources/Assets.xcassets/NotificationImage.imageset/NotificationImage.png.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/NotificationImage.imageset/NotificationImage.png.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json.license b/PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/Contents.json.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png.license b/PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png.license new file mode 100644 index 00000000..4f324d88 --- /dev/null +++ b/PAWS/Resources/Assets.xcassets/ScottSquarePhoto.imageset/ScottSquarePhoto.png.license @@ -0,0 +1,6 @@ + +This source file is part of the PAWS application based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT From 8f7f6aa5407fdee2854f71b0a5a5cdcdab4d39cf Mon Sep 17 00:00:00 2001 From: Paul Schmiedmayer Date: Tue, 30 Jan 2024 23:32:24 -0800 Subject: [PATCH 4/6] Update GitHub Action --- .github/workflows/beta-deployment.yml | 2 +- .github/workflows/build-and-test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/beta-deployment.yml b/.github/workflows/beta-deployment.yml index 89f94b9f..b5afb88a 100644 --- a/.github/workflows/beta-deployment.yml +++ b/.github/workflows/beta-deployment.yml @@ -24,7 +24,7 @@ jobs: iosapptestflightdeployment: name: iOS App TestFlight Deployment needs: buildandtest - uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2 + uses: CS342/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2 permissions: contents: read with: diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index e44a1956..4e80132c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -31,7 +31,7 @@ jobs: contents: read buildandtest: name: Build and Test - uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2 + uses: CS342/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2 permissions: contents: read with: From 8e3dae6799927f120093d98df7ca13b389fee25e Mon Sep 17 00:00:00 2001 From: Paul Schmiedmayer Date: Tue, 30 Jan 2024 23:56:04 -0800 Subject: [PATCH 5/6] Update Setup --- CONTRIBUTORS.md | 12 +- PAWS.xcodeproj/project.pbxproj | 45 +- .../xcshareddata/swiftpm/Package.resolved | 27 -- PAWS/PAWSStandard.swift | 1 - .../Resources/SocialSupportQuestionnaire.json | 387 ------------------ PAWS/SharedContext/FeatureFlags.swift | 2 - PAWSUITests/ContactsTests.swift | 39 -- PAWSUITests/ContributionsTest.swift | 41 -- ...ockUploadTests.swift => LaunchTests.swift} | 12 +- PAWSUITests/OnboardingTests.swift | 227 ---------- PAWSUITests/SchedulerTests.swift | 36 -- 11 files changed, 19 insertions(+), 810 deletions(-) delete mode 100644 PAWS/Resources/SocialSupportQuestionnaire.json delete mode 100644 PAWSUITests/ContactsTests.swift delete mode 100644 PAWSUITests/ContributionsTest.swift rename PAWSUITests/{MockUploadTests.swift => LaunchTests.swift} (53%) delete mode 100644 PAWSUITests/OnboardingTests.swift delete mode 100644 PAWSUITests/SchedulerTests.swift diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 6f474450..a5943558 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -14,4 +14,14 @@ PAWS Contributors * [Paul Schmiedmayer](https://github.com/PSchmiedmayer) * [Andreas Bauer](https://github.com/Supereg) * [Philipp Zagar](https://github.com/philippzagar) -* [Nikolai Madlener](https://github.com/NikolaiMadlener) \ No newline at end of file +* [Nikolai Madlener](https://github.com/NikolaiMadlener) +* [Caitlin Kunchur](https://github.com/ckunchur) +* [Shriya Reddy](https://github.com/reddyrose) +* [Riya Karumanchi](https://github.com/riyakaru) +* [Vishnu Ravi](https://github.com/vishnuravi) +* [Ananya Vasireddy](https://github.com/ananya-vasireddy) +* [Abhinav Agarwal](https://github.com/abhinav30219) +* [Raghav Samavedam](https://github.com/RSamavedam) +* [Aydin Zahedivash](http://github.com/aydinzahedi) +* [Scott Randolph Ceresnak](https://med.stanford.edu/profiles/scott-ceresnak) +* [Oliver Aalami](http://github.com/aalami5) \ No newline at end of file diff --git a/PAWS.xcodeproj/project.pbxproj b/PAWS.xcodeproj/project.pbxproj index 63d4b680..05b081f6 100644 --- a/PAWS.xcodeproj/project.pbxproj +++ b/PAWS.xcodeproj/project.pbxproj @@ -7,12 +7,10 @@ objects = { /* Begin PBXBuildFile section */ - 2F1B52CE2A4F5CCE003AE151 /* MockUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F1B52CD2A4F5CCE003AE151 /* MockUploadTests.swift */; }; 2F3D4ABC2A4E7C290068FB2F /* SpeziScheduler in Frameworks */ = {isa = PBXBuildFile; productRef = 2F3D4ABB2A4E7C290068FB2F /* SpeziScheduler */; }; 2F49B7762980407C00BCB272 /* Spezi in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7752980407B00BCB272 /* Spezi */; }; - 2F4E237E2989A2FE0013F3D9 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */; }; + 2F4E237E2989A2FE0013F3D9 /* LaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E237D2989A2FE0013F3D9 /* LaunchTests.swift */; }; 2F4E23832989D51F0013F3D9 /* PAWSTestingSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E23822989D51F0013F3D9 /* PAWSTestingSetup.swift */; }; - 2F4E23872989DB360013F3D9 /* ContactsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E23862989DB360013F3D9 /* ContactsTests.swift */; }; 2F4FC8D729EE69D300BFFE26 /* MockUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4FC8D629EE69D300BFFE26 /* MockUpload.swift */; }; 2F5E32BD297E05EA003432F8 /* PAWSDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F5E32BC297E05EA003432F8 /* PAWSDelegate.swift */; }; 2F6025CB29BBE70F0045459E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2F6025CA29BBE70F0045459E /* GoogleService-Info.plist */; }; @@ -23,7 +21,6 @@ 2FB099B32A875DF100B20952 /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 2FB099B22A875DF100B20952 /* FirebaseFirestoreSwift */; }; 2FB099B62A875E2B00B20952 /* HealthKitOnFHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 2FB099B52A875E2B00B20952 /* HealthKitOnFHIR */; }; 2FBD738C2A3BD150004228E7 /* SpeziScheduler in Frameworks */ = {isa = PBXBuildFile; productRef = 2FBD738B2A3BD150004228E7 /* SpeziScheduler */; }; - 2FC3439029EE6346002D773C /* SocialSupportQuestionnaire.json in Resources */ = {isa = PBXBuildFile; fileRef = 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */; }; 2FC3439229EE634B002D773C /* ConsentDocument.md in Resources */ = {isa = PBXBuildFile; fileRef = 2FE5DC2C29EDD78E004B9AB4 /* ConsentDocument.md */; }; 2FC975A82978F11A00BA99FE /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FC975A72978F11A00BA99FE /* Home.swift */; }; 2FCC1DBE2B6A1C9000C686BE /* ECGRecordingsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FCC1DBD2B6A1C9000C686BE /* ECGRecordingsList.swift */; }; @@ -49,7 +46,6 @@ 2FE5DC7529EDD8E6004B9AB4 /* SpeziFirebaseAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC7429EDD8E6004B9AB4 /* SpeziFirebaseAccount */; }; 2FE5DC7729EDD8E6004B9AB4 /* SpeziFirebaseConfiguration in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC7629EDD8E6004B9AB4 /* SpeziFirebaseConfiguration */; }; 2FE5DC7929EDD8E6004B9AB4 /* SpeziFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC7829EDD8E6004B9AB4 /* SpeziFirestore */; }; - 2FE5DC8429EDD934004B9AB4 /* SpeziQuestionnaire in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8329EDD934004B9AB4 /* SpeziQuestionnaire */; }; 2FE5DC8A29EDD972004B9AB4 /* SpeziLocalStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8929EDD972004B9AB4 /* SpeziLocalStorage */; }; 2FE5DC8C29EDD972004B9AB4 /* SpeziSecureStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8B29EDD972004B9AB4 /* SpeziSecureStorage */; }; 2FE5DC8F29EDD980004B9AB4 /* SpeziViews in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8E29EDD980004B9AB4 /* SpeziViews */; }; @@ -66,12 +62,10 @@ 566155292AB8447C00209B80 /* Package+LicenseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566155282AB8447C00209B80 /* Package+LicenseType.swift */; }; 5661552E2AB854C000209B80 /* PackageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5661552D2AB854C000209B80 /* PackageHelper.swift */; }; 5680DD392AB8983D004E6D4A /* PackageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5680DD382AB8983D004E6D4A /* PackageCell.swift */; }; - 5680DD3E2AB8CD84004E6D4A /* ContributionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */; }; 56F6F2A02AB441930022FE5A /* ContributionsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56F6F29F2AB441930022FE5A /* ContributionsList.swift */; }; 653A2551283387FE005D4D48 /* PAWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A2550283387FE005D4D48 /* PAWS.swift */; }; 653A255528338800005D4D48 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 653A255428338800005D4D48 /* Assets.xcassets */; }; 653A256228338800005D4D48 /* PAWSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A256128338800005D4D48 /* PAWSTests.swift */; }; - 653A256C28338800005D4D48 /* SchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A256B28338800005D4D48 /* SchedulerTests.swift */; }; 9733CFC62A8066DE001B7ABC /* SpeziOnboarding in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8029EDD91D004B9AB4 /* SpeziOnboarding */; }; 9739A0C62AD7B5730084BEA5 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 9739A0C52AD7B5730084BEA5 /* FirebaseStorage */; }; 97D73D6A2AD860AD00B47FA0 /* SpeziFirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */; }; @@ -99,10 +93,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 2F1B52CD2A4F5CCE003AE151 /* MockUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUploadTests.swift; sourceTree = ""; }; - 2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = ""; }; + 2F4E237D2989A2FE0013F3D9 /* LaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTests.swift; sourceTree = ""; }; 2F4E23822989D51F0013F3D9 /* PAWSTestingSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSTestingSetup.swift; sourceTree = ""; }; - 2F4E23862989DB360013F3D9 /* ContactsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsTests.swift; sourceTree = ""; }; 2F4FC8D629EE69D300BFFE26 /* MockUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUpload.swift; sourceTree = ""; }; 2F5E32BC297E05EA003432F8 /* PAWSDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSDelegate.swift; sourceTree = ""; }; 2F6025CA29BBE70F0045459E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; @@ -129,7 +121,6 @@ 2FE5DC4329EDD7F2004B9AB4 /* Bundle+Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Image.swift"; sourceTree = ""; }; 2FE5DC4429EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CodableArray+RawRepresentable.swift"; sourceTree = ""; }; 2FE5DC4B29EDD7FA004B9AB4 /* PAWSTaskContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PAWSTaskContext.swift; sourceTree = ""; }; - 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SocialSupportQuestionnaire.json; sourceTree = ""; }; 2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountOnboarding.swift; sourceTree = ""; }; 2FF53D8C2A8729D600042B76 /* PAWSStandard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PAWSStandard.swift; sourceTree = ""; }; 2FFD22F52B59ABE2005DD268 /* PAWSCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSCard.swift; sourceTree = ""; }; @@ -139,7 +130,6 @@ 566155282AB8447C00209B80 /* Package+LicenseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Package+LicenseType.swift"; sourceTree = ""; }; 5661552D2AB854C000209B80 /* PackageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageHelper.swift; sourceTree = ""; }; 5680DD382AB8983D004E6D4A /* PackageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageCell.swift; sourceTree = ""; }; - 5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributionsTest.swift; sourceTree = ""; }; 56F6F29F2AB441930022FE5A /* ContributionsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributionsList.swift; sourceTree = ""; }; 653A254D283387FE005D4D48 /* PAWS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PAWS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 653A2550283387FE005D4D48 /* PAWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWS.swift; sourceTree = ""; }; @@ -147,7 +137,6 @@ 653A255D28338800005D4D48 /* PAWSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PAWSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 653A256128338800005D4D48 /* PAWSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PAWSTests.swift; sourceTree = ""; }; 653A256728338800005D4D48 /* PAWSUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PAWSUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 653A256B28338800005D4D48 /* SchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchedulerTests.swift; sourceTree = ""; }; 653A258928339462005D4D48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSetupHeader.swift; sourceTree = ""; }; A9DFE8A82ABE551400428242 /* AccountButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountButton.swift; sourceTree = ""; }; @@ -164,7 +153,6 @@ 2FB099AF2A875DF100B20952 /* FirebaseAuth in Frameworks */, 97D73D6A2AD860AD00B47FA0 /* SpeziFirebaseStorage in Frameworks */, 2FE5DC6729EDD894004B9AB4 /* SpeziContact in Frameworks */, - 2FE5DC8429EDD934004B9AB4 /* SpeziQuestionnaire in Frameworks */, 2FB099B32A875DF100B20952 /* FirebaseFirestoreSwift in Frameworks */, 5661551D2AB8384200209B80 /* SwiftPackageList in Frameworks */, 2FB099B12A875DF100B20952 /* FirebaseFirestore in Frameworks */, @@ -260,7 +248,6 @@ 653A255428338800005D4D48 /* Assets.xcassets */, 2FA0BFEC2ACC977500E0EF83 /* Localizable.xcstrings */, 2FE5DC2C29EDD78E004B9AB4 /* ConsentDocument.md */, - 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */, ); path = Resources; sourceTree = ""; @@ -373,11 +360,7 @@ 653A256A28338800005D4D48 /* PAWSUITests */ = { isa = PBXGroup; children = ( - 2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */, - 653A256B28338800005D4D48 /* SchedulerTests.swift */, - 2F4E23862989DB360013F3D9 /* ContactsTests.swift */, - 2F1B52CD2A4F5CCE003AE151 /* MockUploadTests.swift */, - 5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */, + 2F4E237D2989A2FE0013F3D9 /* LaunchTests.swift */, ); path = PAWSUITests; sourceTree = ""; @@ -425,7 +408,6 @@ 2FE5DC7429EDD8E6004B9AB4 /* SpeziFirebaseAccount */, 2FE5DC7629EDD8E6004B9AB4 /* SpeziFirebaseConfiguration */, 2FE5DC7829EDD8E6004B9AB4 /* SpeziFirestore */, - 2FE5DC8329EDD934004B9AB4 /* SpeziQuestionnaire */, 2FE5DC8929EDD972004B9AB4 /* SpeziLocalStorage */, 2FE5DC8B29EDD972004B9AB4 /* SpeziSecureStorage */, 2FE5DC8E29EDD980004B9AB4 /* SpeziViews */, @@ -524,7 +506,6 @@ 2FE5DC6529EDD894004B9AB4 /* XCRemoteSwiftPackageReference "SpeziContact" */, 2FE5DC7029EDD8D3004B9AB4 /* XCRemoteSwiftPackageReference "SpeziHealthKit" */, 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */, - 2FE5DC8229EDD934004B9AB4 /* XCRemoteSwiftPackageReference "SpeziQuestionnaire" */, 2FE5DC8829EDD972004B9AB4 /* XCRemoteSwiftPackageReference "SpeziStorage" */, 2FE5DC8D29EDD980004B9AB4 /* XCRemoteSwiftPackageReference "SpeziViews" */, 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, @@ -555,7 +536,6 @@ files = ( 2FC3439229EE634B002D773C /* ConsentDocument.md in Resources */, 653A255528338800005D4D48 /* Assets.xcassets in Resources */, - 2FC3439029EE6346002D773C /* SocialSupportQuestionnaire.json in Resources */, 2FA0BFED2ACC977500E0EF83 /* Localizable.xcstrings in Resources */, 2F6025CB29BBE70F0045459E /* GoogleService-Info.plist in Resources */, ); @@ -633,11 +613,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5680DD3E2AB8CD84004E6D4A /* ContributionsTest.swift in Sources */, - 2F4E23872989DB360013F3D9 /* ContactsTests.swift in Sources */, - 2F4E237E2989A2FE0013F3D9 /* OnboardingTests.swift in Sources */, - 2F1B52CE2A4F5CCE003AE151 /* MockUploadTests.swift in Sources */, - 653A256C28338800005D4D48 /* SchedulerTests.swift in Sources */, + 2F4E237E2989A2FE0013F3D9 /* LaunchTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1216,14 +1192,6 @@ minimumVersion = 1.0.0; }; }; - 2FE5DC8229EDD934004B9AB4 /* XCRemoteSwiftPackageReference "SpeziQuestionnaire" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/StanfordSpezi/SpeziQuestionnaire.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; - }; - }; 2FE5DC8829EDD972004B9AB4 /* XCRemoteSwiftPackageReference "SpeziStorage" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/StanfordSpezi/SpeziStorage.git"; @@ -1365,11 +1333,6 @@ package = 97F466E62A76BBEE005DC9B4 /* XCRemoteSwiftPackageReference "SpeziOnboarding" */; productName = SpeziOnboarding; }; - 2FE5DC8329EDD934004B9AB4 /* SpeziQuestionnaire */ = { - isa = XCSwiftPackageProductDependency; - package = 2FE5DC8229EDD934004B9AB4 /* XCRemoteSwiftPackageReference "SpeziQuestionnaire" */; - productName = SpeziQuestionnaire; - }; 2FE5DC8929EDD972004B9AB4 /* SpeziLocalStorage */ = { isa = XCSwiftPackageProductDependency; package = 2FE5DC8829EDD972004B9AB4 /* XCRemoteSwiftPackageReference "SpeziStorage" */; diff --git a/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6e8a9546..b21a4c1c 100644 --- a/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/PAWS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -144,24 +144,6 @@ "version" : "2.3.1" } }, - { - "identity" : "researchkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordBDHG/ResearchKit", - "state" : { - "revision" : "209164ed20592a2213c4bd69cefcb078d9de0692", - "version" : "2.2.21" - } - }, - { - "identity" : "researchkitonfhir", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordBDHG/ResearchKitOnFHIR", - "state" : { - "revision" : "ea4d9691591594177e7dfbc8c246324855d73eb5", - "version" : "1.0.1" - } - }, { "identity" : "sourcekitten", "kind" : "remoteSourceControl", @@ -243,15 +225,6 @@ "version" : "1.0.0" } }, - { - "identity" : "speziquestionnaire", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziQuestionnaire.git", - "state" : { - "revision" : "930a4099db1aca9db0b6ed4e77687141c4780052", - "version" : "1.0.0" - } - }, { "identity" : "spezischeduler", "kind" : "remoteSourceControl", diff --git a/PAWS/PAWSStandard.swift b/PAWS/PAWSStandard.swift index ba106021..51a5c347 100644 --- a/PAWS/PAWSStandard.swift +++ b/PAWS/PAWSStandard.swift @@ -18,7 +18,6 @@ import SpeziFirestore import SpeziHealthKit import SpeziMockWebService import SpeziOnboarding -import SpeziQuestionnaire import SwiftUI diff --git a/PAWS/Resources/SocialSupportQuestionnaire.json b/PAWS/Resources/SocialSupportQuestionnaire.json deleted file mode 100644 index d3c584df..00000000 --- a/PAWS/Resources/SocialSupportQuestionnaire.json +++ /dev/null @@ -1,387 +0,0 @@ -{ - "resourceType": "Questionnaire", - "language": "en-US", - "id": "socialsupport", - "name": "SocialSupport", - "title": "Social Support", - "description": "This survey measures tangible social support plus a couple of demographic questions.", - "version": "1", - "status": "draft", - "publisher": "RAND Corp", - "meta": { - "profile": [ - "http://spezi.stanford.edu/fhir/StructureDefinition/sdf-Questionnaire" - ], - "tag": [ - { - "system": "urn:ietf:bcp:47", - "code": "en-US", - "display": "English" - } - ] - }, - "useContext": [ - { - "code": { - "system": "http://hl7.org/fhir/ValueSet/usage-context-type", - "code": "focus", - "display": "Clinical Focus" - }, - "valueCodeableConcept": { - "coding": [ - { - "system": "urn:oid:2.16.578.1.12.4.1.1.8655", - "display": "Social Support" - } - ] - } - } - ], - "contact": [ - { - "name": "https://www.rand.org/health-care/surveys_tools/mos/social-support/survey-instrument.html" - } - ], - "subjectType": [ - "Patient" - ], - "purpose": "The RAND Medical Outcomes Social Support survey is a 4-item questionnaire that measures social support.", - "copyright": "RAND Corp surveys are open-source and free to use.", - "date": "2023-01-23T00:00:00-08:00", - "url": "http://spezi.stanford.edu/fhir/questionnaire/32f43c8e-93e9-4c70-97a0-e716f8030073", - "item": [ - { - "linkId": "dcea2683-9815-4505-b240-e75b502b29ef", - "type": "choice", - "text": "How often do you need someone to help you if you were confined to bed?", - "required": false, - "answerOption": [ - { - "valueCoding": { - "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", - "code": "1", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "None of the time" - } - }, - { - "valueCoding": { - "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", - "code": "2", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "A little of the time" - } - }, - { - "valueCoding": { - "id": "e32f7952-e280-48d7-9746-c13dbb26638f", - "code": "3", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Some of the time" - } - }, - { - "valueCoding": { - "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", - "code": "4", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Most of the time" - } - }, - { - "valueCoding": { - "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", - "code": "5", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "All of the time" - } - } - ] - }, - { - "linkId": "ce09d701-7b93-4150-defb-51825e05ade9", - "type": "choice", - "text": "How often do you need someone to take you to the doctor if you needed it?", - "required": false, - "answerOption": [ - { - "valueCoding": { - "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", - "code": "1", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "None of the time" - } - }, - { - "valueCoding": { - "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", - "code": "2", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "A little of the time" - } - }, - { - "valueCoding": { - "id": "e32f7952-e280-48d7-9746-c13dbb26638f", - "code": "3", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Some of the time" - } - }, - { - "valueCoding": { - "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", - "code": "4", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Most of the time" - } - }, - { - "valueCoding": { - "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", - "code": "5", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "All of the time" - } - } - ] - }, - { - "linkId": "58e97564-5f4d-4d4b-86d5-6429cbbc7a8e", - "type": "choice", - "text": "How often do you need someone to prepare your meals if you were unable to do it yourself?", - "required": false, - "answerOption": [ - { - "valueCoding": { - "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", - "code": "1", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "None of the time" - } - }, - { - "valueCoding": { - "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", - "code": "2", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "A little of the time" - } - }, - { - "valueCoding": { - "id": "e32f7952-e280-48d7-9746-c13dbb26638f", - "code": "3", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Some of the time" - } - }, - { - "valueCoding": { - "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", - "code": "4", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Most of the time" - } - }, - { - "valueCoding": { - "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", - "code": "5", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "All of the time" - } - } - ] - }, - { - "linkId": "ad161c49-e8a6-4d31-90e8-02b2887a765f", - "type": "choice", - "text": "How often do you need someone to help with daily chores if you were sick", - "required": false, - "answerOption": [ - { - "valueCoding": { - "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", - "code": "1", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "None of the time" - } - }, - { - "valueCoding": { - "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", - "code": "2", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "A little of the time" - } - }, - { - "valueCoding": { - "id": "e32f7952-e280-48d7-9746-c13dbb26638f", - "code": "3", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Some of the time" - } - }, - { - "valueCoding": { - "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", - "code": "4", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Most of the time" - } - }, - { - "valueCoding": { - "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", - "code": "5", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "All of the time" - } - } - ] - }, - { - "linkId": "ba518851-2843-4bbd-c0f7-5b5692d542e0", - "type": "integer", - "text": "What is your age?", - "extension": [ - { - "url": "http://hl7.org/fhir/StructureDefinition/minValue", - "valueInteger": 18 - }, - { - "url": "http://hl7.org/fhir/StructureDefinition/maxValue", - "valueInteger": 120 - }, - { - "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", - "valueString": "Please enter a valid age." - } - ], - "required": false - }, - { - "linkId": "695525f3-3e89-4455-8e25-878171c596da", - "type": "choice", - "text": "What is your preferred contact method?", - "required": false, - "answerOption": [ - { - "valueCoding": { - "id": "b7a3d7a5-52b9-49b1-8b59-7a3885483f1c", - "code": "phone-call", - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "display": "Phone call" - } - }, - { - "valueCoding": { - "id": "3d42dde0-8e60-4832-bd46-bd06de28cbf2", - "code": "text-message", - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "display": "Text message" - } - }, - { - "valueCoding": { - "id": "e672cfc6-118f-4a2d-aafd-02722ff876b9", - "code": "e-mail", - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "display": "E-mail" - } - } - ] - }, - { - "linkId": "c3bea33d-4c50-4f4a-8ae4-1a52be326b19", - "type": "string", - "text": "What is your phone number? Ex. (555) 555-5555", - "required": false, - "enableWhen": [ - { - "question": "695525f3-3e89-4455-8e25-878171c596da", - "operator": "=", - "answerCoding": { - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "code": "phone-call" - } - } - ], - "extension": [ - { - "url": "http://hl7.org/fhir/StructureDefinition/regex", - "valueString": "^(\\([0-9]{3}\\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$" - }, - { - "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", - "valueString": "Please enter a valid phone number." - } - ] - }, - { - "linkId": "8e906a39-5fd0-42a8-f42c-bd96d719dd13", - "type": "string", - "text": "What is your text number? Ex. (555) 555-5555", - "required": false, - "enableWhen": [ - { - "question": "695525f3-3e89-4455-8e25-878171c596da", - "operator": "=", - "answerCoding": { - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "code": "text-message" - } - } - ], - "extension": [ - { - "url": "http://hl7.org/fhir/StructureDefinition/regex", - "valueString": "^(\\([0-9]{3}\\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$" - }, - { - "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", - "valueString": "Please enter a valid phone number." - } - ] - }, - { - "linkId": "86290b0a-017e-4193-8707-dc0c2146f0eb", - "type": "string", - "text": "What is your e-mail?", - "extension": [ - { - "url": "http://hl7.org/fhir/StructureDefinition/regex", - "valueString": ".*@.+" - }, - { - "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", - "valueString": "Please enter a valid email" - }, - { - "url": "http://hl7.org/fhir/StructureDefinition/minLength", - "valueInteger": 1 - } - ], - "required": false, - "maxLength": 50, - "enableWhen": [ - { - "question": "695525f3-3e89-4455-8e25-878171c596da", - "operator": "=", - "answerCoding": { - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "code": "e-mail" - } - } - ] - }, - { - "linkId": "305f5381-2d8b-4b98-bc04-5a39bee2f7ec", - "type": "display", - "text": "Thank you for taking the survey!", - "required": false - } - ] -} diff --git a/PAWS/SharedContext/FeatureFlags.swift b/PAWS/SharedContext/FeatureFlags.swift index 388d2cfb..9d8e0b4e 100644 --- a/PAWS/SharedContext/FeatureFlags.swift +++ b/PAWS/SharedContext/FeatureFlags.swift @@ -21,6 +21,4 @@ enum FeatureFlags { /// Defines if the application should connect to the local firebase emulator. Always set to true when using the iOS simulator. static let useFirebaseEmulator = CommandLine.arguments.contains("--useFirebaseEmulator") #endif - /// Adds a test task to the schedule at the current time - static let testSchedule = CommandLine.arguments.contains("--testSchedule") } diff --git a/PAWSUITests/ContactsTests.swift b/PAWSUITests/ContactsTests.swift deleted file mode 100644 index f32543d0..00000000 --- a/PAWSUITests/ContactsTests.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// This source file is part of the PAWS application based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import XCTest - - -class ContactsTests: XCTestCase { - override func setUpWithError() throws { - try super.setUpWithError() - - continueAfterFailure = false - - let app = XCUIApplication() - app.launchArguments = ["--skipOnboarding"] - app.launch() - } - - - func testContacts() throws { - let app = XCUIApplication() - - XCTAssertTrue(app.tabBars["Tab Bar"].buttons["Contacts"].waitForExistence(timeout: 2)) - app.tabBars["Tab Bar"].buttons["Contacts"].tap() - - XCTAssertTrue(app.staticTexts["Contact: Leland Stanford"].waitForExistence(timeout: 2)) - - XCTAssertTrue(app.buttons["Call"].waitForExistence(timeout: 2)) - XCTAssertTrue(app.buttons["Text"].waitForExistence(timeout: 2)) - XCTAssertTrue(app.buttons["Email"].waitForExistence(timeout: 2)) - XCTAssertTrue(app.buttons["Website"].waitForExistence(timeout: 2)) - - XCTAssertTrue(app.buttons["Address: 450 Serra Mall\nStanford CA 94305\nUSA"].waitForExistence(timeout: 2)) - } -} diff --git a/PAWSUITests/ContributionsTest.swift b/PAWSUITests/ContributionsTest.swift deleted file mode 100644 index a1e5c2d8..00000000 --- a/PAWSUITests/ContributionsTest.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// This source file is part of the PAWS application based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import XCTest - - -final class ContributionsTest: XCTestCase { - override func setUpWithError() throws { - try super.setUpWithError() - - try disablePasswordAutofill() - - continueAfterFailure = false - - let app = XCUIApplication() - app.launchArguments = ["--showOnboarding"] - app.deleteAndLaunch(withSpringboardAppName: "PAWS") - } - - func testLicenseInformationPage() throws { - let app = XCUIApplication() - - // complete onboarding so user is logged in - try app.conductOnboardingIfNeeded(email: "leland@contributions.stanford.edu") - - - XCTAssertTrue(app.buttons["Your Account"].waitForExistence(timeout: 6.0)) - app.buttons["Your Account"].tap() - - XCTAssertTrue(app.buttons["License Information"].waitForExistence(timeout: 2)) - app.buttons["License Information"].tap() - // Test if the sheet opens by checking if the title of the sheet is present - XCTAssertTrue(app.staticTexts["This project is licensed under the MIT License."].waitForExistence(timeout: 2)) - XCTAssertTrue(app.buttons["Repository Link"].waitForExistence(timeout: 2)) - } -} diff --git a/PAWSUITests/MockUploadTests.swift b/PAWSUITests/LaunchTests.swift similarity index 53% rename from PAWSUITests/MockUploadTests.swift rename to PAWSUITests/LaunchTests.swift index c12c029e..70b4ee8c 100644 --- a/PAWSUITests/MockUploadTests.swift +++ b/PAWSUITests/LaunchTests.swift @@ -9,24 +9,20 @@ import XCTest -class MockUploadTestsTests: XCTestCase { +class LaunchTests: XCTestCase { override func setUpWithError() throws { try super.setUpWithError() continueAfterFailure = false let app = XCUIApplication() - app.launchArguments = ["--skipOnboarding", "--disableFirebase"] + app.launchArguments = ["--skipOnboarding"] app.launch() } - func testMockUpload() throws { + func testApplicationLaunch() throws { let app = XCUIApplication() - - XCTAssertTrue(app.tabBars["Tab Bar"].buttons["Mock Web Service"].waitForExistence(timeout: 2)) - app.tabBars["Tab Bar"].buttons["Mock Web Service"].tap() - - XCTAssertTrue(app.staticTexts["Mock Requests"].waitForExistence(timeout: 2)) + XCTAssertEqual(app.state, .runningForeground) } } diff --git a/PAWSUITests/OnboardingTests.swift b/PAWSUITests/OnboardingTests.swift deleted file mode 100644 index 19355e12..00000000 --- a/PAWSUITests/OnboardingTests.swift +++ /dev/null @@ -1,227 +0,0 @@ -// -// This source file is part of the PAWS application based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import XCTest -import XCTestExtensions -import XCTHealthKit - - -class OnboardingTests: XCTestCase { - override func setUpWithError() throws { - try super.setUpWithError() - - try disablePasswordAutofill() - - continueAfterFailure = false - - let app = XCUIApplication() - app.launchArguments = ["--showOnboarding"] - app.deleteAndLaunch(withSpringboardAppName: "PAWS") - } - - - func testOnboardingFlow() throws { - let app = XCUIApplication() - let email = "leland@onboarding.stanford.edu" - - try app.navigateOnboardingFlow(email: email) - - app.assertOnboardingComplete() - try app.assertAccountInformation(email: email) - } - - func testOnboardingFlowRepeated() throws { - let app = XCUIApplication() - app.launchArguments = ["--showOnboarding", "--disableFirebase"] - app.terminate() - app.launch() - - try app.navigateOnboardingFlow() - app.assertOnboardingComplete() - - app.terminate() - - // Second onboarding round shouldn't display HealthKit and Notification authorizations anymore - app.activate() - - try app.navigateOnboardingFlow(repeated: true) - // Do not show HealthKit and Notification authorization view again - app.assertOnboardingComplete() - } -} - - -extension XCUIApplication { - func conductOnboardingIfNeeded(email: String = "leland@stanford.edu") throws { - let app = XCUIApplication() - - if app.staticTexts["Spezi\nPAWS"].waitForExistence(timeout: 5) { - try app.navigateOnboardingFlow(email: email) - } - } - - fileprivate func navigateOnboardingFlow( - email: String = "leland@stanford.edu", - repeated skippedIfRepeated: Bool = false - ) throws { - try navigateOnboardingFlowWelcome() - try navigateOnboardingFlowInterestingModules() - if staticTexts["Your Account"].waitForExistence(timeout: 5) { - try navigateOnboardingAccount(email: email) - } - if staticTexts["Consent"].waitForExistence(timeout: 5) { - try navigateOnboardingFlowConsent() - } - if !skippedIfRepeated { - try navigateOnboardingFlowHealthKitAccess() - try navigateOnboardingFlowNotification() - } - } - - private func navigateOnboardingFlowWelcome() throws { - XCTAssertTrue(staticTexts["Spezi\nPAWS"].waitForExistence(timeout: 5)) - - XCTAssertTrue(buttons["Learn More"].waitForExistence(timeout: 2)) - buttons["Learn More"].tap() - } - - private func navigateOnboardingFlowInterestingModules() throws { - XCTAssertTrue(staticTexts["Interesting Modules"].waitForExistence(timeout: 5)) - - for _ in 1..<4 { - XCTAssertTrue(buttons["Next"].waitForExistence(timeout: 2)) - buttons["Next"].tap() - } - - XCTAssertTrue(buttons["Next"].waitForExistence(timeout: 2)) - buttons["Next"].tap() - } - - private func navigateOnboardingAccount(email: String) throws { - guard !buttons["Next"].waitForExistence(timeout: 5) else { - buttons["Next"].tap() - return - } - - XCTAssertTrue(buttons["Signup"].waitForExistence(timeout: 2)) - buttons["Signup"].tap() - - XCTAssertTrue(staticTexts["Create a new Account"].waitForExistence(timeout: 2)) - - try collectionViews.textFields["E-Mail Address"].enter(value: email) - try collectionViews.secureTextFields["Password"].enter(value: "StanfordRocks") - try textFields["enter first name"].enter(value: "Leland") - try textFields["enter last name"].enter(value: "Stanford") - - XCTAssertTrue(collectionViews.buttons["Signup"].waitForExistence(timeout: 2)) - collectionViews.buttons["Signup"].tap() - - sleep(3) - - if staticTexts["HealthKit Access"].waitForExistence(timeout: 5) && navigationBars.buttons["Back"].waitForExistence(timeout: 5) { - navigationBars.buttons["Back"].tap() - - XCTAssertTrue(staticTexts["Leland Stanford"].waitForExistence(timeout: 2)) - XCTAssertTrue(staticTexts[email].waitForExistence(timeout: 2)) - - XCTAssertTrue(buttons["Next"].waitForExistence(timeout: 2)) - buttons["Next"].tap() - } - } - - private func navigateOnboardingFlowConsent() throws { - XCTAssertTrue(staticTexts["Consent"].waitForExistence(timeout: 5)) - - XCTAssertTrue(staticTexts["First Name"].waitForExistence(timeout: 2)) - try textFields["Enter your first name ..."].enter(value: "Leland") - - XCTAssertTrue(staticTexts["Last Name"].waitForExistence(timeout: 2)) - try textFields["Enter your last name ..."].enter(value: "Stanford") - - XCTAssertTrue(scrollViews["Signature Field"].waitForExistence(timeout: 2)) - scrollViews["Signature Field"].swipeRight() - - XCTAssertTrue(buttons["I Consent"].waitForExistence(timeout: 2)) - buttons["I Consent"].tap() - } - - private func navigateOnboardingFlowHealthKitAccess() throws { - XCTAssertTrue(staticTexts["HealthKit Access"].waitForExistence(timeout: 5)) - - XCTAssertTrue(buttons["Grant Access"].waitForExistence(timeout: 2)) - buttons["Grant Access"].tap() - - try handleHealthKitAuthorization() - } - - private func navigateOnboardingFlowNotification() throws { - XCTAssertTrue(staticTexts["Notifications"].waitForExistence(timeout: 5)) - - XCTAssertTrue(buttons["Allow Notifications"].waitForExistence(timeout: 2)) - buttons["Allow Notifications"].tap() - - let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") - let alertAllowButton = springboard.buttons["Allow"] - if alertAllowButton.waitForExistence(timeout: 5) { - alertAllowButton.tap() - } - } - - fileprivate func assertOnboardingComplete() { - let tabBar = tabBars["Tab Bar"] - XCTAssertTrue(tabBar.buttons["Schedule"].waitForExistence(timeout: 2)) - XCTAssertTrue(tabBar.buttons["Contacts"].waitForExistence(timeout: 2)) - } - - fileprivate func assertAccountInformation(email: String) throws { - XCTAssertTrue(navigationBars.buttons["Your Account"].waitForExistence(timeout: 2)) - navigationBars.buttons["Your Account"].tap() - - XCTAssertTrue(staticTexts["Account Overview"].waitForExistence(timeout: 5.0)) - XCTAssertTrue(staticTexts["Leland Stanford"].exists) - XCTAssertTrue(staticTexts[email].exists) - XCTAssertTrue(staticTexts["Gender Identity, Choose not to answer"].exists) - - - XCTAssertTrue(navigationBars.buttons["Close"].waitForExistence(timeout: 0.5)) - navigationBars.buttons["Close"].tap() - - XCTAssertTrue(navigationBars.buttons["Your Account"].waitForExistence(timeout: 2)) - navigationBars.buttons["Your Account"].tap() - - XCTAssertTrue(navigationBars.buttons["Edit"].waitForExistence(timeout: 2)) - navigationBars.buttons["Edit"].tap() - - usleep(500_00) - XCTAssertFalse(navigationBars.buttons["Close"].exists) - - XCTAssertTrue(buttons["Delete Account"].waitForExistence(timeout: 2)) - buttons["Delete Account"].tap() - - let alert = "Are you sure you want to delete your account?" - XCTAssertTrue(alerts[alert].waitForExistence(timeout: 6.0)) - alerts[alert].buttons["Delete"].tap() - - XCTAssertTrue(alerts["Authentication Required"].waitForExistence(timeout: 2.0)) - XCTAssertTrue(alerts["Authentication Required"].secureTextFields["Password"].waitForExistence(timeout: 0.5)) - typeText("StanfordRocks") // the password field has focus already - XCTAssertTrue(alerts["Authentication Required"].buttons["Login"].waitForExistence(timeout: 0.5)) - alerts["Authentication Required"].buttons["Login"].tap() - - sleep(2) - - // Login - try textFields["E-Mail Address"].enter(value: email) - try secureTextFields["Password"].enter(value: "StanfordRocks") - - XCTAssertTrue(buttons["Login"].waitForExistence(timeout: 0.5)) - buttons["Login"].tap() - - XCTAssertTrue(alerts["Invalid Credentials"].waitForExistence(timeout: 2.0)) - } -} diff --git a/PAWSUITests/SchedulerTests.swift b/PAWSUITests/SchedulerTests.swift deleted file mode 100644 index 6442f473..00000000 --- a/PAWSUITests/SchedulerTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// This source file is part of the PAWS application based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import XCTest -import XCTestExtensions - - -class SchedulerTests: XCTestCase { - override func setUpWithError() throws { - try super.setUpWithError() - - continueAfterFailure = false - - let app = XCUIApplication() - app.launchArguments = ["--skipOnboarding", "--testSchedule"] - app.deleteAndLaunch(withSpringboardAppName: "PAWS") - } - - - func testScheduler() throws { - let app = XCUIApplication() - - XCTAssertTrue(app.tabBars["Tab Bar"].buttons["Schedule"].waitForExistence(timeout: 2)) - app.tabBars["Tab Bar"].buttons["Schedule"].tap() - - XCTAssertTrue(app.staticTexts["Start Questionnaire"].waitForExistence(timeout: 2)) - app.staticTexts["Start Questionnaire"].tap() - - XCTAssertTrue(app.staticTexts["Social Support"].waitForExistence(timeout: 2)) - } -} From 869ab698a5dd0be04dd53a7a2222d00a18b15bb0 Mon Sep 17 00:00:00 2001 From: Paul Schmiedmayer Date: Wed, 31 Jan 2024 00:01:09 -0800 Subject: [PATCH 6/6] Update Setup --- fastlane/Fastfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 884fb0e0..8a6591c3 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -28,7 +28,7 @@ platform :ios do max_concurrent_simulators: 1, result_bundle: true, output_directory: ".", - xcargs: "-skipPackagePluginValidation" + xcargs: "-skipPackagePluginValidation -skipMacroValidation" ) end @@ -38,7 +38,7 @@ platform :ios do skip_archive: true, skip_codesigning: true, derived_data_path: ".derivedData", - xcargs: "-skipPackagePluginValidation" + xcargs: "-skipPackagePluginValidation -skipMacroValidation" ) end