From ea6293799c1484aa81530b1eca55dc84c927602a Mon Sep 17 00:00:00 2001 From: Laurent Date: Sat, 7 Oct 2023 17:46:01 +0200 Subject: [PATCH] Avoid systematic crashes when things go wrong + better email management --- LeCountdown.xcodeproj/project.pbxproj | 4 +++ LeCountdown/AppDelegate.swift | 11 ++++---- .../Model/Model+SharedExtensions.swift | 3 +-- LeCountdown/Utils/URLs.swift | 12 +++++++++ LeCountdown/Views/Reusable/MailView.swift | 3 ++- LeCountdown/Views/SettingsView.swift | 25 ++++++++++++++++--- 6 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 LeCountdown/Utils/URLs.swift diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index c375b53..8c18f0c 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -126,6 +126,7 @@ C4742B59298411E800D5D950 /* CountdownFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4742B58298411E800D5D950 /* CountdownFormView.swift */; }; C4742B5B298414B000D5D950 /* ImageSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4742B5A298414B000D5D950 /* ImageSelectionView.swift */; }; C4742B5F2984205000D5D950 /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4742B5E2984205000D5D950 /* ViewModifiers.swift */; }; + C47A9AF32AD1B32C00618A50 /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47A9AF22AD1B32C00618A50 /* URLs.swift */; }; C47C933529F01B5E00C780E2 /* FileLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4556F6A29E40B7800DEB40B /* FileLogger.swift */; }; C47C933629F01B6600C780E2 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4556F6E29E40BED00DEB40B /* FileUtils.swift */; }; C47C933729F01B7A00C780E2 /* Codable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4556F7029E40DCF00DEB40B /* Codable+Extensions.swift */; }; @@ -434,6 +435,7 @@ C4742B58298411E800D5D950 /* CountdownFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountdownFormView.swift; sourceTree = ""; }; C4742B5A298414B000D5D950 /* ImageSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSelectionView.swift; sourceTree = ""; }; C4742B5E2984205000D5D950 /* ViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifiers.swift; sourceTree = ""; }; + C47A9AF22AD1B32C00618A50 /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = ""; }; C47C933829F13BD100C780E2 /* AppleMusicPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleMusicPickerView.swift; sourceTree = ""; }; C47C933C29F13DBD00C780E2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; C48940DD2AC307860086F4FA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; @@ -779,6 +781,7 @@ C4060DF4297AE9A7003FAB80 /* TimeInterval+Extensions.swift */, C473C33B29ACEC4F0056B38A /* Tip.swift */, C4BA2B7829A65C1400CB4FBA /* UIDevice+Extensions.swift */, + C47A9AF22AD1B32C00618A50 /* URLs.swift */, ); path = Utils; sourceTree = ""; @@ -1349,6 +1352,7 @@ C4F8B17A298AC234005C86A5 /* Countdown+CoreDataClass.swift in Sources */, C42E970229E6B32B005B1B8C /* CalendarView.swift in Sources */, C4BA2B0F299BE61E00CB4FBA /* Interval+CoreDataClass.swift in Sources */, + C47A9AF32AD1B32C00618A50 /* URLs.swift in Sources */, C4F8B164298A9A92005C86A5 /* AlarmFormView.swift in Sources */, C4286EB02A1B75AB0070D075 /* BoringContext.swift in Sources */, C40FDB622992985C0042A390 /* TextToSpeechRecorder.swift in Sources */, diff --git a/LeCountdown/AppDelegate.swift b/LeCountdown/AppDelegate.swift index fd50e35..b31349f 100644 --- a/LeCountdown/AppDelegate.swift +++ b/LeCountdown/AppDelegate.swift @@ -90,8 +90,9 @@ extension AppDelegate: UNUserNotificationCenterDelegate { print("didReceive notification") FileLogger.log("userNotificationCenter didReceive > cancelling sound player") - let timerId = self._timerId(notificationId: response.notification.request.identifier) - Conductor.maestro.cancelSoundPlayer(id: timerId) + if let timerId = self._timerId(notificationId: response.notification.request.identifier) { + Conductor.maestro.cancelSoundPlayer(id: timerId) + } } func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { @@ -102,13 +103,13 @@ extension AppDelegate: UNUserNotificationCenterDelegate { // Conductor.maestro.notifyUser(countdownId: timerId) } - fileprivate func _timerId(notificationId: String) -> TimerID { + fileprivate func _timerId(notificationId: String) -> TimerID? { let components = notificationId.components(separatedBy: CountdownScheduler.notificationIdSeparator) if components.count == 2 { return components[0] } else { - FileLogger.log("app terminated by ourselves") - fatalError("bad notification format : \(notificationId)") + FileLogger.log("Couldn't parse notification Id: \(notificationId)") + return nil } } diff --git a/LeCountdown/Model/Model+SharedExtensions.swift b/LeCountdown/Model/Model+SharedExtensions.swift index dfbf649..26f16ac 100644 --- a/LeCountdown/Model/Model+SharedExtensions.swift +++ b/LeCountdown/Model/Model+SharedExtensions.swift @@ -27,8 +27,7 @@ extension AbstractTimer { if let url = URL(string: self.stringId) { return url } else { -// FileLogger.log("app terminated by ourselves") - fatalError("Can't produce url with \(self.stringId)") + return URL(filePath: "") // dummy URL to avoid the pain of dealing with optional/error } } diff --git a/LeCountdown/Utils/URLs.swift b/LeCountdown/Utils/URLs.swift new file mode 100644 index 0000000..3610c2b --- /dev/null +++ b/LeCountdown/Utils/URLs.swift @@ -0,0 +1,12 @@ +// +// URLs.swift +// LeCountdown +// +// Created by Laurent Morvillier on 07/10/2023. +// + +import Foundation + +enum URLs: String { + case mail = "hello@getenchanted.app" +} diff --git a/LeCountdown/Views/Reusable/MailView.swift b/LeCountdown/Views/Reusable/MailView.swift index 13f36c3..746833e 100644 --- a/LeCountdown/Views/Reusable/MailView.swift +++ b/LeCountdown/Views/Reusable/MailView.swift @@ -33,10 +33,11 @@ struct MailView: UIViewControllerRepresentable { } func makeUIViewController(context: UIViewControllerRepresentableContext) -> MFMailComposeViewController { + let vc = MFMailComposeViewController() vc.mailComposeDelegate = context.coordinator vc.setSubject(Bundle.main.applicationName) - vc.setToRecipients(["hello@getenchanted.app"]) + vc.setToRecipients([URLs.mail.rawValue]) let lastLogs = FileLogger.main.logs.suffix(40).reversed() let logs = lastLogs.map { $0.date.formattedDateTime + "\n" + $0.content } diff --git a/LeCountdown/Views/SettingsView.swift b/LeCountdown/Views/SettingsView.swift index 874cad6..ac22d96 100644 --- a/LeCountdown/Views/SettingsView.swift +++ b/LeCountdown/Views/SettingsView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import MessageUI struct SettingsView: View { @@ -15,6 +16,7 @@ struct SettingsView: View { @State var showMailView: Bool = false @State var showLogsSheet: Bool = false + @State var emailCopied: Bool = false var body: some View { @@ -36,11 +38,26 @@ struct SettingsView: View { }) .frame(width: 120.0) } - Button { - self.showMailView = true - } label: { - Text("Contact us") + + if MFMailComposeViewController.canSendMail() { + Button { + self.showMailView = true + } label: { + Text("Contact us") + } + } else { + if self.emailCopied { + Label("Copied", systemImage: "checkmark") + } else { + Button { + UIPasteboard.general.string = URLs.mail.rawValue + self.emailCopied = true + } label: { + LabeledContent("Contact us", value: "tap to copy email") + } + } } + Button { withAnimation { self.showLogsSheet.toggle()