diff --git a/.firebaserc b/.firebaserc deleted file mode 100644 index 215d2d0..0000000 --- a/.firebaserc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "projects": { - "default": "cs342-2024-intake" - } -} diff --git a/.firebaserc.license b/.firebaserc.license deleted file mode 100644 index 8a53724..0000000 --- a/.firebaserc.license +++ /dev/null @@ -1,5 +0,0 @@ -This source file is part of the Intake 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/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 7d0000e..26ade98 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -29,8 +29,7 @@ jobs: with: artifactname: Intake.xcresult runsonlabels: '["macOS", "self-hosted"]' - setupfirebaseemulator: true - customcommand: "firebase emulators:exec 'fastlane test'" + customcommand: "fastlane test" uploadcoveragereport: name: Upload Coverage Report needs: buildandtest diff --git a/Intake.xcodeproj/project.pbxproj b/Intake.xcodeproj/project.pbxproj index 99d6901..4925e6e 100644 --- a/Intake.xcodeproj/project.pbxproj +++ b/Intake.xcodeproj/project.pbxproj @@ -7,24 +7,16 @@ objects = { /* Begin PBXBuildFile section */ - 27FA29902A388E9B009CAC45 /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FA298F2A388E9B009CAC45 /* ModalView.swift */; }; 2F1AC9DF2B4E840E00C24973 /* Intake.docc in Sources */ = {isa = PBXBuildFile; fileRef = 2F1AC9DE2B4E840E00C24973 /* Intake.docc */; }; - 2F3D4ABC2A4E7C290068FB2F /* SpeziScheduler in Frameworks */ = {isa = PBXBuildFile; productRef = 2F3D4ABB2A4E7C290068FB2F /* SpeziScheduler */; }; 2F49B7762980407C00BCB272 /* Spezi in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7752980407B00BCB272 /* Spezi */; }; 2F4E237E2989A2FE0013F3D9 /* LaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E237D2989A2FE0013F3D9 /* LaunchTests.swift */; }; 2F4E23832989D51F0013F3D9 /* IntakeTestingSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4E23822989D51F0013F3D9 /* IntakeTestingSetup.swift */; }; - 2F4FC8D729EE69D300BFFE26 /* MockUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F4FC8D629EE69D300BFFE26 /* MockUpload.swift */; }; 2F5E32BD297E05EA003432F8 /* IntakeDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F5E32BC297E05EA003432F8 /* IntakeDelegate.swift */; }; 2F6025CB29BBE70F0045459E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2F6025CA29BBE70F0045459E /* GoogleService-Info.plist */; }; 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 */; }; - 2FC3439029EE6346002D773C /* SocialSupportQuestionnaire.json in Resources */ = {isa = PBXBuildFile; fileRef = 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */; }; 2FC3439229EE634B002D773C /* ConsentDocument.md in Resources */ = {isa = PBXBuildFile; fileRef = 2FE5DC2C29EDD78E004B9AB4 /* ConsentDocument.md */; }; 2FC975A82978F11A00BA99FE /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FC975A72978F11A00BA99FE /* Home.swift */; }; - 2FE5DC2629EDD38A004B9AB4 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC2529EDD38A004B9AB4 /* Contacts.swift */; }; 2FE5DC3529EDD7CA004B9AB4 /* Consent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC2F29EDD7CA004B9AB4 /* Consent.swift */; }; 2FE5DC3629EDD7CA004B9AB4 /* HealthKitPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC3029EDD7CA004B9AB4 /* HealthKitPermissions.swift */; }; 2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC3129EDD7CA004B9AB4 /* OnboardingFlow.swift */; }; @@ -35,30 +27,16 @@ 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 /* IntakeTaskContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4B29EDD7FA004B9AB4 /* IntakeTaskContext.swift */; }; - 2FE5DC5229EDD7FA004B9AB4 /* IntakeScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FE5DC4C29EDD7FA004B9AB4 /* IntakeScheduler.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 /* IntakeStandard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FF53D8C2A8729D600042B76 /* IntakeStandard.swift */; }; 3C89F66D2B9D948B00A4F52D /* PatientInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C89F66C2B9D948B00A4F52D /* PatientInfo.swift */; }; - 3CD23D612BA137D500AB9914 /* Documentation.docc in Sources */ = {isa = PBXBuildFile; fileRef = 3CD23D602BA137D500AB9914 /* Documentation.docc */; }; - 3CD23D642BA14D6D00AB9914 /* NinasTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CD23D632BA14D6D00AB9914 /* NinasTests.swift */; }; + 3CD23D612BA137D500AB9914 /* Intake.docc in Sources */ = {isa = PBXBuildFile; fileRef = 3CD23D602BA137D500AB9914 /* Intake.docc */; }; + 3CD23D642BA14D6D00AB9914 /* PatientInformationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CD23D632BA14D6D00AB9914 /* PatientInformationTests.swift */; }; 510CAAF12BA0DFFB00872B1A /* MedicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510CAAF02BA0DFFB00872B1A /* MedicationTests.swift */; }; 511827962B740192002033A0 /* SurgeryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511827952B740191002033A0 /* SurgeryView.swift */; }; 5142133E2BA367F70012AFD7 /* SurgeryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5142133D2BA367F70012AFD7 /* SurgeryTests.swift */; }; @@ -73,11 +51,6 @@ 51A360142B9648A7004E7E12 /* SurgeryLLMAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A360132B9648A7004E7E12 /* SurgeryLLMAssistant.swift */; }; 51A360162B965819004E7E12 /* AllergyLLMAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A360152B965819004E7E12 /* AllergyLLMAssistant.swift */; }; 51A360182B9659AE004E7E12 /* MedicalHistoryLLMAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A360172B9659AE004E7E12 /* MedicalHistoryLLMAssistant.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 */; }; - 56F6F2A02AB441930022FE5A /* ContributionsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56F6F29F2AB441930022FE5A /* ContributionsList.swift */; }; 5A0C1A192B69691000120506 /* SpeziFHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 5A0C1A182B69691000120506 /* SpeziFHIR */; }; 5A0C1A1B2B69691000120506 /* SpeziFHIRHealthKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5A0C1A1A2B69691000120506 /* SpeziFHIRHealthKit */; }; 5A2B9F7A2B69AE2C005CA63F /* MedicalHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2B9F792B69AE2C005CA63F /* MedicalHistoryView.swift */; }; @@ -87,16 +60,10 @@ 5A2B9F8F2B69E286005CA63F /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2B9F8E2B69E286005CA63F /* MockData.swift */; }; 5A2B9F9E2B69E32A005CA63F /* Beatris270_Bogan287_5b3645de-a2d0-d016-0839-bab3757c4c58.json in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F922B69E32A005CA63F /* Beatris270_Bogan287_5b3645de-a2d0-d016-0839-bab3757c4c58.json */; }; 5A2B9F9F2B69E32A005CA63F /* Edythe31_Morar593_9c3df38a-d3b7-2198-3898-51f9153d023d.json in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F932B69E32A005CA63F /* Edythe31_Morar593_9c3df38a-d3b7-2198-3898-51f9153d023d.json */; }; - 5A2B9FA02B69E32A005CA63F /* Beatris270_Bogan287_5b3645de-a2d0-d016-0839-bab3757c4c58.json.license in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F942B69E32A005CA63F /* Beatris270_Bogan287_5b3645de-a2d0-d016-0839-bab3757c4c58.json.license */; }; - 5A2B9FA12B69E32A005CA63F /* Gonzalo160_Duenas839_ed70a28f-30b2-acb7-658a-8b340dadd685.json.license in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F952B69E32A005CA63F /* Gonzalo160_Duenas839_ed70a28f-30b2-acb7-658a-8b340dadd685.json.license */; }; - 5A2B9FA22B69E32A005CA63F /* Allen322_Ferry570_ad134528-56a5-35fd-c37f-466ff119c625.json.license in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F962B69E32A005CA63F /* Allen322_Ferry570_ad134528-56a5-35fd-c37f-466ff119c625.json.license */; }; 5A2B9FA32B69E32A005CA63F /* Allen322_Ferry570_ad134528-56a5-35fd-c37f-466ff119c625.json in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F972B69E32A005CA63F /* Allen322_Ferry570_ad134528-56a5-35fd-c37f-466ff119c625.json */; }; - 5A2B9FA42B69E32A005CA63F /* Jacklyn830_Veum823_e0e1f21a-22a7-d166-7bb1-63f6bbce1a32.json.license in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F982B69E32A005CA63F /* Jacklyn830_Veum823_e0e1f21a-22a7-d166-7bb1-63f6bbce1a32.json.license */; }; 5A2B9FA52B69E32A005CA63F /* Milton509_Ortiz186_d66b5418-06cb-fc8a-8c13-85685b6ac939.json in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F992B69E32A005CA63F /* Milton509_Ortiz186_d66b5418-06cb-fc8a-8c13-85685b6ac939.json */; }; 5A2B9FA62B69E32A005CA63F /* Gonzalo160_Duenas839_ed70a28f-30b2-acb7-658a-8b340dadd685.json in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F9A2B69E32A005CA63F /* Gonzalo160_Duenas839_ed70a28f-30b2-acb7-658a-8b340dadd685.json */; }; 5A2B9FA72B69E32A005CA63F /* Jacklyn830_Veum823_e0e1f21a-22a7-d166-7bb1-63f6bbce1a32.json in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F9B2B69E32A005CA63F /* Jacklyn830_Veum823_e0e1f21a-22a7-d166-7bb1-63f6bbce1a32.json */; }; - 5A2B9FA82B69E32A005CA63F /* Edythe31_Morar593_9c3df38a-d3b7-2198-3898-51f9153d023d.json.license in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F9C2B69E32A005CA63F /* Edythe31_Morar593_9c3df38a-d3b7-2198-3898-51f9153d023d.json.license */; }; - 5A2B9FA92B69E32A005CA63F /* Milton509_Ortiz186_d66b5418-06cb-fc8a-8c13-85685b6ac939.json.license in Resources */ = {isa = PBXBuildFile; fileRef = 5A2B9F9D2B69E32A005CA63F /* Milton509_Ortiz186_d66b5418-06cb-fc8a-8c13-85685b6ac939.json.license */; }; 5A2B9FAB2B69E430005CA63F /* FHIRStore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2B9FAA2B69E430005CA63F /* FHIRStore+Extensions.swift */; }; 5A2B9FB62B6AFE5D005CA63F /* AllergyRecords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2B9FB52B6AFE5D005CA63F /* AllergyRecords.swift */; }; 5A2B9FBC2B6C7B29005CA63F /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2B9FBB2B6C7B29005CA63F /* ReactionView.swift */; }; @@ -107,18 +74,17 @@ 5AEA5F3B2B90081B00F1577A /* ScrollablePDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA5F3A2B90081B00F1577A /* ScrollablePDF.swift */; }; 5AEA5F422B90710B00F1577A /* EditPatient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA5F412B90710B00F1577A /* EditPatient.swift */; }; 5AEA5F472B93034A00F1577A /* ReactionPDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA5F462B93034A00F1577A /* ReactionPDF.swift */; }; - 637BE1D52B572B4800EA19C6 /* Questionnaire.json.license in Resources */ = {isa = PBXBuildFile; fileRef = 637BE1D42B572B4800EA19C6 /* Questionnaire.json.license */; }; 653A2551283387FE005D4D48 /* Intake.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A2550283387FE005D4D48 /* Intake.swift */; }; 653A255528338800005D4D48 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 653A255428338800005D4D48 /* Assets.xcassets */; }; 653A256228338800005D4D48 /* IntakeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A256128338800005D4D48 /* IntakeTests.swift */; }; + 970157092BB924BF00335713 /* ExportView+ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970157082BB924BF00335713 /* ExportView+ShareSheet.swift */; }; + 9701570E2BB925DE00335713 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9701570D2BB925DE00335713 /* DataStore.swift */; }; + 970157102BB9260400335713 /* HealthDataModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9701570F2BB9260400335713 /* HealthDataModels.swift */; }; + 970157122BB9266400335713 /* NavigationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970157112BB9266400335713 /* NavigationHelpers.swift */; }; + 970157182BB929FC00335713 /* SettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970157172BB929FC00335713 /* SettingsButton.swift */; }; 9733CFC62A8066DE001B7ABC /* SpeziOnboarding in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8029EDD91D004B9AB4 /* SpeziOnboarding */; }; - 9739A0C62AD7B5730084BEA5 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 9739A0C52AD7B5730084BEA5 /* FirebaseStorage */; }; - 97D73D6A2AD860AD00B47FA0 /* SpeziFirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */; }; - A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */; }; - A9D83F962B083794000D0C78 /* SpeziFirebaseAccountStorage in Frameworks */ = {isa = PBXBuildFile; productRef = A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */; }; - A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DFE8A82ABE551400428242 /* AccountButton.swift */; }; - A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */; }; - ACAA47812B571C800032D21F /* Questionnaire.json in Resources */ = {isa = PBXBuildFile; fileRef = ACAA47802B571C7F0032D21F /* Questionnaire.json */; }; + 97FE2EA42BB93582006A3B8E /* StartButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97FE2EA32BB93582006A3B8E /* StartButton.swift */; }; + 97FE2EA62BB935A2006A3B8E /* LoadLastButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97FE2EA52BB935A2006A3B8E /* LoadLastButton.swift */; }; ACBC9D1D2BA2B3F6003E2581 /* AllergyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACBC9D1C2BA2B3F6003E2581 /* AllergyTests.swift */; }; ACDF32ED2B9D0F4300B127E2 /* MenstrualHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACFFA1D02B8FD8BB0006E6D4 /* MenstrualHistory.swift */; }; ACF862BE2B96E29600ACBA1E /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF862BD2B96E29600ACBA1E /* ExportView.swift */; }; @@ -131,7 +97,7 @@ F42AB1DF2B637C9D002E13A6 /* LLMInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42AB1DE2B637C9C002E13A6 /* LLMInteraction.swift */; }; F42AB1E52B6383F9002E13A6 /* LLMOpenAITokenOnboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42AB1E42B6383F9002E13A6 /* LLMOpenAITokenOnboarding.swift */; }; F42AB1EC2B6DBF21002E13A6 /* SummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42AB1EB2B6DBF20002E13A6 /* SummaryView.swift */; }; - F4F4F8812B8C6FC5008FBEED /* Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4F8802B8C6FC5008FBEED /* Elements.swift */; }; + F4F4F8812B8C6FC5008FBEED /* ViewElements.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4F8802B8C6FC5008FBEED /* ViewElements.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -152,18 +118,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 27FA298F2A388E9B009CAC45 /* ModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalView.swift; sourceTree = ""; }; 2F1AC9DE2B4E840E00C24973 /* Intake.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Intake.docc; sourceTree = ""; }; 2F4E237D2989A2FE0013F3D9 /* LaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTests.swift; sourceTree = ""; }; 2F4E23822989D51F0013F3D9 /* IntakeTestingSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeTestingSetup.swift; sourceTree = ""; }; - 2F4FC8D629EE69D300BFFE26 /* MockUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUpload.swift; sourceTree = ""; }; 2F5E32BC297E05EA003432F8 /* IntakeDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeDelegate.swift; sourceTree = ""; }; 2F6025CA29BBE70F0045459E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 2FA0BFEC2ACC977500E0EF83 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; 2FAEC07F297F583900C11C42 /* Intake.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Intake.entitlements; sourceTree = ""; }; 2FC94CD4298B0A1D009C8209 /* Intake.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Intake.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 = ""; }; 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 = ""; }; @@ -175,18 +138,10 @@ 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 /* IntakeTaskContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntakeTaskContext.swift; sourceTree = ""; }; - 2FE5DC4C29EDD7FA004B9AB4 /* IntakeScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntakeScheduler.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 /* IntakeStandard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntakeStandard.swift; sourceTree = ""; }; 3C89F66C2B9D948B00A4F52D /* PatientInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatientInfo.swift; sourceTree = ""; }; - 3CD23D602BA137D500AB9914 /* Documentation.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Documentation.docc; sourceTree = ""; }; - 3CD23D632BA14D6D00AB9914 /* NinasTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NinasTests.swift; sourceTree = ""; }; + 3CD23D602BA137D500AB9914 /* Intake.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Intake.docc; sourceTree = ""; }; + 3CD23D632BA14D6D00AB9914 /* PatientInformationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatientInformationTests.swift; sourceTree = ""; }; 510CAAF02BA0DFFB00872B1A /* MedicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MedicationTests.swift; sourceTree = ""; }; 511827952B740191002033A0 /* SurgeryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurgeryView.swift; sourceTree = ""; }; 5142133D2BA367F70012AFD7 /* SurgeryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurgeryTests.swift; sourceTree = ""; }; @@ -200,10 +155,6 @@ 51A360132B9648A7004E7E12 /* SurgeryLLMAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurgeryLLMAssistant.swift; sourceTree = ""; }; 51A360152B965819004E7E12 /* AllergyLLMAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllergyLLMAssistant.swift; sourceTree = ""; }; 51A360172B9659AE004E7E12 /* MedicalHistoryLLMAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MedicalHistoryLLMAssistant.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 = ""; }; - 56F6F29F2AB441930022FE5A /* ContributionsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributionsList.swift; sourceTree = ""; }; 5A2B9F792B69AE2C005CA63F /* MedicalHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MedicalHistoryView.swift; sourceTree = ""; }; 5A2B9F842B69E06B005CA63F /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 5A2B9F852B69E06B005CA63F /* ResourceSelection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceSelection.swift; sourceTree = ""; }; @@ -239,10 +190,14 @@ 653A256128338800005D4D48 /* IntakeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntakeTests.swift; sourceTree = ""; }; 653A256728338800005D4D48 /* IntakeUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IntakeUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; - ACAA47802B571C7F0032D21F /* Questionnaire.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Questionnaire.json; sourceTree = ""; }; + 970157082BB924BF00335713 /* ExportView+ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ExportView+ShareSheet.swift"; sourceTree = ""; }; + 9701570D2BB925DE00335713 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = ""; }; + 9701570F2BB9260400335713 /* HealthDataModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthDataModels.swift; sourceTree = ""; }; + 970157112BB9266400335713 /* NavigationHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationHelpers.swift; sourceTree = ""; }; + 970157172BB929FC00335713 /* SettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButton.swift; sourceTree = ""; }; + 97FE2E9F2BB93182006A3B8E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 97FE2EA32BB93582006A3B8E /* StartButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartButton.swift; sourceTree = ""; }; + 97FE2EA52BB935A2006A3B8E /* LoadLastButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadLastButton.swift; sourceTree = ""; }; ACBC9D1C2BA2B3F6003E2581 /* AllergyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllergyTests.swift; sourceTree = ""; }; ACF862BD2B96E29600ACBA1E /* ExportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportView.swift; sourceTree = ""; }; ACFFA1CD2B8FD7190006E6D4 /* SmokingHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmokingHistory.swift; sourceTree = ""; }; @@ -251,7 +206,7 @@ F42AB1DE2B637C9C002E13A6 /* LLMInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LLMInteraction.swift; sourceTree = ""; }; F42AB1E42B6383F9002E13A6 /* LLMOpenAITokenOnboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LLMOpenAITokenOnboarding.swift; sourceTree = ""; }; F42AB1EB2B6DBF20002E13A6 /* SummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SummaryView.swift; sourceTree = ""; }; - F4F4F8802B8C6FC5008FBEED /* Elements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Elements.swift; sourceTree = ""; }; + F4F4F8802B8C6FC5008FBEED /* ViewElements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewElements.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -261,34 +216,19 @@ files = ( F42AB1D22B6379B5002E13A6 /* SpeziLLM in Frameworks */, 9733CFC62A8066DE001B7ABC /* SpeziOnboarding in Frameworks */, - 2FE5DC6429EDD883004B9AB4 /* SpeziAccount in Frameworks */, F42AB1D42B6379B5002E13A6 /* SpeziLLMLocal in Frameworks */, - 2FB099AF2A875DF100B20952 /* FirebaseAuth in Frameworks */, - 97D73D6A2AD860AD00B47FA0 /* SpeziFirebaseStorage in Frameworks */, 51805C182B81898700D17109 /* SpeziMedication in Frameworks */, F42AB1D82B6379B5002E13A6 /* SpeziLLMOpenAI in Frameworks */, - 2FE5DC6729EDD894004B9AB4 /* SpeziContact in Frameworks */, 5A0C1A1B2B69691000120506 /* SpeziFHIRHealthKit in Frameworks */, 5A0C1A192B69691000120506 /* SpeziFHIR in Frameworks */, - 2FE5DC8429EDD934004B9AB4 /* SpeziQuestionnaire in Frameworks */, - 2FB099B32A875DF100B20952 /* FirebaseFirestoreSwift in Frameworks */, - 5661551D2AB8384200209B80 /* SwiftPackageList in Frameworks */, - 2FB099B12A875DF100B20952 /* FirebaseFirestore in Frameworks */, - A9D83F962B083794000D0C78 /* SpeziFirebaseAccountStorage in Frameworks */, F42AB1D62B6379B5002E13A6 /* SpeziLLMLocalDownload in Frameworks */, 2FB099B62A875E2B00B20952 /* HealthKitOnFHIR in Frameworks */, 2FE5DC8A29EDD972004B9AB4 /* SpeziLocalStorage in Frameworks */, 2FE5DC8C29EDD972004B9AB4 /* SpeziSecureStorage in Frameworks */, - 2FE5DC7529EDD8E6004B9AB4 /* SpeziFirebaseAccount in Frameworks */, - 9739A0C62AD7B5730084BEA5 /* FirebaseStorage in Frameworks */, - 2FF53D8B2A8725DE00042B76 /* SpeziMockWebService in Frameworks */, 2FE5DC7229EDD8D3004B9AB4 /* SpeziHealthKit in Frameworks */, 2F49B7762980407C00BCB272 /* Spezi in Frameworks */, 2FE5DC8F29EDD980004B9AB4 /* SpeziViews in Frameworks */, 5A2B9F8A2B69E0AF005CA63F /* SpeziFHIRMockPatients in Frameworks */, - 2F3D4ABC2A4E7C290068FB2F /* SpeziScheduler in Frameworks */, - 2FE5DC7929EDD8E6004B9AB4 /* SpeziFirestore in Frameworks */, - 2FE5DC7729EDD8E6004B9AB4 /* SpeziFirebaseConfiguration in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -311,14 +251,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 2F4FC8D529EE69BE00BFFE26 /* MockUpload */ = { - isa = PBXGroup; - children = ( - 2F4FC8D629EE69D300BFFE26 /* MockUpload.swift */, - ); - path = MockUpload; - sourceTree = ""; - }; 2FC9759D2978E30800BA99FE /* Supporting Files */ = { isa = PBXGroup; children = ( @@ -330,21 +262,12 @@ 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 */, ); @@ -358,28 +281,12 @@ 653A255428338800005D4D48 /* Assets.xcassets */, 2FA0BFEC2ACC977500E0EF83 /* Localizable.xcstrings */, 2FE5DC2C29EDD78E004B9AB4 /* ConsentDocument.md */, - ACAA47802B571C7F0032D21F /* Questionnaire.json */, - 2FE5DC5529EDD811004B9AB4 /* SocialSupportQuestionnaire.json */, 637BE1D42B572B4800EA19C6 /* Questionnaire.json.license */, - 3CD23D602BA137D500AB9914 /* Documentation.docc */, + 3CD23D602BA137D500AB9914 /* Intake.docc */, ); path = Resources; sourceTree = ""; }; - 2FE5DC3B29EDD7D0004B9AB4 /* Schedule */ = { - isa = PBXGroup; - children = ( - 2FE5DC4D29EDD7FA004B9AB4 /* Bundle+Questionnaire.swift */, - 2FE5DC4929EDD7FA004B9AB4 /* EventContext.swift */, - 2FE5DC4A29EDD7FA004B9AB4 /* EventContextView.swift */, - 2FE5DC4829EDD7FA004B9AB4 /* ScheduleView.swift */, - 2FE5DC4C29EDD7FA004B9AB4 /* IntakeScheduler.swift */, - 2FE5DC4B29EDD7FA004B9AB4 /* IntakeTaskContext.swift */, - 27FA298F2A388E9B009CAC45 /* ModalView.swift */, - ); - path = Schedule; - sourceTree = ""; - }; 2FE5DC3C29EDD7DA004B9AB4 /* SharedContext */ = { isa = PBXGroup; children = ( @@ -392,6 +299,7 @@ 2FE5DC3D29EDD7E4004B9AB4 /* Helper */ = { isa = PBXGroup; children = ( + F4F4F8802B8C6FC5008FBEED /* ViewElements.swift */, 2FE5DC4229EDD7F2004B9AB4 /* Binding+Negate.swift */, 2FE5DC4329EDD7F2004B9AB4 /* Bundle+Image.swift */, 2FE5DC4429EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift */, @@ -399,64 +307,49 @@ path = Helper; sourceTree = ""; }; - 3C89F6682B9D939500A4F52D /* General Data View */ = { + 3C89F6682B9D939500A4F52D /* PatientView */ = { isa = PBXGroup; children = ( 3C89F66C2B9D948B00A4F52D /* PatientInfo.swift */, + 5AEA5F412B90710B00F1577A /* EditPatient.swift */, ); - path = "General Data View"; + path = PatientView; sourceTree = ""; }; - 511827942B740191002033A0 /* Surgery */ = { + 511827942B740191002033A0 /* SurgeryView */ = { isa = PBXGroup; children = ( 511827952B740191002033A0 /* SurgeryView.swift */, 51A360132B9648A7004E7E12 /* SurgeryLLMAssistant.swift */, ); - path = Surgery; + path = SurgeryView; sourceTree = ""; }; - 519E830A2B7C4F1600A2D92D /* Medication View */ = { + 519E830A2B7C4F1600A2D92D /* MedicationView */ = { isa = PBXGroup; children = ( - 51805C112B81853700D17109 /* IntakeMedication.swift */, - 51805C192B818A1A00D17109 /* IntakeDosage.swift */, + 97FE2EA82BB9410F006A3B8E /* Models */, 51805C142B81857100D17109 /* IntakeMedicationViewModel.swift */, - 51805C1C2B818A4400D17109 /* IntakeMedicationInstance.swift */, 51A027662B82CDA300A195C8 /* MedicationContentView.swift */, 51A3600F2B944517004E7E12 /* MedicationLLMAssistant.swift */, ); - path = "Medication View"; - sourceTree = ""; - }; - 56F6F29E2AB441640022FE5A /* Contributions */ = { - isa = PBXGroup; - children = ( - 56F6F29F2AB441930022FE5A /* ContributionsList.swift */, - 5680DD382AB8983D004E6D4A /* PackageCell.swift */, - 566155282AB8447C00209B80 /* Package+LicenseType.swift */, - 5661552D2AB854C000209B80 /* PackageHelper.swift */, - ); - path = Contributions; + path = MedicationView; sourceTree = ""; }; - 5A2B9F762B69AE07005CA63F /* Medical History */ = { + 5A2B9F762B69AE07005CA63F /* MedicalHistoryView */ = { isa = PBXGroup; children = ( 5A2B9F792B69AE2C005CA63F /* MedicalHistoryView.swift */, 51A360172B9659AE004E7E12 /* MedicalHistoryLLMAssistant.swift */, ); - path = "Medical History"; + path = MedicalHistoryView; sourceTree = ""; }; - 5A2B9F832B69E06B005CA63F /* Settings */ = { + 5A2B9F832B69E06B005CA63F /* SettingsView */ = { isa = PBXGroup; children = ( - 5A2B9F842B69E06B005CA63F /* SettingsView.swift */, - 5A2B9F852B69E06B005CA63F /* ResourceSelection.swift */, ); - name = Settings; - path = Intake/Settings; + path = SettingsView; sourceTree = ""; }; 5A2B9F8D2B69E286005CA63F /* Mock Data */ = { @@ -487,7 +380,7 @@ path = MockPatients; sourceTree = ""; }; - 5A2B9FB32B6AFE1F005CA63F /* Allergy Records */ = { + 5A2B9FB32B6AFE1F005CA63F /* AllergyView */ = { isa = PBXGroup; children = ( 5A2B9FB52B6AFE5D005CA63F /* AllergyRecords.swift */, @@ -498,13 +391,13 @@ 5AEA5F462B93034A00F1577A /* ReactionPDF.swift */, 51A360152B965819004E7E12 /* AllergyLLMAssistant.swift */, ); - path = "Allergy Records"; + path = AllergyView; sourceTree = ""; }; 653A2544283387FE005D4D48 = { isa = PBXGroup; children = ( - 5A2B9F832B69E06B005CA63F /* Settings */, + 97FE2E9F2BB93182006A3B8E /* README.md */, 2FC94CD4298B0A1D009C8209 /* Intake.xctestplan */, 653A254F283387FE005D4D48 /* Intake */, 653A256028338800005D4D48 /* IntakeTests */, @@ -527,34 +420,28 @@ 653A254F283387FE005D4D48 /* Intake */ = { isa = PBXGroup; children = ( - 3C89F6682B9D939500A4F52D /* General Data View */, - ACF862BC2B96E28400ACBA1E /* Export */, - F4F4F8802B8C6FC5008FBEED /* Elements.swift */, - 519E830A2B7C4F1600A2D92D /* Medication View */, - 511827942B740191002033A0 /* Surgery */, - AC2A17272B70684D00F560D0 /* SocialHistory */, - 5A2B9FB32B6AFE1F005CA63F /* Allergy Records */, + 9701570B2BB925CC00335713 /* Models */, + 2FE5DC2829EDD398004B9AB4 /* Onboarding */, + 970157162BB929DB00335713 /* HomeView */, + 3C89F6682B9D939500A4F52D /* PatientView */, + F42AB1DA2B637C5F002E13A6 /* ChiefComplaintView */, + 5A2B9F762B69AE07005CA63F /* MedicalHistoryView */, + 519E830A2B7C4F1600A2D92D /* MedicationView */, + 5A2B9FB32B6AFE1F005CA63F /* AllergyView */, + 511827942B740191002033A0 /* SurgeryView */, + AC2A17272B70684D00F560D0 /* SocialHistoryView */, + ACF862BC2B96E28400ACBA1E /* ExportView */, + 5A2B9F832B69E06B005CA63F /* SettingsView */, 5A2B9F8D2B69E286005CA63F /* Mock Data */, - 5A2B9F762B69AE07005CA63F /* Medical History */, + 2FE5DC3D29EDD7E4004B9AB4 /* Helper */, + 2FE5DC3C29EDD7DA004B9AB4 /* SharedContext */, + 2FE5DC2D29EDD792004B9AB4 /* Resources */, + 2FC9759D2978E30800BA99FE /* Supporting Files */, 653A2550283387FE005D4D48 /* Intake.swift */, 2F5E32BC297E05EA003432F8 /* IntakeDelegate.swift */, 2FF53D8C2A8729D600042B76 /* IntakeStandard.swift */, 2F4E23822989D51F0013F3D9 /* IntakeTestingSetup.swift */, 2FC975A72978F11A00BA99FE /* Home.swift */, - F42AB1DA2B637C5F002E13A6 /* ChiefComplaint */, - A9720E412ABB68B300872D23 /* Account */, - 2FE5DC2829EDD398004B9AB4 /* Onboarding */, - 2FE5DC3B29EDD7D0004B9AB4 /* Schedule */, - 2FE5DC2729EDD38D004B9AB4 /* Contacts */, - 56F6F29E2AB441640022FE5A /* Contributions */, - 2F4FC8D529EE69BE00BFFE26 /* MockUpload */, - 2FE5DC3C29EDD7DA004B9AB4 /* SharedContext */, - 2FE5DC3D29EDD7E4004B9AB4 /* Helper */, - 2FE5DC2D29EDD792004B9AB4 /* Resources */, - 2FC9759D2978E30800BA99FE /* Supporting Files */, - 5AEA5F3A2B90081B00F1577A /* ScrollablePDF.swift */, - 5AAB83A62B9C04E70008407A /* LLMFiltering.swift */, - 5AEA5F412B90710B00F1577A /* EditPatient.swift */, ); path = Intake; sourceTree = ""; @@ -573,7 +460,7 @@ 2F4E237D2989A2FE0013F3D9 /* LaunchTests.swift */, 510CAAF02BA0DFFB00872B1A /* MedicationTests.swift */, ACBC9D1C2BA2B3F6003E2581 /* AllergyTests.swift */, - 3CD23D632BA14D6D00AB9914 /* NinasTests.swift */, + 3CD23D632BA14D6D00AB9914 /* PatientInformationTests.swift */, 5142133D2BA367F70012AFD7 /* SurgeryTests.swift */, 5142133F2BA368140012AFD7 /* ConditionsTests.swift */, ); @@ -587,34 +474,67 @@ name = Frameworks; sourceTree = ""; }; - A9720E412ABB68B300872D23 /* Account */ = { + 9701570B2BB925CC00335713 /* Models */ = { isa = PBXGroup; children = ( - A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */, - A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */, - A9DFE8A82ABE551400428242 /* AccountButton.swift */, + 9701570D2BB925DE00335713 /* DataStore.swift */, + 9701570F2BB9260400335713 /* HealthDataModels.swift */, + 970157112BB9266400335713 /* NavigationHelpers.swift */, + 5AAB83A62B9C04E70008407A /* LLMFiltering.swift */, ); - path = Account; + path = Models; sourceTree = ""; }; - AC2A17272B70684D00F560D0 /* SocialHistory */ = { + 970157162BB929DB00335713 /* HomeView */ = { + isa = PBXGroup; + children = ( + 97FE2EA72BB93C6C006A3B8E /* Settings */, + 97FE2EA52BB935A2006A3B8E /* LoadLastButton.swift */, + 97FE2EA32BB93582006A3B8E /* StartButton.swift */, + ); + path = HomeView; + sourceTree = ""; + }; + 97FE2EA72BB93C6C006A3B8E /* Settings */ = { + isa = PBXGroup; + children = ( + 970157172BB929FC00335713 /* SettingsButton.swift */, + 5A2B9F842B69E06B005CA63F /* SettingsView.swift */, + 5A2B9F852B69E06B005CA63F /* ResourceSelection.swift */, + ); + path = Settings; + sourceTree = ""; + }; + 97FE2EA82BB9410F006A3B8E /* Models */ = { + isa = PBXGroup; + children = ( + 51805C112B81853700D17109 /* IntakeMedication.swift */, + 51805C192B818A1A00D17109 /* IntakeDosage.swift */, + 51805C1C2B818A4400D17109 /* IntakeMedicationInstance.swift */, + ); + path = Models; + sourceTree = ""; + }; + AC2A17272B70684D00F560D0 /* SocialHistoryView */ = { isa = PBXGroup; children = ( ACFFA1D02B8FD8BB0006E6D4 /* MenstrualHistory.swift */, ACFFA1CD2B8FD7190006E6D4 /* SmokingHistory.swift */, ); - path = SocialHistory; + path = SocialHistoryView; sourceTree = ""; }; - ACF862BC2B96E28400ACBA1E /* Export */ = { + ACF862BC2B96E28400ACBA1E /* ExportView */ = { isa = PBXGroup; children = ( + 5AEA5F3A2B90081B00F1577A /* ScrollablePDF.swift */, ACF862BD2B96E29600ACBA1E /* ExportView.swift */, + 970157082BB924BF00335713 /* ExportView+ShareSheet.swift */, ); - path = Export; + path = ExportView; sourceTree = ""; }; - F42AB1DA2B637C5F002E13A6 /* ChiefComplaint */ = { + F42AB1DA2B637C5F002E13A6 /* ChiefComplaintView */ = { isa = PBXGroup; children = ( F42AB1E12B637CAC002E13A6 /* OpenAI */, @@ -623,7 +543,7 @@ F42AB1DE2B637C9C002E13A6 /* LLMInteraction.swift */, F42AB1EB2B6DBF20002E13A6 /* SummaryView.swift */, ); - path = ChiefComplaint; + path = ChiefComplaintView; sourceTree = ""; }; F42AB1E12B637CAC002E13A6 /* OpenAI */ = { @@ -654,27 +574,12 @@ name = Intake; packageProductDependencies = ( 2F49B7752980407B00BCB272 /* Spezi */, - 2FE5DC6329EDD883004B9AB4 /* SpeziAccount */, - 2FE5DC6629EDD894004B9AB4 /* SpeziContact */, 2FE5DC7129EDD8D3004B9AB4 /* SpeziHealthKit */, - 2FE5DC7429EDD8E6004B9AB4 /* SpeziFirebaseAccount */, - 2FE5DC7629EDD8E6004B9AB4 /* SpeziFirebaseConfiguration */, - 2FE5DC7829EDD8E6004B9AB4 /* SpeziFirestore */, - 2FE5DC8329EDD934004B9AB4 /* SpeziQuestionnaire */, 2FE5DC8929EDD972004B9AB4 /* SpeziLocalStorage */, 2FE5DC8B29EDD972004B9AB4 /* SpeziSecureStorage */, 2FE5DC8E29EDD980004B9AB4 /* SpeziViews */, - 2F3D4ABB2A4E7C290068FB2F /* SpeziScheduler */, 2FE5DC8029EDD91D004B9AB4 /* SpeziOnboarding */, - 2FF53D8A2A8725DE00042B76 /* SpeziMockWebService */, - 2FB099AE2A875DF100B20952 /* FirebaseAuth */, - 2FB099B02A875DF100B20952 /* FirebaseFirestore */, - 2FB099B22A875DF100B20952 /* FirebaseFirestoreSwift */, 2FB099B52A875E2B00B20952 /* HealthKitOnFHIR */, - 5661551C2AB8384200209B80 /* SwiftPackageList */, - 9739A0C52AD7B5730084BEA5 /* FirebaseStorage */, - 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */, - A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */, F42AB1D12B6379B5002E13A6 /* SpeziLLM */, F42AB1D32B6379B5002E13A6 /* SpeziLLMLocal */, F42AB1D52B6379B5002E13A6 /* SpeziLLMLocalDownload */, @@ -762,21 +667,13 @@ 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" */, F42AB1D02B6379B5002E13A6 /* XCRemoteSwiftPackageReference "SpeziLLM" */, 5A0C1A162B69667B00120506 /* XCRemoteSwiftPackageReference "SpeziFHIR" */, 51805C162B81898700D17109 /* XCRemoteSwiftPackageReference "SpeziMedication" */, @@ -799,23 +696,14 @@ files = ( 5A2B9FA52B69E32A005CA63F /* Milton509_Ortiz186_d66b5418-06cb-fc8a-8c13-85685b6ac939.json in Resources */, 2FC3439229EE634B002D773C /* ConsentDocument.md in Resources */, - 5A2B9FA02B69E32A005CA63F /* Beatris270_Bogan287_5b3645de-a2d0-d016-0839-bab3757c4c58.json.license in Resources */, - 637BE1D52B572B4800EA19C6 /* Questionnaire.json.license in Resources */, - 5A2B9FA92B69E32A005CA63F /* Milton509_Ortiz186_d66b5418-06cb-fc8a-8c13-85685b6ac939.json.license in Resources */, 5A2B9FA32B69E32A005CA63F /* Allen322_Ferry570_ad134528-56a5-35fd-c37f-466ff119c625.json in Resources */, - 5A2B9FA82B69E32A005CA63F /* Edythe31_Morar593_9c3df38a-d3b7-2198-3898-51f9153d023d.json.license in Resources */, 653A255528338800005D4D48 /* Assets.xcassets in Resources */, 5A2B9F9F2B69E32A005CA63F /* Edythe31_Morar593_9c3df38a-d3b7-2198-3898-51f9153d023d.json in Resources */, 5A2B9FA62B69E32A005CA63F /* Gonzalo160_Duenas839_ed70a28f-30b2-acb7-658a-8b340dadd685.json in Resources */, - 2FC3439029EE6346002D773C /* SocialSupportQuestionnaire.json in Resources */, - 5A2B9FA22B69E32A005CA63F /* Allen322_Ferry570_ad134528-56a5-35fd-c37f-466ff119c625.json.license in Resources */, 5A2B9F9E2B69E32A005CA63F /* Beatris270_Bogan287_5b3645de-a2d0-d016-0839-bab3757c4c58.json in Resources */, 2FA0BFED2ACC977500E0EF83 /* Localizable.xcstrings in Resources */, 2F6025CB29BBE70F0045459E /* GoogleService-Info.plist in Resources */, - 5A2B9FA12B69E32A005CA63F /* Gonzalo160_Duenas839_ed70a28f-30b2-acb7-658a-8b340dadd685.json.license in Resources */, 5A2B9FA72B69E32A005CA63F /* Jacklyn830_Veum823_e0e1f21a-22a7-d166-7bb1-63f6bbce1a32.json in Resources */, - 5A2B9FA42B69E32A005CA63F /* Jacklyn830_Veum823_e0e1f21a-22a7-d166-7bb1-63f6bbce1a32.json.license in Resources */, - ACAA47812B571C800032D21F /* Questionnaire.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -863,71 +751,61 @@ files = ( ACDF32ED2B9D0F4300B127E2 /* MenstrualHistory.swift in Sources */, 2FE5DC4129EDD7EE004B9AB4 /* StorageKeys.swift in Sources */, - 2FE5DCB129EE6107004B9AB4 /* AccountOnboarding.swift in Sources */, - 2F4FC8D729EE69D300BFFE26 /* MockUpload.swift in Sources */, 2FE5DC3A29EDD7CA004B9AB4 /* Welcome.swift in Sources */, F42AB1DF2B637C9D002E13A6 /* LLMInteraction.swift in Sources */, 51A360162B965819004E7E12 /* AllergyLLMAssistant.swift in Sources */, 3C89F66D2B9D948B00A4F52D /* PatientInfo.swift in Sources */, + 97FE2EA62BB935A2006A3B8E /* LoadLastButton.swift in Sources */, + 97FE2EA42BB93582006A3B8E /* StartButton.swift in Sources */, 2FE5DC3829EDD7CA004B9AB4 /* InterestingModules.swift in Sources */, 2FE5DC3529EDD7CA004B9AB4 /* Consent.swift in Sources */, 51A360182B9659AE004E7E12 /* MedicalHistoryLLMAssistant.swift in Sources */, 2FE5DC4529EDD7F2004B9AB4 /* Binding+Negate.swift in Sources */, 2FC975A82978F11A00BA99FE /* Home.swift in Sources */, - 2FE5DC4E29EDD7FA004B9AB4 /* ScheduleView.swift in Sources */, 51805C1D2B818A4400D17109 /* IntakeMedicationInstance.swift in Sources */, - A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */, 2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */, 2F1AC9DF2B4E840E00C24973 /* Intake.docc in Sources */, 2FF53D8D2A8729D600042B76 /* IntakeStandard.swift in Sources */, 2FE5DC4729EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift in Sources */, 5A2B9FAB2B69E430005CA63F /* FHIRStore+Extensions.swift in Sources */, 5AEA5F472B93034A00F1577A /* ReactionPDF.swift in Sources */, - A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */, 5AEA5F2C2B8680F300F1577A /* AddAllergy.swift in Sources */, 2FE5DC4029EDD7EE004B9AB4 /* FeatureFlags.swift in Sources */, 2FE5DC4629EDD7F2004B9AB4 /* Bundle+Image.swift in Sources */, 51A360142B9648A7004E7E12 /* SurgeryLLMAssistant.swift in Sources */, ACFFA1CE2B8FD7190006E6D4 /* SmokingHistory.swift in Sources */, + 970157182BB929FC00335713 /* SettingsButton.swift in Sources */, F42AB1DC2B637C8C002E13A6 /* LLMOnboardingView.swift in Sources */, - 2FE5DC4F29EDD7FA004B9AB4 /* EventContext.swift in Sources */, - 2FE5DC5029EDD7FA004B9AB4 /* EventContextView.swift in Sources */, 5A2B9F872B69E06B005CA63F /* ResourceSelection.swift in Sources */, 2F4E23832989D51F0013F3D9 /* IntakeTestingSetup.swift in Sources */, 51805C152B81857100D17109 /* IntakeMedicationViewModel.swift in Sources */, 5A2B9F862B69E06B005CA63F /* SettingsView.swift in Sources */, 51805C1A2B818A1A00D17109 /* IntakeDosage.swift in Sources */, - F4F4F8812B8C6FC5008FBEED /* Elements.swift in Sources */, - 2FE5DC5329EDD7FA004B9AB4 /* Bundle+Questionnaire.swift in Sources */, + F4F4F8812B8C6FC5008FBEED /* ViewElements.swift in Sources */, 5A2B9FB62B6AFE5D005CA63F /* AllergyRecords.swift in Sources */, - 2FE5DC5129EDD7FA004B9AB4 /* IntakeTaskContext.swift in Sources */, 51A360102B944517004E7E12 /* MedicationLLMAssistant.swift in Sources */, - 56F6F2A02AB441930022FE5A /* ContributionsList.swift in Sources */, 5A2B9F7A2B69AE2C005CA63F /* MedicalHistoryView.swift in Sources */, + 9701570E2BB925DE00335713 /* DataStore.swift in Sources */, 5A2B9F8F2B69E286005CA63F /* MockData.swift in Sources */, 5A2B9FBC2B6C7B29005CA63F /* ReactionView.swift in Sources */, - 566155292AB8447C00209B80 /* Package+LicenseType.swift in Sources */, - 3CD23D612BA137D500AB9914 /* Documentation.docc in Sources */, - 5680DD392AB8983D004E6D4A /* PackageCell.swift in Sources */, + 3CD23D612BA137D500AB9914 /* Intake.docc in Sources */, 5AEA5F422B90710B00F1577A /* EditPatient.swift in Sources */, 5AAB83A72B9C04E70008407A /* LLMFiltering.swift in Sources */, F42AB1EC2B6DBF21002E13A6 /* SummaryView.swift in Sources */, 2F5E32BD297E05EA003432F8 /* IntakeDelegate.swift in Sources */, + 970157102BB9260400335713 /* HealthDataModels.swift in Sources */, 5AAB83B32B9EBB070008407A /* ReactionSectionView.swift in Sources */, 511827962B740192002033A0 /* SurgeryView.swift in Sources */, - 2FE5DC5229EDD7FA004B9AB4 /* IntakeScheduler.swift in Sources */, + 970157092BB924BF00335713 /* ExportView+ShareSheet.swift in Sources */, 5AEA5F3B2B90081B00F1577A /* ScrollablePDF.swift in Sources */, F42AB1E52B6383F9002E13A6 /* LLMOpenAITokenOnboarding.swift in Sources */, 51805C122B81853800D17109 /* IntakeMedication.swift in Sources */, - A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */, ACF862BE2B96E29600ACBA1E /* ExportView.swift in Sources */, 51A027672B82CDA300A195C8 /* MedicationContentView.swift in Sources */, + 970157122BB9266400335713 /* NavigationHelpers.swift in Sources */, 653A2551283387FE005D4D48 /* Intake.swift in Sources */, 2FE5DC3629EDD7CA004B9AB4 /* HealthKitPermissions.swift in Sources */, - 5661552E2AB854C000209B80 /* PackageHelper.swift in Sources */, 5AEA5F212B82DDD000F1577A /* LLMAssistantView.swift in Sources */, - 27FA29902A388E9B009CAC45 /* ModalView.swift in Sources */, - 2FE5DC2629EDD38A004B9AB4 /* Contacts.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -948,7 +826,7 @@ 510CAAF12BA0DFFB00872B1A /* MedicationTests.swift in Sources */, 5142133E2BA367F70012AFD7 /* SurgeryTests.swift in Sources */, 2F4E237E2989A2FE0013F3D9 /* LaunchTests.swift in Sources */, - 3CD23D642BA14D6D00AB9914 /* NinasTests.swift in Sources */, + 3CD23D642BA14D6D00AB9914 /* PatientInformationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1465,14 +1343,6 @@ /* 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"; @@ -1489,22 +1359,6 @@ 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"; @@ -1513,22 +1367,6 @@ 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"; @@ -1545,14 +1383,6 @@ 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"; @@ -1569,14 +1399,6 @@ minimumVersion = 0.3.5; }; }; - 2FE750CA2A87240100723EAE /* XCRemoteSwiftPackageReference "SpeziMockWebService" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/StanfordSpezi/SpeziMockWebService.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; - }; - }; 51805C162B81898700D17109 /* XCRemoteSwiftPackageReference "SpeziMedication" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/StanfordSpezi/SpeziMedication.git"; @@ -1620,76 +1442,26 @@ /* 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; }; - 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" */; @@ -1715,21 +1487,11 @@ package = 2FE5DC9A29EDD9EF004B9AB4 /* XCRemoteSwiftPackageReference "XCTHealthKit" */; productName = XCTHealthKit; }; - 2FF53D8A2A8725DE00042B76 /* SpeziMockWebService */ = { - isa = XCSwiftPackageProductDependency; - package = 2FE750CA2A87240100723EAE /* XCRemoteSwiftPackageReference "SpeziMockWebService" */; - productName = SpeziMockWebService; - }; 51805C172B81898700D17109 /* SpeziMedication */ = { isa = XCSwiftPackageProductDependency; package = 51805C162B81898700D17109 /* XCRemoteSwiftPackageReference "SpeziMedication" */; productName = SpeziMedication; }; - 5661551C2AB8384200209B80 /* SwiftPackageList */ = { - isa = XCSwiftPackageProductDependency; - package = 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */; - productName = SwiftPackageList; - }; 566155212AB83CF200209B80 /* SwiftPackageListJSONPlugin */ = { isa = XCSwiftPackageProductDependency; package = 5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */; @@ -1750,21 +1512,6 @@ package = 5A0C1A162B69667B00120506 /* XCRemoteSwiftPackageReference "SpeziFHIR" */; productName = SpeziFHIRMockPatients; }; - 9739A0C52AD7B5730084BEA5 /* FirebaseStorage */ = { - isa = XCSwiftPackageProductDependency; - package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebaseStorage; - }; - 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */ = { - isa = XCSwiftPackageProductDependency; - package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; - productName = SpeziFirebaseStorage; - }; - A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */ = { - isa = XCSwiftPackageProductDependency; - package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */; - productName = SpeziFirebaseAccountStorage; - }; F42AB1D12B6379B5002E13A6 /* SpeziLLM */ = { isa = XCSwiftPackageProductDependency; package = F42AB1D02B6379B5002E13A6 /* XCRemoteSwiftPackageReference "SpeziLLM" */; diff --git a/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 25996fa..75070c0 100644 --- a/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Intake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,24 +1,6 @@ { - "originHash" : "cdbe60a3382a8a962c7fcc00210e56e13314cb3e246d0e01fe8e25a1268623d8", + "originHash" : "32b57526de16a25fdf6722befe657da59ae2666c82d4cc175cdf34510a1f05b8", "pins" : [ - { - "identity" : "abseil-cpp-binary", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/abseil-cpp-binary.git", - "state" : { - "revision" : "df308b8b46607675f2b9ec8e569109008f9155ce", - "version" : "1.2022062300.1" - } - }, - { - "identity" : "app-check", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/app-check.git", - "state" : { - "revision" : "3e464dad87dad2d29bb29a97836789bf0f8f67d2", - "version" : "10.18.1" - } - }, { "identity" : "fhirmodels", "kind" : "remoteSourceControl", @@ -28,60 +10,6 @@ "version" : "0.5.0" } }, - { - "identity" : "firebase-ios-sdk", - "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/firebase-ios-sdk.git", - "state" : { - "revision" : "be49849dcba96f2b5ee550d4eceb2c0fa27dade4", - "version" : "10.22.1" - } - }, - { - "identity" : "googleappmeasurement", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GoogleAppMeasurement.git", - "state" : { - "revision" : "482cfa4e5880f0a29f66ecfd60c5a62af28bd1f0", - "version" : "10.22.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" : "ea4cb5cc0c39c732b85386263116d2e2fdbbdc61", - "version" : "1.49.2" - } - }, - { - "identity" : "gtm-session-fetcher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/gtm-session-fetcher.git", - "state" : { - "revision" : "76135c9f4e1ac85459d5fec61b6f76ac47ab3a4c", - "version" : "3.3.1" - } - }, { "identity" : "healthkitonfhir", "kind" : "remoteSourceControl", @@ -91,24 +19,6 @@ "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" : "llama.cpp", "kind" : "remoteSourceControl", @@ -118,15 +28,6 @@ "version" : "0.2.1" } }, - { - "identity" : "nanopb", - "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/nanopb.git", - "state" : { - "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", - "version" : "2.30910.0" - } - }, { "identity" : "openai", "kind" : "remoteSourceControl", @@ -136,33 +37,6 @@ "version" : "0.2.6" } }, - { - "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" : "64512d0a0a5cc3e9d5b3fc5217c54f11d0dc044c", - "version" : "2.2.28" - } - }, - { - "identity" : "researchkitonfhir", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordBDHG/ResearchKitOnFHIR", - "state" : { - "revision" : "7c2efdcb17796fc9ee686900304dbbe9dd4aaf85", - "version" : "1.1.2" - } - }, { "identity" : "spezi", "kind" : "remoteSourceControl", @@ -172,15 +46,6 @@ "version" : "1.2.3" } }, - { - "identity" : "speziaccount", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziAccount.git", - "state" : { - "revision" : "a7d289ef3be54de62b25dc92e8f7ff1a0f093906", - "version" : "1.2.1" - } - }, { "identity" : "spezichat", "kind" : "remoteSourceControl", @@ -190,15 +55,6 @@ "version" : "0.1.9" } }, - { - "identity" : "spezicontact", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziContact.git", - "state" : { - "revision" : "494b776f8c98d771e4a609a1fb706097dba4c030", - "version" : "1.0.0" - } - }, { "identity" : "spezifhir", "kind" : "remoteSourceControl", @@ -208,15 +64,6 @@ "version" : "0.6.0" } }, - { - "identity" : "spezifirebase", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziFirebase.git", - "state" : { - "revision" : "e05e665b7da39aa399ecd7fba393aab49b8f3034", - "version" : "1.0.1" - } - }, { "identity" : "spezifoundation", "kind" : "remoteSourceControl", @@ -240,8 +87,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/StanfordSpezi/SpeziLLM", "state" : { - "revision" : "ca379106637c69fd0138360643d0129176a8ca6b", - "version" : "0.7.1" + "revision" : "dc37b91ed55c9d50eaf58e645d454cb62e3681d1", + "version" : "0.7.2" } }, { @@ -253,15 +100,6 @@ "version" : "0.4.1" } }, - { - "identity" : "spezimockwebservice", - "kind" : "remoteSourceControl", - "location" : "https://github.com/StanfordSpezi/SpeziMockWebService.git", - "state" : { - "revision" : "b18067d3499e630bbd995ef05a296ef8fdd42528", - "version" : "1.0.0" - } - }, { "identity" : "spezionboarding", "kind" : "remoteSourceControl", @@ -271,24 +109,6 @@ "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" : "spezispeech", "kind" : "remoteSourceControl", @@ -316,15 +136,6 @@ "version" : "1.3.1" } }, - { - "identity" : "swift-argument-parser", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser", - "state" : { - "revision" : "c8ed701b513cf5177118a175d85fbbbcd707ab41", - "version" : "1.3.0" - } - }, { "identity" : "swift-collections", "kind" : "remoteSourceControl", @@ -334,24 +145,6 @@ "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" : "65e8f29b2d63c4e38e736b25c27b83e012159be8", - "version" : "1.25.2" - } - }, { "identity" : "xctestextensions", "kind" : "remoteSourceControl", diff --git a/Intake/Account/AccountButton.swift b/Intake/Account/AccountButton.swift deleted file mode 100644 index c297d7a..0000000 --- a/Intake/Account/AccountButton.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/Account/AccountSetupHeader.swift b/Intake/Account/AccountSetupHeader.swift deleted file mode 100644 index d9e5203..0000000 --- a/Intake/Account/AccountSetupHeader.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/Account/AccountSheet.swift b/Intake/Account/AccountSheet.swift deleted file mode 100644 index d77450c..0000000 --- a/Intake/Account/AccountSheet.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/Allergy Records/AddAllergy.swift b/Intake/AllergyView/AddAllergy.swift similarity index 95% rename from Intake/Allergy Records/AddAllergy.swift rename to Intake/AllergyView/AddAllergy.swift index e56b862..4a3c4f3 100644 --- a/Intake/Allergy Records/AddAllergy.swift +++ b/Intake/AllergyView/AddAllergy.swift @@ -1,9 +1,3 @@ -// -// AddConditionView.swift -// Intake -// -// Created by Akash Gupta on 2/19/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University diff --git a/Intake/Allergy Records/AllergyLLMAssistant.swift b/Intake/AllergyView/AllergyLLMAssistant.swift similarity index 75% rename from Intake/Allergy Records/AllergyLLMAssistant.swift rename to Intake/AllergyView/AllergyLLMAssistant.swift index f3a3f99..e8516c4 100644 --- a/Intake/Allergy Records/AllergyLLMAssistant.swift +++ b/Intake/AllergyView/AllergyLLMAssistant.swift @@ -1,9 +1,4 @@ // -// AllergyLLMAssistant.swift -// Intake -// -// Created by Kate Callon on 3/4/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -18,35 +13,20 @@ import SpeziLLMLocal import SpeziLLMOpenAI import SwiftUI -// This class allows for the LLM to store output information in the allergyItem variable and it's equatable so the onChange function can recognize when there's been an update to allergy information. + +/// This class allows for the LLM to store output information in the allergyItem variable and it's equatable so the onChange function can recognize when there's been an update to allergy information. @Observable class AllergyItemBox: Equatable { var allergyItem: AllergyItem? - + init() {} - + static func == (lhs: AllergyItemBox, rhs: AllergyItemBox) -> Bool { lhs.allergyItem == rhs.allergyItem } } -// This function gathers current patient allergy information and inputs it into the LLM assistant system prompt. -func getCurrentPatientAllergy(allergyList: [AllergyItem]) -> String? { - var allergyDetails = "The patient has several allergies described in the next sentences." - - for allergy in allergyList { - let allergyName = allergy.allergy - if let allergyReaction = allergy.reaction.first?.reaction { - allergyDetails += "The patient has allergy \(allergyName) with the reaction \(allergyReaction).\n" - } else { - allergyDetails += "The patient has allergy \(allergyName).\n" - } - } - - return allergyDetails.isEmpty ? nil : allergyDetails -} - -// The Allergy LLM Assistant allows the patient to ask questions about their current allergies and add any additional allergies to their list. +/// The Allergy LLM Assistant allows the patient to ask questions about their current allergies and add any additional allergies to their list. struct UpdateAllergyFunction: LLMFunction { static let name: String = "update_allergies" static let description: String = """ @@ -58,12 +38,12 @@ struct UpdateAllergyFunction: LLMFunction { @Parameter(description: "The reaction of the allergy the patient wants to create.") var allergyReaction: String let allergyItemBox: AllergyItemBox - + init(allergyItemBox: AllergyItemBox) { self.allergyItemBox = allergyItemBox } - + func execute() async throws -> String? { let updatedAllergy = AllergyItem(allergy: allergyName, reaction: [ReactionItem(reaction: allergyReaction)]) allergyItemBox.allergyItem = updatedAllergy @@ -77,14 +57,14 @@ struct AllergyLLMAssistant: View { @Environment(NavigationPathWrapper.self) private var navigationPath @Environment(LLMOpenAITokenSaver.self) private var tokenSaver - @Binding var presentingAccount: Bool @LLMSessionProvider var session: LLMOpenAISession - - @State var showOnboarding = true + + @AppStorage(StorageKeys.llmOnboardingComplete) var showOnboarding = true @State var greeting = true @State var allergyItemBox: AllergyItemBox + var body: some View { @Bindable var data = data @@ -97,13 +77,13 @@ struct AllergyLLMAssistant: View { LLMOnboardingView(showOnboarding: $showOnboarding) } - .onAppear { + .task { checkToken() if let currentallergy = getCurrentPatientAllergy(allergyList: data.allergyData) { session.context.append( - systemMessage: currentallergy - ) + systemMessage: currentallergy + ) } if greeting { @@ -118,9 +98,9 @@ struct AllergyLLMAssistant: View { } } } - - init(presentingAccount: Binding) { - self._presentingAccount = presentingAccount + + + init() { let temporaryAllergyItemBox = AllergyItemBox() self.allergyItemBox = temporaryAllergyItemBox self._session = LLMSessionProvider( @@ -142,13 +122,31 @@ struct AllergyLLMAssistant: View { ) } + + /// This function gathers current patient allergy information and inputs it into the LLM assistant system prompt. + func getCurrentPatientAllergy(allergyList: [AllergyItem]) -> String? { + var allergyDetails = "The patient has several allergies described in the next sentences." + + for allergy in allergyList { + let allergyName = allergy.allergy + if let allergyReaction = allergy.reaction.first?.reaction { + allergyDetails += "The patient has allergy \(allergyName) with the reaction \(allergyReaction).\n" + } else { + allergyDetails += "The patient has allergy \(allergyName).\n" + } + } + + return allergyDetails.isEmpty ? nil : allergyDetails + } + private func checkToken() { showOnboarding = !tokenSaver.tokenPresent } } + #Preview { - LLMInteraction(presentingAccount: .constant(false)) + LLMInteraction() .previewWith { LLMRunner { LLMOpenAIPlatform() diff --git a/Intake/Allergy Records/AllergyRecords.swift b/Intake/AllergyView/AllergyRecords.swift similarity index 95% rename from Intake/Allergy Records/AllergyRecords.swift rename to Intake/AllergyView/AllergyRecords.swift index 30c7323..58f2c58 100644 --- a/Intake/Allergy Records/AllergyRecords.swift +++ b/Intake/AllergyView/AllergyRecords.swift @@ -1,7 +1,3 @@ -// MedicalHistoryView.swift -// Intake -// -// Created by Akash Gupta on 1/30/24. // // This source file is part of the Intake based on the Stanford Spezi Template Application project // @@ -18,9 +14,9 @@ import SpeziLLMOpenAI import SwiftUI struct ChatButton: View { - // Use @Binding to create a two-way binding to the parent view's showingChat state @Binding var showingChat: Bool + var body: some View { Button(action: { // Toggle the provided state @@ -37,6 +33,7 @@ struct ChatButton: View { } } + struct AllergyList: View { @Environment(FHIRStore.self) private var fhirStore @Environment(NavigationPathWrapper.self) private var navigationPath @@ -46,7 +43,6 @@ struct AllergyList: View { @State private var showingReaction = false @State private var selectedIndex = 0 @State private var showingChat = false - @State private var presentingAccount = false @State private var newAllergy = AllergyItem(allergy: "", reaction: []) @LLMSessionProvider var session: LLMOpenAISession @@ -91,8 +87,9 @@ struct AllergyList: View { } .navigationTitle("Allergies") .navigationBarItems(trailing: addAllergyButton) - .navigationBarItems(trailing: NavigationLink(destination: AllergyLLMAssistant(presentingAccount: $presentingAccount)) { - Text("Chat") + .navigationBarItems(trailing: NavigationLink(destination: AllergyLLMAssistant()) { + Image(systemName: "bubble") + .accessibilityLabel("Chat with LLM Assistant") }) } @@ -163,7 +160,6 @@ struct AllergyList: View { private func chatSheetView() -> some View { LLMAssistantView( - presentingAccount: .constant(false), pageTitle: .constant("Allergy Assistant"), initialQuestion: .constant("Do you have any questions about your allergies?"), prompt: .constant("Pretend you are a nurse. Your job is to help the patient understand what allergies they have.") diff --git a/Intake/Allergy Records/ReactionPDF.swift b/Intake/AllergyView/ReactionPDF.swift similarity index 95% rename from Intake/Allergy Records/ReactionPDF.swift rename to Intake/AllergyView/ReactionPDF.swift index 2cba1dd..e84a492 100644 --- a/Intake/Allergy Records/ReactionPDF.swift +++ b/Intake/AllergyView/ReactionPDF.swift @@ -1,9 +1,4 @@ // -// AddConditionView.swift -// Intake -// -// Created by Akash Gupta on 2/19/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -20,6 +15,8 @@ struct ReactionPDF: View { @State private var index: Int @Environment(DataStore.self) private var data @Binding private var showingReaction: Bool + + var body: some View { NavigationView { VStack { @@ -45,6 +42,7 @@ struct ReactionPDF: View { } } + init(index: Int, showingReaction: Binding) { self._index = State(initialValue: index) self._showingReaction = showingReaction diff --git a/Intake/Allergy Records/ReactionSectionView.swift b/Intake/AllergyView/ReactionSectionView.swift similarity index 94% rename from Intake/Allergy Records/ReactionSectionView.swift rename to Intake/AllergyView/ReactionSectionView.swift index 7ad3d80..4716963 100644 --- a/Intake/Allergy Records/ReactionSectionView.swift +++ b/Intake/AllergyView/ReactionSectionView.swift @@ -1,9 +1,4 @@ // -// ReactionSectionView.swift -// Intake -// -// Created by Akash Gupta on 3/10/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -20,6 +15,7 @@ struct ReactionSectionView: View { @Environment(DataStore.self) private var data var index: Int + var body: some View { Form { // Use Form instead of List Section(header: headerTitle) { @@ -50,6 +46,8 @@ struct ReactionSectionView: View { EditButton() } } + + func delete(at offsets: IndexSet) { data.allergyData[index].reaction.remove(atOffsets: offsets) } diff --git a/Intake/Allergy Records/ReactionView.swift b/Intake/AllergyView/ReactionView.swift similarity index 96% rename from Intake/Allergy Records/ReactionView.swift rename to Intake/AllergyView/ReactionView.swift index b127ac9..74c76c5 100644 --- a/Intake/Allergy Records/ReactionView.swift +++ b/Intake/AllergyView/ReactionView.swift @@ -1,23 +1,21 @@ // -// ReactionView.swift -// Intake -// -// Created by Akash Gupta on 2/1/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // // SPDX-License-Identifier: MIT // + import Foundation import SpeziFHIR import SwiftUI + struct ReactionView: View { @State private var reactionRecords: [ReactionItem] @State private var name: String + var body: some View { NavigationView { List { @@ -59,12 +57,14 @@ struct ReactionView: View { } } + init(reactionRecords: [ReactionItem], name: String) { self._reactionRecords = State(initialValue: reactionRecords) self._name = State(initialValue: name) } } + #Preview { ReactionView(reactionRecords: [ReactionItem(reaction: "hello")], name: "Diabetes") .previewWith { diff --git a/Intake/ChiefComplaint/LLMAssistantView.swift b/Intake/ChiefComplaintView/LLMAssistantView.swift similarity index 78% rename from Intake/ChiefComplaint/LLMAssistantView.swift rename to Intake/ChiefComplaintView/LLMAssistantView.swift index 18eb911..d1fdce0 100644 --- a/Intake/ChiefComplaint/LLMAssistantView.swift +++ b/Intake/ChiefComplaintView/LLMAssistantView.swift @@ -1,9 +1,4 @@ // -// LLMAssistantView.swift -// Intake -// -// Created by Akash Gupta on 2/18/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -18,16 +13,17 @@ import SpeziLLMLocal import SpeziLLMOpenAI import SwiftUI + struct LLMAssistantView: View { @Environment(LLMOpenAITokenSaver.self) var tokenSaver - @Binding var presentingAccount: Bool - @State var showOnboarding = true + @AppStorage(StorageKeys.llmOnboardingComplete) var showOnboarding = true @State var greeting = true @Binding var pageTitle: String @Binding var initialQuestion: String @Binding var prompt: String @LLMSessionProvider var session: LLMOpenAISession + var body: some View { NavigationView { LLMChatView( @@ -37,12 +33,7 @@ struct LLMAssistantView: View { LLMOnboardingView(showOnboarding: $showOnboarding) } .navigationTitle(pageTitle) - .toolbar { - if AccountButton.shouldDisplay { - AccountButton(isPresented: $presentingAccount) - } - } - .onAppear { + .task { checkToken() if greeting { @@ -54,8 +45,8 @@ struct LLMAssistantView: View { } } - init(presentingAccount: Binding, pageTitle: Binding, initialQuestion: Binding, prompt: Binding) { - self._presentingAccount = presentingAccount + + init(pageTitle: Binding, initialQuestion: Binding, prompt: Binding) { self._session = LLMSessionProvider( schema: LLMOpenAISchema( parameters: .init( @@ -68,14 +59,16 @@ struct LLMAssistantView: View { self._initialQuestion = initialQuestion self._prompt = prompt } + + private func checkToken() { showOnboarding = !tokenSaver.tokenPresent } } + #Preview { LLMAssistantView( - presentingAccount: .constant(false), pageTitle: .constant("Allergy Assistant"), initialQuestion: .constant("Do you have any questions about your allergies"), prompt: .constant("Pretend you are a nurse. Your job is to help the patient understand what allergies they have.") diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaintView/LLMInteraction.swift similarity index 85% rename from Intake/ChiefComplaint/LLMInteraction.swift rename to Intake/ChiefComplaintView/LLMInteraction.swift index cf88a5e..2e6e2ce 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaintView/LLMInteraction.swift @@ -1,18 +1,10 @@ // -// LLMInteraction.swift -// Intake -// -// Created by Nick Riedman on 1/25/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // // SPDX-License-Identifier: MIT // -// LLMInteraction is the core functionality of Chief Complaint. It initializes the LLM, uses a robust system prompt to ensure that the questions -// asked are specific and medically sound. It utilizes function calling to identify when enough information has been gathered about the patient -// to be helpful to a doctor. The function call import Foundation @@ -24,27 +16,10 @@ import SpeziLLMOpenAI import SwiftUI +/// LLMInteraction is the core functionality of Chief Complaint. It initializes the LLM, uses a robust system prompt to ensure that the questions +/// asked are specific and medically sound. It utilizes function calling to identify when enough information has been gathered about the patient +/// to be helpful to a doctor. struct LLMInteraction: View { - // I needed to disable this error because any order I tried would not work. Seems to be an issue with swiftlint - // swiftlint:disable type_contents_order - @State private var fullName: String = "" - @State private var firstName: String = "" - @State private var dob: String = "" - @State private var gender: String = "" - @State var showOnboarding = true - @State var greeting = true - @State var stringBox: StringBox = .init() - @State var showSheet = false - @Environment(LLMRunner.self) var runner: LLMRunner - @Environment(FHIRStore.self) private var fhirStore - @Environment(DataStore.self) private var data - @Environment(NavigationPathWrapper.self) private var navigationPath - - @Environment(LLMOpenAITokenSaver.self) private var tokenSaver - - @Binding var presentingAccount: Bool - @LLMSessionProvider var session: LLMOpenAISession - @Observable class StringBox: Equatable { var llmResponseSummary: String @@ -84,6 +59,25 @@ struct LLMInteraction: View { return nil } } + + + @State private var fullName: String = "" + @State private var firstName: String = "" + @State private var dob: String = "" + @State private var gender: String = "" + @AppStorage(StorageKeys.llmOnboardingComplete) var showOnboarding = true + @State var greeting = true + @State var stringBox: StringBox = .init() + @State var showSheet = false + @Environment(LLMRunner.self) var runner: LLMRunner + @Environment(FHIRStore.self) private var fhirStore + @Environment(DataStore.self) private var data + @Environment(NavigationPathWrapper.self) private var navigationPath + + @Environment(LLMOpenAITokenSaver.self) private var tokenSaver + + @LLMSessionProvider var session: LLMOpenAISession + var body: some View { @Bindable var data = data @@ -100,7 +94,7 @@ struct LLMInteraction: View { LLMOnboardingView(showOnboarding: $showOnboarding) } - .onAppear { + .task { let nameString = data.generalData.name.components(separatedBy: " ") if let firstNameValue = nameString.first { firstName = firstNameValue @@ -132,8 +126,8 @@ struct LLMInteraction: View { } } - init(presentingAccount: Binding) { - self._presentingAccount = presentingAccount + + init() { let temporaryStringBox = StringBox() self.stringBox = temporaryStringBox self._session = LLMSessionProvider( @@ -148,6 +142,7 @@ struct LLMInteraction: View { ) } + private func showSummary() { navigationPath.path.append(NavigationViews.concern) } @@ -157,8 +152,9 @@ struct LLMInteraction: View { } } + #Preview { - LLMInteraction(presentingAccount: .constant(false)) + LLMInteraction() .previewWith { LLMRunner { LLMOpenAIPlatform() diff --git a/Intake/ChiefComplaint/LLMOnboardingView.swift b/Intake/ChiefComplaintView/LLMOnboardingView.swift similarity index 81% rename from Intake/ChiefComplaint/LLMOnboardingView.swift rename to Intake/ChiefComplaintView/LLMOnboardingView.swift index 37225e2..00ac63d 100644 --- a/Intake/ChiefComplaint/LLMOnboardingView.swift +++ b/Intake/ChiefComplaintView/LLMOnboardingView.swift @@ -1,9 +1,4 @@ // -// LLMOnboardingView.swift -// Intake -// -// Created by Nick Riedman on 1/25/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -15,10 +10,12 @@ import SpeziLLMOpenAI import SpeziOnboarding import SwiftUI -// Provide a basic onboarding view to submit OpenAI API Key + +/// Provide a basic onboarding view to submit OpenAI API Key struct LLMOnboardingView: View { @Binding var showOnboarding: Bool + var body: some View { OnboardingStack(onboardingFlowComplete: !$showOnboarding) { // OpenAI Onboarding diff --git a/Intake/ChiefComplaint/OpenAI/LLMOpenAITokenOnboarding.swift b/Intake/ChiefComplaintView/OpenAI/LLMOpenAITokenOnboarding.swift similarity index 87% rename from Intake/ChiefComplaint/OpenAI/LLMOpenAITokenOnboarding.swift rename to Intake/ChiefComplaintView/OpenAI/LLMOpenAITokenOnboarding.swift index 94029a5..d1d2dec 100644 --- a/Intake/ChiefComplaint/OpenAI/LLMOpenAITokenOnboarding.swift +++ b/Intake/ChiefComplaintView/OpenAI/LLMOpenAITokenOnboarding.swift @@ -1,9 +1,4 @@ // -// LLMOpenAITokenOnboarding.swift -// Intake -// -// Created by Nick Riedman on 1/25/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -26,6 +21,7 @@ struct LLMOpenAITokenOnboarding: View { } } + #Preview { OnboardingStack { LLMOpenAITokenOnboarding() diff --git a/Intake/ChiefComplaint/SummaryView.swift b/Intake/ChiefComplaintView/SummaryView.swift similarity index 92% rename from Intake/ChiefComplaint/SummaryView.swift rename to Intake/ChiefComplaintView/SummaryView.swift index ec1fe05..e437ae3 100644 --- a/Intake/ChiefComplaint/SummaryView.swift +++ b/Intake/ChiefComplaintView/SummaryView.swift @@ -1,9 +1,4 @@ // -// SummaryView.swift -// Intake -// -// Created by Nick Riedman on 2/2/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -17,6 +12,7 @@ import SwiftUI struct ComplaintForm: View { @Binding var chiefComplaint: String + var body: some View { Form { Section(header: Text("Here is a summary of the Primary Concern")) { @@ -27,10 +23,12 @@ struct ComplaintForm: View { } } + struct SummaryView: View { @Binding var chiefComplaint: String @Environment(NavigationPathWrapper.self) private var navigationPath + var body: some View { VStack(alignment: .leading, spacing: 20) { ComplaintForm(chiefComplaint: $chiefComplaint) diff --git a/Intake/Contacts/Contacts.swift b/Intake/Contacts/Contacts.swift deleted file mode 100644 index f58a080..0000000 --- a/Intake/Contacts/Contacts.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// This source file is part of the Intake 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 Intake. -struct Contacts: View { - let contacts = [ - Contact( - name: PersonNameComponents( - givenName: "Oliver O.", - familyName: "Aalami" - ), - image: Image("ProfilePicture"), // swiftlint:disable:this accessibility_label_for_image - title: "CLINICAL PROFESSOR, SURGERY - VASCULAR SURGERY", - description: String(localized: "OLIVER_AALAMI_BIO"), - organization: "Stanford University", - address: { - let address = CNMutablePostalAddress() - address.country = "USA" - address.state = "CA" - address.postalCode = "94305" - address.city = "Stanford" - address.street = "300 Pasteur Dr" - address.subAdministrativeArea = "Rm H3640 MC 5308" - return address - }(), - contactOptions: [ - .call("+1 (650) 725-5227"), - .text("+1 (650) 725-5227"), - .email(addresses: ["aalami@stanford.edu"]), - ContactOption( - image: Image(systemName: "safari.fill"), // swiftlint:disable:this accessibility_label_for_image - title: "Website", - action: { - if let url = URL(string: "https://profiles.stanford.edu/oliver-aalami") { - 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/Intake/Contributions/ContributionsList.swift b/Intake/Contributions/ContributionsList.swift deleted file mode 100644 index 2959dee..0000000 --- a/Intake/Contributions/ContributionsList.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/Contributions/Package+LicenseType.swift b/Intake/Contributions/Package+LicenseType.swift deleted file mode 100644 index 452bf3e..0000000 --- a/Intake/Contributions/Package+LicenseType.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/Contributions/PackageCell.swift b/Intake/Contributions/PackageCell.swift deleted file mode 100644 index fbd9480..0000000 --- a/Intake/Contributions/PackageCell.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/Contributions/PackageHelper.swift b/Intake/Contributions/PackageHelper.swift deleted file mode 100644 index 68593e4..0000000 --- a/Intake/Contributions/PackageHelper.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/EditPatient.swift b/Intake/EditPatient.swift deleted file mode 100644 index 735d6c1..0000000 --- a/Intake/EditPatient.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// EditPatient.swift -// Intake -// -// Created by Akash Gupta on 2/29/24. -// -// This source file is part of the Intake based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import Foundation -import SpeziFHIR -import SwiftUI - -struct EditPatientView: View { - @Environment(DataStore.self) private var data - @Environment(FHIRStore.self) private var fhirStore - - var body: some View { - @Bindable var data = data - - VStack { - Form { - Section(header: Text("Name")) { - TextField("Name", text: $data.generalData.name) - } - Section(header: Text("Date of Birth")) { - TextField("Date of Birth", text: $data.generalData.birthdate) - } - Section(header: Text("Age")) { - TextField("Age", text: $data.generalData.age) - } - Section(header: Text("Sex")) { - TextField("Sex", text: $data.generalData.sex) - } - } - SubmitButton(nextView: NavigationViews.pdfs) - } - } - } - -// -// #Preview { -// EditPatientView() -// } diff --git a/Intake/ExportView/ExportView+ShareSheet.swift b/Intake/ExportView/ExportView+ShareSheet.swift new file mode 100644 index 0000000..7428f3b --- /dev/null +++ b/Intake/ExportView/ExportView+ShareSheet.swift @@ -0,0 +1,37 @@ +// +// This source file is part of the Intake based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import PDFKit +import SwiftUI + + +extension ExportView { + struct ShareSheet: UIViewControllerRepresentable { + let sharedItem: PDFDocument + + + func makeUIViewController(context: Context) -> UIActivityViewController { + let temporaryPath = FileManager.default.temporaryDirectory.appendingPathComponent( + LocalizedStringResource("Intake Form").localizedString() + ".pdf" + ) + try? sharedItem.dataRepresentation()?.write(to: temporaryPath) + + let controller = UIActivityViewController( + activityItems: [temporaryPath], + applicationActivities: nil + ) + controller.completionWithItemsHandler = { _, _, _, _ in + try? FileManager.default.removeItem(at: temporaryPath) + } + + return controller + } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} + } +} diff --git a/Intake/Export/ExportView.swift b/Intake/ExportView/ExportView.swift similarity index 87% rename from Intake/Export/ExportView.swift rename to Intake/ExportView/ExportView.swift index 1cfc27e..109fb8e 100644 --- a/Intake/Export/ExportView.swift +++ b/Intake/ExportView/ExportView.swift @@ -1,27 +1,25 @@ +// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // // SPDX-License-Identifier: MIT // -// ExportView displays all of the information gathered from Intake thus far in one view. It then uses ImageRenderer to export the information to a PDF -// with a simple share button. -// swiftlint disable: closure_body_length import PDFKit import SpeziFHIR import SwiftUI import UIKit -// Again, I had to disable this error as it was causing issues and could not be resolved. -// swiftlint:disable file_types_order + +/// ExportView displays all of the information gathered from Intake thus far in one view. It then uses ImageRenderer to export the information to a PDF +/// with a simple share button. struct ExportView: View { @Environment(DataStore.self) var data @State private var isSharing = false @State private var pdfData: PDFDocument? - // A long closure body length here is imperative for this view to be formatted correctly. Thus, I had to disable this warning. - // swiftlint:disable closure_body_length + var body: some View { ScrollView { self.wrappedBody @@ -50,9 +48,9 @@ struct ExportView: View { print("PDF data changed") } } - @ViewBuilder - // swiftlint:disable attributes - private var wrappedBody: some View { + + // swiftlint:disable closure_body_length + @ViewBuilder private var wrappedBody: some View { VStack(alignment: .leading) { Text("MEDICAL HISTORY") .fontWeight(.bold) @@ -206,13 +204,14 @@ struct ExportView: View { } } } - // swiftlint:enable:closure_body_length } .if(isSharing, transform: { view in view .padding() }) } + // swiftlint:enable closure_body_length + @MainActor private func shareButtonTapped() async { @@ -220,7 +219,6 @@ struct ExportView: View { self.pdfData = await self.exportToPDF() } - @MainActor func exportToPDF() async -> PDFDocument? { let renderer = ImageRenderer(content: self.wrappedBody) @@ -274,30 +272,6 @@ struct ExportView: View { } -struct ShareSheet: UIViewControllerRepresentable { - let sharedItem: PDFDocument - - - func makeUIViewController(context: Context) -> UIActivityViewController { - let temporaryPath = FileManager.default.temporaryDirectory.appendingPathComponent( - LocalizedStringResource("Intake Form").localizedString() + ".pdf" - ) - try? sharedItem.dataRepresentation()?.write(to: temporaryPath) - - let controller = UIActivityViewController( - activityItems: [temporaryPath], - applicationActivities: nil - ) - controller.completionWithItemsHandler = { _, _, _, _ in - try? FileManager.default.removeItem(at: temporaryPath) - } - - return controller - } - - func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} -} - struct ExportView_Previews: PreviewProvider { static var previews: some View { ExportView() diff --git a/Intake/ScrollablePDF.swift b/Intake/ExportView/ScrollablePDF.swift similarity index 76% rename from Intake/ScrollablePDF.swift rename to Intake/ExportView/ScrollablePDF.swift index a19586d..563861d 100644 --- a/Intake/ScrollablePDF.swift +++ b/Intake/ExportView/ScrollablePDF.swift @@ -1,9 +1,4 @@ // -// ScrollablePDF.swift -// Intake -// -// Created by Akash Gupta on 2/28/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -37,6 +32,7 @@ struct HeaderTitle: View { } } + struct ScrollablePDF: View { private struct ConditionSection: View { @Environment(DataStore.self) private var data @@ -80,7 +76,7 @@ struct ScrollablePDF: View { let data = try encoder.encode(dataStore) // You can also use UserDefaults if the data is small enough, but file storage is recommended for larger data if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { - let pathWithFilename = documentDirectory.appendingPathComponent("DataStore3.json") + let pathWithFilename = documentDirectory.appendingPathComponent("DataStore.json") try data.write(to: pathWithFilename) print("successfully stored") } @@ -182,7 +178,7 @@ struct ScrollablePDF: View { @Environment(NavigationPathWrapper.self) private var navigationPath var body: some View { - Section(header: HeaderTitle(title: "Surgical History", nextView: NavigationViews.allergies)) { + Section(header: HeaderTitle(title: "Allergies", nextView: NavigationViews.allergies)) { @Bindable var data = data List($data.allergyData, id: \.id) { $item in HStack { @@ -208,67 +204,69 @@ struct ScrollablePDF: View { } private struct MenstrualSection: View { - @Environment(DataStore.self) private var data - - var body: some View { - Section(header: Text("Menstrual History")) { - VStack(alignment: .leading) { - HStack { - Text("Start Date:") - Spacer() - Text(data.menstrualHistory.startDate, style: .date) - .foregroundColor(.secondary) - } - HStack { - Text("End Date:") - Spacer() - Text(data.menstrualHistory.endDate, style: .date) - .foregroundColor(.secondary) - } - HStack { - Text("Additional Details:") - Spacer() - Text(data.menstrualHistory.additionalDetails) - .foregroundColor(.secondary) - } - } - } - } - } - - private struct SmokingSection: View { - @Environment(DataStore.self) private var data - - var body: some View { - Section(header: Text("Smoking History")) { - VStack(alignment: .leading) { - HStack { - Text("Currently Smoking:") - Spacer() - Text(data.smokingHistory.currentlySmoking ? "Yes" : "No") - .foregroundColor(.secondary) - } - HStack { - Text("Smoked in the Past:") - Spacer() - Text(data.smokingHistory.smokedInThePast ? "Yes" : "No") - .foregroundColor(.secondary) - } - HStack { - Text("Additional Details:") - Spacer() - Text(data.smokingHistory.additionalDetails) - .foregroundColor(.secondary) - } - } - } - } - } + @Environment(DataStore.self) private var data + + var body: some View { + Section(header: Text("Menstrual History")) { + VStack(alignment: .leading) { + HStack { + Text("Start Date:") + Spacer() + Text(data.menstrualHistory.startDate, style: .date) + .foregroundColor(.secondary) + } + HStack { + Text("End Date:") + Spacer() + Text(data.menstrualHistory.endDate, style: .date) + .foregroundColor(.secondary) + } + HStack { + Text("Additional Details:") + Spacer() + Text(data.menstrualHistory.additionalDetails) + .foregroundColor(.secondary) + } + } + } + } + } + + private struct SmokingSection: View { + @Environment(DataStore.self) private var data + + var body: some View { + Section(header: Text("Smoking History")) { + VStack(alignment: .leading) { + HStack { + Text("Currently Smoking:") + Spacer() + Text(data.smokingHistory.currentlySmoking ? "Yes" : "No") + .foregroundColor(.secondary) + } + HStack { + Text("Smoked in the Past:") + Spacer() + Text(data.smokingHistory.smokedInThePast ? "Yes" : "No") + .foregroundColor(.secondary) + } + HStack { + Text("Additional Details:") + Spacer() + Text(data.smokingHistory.additionalDetails) + .foregroundColor(.secondary) + } + } + } + } + } + @Environment(DataStore.self) private var data @Environment(NavigationPathWrapper.self) private var navigationPath @Environment(ReachedEndWrapper.self) private var end + var body: some View { VStack { Form { @@ -283,12 +281,13 @@ struct ScrollablePDF: View { } SmokingSection() } - .navigationTitle("Patient Form") - .onAppear(perform: { - end.reachedEnd = true - }) + ExportButton() .padding() } + .navigationTitle("Patient Form") + .task { + end.reachedEnd = true + } } } diff --git a/Intake/Elements.swift b/Intake/Helper/ViewElements.swift similarity index 96% rename from Intake/Elements.swift rename to Intake/Helper/ViewElements.swift index 244f650..b24c49d 100644 --- a/Intake/Elements.swift +++ b/Intake/Helper/ViewElements.swift @@ -1,9 +1,3 @@ -// -// Elements.swift -// Intake -// -// Created by Nick Riedman on 2/25/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University diff --git a/Intake/Home.swift b/Intake/Home.swift index 4f13cc0..8e7f57d 100644 --- a/Intake/Home.swift +++ b/Intake/Home.swift @@ -6,162 +6,15 @@ // SPDX-License-Identifier: MIT // -import SpeziAccount -import SpeziMockWebService import SwiftUI -enum NavigationViews: String { - case allergies - case surgical - case medical - case menstrual - case smoking - case medication - case chat - case concern - case export - case patient - case pdfs - case inspect - case general - case newAllergy -} - -struct StartButton: View { - @Binding var navigationPath: NavigationPath - - var body: some View { - Button(action: { - if FeatureFlags.testMedication { - navigationPath.append(NavigationViews.medication) - } else if FeatureFlags.testAllergy { - navigationPath.append(NavigationViews.allergies) - } else if FeatureFlags.testMenstrual { - navigationPath.append(NavigationViews.menstrual) - } else if FeatureFlags.testSmoking { - navigationPath.append(NavigationViews.smoking) - } else if FeatureFlags.testSurgery { - navigationPath.append(NavigationViews.surgical) - } else if FeatureFlags.testCondition { - navigationPath.append(NavigationViews.medical) - } else { - navigationPath.append(NavigationViews.general) - } - }) { - Text("Create New Form") - .font(.headline) - .fontWeight(.bold) - .foregroundColor(.white) - .padding() - .background(Color.blue) - .cornerRadius(10) - }.accessibilityIdentifier("Create New Form") - } -} - -struct LoadLastButton: View { - @Binding var navigationPath: NavigationPath - @Binding var disabled: Bool - @Environment(DataStore.self) private var data - - var body: some View { - Button(action: { - let fetchData = loadDataStore() - if let loadedData = fetchData { - data.allergyData = loadedData.allergyData - data.generalData = loadedData.generalData - data.surgeries = loadedData.surgeries - data.conditionData = loadedData.conditionData - data.menstrualHistory = loadedData.menstrualHistory - data.smokingHistory = loadedData.smokingHistory - data.chiefComplaint = loadedData.chiefComplaint - data.surgeriesLoaded = loadedData.surgeriesLoaded - data.medicationData = loadedData.medicationData - navigationPath.append(NavigationViews.pdfs) - } - }) { - Text("Load Latest Form") - .font(.headline) - .fontWeight(.bold) - .foregroundColor(Color.white) - .padding() - .background(disabled ? Color.blue.opacity(0.5) : Color.blue) - .cornerRadius(10) - } - .disabled(disabled) - } - - func loadDataStore() -> DataStore? { - let decoder = JSONDecoder() - if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { - let pathWithFilename = documentDirectory.appendingPathComponent("DataStore3.json") - if let data = try? Data(contentsOf: pathWithFilename) { - do { - let dataStore = try decoder.decode(DataStore.self, from: data) - print("successfully loaded") - return dataStore - } catch { - print("Failed to load DataStore: \(error)") - } - } - } - return nil - } -} - - -struct SettingsButton: View { - @Binding var showSettings: Bool - - var body: some View { - Button( - action: { - showSettings.toggle() - }, - label: { - Image(systemName: "gear") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 30, height: 30) - .foregroundColor(.blue) - .accessibilityLabel("SETTINGS") - } - ) - } -} struct HomeView: View { - static var accountEnabled: Bool { - !FeatureFlags.disableFirebase && !FeatureFlags.skipOnboarding - } - - @State private var presentingAccount = false @State private var showSettings = false - @State var isButtonDisabled = true @Environment(NavigationPathWrapper.self) private var navigationPath @Environment(DataStore.self) private var data - private var homeLogo: some View { - Image(systemName: "waveform.path.ecg") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 100, height: 100) - .foregroundColor(.blue) - .accessibilityLabel(Text("HOME_LOGO")) - } - - private var homeTitle: some View { - Group { - Text("ReForm") - .font(.largeTitle) - .fontWeight(.bold) - .foregroundColor(.black) - Text("AI-assisted medical intake") - .font(.title2) - .foregroundColor(.gray) - } - } var body: some View { @Bindable var navigationPath = navigationPath @@ -170,24 +23,26 @@ struct HomeView: View { NavigationStack(path: $navigationPath.path) { VStack { Spacer() + homeLogo homeTitle + Spacer() - LoadLastButton(navigationPath: $navigationPath.path, disabled: $isButtonDisabled) + + LoadLastButton(navigationPath: $navigationPath.path, disabled: !isLoadEnabled) .padding(.bottom, 10) StartButton(navigationPath: $navigationPath.path) .padding(.top, 10) + Spacer() } - .toolbar { SettingsButton(showSettings: $showSettings) } - .navigationDestination(for: NavigationViews.self) { view in switch view { case .smoking: SmokingHistoryView() - case .chat: LLMInteraction(presentingAccount: $presentingAccount) + case .chat: LLMInteraction() case .allergies: AllergyList() case .surgical: SurgeryView() case .medical: MedicalHistoryView() @@ -203,67 +58,46 @@ struct HomeView: View { } } } - .sheet(isPresented: $presentingAccount) { - AccountSheet() - } .sheet(isPresented: $showSettings) { SettingsView() } - .accountRequired(Self.accountEnabled) { - AccountSheet() - } - .verifyRequiredAccountDetails(Self.accountEnabled) - .onAppear { - let fetchData = loadDataStore() - if fetchData != nil { - isButtonDisabled = false - } else { - isButtonDisabled = true - } + } + + private var homeLogo: some View { + Image(systemName: "waveform.path.ecg") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 100, height: 100) + .foregroundColor(.blue) + .accessibilityLabel(Text("HOME_LOGO")) + } + + private var homeTitle: some View { + Group { + Text("ReForm") + .font(.largeTitle) + .fontWeight(.bold) + .foregroundColor(.black) + Text("AI-assisted medical intake") + .font(.title2) + .foregroundColor(.gray) } } - func loadDataStore() -> DataStore? { - let decoder = JSONDecoder() + private var isLoadEnabled: Bool { if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { - let pathWithFilename = documentDirectory.appendingPathComponent("DataStore3.json") - if let data = try? Data(contentsOf: pathWithFilename) { - do { - let dataStore = try decoder.decode(DataStore.self, from: data) - print("successfully loaded") - return dataStore - } catch { - print("Failed to load DataStore: \(error)") - } - } + let pathWithFilename = documentDirectory.appendingPathComponent("DataStore.json") + + return FileManager.default.fileExists(atPath: pathWithFilename.path()) } - return nil + return false } } -#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: IntakeStandard()) { - IntakeScheduler() - MockWebService() - AccountConfiguration(building: details, active: MockUserIdPasswordAccountService()) - } -} +#if DEBUG #Preview { - CommandLine.arguments.append("--disableFirebase") // make sure the MockWebService is displayed - return HomeView() - .previewWith(standard: IntakeStandard()) { - IntakeScheduler() - MockWebService() - AccountConfiguration { - MockUserIdPasswordAccountService() - } - } + HomeView() + .previewWith(standard: IntakeStandard()) {} } #endif diff --git a/Intake/HomeView/LoadLastButton.swift b/Intake/HomeView/LoadLastButton.swift new file mode 100644 index 0000000..4541dce --- /dev/null +++ b/Intake/HomeView/LoadLastButton.swift @@ -0,0 +1,61 @@ +// +// This source file is part of the Intake based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +struct LoadLastButton: View { + @Binding var navigationPath: NavigationPath + var disabled: Bool + @Environment(DataStore.self) private var data + + + var body: some View { + Button(action: { + let fetchData = loadDataStore() + if let loadedData = fetchData { + data.allergyData = loadedData.allergyData + data.generalData = loadedData.generalData + data.surgeries = loadedData.surgeries + data.conditionData = loadedData.conditionData + data.menstrualHistory = loadedData.menstrualHistory + data.smokingHistory = loadedData.smokingHistory + data.chiefComplaint = loadedData.chiefComplaint + data.surgeriesLoaded = loadedData.surgeriesLoaded + data.medicationData = loadedData.medicationData + navigationPath.append(NavigationViews.pdfs) + } + }) { + Text("Load Latest Form") + .font(.headline) + .fontWeight(.bold) + .foregroundColor(Color.white) + .padding() + .background(disabled ? Color.blue.opacity(0.5) : Color.blue) + .cornerRadius(10) + } + .disabled(disabled) + } + + func loadDataStore() -> DataStore? { + let decoder = JSONDecoder() + if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { + let pathWithFilename = documentDirectory.appendingPathComponent("DataStore.json") + if let data = try? Data(contentsOf: pathWithFilename) { + do { + let dataStore = try decoder.decode(DataStore.self, from: data) + print("successfully loaded") + return dataStore + } catch { + print("Failed to load DataStore: \(error)") + } + } + } + return nil + } +} diff --git a/Intake/Settings/ResourceSelection.swift b/Intake/HomeView/Settings/ResourceSelection.swift similarity index 69% rename from Intake/Settings/ResourceSelection.swift rename to Intake/HomeView/Settings/ResourceSelection.swift index 60480c6..651b5d3 100644 --- a/Intake/Settings/ResourceSelection.swift +++ b/Intake/HomeView/Settings/ResourceSelection.swift @@ -11,22 +11,18 @@ import SpeziFHIR import SpeziFHIRMockPatients import SwiftUI + struct ResourceSelection: View { @Environment(IntakeStandard.self) private var standard @Environment(FHIRStore.self) private var store - + @State private var bundles: [ModelsR4.Bundle] = [] @State private var showBundleSelection = false - + + @MainActor var useHealthKitResources: Binding { Binding( get: { - /* - if FeatureFlags.mockPatients { - showBundleSelection = true - return false - } - */ standard.useHealthKitResources }, set: { newValue in @@ -35,27 +31,27 @@ struct ResourceSelection: View { } ) } - + var body: some View { Form { Section { Toggle(isOn: useHealthKitResources) { Text("Use HealthKit Resources") } - .onChange(of: useHealthKitResources.wrappedValue, initial: true) { - if useHealthKitResources.wrappedValue { - _Concurrency.Task { - await standard.loadHealthKitResources() - } - } else { - guard let firstMockPatient = bundles.first else { - return - } - - store.removeAllResources() - store.load(bundle: firstMockPatient) + .onChange(of: useHealthKitResources.wrappedValue, initial: true) { + if useHealthKitResources.wrappedValue { + _Concurrency.Task { + await standard.loadHealthKitResources() + } + } else { + guard let firstMockPatient = bundles.first else { + return } + + store.removeAllResources() + store.load(bundle: firstMockPatient) } + } } if showBundleSelection { Section { @@ -74,8 +70,6 @@ struct ResourceSelection: View { } .task { self.bundles = await ModelsR4.Bundle.llmOnFHIRMockPatients - } - .onAppear { showBundleSelection = !standard.useHealthKitResources } .navigationTitle(Text("Resource Settings")) diff --git a/Intake/HomeView/Settings/SettingsButton.swift b/Intake/HomeView/Settings/SettingsButton.swift new file mode 100644 index 0000000..c4a1ab1 --- /dev/null +++ b/Intake/HomeView/Settings/SettingsButton.swift @@ -0,0 +1,31 @@ +// +// This source file is part of the Intake based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +struct SettingsButton: View { + @Binding var showSettings: Bool + + + var body: some View { + Button( + action: { + showSettings.toggle() + }, + label: { + Image(systemName: "gear") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 30, height: 30) + .foregroundColor(.blue) + .accessibilityLabel("SETTINGS") + } + ) + } +} diff --git a/Intake/HomeView/Settings/SettingsView.swift b/Intake/HomeView/Settings/SettingsView.swift new file mode 100644 index 0000000..7e2a3a7 --- /dev/null +++ b/Intake/HomeView/Settings/SettingsView.swift @@ -0,0 +1,55 @@ +// +// This source file is part of the Stanford LLM on FHIR project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +struct SettingsView: View { + private enum SettingsDestinations { + case resourceSelection + } + + @State private var path = NavigationPath() + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack(path: $path) { + List { + resourcesSettings + } + .navigationTitle("SETTINGS_TITLE") + .navigationDestination(for: SettingsDestinations.self) { destination in + navigationDesination(for: destination) + } + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("FHIR_RESOURCES_CHAT_CANCEL") { + dismiss() + } + } + } + } + } + + private var resourcesSettings: some View { + Section("Resource Selection") { + NavigationLink(value: SettingsDestinations.resourceSelection) { + Text("Resource Selection") + } + } + } + + private func navigationDesination(for destination: SettingsDestinations) -> some View { + Group { + switch destination { + case .resourceSelection: + ResourceSelection() + } + } + } +} diff --git a/Intake/HomeView/StartButton.swift b/Intake/HomeView/StartButton.swift new file mode 100644 index 0000000..f8fcd3c --- /dev/null +++ b/Intake/HomeView/StartButton.swift @@ -0,0 +1,43 @@ +// +// This source file is part of the Intake based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + + +struct StartButton: View { + @Binding var navigationPath: NavigationPath + + + var body: some View { + Button(action: { + if FeatureFlags.testMedication { + navigationPath.append(NavigationViews.medication) + } else if FeatureFlags.testAllergy { + navigationPath.append(NavigationViews.allergies) + } else if FeatureFlags.testMenstrual { + navigationPath.append(NavigationViews.menstrual) + } else if FeatureFlags.testSmoking { + navigationPath.append(NavigationViews.smoking) + } else if FeatureFlags.testSurgery { + navigationPath.append(NavigationViews.surgical) + } else if FeatureFlags.testCondition { + navigationPath.append(NavigationViews.medical) + } else { + navigationPath.append(NavigationViews.general) + } + }) { + Text("Create New Form") + .font(.headline) + .fontWeight(.bold) + .foregroundColor(.white) + .padding() + .background(Color.blue) + .cornerRadius(10) + }.accessibilityIdentifier("Create New Form") + } +} diff --git a/Intake/Intake.swift b/Intake/Intake.swift index b98f96e..609a4dc 100644 --- a/Intake/Intake.swift +++ b/Intake/Intake.swift @@ -7,105 +7,20 @@ // import Spezi -import SpeziFirebaseAccount import SwiftUI -@Observable -class NavigationPathWrapper { - var path = NavigationPath() -} - -struct PatientData: Codable { - var name: String - var birthdate: String - var age: String - var sex: String -} - -struct ReactionItem: Identifiable, Codable { - var id = UUID() - var reaction: String -} - - -struct AllergyItem: Identifiable, Equatable, Codable { - var id = UUID() - var allergy: String - var reaction: [ReactionItem] - - static func == (lhs: AllergyItem, rhs: AllergyItem) -> Bool { - lhs.allergy == rhs.allergy - } -} - -struct MedicalHistoryItem: Identifiable, Equatable, Codable { - var id = UUID() - var condition: String - var active: Bool -} - - -struct MenstrualHistoryItem: Codable { - var startDate: Date - var endDate: Date - var additionalDetails: String -} - -struct SmokingHistoryItem: Codable { - var hasSmokedOrSmoking: Bool - var currentlySmoking: Bool - var smokedInThePast: Bool - var additionalDetails: String -} - -struct SurgeryItem: Identifiable, Equatable, Codable { - var id = UUID() - var surgeryName: String = "" - var date: String = "" - var endDate: String = "" - var status: String = "" - var location: String = "" - var notes: [String] = [] - var bodySites: [String] = [] - var complications: [String] = [] -} - - -@Observable -class DataStore: Codable { - var allergyData: [AllergyItem] = [] - var conditionData: [MedicalHistoryItem] = [] - var medicationData: Set = [] - var surgeries: [SurgeryItem] = [] - var surgeriesLoaded = false - var chiefComplaint: String = "" - var generalData = PatientData(name: "", birthdate: "", age: "", sex: "") - var menstrualHistory = MenstrualHistoryItem(startDate: Date(), endDate: Date(), additionalDetails: "") - var smokingHistory = SmokingHistoryItem(hasSmokedOrSmoking: Bool(), currentlySmoking: Bool(), smokedInThePast: Bool(), additionalDetails: "") -} - -@Observable -class ReachedEndWrapper { - var reachedEnd = false - var surgeriesLoaded = false -} - -@Observable -class LoadedWrapper { - var conditionData = false - var allergyData = false -} @main struct Intake: App { @UIApplicationDelegateAdaptor(IntakeDelegate.self) var appDelegate @AppStorage(StorageKeys.onboardingFlowComplete) var completedOnboardingFlow = false - let navigationPath = NavigationPathWrapper() - let data = DataStore() - let reachedEnd = ReachedEndWrapper() - let loaded = LoadedWrapper() + @State var navigationPath = NavigationPathWrapper() + @State var data = DataStore() + @State var reachedEnd = ReachedEndWrapper() + @State var loaded = LoadedWrapper() + var body: some Scene { WindowGroup { ZStack { diff --git a/Intake/IntakeDelegate.swift b/Intake/IntakeDelegate.swift index c9afce2..dac1e4c 100644 --- a/Intake/IntakeDelegate.swift +++ b/Intake/IntakeDelegate.swift @@ -7,75 +7,48 @@ // import Spezi -import SpeziAccount -import SpeziFirebaseAccount -import SpeziFirebaseStorage -import SpeziFirestore import SpeziHealthKit import SpeziLLM import SpeziLLMLocal import SpeziLLMOpenAI -import SpeziMockWebService import SpeziOnboarding -import SpeziScheduler import SwiftUI + class IntakeDelegate: SpeziAppDelegate { override var configuration: Configuration { Configuration(standard: IntakeStandard()) { - 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 } LLMRunner { LLMOpenAIPlatform() } - IntakeScheduler() 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(.automatic) + CollectSamples( + [ + HKClinicalType(.allergyRecord), + HKClinicalType(.clinicalNoteRecord), + HKClinicalType(.conditionRecord), + HKClinicalType(.coverageRecord), + HKClinicalType(.immunizationRecord), + HKClinicalType(.labResultRecord), + HKClinicalType(.medicationRecord), + HKClinicalType(.procedureRecord), + HKClinicalType(.vitalSignRecord) + ], + predicate: HKQuery.predicateForSamples( + withStart: Date.distantPast, + end: nil, + options: .strictEndDate + ), + deliverySetting: .anchorQuery(saveAnchor: false) ) } } diff --git a/Intake/IntakeStandard.swift b/Intake/IntakeStandard.swift index 4c5ec8b..f27d9a7 100644 --- a/Intake/IntakeStandard.swift +++ b/Intake/IntakeStandard.swift @@ -6,70 +6,26 @@ // SPDX-License-Identifier: MIT // -import FirebaseFirestore -import FirebaseStorage import HealthKitOnFHIR +import ModelsR4 import OSLog import PDFKit import Spezi -import SpeziAccount import SpeziFHIR import SpeziFHIRHealthKit -import SpeziFirebaseAccountStorage -import SpeziFirestore import SpeziHealthKit -import SpeziMockWebService import SpeziOnboarding -import SpeziQuestionnaire import SwiftUI -actor IntakeStandard: Standard, EnvironmentAccessible, HealthKitConstraint, OnboardingConstraint, AccountStorageConstraint { - enum IntakeStandardError: Error { - case userNotAuthenticatedYet - } - - private static var userCollection: CollectionReference { - Firestore.firestore().collection("users") - } +actor IntakeStandard: Standard, EnvironmentAccessible, HealthKitConstraint, OnboardingConstraint { @Dependency var fhirStore: FHIRStore @MainActor var useHealthKitResources = true private var samples: [HKSample] = [] - - @Dependency var mockWebService: MockWebService? - @Dependency var accountStorage: FirestoreAccountStorage? - - @AccountReference var account: Account - private let logger = Logger(subsystem: "Intake", category: "Standard") - private var userDocumentReference: DocumentReference { - get async throws { - guard let details = await account.details else { - throw IntakeStandardError.userNotAuthenticatedYet - } - - return Self.userCollection.document(details.accountId) - } - } - - private var userBucketReference: StorageReference { - get async throws { - guard let details = await account.details else { - throw IntakeStandardError.userNotAuthenticatedYet - } - - return Storage.storage().reference().child("users/\(details.accountId)") - } - } - - init() { - if !FeatureFlags.disableFirebase { - _accountStorage = Dependency(wrappedValue: FirestoreAccountStorage(storeIn: IntakeStandard.userCollection)) - } - } - + func add(sample: HKSample) async { samples.append(sample) if await useHealthKitResources { @@ -95,40 +51,6 @@ actor IntakeStandard: Standard, EnvironmentAccessible, HealthKitConstraint, Onbo useHealthKitResources = true } - 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`. @@ -137,64 +59,12 @@ actor IntakeStandard: Standard, EnvironmentAccessible, HealthKitConstraint, Onbo 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) - + 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 } - 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) + let filePath = basePath.appending(path: "consentForm_\(dateString).pdf") + consent.write(to: filePath) } } diff --git a/Intake/IntakeTestingSetup.swift b/Intake/IntakeTestingSetup.swift index 99441cf..34f4ba7 100644 --- a/Intake/IntakeTestingSetup.swift +++ b/Intake/IntakeTestingSetup.swift @@ -5,15 +5,18 @@ // // SPDX-License-Identifier: MIT // + import ModelsR4 import SpeziFHIR import SpeziFHIRMockPatients import SwiftUI + private struct IntakeAppTestingSetup: ViewModifier { @AppStorage(StorageKeys.onboardingFlowComplete) var completedOnboardingFlow = false @Environment(FHIRStore.self) private var store + func body(content: Content) -> some View { content .task { @@ -31,6 +34,8 @@ private struct IntakeAppTestingSetup: ViewModifier { } } } + + extension View { func testingSetup() -> some View { self.modifier(IntakeAppTestingSetup()) diff --git a/Intake/Medical History/MedicalHistoryLLMAssistant.swift b/Intake/MedicalHistoryView/MedicalHistoryLLMAssistant.swift similarity index 76% rename from Intake/Medical History/MedicalHistoryLLMAssistant.swift rename to Intake/MedicalHistoryView/MedicalHistoryLLMAssistant.swift index ec8b0bf..04339ee 100644 --- a/Intake/Medical History/MedicalHistoryLLMAssistant.swift +++ b/Intake/MedicalHistoryView/MedicalHistoryLLMAssistant.swift @@ -1,9 +1,4 @@ // -// MedicalHistoryLLMAssistant.swift -// Intake -// -// Created by Kate Callon on 3/4/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -18,7 +13,7 @@ import SpeziLLMLocal import SpeziLLMOpenAI import SwiftUI -// This box was needed in order to set the output of the LLM to the medicalHistoryItem and it's equatable in order to check if there's been a change in order to add an additional medical history data point. +/// This box was needed in order to set the output of the LLM to the medicalHistoryItem and it's equatable in order to check if there's been a change in order to add an additional medical history data point. @Observable class MedicalHistoryItemBox: Equatable { var medicalHistoryItem: MedicalHistoryItem? @@ -30,24 +25,7 @@ class MedicalHistoryItemBox: Equatable { } } -// This function gets the current patient medical history data and inputs it into the system prompt. -func getCurrentPatientMedicalHistory(medHistoryList: [MedicalHistoryItem]) -> String? { - var medHistoryDetails = "The patient has had several conditions in their medical history described in the following sentences." - - for medHistory in medHistoryList { - let medHistoryName = medHistory.condition - let active = medHistory.active - if active { - medHistoryDetails += "The patient has the condition \(medHistoryName) and it is currently an active condition.\n" - } else { - medHistoryDetails += "The patient has the condition \(medHistoryName) and it is currently an inactive condition.\n" - } - } - - return medHistoryDetails.isEmpty ? nil : medHistoryDetails -} - -// This LLM Assistant allows the patient to ask about their current medical history and add new data to their medical history list. +/// This LLM Assistant allows the patient to ask about their current medical history and add new data to their medical history list. struct UpdateMedicalHistoryFunction: LLMFunction { static let name: String = "update_medical_history" static let description: String = """ @@ -84,14 +62,14 @@ struct MedicalHistoryLLMAssistant: View { @Environment(NavigationPathWrapper.self) private var navigationPath @Environment(LLMOpenAITokenSaver.self) private var tokenSaver - @Binding var presentingAccount: Bool @LLMSessionProvider var session: LLMOpenAISession - @State var showOnboarding = true + @AppStorage(StorageKeys.llmOnboardingComplete) var showOnboarding = true @State var greeting = true @State var medicalHistoryItemBox: MedicalHistoryItemBox + var body: some View { @Bindable var data = data @@ -104,7 +82,7 @@ struct MedicalHistoryLLMAssistant: View { LLMOnboardingView(showOnboarding: $showOnboarding) } - .onAppear { + .task { checkToken() if let currentMedHistory = getCurrentPatientMedicalHistory(medHistoryList: data.conditionData) { @@ -126,8 +104,8 @@ struct MedicalHistoryLLMAssistant: View { } } - init(presentingAccount: Binding) { - self._presentingAccount = presentingAccount + + init() { let temporaryMedicalHistoryItemBox = MedicalHistoryItemBox() self.medicalHistoryItemBox = temporaryMedicalHistoryItemBox self._session = LLMSessionProvider( @@ -149,13 +127,32 @@ struct MedicalHistoryLLMAssistant: View { ) } + + // This function gets the current patient medical history data and inputs it into the system prompt. + func getCurrentPatientMedicalHistory(medHistoryList: [MedicalHistoryItem]) -> String? { + var medHistoryDetails = "The patient has had several conditions in their medical history described in the following sentences." + + for medHistory in medHistoryList { + let medHistoryName = medHistory.condition + let active = medHistory.active + if active { + medHistoryDetails += "The patient has the condition \(medHistoryName) and it is currently an active condition.\n" + } else { + medHistoryDetails += "The patient has the condition \(medHistoryName) and it is currently an inactive condition.\n" + } + } + + return medHistoryDetails.isEmpty ? nil : medHistoryDetails + } + private func checkToken() { showOnboarding = !tokenSaver.tokenPresent } } + #Preview { - LLMInteraction(presentingAccount: .constant(false)) + LLMInteraction() .previewWith { LLMRunner { LLMOpenAIPlatform() diff --git a/Intake/Medical History/MedicalHistoryView.swift b/Intake/MedicalHistoryView/MedicalHistoryView.swift similarity index 97% rename from Intake/Medical History/MedicalHistoryView.swift rename to Intake/MedicalHistoryView/MedicalHistoryView.swift index 7ed9a7a..9b786a5 100644 --- a/Intake/Medical History/MedicalHistoryView.swift +++ b/Intake/MedicalHistoryView/MedicalHistoryView.swift @@ -1,7 +1,3 @@ -// MedicalHistoryView.swift -// Intake -// -// Created by Akash Gupta on 1/30/24. // // This source file is part of the Intake based on the Stanford Spezi Template Application project // @@ -17,6 +13,7 @@ import SpeziLLM import SpeziLLMOpenAI import SwiftUI + struct MedicalHistoryView: View { @Environment(FHIRStore.self) private var fhirStore @Environment(NavigationPathWrapper.self) private var navigationPath @@ -28,6 +25,7 @@ struct MedicalHistoryView: View { @LLMSessionProvider var session: LLMOpenAISession + var body: some View { if loaded.conditionData { VStack { @@ -63,8 +61,9 @@ struct MedicalHistoryView: View { } .navigationTitle("Medical History") .navigationBarItems(trailing: addConditionButton) - .navigationBarItems(trailing: NavigationLink(destination: MedicalHistoryLLMAssistant(presentingAccount: .constant(false))) { - Text("Chat") + .navigationBarItems(trailing: NavigationLink(destination: MedicalHistoryLLMAssistant()) { + Image(systemName: "bubble") + .accessibilityLabel("Chat with LLM Assistant") }) } @@ -105,6 +104,7 @@ struct MedicalHistoryView: View { .foregroundColor(.gray) } + init() { // swiftlint:disable:this function_body_length let systemPrompt = """ You are a helpful assistant that filters lists of conditions. You will be given\ @@ -160,6 +160,7 @@ struct MedicalHistoryView: View { ) } + private func addConditionAction() { data.conditionData.append(MedicalHistoryItem(condition: "", active: false)) } @@ -182,7 +183,6 @@ struct MedicalHistoryView: View { private func chatSheetView() -> some View { LLMAssistantView( - presentingAccount: .constant(false), pageTitle: .constant("Medical History Assistant"), initialQuestion: .constant("Do you have any questions about your medical conditions"), prompt: .constant( @@ -230,6 +230,7 @@ struct MedicalHistoryView: View { } } + #Preview { MedicalHistoryView() .previewWith { diff --git a/Intake/Medication View/IntakeMedicationViewModel.swift b/Intake/MedicationView/IntakeMedicationViewModel.swift similarity index 95% rename from Intake/Medication View/IntakeMedicationViewModel.swift rename to Intake/MedicationView/IntakeMedicationViewModel.swift index c5f575d..bec42cb 100644 --- a/Intake/Medication View/IntakeMedicationViewModel.swift +++ b/Intake/MedicationView/IntakeMedicationViewModel.swift @@ -1,10 +1,3 @@ -// -// IntakeMedicationViewModel.swift -// Intake -// -// Created by Kate Callon on 2/17/24. -// -// // This source file is part of the Intake based on the Stanford Spezi Template Medication project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -19,7 +12,7 @@ import SpeziFHIR import SpeziMedication import SwiftUI -// The IntakeMedicationSettingsViewModel takes the patient's FHIRStore medications and adds any that match to the medicationOptions to the medicationInstances list which is then used for the MedicationContentView. +/// The IntakeMedicationSettingsViewModel takes the patient's FHIRStore medications and adds any that match to the medicationOptions to the medicationInstances list which is then used for the MedicationContentView. @Observable class IntakeMedicationSettingsViewModel: Module, MedicationSettingsViewModel, CustomStringConvertible { var medicationInstances: Set = [] diff --git a/Intake/Medication View/MedicationContentView.swift b/Intake/MedicationView/MedicationContentView.swift similarity index 52% rename from Intake/Medication View/MedicationContentView.swift rename to Intake/MedicationView/MedicationContentView.swift index 0d3d49b..c246567 100644 --- a/Intake/Medication View/MedicationContentView.swift +++ b/Intake/MedicationView/MedicationContentView.swift @@ -1,10 +1,3 @@ -// -// MedicationContentView.swift -// Intake -// -// Created by Kate Callon on 2/18/24. -// -// // This source file is part of the Intake based on the Stanford Spezi Template Medication project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -17,15 +10,15 @@ import SpeziFHIR import SpeziMedication import SwiftUI -// This view displays the medications in the patient's FHIR data, and allows them to add, update and delete their medications. +/// This view displays the medications in the patient's FHIR data, and allows them to add, update and delete their medications. struct MedicationContentView: View { @Environment(FHIRStore.self) private var fhirStore @Environment(NavigationPathWrapper.self) private var navigationPath @Environment(DataStore.self) private var data @State private var presentSettings = false - @State private var medicationSettingsViewModel: IntakeMedicationSettingsViewModel? + var body: some View { VStack { if let medicationSettingsViewModel { @@ -38,33 +31,31 @@ struct MedicationContentView: View { navigationPath.path.append(NavigationViews.allergies) } } - .navigationTitle("Medications") - .navigationBarItems(trailing: NavigationLink(destination: MedicationLLMAssistant(presentingAccount: .constant(false))) { - Text("Chat") - }) + .navigationTitle("Medications") + .navigationBarItems(trailing: NavigationLink(destination: MedicationLLMAssistant()) { + Image(systemName: "bubble") + .accessibilityLabel("Chat with LLM Assistant") + }) } else { ProgressView() } } - // Updates the medicationSettingsViewModel init if there's a change to the patient's fhirStore medications. -// .onChange(of: fhirStore.llmMedications) { -// medicationSettingsViewModel = .init(existingMedications: fhirStore.llmMedications) -// } - // Task to initialize the MedicationSettingsViewModel with the patient's existing fhirStore medications. - .task { - let patientMedications = fhirStore.llmMedications - self.medicationSettingsViewModel = IntakeMedicationSettingsViewModel(existingMedications: patientMedications) - var initialData: Set = [] - if let newMed = self.medicationSettingsViewModel?.medicationInstances { - initialData = newMed + // Task to initialize the MedicationSettingsViewModel with the patient's existing fhirStore medications. + .task { + let patientMedications = fhirStore.llmMedications + self.medicationSettingsViewModel = IntakeMedicationSettingsViewModel(existingMedications: patientMedications) + + if !data.medicationData.isEmpty { + medicationSettingsViewModel?.medicationInstances = data.medicationData + } + } + .onDisappear { + data.medicationData = medicationSettingsViewModel?.medicationInstances ?? [] } - data.medicationData = initialData - } } - - init() {} } + #Preview { MedicationContentView() } diff --git a/Intake/Medication View/MedicationLLMAssistant.swift b/Intake/MedicationView/MedicationLLMAssistant.swift similarity index 66% rename from Intake/Medication View/MedicationLLMAssistant.swift rename to Intake/MedicationView/MedicationLLMAssistant.swift index c88df02..513cbfb 100644 --- a/Intake/Medication View/MedicationLLMAssistant.swift +++ b/Intake/MedicationView/MedicationLLMAssistant.swift @@ -1,9 +1,3 @@ -// -// MedicationLLMAssistant.swift -// Intake -// -// Created by Kate Callon on 3/2/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -18,30 +12,15 @@ import SpeziLLMLocal import SpeziLLMOpenAI import SwiftUI -// Adds the current patient medications to the system prompt. -func getCurrentPatientMedications(medicationList: Set) -> String? { - var medicationDetails = "The patient is currently taking several medications:" - print(medicationList) - for medication in medicationList { - let medName = medication.type.localizedDescription - let dose = medication.dosage.localizedDescription - let frequency = medication.schedule.frequency - medicationDetails += "The patient is taking medication \(medName), the dose is \(dose), and the frequency is \(frequency).\n" - } - - return medicationDetails.isEmpty ? nil : medicationDetails -} - -// Provides medication LLM assistant functionality to allow the patient to ask about their current medications. +/// Provides medication LLM assistant functionality to allow the patient to ask about their current medications. struct MedicationLLMAssistant: View { @Environment(DataStore.self) private var data @Environment(NavigationPathWrapper.self) private var navigationPath @Environment(LLMOpenAITokenSaver.self) private var tokenSaver - @Binding var presentingAccount: Bool @LLMSessionProvider var session: LLMOpenAISession - - @State var showOnboarding = true + + @AppStorage(StorageKeys.llmOnboardingComplete) var showOnboarding = true @State var greeting = true var body: some View { @@ -51,12 +30,10 @@ struct MedicationLLMAssistant: View { session: $session ) .navigationTitle("Medications Assistant") - .sheet(isPresented: $showOnboarding) { LLMOnboardingView(showOnboarding: $showOnboarding) } - - .onAppear { + .task { checkToken() if greeting { @@ -67,14 +44,14 @@ struct MedicationLLMAssistant: View { if let currentMed = getCurrentPatientMedications(medicationList: data.medicationData) { session.context.append( - systemMessage: currentMed - ) + systemMessage: currentMed + ) } } } - - init(presentingAccount: Binding) { - self._presentingAccount = presentingAccount + + + init() { self._session = LLMSessionProvider( schema: LLMOpenAISchema( parameters: .init( @@ -91,13 +68,29 @@ struct MedicationLLMAssistant: View { ) } + + /// Adds the current patient medications to the system prompt. + func getCurrentPatientMedications(medicationList: Set) -> String? { + var medicationDetails = "The patient is currently taking several medications:" + print(medicationList) + for medication in medicationList { + let medName = medication.type.localizedDescription + let dose = medication.dosage.localizedDescription + let frequency = medication.schedule.frequency + medicationDetails += "The patient is taking medication \(medName), the dose is \(dose), and the frequency is \(frequency).\n" + } + + return medicationDetails.isEmpty ? nil : medicationDetails + } + private func checkToken() { showOnboarding = !tokenSaver.tokenPresent } } + #Preview { - LLMInteraction(presentingAccount: .constant(false)) + LLMInteraction() .previewWith { LLMRunner { LLMOpenAIPlatform() diff --git a/Intake/Medication View/IntakeDosage.swift b/Intake/MedicationView/Models/IntakeDosage.swift similarity index 63% rename from Intake/Medication View/IntakeDosage.swift rename to Intake/MedicationView/Models/IntakeDosage.swift index 788de09..306764d 100644 --- a/Intake/Medication View/IntakeDosage.swift +++ b/Intake/MedicationView/Models/IntakeDosage.swift @@ -1,10 +1,3 @@ -// -// IntakeDosage.swift -// Intake -// -// Created by Kate Callon on 2/17/24. -// -// // This source file is part of the Intake based on the Stanford Spezi Template Medication project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -15,7 +8,7 @@ import Foundation import SpeziMedication -// The IntakeDosage struct has a localizedDescription that describes the dosage information. +/// The IntakeDosage struct has a localizedDescription that describes the dosage information. struct IntakeDosage: Dosage, Codable { var localizedDescription: String } diff --git a/Intake/Medication View/IntakeMedication.swift b/Intake/MedicationView/Models/IntakeMedication.swift similarity index 63% rename from Intake/Medication View/IntakeMedication.swift rename to Intake/MedicationView/Models/IntakeMedication.swift index aee0db7..f20f86e 100644 --- a/Intake/Medication View/IntakeMedication.swift +++ b/Intake/MedicationView/Models/IntakeMedication.swift @@ -1,10 +1,3 @@ -// -// IntakeMedication.swift -// Intake -// -// Created by Kate Callon on 2/17/24. -// -// // This source file is part of the Intake based on the Stanford Spezi Medication Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -15,7 +8,7 @@ import Foundation import SpeziMedication -// This describes the IntakeMedication struct which contains a localizedDescription (medication name) and a list of dosages. +/// Describes the IntakeMedication struct which contains a localizedDescription (medication name) and a list of dosages. struct IntakeMedication: Medication, Comparable, Codable { var localizedDescription: String var dosages: [IntakeDosage] diff --git a/Intake/Medication View/IntakeMedicationInstance.swift b/Intake/MedicationView/Models/IntakeMedicationInstance.swift similarity index 74% rename from Intake/Medication View/IntakeMedicationInstance.swift rename to Intake/MedicationView/Models/IntakeMedicationInstance.swift index 2d694c0..431e7e4 100644 --- a/Intake/Medication View/IntakeMedicationInstance.swift +++ b/Intake/MedicationView/Models/IntakeMedicationInstance.swift @@ -1,10 +1,3 @@ -// -// IntakeMedicationInstance.swift -// Intake -// -// Created by Kate Callon on 2/17/24. -// -// // This source file is part of the Intake based on the Stanford Spezi Template Medication project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -15,7 +8,7 @@ import Foundation import SpeziMedication -// This defines an IntakeMedicationInstance which is composed of an id, an IntakeMedication type, a dosage, and a schedule. +/// This defines an IntakeMedicationInstance which is composed of an id, an IntakeMedication type, a dosage, and a schedule. struct IntakeMedicationInstance: MedicationInstance, MedicationInstanceInitializable, Codable { let id: UUID let type: IntakeMedication diff --git a/Intake/MockUpload/MockUpload.swift b/Intake/MockUpload/MockUpload.swift deleted file mode 100644 index 91305fe..0000000 --- a/Intake/MockUpload/MockUpload.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/Models/DataStore.swift b/Intake/Models/DataStore.swift new file mode 100644 index 0000000..b5bf51f --- /dev/null +++ b/Intake/Models/DataStore.swift @@ -0,0 +1,23 @@ +// +// This source file is part of the Intake based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation + + +@Observable +class DataStore: Codable { + var allergyData: [AllergyItem] = [] + var conditionData: [MedicalHistoryItem] = [] + var medicationData: Set = [] + var surgeries: [SurgeryItem] = [] + var surgeriesLoaded = false + var chiefComplaint: String = "" + var generalData = PatientData(name: "", birthdate: "", age: "", sex: "") + var menstrualHistory = MenstrualHistoryItem(startDate: Date(), endDate: Date(), additionalDetails: "") + var smokingHistory = SmokingHistoryItem(hasSmokedOrSmoking: Bool(), currentlySmoking: Bool(), smokedInThePast: Bool(), additionalDetails: "") +} diff --git a/Intake/Models/HealthDataModels.swift b/Intake/Models/HealthDataModels.swift new file mode 100644 index 0000000..71b7dc4 --- /dev/null +++ b/Intake/Models/HealthDataModels.swift @@ -0,0 +1,65 @@ +// +// This source file is part of the Intake based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation + + +struct PatientData: Codable { + var name: String + var birthdate: String + var age: String + var sex: String +} + +struct ReactionItem: Identifiable, Codable { + var id = UUID() + var reaction: String +} + + +struct AllergyItem: Identifiable, Equatable, Codable { + var id = UUID() + var allergy: String + var reaction: [ReactionItem] + + static func == (lhs: AllergyItem, rhs: AllergyItem) -> Bool { + lhs.allergy == rhs.allergy + } +} + +struct MedicalHistoryItem: Identifiable, Equatable, Codable { + var id = UUID() + var condition: String + var active: Bool +} + + +struct MenstrualHistoryItem: Codable { + var startDate: Date + var endDate: Date + var additionalDetails: String +} + +struct SmokingHistoryItem: Codable { + var hasSmokedOrSmoking: Bool + var currentlySmoking: Bool + var smokedInThePast: Bool + var additionalDetails: String +} + +struct SurgeryItem: Identifiable, Equatable, Codable { + var id = UUID() + var surgeryName: String = "" + var date: String = "" + var endDate: String = "" + var status: String = "" + var location: String = "" + var notes: [String] = [] + var bodySites: [String] = [] + var complications: [String] = [] +} diff --git a/Intake/LLMFiltering.swift b/Intake/Models/LLMFiltering.swift similarity index 97% rename from Intake/LLMFiltering.swift rename to Intake/Models/LLMFiltering.swift index 262351d..9d64809 100644 --- a/Intake/LLMFiltering.swift +++ b/Intake/Models/LLMFiltering.swift @@ -1,10 +1,4 @@ // -// LLMFiltering.swift -// Intake -// -// Created by Akash Gupta on 3/8/24. -// -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -23,11 +17,13 @@ class LLMFiltering { private var session: LLMOpenAISession private var data: DataStore + init(session: LLMOpenAISession, data: DataStore) { self.session = session self.data = data } + func filter(surgeries: [String]) async -> [String] { let stopWords = [ "screen", @@ -89,9 +85,7 @@ class LLMFiltering { return responseText } - func filterSurgeries() async throws -> [SurgeryItem] { - @Environment(DataStore.self) var data let filteredNames = try await self.LLMFilter(names: []) let filteredSurgeries = data.surgeries.filter { self.containsAnyWords(item: $0.surgeryName, words: filteredNames) } var cleaned = filteredSurgeries diff --git a/Intake/Models/NavigationHelpers.swift b/Intake/Models/NavigationHelpers.swift new file mode 100644 index 0000000..e97ff7b --- /dev/null +++ b/Intake/Models/NavigationHelpers.swift @@ -0,0 +1,46 @@ +// +// This source file is part of the Intake based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SwiftUI + + +@Observable +class NavigationPathWrapper { + var path = NavigationPath() +} + +@Observable +class ReachedEndWrapper { + var reachedEnd = false + var surgeriesLoaded = false +} + +@Observable +class LoadedWrapper { + var conditionData = false + var allergyData = false +} + + +enum NavigationViews: String { + case allergies + case surgical + case medical + case menstrual + case smoking + case medication + case chat + case concern + case export + case patient + case pdfs + case inspect + case general + case newAllergy +} diff --git a/Intake/Onboarding/AccountOnboarding.swift b/Intake/Onboarding/AccountOnboarding.swift deleted file mode 100644 index 6c6a7c0..0000000 --- a/Intake/Onboarding/AccountOnboarding.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/Onboarding/OnboardingFlow.swift b/Intake/Onboarding/OnboardingFlow.swift index c7148bd..adee8ed 100644 --- a/Intake/Onboarding/OnboardingFlow.swift +++ b/Intake/Onboarding/OnboardingFlow.swift @@ -6,8 +6,6 @@ // SPDX-License-Identifier: MIT // -import SpeziAccount -import SpeziFirebaseAccount import SpeziHealthKit import SpeziOnboarding import SwiftUI @@ -15,10 +13,9 @@ import SwiftUI /// Displays an multi-step onboarding flow for the Intake. struct OnboardingFlow: View { @Environment(HealthKit.self) private var healthKitDataSource - @Environment(IntakeScheduler.self) private var scheduler - @AppStorage(StorageKeys.onboardingFlowComplete) private var completedOnboardingFlow = false + private var healthKitAuthorization: Bool { // As HealthKit not available in preview simulator if ProcessInfo.processInfo.isPreviewSimulator { @@ -32,10 +29,6 @@ struct OnboardingFlow: View { Welcome() InterestingModules() - if !FeatureFlags.disableFirebase { - AccountOnboarding() - } - #if !(targetEnvironment(simulator) && (arch(i386) || arch(x86_64))) Consent() #endif @@ -51,15 +44,9 @@ struct OnboardingFlow: View { #if DEBUG #Preview { OnboardingFlow() - .environment(Account(MockUserIdPasswordAccountService())) .previewWith(standard: IntakeStandard()) { OnboardingDataSource() HealthKit() - AccountConfiguration { - MockUserIdPasswordAccountService() - } - - IntakeScheduler() } } #endif diff --git a/Intake/PatientView/EditPatient.swift b/Intake/PatientView/EditPatient.swift new file mode 100644 index 0000000..56564aa --- /dev/null +++ b/Intake/PatientView/EditPatient.swift @@ -0,0 +1,46 @@ +// +// This source file is part of the Intake based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SpeziFHIR +import SwiftUI + + +struct EditPatientView: View { + @Environment(DataStore.self) private var data + @Environment(FHIRStore.self) private var fhirStore + + + var body: some View { + @Bindable var data = data + + VStack { + Form { + Section(header: Text("Name")) { + TextField("Name", text: $data.generalData.name) + } + Section(header: Text("Date of Birth")) { + TextField("Date of Birth", text: $data.generalData.birthdate) + } + Section(header: Text("Age")) { + TextField("Age", text: $data.generalData.age) + } + Section(header: Text("Sex")) { + TextField("Sex", text: $data.generalData.sex) + } + } + + SubmitButton(nextView: NavigationViews.pdfs) + } + } +} + + +// #Preview { +// EditPatientView() +// } diff --git a/Intake/General Data View/PatientInfo.swift b/Intake/PatientView/PatientInfo.swift similarity index 69% rename from Intake/General Data View/PatientInfo.swift rename to Intake/PatientView/PatientInfo.swift index 90ec327..3a49b88 100644 --- a/Intake/General Data View/PatientInfo.swift +++ b/Intake/PatientView/PatientInfo.swift @@ -1,36 +1,66 @@ -//// -//// SwiftUIView.swift -//// Intake -//// -//// Created by Nina Boord on 3/9/24. -//// // This source file is part of the Intake based on the Stanford Spezi Template Medication project // // SPDX-FileCopyrightText: 2023 Stanford University // // SPDX-License-Identifier: MIT // -// The Patient Info View allows the patient to enter their name, date of birth, and sex. If the patient is connected with Healthkit, it allows -// them to verify their information is correct and edit it if not. The information is automatically pulled from Healthkit if the patient is connected. import SpeziFHIR import SwiftUI -// swiftlint:disable type_contents_order -// Again, no matter the order of these variables, there is still issues with type_contents_order. It was necessary to just disable this. +/// The Patient Info View allows the patient to enter their name, date of birth, and sex. If the patient is connected with Healthkit, it allows +/// them to verify their information is correct and edit it if not. The information is automatically pulled from Healthkit if the patient is connected. struct PatientInfo: View { @State private var fullName: String = "" @State private var firstName: String = "" @State private var birthdate: String = "" @State private var gender: String = "female" - @State private var sexOption: String = "" + @State private var sexOption: String = "Female" @State private var birthdateDateFormat = Date() @Environment(DataStore.self) private var data @Environment(NavigationPathWrapper.self) private var navigationPath @Environment(FHIRStore.self) private var fhirStore - func calculateAge(from dobString: String, with format: String = "yyyy-MM-dd") -> String { + + var body: some View { + @Bindable var data = data + + VStack { + Form { + Section(header: Text("Patient Information")) { + TextField(text: $data.generalData.name) { + Text("Full name") + } + + DatePicker("Date of Birth:", selection: $birthdateDateFormat, in: ...Date(), displayedComponents: .date) + .datePickerStyle(DefaultDatePickerStyle()) + + Picker("Sex", selection: $sexOption) { + ForEach(["Female", "Male"], id: \.self) { option in + Text(option).tag(option) + } + } + .pickerStyle(MenuPickerStyle()) + } + } + + SubmitButtonWithAction( + nextView: FeatureFlags.skipToScrollable ? .pdfs : .chat, + onButtonTap: { + updateData() + }, + accessibilityIdentifier: "Next" + ) + .padding() + } + .task { + loadData() + } + } + + + private func calculateAge(from dobString: String, with format: String = "yyyy-MM-dd") -> String { if dobString.isEmpty { return "" } @@ -49,7 +79,7 @@ struct PatientInfo: View { } } - func getValue(forKey key: String, from jsonString: String) -> String? { + private func getValue(forKey key: String, from jsonString: String) -> String? { guard let jsonData = jsonString.data(using: .utf8) else { print("Error: Cannot create Data from JSON string") return nil @@ -81,7 +111,7 @@ struct PatientInfo: View { return nil } - func getInfo(patient: FHIRResource, field: String) -> String { + private func getInfo(patient: FHIRResource, field: String) -> String { let jsonDescription = patient.jsonDescription if let infoValue = getValue(forKey: field, from: jsonDescription) { @@ -93,54 +123,6 @@ struct PatientInfo: View { return "" } - var body: some View { - @Bindable var data = data - // Necessary because without it other linting errors arise regarding each argument needing their own line - // swiftlint:disable closure_body_length - Form { - Section(header: Text("Patient Information")) { - HStack { - TextField("Full name", text: $data.generalData.name) - Spacer() - } - HStack { - DatePicker("Date of Birth:", selection: $birthdateDateFormat, in: ...Date(), displayedComponents: .date) - .datePickerStyle(DefaultDatePickerStyle()) - } - HStack { - let options = ["Female", "Male"] - Picker("Sex", selection: $sexOption) { - ForEach(options, id: \.self) { option in - Text(option).tag(option) - } - } - .pickerStyle(MenuPickerStyle()) - } - } - Spacer() - if FeatureFlags.skipToScrollable { - SubmitButtonWithAction( - nextView: .pdfs, - onButtonTap: { - updateData() - }, - accessibilityIdentifier: "Next" - ) - } else { - SubmitButtonWithAction( - nextView: .chat, - onButtonTap: { - updateData() - }, - accessibilityIdentifier: "Next" - ) - } - } - .task { - loadData() - } - } - @MainActor private func loadData() { if let patient = fhirStore.patient { diff --git a/Intake/Resources/AppIcon.png.license b/Intake/Resources/AppIcon.png.license deleted file mode 100644 index d9041c7..0000000 --- a/Intake/Resources/AppIcon.png.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the Intake based on the Stanford Spezi Template Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/Contents.json b/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/Contents.json deleted file mode 100644 index feec583..0000000 --- a/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "filename" : "ProfilePicture.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "localizable" : true - } -} diff --git a/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/Contents.json.license b/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/Contents.json.license deleted file mode 100644 index db20e1c..0000000 --- a/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/Contents.json.license +++ /dev/null @@ -1,5 +0,0 @@ -This source file is part of the Intake based on the Stanford Spezi Template Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/ProfilePicture.png b/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/ProfilePicture.png deleted file mode 100644 index 5307a0c..0000000 Binary files a/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/ProfilePicture.png and /dev/null differ diff --git a/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/ProfilePicture.png.license b/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/ProfilePicture.png.license deleted file mode 100644 index db20e1c..0000000 --- a/Intake/Resources/Assets.xcassets/ProfilePicture.imageset/ProfilePicture.png.license +++ /dev/null @@ -1,5 +0,0 @@ -This source file is part of the Intake based on the Stanford Spezi Template Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/Intake/Resources/Assets.xcassets/ProfilePicture.png b/Intake/Resources/Assets.xcassets/ProfilePicture.png deleted file mode 100644 index 5307a0c..0000000 Binary files a/Intake/Resources/Assets.xcassets/ProfilePicture.png and /dev/null differ diff --git a/Intake/Resources/Assets.xcassets/ProfilePicture.png.license b/Intake/Resources/Assets.xcassets/ProfilePicture.png.license deleted file mode 100644 index db20e1c..0000000 --- a/Intake/Resources/Assets.xcassets/ProfilePicture.png.license +++ /dev/null @@ -1,5 +0,0 @@ -This source file is part of the Intake based on the Stanford Spezi Template Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT \ No newline at end of file diff --git a/Intake/Resources/Documentation.docc/Documentation.md b/Intake/Resources/Documentation.docc/Documentation.md deleted file mode 100644 index 2b489a0..0000000 --- a/Intake/Resources/Documentation.docc/Documentation.md +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT - -# ``Intake`` - -Summary - -## Overview - -Text - -## Topics - -### Group - -- ``Symbol`` diff --git a/Intake/Resources/Edythe31.json b/Intake/Resources/Edythe31.json deleted file mode 100644 index e69de29..0000000 diff --git a/Intake/Resources/Intake.docc/Intake.md b/Intake/Resources/Intake.docc/Intake.md new file mode 100644 index 0000000..9721693 --- /dev/null +++ b/Intake/Resources/Intake.docc/Intake.md @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT + +# ``Intake`` + +The intake project of CS342 winter 2024. diff --git a/Intake/Resources/Localizable.xcstrings b/Intake/Resources/Localizable.xcstrings index b1062e9..1bc5bc4 100644 --- a/Intake/Resources/Localizable.xcstrings +++ b/Intake/Resources/Localizable.xcstrings @@ -24,6 +24,7 @@ }, "ACCOUNT_NEXT" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -34,6 +35,7 @@ } }, "ACCOUNT_SETUP_DESCRIPTION" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -44,6 +46,7 @@ } }, "ACCOUNT_SIGNED_IN_DESCRIPTION" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -54,6 +57,7 @@ } }, "ACCOUNT_SUBTITLE" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -65,6 +69,7 @@ }, "ACCOUNT_TITLE" : { "comment" : "MARK: Account", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -76,6 +81,9 @@ }, "Active" : { + }, + "Active Box" : { + }, "Add Allergy Field" : { @@ -86,7 +94,7 @@ "Add_allergy" : { }, - "Add_allergy" : { + "add_condition" : { }, "ADD_REACTION" : { @@ -139,9 +147,6 @@ }, "Back" : { - }, - "Chat" : { - }, "Chief Complaint:" : { @@ -159,6 +164,7 @@ }, "CLOSE" : { "comment" : "MARK: General", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -169,6 +175,7 @@ } }, "COMPLETED_TASK_LABEL %@" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -180,6 +187,9 @@ }, "Condition" : { + }, + "Condition Box" : { + }, "CONSENT_LOADING_ERROR" : { "localizations" : { @@ -192,6 +202,7 @@ } }, "CONTACTS_NAVIGATION_TITLE" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -214,6 +225,7 @@ } }, "CONTRIBUTIONS_LIST_DESCRIPTION" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -224,6 +236,7 @@ } }, "CONTRIBUTIONS_LIST_FOOTER" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -234,6 +247,7 @@ } }, "CONTRIBUTIONS_LIST_HEADER" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -266,9 +280,6 @@ }, "DELETE_SURGERY" : { - }, - "Do you currently smoke or have you smoked in the past?" : { - }, "Download your medical records from your health system." : { @@ -383,6 +394,7 @@ }, "LICENSE_INFO_TITLE" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -572,6 +584,7 @@ } }, "OLIVER_AALAMI_BIO" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -615,6 +628,7 @@ }, "PROJECT_LICENSE_DESCRIPTION" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -625,6 +639,7 @@ } }, "QUESTIONNAIRE_DESCRIPTION" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -635,6 +650,7 @@ } }, "QUESTIONNAIRE_TITLE" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -651,6 +667,7 @@ }, "Repository Link" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -676,6 +693,7 @@ }, "SCHEDULE_LIST_TITLE" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -763,11 +781,24 @@ }, "Surgery Assistant" : { + }, + "SURGERY_DATE" : { + + }, + "SURGERY_LOCATION" : { + + }, + "SURGERY_NAME" : { + + }, + "SURGERY_STATUS" : { + }, "Surgical History" : { }, "TASK_CONTEXT_ACTION_QUESTIONNAIRE" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -778,6 +809,7 @@ } }, "TASK_CONTEXT_ACTION_TEST" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -788,6 +820,7 @@ } }, "TASK_LABEL %@" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { diff --git a/Intake/Resources/Questionnaire.json b/Intake/Resources/Questionnaire.json deleted file mode 100644 index 75c7b10..0000000 --- a/Intake/Resources/Questionnaire.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"task4","resourceType":"Questionnaire","language":"en-US","status":"draft","publisher":"Stanford Biodesign Digital Health","meta":{"profile":["http://spezi.health/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":"task4"}]}}],"contact":[{"name":"http://spezi.health"}],"subjectType":["Patient"],"url":"http://spezi.health/fhir/questionnaire/ea57e7df-e569-4fec-83d1-5e43a1944c84","item":[{"linkId":"d59fac04-c9e6-4c27-82c1-e3010cc619c8","type":"group","item":[{"linkId":"dbb5b89e-f632-4a9e-8cd0-7ab9fddaaf82","type":"string","text":"Full name","required":true},{"linkId":"5529c3f4-a025-4761-ad03-748a39a14f88","type":"date","text":"Date of birth\n","required":true},{"linkId":"c0c696c2-2ff6-4e84-d808-e5a148151d5a","type":"string","text":"Address","required":true},{"linkId":"3003a9b3-bf1b-4bff-82ff-c4f1c4887e39","type":"group","text":"Contact details","item":[{"linkId":"0eb19e90-16f1-4e4e-8ef5-aea6cddc961f","type":"string","text":"Email address","required":true},{"linkId":"96187243-4eec-439c-8058-f97cb76755ba","type":"integer","text":"Phone number","required":true}],"required":false},{"linkId":"5e3f3873-9651-47cf-a652-51008976e95f","type":"choice","text":"Gender","required":false,"answerOption":[{"valueCoding":{"id":"262c1a93-637a-4e18-d71b-926e0972d4a7","code":"Male","system":"urn:uuid:69e068eb-23d1-4d83-a06f-a8de531ccf65","display":"Male"}},{"valueCoding":{"id":"7864f936-ef73-48df-ff3c-cf99af7f757e","code":"Female","system":"urn:uuid:69e068eb-23d1-4d83-a06f-a8de531ccf65","display":"Female"}},{"valueCoding":{"id":"7dd6248b-1053-4bb4-8f0f-f46d8a8b2118","code":"Non-binary","system":"urn:uuid:69e068eb-23d1-4d83-a06f-a8de531ccf65","display":"Non-binary"}},{"valueCoding":{"id":"10c21c81-de9a-49bb-8cea-e8554222ff57","code":"Prefer-not-to-respond","system":"urn:uuid:69e068eb-23d1-4d83-a06f-a8de531ccf65","display":"Prefer not to respond"}}]},{"linkId":"b70ae8b1-33ea-4ab6-f246-0b245edfaf27","type":"choice","text":"Marital status","required":true,"answerOption":[{"valueCoding":{"id":"789ef8ab-fd5a-44c5-fefd-26d410abb854","code":"single","system":"urn:uuid:278980b7-9374-4d67-80b8-cadfcc1840b9","display":"Single"}},{"valueCoding":{"id":"887e02ab-d5f6-4539-9afc-83a547183cf0","code":"married","system":"urn:uuid:278980b7-9374-4d67-80b8-cadfcc1840b9","display":"Married"}},{"valueCoding":{"id":"ed865440-844b-4bec-9034-31065e44163e","code":"divorced","system":"urn:uuid:278980b7-9374-4d67-80b8-cadfcc1840b9","display":"Divorced"}},{"valueCoding":{"id":"38777e8c-b04b-4887-99d2-c089770a66b0","code":"widowed","system":"urn:uuid:278980b7-9374-4d67-80b8-cadfcc1840b9","display":"Widowed"}},{"valueCoding":{"id":"3bb0baaa-47f2-4973-8f8b-98ec380e450c","code":"seperated","system":"urn:uuid:278980b7-9374-4d67-80b8-cadfcc1840b9","display":"Seperated"}}]},{"linkId":"00a0f56a-6e7c-4a5c-81d8-fd6896b4ec57","type":"string","text":"Occupation","required":false}],"required":false,"text":"Personal and Demographic Information\n"},{"linkId":"588aa772-5a60-4ef4-9ba1-1230adab30a9","type":"group","text":"Emergency Contact Details\n","item":[{"linkId":"644b6519-69c7-4a46-b410-e0161e38a265","type":"string","text":"Name of emergency contact","required":true},{"linkId":"b2c91cf9-d276-4700-801c-8e6b3783f60e","type":"choice","text":"Relationship to the patient","required":false,"answerOption":[{"valueCoding":{"id":"96b2fa2e-079a-4a08-8105-535a444f42b7","code":"parent","system":"urn:uuid:4280c02b-ecb4-4450-b0af-017a9e86d5f6","display":"Parent"}},{"valueCoding":{"id":"023f6fcc-9502-4cbb-c911-4d04e7f48168","code":"friend","system":"urn:uuid:4280c02b-ecb4-4450-b0af-017a9e86d5f6","display":"Friend"}},{"valueCoding":{"id":"52c22ef3-80e0-445c-8413-0b77e521fc5e","code":"guardian","system":"urn:uuid:4280c02b-ecb4-4450-b0af-017a9e86d5f6","display":"Guardian"}},{"valueCoding":{"id":"03917d67-4b8d-40d4-f1f5-5ecbebcebe77","code":"extended-family","system":"urn:uuid:4280c02b-ecb4-4450-b0af-017a9e86d5f6","display":"Extended family"}}]},{"linkId":"959d8df6-b56c-4f5d-8176-2872b4408bb0","type":"group","text":"Contact details\n","item":[{"linkId":"1d82a12a-314f-44fe-8e11-aeb4121b8939","type":"string","text":"Email address","required":false},{"linkId":"ee4e79bf-2eae-4411-d742-f2f8a124d400","type":"integer","text":"Phone number","required":false}],"required":false}],"required":false},{"linkId":"2dfcf2f3-5941-4527-8ccc-ce190fe2ac4a","type":"group","text":"Reason for Visit","item":[{"linkId":"f30a0478-a14d-4e88-87f9-42d6e924d8a1","type":"string","text":"Chief complaint?","required":false},{"linkId":"87decc51-b6ca-4d81-9a24-9b40d937fc9a","type":"string","text":"Past Medical History","required":false},{"linkId":"522dec16-6823-474d-f403-44b6a52a5e33","type":"string","text":"Past Surgical History","required":false},{"linkId":"af2c11d6-3e6f-4089-adf0-03c859e75287","type":"group","text":"Medications","item":[{"linkId":"cb70cd3e-d349-4973-9385-1d5c7bef75f1","type":"string","text":"Medication name","required":false},{"linkId":"932ebee8-ed58-4857-8b0c-bc90e5aed0e7","type":"string","text":"Dose with units (eg: 10mg)","required":false},{"linkId":"eecdbe05-76e1-4e15-840a-e81b9969c34c","type":"string","text":"Route (eg: by mouth, topical, injection, etc.)","required":false}],"required":false},{"linkId":"33bab683-6459-4934-8b98-a3dd271411f3","type":"group","text":"Allergies","item":[{"linkId":"4c9412a5-dd3e-45fd-d1d8-98c43f604b1d","type":"string","text":"Allergen","required":false},{"linkId":"d52057e5-b8ce-42a7-8d21-987aa6957795","type":"string","text":"Reaction type (eg: rash, anaphylaxis, etc.)","required":false}],"required":false}],"required":false}]} \ No newline at end of file diff --git a/Intake/Resources/Questionnaire.json.license b/Intake/Resources/Questionnaire.json.license deleted file mode 100644 index d9041c7..0000000 --- a/Intake/Resources/Questionnaire.json.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the Intake based on the Stanford Spezi Template Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/screenshots/chiefComplaint.png b/Intake/Resources/Screenshots/chiefComplaint.png similarity index 100% rename from screenshots/chiefComplaint.png rename to Intake/Resources/Screenshots/chiefComplaint.png diff --git a/screenshots/chiefComplaint.png.license b/Intake/Resources/Screenshots/chiefComplaint.png.license similarity index 100% rename from screenshots/chiefComplaint.png.license rename to Intake/Resources/Screenshots/chiefComplaint.png.license diff --git a/screenshots/medication.png b/Intake/Resources/Screenshots/medication.png similarity index 100% rename from screenshots/medication.png rename to Intake/Resources/Screenshots/medication.png diff --git a/screenshots/medication.png.license b/Intake/Resources/Screenshots/medication.png.license similarity index 100% rename from screenshots/medication.png.license rename to Intake/Resources/Screenshots/medication.png.license diff --git a/screenshots/summary.png b/Intake/Resources/Screenshots/summary.png similarity index 100% rename from screenshots/summary.png rename to Intake/Resources/Screenshots/summary.png diff --git a/screenshots/summary.png.license b/Intake/Resources/Screenshots/summary.png.license similarity index 100% rename from screenshots/summary.png.license rename to Intake/Resources/Screenshots/summary.png.license diff --git a/Intake/Resources/SocialSupportQuestionnaire.json b/Intake/Resources/SocialSupportQuestionnaire.json deleted file mode 100644 index d3c584d..0000000 --- a/Intake/Resources/SocialSupportQuestionnaire.json +++ /dev/null @@ -1,387 +0,0 @@ -{ - "resourceType": "Questionnaire", - "language": "en-US", - "id": "socialsupport", - "name": "SocialSupport", - "title": "Social Support", - "description": "This survey measures tangible social support plus a couple of demographic questions.", - "version": "1", - "status": "draft", - "publisher": "RAND Corp", - "meta": { - "profile": [ - "http://spezi.stanford.edu/fhir/StructureDefinition/sdf-Questionnaire" - ], - "tag": [ - { - "system": "urn:ietf:bcp:47", - "code": "en-US", - "display": "English" - } - ] - }, - "useContext": [ - { - "code": { - "system": "http://hl7.org/fhir/ValueSet/usage-context-type", - "code": "focus", - "display": "Clinical Focus" - }, - "valueCodeableConcept": { - "coding": [ - { - "system": "urn:oid:2.16.578.1.12.4.1.1.8655", - "display": "Social Support" - } - ] - } - } - ], - "contact": [ - { - "name": "https://www.rand.org/health-care/surveys_tools/mos/social-support/survey-instrument.html" - } - ], - "subjectType": [ - "Patient" - ], - "purpose": "The RAND Medical Outcomes Social Support survey is a 4-item questionnaire that measures social support.", - "copyright": "RAND Corp surveys are open-source and free to use.", - "date": "2023-01-23T00:00:00-08:00", - "url": "http://spezi.stanford.edu/fhir/questionnaire/32f43c8e-93e9-4c70-97a0-e716f8030073", - "item": [ - { - "linkId": "dcea2683-9815-4505-b240-e75b502b29ef", - "type": "choice", - "text": "How often do you need someone to help you if you were confined to bed?", - "required": false, - "answerOption": [ - { - "valueCoding": { - "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", - "code": "1", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "None of the time" - } - }, - { - "valueCoding": { - "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", - "code": "2", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "A little of the time" - } - }, - { - "valueCoding": { - "id": "e32f7952-e280-48d7-9746-c13dbb26638f", - "code": "3", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Some of the time" - } - }, - { - "valueCoding": { - "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", - "code": "4", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Most of the time" - } - }, - { - "valueCoding": { - "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", - "code": "5", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "All of the time" - } - } - ] - }, - { - "linkId": "ce09d701-7b93-4150-defb-51825e05ade9", - "type": "choice", - "text": "How often do you need someone to take you to the doctor if you needed it?", - "required": false, - "answerOption": [ - { - "valueCoding": { - "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", - "code": "1", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "None of the time" - } - }, - { - "valueCoding": { - "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", - "code": "2", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "A little of the time" - } - }, - { - "valueCoding": { - "id": "e32f7952-e280-48d7-9746-c13dbb26638f", - "code": "3", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Some of the time" - } - }, - { - "valueCoding": { - "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", - "code": "4", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Most of the time" - } - }, - { - "valueCoding": { - "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", - "code": "5", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "All of the time" - } - } - ] - }, - { - "linkId": "58e97564-5f4d-4d4b-86d5-6429cbbc7a8e", - "type": "choice", - "text": "How often do you need someone to prepare your meals if you were unable to do it yourself?", - "required": false, - "answerOption": [ - { - "valueCoding": { - "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", - "code": "1", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "None of the time" - } - }, - { - "valueCoding": { - "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", - "code": "2", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "A little of the time" - } - }, - { - "valueCoding": { - "id": "e32f7952-e280-48d7-9746-c13dbb26638f", - "code": "3", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Some of the time" - } - }, - { - "valueCoding": { - "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", - "code": "4", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Most of the time" - } - }, - { - "valueCoding": { - "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", - "code": "5", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "All of the time" - } - } - ] - }, - { - "linkId": "ad161c49-e8a6-4d31-90e8-02b2887a765f", - "type": "choice", - "text": "How often do you need someone to help with daily chores if you were sick", - "required": false, - "answerOption": [ - { - "valueCoding": { - "id": "3d6fe1b8-c64b-497c-8583-db7ddda9e94e", - "code": "1", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "None of the time" - } - }, - { - "valueCoding": { - "id": "b4081e9d-d0f1-4aea-9a15-eac4a15d1d10", - "code": "2", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "A little of the time" - } - }, - { - "valueCoding": { - "id": "e32f7952-e280-48d7-9746-c13dbb26638f", - "code": "3", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Some of the time" - } - }, - { - "valueCoding": { - "id": "d2f6172d-9402-4cb3-870a-584a7be3a5d7", - "code": "4", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "Most of the time" - } - }, - { - "valueCoding": { - "id": "ec48001e-f03e-4a14-8a7a-9fcf34fa81d2", - "code": "5", - "system": "urn:uuid:e9ecdd47-2e8b-49b3-8780-9d0769a246aa", - "display": "All of the time" - } - } - ] - }, - { - "linkId": "ba518851-2843-4bbd-c0f7-5b5692d542e0", - "type": "integer", - "text": "What is your age?", - "extension": [ - { - "url": "http://hl7.org/fhir/StructureDefinition/minValue", - "valueInteger": 18 - }, - { - "url": "http://hl7.org/fhir/StructureDefinition/maxValue", - "valueInteger": 120 - }, - { - "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", - "valueString": "Please enter a valid age." - } - ], - "required": false - }, - { - "linkId": "695525f3-3e89-4455-8e25-878171c596da", - "type": "choice", - "text": "What is your preferred contact method?", - "required": false, - "answerOption": [ - { - "valueCoding": { - "id": "b7a3d7a5-52b9-49b1-8b59-7a3885483f1c", - "code": "phone-call", - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "display": "Phone call" - } - }, - { - "valueCoding": { - "id": "3d42dde0-8e60-4832-bd46-bd06de28cbf2", - "code": "text-message", - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "display": "Text message" - } - }, - { - "valueCoding": { - "id": "e672cfc6-118f-4a2d-aafd-02722ff876b9", - "code": "e-mail", - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "display": "E-mail" - } - } - ] - }, - { - "linkId": "c3bea33d-4c50-4f4a-8ae4-1a52be326b19", - "type": "string", - "text": "What is your phone number? Ex. (555) 555-5555", - "required": false, - "enableWhen": [ - { - "question": "695525f3-3e89-4455-8e25-878171c596da", - "operator": "=", - "answerCoding": { - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "code": "phone-call" - } - } - ], - "extension": [ - { - "url": "http://hl7.org/fhir/StructureDefinition/regex", - "valueString": "^(\\([0-9]{3}\\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$" - }, - { - "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", - "valueString": "Please enter a valid phone number." - } - ] - }, - { - "linkId": "8e906a39-5fd0-42a8-f42c-bd96d719dd13", - "type": "string", - "text": "What is your text number? Ex. (555) 555-5555", - "required": false, - "enableWhen": [ - { - "question": "695525f3-3e89-4455-8e25-878171c596da", - "operator": "=", - "answerCoding": { - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "code": "text-message" - } - } - ], - "extension": [ - { - "url": "http://hl7.org/fhir/StructureDefinition/regex", - "valueString": "^(\\([0-9]{3}\\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$" - }, - { - "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", - "valueString": "Please enter a valid phone number." - } - ] - }, - { - "linkId": "86290b0a-017e-4193-8707-dc0c2146f0eb", - "type": "string", - "text": "What is your e-mail?", - "extension": [ - { - "url": "http://hl7.org/fhir/StructureDefinition/regex", - "valueString": ".*@.+" - }, - { - "url": "http://biodesign.stanford.edu/fhir/StructureDefinition/validationtext", - "valueString": "Please enter a valid email" - }, - { - "url": "http://hl7.org/fhir/StructureDefinition/minLength", - "valueInteger": 1 - } - ], - "required": false, - "maxLength": 50, - "enableWhen": [ - { - "question": "695525f3-3e89-4455-8e25-878171c596da", - "operator": "=", - "answerCoding": { - "system": "urn:uuid:736ac230-812a-4f4a-edec-5156910fb6ec", - "code": "e-mail" - } - } - ] - }, - { - "linkId": "305f5381-2d8b-4b98-bc04-5a39bee2f7ec", - "type": "display", - "text": "Thank you for taking the survey!", - "required": false - } - ] -} diff --git a/Intake/Resources/SocialSupportQuestionnaire.json.license b/Intake/Resources/SocialSupportQuestionnaire.json.license deleted file mode 100644 index d9041c7..0000000 --- a/Intake/Resources/SocialSupportQuestionnaire.json.license +++ /dev/null @@ -1,6 +0,0 @@ - -This source file is part of the Intake based on the Stanford Spezi Template Application project - -SPDX-FileCopyrightText: 2023 Stanford University - -SPDX-License-Identifier: MIT diff --git a/Intake/Schedule/Bundle+Questionnaire.swift b/Intake/Schedule/Bundle+Questionnaire.swift deleted file mode 100644 index 98c5e6d..0000000 --- a/Intake/Schedule/Bundle+Questionnaire.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/Schedule/EventContext.swift b/Intake/Schedule/EventContext.swift deleted file mode 100644 index 7125f58..0000000 --- a/Intake/Schedule/EventContext.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/Schedule/EventContextView.swift b/Intake/Schedule/EventContextView.swift deleted file mode 100644 index 072c2ee..0000000 --- a/Intake/Schedule/EventContextView.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// This source file is part of the Intake 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 = IntakeScheduler.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/Intake/Schedule/IntakeScheduler.swift b/Intake/Schedule/IntakeScheduler.swift deleted file mode 100644 index b085e2e..0000000 --- a/Intake/Schedule/IntakeScheduler.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// This source file is part of the Intake 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 ``IntakeTaskContext`` to schedule and manage tasks and events in the -/// Intake. -typealias IntakeScheduler = Scheduler - -extension IntakeScheduler { - 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: "QUESTIONNAIRE_TITLE"), - description: String(localized: "QUESTIONNAIRE_DESCRIPTION"), - schedule: Schedule( - start: Calendar.current.startOfDay(for: Date()), - repetition: .matching(dateComponents), - end: .numberOfEvents(365) - ), - notifications: true, - context: IntakeTaskContext.questionnaire(Bundle.main.questionnaire(withName: "Questionnaire")) - ) - } - - /// Creates a default instance of the ``IntakeScheduler`` by scheduling the tasks listed below. - convenience init() { - self.init(tasks: [Self.socialSupportTask]) - } -} diff --git a/Intake/Schedule/IntakeTaskContext.swift b/Intake/Schedule/IntakeTaskContext.swift deleted file mode 100644 index 3d29daf..0000000 --- a/Intake/Schedule/IntakeTaskContext.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// This source file is part of the Intake 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 Intake. -/// -/// We currently only support `Questionnaire`s, more cases can be added in the future. -enum IntakeTaskContext: 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/Intake/Schedule/ModalView.swift b/Intake/Schedule/ModalView.swift deleted file mode 100644 index e878692..0000000 --- a/Intake/Schedule/ModalView.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// This source file is part of the Intake 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/Intake/Schedule/ScheduleView.swift b/Intake/Schedule/ScheduleView.swift deleted file mode 100644 index bdf21c4..0000000 --- a/Intake/Schedule/ScheduleView.swift +++ /dev/null @@ -1,123 +0,0 @@ -// -// This source file is part of the Intake based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SpeziAccount -import SpeziQuestionnaire -import SpeziScheduler -import SwiftUI - -struct ScheduleView: View { - @Environment(IntakeStandard.self) private var standard - @Environment(IntakeScheduler.self) private var scheduler - @State private var eventContextsByDate: [Date: [EventContext]] = [:] - @State private var presentedContext: EventContext? - - @Binding private var presentingAccount: Bool - - private var startOfDays: [Date] { - Array(eventContextsByDate.keys) - } - - var body: some View { - NavigationStack { - List(startOfDays, id: \.timeIntervalSinceNow) { startOfDay in - Section(format(startOfDay: startOfDay)) { - ForEach(eventContextsByDate[startOfDay] ?? [], id: \.event) { eventContext in - EventContextView(eventContext: eventContext) - .onTapGesture { - if !eventContext.event.complete { - presentedContext = eventContext - } - } - } - } - } - .onChange(of: scheduler) { - calculateEventContextsByDate() - } - .task { - calculateEventContextsByDate() - } - .sheet(item: $presentedContext) { presentedContext in - destination(withContext: presentedContext) - } - .toolbar { - if AccountButton.shouldDisplay { - AccountButton(isPresented: $presentingAccount) - } - } - .navigationTitle("SCHEDULE_LIST_TITLE") - } - } - - init(presentingAccount: Binding) { - self._presentingAccount = presentingAccount - } - - private func destination(withContext eventContext: EventContext) -> some View { - @ViewBuilder var destination: some View { - switch eventContext.task.context { - case let .questionnaire(questionnaire): - QuestionnaireView(questionnaire: questionnaire) { result in - presentedContext = nil - - guard case let .completed(response) = result else { - return // user cancelled the task - } - - eventContext.event.complete(true) - await standard.add(response: response) - } - case let .test(string): - ModalView(text: string, buttonText: String(localized: "CLOSE")) { - await eventContext.event.complete(true) - } - } - } - return destination - } - - private func format(startOfDay: Date) -> String { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .long - dateFormatter.timeStyle = .none - return dateFormatter.string(from: startOfDay) - } - - private func calculateEventContextsByDate() { - let eventContexts = scheduler.tasks.flatMap { task in - task - .events( - from: Calendar.current.startOfDay(for: .now), - to: .numberOfEventsOrEndDate(100, .now) - ) - .map { event in - EventContext(event: event, task: task) - } - } - .sorted() - - let newEventContextsByDate = Dictionary(grouping: eventContexts) { eventContext in - Calendar.current.startOfDay(for: eventContext.event.scheduledAt) - } - - eventContextsByDate = newEventContextsByDate - } -} - -#if DEBUG -#Preview("ScheduleView") { - ScheduleView(presentingAccount: .constant(false)) - .previewWith(standard: IntakeStandard()) { - IntakeScheduler() - AccountConfiguration { - MockUserIdPasswordAccountService() - } - } -} -#endif diff --git a/Intake/Settings/SettingsView.swift b/Intake/Settings/SettingsView.swift deleted file mode 100644 index 76209dc..0000000 --- a/Intake/Settings/SettingsView.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// This source file is part of the Stanford LLM on FHIR project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -// import SpeziFHIRInterpretation -// import SpeziOpenAI -import SwiftUI - -struct SettingsView: View { - private enum SettingsDestinations { - case openAIKey - case openAIModel - case resourceSelection - case promptSummary - case promptInterpretation - case promptMultipleResourceInterpretation - } - - @State private var path = NavigationPath() - @Environment(\.dismiss) private var dismiss - - var body: some View { - NavigationStack(path: $path) { - List { - // openAISettings - // speechSettings - // resourcesLimitSettings - resourcesSettings - // promptsSettings - } - .navigationTitle("SETTINGS_TITLE") - .navigationDestination(for: SettingsDestinations.self) { destination in - navigationDesination(for: destination) - } - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("FHIR_RESOURCES_CHAT_CANCEL") { - dismiss() - } - } - } - } - } - - /* - private var speechSettings: some View { - Section("SETTINGS_SPEECH") { - Toggle(isOn: $enableTextToSpeech) { - Text("SETTINGS_SPEECH_TEXT_TO_SPEECH") - } - } - } - - private var resourcesLimitSettings: some View { - Section("Resource Limit") { - Stepper(value: $resourceLimit, in: 10...2000, step: 10) { - Text("Resource Limit \(resourceLimit)") - } - } - } - */ - - private var resourcesSettings: some View { - Section("Resource Selection") { - NavigationLink(value: SettingsDestinations.resourceSelection) { - Text("Resource Selection") - } - } - } - - /* - private var openAISettings: some View { - Section("SETTINGS_OPENAI") { - NavigationLink(value: SettingsDestinations.openAIKey) { - Text("SETTINGS_OPENAI_KEY") - } - NavigationLink(value: SettingsDestinations.openAIModel) { - Text("SETTINGS_OPENAI_MODEL") - } - } - } - - private var promptsSettings: some View { - Section("SETTINGS_PROMPTS") { - NavigationLink(value: SettingsDestinations.promptSummary) { - Text("SETTINGS_PROMPTS_SUMMARY") - } - NavigationLink(value: SettingsDestinations.promptInterpretation) { - Text("SETTINGS_PROMPTS_INTERPRETATION") - } - NavigationLink(value: SettingsDestinations.promptMultipleResourceInterpretation) { - Text("SETTINGS_PROMPTS_INTERPRETATION_MULTIPLE_RESOURCES") - } - } - } - */ - - private func navigationDesination(for destination: SettingsDestinations) -> some View { - Group { - switch destination { - case .resourceSelection: - ResourceSelection() - default: EmptyView() - } - } - } -} diff --git a/Intake/SharedContext/FeatureFlags.swift b/Intake/SharedContext/FeatureFlags.swift index 8776435..1b51700 100644 --- a/Intake/SharedContext/FeatureFlags.swift +++ b/Intake/SharedContext/FeatureFlags.swift @@ -12,15 +12,6 @@ enum FeatureFlags { 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") static let testPatient = CommandLine.arguments.contains("--testPatient") diff --git a/Intake/SharedContext/StorageKeys.swift b/Intake/SharedContext/StorageKeys.swift index b4f2926..7cdce4e 100644 --- a/Intake/SharedContext/StorageKeys.swift +++ b/Intake/SharedContext/StorageKeys.swift @@ -17,4 +17,8 @@ enum StorageKeys { // MARK: - Home /// The currently selected home tab. static let homeTabSelection = "home.tabselection" + + + // MARK: - LLM Onboarding + static let llmOnboardingComplete = "llm.onboarding.complete" } diff --git a/Intake/SocialHistory/MenstrualHistory.swift b/Intake/SocialHistory/MenstrualHistory.swift deleted file mode 100644 index 0b559d2..0000000 --- a/Intake/SocialHistory/MenstrualHistory.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// MenstrualHistory.swift -// Intake -// -// Created by Zoya Garg on 2/28/24. -// -// This source file is part of the Intake based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT - -import Foundation -import HealthKit -import SwiftUI - -struct SocialHistoryQuestionView: View { - @State private var additionalDetails: String = "" - @State private var startDate = Date() - @State private var endDate = Date() - @State private var healthStore = HKHealthStore() - @State private var isFemale = false - @State private var showMaleSlide = false - @Environment(NavigationPathWrapper.self) private var navigationPath - @Environment(DataStore.self) private var data - // do this ONLY in nav before this - var body: some View { - NavigationView { - VStack { - if data.generalData.sex == "Female" { - Form { - Section(header: Text("Menstrual Information").foregroundColor(.gray)) { - @Bindable var data = data - DatePicker("Last period's start date", selection: $startDate, in: ...Date(), displayedComponents: .date) - .datePickerStyle(DefaultDatePickerStyle()) - - DatePicker("Last period's end date", selection: $endDate, in: ...Date(), displayedComponents: .date) - .datePickerStyle(DefaultDatePickerStyle()) - } - - Section(header: Text("Additional Symptoms").foregroundColor(.gray)) { - @Bindable var data = data - TextField("Ex: Heavy bleeding on second day, fatigue...", text: $additionalDetails) - } - } - .navigationTitle("Social History") - .task { - startDate = data.menstrualHistory.startDate - endDate = data.menstrualHistory.endDate - additionalDetails = data.menstrualHistory.additionalDetails - } - /*.task { - fetchHealthKitData() - }*/ - .onDisappear { - data.menstrualHistory = MenstrualHistoryItem(startDate: startDate, endDate: endDate, additionalDetails: additionalDetails) - } - if FeatureFlags.skipToScrollable { - SubmitButton(nextView: NavigationViews.pdfs) - .padding() - } else { - SubmitButton(nextView: NavigationViews.smoking) - .padding() - } - } - } - } - } -} diff --git a/Intake/SocialHistoryView/MenstrualHistory.swift b/Intake/SocialHistoryView/MenstrualHistory.swift new file mode 100644 index 0000000..e5dde28 --- /dev/null +++ b/Intake/SocialHistoryView/MenstrualHistory.swift @@ -0,0 +1,60 @@ +// This source file is part of the Intake based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT + +import Foundation +import HealthKit +import SwiftUI + + +struct SocialHistoryQuestionView: View { + @State private var additionalDetails: String = "" + @State private var startDate = Date() + @State private var endDate = Date() + @State private var healthStore = HKHealthStore() + @State private var isFemale = false + @State private var showMaleSlide = false + @Environment(NavigationPathWrapper.self) private var navigationPath + @Environment(DataStore.self) private var data + + + var body: some View { + VStack { + if data.generalData.sex == "Female" { + Form { + Section(header: Text("Menstrual Information").foregroundColor(.gray)) { + @Bindable var data = data + DatePicker("Last period's start date", selection: $startDate, in: ...Date(), displayedComponents: .date) + .datePickerStyle(DefaultDatePickerStyle()) + + DatePicker("Last period's end date", selection: $endDate, in: ...Date(), displayedComponents: .date) + .datePickerStyle(DefaultDatePickerStyle()) + } + + Section(header: Text("Additional Symptoms").foregroundColor(.gray)) { + @Bindable var data = data + TextField("Ex: Heavy bleeding on second day, fatigue...", text: $additionalDetails) + } + } + .navigationTitle("Menstrual History") + .task { + startDate = data.menstrualHistory.startDate + endDate = data.menstrualHistory.endDate + additionalDetails = data.menstrualHistory.additionalDetails + } + .onDisappear { + data.menstrualHistory = MenstrualHistoryItem(startDate: startDate, endDate: endDate, additionalDetails: additionalDetails) + } + if FeatureFlags.skipToScrollable { + SubmitButton(nextView: NavigationViews.pdfs) + .padding() + } else { + SubmitButton(nextView: NavigationViews.smoking) + .padding() + } + } + } + } +} diff --git a/Intake/SocialHistory/SmokingHistory.swift b/Intake/SocialHistoryView/SmokingHistory.swift similarity index 53% rename from Intake/SocialHistory/SmokingHistory.swift rename to Intake/SocialHistoryView/SmokingHistory.swift index c8f975f..00330f5 100644 --- a/Intake/SocialHistory/SmokingHistory.swift +++ b/Intake/SocialHistoryView/SmokingHistory.swift @@ -1,9 +1,3 @@ -// -// SmokingHistory.swift -// Intake -// -// Created by Zoya Garg on 2/28/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -12,39 +6,39 @@ import SwiftUI + struct SmokingHistoryView: View { @State private var hasSmokedOrSmoking = false @State private var currentlySmoking = false @State private var smokedInThePast = false @State private var additionalDetails: String = "" @Environment(DataStore.self) private var data - @Environment(NavigationPathWrapper.self) private var navigationPath // Ensure you have this environment object - @Environment(ReachedEndWrapper.self) private var end // And this one, if they're part of your app architecture + @Environment(NavigationPathWrapper.self) private var navigationPath + @Environment(ReachedEndWrapper.self) private var end + var body: some View { - NavigationView { - VStack { - Form { - initialSmokingQuestionSection - - if hasSmokedOrSmoking { - followUpQuestionsSection - additionalDetailsSection - } - } - .navigationTitle("Social History") - .onDisappear { - storeSmokingHistory() - } - if FeatureFlags.skipToScrollable { - SubmitButton(nextView: NavigationViews.pdfs) - .padding() - } else { - SubmitButton(nextView: NavigationViews.pdfs) - .padding() + VStack { + Form { + initialSmokingQuestionSection + + if hasSmokedOrSmoking { + followUpQuestionsSection + additionalDetailsSection } } + if FeatureFlags.skipToScrollable { + SubmitButton(nextView: NavigationViews.pdfs) + .padding() + } else { + SubmitButton(nextView: NavigationViews.pdfs) + .padding() + } } + .navigationTitle("Social History") + .onDisappear { + storeSmokingHistory() + } } private var initialSmokingQuestionSection: some View { @@ -66,8 +60,13 @@ struct SmokingHistoryView: View { } } + private func storeSmokingHistory() { - // swiftlint:disable:next line_length - data.smokingHistory = SmokingHistoryItem(hasSmokedOrSmoking: hasSmokedOrSmoking, currentlySmoking: currentlySmoking, smokedInThePast: smokedInThePast, additionalDetails: additionalDetails) + data.smokingHistory = SmokingHistoryItem( + hasSmokedOrSmoking: hasSmokedOrSmoking, + currentlySmoking: currentlySmoking, + smokedInThePast: smokedInThePast, + additionalDetails: additionalDetails + ) } } diff --git a/Intake/Surgery/SurgeryLLMAssistant.swift b/Intake/SurgeryView/SurgeryLLMAssistant.swift similarity index 76% rename from Intake/Surgery/SurgeryLLMAssistant.swift rename to Intake/SurgeryView/SurgeryLLMAssistant.swift index ffd9410..ef9105c 100644 --- a/Intake/Surgery/SurgeryLLMAssistant.swift +++ b/Intake/SurgeryView/SurgeryLLMAssistant.swift @@ -1,9 +1,3 @@ -// -// SurgeryLLMAssistant.swift -// Intake -// -// Created by Kate Callon on 3/4/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -18,32 +12,19 @@ import SpeziLLMLocal import SpeziLLMOpenAI import SwiftUI -// This SurgeryItemBox was needed so the LLM could input information to the variable surgeryItem and the equatable allows for the onChange function to recognize updated information has been added. +/// This SurgeryItemBox was needed so the LLM could input information to the variable surgeryItem and the equatable allows for the onChange function to recognize updated information has been added. @Observable class SurgeryItemBox: Equatable { var surgeryItem: SurgeryItem? - + init() {} - + static func == (lhs: SurgeryItemBox, rhs: SurgeryItemBox) -> Bool { lhs.surgeryItem == rhs.surgeryItem } } -// This function allows for updated patient surgery information to be inputted the the Surgery LLM Assistant system prompt. -func getCurrentPatientSurgery(surgeryList: [SurgeryItem]) -> String? { - var surgeryDetails = "The patient has had several surgeries." - - for surgery in surgeryList { - let surgeryName = surgery.surgeryName - let surgeryDate = surgery.date - surgeryDetails += "The patient had surgery \(surgeryName) on \(String(describing: surgeryDate)).\n" - } - - return surgeryDetails.isEmpty ? nil : surgeryDetails -} - -// This Surgery LLM Assistant allows for the patient to ask about their current surgeries and add new surgery information to their list. +/// This Surgery LLM Assistant allows for the patient to ask about their current surgeries and add new surgery information to their list. struct UpdateSurgeryFunction: LLMFunction { static let name: String = "update_surgeries" static let description: String = """ @@ -55,12 +36,12 @@ struct UpdateSurgeryFunction: LLMFunction { @Parameter(description: "The surgery date the patient wants to create.") var surgeryDate: String let surgeryItemBox: SurgeryItemBox - + init(surgeryItemBox: SurgeryItemBox) { self.surgeryItemBox = surgeryItemBox } - + func execute() async throws -> String? { let updatedSurgery = SurgeryItem(surgeryName: surgeryName, date: surgeryDate) surgeryItemBox.surgeryItem = updatedSurgery @@ -73,10 +54,9 @@ struct SurgeryLLMAssistant: View { @Environment(NavigationPathWrapper.self) private var navigationPath @Environment(LLMOpenAITokenSaver.self) private var tokenSaver - @Binding var presentingAccount: Bool @LLMSessionProvider var session: LLMOpenAISession - - @State var showOnboarding = true + + @AppStorage(StorageKeys.llmOnboardingComplete) var showOnboarding = true @State var greeting = true @State var surgeryItemBox: SurgeryItemBox @@ -93,7 +73,7 @@ struct SurgeryLLMAssistant: View { LLMOnboardingView(showOnboarding: $showOnboarding) } - .onAppear { + .task { checkToken() print("surgerybox", surgeryItemBox) @@ -105,8 +85,8 @@ struct SurgeryLLMAssistant: View { if let currentSurgery = getCurrentPatientSurgery(surgeryList: data.surgeries) { session.context.append( - systemMessage: currentSurgery - ) + systemMessage: currentSurgery + ) } } .onChange(of: surgeryItemBox.surgeryItem) { _, newValue in @@ -115,9 +95,8 @@ struct SurgeryLLMAssistant: View { } } } - - init(presentingAccount: Binding) { - self._presentingAccount = presentingAccount + + init() { let temporarySurgeryItemBox = SurgeryItemBox() self.surgeryItemBox = temporarySurgeryItemBox self._session = LLMSessionProvider( @@ -139,13 +118,27 @@ struct SurgeryLLMAssistant: View { ) } + /// This function allows for updated patient surgery information to be inputted the the Surgery LLM Assistant system prompt. + func getCurrentPatientSurgery(surgeryList: [SurgeryItem]) -> String? { + var surgeryDetails = "The patient has had several surgeries." + + for surgery in surgeryList { + let surgeryName = surgery.surgeryName + let surgeryDate = surgery.date + surgeryDetails += "The patient had surgery \(surgeryName) on \(String(describing: surgeryDate)).\n" + } + + return surgeryDetails.isEmpty ? nil : surgeryDetails + } + private func checkToken() { showOnboarding = !tokenSaver.tokenPresent } } + #Preview { - LLMInteraction(presentingAccount: .constant(false)) + LLMInteraction() .previewWith { LLMRunner { LLMOpenAIPlatform() diff --git a/Intake/Surgery/SurgeryView.swift b/Intake/SurgeryView/SurgeryView.swift similarity index 93% rename from Intake/Surgery/SurgeryView.swift rename to Intake/SurgeryView/SurgeryView.swift index 2cec78f..c950395 100644 --- a/Intake/Surgery/SurgeryView.swift +++ b/Intake/SurgeryView/SurgeryView.swift @@ -1,9 +1,3 @@ -// -// SurgeryView.swift -// Intake -// -// Created by Kate Callon on 2/6/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -18,27 +12,6 @@ import SpeziLLM import SpeziLLMOpenAI import SwiftUI - -func compare(surgery1: SurgeryItem, surgery2: SurgeryItem) -> Bool { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd" - - if let date1 = dateFormatter.date(from: surgery1.date) { - if let date2 = dateFormatter.date(from: surgery2.date) { - return date1 > date2 - } - return true - } - - return false -} - -func sortSurgeriesByDate(surgeries: inout [SurgeryItem]) { - if surgeries.count > 1 { - surgeries.sort { compare(surgery1: $0, surgery2: $1) } - } -} - struct AddSurgery: View { @Binding var surgeries: [SurgeryItem] @Environment(DataStore.self) var data @@ -114,6 +87,7 @@ struct SurgeryView: View { private var LLMFiltering = true @LLMSessionProvider var session: LLMOpenAISession + var body: some View { @Bindable var data = data if data.surgeriesLoaded { @@ -129,10 +103,11 @@ struct SurgeryView: View { } .navigationTitle("Surgical History") .navigationBarItems(trailing: AddSurgery(surgeries: $data.surgeries)) - .navigationBarItems(trailing: NavigationLink(destination: SurgeryLLMAssistant(presentingAccount: .constant(false))) { - Text("Chat") + .navigationBarItems(trailing: NavigationLink(destination: SurgeryLLMAssistant()) { + Image(systemName: "bubble") + .accessibilityLabel("Chat with LLM Assistant") }) - .onAppear { + .task { sortSurgeriesByDate(surgeries: &data.surgeries) } } else { @@ -169,6 +144,7 @@ struct SurgeryView: View { } } + init() { let systemPrompt = """ You are a helpful assistant that filters lists of procedures. You will be given\ @@ -199,14 +175,15 @@ struct SurgeryView: View { ) ) } - + + func delete(at offsets: IndexSet) { data.surgeries.remove(atOffsets: offsets) } - + func getProcedures() async { let procedures = fhirStore.procedures - + for pro in procedures where !data.surgeries.contains(where: { $0.surgeryName == pro.displayName }) { let vrs = pro.versionedResource switch vrs { @@ -323,6 +300,26 @@ struct SurgeryView: View { } } + private func compare(surgery1: SurgeryItem, surgery2: SurgeryItem) -> Bool { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + + if let date1 = dateFormatter.date(from: surgery1.date) { + if let date2 = dateFormatter.date(from: surgery2.date) { + return date1 > date2 + } + return true + } + + return false + } + + private func sortSurgeriesByDate(surgeries: inout [SurgeryItem]) { + if surgeries.count > 1 { + surgeries.sort { compare(surgery1: $0, surgery2: $1) } + } + } + func containsAnyWords(item: String, words: [String]) -> Bool { words.contains { item.contains($0) } } @@ -363,6 +360,7 @@ struct SurgeryView: View { } } + #Preview { SurgeryView() .previewWith { diff --git a/IntakeUITests/AllergyTests.swift b/IntakeUITests/AllergyTests.swift index c92e917..b82ee28 100644 --- a/IntakeUITests/AllergyTests.swift +++ b/IntakeUITests/AllergyTests.swift @@ -1,14 +1,10 @@ // -// AllergyTests.swift -// IntakeUITests -// -// Created by Zoya Garg on 3/13/24. - // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University // // SPDX-License-Identifier: MIT +// import Foundation import XCTest @@ -20,7 +16,7 @@ class AllergyTests: XCTestCase { continueAfterFailure = false let app = XCUIApplication() - app.launchArguments = ["--skipOnboarding", "--disableFirebase", "--testPatient", "--testAllergy", "--skipToScrollable"] + app.launchArguments = ["--skipOnboarding", "--testPatient", "--testAllergy", "--skipToScrollable"] app.launch() } diff --git a/IntakeUITests/ConditionsTests.swift b/IntakeUITests/ConditionsTests.swift index bd077c0..11b9af6 100644 --- a/IntakeUITests/ConditionsTests.swift +++ b/IntakeUITests/ConditionsTests.swift @@ -1,9 +1,4 @@ // -// ConditionsTests.swift -// IntakeUITests -// -// Created by Kate Callon on 3/14/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -14,7 +9,8 @@ import Foundation import XCTest -// Due to the nature of how conditions are added, this was difficult to test since there are an arbitrary number of rows due to LLM filtering. Therefore, this just checks the existence of the buttons. + +/// Due to the nature of how conditions are added, this was difficult to test since there are an arbitrary number of rows due to LLM filtering. Therefore, this just checks the existence of the buttons. class ConditionTests: XCTestCase { override func setUpWithError() throws { try super.setUpWithError() @@ -22,14 +18,14 @@ class ConditionTests: XCTestCase { continueAfterFailure = false let app = XCUIApplication() - app.launchArguments = ["--skipOnboarding", "--disableFirebase", "--testPatient", "--testCondition", "--skipToScrollable"] + app.launchArguments = ["--skipOnboarding", "--testPatient", "--testCondition", "--skipToScrollable"] app.launch() } func testConditions() throws { let app = XCUIApplication() - // Small workaround to wait until the madications loaded into main memory + // Small workaround to wait until the medications loaded into main memory sleep(10) XCTAssertEqual(app.state, .runningForeground) @@ -37,7 +33,7 @@ class ConditionTests: XCTestCase { sleep(5) - XCTAssertTrue(app.navigationBars["Medical History"].buttons["Chat"].waitForExistence(timeout: 2)) + XCTAssertTrue(app.navigationBars["Medical History"].buttons["Chat with LLM Assistant"].waitForExistence(timeout: 2)) XCTAssertTrue(app.navigationBars["Medical History"].buttons["add_condition"].waitForExistence(timeout: 2)) app.navigationBars["Medical History"].buttons["add_condition"].tap() } diff --git a/IntakeUITests/LaunchTests.swift b/IntakeUITests/LaunchTests.swift index b828d47..65a24fc 100644 --- a/IntakeUITests/LaunchTests.swift +++ b/IntakeUITests/LaunchTests.swift @@ -16,13 +16,11 @@ class LaunchTests: XCTestCase { continueAfterFailure = false let app = XCUIApplication() - app.launchArguments = ["--skipOnboarding"] app.launch() } -// func testApplicationLaunch() throws { -// let app = XCUIApplication() -// XCTAssertEqual(app.state, .runningForeground) -// app.buttons["Start"].tap() -// } + func testApplicationLaunch() throws { + let app = XCUIApplication() + XCTAssertEqual(app.state, .runningForeground) + } } diff --git a/IntakeUITests/MedicationTests.swift b/IntakeUITests/MedicationTests.swift index d7fc7b8..816fb02 100644 --- a/IntakeUITests/MedicationTests.swift +++ b/IntakeUITests/MedicationTests.swift @@ -1,10 +1,4 @@ // -// MedicationTests.swift -// IntakeUITests -// -// Created by Kate Callon on 3/12/24. -// -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -15,7 +9,7 @@ import Foundation import XCTest -// This tests checking if the current patient medications appear, adding a new medication, filling out its information, and seeing if it persists. +/// These tests checking if the current patient medications appear, adding a new medication, filling out its information, and seeing if it persists. class MedicationTests: XCTestCase { override func setUpWithError() throws { try super.setUpWithError() @@ -23,14 +17,14 @@ class MedicationTests: XCTestCase { continueAfterFailure = false let app = XCUIApplication() - app.launchArguments = ["--skipOnboarding", "--disableFirebase", "--testPatient", "--testMedication", "--skipToScrollable"] + app.launchArguments = ["--skipOnboarding", "--testPatient", "--testMedication", "--skipToScrollable"] app.launch() } func testMedications() throws { let app = XCUIApplication() - // Small workaround to wait until the madications loaded into main memory + // Small workaround to wait until the medications loaded into main memory sleep(10) XCTAssertEqual(app.state, .runningForeground) @@ -39,7 +33,7 @@ class MedicationTests: XCTestCase { XCTAssertTrue(app.staticTexts["Hydrochlorothiazide 25 MG Oral Tablet"].waitForExistence(timeout: 10)) XCTAssertTrue(app.staticTexts["amLODIPine 2.5 MG Oral Tablet"].waitForExistence(timeout: 10)) XCTAssertTrue(app.navigationBars["Medication Settings"].buttons["Add New Medication"].waitForExistence(timeout: 2)) - XCTAssertTrue(app.navigationBars["Medication Settings"].buttons["Chat"].waitForExistence(timeout: 2)) + XCTAssertTrue(app.navigationBars["Medication Settings"].buttons["Chat with LLM Assistant"].waitForExistence(timeout: 2)) app.navigationBars["Medication Settings"].buttons["Add New Medication"].tap() app.buttons["Verapamil Hydrochloride 40 MG"].tap() app.buttons["Save Dosage"].tap() diff --git a/IntakeUITests/NinasTests.swift b/IntakeUITests/PatientInformationTests.swift similarity index 80% rename from IntakeUITests/NinasTests.swift rename to IntakeUITests/PatientInformationTests.swift index 87c00a7..5a216f4 100644 --- a/IntakeUITests/NinasTests.swift +++ b/IntakeUITests/PatientInformationTests.swift @@ -6,13 +6,13 @@ // SPDX-License-Identifier: MIT // -// I test the following: -// 1. If a patient is connected with healthkit, does the information in the PatientInfo view show up? -// 2. If a patient is not connected with healthkit and fills in their information manually, does the information in the PatientInfo view show up? -// 3. Does the data in each case persist to scrollableView? - import XCTest + +/// Test the following: +/// 1. If a patient is connected with healthkit, does the information in the PatientInfo view show up? +/// 2. If a patient is not connected with healthkit and fills in their information manually, does the information in the PatientInfo view show up? +/// 3. Does the data in each case persist to scrollableView? final class NinasTests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false @@ -28,7 +28,7 @@ final class NinasTests: XCTestCase { func testIfHealthKitDataInScrollable() throws { let app = XCUIApplication() - app.launchArguments = ["--disableFirebase", "--skipOnboarding", "--testPatient", "--skipToScrollable"] + app.launchArguments = ["--skipOnboarding", "--testPatient", "--skipToScrollable"] app.launch() sleep(1) let startButton = app.buttons["Create New Form"] @@ -46,7 +46,7 @@ final class NinasTests: XCTestCase { func testIfCustomDataInScrollable() throws { let app = XCUIApplication() - app.launchArguments = ["--disableFirebase", "--skipOnboarding", "--skipToScrollable"] + app.launchArguments = ["--skipOnboarding", "--skipToScrollable"] app.launch() sleep(1) let startButton = app.buttons["Create New Form"] diff --git a/IntakeUITests/SurgeryTests.swift b/IntakeUITests/SurgeryTests.swift index 901ae97..61fd095 100644 --- a/IntakeUITests/SurgeryTests.swift +++ b/IntakeUITests/SurgeryTests.swift @@ -1,9 +1,4 @@ // -// SurgeryTests.swift -// IntakeUITests -// -// Created by Kate Callon on 3/14/24. -// // This source file is part of the Intake based on the Stanford Spezi Template Application project // // SPDX-FileCopyrightText: 2023 Stanford University @@ -14,7 +9,7 @@ import Foundation import XCTest -// This tests adding a new surgery, filling out its information, and seeing if it persists. +/// This tests adding a new surgery, filling out its information, and seeing if it persists. class SurgeryTests: XCTestCase { override func setUpWithError() throws { try super.setUpWithError() @@ -22,7 +17,7 @@ class SurgeryTests: XCTestCase { continueAfterFailure = false let app = XCUIApplication() - app.launchArguments = ["--skipOnboarding", "--disableFirebase", "--testPatient", "--testSurgery", "--skipToScrollable"] + app.launchArguments = ["--skipOnboarding", "--testPatient", "--testSurgery", "--skipToScrollable"] app.launch() } @@ -37,7 +32,7 @@ class SurgeryTests: XCTestCase { sleep(5) - XCTAssertTrue(app.navigationBars["Surgical History"].buttons["Chat"].waitForExistence(timeout: 2)) + XCTAssertTrue(app.navigationBars["Surgical History"].buttons["Chat with LLM Assistant"].waitForExistence(timeout: 2)) XCTAssertTrue(app.navigationBars["Surgical History"].buttons["ADD_SURGERY"].waitForExistence(timeout: 2)) app.navigationBars["Surgical History"].buttons["ADD_SURGERY"].tap() app.textFields["SURGERY_NAME"].tap() diff --git a/README.md b/README.md index 041cdad..f245550 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The medical history, allergies, and surgeries all use [SpeziLLM](https://github. The medical history, surgery, and allergy views all use [SpeziLLM](https://github.com/StanfordSpezi/SpeziLLM) to allow the user to click the chat button in the to and ask questions about that corresponding section, with an added ability to add entries to your form through the LLM chat. -|![Screenshot displaying the chief complaint view.](screenshots/chiefComplaint.png#gh-light-mode-only) ![Screenshot displaying the chief complaint view.](screenshots/chiefComplaint.png#gh-dark-mode-only)|![Screenshot displaying the medication view.](screenshots/medication.png#gh-light-mode-only)![Screenshot displaying the medication view.](screenshots/medication.png#gh-dark-mode-only)|![Screenshot displaying the summary view.](screenshots/summary.png#gh-light-mode-only)![Screenshot displaying the summary view.](screenshots/summary.png#gh-dark-mode-only) +|![Screenshot displaying the chief complaint view.](Intake/Resources/Screenshots/chiefComplaint.png#gh-light-mode-only) ![Screenshot displaying the chief complaint view.](Intake/Resources/Screenshots/chiefComplaint.png#gh-dark-mode-only)|![Screenshot displaying the medication view.](Intake/Resources/Screenshots/medication.png#gh-light-mode-only)![Screenshot displaying the medication view.](Intake/Resources/Screenshots/medication.png#gh-dark-mode-only)|![Screenshot displaying the summary view.](Intake/Resources/Screenshots/summary.png#gh-light-mode-only)![Screenshot displaying the summary view.](Intake/Resources/Screenshots/summary.png#gh-dark-mode-only) |:--:|:--:|:--:| The image on the left is the chief complaint feature which uses [SpeziLLM](https://github.com/StanfordSpezi/SpeziLLM) to chat with the user about the reason for their visit. It asks specifically tailored questions based on the users response, and then forumalizes a chief complaint for the user once it has enough information to do so. diff --git a/firebase.json b/firebase.json deleted file mode 100644 index 0b85064..0000000 --- a/firebase.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "firestore": { - "rules": "firestore.rules" - }, - "storage": { - "rules": "firebasestorage.rules" - }, - "emulators": { - "auth": { - "port": 9099 - }, - "firestore": { - "port": 8080 - }, - "ui": { - "enabled": true, - "port": 4000 - }, - "storage": { - "port": 9199 - }, - "singleProjectMode": true - } -} \ No newline at end of file diff --git a/firebase.json.license b/firebase.json.license deleted file mode 100644 index 8a53724..0000000 --- a/firebase.json.license +++ /dev/null @@ -1,5 +0,0 @@ -This source file is part of the Intake 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/firebasestorage.rules b/firebasestorage.rules deleted file mode 100644 index 18a3bd2..0000000 --- a/firebasestorage.rules +++ /dev/null @@ -1,12 +0,0 @@ -rules_version = '2'; -service firebase.storage { - match /b/{bucket}/o { - match /users/{userId}/{allPaths=**} { - allow read, write: if request.auth != null && request.auth.uid == userId; - } - - match /{allPaths=**} { - allow read, write: if false; - } - } -} diff --git a/firebasestorage.rules.license b/firebasestorage.rules.license deleted file mode 100644 index 8a53724..0000000 --- a/firebasestorage.rules.license +++ /dev/null @@ -1,5 +0,0 @@ -This source file is part of the Intake based on the Stanford Spezi Template Application project - -SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) - -SPDX-License-Identifier: MIT diff --git a/firestore.rules b/firestore.rules deleted file mode 100644 index 5a233b0..0000000 --- a/firestore.rules +++ /dev/null @@ -1,9 +0,0 @@ -rules_version = '2'; -service cloud.firestore { - match /databases/{database}/documents { - // Allow only authenticated content owners access - match /users/{userId}/{documents=**} { - allow read, write: if request.auth != null && request.auth.uid == userId; - } - } -} diff --git a/firestore.rules.license b/firestore.rules.license deleted file mode 100644 index 8a53724..0000000 --- a/firestore.rules.license +++ /dev/null @@ -1,5 +0,0 @@ -This source file is part of the Intake based on the Stanford Spezi Template Application project - -SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) - -SPDX-License-Identifier: MIT