From 3a4e60587aa27484f0ab4d09dfda6b033828eb5c Mon Sep 17 00:00:00 2001 From: Trey t Date: Thu, 26 Feb 2026 19:31:10 -0600 Subject: [PATCH] Fix production crash points, actor-isolation warnings, and rebrand URLs Remove all fatalError/force unwrap/force cast crash points from production paths (ShowBasedOnVoteLogics, Random, ReflectApp, NoteEditorView). Fix actor-isolation warnings by wrapping off-main-thread AnalyticsManager calls in Task { @MainActor in } (LocalNotification) and replacing DispatchQueue with Task.detached + MainActor.run (LiveActivityPreviewView). Update legal URLs from feels.88oakapps.com to reflect.88oakapps.com in SettingsView. Co-Authored-By: Claude Opus 4.6 --- Reflect.xcodeproj/project.pbxproj | 20 +++++++++---------- Shared/LocalNotification.swift | 8 ++++++-- Shared/Random.swift | 14 ++++++------- Shared/ReflectApp.swift | 3 ++- Shared/ShowBasedOnVoteLogics.swift | 8 ++------ Shared/Views/NoteEditorView.swift | 4 ++-- .../LiveActivityPreviewView.swift | 17 +++++++++------- Shared/Views/SettingsView/SettingsView.swift | 8 ++++---- 8 files changed, 43 insertions(+), 39 deletions(-) diff --git a/Reflect.xcodeproj/project.pbxproj b/Reflect.xcodeproj/project.pbxproj index 9ff078d..0f306f1 100644 --- a/Reflect.xcodeproj/project.pbxproj +++ b/Reflect.xcodeproj/project.pbxproj @@ -928,7 +928,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Reflect Watch App/Reflect Watch AppDebug.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 23; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = QND55P4443; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -940,7 +940,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.reflect.debug.watch; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; @@ -1303,20 +1303,20 @@ CODE_SIGN_ENTITLEMENTS = ReflectWidgetExtensionDev.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 23; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = QND55P4443; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "ReflectWidgetExtension-Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = ReflectWidget; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSSupportsLiveActivities = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.6; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.reflect.debug.widget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1337,20 +1337,20 @@ CODE_SIGN_ENTITLEMENTS = ReflectWidgetExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 23; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = QND55P4443; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "ReflectWidgetExtension-Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = ReflectWidget; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSSupportsLiveActivities = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.6; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.reflect.widget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1389,7 +1389,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "Reflect Watch App/Reflect Watch App.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 23; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = QND55P4443; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -1401,7 +1401,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.reflect.watch; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = watchos; diff --git a/Shared/LocalNotification.swift b/Shared/LocalNotification.swift index bfad30d..a77882d 100644 --- a/Shared/LocalNotification.swift +++ b/Shared/LocalNotification.swift @@ -21,10 +21,14 @@ class LocalNotification { public class func testIfEnabled(completion: @escaping (Result) -> Void) { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in if success { - AnalyticsManager.shared.track(.notificationEnabled) + Task { @MainActor in + AnalyticsManager.shared.track(.notificationEnabled) + } completion(.success(true)) } else if let error = error { - AnalyticsManager.shared.track(.notificationDisabled) + Task { @MainActor in + AnalyticsManager.shared.track(.notificationDisabled) + } completion(.failure(error)) } } diff --git a/Shared/Random.swift b/Shared/Random.swift index c0cecb1..c80d87c 100644 --- a/Shared/Random.swift +++ b/Shared/Random.swift @@ -27,9 +27,9 @@ struct Constants { struct GroupUserDefaults { static var groupDefaults: UserDefaults { #if DEBUG - return UserDefaults(suiteName: Constants.groupShareIdDebug)! + return UserDefaults(suiteName: Constants.groupShareIdDebug) ?? .standard #else - return UserDefaults(suiteName: Constants.groupShareId)! + return UserDefaults(suiteName: Constants.groupShareId) ?? .standard #endif } } @@ -68,7 +68,7 @@ class Random { static var existingDayFormat = [NSNumber: String]() static func dayFormat(fromDate date: Date) -> String { let components = Calendar.current.dateComponents([.day], from: date) - let day = components.day! + let day = components.day ?? 1 let formatter = NumberFormatter() formatter.numberStyle = .ordinal @@ -411,18 +411,18 @@ final class DateFormattingCache { extension Bundle { var appName: String { - return infoDictionary?["CFBundleName"] as! String + return infoDictionary?["CFBundleName"] as? String ?? "Reflect" } var bundleId: String { - return bundleIdentifier! + return bundleIdentifier ?? "com.88oakapps.reflect" } var versionNumber: String { - return infoDictionary?["CFBundleShortVersionString"] as! String + return infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0" } var buildNumber: String { - return infoDictionary?["CFBundleVersion"] as! String + return infoDictionary?["CFBundleVersion"] as? String ?? "0" } } diff --git a/Shared/ReflectApp.swift b/Shared/ReflectApp.swift index 9753619..6cfce52 100644 --- a/Shared/ReflectApp.swift +++ b/Shared/ReflectApp.swift @@ -33,7 +33,8 @@ struct ReflectApp: App { BGTaskScheduler.shared.cancelAllTaskRequests() BGTaskScheduler.shared.register(forTaskWithIdentifier: BGTask.updateDBMissingID, using: nil) { (task) in - BGTask.runFillInMissingDatesTask(task: task as! BGProcessingTask) + guard let processingTask = task as? BGProcessingTask else { return } + BGTask.runFillInMissingDatesTask(task: processingTask) } UNUserNotificationCenter.current().setBadgeCount(0) diff --git a/Shared/ShowBasedOnVoteLogics.swift b/Shared/ShowBasedOnVoteLogics.swift index 68e74a1..507f140 100644 --- a/Shared/ShowBasedOnVoteLogics.swift +++ b/Shared/ShowBasedOnVoteLogics.swift @@ -90,11 +90,7 @@ class ShowBasedOnVoteLogics { date = Calendar.current.date(byAdding: .day, value: -1, to: now) } - guard let date = date else { - fatalError("missing getCurrentVotingDate") - } - - return date + return date ?? now } static public func getVotingTitle(onboardingData: OnboardingData, now: Date = Date()) -> String { @@ -106,7 +102,7 @@ class ShowBasedOnVoteLogics { return String(localized: "add_mood_header_view_title_today") case (false, .Previous): - let date = Calendar.current.date(byAdding: .day, value: -2, to: now)! + let date = Calendar.current.date(byAdding: .day, value: -2, to: now) ?? now return String(format: String(localized: "add_mood_header_view_title"), Random.weekdayName(fromDate: date)) case (true, .Previous): return String(localized: "add_mood_header_view_title_yesterday") diff --git a/Shared/Views/NoteEditorView.swift b/Shared/Views/NoteEditorView.swift index 931e594..78cfd1a 100644 --- a/Shared/Views/NoteEditorView.swift +++ b/Shared/Views/NoteEditorView.swift @@ -478,9 +478,9 @@ struct EntryDetailView: View { Button("Choose from Library") { showPhotoPicker = true } - if entry.photoID != nil { + if let photoID = entry.photoID { Button("Remove Photo", role: .destructive) { - _ = PhotoManager.shared.deletePhoto(id: entry.photoID!) + _ = PhotoManager.shared.deletePhoto(id: photoID) _ = DataController.shared.updatePhoto(forDate: entry.forDate, photoID: nil) } } diff --git a/Shared/Views/SettingsView/LiveActivityPreviewView.swift b/Shared/Views/SettingsView/LiveActivityPreviewView.swift index 67457f3..7e87e24 100644 --- a/Shared/Views/SettingsView/LiveActivityPreviewView.swift +++ b/Shared/Views/SettingsView/LiveActivityPreviewView.swift @@ -321,9 +321,12 @@ struct LiveActivityRecordingView: View { exportPath = outputDir.path print("📁 Exporting frames to: \(exportPath)") - // Export frames on background queue - DispatchQueue.global(qos: .userInitiated).async { - for streak in 1...targetStreak { + let target = targetStreak + let outDir = outputDir + let outPath = exportPath + + Task.detached(priority: .userInitiated) { + for streak in 1...target { let mood = getMoodForStreak(streak) let cardView = LiveActivityCardView( @@ -337,21 +340,21 @@ struct LiveActivityRecordingView: View { if let uiImage = renderer.uiImage { let filename = String(format: "frame_%04d.png", streak) - let fileURL = outputDir.appendingPathComponent(filename) + let fileURL = outDir.appendingPathComponent(filename) if let pngData = uiImage.pngData() { try? pngData.write(to: fileURL) } } - DispatchQueue.main.async { + await MainActor.run { exportProgress = streak } } - DispatchQueue.main.async { + await MainActor.run { exportComplete = true - print("✅ Export complete! \(targetStreak) frames saved to: \(exportPath)") + print("✅ Export complete! \(target) frames saved to: \(outPath)") } } } diff --git a/Shared/Views/SettingsView/SettingsView.swift b/Shared/Views/SettingsView/SettingsView.swift index 8a3cbef..053375f 100644 --- a/Shared/Views/SettingsView/SettingsView.swift +++ b/Shared/Views/SettingsView/SettingsView.swift @@ -1030,7 +1030,7 @@ struct SettingsContentView: View { private var eulaButton: some View { Button(action: { AnalyticsManager.shared.track(.eulaViewed) - if let url = URL(string: "https://feels.88oakapps.com/eula.html") { + if let url = URL(string: "https://reflect.88oakapps.com/eula.html") { UIApplication.shared.open(url) } }, label: { @@ -1049,7 +1049,7 @@ struct SettingsContentView: View { private var privacyButton: some View { Button(action: { AnalyticsManager.shared.track(.privacyPolicyViewed) - if let url = URL(string: "https://feels.88oakapps.com/privacy.html") { + if let url = URL(string: "https://reflect.88oakapps.com/privacy.html") { UIApplication.shared.open(url) } }, label: { @@ -1757,7 +1757,7 @@ struct SettingsView: View { private var eulaButton: some View { Button(action: { AnalyticsManager.shared.track(.eulaViewed) - openURL(URL(string: "https://feels.88oakapps.com/eula.html")!) + if let url = URL(string: "https://reflect.88oakapps.com/eula.html") { openURL(url) } }, label: { Text(String(localized: "settings_view_show_eula")) .foregroundColor(textColor) @@ -1772,7 +1772,7 @@ struct SettingsView: View { private var privacyButton: some View { Button(action: { AnalyticsManager.shared.track(.privacyPolicyViewed) - openURL(URL(string: "https://feels.88oakapps.com/privacy.html")!) + if let url = URL(string: "https://reflect.88oakapps.com/privacy.html") { openURL(url) } }, label: { Text(String(localized: "settings_view_show_privacy")) .foregroundColor(textColor)