From 77422af1ef625b0a482481ac0392ef7db90b33cc Mon Sep 17 00:00:00 2001
From: Catherine Zhang <137839789+czhang771@users.noreply.github.com>
Date: Wed, 6 Mar 2024 19:54:41 -0500
Subject: [PATCH] Onboarding Medication Page (#48)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# Onboarding Medication Page
## :recycle: Current situation & Problem
Allows users to upload a picture of their medication which is stored in
Firebase.
## :gear: Release Notes
Created Medication Page in the onboarding flow where user can take a
photo or upload a photo of their medication chart.
## :books: Documentation
Used SourceImage Package
## :white_check_mark: Testing
## :pencil: Code of Conduct & Contributing Guidelines
By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md).
---
PICS.xcodeproj/project.pbxproj | 25 +++++++++
.../xcshareddata/swiftpm/Package.resolved | 9 ++++
PICS/Onboarding/ContentView.swift | 53 +++++++++++++++++++
PICS/Onboarding/Medication.swift | 52 ++++++++++++++++++
PICS/Onboarding/OnboardingFlow.swift | 4 +-
PICS/PICSStandard.swift | 31 +++++++++++
PICS/Resources/Localizable.xcstrings | 28 ++++++++++
7 files changed, 201 insertions(+), 1 deletion(-)
create mode 100644 PICS/Onboarding/ContentView.swift
create mode 100644 PICS/Onboarding/Medication.swift
diff --git a/PICS.xcodeproj/project.pbxproj b/PICS.xcodeproj/project.pbxproj
index a56ea0a..c533b1c 100644
--- a/PICS.xcodeproj/project.pbxproj
+++ b/PICS.xcodeproj/project.pbxproj
@@ -17,6 +17,9 @@
22365B782B902C9100C4528E /* Onboarding-Questionnaire.json in Resources */ = {isa = PBXBuildFile; fileRef = 22365B772B902C9100C4528E /* Onboarding-Questionnaire.json */; };
226C16F02B69DF3500FBA97D /* HeightKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226C16EF2B69DF3500FBA97D /* HeightKey.swift */; };
226C16F22B6C820C00FBA97D /* WeightKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226C16F12B6C820B00FBA97D /* WeightKey.swift */; };
+ 22C75CE12B978E33008986AF /* Medication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C75CE02B978E33008986AF /* Medication.swift */; };
+ 22C75CF02B979D02008986AF /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C75CEF2B979D02008986AF /* ContentView.swift */; };
+ 22C75CF72B979EA7008986AF /* ImageSource in Frameworks */ = {isa = PBXBuildFile; productRef = 22C75CF62B979EA7008986AF /* ImageSource */; };
27FA29902A388E9B009CAC45 /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FA298F2A388E9B009CAC45 /* ModalView.swift */; };
2F1AC9DF2B4E840E00C24973 /* PICS.docc in Sources */ = {isa = PBXBuildFile; fileRef = 2F1AC9DE2B4E840E00C24973 /* PICS.docc */; };
2F3D4ABC2A4E7C290068FB2F /* SpeziScheduler in Frameworks */ = {isa = PBXBuildFile; productRef = 2F3D4ABB2A4E7C290068FB2F /* SpeziScheduler */; };
@@ -137,6 +140,8 @@
22365B772B902C9100C4528E /* Onboarding-Questionnaire.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "Onboarding-Questionnaire.json"; sourceTree = ""; };
226C16EF2B69DF3500FBA97D /* HeightKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeightKey.swift; sourceTree = ""; };
226C16F12B6C820B00FBA97D /* WeightKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightKey.swift; sourceTree = ""; };
+ 22C75CE02B978E33008986AF /* Medication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Medication.swift; sourceTree = ""; };
+ 22C75CEF2B979D02008986AF /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
27FA298F2A388E9B009CAC45 /* ModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalView.swift; sourceTree = ""; };
2F1AC9DE2B4E840E00C24973 /* PICS.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = PICS.docc; sourceTree = ""; };
2F4E237D2989A2FE0013F3D9 /* LaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTests.swift; sourceTree = ""; };
@@ -219,6 +224,7 @@
2FE5DC6729EDD894004B9AB4 /* SpeziContact in Frameworks */,
2FE5DC8429EDD934004B9AB4 /* SpeziQuestionnaire in Frameworks */,
2FB099B32A875DF100B20952 /* FirebaseFirestoreSwift in Frameworks */,
+ 22C75CF72B979EA7008986AF /* ImageSource in Frameworks */,
5661551D2AB8384200209B80 /* SwiftPackageList in Frameworks */,
2FB099B12A875DF100B20952 /* FirebaseFirestore in Frameworks */,
A9D83F962B083794000D0C78 /* SpeziFirebaseAccountStorage in Frameworks */,
@@ -304,6 +310,8 @@
2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */,
2FE5DC2F29EDD7CA004B9AB4 /* Consent.swift */,
2FE5DC3029EDD7CA004B9AB4 /* HealthKitPermissions.swift */,
+ 22C75CE02B978E33008986AF /* Medication.swift */,
+ 22C75CEF2B979D02008986AF /* ContentView.swift */,
22306A292B7C4E8E000A8EC1 /* OnboardingQuestionnaire.swift */,
22306A402B8F20C4000A8EC1 /* AccountQuestionnaire.swift */,
);
@@ -535,6 +543,7 @@
9739A0C52AD7B5730084BEA5 /* FirebaseStorage */,
97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */,
A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */,
+ 22C75CF62B979EA7008986AF /* ImageSource */,
);
productName = PICS;
productReference = 653A254D283387FE005D4D48 /* PICS.app */;
@@ -629,6 +638,7 @@
2FE750CA2A87240100723EAE /* XCRemoteSwiftPackageReference "SpeziMockWebService" */,
2FB099B42A875E2B00B20952 /* XCRemoteSwiftPackageReference "HealthKitOnFHIR" */,
5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */,
+ 22C75CF52B979EA7008986AF /* XCRemoteSwiftPackageReference "ImageSource" */,
);
productRefGroup = 653A254E283387FE005D4D48 /* Products */;
projectDirPath = "";
@@ -721,6 +731,7 @@
22306A2A2B7C4E8E000A8EC1 /* OnboardingQuestionnaire.swift in Sources */,
2FE5DC3829EDD7CA004B9AB4 /* InterestingModules.swift in Sources */,
2FE5DC3529EDD7CA004B9AB4 /* Consent.swift in Sources */,
+ 22C75CF02B979D02008986AF /* ContentView.swift in Sources */,
2FE5DC4529EDD7F2004B9AB4 /* Binding+Negate.swift in Sources */,
8644E67B2B6C75B1001218D0 /* MapView.swift in Sources */,
2FC975A82978F11A00BA99FE /* Home.swift in Sources */,
@@ -729,6 +740,7 @@
2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */,
2F1AC9DF2B4E840E00C24973 /* PICS.docc in Sources */,
86EB7FF72B8FB7A000D52EE2 /* NotificationPermissions.swift in Sources */,
+ 22C75CE12B978E33008986AF /* Medication.swift in Sources */,
226C16F22B6C820C00FBA97D /* WeightKey.swift in Sources */,
A45993292B90544300A98C95 /* Assessments.swift in Sources */,
2FF53D8D2A8729D600042B76 /* PICSStandard.swift in Sources */,
@@ -1295,6 +1307,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
+ 22C75CF52B979EA7008986AF /* XCRemoteSwiftPackageReference "ImageSource" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/StanfordBDHG/ImageSource";
+ requirement = {
+ branch = main;
+ kind = branch;
+ };
+ };
2F3D4ABA2A4E7C290068FB2F /* XCRemoteSwiftPackageReference "SpeziScheduler" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/StanfordSpezi/SpeziScheduler.git";
@@ -1426,6 +1446,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
+ 22C75CF62B979EA7008986AF /* ImageSource */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 22C75CF52B979EA7008986AF /* XCRemoteSwiftPackageReference "ImageSource" */;
+ productName = ImageSource;
+ };
2F3D4ABB2A4E7C290068FB2F /* SpeziScheduler */ = {
isa = XCSwiftPackageProductDependency;
package = 2F3D4ABA2A4E7C290068FB2F /* XCRemoteSwiftPackageReference "SpeziScheduler" */;
diff --git a/PICS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/PICS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 06d8f3e..56cecb1 100644
--- a/PICS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/PICS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -90,6 +90,15 @@
"version" : "0.2.5"
}
},
+ {
+ "identity" : "imagesource",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/StanfordBDHG/ImageSource",
+ "state" : {
+ "branch" : "main",
+ "revision" : "9f942f21a485f4d552cfcefffae2e4077c0e9635"
+ }
+ },
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
diff --git a/PICS/Onboarding/ContentView.swift b/PICS/Onboarding/ContentView.swift
new file mode 100644
index 0000000..d2c6a6f
--- /dev/null
+++ b/PICS/Onboarding/ContentView.swift
@@ -0,0 +1,53 @@
+//
+// This source file is part of the ImageSource open source project
+//
+// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
+//
+// SPDX-License-Identifier: MIT
+//
+import Foundation
+import ImageSource
+import PDFKit
+import SwiftUI
+
+
+struct ContentView: View {
+ @State var image: UIImage?
+ @Environment(PICSStandard.self) private var standard
+
+ private var swiftUIImage: some View {
+ image.flatMap {
+ Image(uiImage: $0)
+ .accessibilityLabel(Text("Medication Plan"))
+ }
+ }
+
+ var body: some View {
+ ImageSource(image: $image)
+ .clipShape(RoundedRectangle(cornerRadius: 8))
+ .padding()
+ .onChange(of: image) {
+ uploadImage(image: image)
+ }
+ }
+ func uploadImage(image: UIImage?) {
+ if let img = image {
+ if let pdfPage = PDFPage(image: img) {
+ let pdfDocument = PDFDocument()
+ pdfDocument.insert(pdfPage, at: 0)
+ Task {
+ await standard.storeImage(image: pdfDocument)
+ }
+ } else {
+ print("Failed to create PDFPage from image")
+ }
+ }
+ }
+}
+
+
+struct ContentView_Previews: PreviewProvider {
+ static var previews: some View {
+ ContentView()
+ }
+}
diff --git a/PICS/Onboarding/Medication.swift b/PICS/Onboarding/Medication.swift
new file mode 100644
index 0000000..d9e55ac
--- /dev/null
+++ b/PICS/Onboarding/Medication.swift
@@ -0,0 +1,52 @@
+//
+// This source file is part of the PICS based on the Stanford Spezi Template Application project
+//
+// SPDX-FileCopyrightText: 2023 Stanford University
+//
+// SPDX-License-Identifier: MIT
+//
+import Foundation
+import SpeziHealthKit
+import SpeziOnboarding
+import SwiftUI
+
+
+struct Medication: View {
+ @Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath
+
+ var body: some View {
+ OnboardingView(
+ contentView: {
+ VStack {
+ OnboardingTitleView(
+ title: "MEDICATION_TITLE",
+ subtitle: "MEDICATION_SUBTITLE"
+ )
+ Spacer()
+ ContentView()
+ }
+ }, actionView: {
+ OnboardingActionsView(
+ "Upload Photo",
+ action: {
+ onboardingNavigationPath.nextStep()
+ }
+ )
+ }
+ )
+
+ // Small fix as otherwise "Login" or "Sign up" is still shown in the nav bar
+ .navigationTitle(Text(verbatim: ""))
+ }
+}
+
+
+#if DEBUG
+#Preview {
+ OnboardingStack {
+ Medication()
+ }
+ .previewWith(standard: PICSStandard()) {
+ }
+}
+#endif
diff --git a/PICS/Onboarding/OnboardingFlow.swift b/PICS/Onboarding/OnboardingFlow.swift
index f55375e..769d5c9 100644
--- a/PICS/Onboarding/OnboardingFlow.swift
+++ b/PICS/Onboarding/OnboardingFlow.swift
@@ -36,11 +36,13 @@ struct OnboardingFlow: View {
OnboardingStack(onboardingFlowComplete: $completedOnboardingFlow) {
Welcome()
InterestingModules()
-
if !FeatureFlags.disableFirebase {
AccountOnboarding()
}
+
+ Medication()
AccountQuestionnaire()
+
#if !(targetEnvironment(simulator) && (arch(i386) || arch(x86_64)))
Consent()
#endif
diff --git a/PICS/PICSStandard.swift b/PICS/PICSStandard.swift
index c197882..fe14cf3 100644
--- a/PICS/PICSStandard.swift
+++ b/PICS/PICSStandard.swift
@@ -212,6 +212,37 @@ actor PICSStandard: Standard, EnvironmentAccessible, HealthKitConstraint, Onboar
logger.error("Could not store consent form: \(error)")
}
}
+
+ func storeImage(image: PDFDocument) async {
+ let formatter = DateFormatter()
+ formatter.dateFormat = "yyyy-MM-dd_HHmmss"
+ let dateString = formatter.string(from: Date())
+
+ guard !FeatureFlags.disableFirebase else {
+ guard let basePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
+ logger.error("Could not create path for storing medication plan.")
+ return
+ }
+
+ let filePath = basePath.appending(path: "medicationPlan_\(dateString).pdf")
+ image.write(to: filePath)
+
+ return
+ }
+
+ do {
+ guard let medicationData = image.dataRepresentation() else {
+ logger.error("Could not store medication plan.")
+ return
+ }
+
+ let metadata = StorageMetadata()
+ metadata.contentType = "application/pdf"
+ _ = try await userBucketReference.child("medicationPlan/\(dateString).pdf").putDataAsync(medicationData, metadata: metadata)
+ } catch {
+ logger.error("Could not store medication plan: \(error)")
+ }
+ }
func create(_ identifier: AdditionalRecordId, _ details: SignupDetails) async throws {
diff --git a/PICS/Resources/Localizable.xcstrings b/PICS/Resources/Localizable.xcstrings
index 3d5c697..d033162 100644
--- a/PICS/Resources/Localizable.xcstrings
+++ b/PICS/Resources/Localizable.xcstrings
@@ -758,6 +758,31 @@
}
}
},
+ "Medication Plan" : {
+
+ },
+ "MEDICATION_SUBTITLE" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Please upload a photo of your medication chart. "
+ }
+ }
+ }
+ },
+ "MEDICATION_TITLE" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medication Information"
+ }
+ }
+ }
+ },
"MiniNutritional_DESCRIPTION" : {
"extractionState" : "manual",
"localizations" : {
@@ -1249,6 +1274,9 @@
}
}
}
+ },
+ "Upload Photo" : {
+
},
"Value" : {