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. Screenshot 2024-03-06 at 10 24 12 am ## :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" : {