diff --git a/.firebaserc b/.firebaserc index e2356c1..fd223b6 100644 --- a/.firebaserc +++ b/.firebaserc @@ -1,5 +1,5 @@ { "projects": { - "default": "cs3422023utah" + "default": "cs342-2023-utah" } } diff --git a/Utah.xcodeproj/project.pbxproj b/Utah.xcodeproj/project.pbxproj index 54ff4ef..b9e239f 100644 --- a/Utah.xcodeproj/project.pbxproj +++ b/Utah.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 23B0449B299DB70A008CD957 /* GetUpAndGoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23B0449A299DB70A008CD957 /* GetUpAndGoTests.swift */; }; + 27A7B3E829A7B2C8000B32B4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 27A7B3E729A7B2C8000B32B4 /* GoogleService-Info.plist */; }; 2F49B7762980407C00BCB272 /* CardinalKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7752980407B00BCB272 /* CardinalKit */; }; 2F49B7782980407C00BCB272 /* FHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7772980407C00BCB272 /* FHIR */; }; 2F49B77A2980407C00BCB272 /* HealthKitDataSource in Frameworks */ = {isa = PBXBuildFile; productRef = 2F49B7792980407C00BCB272 /* HealthKitDataSource */; }; @@ -30,7 +31,6 @@ 2F905711299E3E0D003D3802 /* FirebaseAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 2F905710299E3E0D003D3802 /* FirebaseAccount */; }; 2F905713299E3E0D003D3802 /* FirestoreDataStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 2F905712299E3E0D003D3802 /* FirestoreDataStorage */; }; 2F905715299E3E0D003D3802 /* FirestoreStoragePrefixUserIdAdapter in Frameworks */ = {isa = PBXBuildFile; productRef = 2F905714299E3E0D003D3802 /* FirestoreStoragePrefixUserIdAdapter */; }; - 2F905719299E4205003D3802 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2F905717299E4205003D3802 /* GoogleService-Info.plist */; }; 2FC9759F2978E39600BA99FE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2FC9759E2978E39600BA99FE /* Localizable.strings */; }; 2FC975A82978F11A00BA99FE /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FC975A72978F11A00BA99FE /* Home.swift */; }; 653A2551283387FE005D4D48 /* Utah.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A2550283387FE005D4D48 /* Utah.swift */; }; @@ -65,12 +65,12 @@ /* Begin PBXFileReference section */ 23B0449A299DB70A008CD957 /* GetUpAndGoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUpAndGoTests.swift; sourceTree = ""; }; + 27A7B3E729A7B2C8000B32B4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 2F49B77329803E8F00BCB272 /* UtahModules */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = UtahModules; sourceTree = ""; }; 2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = ""; }; 2F4E23822989D51F0013F3D9 /* UtahAppTestingSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtahAppTestingSetup.swift; sourceTree = ""; }; 2F4E23882989DB400013F3D9 /* HealthKitUploadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitUploadTests.swift; sourceTree = ""; }; 2F5E32BC297E05EA003432F8 /* UtahAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtahAppDelegate.swift; sourceTree = ""; }; - 2F905717299E4205003D3802 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 2FAEC07F297F583900C11C42 /* Utah.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Utah.entitlements; sourceTree = ""; }; 2FC94CD4298B0A1D009C8209 /* Utah.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Utah.xctestplan; sourceTree = ""; }; 2FC9759E2978E39600BA99FE /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; @@ -140,7 +140,7 @@ children = ( 2FAEC07F297F583900C11C42 /* Utah.entitlements */, 653A258928339462005D4D48 /* Info.plist */, - 2F905717299E4205003D3802 /* GoogleService-Info.plist */, + 27A7B3E729A7B2C8000B32B4 /* GoogleService-Info.plist */, 653A255428338800005D4D48 /* Assets.xcassets */, 2FC9759E2978E39600BA99FE /* Localizable.strings */, ); @@ -348,9 +348,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 2F905719299E4205003D3802 /* GoogleService-Info.plist in Resources */, 653A255528338800005D4D48 /* Assets.xcassets in Resources */, 2FC9759F2978E39600BA99FE /* Localizable.strings in Resources */, + 27A7B3E829A7B2C8000B32B4 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Utah.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Utah.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a93ab4e..024bffc 100644 --- a/Utah.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Utah.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk", "state" : { - "revision" : "0df86ea17d5d281415be74f2290df8431644f156", - "version" : "10.4.0" + "revision" : "dce2e1abc6c0d5e830ff1cffe3f8633fda64001e", + "version" : "10.3.0" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { - "revision" : "9a09ece724128e8d1e14c5133b87c0e236844ac0", - "version" : "10.4.0" + "revision" : "71eb6700dd53a851473c48d392f00a3ab26699a6", + "version" : "10.1.0" } }, { diff --git a/Utah/Supporting Files/GoogleService-Info.plist b/Utah/Supporting Files/GoogleService-Info.plist index 82498b1..59db3c2 100644 --- a/Utah/Supporting Files/GoogleService-Info.plist +++ b/Utah/Supporting Files/GoogleService-Info.plist @@ -3,32 +3,32 @@ CLIENT_ID - CLIENT_ID + 515655571673-lb80ks5ts4uuok8h6bedjm1lcs2phr2v.apps.googleusercontent.com REVERSED_CLIENT_ID - REVERSED_CLIENT_ID + com.googleusercontent.apps.515655571673-lb80ks5ts4uuok8h6bedjm1lcs2phr2v API_KEY - API_KEY + AIzaSyDTCzr0dv4FrJ-sGHEmgXSVygWHKxvCzzA GCM_SENDER_ID - GCM_SENDER_ID + 515655571673 PLIST_VERSION 1 BUNDLE_ID edu.stanford.cs342.2023.utah PROJECT_ID - cs3422023utah + cs342-2023-utah STORAGE_BUCKET - STORAGE_BUCKET + cs342-2023-utah.appspot.com IS_ADS_ENABLED - + IS_ANALYTICS_ENABLED - + IS_APPINVITE_ENABLED - + IS_GCM_ENABLED - + IS_SIGNIN_ENABLED - + GOOGLE_APP_ID - 1:123456789012:ios:1234567890123456789012 + 1:515655571673:ios:e7be75d831177550f206c8 - + \ No newline at end of file diff --git a/Utah/UtahAppDelegate.swift b/Utah/UtahAppDelegate.swift index c664937..e629929 100644 --- a/Utah/UtahAppDelegate.swift +++ b/Utah/UtahAppDelegate.swift @@ -11,6 +11,7 @@ import FHIR import FHIRToFirestoreAdapter import FirebaseAccount import FirebaseAuth +import FirebaseCore import FirestoreDataStorage import FirestoreStoragePrefixUserIdAdapter import HealthKit @@ -45,8 +46,7 @@ class UtahAppDelegate: CardinalKitAppDelegate { adapter: { FHIRToFirestoreAdapter() FirestoreStoragePrefixUserIdAdapter() - }, - settings: .emulator + } ) } diff --git a/UtahModules/Package.swift b/UtahModules/Package.swift index 416a20f..010bc48 100644 --- a/UtahModules/Package.swift +++ b/UtahModules/Package.swift @@ -26,7 +26,8 @@ let package = Package( .library(name: "UtahTrends", targets: ["UtahTrends"]) ], dependencies: [ - .package(url: "https://github.com/StanfordBDHG/CardinalKit.git", .upToNextMinor(from: "0.3.1")) + .package(url: "https://github.com/StanfordBDHG/CardinalKit.git", .upToNextMinor(from: "0.3.1")), + .package(url: "https://github.com/firebase/firebase-ios-sdk", from: "10.3.0") ], targets: [ .target( @@ -67,7 +68,9 @@ let package = Package( .target(name: "UtahSharedContext"), .product(name: "FHIR", package: "CardinalKit"), .product(name: "Questionnaires", package: "CardinalKit"), - .product(name: "Scheduler", package: "CardinalKit") + .product(name: "Scheduler", package: "CardinalKit"), + .product(name: "FirebaseFirestore", package: "firebase-ios-sdk"), + .product(name: "FirebaseStorage", package: "firebase-ios-sdk") ] ), .target( diff --git a/UtahModules/Sources/UtahSchedule/Edmonton/EdmontonViewController.swift b/UtahModules/Sources/UtahSchedule/Edmonton/EdmontonViewController.swift index 58a45f9..a7e44fd 100644 --- a/UtahModules/Sources/UtahSchedule/Edmonton/EdmontonViewController.swift +++ b/UtahModules/Sources/UtahSchedule/Edmonton/EdmontonViewController.swift @@ -8,7 +8,6 @@ // swiftlint:disable function_body_length // swiftlint:disable closure_body_length -// swiftlint:disable legacy_objc_type import Foundation import ResearchKit @@ -57,9 +56,9 @@ struct EdmontonViewController: UIViewControllerRepresentable { // Question 2 let q2Choices = [ - ORKTextChoice(text: "0", value: 0 as NSNumber), - ORKTextChoice(text: "1-2", value: 1 as NSNumber), - ORKTextChoice(text: ">2", value: 2 as NSNumber) + ORKTextChoice(text: "0", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "1-2", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: ">2", value: "2" as NSSecureCoding & NSCopying & NSObjectProtocol) ] let q2ChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: q2Choices) let q2Step = ORKQuestionStep( @@ -74,11 +73,11 @@ struct EdmontonViewController: UIViewControllerRepresentable { // Question 3 let q3Choices = [ - ORKTextChoice(text: "Excellent", value: 0 as NSNumber), - ORKTextChoice(text: "Very Good", value: 0 as NSNumber), - ORKTextChoice(text: "Good", value: 0 as NSNumber), - ORKTextChoice(text: "Fair", value: 1 as NSNumber), - ORKTextChoice(text: "Poor", value: 2 as NSNumber) + ORKTextChoice(text: "Excellent", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Very Good", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Good", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Fair", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Poor", value: "2" as NSSecureCoding & NSCopying & NSObjectProtocol) ] let q3ChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: q3Choices) let q3Step = ORKQuestionStep( @@ -93,9 +92,9 @@ struct EdmontonViewController: UIViewControllerRepresentable { // Question 4 let q4Choices = [ - ORKTextChoice(text: "0-1", value: 0 as NSNumber), - ORKTextChoice(text: "2-4", value: 1 as NSNumber), - ORKTextChoice(text: "5-8", value: 2 as NSNumber) + ORKTextChoice(text: "0-1", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "2-4", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "5-8", value: "2" as NSSecureCoding & NSCopying & NSObjectProtocol) ] let q4ChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: q4Choices) let q4Step = ORKQuestionStep( @@ -113,9 +112,9 @@ struct EdmontonViewController: UIViewControllerRepresentable { // Question 5 let q5Choices = [ - ORKTextChoice(text: "Always", value: 0 as NSNumber), - ORKTextChoice(text: "Sometimes", value: 1 as NSNumber), - ORKTextChoice(text: "Never", value: 2 as NSNumber) + ORKTextChoice(text: "Always", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Sometimes", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Never", value: "2" as NSSecureCoding & NSCopying & NSObjectProtocol) ] let q5ChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: q5Choices) let q5Step = ORKQuestionStep( @@ -129,7 +128,10 @@ struct EdmontonViewController: UIViewControllerRepresentable { steps += [q5Step] // Question 6-10 - let q6to10Choices = [ ORKTextChoice(text: "Yes", value: 1 as NSNumber), ORKTextChoice(text: "No", value: 0 as NSNumber) ] + let q6to10Choices = [ + ORKTextChoice(text: "Yes", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "No", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol) + ] let q6to10ChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: q6to10Choices) let questions = [ "Do you use five or more different prescription medications on a regular basis?", diff --git a/UtahModules/Sources/UtahSchedule/Edmonton/EdmontonViewCoordinator.swift b/UtahModules/Sources/UtahSchedule/Edmonton/EdmontonViewCoordinator.swift index fa612f4..26f6437 100644 --- a/UtahModules/Sources/UtahSchedule/Edmonton/EdmontonViewCoordinator.swift +++ b/UtahModules/Sources/UtahSchedule/Edmonton/EdmontonViewCoordinator.swift @@ -7,17 +7,91 @@ // // swiftlint:disable lower_acl_than_parent - +import Firebase +import FirebaseStorage import Foundation +import ModelsR4 import ResearchKit + class EdmontonViewCoordinator: NSObject, ORKTaskViewControllerDelegate { - // called when the survey is completed, need to figure out how to upload data to firestore public func taskViewController( _ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error? ) { + switch reason { + case .completed: + // Convert the responses into a FHIR object using ResearchKitOnFHIR + let fhirResponse = taskViewController.result.fhirResponse + + // Add a patient identifier to the response so we know who did this survey + fhirResponse.subject = Reference(reference: FHIRPrimitive(FHIRString("Patient/PATIENT_ID"))) + + do { + // Parse the FHIR object into JSON + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let data = try encoder.encode(fhirResponse) + + // Print out the JSON for debugging + let json = String(decoding: data, as: UTF8.self) + print(json) + + // Convert the FHIR object to a dictionary and upload to Firebase + let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + let identifier = fhirResponse.id?.value?.string ?? UUID().uuidString + + guard let jsonDict else { + return + } + + let database = Firestore.firestore() + database.collection("edmontonsurveys").document(identifier).setData(jsonDict) { err in + if let err { + print("Error writing document: \(err)") + } else { + print("Document successfully written.") + } + } + + // Upload any files that are attached to the FHIR object to Firebase + if let responseItems = fhirResponse.item { + for item in responseItems { + if case let .attachment(value) = item.answer?.first?.value { + guard let fileURL = value.url?.value?.url else { + continue + } + + // Get a reference to the Cloud Storage service + let storageRef = Storage.storage().reference() + + // Create a reference for the new file + // and put it in the "edmonton" file on Cloud Storage + let fileName = fileURL.lastPathComponent + let fileRef = storageRef.child("edmonton/" + fileName) + + // Upload the file to Cloud Storage using the reference + fileRef.putFile(from: fileURL, metadata: nil) { _, error in + if let error { + print("An error occurred: \(error)") + } else { + print("\(fileName) was uploaded successfully!") + } + } + } + } + } + } catch { + // Something didn't work! + print(error.localizedDescription) + } + default: + break + } + + // We're done, dismiss the survey + taskViewController.dismiss(animated: true, completion: nil) taskViewController.dismiss(animated: true, completion: nil) } } diff --git a/UtahModules/Sources/UtahSchedule/WIQ/WIQViewController.swift b/UtahModules/Sources/UtahSchedule/WIQ/WIQViewController.swift index 734a87c..ab01630 100644 --- a/UtahModules/Sources/UtahSchedule/WIQ/WIQViewController.swift +++ b/UtahModules/Sources/UtahSchedule/WIQ/WIQViewController.swift @@ -6,8 +6,6 @@ // SPDX-License-Identifier: MIT // -// swiftlint:disable legacy_objc_type - import ResearchKit import SwiftUI import UIKit @@ -30,13 +28,13 @@ struct WIQViewController: UIViewControllerRepresentable { instruction.text = "Patient Mobility Assessment" steps += [instruction] - + let wiqChoices = [ - ORKTextChoice(text: "No Difficulty", value: 0 as NSNumber), - ORKTextChoice(text: "Slight Difficulty", value: 1 as NSNumber), - ORKTextChoice(text: "Some Difficulty", value: 2 as NSNumber), - ORKTextChoice(text: "Much Difficulty", value: 3 as NSNumber), - ORKTextChoice(text: "Unable to Do", value: 4 as NSNumber) + ORKTextChoice(text: "No Difficulty", value: "0" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Slight Difficulty", value: "1" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Some Difficulty", value: "2" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Much Difficulty", value: "3" as NSSecureCoding & NSCopying & NSObjectProtocol), + ORKTextChoice(text: "Unable to Do", value: "4" as NSSecureCoding & NSCopying & NSObjectProtocol) ] let wiqAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: wiqChoices) diff --git a/UtahModules/Sources/UtahSchedule/WIQ/WIQViewCoordinator.swift b/UtahModules/Sources/UtahSchedule/WIQ/WIQViewCoordinator.swift index 1ea5e03..d1f3eed 100644 --- a/UtahModules/Sources/UtahSchedule/WIQ/WIQViewCoordinator.swift +++ b/UtahModules/Sources/UtahSchedule/WIQ/WIQViewCoordinator.swift @@ -7,8 +7,10 @@ // // swiftlint:disable lower_acl_than_parent - +import Firebase +import FirebaseFirestore import Foundation +import ModelsR4 import ResearchKit class WIQViewCoordinator: NSObject, ORKTaskViewControllerDelegate { @@ -18,6 +20,49 @@ class WIQViewCoordinator: NSObject, ORKTaskViewControllerDelegate { didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error? ) { + switch reason { + case .completed: + // Convert the responses into a FHIR object using ResearchKitOnFHIR + let fhirResponses = taskViewController.result.fhirResponse + + // Add a patient identifier to the response so we know who did this survey + fhirResponses.subject = Reference(reference: FHIRPrimitive(FHIRString("Patient/PATIENT_ID"))) + + do { + // Parse the FHIR object into JSON + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let data = try encoder.encode(fhirResponses) + + // Print out the JSON for debugging + let json = String(decoding: data, as: UTF8.self) + print(json) + + // Convert to dictionary and upload to Firebase + let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + let identifier = fhirResponses.id?.value?.string ?? UUID().uuidString + + guard let jsonDict else { + return + } + + let database = Firestore.firestore() + database.collection("wiqsurveys").document(identifier).setData(jsonDict) { err in + if let err { + print("Error writing document: \(err)") + } else { + print("Document successfully written.") + } + } + } catch { + // Something didn't work! + print(error.localizedDescription) + } + default: + break + } + + // We're done, dismiss the survey taskViewController.dismiss(animated: true, completion: nil) } }