diff --git a/StrokeCog.xcodeproj/project.pbxproj b/StrokeCog.xcodeproj/project.pbxproj new file mode 100644 index 0000000..32e5ab3 --- /dev/null +++ b/StrokeCog.xcodeproj/project.pbxproj @@ -0,0 +1,1422 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 27FA29902A388E9B009CAC45 /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FA298F2A388E9B009CAC45 /* ModalView.swift */; }; + 2F1AC9DF2B4E840E00C24973 /* StrokeCog.docc in Sources */ = {isa = PBXBuildFile; fileRef = 2F1AC9DE2B4E840E00C24973 /* StrokeCog.docc */; }; + 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 */; }; + 2F4E23832989D51F0013F3D9 /* StrokeCogTestingSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E23822989D51F0013F3D9 /* StrokeCogTestingSetup.swift */; }; + 2F4E23872989DB360013F3D9 /* ContactsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E23862989DB360013F3D9 /* ContactsTests.swift */; }; + 2F4FC8D729EE69D300BFFE26 /* MockUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4FC8D629EE69D300BFFE26 /* MockUpload.swift */; }; + 2F5E32BD297E05EA003432F8 /* StrokeCogDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F5E32BC297E05EA003432F8 /* StrokeCogDelegate.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 */; }; + 2FC3439129EE6349002D773C /* AppIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 2FE5DC2A29EDD78D004B9AB4 /* AppIcon.png */; }; + 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 /* StrokeCogTaskContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4B29EDD7FA004B9AB4 /* StrokeCogTaskContext.swift */; }; + 2FE5DC5229EDD7FA004B9AB4 /* StrokeCogScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4C29EDD7FA004B9AB4 /* StrokeCogScheduler.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 /* StrokeCogStandard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FF53D8C2A8729D600042B76 /* StrokeCogStandard.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 /* StrokeCog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A2550283387FE005D4D48 /* StrokeCog.swift */; }; + 653A255528338800005D4D48 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 653A255428338800005D4D48 /* Assets.xcassets */; }; + 653A256228338800005D4D48 /* StrokeCogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A256128338800005D4D48 /* StrokeCogTests.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 */; }; + A92E4DF02BAA001100AC8DE8 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = A92E4DEF2BAA001100AC8DE8 /* OrderedCollections */; }; + 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 */ + 653A255E28338800005D4D48 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 653A2545283387FE005D4D48 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 653A254C283387FE005D4D48; + remoteInfo = StrokeCog; + }; + 653A256828338800005D4D48 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 653A2545283387FE005D4D48 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 653A254C283387FE005D4D48; + remoteInfo = StrokeCog; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 27FA298F2A388E9B009CAC45 /* ModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalView.swift; sourceTree = ""; }; + 2F1AC9DE2B4E840E00C24973 /* StrokeCog.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = StrokeCog.docc; 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 /* StrokeCogTestingSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeCogTestingSetup.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 /* StrokeCogDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeCogDelegate.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 /* StrokeCog.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StrokeCog.entitlements; sourceTree = ""; }; + 2FC94CD4298B0A1D009C8209 /* StrokeCog.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = StrokeCog.xctestplan; 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 = ""; }; + 2FE5DC2A29EDD78D004B9AB4 /* AppIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AppIcon.png; 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 /* StrokeCogTaskContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrokeCogTaskContext.swift; sourceTree = ""; }; + 2FE5DC4C29EDD7FA004B9AB4 /* StrokeCogScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrokeCogScheduler.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 /* StrokeCogStandard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrokeCogStandard.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 /* StrokeCog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StrokeCog.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 653A2550283387FE005D4D48 /* StrokeCog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeCog.swift; sourceTree = ""; }; + 653A255428338800005D4D48 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 653A255D28338800005D4D48 /* StrokeCogTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StrokeCogTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 653A256128338800005D4D48 /* StrokeCogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeCogTests.swift; sourceTree = ""; }; + 653A256728338800005D4D48 /* StrokeCogUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StrokeCogUITests.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 */ + 653A254A283387FE005D4D48 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 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 */, + A92E4DF02BAA001100AC8DE8 /* OrderedCollections 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; + }; + 653A255A28338800005D4D48 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 653A256428338800005D4D48 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 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 /* StrokeCog.entitlements */, + 653A258928339462005D4D48 /* Info.plist */, + 2F6025CA29BBE70F0045459E /* GoogleService-Info.plist */, + 2F1AC9DE2B4E840E00C24973 /* StrokeCog.docc */, + ); + 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 */, + 2FE5DC2A29EDD78D004B9AB4 /* AppIcon.png */, + 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 /* StrokeCogScheduler.swift */, + 2FE5DC4B29EDD7FA004B9AB4 /* StrokeCogTaskContext.swift */, + 27FA298F2A388E9B009CAC45 /* ModalView.swift */, + ); + path = Schedule; + sourceTree = ""; + }; + 2FE5DC3C29EDD7DA004B9AB4 /* SharedContext */ = { + isa = PBXGroup; + children = ( + 2FE5DC3E29EDD7ED004B9AB4 /* FeatureFlags.swift */, + 2FE5DC3F29EDD7EE004B9AB4 /* StorageKeys.swift */, + ); + path = SharedContext; + sourceTree = ""; + }; + 2FE5DC3D29EDD7E4004B9AB4 /* Helper */ = { + isa = PBXGroup; + children = ( + 2FE5DC4229EDD7F2004B9AB4 /* Binding+Negate.swift */, + 2FE5DC4329EDD7F2004B9AB4 /* Bundle+Image.swift */, + 2FE5DC4429EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift */, + ); + path = Helper; + 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 /* StrokeCog.xctestplan */, + 653A254F283387FE005D4D48 /* StrokeCog */, + 653A256028338800005D4D48 /* StrokeCogTests */, + 653A256A28338800005D4D48 /* StrokeCogUITests */, + 653A254E283387FE005D4D48 /* Products */, + 653A258B283395A7005D4D48 /* Frameworks */, + ); + sourceTree = ""; + }; + 653A254E283387FE005D4D48 /* Products */ = { + isa = PBXGroup; + children = ( + 653A254D283387FE005D4D48 /* StrokeCog.app */, + 653A255D28338800005D4D48 /* StrokeCogTests.xctest */, + 653A256728338800005D4D48 /* StrokeCogUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 653A254F283387FE005D4D48 /* StrokeCog */ = { + isa = PBXGroup; + children = ( + 653A2550283387FE005D4D48 /* StrokeCog.swift */, + 2F5E32BC297E05EA003432F8 /* StrokeCogDelegate.swift */, + 2FF53D8C2A8729D600042B76 /* StrokeCogStandard.swift */, + 2F4E23822989D51F0013F3D9 /* StrokeCogTestingSetup.swift */, + 2FC975A72978F11A00BA99FE /* Home.swift */, + A9720E412ABB68B300872D23 /* Account */, + 2FE5DC2829EDD398004B9AB4 /* Onboarding */, + 2FE5DC3B29EDD7D0004B9AB4 /* Schedule */, + 2FE5DC2729EDD38D004B9AB4 /* Contacts */, + 56F6F29E2AB441640022FE5A /* Contributions */, + 2F4FC8D529EE69BE00BFFE26 /* MockUpload */, + 2FE5DC3C29EDD7DA004B9AB4 /* SharedContext */, + 2FE5DC3D29EDD7E4004B9AB4 /* Helper */, + 2FE5DC2D29EDD792004B9AB4 /* Resources */, + 2FC9759D2978E30800BA99FE /* Supporting Files */, + ); + path = StrokeCog; + sourceTree = ""; + }; + 653A256028338800005D4D48 /* StrokeCogTests */ = { + isa = PBXGroup; + children = ( + 653A256128338800005D4D48 /* StrokeCogTests.swift */, + ); + path = StrokeCogTests; + sourceTree = ""; + }; + 653A256A28338800005D4D48 /* StrokeCogUITests */ = { + isa = PBXGroup; + children = ( + 2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */, + 653A256B28338800005D4D48 /* SchedulerTests.swift */, + 2F4E23862989DB360013F3D9 /* ContactsTests.swift */, + 2F1B52CD2A4F5CCE003AE151 /* MockUploadTests.swift */, + 5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */, + ); + path = StrokeCogUITests; + sourceTree = ""; + }; + 653A258B283395A7005D4D48 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + 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 */ + 653A254C283387FE005D4D48 /* StrokeCog */ = { + isa = PBXNativeTarget; + buildConfigurationList = 653A257128338800005D4D48 /* Build configuration list for PBXNativeTarget "StrokeCog" */; + buildPhases = ( + 653A2549283387FE005D4D48 /* Sources */, + 653A254A283387FE005D4D48 /* Frameworks */, + 653A254B283387FE005D4D48 /* Resources */, + 2F5B528D29BD237B002020B7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 566155222AB83CF200209B80 /* PBXTargetDependency */, + ); + name = StrokeCog; + packageProductDependencies = ( + 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 */, + A92E4DEF2BAA001100AC8DE8 /* OrderedCollections */, + ); + productName = StrokeCog; + productReference = 653A254D283387FE005D4D48 /* StrokeCog.app */; + productType = "com.apple.product-type.application"; + }; + 653A255C28338800005D4D48 /* StrokeCogTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 653A257428338800005D4D48 /* Build configuration list for PBXNativeTarget "StrokeCogTests" */; + buildPhases = ( + 653A255928338800005D4D48 /* Sources */, + 653A255A28338800005D4D48 /* Frameworks */, + 653A255B28338800005D4D48 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 653A255F28338800005D4D48 /* PBXTargetDependency */, + ); + name = StrokeCogTests; + productName = StrokeCogTests; + productReference = 653A255D28338800005D4D48 /* StrokeCogTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 653A256628338800005D4D48 /* StrokeCogUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 653A257728338800005D4D48 /* Build configuration list for PBXNativeTarget "StrokeCogUITests" */; + buildPhases = ( + 653A256328338800005D4D48 /* Sources */, + 653A256428338800005D4D48 /* Frameworks */, + 653A256528338800005D4D48 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 653A256928338800005D4D48 /* PBXTargetDependency */, + ); + name = StrokeCogUITests; + packageProductDependencies = ( + 2FE5DC9829EDD9D9004B9AB4 /* XCTestExtensions */, + 2FE5DC9B29EDD9EF004B9AB4 /* XCTHealthKit */, + ); + productName = StrokeCogUITests; + productReference = 653A256728338800005D4D48 /* StrokeCogUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 653A2545283387FE005D4D48 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1340; + LastUpgradeCheck = 1520; + TargetAttributes = { + 653A254C283387FE005D4D48 = { + CreatedOnToolsVersion = 13.4; + }; + 653A255C28338800005D4D48 = { + CreatedOnToolsVersion = 13.4; + TestTargetID = 653A254C283387FE005D4D48; + }; + 653A256628338800005D4D48 = { + CreatedOnToolsVersion = 13.4; + TestTargetID = 653A254C283387FE005D4D48; + }; + }; + }; + buildConfigurationList = 653A2548283387FE005D4D48 /* Build configuration list for PBXProject "StrokeCog" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 653A2544283387FE005D4D48; + packageReferences = ( + 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" */, + A92E4DEE2BAA001100AC8DE8 /* XCRemoteSwiftPackageReference "swift-collections" */, + ); + productRefGroup = 653A254E283387FE005D4D48 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 653A254C283387FE005D4D48 /* StrokeCog */, + 653A255C28338800005D4D48 /* StrokeCogTests */, + 653A256628338800005D4D48 /* StrokeCogUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 653A254B283387FE005D4D48 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2FC3439229EE634B002D773C /* ConsentDocument.md in Resources */, + 2FC3439129EE6349002D773C /* AppIcon.png in Resources */, + 653A255528338800005D4D48 /* Assets.xcassets in Resources */, + 2FC3439029EE6346002D773C /* SocialSupportQuestionnaire.json in Resources */, + 2FA0BFED2ACC977500E0EF83 /* Localizable.xcstrings in Resources */, + 2F6025CB29BBE70F0045459E /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 653A255B28338800005D4D48 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 653A256528338800005D4D48 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* 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; + buildActionMask = 2147483647; + files = ( + 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 */, + 2FC975A82978F11A00BA99FE /* Home.swift in Sources */, + 2FE5DC4E29EDD7FA004B9AB4 /* ScheduleView.swift in Sources */, + A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */, + 2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */, + 2F1AC9DF2B4E840E00C24973 /* StrokeCog.docc in Sources */, + 2FF53D8D2A8729D600042B76 /* StrokeCogStandard.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 */, + 2F4E23832989D51F0013F3D9 /* StrokeCogTestingSetup.swift in Sources */, + 2FE5DC5329EDD7FA004B9AB4 /* Bundle+Questionnaire.swift in Sources */, + 2FE5DC5129EDD7FA004B9AB4 /* StrokeCogTaskContext.swift in Sources */, + 56F6F2A02AB441930022FE5A /* ContributionsList.swift in Sources */, + 566155292AB8447C00209B80 /* Package+LicenseType.swift in Sources */, + 5680DD392AB8983D004E6D4A /* PackageCell.swift in Sources */, + 2F5E32BD297E05EA003432F8 /* StrokeCogDelegate.swift in Sources */, + 2FE5DC5229EDD7FA004B9AB4 /* StrokeCogScheduler.swift in Sources */, + A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */, + 653A2551283387FE005D4D48 /* StrokeCog.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; + }; + 653A255928338800005D4D48 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 653A256228338800005D4D48 /* StrokeCogTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 653A256328338800005D4D48 /* Sources */ = { + 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 */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 566155222AB83CF200209B80 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = 566155212AB83CF200209B80 /* SwiftPackageListJSONPlugin */; + }; + 653A255F28338800005D4D48 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 653A254C283387FE005D4D48 /* StrokeCog */; + targetProxy = 653A255E28338800005D4D48 /* PBXContainerItemProxy */; + }; + 653A256928338800005D4D48 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 653A254C283387FE005D4D48 /* StrokeCog */; + targetProxy = 653A256828338800005D4D48 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 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"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + 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; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + 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 = TEST; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Test; + }; + 2FEE10312998C89C000822E1 /* Test */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "StrokeCog/Supporting Files/StrokeCog.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "StrokeCog/Supporting Files/Info.plist"; + 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 StrokeCog uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The StrokeCog 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_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UIStrokeCoglicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIStrokeCoglicationSupportsIndirectInputEvents = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.odden.strokecog; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + "SWIFT_ELicenseRef-StrokeCog_LOC_STRINGS" = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Test; + }; + 2FEE10322998C89C000822E1 /* Test */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 637867499T; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.odden.strokecog.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + "SWIFT_ELicenseRef-StrokeCog_LOC_STRINGS" = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StrokeCog.app/StrokeCog"; + }; + name = Test; + }; + 2FEE10332998C89C000822E1 /* Test */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 637867499T; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.odden.strokecoguitests; + PRODUCT_NAME = "$(TARGET_NAME)"; + "SWIFT_ELicenseRef-StrokeCog_LOC_STRINGS" = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = StrokeCog; + }; + name = Test; + }; + 653A256F28338800005D4D48 /* Debug */ = { + 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"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + 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; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + 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_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 653A257028338800005D4D48 /* Release */ = { + 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"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + 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; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 653A257228338800005D4D48 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "StrokeCog/Supporting Files/StrokeCog.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "StrokeCog/Supporting Files/Info.plist"; + 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 StrokeCog uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The StrokeCog 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_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UIStrokeCoglicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIStrokeCoglicationSupportsIndirectInputEvents = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.odden.strokecog; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + "SWIFT_ELicenseRef-StrokeCog_LOC_STRINGS" = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 653A257328338800005D4D48 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "StrokeCog/Supporting Files/StrokeCog.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 637867499T; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "StrokeCog/Supporting Files/Info.plist"; + 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 StrokeCog uses the step count to demonstrate Spezi's integration with HealthKit."; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "The StrokeCog 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_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_UIStrokeCoglicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIStrokeCoglicationSupportsIndirectInputEvents = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.odden.strokecog; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "StrokeCog"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + "SWIFT_ELicenseRef-StrokeCog_LOC_STRINGS" = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 653A257528338800005D4D48 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 637867499T; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.odden.strokecog.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + "SWIFT_ELicenseRef-StrokeCog_LOC_STRINGS" = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StrokeCog.app/StrokeCog"; + }; + name = Debug; + }; + 653A257628338800005D4D48 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 637867499T; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.odden.strokecog.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + "SWIFT_ELicenseRef-StrokeCog_LOC_STRINGS" = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StrokeCog.app/StrokeCog"; + }; + name = Release; + }; + 653A257828338800005D4D48 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 637867499T; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.odden.strokecoguitests; + PRODUCT_NAME = "$(TARGET_NAME)"; + "SWIFT_ELicenseRef-StrokeCog_LOC_STRINGS" = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = StrokeCog; + }; + name = Debug; + }; + 653A257928338800005D4D48 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 637867499T; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.odden.strokecoguitests; + PRODUCT_NAME = "$(TARGET_NAME)"; + "SWIFT_ELicenseRef-StrokeCog_LOC_STRINGS" = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = StrokeCog; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 653A2548283387FE005D4D48 /* Build configuration list for PBXProject "StrokeCog" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 653A256F28338800005D4D48 /* Debug */, + 2FEE10302998C89C000822E1 /* Test */, + 653A257028338800005D4D48 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 653A257128338800005D4D48 /* Build configuration list for PBXNativeTarget "StrokeCog" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 653A257228338800005D4D48 /* Debug */, + 2FEE10312998C89C000822E1 /* Test */, + 653A257328338800005D4D48 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 653A257428338800005D4D48 /* Build configuration list for PBXNativeTarget "StrokeCogTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 653A257528338800005D4D48 /* Debug */, + 2FEE10322998C89C000822E1 /* Test */, + 653A257628338800005D4D48 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 653A257728338800005D4D48 /* Build configuration list for PBXNativeTarget "StrokeCogUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 653A257828338800005D4D48 /* Debug */, + 2FEE10332998C89C000822E1 /* Test */, + 653A257928338800005D4D48 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 2F3D4ABA2A4E7C290068FB2F /* XCRemoteSwiftPackageReference "SpeziScheduler" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/SpeziScheduler.git"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 0.8.0; + }; + }; + 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "Spezi" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/Spezi"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.0; + }; + }; + 2FB099B42A875E2B00B20952 /* XCRemoteSwiftPackageReference "HealthKitOnFHIR" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordBDHG/HealthKitOnFHIR.git"; + requirement = { + kind = upToNextMinorVersion; + 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; + }; + }; + 2FE5DC7029EDD8D3004B9AB4 /* XCRemoteSwiftPackageReference "SpeziHealthKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordSpezi/SpeziHealthKit.git"; + requirement = { + kind = upToNextMinorVersion; + 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; + }; + }; + 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.17.0; + }; + }; + 2FE5DC9729EDD9D9004B9AB4 /* XCRemoteSwiftPackageReference "XCTestExtensions" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/StanfordBDHG/XCTestExtensions.git"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 0.4.8; + }; + }; + 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; + }; + }; + A92E4DEE2BAA001100AC8DE8 /* XCRemoteSwiftPackageReference "swift-collections" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-collections.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 2F3D4ABB2A4E7C290068FB2F /* SpeziScheduler */ = { + isa = XCSwiftPackageProductDependency; + package = 2F3D4ABA2A4E7C290068FB2F /* XCRemoteSwiftPackageReference "SpeziScheduler" */; + productName = SpeziScheduler; + }; + 2F49B7752980407B00BCB272 /* Spezi */ = { + isa = XCSwiftPackageProductDependency; + package = 2F49B7742980407B00BCB272 /* XCRemoteSwiftPackageReference "Spezi" */; + productName = Spezi; + }; + 2FB099AE2A875DF100B20952 /* FirebaseAuth */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAuth; + }; + 2FB099B02A875DF100B20952 /* FirebaseFirestore */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseFirestore; + }; + 2FB099B22A875DF100B20952 /* FirebaseFirestoreSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseFirestoreSwift; + }; + 2FB099B52A875E2B00B20952 /* HealthKitOnFHIR */ = { + isa = XCSwiftPackageProductDependency; + package = 2FB099B42A875E2B00B20952 /* XCRemoteSwiftPackageReference "HealthKitOnFHIR" */; + productName = HealthKitOnFHIR; + }; + 2FBD738B2A3BD150004228E7 /* SpeziScheduler */ = { + isa = XCSwiftPackageProductDependency; + productName = SpeziScheduler; + }; + 2FE5DC6329EDD883004B9AB4 /* SpeziAccount */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC6229EDD883004B9AB4 /* XCRemoteSwiftPackageReference "SpeziAccount" */; + productName = SpeziAccount; + }; + 2FE5DC6629EDD894004B9AB4 /* SpeziContact */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC6529EDD894004B9AB4 /* XCRemoteSwiftPackageReference "SpeziContact" */; + productName = SpeziContact; + }; + 2FE5DC7129EDD8D3004B9AB4 /* SpeziHealthKit */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC7029EDD8D3004B9AB4 /* XCRemoteSwiftPackageReference "SpeziHealthKit" */; + productName = SpeziHealthKit; + }; + 2FE5DC7429EDD8E6004B9AB4 /* SpeziFirebaseAccount */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; + productName = SpeziFirebaseAccount; + }; + 2FE5DC7629EDD8E6004B9AB4 /* SpeziFirebaseConfiguration */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; + productName = SpeziFirebaseConfiguration; + }; + 2FE5DC7829EDD8E6004B9AB4 /* SpeziFirestore */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; + productName = SpeziFirestore; + }; + 2FE5DC8029EDD91D004B9AB4 /* SpeziOnboarding */ = { + isa = XCSwiftPackageProductDependency; + package = 97F466E62A76BBEE005DC9B4 /* XCRemoteSwiftPackageReference "SpeziOnboarding" */; + productName = SpeziOnboarding; + }; + 2FE5DC8329EDD934004B9AB4 /* SpeziQuestionnaire */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC8229EDD934004B9AB4 /* XCRemoteSwiftPackageReference "SpeziQuestionnaire" */; + productName = SpeziQuestionnaire; + }; + 2FE5DC8929EDD972004B9AB4 /* SpeziLocalStorage */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC8829EDD972004B9AB4 /* XCRemoteSwiftPackageReference "SpeziStorage" */; + productName = SpeziLocalStorage; + }; + 2FE5DC8B29EDD972004B9AB4 /* SpeziSecureStorage */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC8829EDD972004B9AB4 /* XCRemoteSwiftPackageReference "SpeziStorage" */; + productName = SpeziSecureStorage; + }; + 2FE5DC8E29EDD980004B9AB4 /* SpeziViews */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC8D29EDD980004B9AB4 /* XCRemoteSwiftPackageReference "SpeziViews" */; + productName = SpeziViews; + }; + 2FE5DC9829EDD9D9004B9AB4 /* XCTestExtensions */ = { + isa = XCSwiftPackageProductDependency; + 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; + }; + 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; + productName = SpeziFirebaseStorage; + }; + A92E4DEF2BAA001100AC8DE8 /* OrderedCollections */ = { + isa = XCSwiftPackageProductDependency; + package = A92E4DEE2BAA001100AC8DE8 /* XCRemoteSwiftPackageReference "swift-collections" */; + productName = OrderedCollections; + }; + A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */ = { + isa = XCSwiftPackageProductDependency; + package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; + productName = SpeziFirebaseAccountStorage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 653A2545283387FE005D4D48 /* Project object */; +} diff --git a/StrokeCog.xcodeproj/project.pbxproj.license b/StrokeCog.xcodeproj/project.pbxproj.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog.xcodeproj/project.pbxproj.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StrokeCog.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/StrokeCog.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StrokeCog.xcodeproj/project.xcworkspace/contents.xcworkspacedata.license b/StrokeCog.xcodeproj/project.xcworkspace/contents.xcworkspacedata.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog.xcodeproj/project.xcworkspace/contents.xcworkspacedata.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist.license b/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..d592ae6 --- /dev/null +++ b/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,321 @@ +{ + "originHash" : "9ddbf125e828f8f258514b9b7b616777823852827ae6b79035a44b66b986b5e0", + "pins" : [ + { + "identity" : "abseil-cpp-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/abseil-cpp-binary.git", + "state" : { + "revision" : "7ce7be095bc3ed3c98b009532fe2d7698c132614", + "version" : "1.2024011601.0" + } + }, + { + "identity" : "app-check", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/app-check.git", + "state" : { + "revision" : "3e464dad87dad2d29bb29a97836789bf0f8f67d2", + "version" : "10.18.1" + } + }, + { + "identity" : "fhirmodels", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/FHIRModels.git", + "state" : { + "revision" : "861afd5816a98d38f86220eab2f812d76cad84a0", + "version" : "0.5.0" + } + }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk.git", + "state" : { + "revision" : "888f0b6026e2441a69e3ee2ad5293c7a92031e62", + "version" : "10.23.1" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "c7a5917ebe48d69f421aadf154ef3969c8b7f12d", + "version" : "10.23.1" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "a637d318ae7ae246b02d7305121275bc75ed5565", + "version" : "9.4.0" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "26c898aed8bed13b8a63057ee26500abbbcb8d55", + "version" : "7.13.1" + } + }, + { + "identity" : "grpc-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/grpc-binary.git", + "state" : { + "revision" : "67043f6389d0e28b38fa02d1c6952afeb04d807f", + "version" : "1.62.1" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "9534039303015a84837090d20fa21cae6e5eadb6", + "version" : "3.3.2" + } + }, + { + "identity" : "healthkitonfhir", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordBDHG/HealthKitOnFHIR.git", + "state" : { + "revision" : "00d64d38a8f0d826ee9e27b6f3ce32314a29fd3e", + "version" : "0.2.6" + } + }, + { + "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" : "43aaef65e0c665daadf848761d560e446d350d3d", + "version" : "1.22.4" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", + "version" : "2.30910.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", + "version" : "2.4.0" + } + }, + { + "identity" : "researchkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordBDHG/ResearchKit", + "state" : { + "revision" : "6b28cdf0d06c3d6e96b5585369968b85deac96e0", + "version" : "2.2.29" + } + }, + { + "identity" : "researchkitonfhir", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordBDHG/ResearchKitOnFHIR", + "state" : { + "revision" : "7c2efdcb17796fc9ee686900304dbbe9dd4aaf85", + "version" : "1.1.2" + } + }, + { + "identity" : "spezi", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/Spezi", + "state" : { + "revision" : "c43e4fa3d3938a847de2b677091a34ddaea5bc76", + "version" : "1.2.3" + } + }, + { + "identity" : "speziaccount", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziAccount.git", + "state" : { + "revision" : "a7d289ef3be54de62b25dc92e8f7ff1a0f093906", + "version" : "1.2.1" + } + }, + { + "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" : "e05e665b7da39aa399ecd7fba393aab49b8f3034", + "version" : "1.0.1" + } + }, + { + "identity" : "spezifoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziFoundation", + "state" : { + "revision" : "01af5b91a54f30ddd121258e81aff2ddc2a99ff9", + "version" : "1.0.4" + } + }, + { + "identity" : "spezihealthkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziHealthKit.git", + "state" : { + "revision" : "1e9cb5a6036ac7f4ff37ea1c3ed4898103339ad1", + "version" : "0.5.3" + } + }, + { + "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" : "4971a82e94996ce0c3d8ecf64fdeec874a1f20d6", + "version" : "1.1.1" + } + }, + { + "identity" : "speziquestionnaire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziQuestionnaire.git", + "state" : { + "revision" : "f9d9b6d99bb1e00bda2974b440dca8367733d591", + "version" : "1.1.0" + } + }, + { + "identity" : "spezischeduler", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziScheduler.git", + "state" : { + "revision" : "eed3980f20b01a788720c869010e3fe2fbfcd1fd", + "version" : "0.8.2" + } + }, + { + "identity" : "spezistorage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziStorage.git", + "state" : { + "revision" : "b958df9b31f24800388a7bfc28f457ce7b82556c", + "version" : "1.0.2" + } + }, + { + "identity" : "speziviews", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordSpezi/SpeziViews.git", + "state" : { + "revision" : "4d2a724d97c8f19ac7de7aa2c046b1cb3ef7b279", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "46989693916f56d1186bd59ac15124caef896560", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", + "version" : "1.1.0" + } + }, + { + "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" : "9f0c76544701845ad98716f3f6a774a892152bcb", + "version" : "1.26.0" + } + }, + { + "identity" : "xctestextensions", + "kind" : "remoteSourceControl", + "location" : "https://github.com/StanfordBDHG/XCTestExtensions.git", + "state" : { + "revision" : "1fe9b8e76aeb7a132af37bfa0892160c9b662dcc", + "version" : "0.4.10" + } + }, + { + "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" : "51da3403f128b120705571ce61e0fe190f8889e6", + "version" : "1.0.1" + } + } + ], + "version" : 3 +} diff --git a/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved.license b/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog.xcodeproj/xcshareddata/xcschemes/StrokeCog.xcscheme b/StrokeCog.xcodeproj/xcshareddata/xcschemes/StrokeCog.xcscheme new file mode 100644 index 0000000..4b87a49 --- /dev/null +++ b/StrokeCog.xcodeproj/xcshareddata/xcschemes/StrokeCog.xcscheme @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StrokeCog.xcodeproj/xcshareddata/xcschemes/StrokeCog.xcscheme.license b/StrokeCog.xcodeproj/xcshareddata/xcschemes/StrokeCog.xcscheme.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog.xcodeproj/xcshareddata/xcschemes/StrokeCog.xcscheme.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog.xctestplan b/StrokeCog.xctestplan new file mode 100644 index 0000000..343fe16 --- /dev/null +++ b/StrokeCog.xctestplan @@ -0,0 +1,44 @@ +{ + "configurations" : [ + { + "id" : "6C9DABEF-5835-4523-A115-B83B0C6E3BBC", + "name" : "Default", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : { + "targets" : [ + { + "containerPath" : "container:StrokeCog.xcodeproj", + "identifier" : "653A254C283387FE005D4D48", + "name" : "StrokeCog" + } + ] + }, + "targetForVariableExpansion" : { + "containerPath" : "container:StrokeCog.xcodeproj", + "identifier" : "653A254C283387FE005D4D48", + "name" : "StrokeCog" + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:StrokeCog.xcodeproj", + "identifier" : "653A255C28338800005D4D48", + "name" : "StrokeCogTests" + } + }, + { + "target" : { + "containerPath" : "container:StrokeCog.xcodeproj", + "identifier" : "653A256628338800005D4D48", + "name" : "StrokeCogUITests" + } + } + ], + "version" : 1 +} diff --git a/StrokeCog.xctestplan.license b/StrokeCog.xctestplan.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog.xctestplan.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog/Account/AccountButton.swift b/StrokeCog/Account/AccountButton.swift new file mode 100644 index 0000000..fa31846 --- /dev/null +++ b/StrokeCog/Account/AccountButton.swift @@ -0,0 +1,38 @@ +// +// This source file is part of the StrokeCog based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +struct AccountButton: View { + static let shouldDisplay = !FeatureFlags.disableFirebase || ProcessInfo.processInfo.isPreviewSimulator + + @Binding private var isPresented: Bool + + + var body: some View { + Button(action: { + isPresented = true + }) { + Image(systemName: "person.crop.circle") + } + .accessibilityLabel("ACCOUNT_TITLE") + } + + + init(isPresented: Binding) { + self._isPresented = isPresented + } +} + + +#if DEBUG +#Preview(traits: .sizeThatFitsLayout) { + AccountButton(isPresented: .constant(false)) +} +#endif diff --git a/StrokeCog/Account/AccountSetupHeader.swift b/StrokeCog/Account/AccountSetupHeader.swift new file mode 100644 index 0000000..e1245d6 --- /dev/null +++ b/StrokeCog/Account/AccountSetupHeader.swift @@ -0,0 +1,43 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Account/AccountSheet.swift b/StrokeCog/Account/AccountSheet.swift new file mode 100644 index 0000000..8db07bd --- /dev/null +++ b/StrokeCog/Account/AccountSheet.swift @@ -0,0 +1,91 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Contacts/Contacts.swift b/StrokeCog/Contacts/Contacts.swift new file mode 100644 index 0000000..f5b8b36 --- /dev/null +++ b/StrokeCog/Contacts/Contacts.swift @@ -0,0 +1,78 @@ +// +// This source file is part of the StrokeCog 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 StrokeCog. +struct Contacts: View { + let contacts = [ + Contact( + name: PersonNameComponents( + givenName: "Leland", + familyName: "Stanford" + ), + image: Image(systemName: "figure.wave.circle"), // swiftlint:disable:this accessibility_label_for_image + title: "University Founder", + description: String(localized: "LELAND_STANFORD_BIO"), + organization: "Stanford University", + address: { + let address = CNMutablePostalAddress() + address.country = "USA" + address.state = "CA" + address.postalCode = "94305" + address.city = "Stanford" + address.street = "450 Serra Mall" + return address + }(), + contactOptions: [ + .call("+1 (650) 723-2300"), + .text("+1 (650) 723-2300"), + .email(addresses: ["contact@stanford.edu"]), + ContactOption( + image: Image(systemName: "safari.fill"), // swiftlint:disable:this accessibility_label_for_image + title: "Website", + action: { + if let url = URL(string: "https://stanford.edu") { + 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/StrokeCog/Contributions/ContributionsList.swift b/StrokeCog/Contributions/ContributionsList.swift new file mode 100644 index 0000000..5c029f8 --- /dev/null +++ b/StrokeCog/Contributions/ContributionsList.swift @@ -0,0 +1,53 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Contributions/Package+LicenseType.swift b/StrokeCog/Contributions/Package+LicenseType.swift new file mode 100644 index 0000000..95eaae6 --- /dev/null +++ b/StrokeCog/Contributions/Package+LicenseType.swift @@ -0,0 +1,104 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Contributions/PackageCell.swift b/StrokeCog/Contributions/PackageCell.swift new file mode 100644 index 0000000..9bb6406 --- /dev/null +++ b/StrokeCog/Contributions/PackageCell.swift @@ -0,0 +1,73 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Contributions/PackageHelper.swift b/StrokeCog/Contributions/PackageHelper.swift new file mode 100644 index 0000000..65a7dd6 --- /dev/null +++ b/StrokeCog/Contributions/PackageHelper.swift @@ -0,0 +1,26 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Helper/Binding+Negate.swift b/StrokeCog/Helper/Binding+Negate.swift new file mode 100644 index 0000000..db30f37 --- /dev/null +++ b/StrokeCog/Helper/Binding+Negate.swift @@ -0,0 +1,20 @@ +// +// This source file is part of the StrokeCog based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +extension Binding where Value == Bool { + /// Negates a `Binding`. + prefix static func ! (value: Binding) -> Binding { + Binding( + get: { !value.wrappedValue }, + set: { value.wrappedValue = !$0 } + ) + } +} diff --git a/StrokeCog/Helper/Bundle+Image.swift b/StrokeCog/Helper/Bundle+Image.swift new file mode 100644 index 0000000..87587d2 --- /dev/null +++ b/StrokeCog/Helper/Bundle+Image.swift @@ -0,0 +1,30 @@ +// +// This source file is part of the StrokeCog based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +extension Foundation.Bundle { + /// Loads an image from the `Bundle` instance. + /// - Parameters: + /// - name: The name of the image. + /// - fileExtension: The file extension of the image. + /// - Returns: Returns the `UIImage` loaded from the `Bundle` instance. + 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.") + } + + guard let resourceData = try? Data(contentsOf: resourceURL), + let image = UIImage(data: resourceData) else { + fatalError("Decode the image named \"\(name).\(fileExtension)\"") + } + + return image + } +} diff --git a/StrokeCog/Helper/CodableArray+RawRepresentable.swift b/StrokeCog/Helper/CodableArray+RawRepresentable.swift new file mode 100644 index 0000000..61da25b --- /dev/null +++ b/StrokeCog/Helper/CodableArray+RawRepresentable.swift @@ -0,0 +1,28 @@ +// +// This source file is part of the StrokeCog based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation + + +extension Array: RawRepresentable where Element: Codable { + public var rawValue: String { + guard let data = try? JSONEncoder().encode(self), + let rawValue = String(data: data, encoding: .utf8) else { + return "[]" + } + return rawValue + } + + public init?(rawValue: String) { + guard let data = rawValue.data(using: .utf8), + let result = try? JSONDecoder().decode([Element].self, from: data) else { + return nil + } + self = result + } +} diff --git a/StrokeCog/Home.swift b/StrokeCog/Home.swift new file mode 100644 index 0000000..1058049 --- /dev/null +++ b/StrokeCog/Home.swift @@ -0,0 +1,86 @@ +// +// This source file is part of the StrokeCog 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 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") + } + 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: StrokeCogStandard()) { + StrokeCogScheduler() + MockWebService() + AccountConfiguration(building: details, active: MockUserIdPasswordAccountService()) + } +} + +#Preview { + CommandLine.arguments.append("--disableFirebase") // make sure the MockWebService is displayed + return HomeView() + .previewWith(standard: StrokeCogStandard()) { + StrokeCogScheduler() + MockWebService() + AccountConfiguration { + MockUserIdPasswordAccountService() + } + } +} +#endif diff --git a/StrokeCog/MockUpload/MockUpload.swift b/StrokeCog/MockUpload/MockUpload.swift new file mode 100644 index 0000000..30bd871 --- /dev/null +++ b/StrokeCog/MockUpload/MockUpload.swift @@ -0,0 +1,41 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Onboarding/AccountOnboarding.swift b/StrokeCog/Onboarding/AccountOnboarding.swift new file mode 100644 index 0000000..a13283d --- /dev/null +++ b/StrokeCog/Onboarding/AccountOnboarding.swift @@ -0,0 +1,64 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Onboarding/Consent.swift b/StrokeCog/Onboarding/Consent.swift new file mode 100644 index 0000000..9caefe0 --- /dev/null +++ b/StrokeCog/Onboarding/Consent.swift @@ -0,0 +1,49 @@ +// +// This source file is part of the StrokeCog 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: StrokeCogStandard()) { + OnboardingDataSource() + } +} +#endif diff --git a/StrokeCog/Onboarding/HealthKitPermissions.swift b/StrokeCog/Onboarding/HealthKitPermissions.swift new file mode 100644 index 0000000..b446d8e --- /dev/null +++ b/StrokeCog/Onboarding/HealthKitPermissions.swift @@ -0,0 +1,77 @@ +// +// This source file is part of the StrokeCog 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: StrokeCogStandard()) { + HealthKit() + } +} +#endif diff --git a/StrokeCog/Onboarding/InterestingModules.swift b/StrokeCog/Onboarding/InterestingModules.swift new file mode 100644 index 0000000..e458620 --- /dev/null +++ b/StrokeCog/Onboarding/InterestingModules.swift @@ -0,0 +1,54 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Onboarding/NotificationPermissions.swift b/StrokeCog/Onboarding/NotificationPermissions.swift new file mode 100644 index 0000000..46eafc3 --- /dev/null +++ b/StrokeCog/Onboarding/NotificationPermissions.swift @@ -0,0 +1,77 @@ +// +// This source file is part of the StrokeCog 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(StrokeCogScheduler.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 { + StrokeCogScheduler() + } +} +#endif diff --git a/StrokeCog/Onboarding/OnboardingFlow.swift b/StrokeCog/Onboarding/OnboardingFlow.swift new file mode 100644 index 0000000..4c45195 --- /dev/null +++ b/StrokeCog/Onboarding/OnboardingFlow.swift @@ -0,0 +1,79 @@ +// +// This source file is part of the StrokeCog 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 StrokeCog. +struct OnboardingFlow: View { + @Environment(HealthKit.self) private var healthKitDataSource + @Environment(StrokeCogScheduler.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: StrokeCogStandard()) { + OnboardingDataSource() + HealthKit() + AccountConfiguration { + MockUserIdPasswordAccountService() + } + + StrokeCogScheduler() + } +} +#endif diff --git a/StrokeCog/Onboarding/Welcome.swift b/StrokeCog/Onboarding/Welcome.swift new file mode 100644 index 0000000..a8012d3 --- /dev/null +++ b/StrokeCog/Onboarding/Welcome.swift @@ -0,0 +1,63 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Resources/AppIcon.png b/StrokeCog/Resources/AppIcon.png new file mode 100644 index 0000000..7414dd9 Binary files /dev/null and b/StrokeCog/Resources/AppIcon.png differ diff --git a/StrokeCog/Resources/AppIcon.png.license b/StrokeCog/Resources/AppIcon.png.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog/Resources/AppIcon.png.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/StrokeCog/Resources/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/StrokeCog/Resources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StrokeCog/Resources/Assets.xcassets/AccentColor.colorset/Contents.json.license b/StrokeCog/Resources/Assets.xcassets/AccentColor.colorset/Contents.json.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog/Resources/Assets.xcassets/AccentColor.colorset/Contents.json.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png b/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png new file mode 100644 index 0000000..7414dd9 Binary files /dev/null and b/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png differ diff --git a/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png.license b/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..cefcc87 --- /dev/null +++ b/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "AppIcon.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json.license b/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog/Resources/Assets.xcassets/Contents.json b/StrokeCog/Resources/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/StrokeCog/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StrokeCog/Resources/Assets.xcassets/Contents.json.license b/StrokeCog/Resources/Assets.xcassets/Contents.json.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog/Resources/Assets.xcassets/Contents.json.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog/Resources/ConsentDocument.md b/StrokeCog/Resources/ConsentDocument.md new file mode 100644 index 0000000..5f9df6f --- /dev/null +++ b/StrokeCog/Resources/ConsentDocument.md @@ -0,0 +1 @@ +Spezi can render consent documents in the markdown format: This is a *markdown* **example**. diff --git a/StrokeCog/Resources/ConsentDocument.md.license b/StrokeCog/Resources/ConsentDocument.md.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog/Resources/ConsentDocument.md.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog/Resources/Localizable.xcstrings b/StrokeCog/Resources/Localizable.xcstrings new file mode 100644 index 0000000..af35f22 --- /dev/null +++ b/StrokeCog/Resources/Localizable.xcstrings @@ -0,0 +1,545 @@ +{ + "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 StrokeCog demonstrates the usage of the Firebase Account Module." + } + } + } + }, + "ACCOUNT_TITLE" : { + "comment" : "MARK: Account", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Your Account" + } + } + } + }, + "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 SpeziStrokeCog." + } + } + } + }, + "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" + } + } + } + }, + "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" + } + } + } + }, + "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" + } + } + } + }, + "LELAND_STANFORD_BIO" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Amasa Leland Stanford (March 9, 1824 – June 21, 1893) was an American industrialist and politician. [...] He and his wife Jane were also the founders of Stanford University, which they named after their late son.\n[https://en.wikipedia.org/wiki/Leland_Stanford]" + } + } + } + }, + "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." + } + } + } + }, + "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" + } + } + } + }, + "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" + } + } + } + }, + "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\nStrokeCog" + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/StrokeCog/Resources/Localizable.xcstrings.license b/StrokeCog/Resources/Localizable.xcstrings.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog/Resources/Localizable.xcstrings.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog/Resources/SocialSupportQuestionnaire.json b/StrokeCog/Resources/SocialSupportQuestionnaire.json new file mode 100644 index 0000000..d3c584d --- /dev/null +++ b/StrokeCog/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/StrokeCog/Resources/SocialSupportQuestionnaire.json.license b/StrokeCog/Resources/SocialSupportQuestionnaire.json.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog/Resources/SocialSupportQuestionnaire.json.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog/Schedule/Bundle+Questionnaire.swift b/StrokeCog/Schedule/Bundle+Questionnaire.swift new file mode 100644 index 0000000..21a5513 --- /dev/null +++ b/StrokeCog/Schedule/Bundle+Questionnaire.swift @@ -0,0 +1,26 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Schedule/EventContext.swift b/StrokeCog/Schedule/EventContext.swift new file mode 100644 index 0000000..ea88c24 --- /dev/null +++ b/StrokeCog/Schedule/EventContext.swift @@ -0,0 +1,25 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Schedule/EventContextView.swift b/StrokeCog/Schedule/EventContextView.swift new file mode 100644 index 0000000..42b7d09 --- /dev/null +++ b/StrokeCog/Schedule/EventContextView.swift @@ -0,0 +1,81 @@ +// +// This source file is part of the StrokeCog 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 = StrokeCogScheduler.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/StrokeCog/Schedule/ModalView.swift b/StrokeCog/Schedule/ModalView.swift new file mode 100644 index 0000000..09ed583 --- /dev/null +++ b/StrokeCog/Schedule/ModalView.swift @@ -0,0 +1,47 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/Schedule/ScheduleView.swift b/StrokeCog/Schedule/ScheduleView.swift new file mode 100644 index 0000000..6b9805f --- /dev/null +++ b/StrokeCog/Schedule/ScheduleView.swift @@ -0,0 +1,119 @@ +// +// This source file is part of the StrokeCog based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import OrderedCollections +import SpeziAccount +import SpeziQuestionnaire +import SpeziScheduler +import SwiftUI + + +struct ScheduleView: View { + @Environment(StrokeCogStandard.self) private var standard + @Environment(StrokeCogScheduler.self) private var scheduler + + @State private var presentedContext: EventContext? + @Binding private var presentingAccount: Bool + + + private var eventContextsByDate: OrderedDictionary { + 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() + + return OrderedDictionary(grouping: eventContexts) { eventContext in + Calendar.current.startOfDay(for: eventContext.event.scheduledAt) + } + } + + + var body: some View { + NavigationStack { + let eventContextsByDate = eventContextsByDate + List(eventContextsByDate.keys, 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 + } + } + } + } + } + .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) + } +} + + +#if DEBUG +#Preview("ScheduleView") { + ScheduleView(presentingAccount: .constant(false)) + .previewWith(standard: StrokeCogStandard()) { + StrokeCogScheduler() + AccountConfiguration { + MockUserIdPasswordAccountService() + } + } +} +#endif diff --git a/StrokeCog/Schedule/StrokeCogScheduler.swift b/StrokeCog/Schedule/StrokeCogScheduler.swift new file mode 100644 index 0000000..273f588 --- /dev/null +++ b/StrokeCog/Schedule/StrokeCogScheduler.swift @@ -0,0 +1,49 @@ +// +// This source file is part of the StrokeCog 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 ``StrokeCogTaskContext`` to schedule and manage tasks and events in the +/// StrokeCog. +typealias StrokeCogScheduler = Scheduler + + +extension StrokeCogScheduler { + 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: StrokeCogTaskContext.questionnaire(Bundle.main.questionnaire(withName: "SocialSupportQuestionnaire")) + ) + } + + /// Creates a default instance of the ``StrokeCogScheduler`` by scheduling the tasks listed below. + convenience init() { + self.init(tasks: [Self.socialSupportTask]) + } +} diff --git a/StrokeCog/Schedule/StrokeCogTaskContext.swift b/StrokeCog/Schedule/StrokeCogTaskContext.swift new file mode 100644 index 0000000..9c71759 --- /dev/null +++ b/StrokeCog/Schedule/StrokeCogTaskContext.swift @@ -0,0 +1,40 @@ +// +// This source file is part of the StrokeCog 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 StrokeCog. +/// +/// We currently only support `Questionnaire`s, more cases can be added in the future. +enum StrokeCogTaskContext: 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/StrokeCog/SharedContext/FeatureFlags.swift b/StrokeCog/SharedContext/FeatureFlags.swift new file mode 100644 index 0000000..f18f9d5 --- /dev/null +++ b/StrokeCog/SharedContext/FeatureFlags.swift @@ -0,0 +1,26 @@ +// +// This source file is part of the StrokeCog based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +/// A collection of feature flags for the StrokeCog. +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. + 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. + static let showOnboarding = CommandLine.arguments.contains("--showOnboarding") + /// Disables the Firebase interactions, including the login/sign-up step and the Firebase Firestore upload. + 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. + 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. + 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/StrokeCog/SharedContext/StorageKeys.swift b/StrokeCog/SharedContext/StorageKeys.swift new file mode 100644 index 0000000..2cc5925 --- /dev/null +++ b/StrokeCog/SharedContext/StorageKeys.swift @@ -0,0 +1,21 @@ +// +// This source file is part of the StrokeCog 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/StrokeCog/StrokeCog.swift b/StrokeCog/StrokeCog.swift new file mode 100644 index 0000000..46c3a14 --- /dev/null +++ b/StrokeCog/StrokeCog.swift @@ -0,0 +1,36 @@ +// +// This source file is part of the StrokeCog based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Spezi +import SpeziFirebaseAccount +import SwiftUI + + +@main +struct StrokeCog: App { + @UIApplicationDelegateAdaptor(StrokeCogDelegate.self) var appDelegate + @AppStorage(StorageKeys.onboardingFlowComplete) var completedOnboardingFlow = false + + + var body: some Scene { + WindowGroup { + ZStack { + if completedOnboardingFlow { + HomeView() + } else { + EmptyView() + } + } + .sheet(isPresented: !$completedOnboardingFlow) { + OnboardingFlow() + } + .testingSetup() + .spezi(appDelegate) + } + } +} diff --git a/StrokeCog/StrokeCogDelegate.swift b/StrokeCog/StrokeCogDelegate.swift new file mode 100644 index 0000000..b1715a4 --- /dev/null +++ b/StrokeCog/StrokeCogDelegate.swift @@ -0,0 +1,84 @@ +// +// This source file is part of the StrokeCog 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 StrokeCogDelegate: SpeziAppDelegate { + override var configuration: Configuration { + Configuration(standard: StrokeCogStandard()) { + 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 + } + + StrokeCogScheduler() + 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/StrokeCog/StrokeCogStandard.swift b/StrokeCog/StrokeCogStandard.swift new file mode 100644 index 0000000..12f4e7c --- /dev/null +++ b/StrokeCog/StrokeCogStandard.swift @@ -0,0 +1,203 @@ +// +// This source file is part of the StrokeCog 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 StrokeCogStandard: Standard, EnvironmentAccessible, HealthKitConstraint, OnboardingConstraint, AccountStorageConstraint { + enum StrokeCogStandardError: 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: "StrokeCog", category: "Standard") + + + private var userDocumentReference: DocumentReference { + get async throws { + guard let details = await account.details else { + throw StrokeCogStandardError.userNotAuthenticatedYet + } + + return Self.userCollection.document(details.accountId) + } + } + + private var userBucketReference: StorageReference { + get async throws { + guard let details = await account.details else { + throw StrokeCogStandardError.userNotAuthenticatedYet + } + + return Storage.storage().reference().child("users/\(details.accountId)") + } + } + + + init() { + if !FeatureFlags.disableFirebase { + _accountStorage = Dependency(wrappedValue: FirestoreAccountStorage(storeIn: StrokeCogStandard.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/StrokeCog/StrokeCogTestingSetup.swift b/StrokeCog/StrokeCogTestingSetup.swift new file mode 100644 index 0000000..3a881a6 --- /dev/null +++ b/StrokeCog/StrokeCogTestingSetup.swift @@ -0,0 +1,34 @@ +// +// This source file is part of the StrokeCog based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +private struct StrokeCogAppTestingSetup: ViewModifier { + @AppStorage(StorageKeys.onboardingFlowComplete) var completedOnboardingFlow = false + + + func body(content: Content) -> some View { + content + .task { + if FeatureFlags.skipOnboarding { + completedOnboardingFlow = true + } + if FeatureFlags.showOnboarding { + completedOnboardingFlow = false + } + } + } +} + + +extension View { + func testingSetup() -> some View { + self.modifier(StrokeCogAppTestingSetup()) + } +} diff --git a/StrokeCog/Supporting Files/GoogleService-Info.plist b/StrokeCog/Supporting Files/GoogleService-Info.plist new file mode 100644 index 0000000..54db832 --- /dev/null +++ b/StrokeCog/Supporting Files/GoogleService-Info.plist @@ -0,0 +1,34 @@ + + + + + CLIENT_ID + CLIENT_ID + REVERSED_CLIENT_ID + REVERSED_CLIENT_ID + API_KEY + API_KEY + GCM_SENDER_ID + GCM_SENDER_ID + PLIST_VERSION + 1 + BUNDLE_ID + com.odden.strokecog + PROJECT_ID + strokecog + STORAGE_BUCKET + STORAGE_BUCKET + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:123456789012:ios:1234567890123456789012 + + diff --git a/StrokeCog/Supporting Files/GoogleService-Info.plist.license b/StrokeCog/Supporting Files/GoogleService-Info.plist.license new file mode 100644 index 0000000..c376e26 --- /dev/null +++ b/StrokeCog/Supporting Files/GoogleService-Info.plist.license @@ -0,0 +1,5 @@ +This source file is part of the StrokeCog 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/StrokeCog/Supporting Files/Info.plist b/StrokeCog/Supporting Files/Info.plist new file mode 100644 index 0000000..5dc3321 --- /dev/null +++ b/StrokeCog/Supporting Files/Info.plist @@ -0,0 +1,17 @@ + + + + + ITSAppUsesNonExemptEncryption + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + + CFBundleAllowMixedLocalizations + + + diff --git a/StrokeCog/Supporting Files/Info.plist.license b/StrokeCog/Supporting Files/Info.plist.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog/Supporting Files/Info.plist.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCog/Supporting Files/StrokeCog.entitlements b/StrokeCog/Supporting Files/StrokeCog.entitlements new file mode 100644 index 0000000..c541ce6 --- /dev/null +++ b/StrokeCog/Supporting Files/StrokeCog.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.developer.applesignin + + Default + + com.apple.developer.healthkit + + com.apple.developer.healthkit.access + + com.apple.developer.healthkit.background-delivery + + + diff --git a/StrokeCog/Supporting Files/StrokeCog.entitlements.license b/StrokeCog/Supporting Files/StrokeCog.entitlements.license new file mode 100644 index 0000000..5989983 --- /dev/null +++ b/StrokeCog/Supporting Files/StrokeCog.entitlements.license @@ -0,0 +1,6 @@ + +This source file is part of the StrokeCog based on the Stanford Spezi Template Application project + +SPDX-FileCopyrightText: 2023 Stanford University + +SPDX-License-Identifier: MIT diff --git a/StrokeCogTests/StrokeCogTests.swift b/StrokeCogTests/StrokeCogTests.swift new file mode 100644 index 0000000..4683ea5 --- /dev/null +++ b/StrokeCogTests/StrokeCogTests.swift @@ -0,0 +1,17 @@ +// +// This source file is part of the StrokeCog based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +@testable import StrokeCog +import XCTest + + +class StrokeCogTests: XCTestCase { + func testExample() throws { + XCTAssertTrue(true) + } +} diff --git a/StrokeCogUITests/ContactsTests.swift b/StrokeCogUITests/ContactsTests.swift new file mode 100644 index 0000000..ee63845 --- /dev/null +++ b/StrokeCogUITests/ContactsTests.swift @@ -0,0 +1,39 @@ +// +// This source file is part of the StrokeCog 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/StrokeCogUITests/ContributionsTest.swift b/StrokeCogUITests/ContributionsTest.swift new file mode 100644 index 0000000..58649db --- /dev/null +++ b/StrokeCogUITests/ContributionsTest.swift @@ -0,0 +1,39 @@ +// +// This source file is part of the StrokeCog 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() + + continueAfterFailure = false + + let app = XCUIApplication() + app.launchArguments = ["--showOnboarding"] + app.deleteAndLaunch(withSpringboardAppName: "StrokeCog") + } + + 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/StrokeCogUITests/MockUploadTests.swift b/StrokeCogUITests/MockUploadTests.swift new file mode 100644 index 0000000..6bde2c3 --- /dev/null +++ b/StrokeCogUITests/MockUploadTests.swift @@ -0,0 +1,32 @@ +// +// This source file is part of the StrokeCog 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/StrokeCogUITests/OnboardingTests.swift b/StrokeCogUITests/OnboardingTests.swift new file mode 100644 index 0000000..47299ef --- /dev/null +++ b/StrokeCogUITests/OnboardingTests.swift @@ -0,0 +1,224 @@ +// +// This source file is part of the StrokeCog 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() + + continueAfterFailure = false + + let app = XCUIApplication() + app.launchArguments = ["--showOnboarding"] + app.deleteAndLaunch(withSpringboardAppName: "StrokeCog") + } + + + 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\nStrokeCog"].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\nStrokeCog"].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 { + if buttons["Logout"].waitForExistence(timeout: 2.0) { + buttons["Logout"].tap() + } + + 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/StrokeCogUITests/SchedulerTests.swift b/StrokeCogUITests/SchedulerTests.swift new file mode 100644 index 0000000..8ca64e3 --- /dev/null +++ b/StrokeCogUITests/SchedulerTests.swift @@ -0,0 +1,36 @@ +// +// This source file is part of the StrokeCog 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: "StrokeCog") + } + + + 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)) + } +}