Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Export Button #16

Merged
merged 3 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions OwnYourData.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
2F0191F92A9E4CF100E9EB0E /* String+LocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0191F82A9E4CF100E9EB0E /* String+LocalizedError.swift */; };
2F0191FB2A9E579E00E9EB0E /* FHIRMultipleResourceInterpreter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0191FA2A9E579E00E9EB0E /* FHIRMultipleResourceInterpreter.swift */; };
2F0191FD2A9E57BD00E9EB0E /* Prompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0191FC2A9E57BD00E9EB0E /* Prompt.swift */; };
2F0608CF2AC3884100836556 /* ExportPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0608CE2AC3884100836556 /* ExportPackage.swift */; };
2F2146F42A82AF7F007CB929 /* SpeziOpenAI in Frameworks */ = {isa = PBXBuildFile; productRef = 2F2146F32A82AF7F007CB929 /* SpeziOpenAI */; };
2F2146F72A82AF9B007CB929 /* SpeziOnboarding in Frameworks */ = {isa = PBXBuildFile; productRef = 2F2146F62A82AF9B007CB929 /* SpeziOnboarding */; };
2F2146FE2A82B236007CB929 /* SpeziFirebaseAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 2F2146FD2A82B236007CB929 /* SpeziFirebaseAccount */; };
Expand All @@ -36,6 +37,7 @@
2F8537712A9DB6C8006994BB /* ModelsR4 in Frameworks */ = {isa = PBXBuildFile; productRef = 2F8537702A9DB6C8006994BB /* ModelsR4 */; };
2F8537742A9DB6E5006994BB /* HealthKitOnFHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 2F8537732A9DB6E5006994BB /* HealthKitOnFHIR */; };
2F8537762A9DB781006994BB /* SpeziAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 2F8537752A9DB781006994BB /* SpeziAccount */; };
2F9652FE2AC388F300977083 /* URL+Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F9652FD2AC388F300977083 /* URL+Zip.swift */; };
2FA2023329CBCC0C0039C21A /* DocumentScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA2023229CBCC0C0039C21A /* DocumentScanner.swift */; };
2FB2943929CBA29900EE91A0 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FB2943329CBA29900EE91A0 /* ProfileView.swift */; };
2FB2943D29CBA29900EE91A0 /* ClinicalTrialsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FB2943729CBA29900EE91A0 /* ClinicalTrialsView.swift */; };
Expand Down Expand Up @@ -103,6 +105,7 @@
2F0191F82A9E4CF100E9EB0E /* String+LocalizedError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+LocalizedError.swift"; sourceTree = "<group>"; };
2F0191FA2A9E579E00E9EB0E /* FHIRMultipleResourceInterpreter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FHIRMultipleResourceInterpreter.swift; sourceTree = "<group>"; };
2F0191FC2A9E57BD00E9EB0E /* Prompt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Prompt.swift; sourceTree = "<group>"; };
2F0608CE2AC3884100836556 /* ExportPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportPackage.swift; sourceTree = "<group>"; };
2F4E237D2989A2FE0013F3D9 /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = "<group>"; };
2F4E23822989D51F0013F3D9 /* TemplateAppTestingSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateAppTestingSetup.swift; sourceTree = "<group>"; };
2F5E32BC297E05EA003432F8 /* TemplateAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateAppDelegate.swift; sourceTree = "<group>"; };
Expand All @@ -112,6 +115,7 @@
2F8537672A9DB67E006994BB /* FHIRResource+Search.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FHIRResource+Search.swift"; sourceTree = "<group>"; };
2F8537682A9DB67E006994BB /* FHIR.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FHIR.swift; sourceTree = "<group>"; };
2F8537692A9DB67E006994BB /* FHIRResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FHIRResource.swift; sourceTree = "<group>"; };
2F9652FD2AC388F300977083 /* URL+Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Zip.swift"; sourceTree = "<group>"; };
2FA2023229CBCC0C0039C21A /* DocumentScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentScanner.swift; sourceTree = "<group>"; };
2FAEC07F297F583900C11C42 /* TemplateApplication.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TemplateApplication.entitlements; sourceTree = "<group>"; };
2FB2943329CBA29900EE91A0 /* ProfileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -294,6 +298,8 @@
isa = PBXGroup;
children = (
2FB2943329CBA29900EE91A0 /* ProfileView.swift */,
2F0608CE2AC3884100836556 /* ExportPackage.swift */,
2F9652FD2AC388F300977083 /* URL+Zip.swift */,
);
path = Profile;
sourceTree = "<group>";
Expand Down Expand Up @@ -601,12 +607,14 @@
2FA2023329CBCC0C0039C21A /* DocumentScanner.swift in Sources */,
FCD3FFB729CD0C31004D1E0E /* LogoView.swift in Sources */,
2FB2FCD029CBDDC00027D85A /* TemplateSignUp.swift in Sources */,
2F9652FE2AC388F300977083 /* URL+Zip.swift in Sources */,
2FB2FCD729CBDDC00027D85A /* Consent.swift in Sources */,
2FB2FCD329CBDDC00027D85A /* TemplateLogin.swift in Sources */,
2F8537652A9D1279006994BB /* HealthKitPermissions.swift in Sources */,
2FE573A729CD4672008EBBD4 /* PDFView.swift in Sources */,
2F0191F62A9E4CBD00E9EB0E /* OpenAIChatView.swift in Sources */,
2FE573A329CD4617008EBBD4 /* PDFDocument+Transferable.swift in Sources */,
2F0608CF2AC3884100836556 /* ExportPackage.swift in Sources */,
2F4E23832989D51F0013F3D9 /* TemplateAppTestingSetup.swift in Sources */,
2F5E32BD297E05EA003432F8 /* TemplateAppDelegate.swift in Sources */,
2FB2FCD129CBDDC00027D85A /* UserView.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions TemplateApplication/Documents/DocumentGallery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct DocumentGallery: View {
},
label: {
Image(systemName: "plus")
.accessibilityLabel("Add Document")
}
)
}
Expand Down
4 changes: 4 additions & 0 deletions TemplateApplication/FHIR Standard/FHIR.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ actor FHIR: Standard, ObservableObject, ObservableObjectProvider, HealthKitConst
Array(_resources.values)
}

@MainActor var exportPackage: ExportPackage {
ExportPackage(resources: resources)
}


init() {
guard HKHealthStore.isHealthDataAvailable() else {
Expand Down
1 change: 1 addition & 0 deletions TemplateApplication/Home.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ struct Home: View {
},
label: {
Image(systemName: "person.crop.circle")
.accessibilityLabel("Profile View")
}
)
}
Expand Down
1 change: 1 addition & 0 deletions TemplateApplication/Instructions/Instructions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct Instructions: View {
ForEach(steps, id: \.offset) { step in
HStack {
Image(systemName: "\(step.offset + 1).circle.fill")
.accessibilityHidden(true)
.foregroundColor(Color("ButtonColor_light"))
.font(.system(size: 45))
.frame(minHeight: 90)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ struct AccountSetup: View {
Group {
if account.signedIn {
Image(systemName: "person.badge.shield.checkmark.fill")
.accessibilityLabel("You are signed in")
} else {
Image(systemName: "person.fill.badge.plus")
.accessibilityLabel("You can sign up or sign in")
}
}
.font(.system(size: 150))
Expand Down
1 change: 1 addition & 0 deletions TemplateApplication/Onboarding/HealthKitPermissions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct HealthKitPermissions: View {
)
Spacer()
Image(systemName: "heart.text.square.fill")
.accessibilityHidden(true)
.font(.system(size: 150))
.foregroundColor(.accentColor)
Text("HEALTHKIT_PERMISSIONS_DESCRIPTION")
Expand Down
6 changes: 3 additions & 3 deletions TemplateApplication/Onboarding/Welcome.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ struct Welcome: View {
subtitle: "WELCOME_SUBTITLE",
areas: [
.init(
icon: Image(systemName: "folder.badge.plus"),
icon: Image(systemName: "folder.badge.plus"), // swiftlint:disable:this accessibility_label_for_image
title: "WELCOME_AREA1_TITLE",
description: "WELCOME_AREA1_DESCRIPTION"
),
.init(
icon: Image(systemName: "magnifyingglass"),
icon: Image(systemName: "magnifyingglass"), // swiftlint:disable:this accessibility_label_for_image
title: "WELCOME_AREA2_TITLE",
description: "WELCOME_AREA2_DESCRIPTION"
),
.init(
icon: Image(systemName: "square.and.arrow.up"),
icon: Image(systemName: "square.and.arrow.up"), // swiftlint:disable:this accessibility_label_for_image
title: "WELCOME_AREA3_TITLE",
description: "WELCOME_AREA3_DESCRIPTION"
)
Expand Down
1 change: 1 addition & 0 deletions TemplateApplication/Overview/InstructionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct InstructionsView: View {
var body: some View {
VStack(alignment: .center) {
Image(systemName: "doc.text.magnifyingglass")
.accessibilityHidden(true)
.font(.system(size: 90))
.foregroundColor(.accentColor)
.padding(.vertical, 8)
Expand Down
1 change: 1 addition & 0 deletions TemplateApplication/Overview/LLMSummaryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct LLMSummaryView: View {
var body: some View {
VStack(alignment: .center) {
Image(systemName: "magnifyingglass")
.accessibilityHidden(true)
.font(.system(size: 90))
.foregroundColor(.accentColor)
.padding(.vertical, 8)
Expand Down
61 changes: 61 additions & 0 deletions TemplateApplication/Profile/ExportPackage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// This source file is part of the Stanford OwnYourData Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//

import CoreTransferable
import OSLog


struct ExportPackage: Transferable {
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(
exportedContentType: .zip,
exporting: { document in
try await SentTransferredFile(document.zipRepresentation)
}
)
}


let resources: [FHIRResource]

private var directory: URL {
FileManager.default.temporaryDirectory.appendingPathComponent(
"edu.stanford.ownyourdate.export",
isDirectory: true
)
}

var zipRepresentation: URL {
get async throws {
if directory.exists {
try FileManager.default.removeItem(at: directory)
}
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)

for resource in resources {
guard let id = resource.id else {
os_log("Can not export resource named \(resource.displayName)")
continue
}

let resourceJSONData = Data(resource.jsonDescription.utf8)
try resourceJSONData.write(to: directory.appending(path: "\(id).json"))
}

let zipURL = try directory.zip()
try FileManager.default.removeItem(at: directory)

return zipURL
}
}


func deleteZipRepresentation() throws {
try FileManager.default.removeItem(at: directory.appendingPathExtension(".zip"))
}
}
103 changes: 69 additions & 34 deletions TemplateApplication/Profile/ProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,51 +19,86 @@ struct ProfileView: View {
@AppStorage(StorageKeys.onboardingFlowComplete) var completedOnboardingFlow = false

@EnvironmentObject var documentManager: DocumentManager
@EnvironmentObject var fhirStandard: FHIR


var body: some View {
VStack {
Image(systemName: "person.circle.fill")
.resizable()
.scaledToFit()
.accessibility(label: Text("profile image"))
.foregroundColor(Color("ButtonColor_dark"))
.frame(width: 120, height: 120)
.padding(.top, 80)
VStack(spacing: 10) {
Text("\(firstName) \(lastName)")
.font(.title2)
Text("Email: \(email)")
.font(.subheadline)
}
Spacer()
OwnYourDataButton(
title: "Log Out",
action: {
do {
try Auth.auth().signOut()

firstName = ""
lastName = ""
email = ""

completedOnboardingFlow = false

documentManager.removeAllDocuments()

print("Logged out.")
} catch {
print("Error signing out: \(error)")
GeometryReader { proxy in
ScrollView(.vertical) {
VStack {
Image(systemName: "person.circle.fill")
.resizable()
.scaledToFit()
.accessibility(label: Text("profile image"))
.foregroundColor(Color("ButtonColor_dark"))
.frame(width: 120, height: 120)
.padding(.top, 40)
VStack(spacing: 10) {
Text("\(firstName) \(lastName)")
.font(.title2)
Text("Email: \(email)")
.font(.subheadline)
}
sharebutton
Spacer(minLength: 64)
logoutButton
}
)
.padding(.bottom, 30)
.frame(minHeight: proxy.size.height)
}
.frame(width: proxy.size.width)
}
.padding(.bottom, 30)
.task {
fetchUserData()
}
}

@ViewBuilder private var sharebutton: some View {
VStack(alignment: .center, spacing: 8) {
ShareLink(
item: fhirStandard.exportPackage,
preview: SharePreview(
Text("FHIR JSON Export Package")
)
) {
HStack {
Image(systemName: "square.and.arrow.up")
.accessibilityHidden(true)
Text("Export")
}
}
.buttonStyle(.borderedProminent)
Text("Export your health records to share them with the OwnYourData team.")
.multilineTextAlignment(.center)
.foregroundStyle(Color.accentColor)
.font(.caption)
}
.padding()
}

@ViewBuilder private var logoutButton: some View {
OwnYourDataButton(
title: "Log Out",
action: {
do {
try Auth.auth().signOut()

firstName = ""
lastName = ""
email = ""

completedOnboardingFlow = false

documentManager.removeAllDocuments()

print("Logged out.")
} catch {
print("Error signing out: \(error)")
}
}
)
}


private func fetchUserData() {
if let currentUser = Auth.auth().currentUser {
Expand Down
Loading