diff --git a/Balance.xcodeproj/project.pbxproj b/Balance.xcodeproj/project.pbxproj index 4d2d54b..dd54b99 100644 --- a/Balance.xcodeproj/project.pbxproj +++ b/Balance.xcodeproj/project.pbxproj @@ -160,6 +160,7 @@ 6CF6BEE92A31103A00A337F3 /* CodableArray+RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF6BEE42A31103A00A337F3 /* CodableArray+RawRepresentable.swift */; }; 6CF6BEEA2A31103A00A337F3 /* Binding+Negate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF6BEE52A31103A00A337F3 /* Binding+Negate.swift */; }; 6CF6BEF52A37B55A00A337F3 /* TrackCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF6BEF42A37B55900A337F3 /* TrackCellView.swift */; }; + 6CF978992A65EC970046E90A /* EmailHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF978982A65EC970046E90A /* EmailHelper.swift */; }; 6CFB02412A438AFA000045E1 /* Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFB02402A438AFA000045E1 /* Photo.swift */; }; 6CFB02432A438CC6000045E1 /* PhotoArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFB02422A438CC6000045E1 /* PhotoArray.swift */; }; 6CFB02452A438D4E000045E1 /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFB02442A438D4E000045E1 /* Category.swift */; }; @@ -338,6 +339,7 @@ 6CF6BEE42A31103A00A337F3 /* CodableArray+RawRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CodableArray+RawRepresentable.swift"; sourceTree = ""; }; 6CF6BEE52A31103A00A337F3 /* Binding+Negate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Binding+Negate.swift"; sourceTree = ""; }; 6CF6BEF42A37B55900A337F3 /* TrackCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackCellView.swift; sourceTree = ""; }; + 6CF978982A65EC970046E90A /* EmailHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailHelper.swift; sourceTree = ""; }; 6CFB02402A438AFA000045E1 /* Photo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Photo.swift; sourceTree = ""; }; 6CFB02422A438CC6000045E1 /* PhotoArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoArray.swift; sourceTree = ""; }; 6CFB02442A438D4E000045E1 /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; @@ -579,6 +581,7 @@ 6C5523F629F80E8800F802D9 /* ShowHideSecureField.swift */, 6C5523F829F8126000F802D9 /* ProfileCellView.swift */, 6C47339029FC5C7D00DC72D0 /* PersonalDataView.swift */, + 6CF978982A65EC970046E90A /* EmailHelper.swift */, ); path = Profile; sourceTree = ""; @@ -1119,6 +1122,7 @@ 2720083D29A7E5830052908D /* PastDiaryEntry.swift in Sources */, 5EF9D78429B5AFF1006C3B22 /* ActivityStorageManager.swift in Sources */, 6CFDD58A2A0D9ECE009D7E56 /* ColoringHomeView.swift in Sources */, + 6CF978992A65EC970046E90A /* EmailHelper.swift in Sources */, 6C0A4BEE29E9B053003007C7 /* AvatarSelectionView.swift in Sources */, 6CF510C029C8FD25008A2F55 /* LocationView.swift in Sources */, 6CF6BEE72A31103A00A337F3 /* FeatureFlags.swift in Sources */, diff --git a/Balance/Distraction/Gallery/LookImages/ImageView.swift b/Balance/Distraction/Gallery/LookImages/ImageView.swift index 2ecedef..cf86014 100644 --- a/Balance/Distraction/Gallery/LookImages/ImageView.swift +++ b/Balance/Distraction/Gallery/LookImages/ImageView.swift @@ -53,7 +53,7 @@ struct ImageView: View { ZStack { backgroundColor.edgesIgnoringSafeArea(.all) VStack { - HeaderMenu(title: "Distraction") + HeaderMenu(title: "Look at Images") Spacer() tabImages .tabViewStyle(.page(indexDisplayMode: .never)) diff --git a/Balance/Profile/EmailHelper.swift b/Balance/Profile/EmailHelper.swift new file mode 100644 index 0000000..ee66db6 --- /dev/null +++ b/Balance/Profile/EmailHelper.swift @@ -0,0 +1,105 @@ +// +// EmailHelper.swift +// Balance +// +// Created by Gonzalo Perisset on 17/07/2023. +// + +import MessageUI +import SwiftUI + +// swiftlint:disable modifier_order +// swiftlint:disable force_unwrapping +// swiftlint:disable unused_closure_parameter +// swiftlint:disable legacy_objc_type +class EmailHelper: NSObject { + static let shared = EmailHelper() + private override init() {} +} + +extension EmailHelper { + func send(subject: String, body: String, file: URL, to: [String]) { + let scenes = UIApplication.shared.connectedScenes + let windowScene = scenes.first as? UIWindowScene + + guard let viewController = windowScene?.windows.first?.rootViewController else { + return + } + + if !MFMailComposeViewController.canSendMail() { + let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + let mails = to.joined(separator: ",") + + let alert = UIAlertController(title: "Cannot open Mail!", message: "", preferredStyle: .actionSheet) + + var haveExternalMailbox = false + + if let url = createEmailUrl(to: mails, subject: subjectEncoded, body: bodyEncoded), UIApplication.shared.canOpenURL(url) { + haveExternalMailbox = true + alert.addAction(UIAlertAction(title: "Gmail", style: .default, handler: { action in + UIApplication.shared.open(url) + })) + } + + if haveExternalMailbox { + alert.message = "Would you like to open an external mailbox?" + } else { + alert.message = "Please add your mail to Settings before using the mail service." + + if let settingsUrl = URL(string: UIApplication.openSettingsURLString), + UIApplication.shared.canOpenURL(settingsUrl) { + alert.addAction(UIAlertAction(title: "Open Settings App", style: .default, handler: { action in + UIApplication.shared.open(settingsUrl) + })) + } + } + + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + viewController.present(alert, animated: true, completion: nil) + return + } + + let mailCompose = MFMailComposeViewController() + mailCompose.setSubject(subject) + mailCompose.setMessageBody(body, isHTML: false) + mailCompose.setToRecipients(to) + mailCompose.mailComposeDelegate = self + + if let data = NSData(contentsOf: file) { + mailCompose.addAttachmentData(data as Data, mimeType: "application/csv", fileName: "export.csv") + } + + viewController.present(mailCompose, animated: true, completion: nil) + } + + private func createEmailUrl(to: String, subject: String, body: String) -> URL? { + let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + + let gmailUrl = URL(string: "googlegmail://co?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)") + let outlookUrl = URL(string: "ms-outlook://compose?to=\(to)&subject=\(subjectEncoded)") + let yahooMail = URL(string: "ymail://mail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)") + let sparkUrl = URL(string: "readdle-spark://compose?recipient=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)") + let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)") + + if let gmailUrl = gmailUrl, UIApplication.shared.canOpenURL(gmailUrl) { + return gmailUrl + } else if let outlookUrl = outlookUrl, UIApplication.shared.canOpenURL(outlookUrl) { + return outlookUrl + } else if let yahooMail = yahooMail, UIApplication.shared.canOpenURL(yahooMail) { + return yahooMail + } else if let sparkUrl = sparkUrl, UIApplication.shared.canOpenURL(sparkUrl) { + return sparkUrl + } + + return defaultUrl + } +} + +// MARK: - MFMailComposeViewControllerDelegate +extension EmailHelper: MFMailComposeViewControllerDelegate { + func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + controller.dismiss(animated: true, completion: nil) + } +} diff --git a/Balance/Profile/ProfileView.swift b/Balance/Profile/ProfileView.swift index 967dbb2..b02d7fd 100644 --- a/Balance/Profile/ProfileView.swift +++ b/Balance/Profile/ProfileView.swift @@ -25,15 +25,15 @@ struct ProfileView: View { @EnvironmentObject var noteStore: NoteStore @EnvironmentObject var drawStore: DrawStore @EnvironmentObject var coloringStore: ColoringStore - #if DEMO +#if DEMO @EnvironmentObject var logStore: ActivityLogStore - #endif +#endif @State private var displayName = "" @State private var avatar = "" @State private var email = "" @State private var patientID = "" @State private var showAlert = false - + var body: some View { ActivityLogContainer { ZStack { @@ -136,6 +136,19 @@ struct ProfileView: View { } var shareOption: some View { + Button(action: { + EmailHelper.shared.send( + subject: "Balance Export", + body: "ParticipantID: " + self.patientID + " - Name: " + self.displayName + " - Email:" + self.email + "\n", + file: convertToCSV(), + to: ["gonzalo.perisset@gmail.com"] + ) + }) { + ProfileCellView(image: "directcurrent", text: "Share data") + } + } + + var shareLink: some View { ShareLink( item: convertToCSV(), subject: Text("Balance Export") @@ -175,10 +188,19 @@ struct ProfileView: View { var logoutOption: some View { Button { print("Logout") -// dismiss() +#if DEMO + UserImageCache.remove(key: self.patientID.appending("UploadedArray")) + UserImageCache.remove(key: self.patientID.appending("RemovedArray")) + UserImageCache.remove(key: self.patientID.appending("FavoritesArray")) + logStore.removeStore() + noteStore.removeStore() + drawStore.removeStore() + coloringStore.removeStore() +#endif + // dismiss() authModel.signOut() completedOnboardingFlow = false -// account.signedIn = false + // account.signedIn = false } label: { ProfileCellView(image: "figure.walk.motion", text: "Logout") } @@ -281,7 +303,7 @@ struct ProfileView: View { } } - func convertToCSV() -> String { + func convertToCSV() -> URL { var noteAsCSV = "ParticipantID: " + self.patientID + " - " + self.displayName + " - " + self.email + "\n" noteAsCSV.append(contentsOf: "id, activeStartTime, activeEndTime, activeDuration, actionTime, actionDescription\n") @@ -291,6 +313,16 @@ struct ProfileView: View { } } - return noteAsCSV + let fileManager = FileManager.default + do { + let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false) + let fileURL = path.appendingPathComponent("export.csv") + try noteAsCSV.write(to: fileURL, atomically: true, encoding: .utf8) + + return fileURL + } catch { + print("error creating file") + } + return URL(fileURLWithPath: "") } } diff --git a/Balance/Supporting Files/Info.plist b/Balance/Supporting Files/Info.plist index 46b8ede..0b90b54 100644 --- a/Balance/Supporting Files/Info.plist +++ b/Balance/Supporting Files/Info.plist @@ -68,5 +68,12 @@ 6897d8e8934a48c19a9f2129c7349a0c SpotifySecretID d389fb0d126f4960ba79b031145f6f49 + LSApplicationQueriesSchemes + + googlegmail + ms-outlook + readdle-spark + ymail +