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 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-26 19:31:10 -06:00
parent 16c5c34942
commit 3a4e60587a
8 changed files with 43 additions and 39 deletions

View File

@@ -928,7 +928,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Reflect Watch App/Reflect Watch AppDebug.entitlements"; CODE_SIGN_ENTITLEMENTS = "Reflect Watch App/Reflect Watch AppDebug.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 23; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QND55P4443; DEVELOPMENT_TEAM = QND55P4443;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -940,7 +940,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.2; MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.reflect.debug.watch; PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.reflect.debug.watch;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos; SDKROOT = watchos;
@@ -1303,20 +1303,20 @@
CODE_SIGN_ENTITLEMENTS = ReflectWidgetExtensionDev.entitlements; CODE_SIGN_ENTITLEMENTS = ReflectWidgetExtensionDev.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 23; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QND55P4443; DEVELOPMENT_TEAM = QND55P4443;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "ReflectWidgetExtension-Info.plist"; INFOPLIST_FILE = "ReflectWidgetExtension-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = ReflectWidget; INFOPLIST_KEY_CFBundleDisplayName = ReflectWidget;
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_NSSupportsLiveActivities = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.6; IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@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_BUNDLE_IDENTIFIER = com.88oakapps.reflect.debug.widget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1337,20 +1337,20 @@
CODE_SIGN_ENTITLEMENTS = ReflectWidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ReflectWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 23; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QND55P4443; DEVELOPMENT_TEAM = QND55P4443;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "ReflectWidgetExtension-Info.plist"; INFOPLIST_FILE = "ReflectWidgetExtension-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = ReflectWidget; INFOPLIST_KEY_CFBundleDisplayName = ReflectWidget;
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_NSSupportsLiveActivities = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.6; IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.2; MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.reflect.widget; PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.reflect.widget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1389,7 +1389,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Reflect Watch App/Reflect Watch App.entitlements"; CODE_SIGN_ENTITLEMENTS = "Reflect Watch App/Reflect Watch App.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 23; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = QND55P4443; DEVELOPMENT_TEAM = QND55P4443;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -1401,7 +1401,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.2; MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.reflect.watch; PRODUCT_BUNDLE_IDENTIFIER = com.88oakapps.reflect.watch;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos; SDKROOT = watchos;

View File

@@ -21,10 +21,14 @@ class LocalNotification {
public class func testIfEnabled(completion: @escaping (Result<Bool, Error>) -> Void) { public class func testIfEnabled(completion: @escaping (Result<Bool, Error>) -> Void) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success { if success {
AnalyticsManager.shared.track(.notificationEnabled) Task { @MainActor in
AnalyticsManager.shared.track(.notificationEnabled)
}
completion(.success(true)) completion(.success(true))
} else if let error = error { } else if let error = error {
AnalyticsManager.shared.track(.notificationDisabled) Task { @MainActor in
AnalyticsManager.shared.track(.notificationDisabled)
}
completion(.failure(error)) completion(.failure(error))
} }
} }

View File

@@ -27,9 +27,9 @@ struct Constants {
struct GroupUserDefaults { struct GroupUserDefaults {
static var groupDefaults: UserDefaults { static var groupDefaults: UserDefaults {
#if DEBUG #if DEBUG
return UserDefaults(suiteName: Constants.groupShareIdDebug)! return UserDefaults(suiteName: Constants.groupShareIdDebug) ?? .standard
#else #else
return UserDefaults(suiteName: Constants.groupShareId)! return UserDefaults(suiteName: Constants.groupShareId) ?? .standard
#endif #endif
} }
} }
@@ -68,7 +68,7 @@ class Random {
static var existingDayFormat = [NSNumber: String]() static var existingDayFormat = [NSNumber: String]()
static func dayFormat(fromDate date: Date) -> String { static func dayFormat(fromDate date: Date) -> String {
let components = Calendar.current.dateComponents([.day], from: date) let components = Calendar.current.dateComponents([.day], from: date)
let day = components.day! let day = components.day ?? 1
let formatter = NumberFormatter() let formatter = NumberFormatter()
formatter.numberStyle = .ordinal formatter.numberStyle = .ordinal
@@ -411,18 +411,18 @@ final class DateFormattingCache {
extension Bundle { extension Bundle {
var appName: String { var appName: String {
return infoDictionary?["CFBundleName"] as! String return infoDictionary?["CFBundleName"] as? String ?? "Reflect"
} }
var bundleId: String { var bundleId: String {
return bundleIdentifier! return bundleIdentifier ?? "com.88oakapps.reflect"
} }
var versionNumber: String { var versionNumber: String {
return infoDictionary?["CFBundleShortVersionString"] as! String return infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0"
} }
var buildNumber: String { var buildNumber: String {
return infoDictionary?["CFBundleVersion"] as! String return infoDictionary?["CFBundleVersion"] as? String ?? "0"
} }
} }

View File

@@ -33,7 +33,8 @@ struct ReflectApp: App {
BGTaskScheduler.shared.cancelAllTaskRequests() BGTaskScheduler.shared.cancelAllTaskRequests()
BGTaskScheduler.shared.register(forTaskWithIdentifier: BGTask.updateDBMissingID, using: nil) { (task) in 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) UNUserNotificationCenter.current().setBadgeCount(0)

View File

@@ -90,11 +90,7 @@ class ShowBasedOnVoteLogics {
date = Calendar.current.date(byAdding: .day, value: -1, to: now) date = Calendar.current.date(byAdding: .day, value: -1, to: now)
} }
guard let date = date else { return date ?? now
fatalError("missing getCurrentVotingDate")
}
return date
} }
static public func getVotingTitle(onboardingData: OnboardingData, now: Date = Date()) -> String { 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") return String(localized: "add_mood_header_view_title_today")
case (false, .Previous): 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)) return String(format: String(localized: "add_mood_header_view_title"), Random.weekdayName(fromDate: date))
case (true, .Previous): case (true, .Previous):
return String(localized: "add_mood_header_view_title_yesterday") return String(localized: "add_mood_header_view_title_yesterday")

View File

@@ -478,9 +478,9 @@ struct EntryDetailView: View {
Button("Choose from Library") { Button("Choose from Library") {
showPhotoPicker = true showPhotoPicker = true
} }
if entry.photoID != nil { if let photoID = entry.photoID {
Button("Remove Photo", role: .destructive) { Button("Remove Photo", role: .destructive) {
_ = PhotoManager.shared.deletePhoto(id: entry.photoID!) _ = PhotoManager.shared.deletePhoto(id: photoID)
_ = DataController.shared.updatePhoto(forDate: entry.forDate, photoID: nil) _ = DataController.shared.updatePhoto(forDate: entry.forDate, photoID: nil)
} }
} }

View File

@@ -321,9 +321,12 @@ struct LiveActivityRecordingView: View {
exportPath = outputDir.path exportPath = outputDir.path
print("📁 Exporting frames to: \(exportPath)") print("📁 Exporting frames to: \(exportPath)")
// Export frames on background queue let target = targetStreak
DispatchQueue.global(qos: .userInitiated).async { let outDir = outputDir
for streak in 1...targetStreak { let outPath = exportPath
Task.detached(priority: .userInitiated) {
for streak in 1...target {
let mood = getMoodForStreak(streak) let mood = getMoodForStreak(streak)
let cardView = LiveActivityCardView( let cardView = LiveActivityCardView(
@@ -337,21 +340,21 @@ struct LiveActivityRecordingView: View {
if let uiImage = renderer.uiImage { if let uiImage = renderer.uiImage {
let filename = String(format: "frame_%04d.png", streak) let filename = String(format: "frame_%04d.png", streak)
let fileURL = outputDir.appendingPathComponent(filename) let fileURL = outDir.appendingPathComponent(filename)
if let pngData = uiImage.pngData() { if let pngData = uiImage.pngData() {
try? pngData.write(to: fileURL) try? pngData.write(to: fileURL)
} }
} }
DispatchQueue.main.async { await MainActor.run {
exportProgress = streak exportProgress = streak
} }
} }
DispatchQueue.main.async { await MainActor.run {
exportComplete = true exportComplete = true
print("✅ Export complete! \(targetStreak) frames saved to: \(exportPath)") print("✅ Export complete! \(target) frames saved to: \(outPath)")
} }
} }
} }

View File

@@ -1030,7 +1030,7 @@ struct SettingsContentView: View {
private var eulaButton: some View { private var eulaButton: some View {
Button(action: { Button(action: {
AnalyticsManager.shared.track(.eulaViewed) 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) UIApplication.shared.open(url)
} }
}, label: { }, label: {
@@ -1049,7 +1049,7 @@ struct SettingsContentView: View {
private var privacyButton: some View { private var privacyButton: some View {
Button(action: { Button(action: {
AnalyticsManager.shared.track(.privacyPolicyViewed) 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) UIApplication.shared.open(url)
} }
}, label: { }, label: {
@@ -1757,7 +1757,7 @@ struct SettingsView: View {
private var eulaButton: some View { private var eulaButton: some View {
Button(action: { Button(action: {
AnalyticsManager.shared.track(.eulaViewed) 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: { }, label: {
Text(String(localized: "settings_view_show_eula")) Text(String(localized: "settings_view_show_eula"))
.foregroundColor(textColor) .foregroundColor(textColor)
@@ -1772,7 +1772,7 @@ struct SettingsView: View {
private var privacyButton: some View { private var privacyButton: some View {
Button(action: { Button(action: {
AnalyticsManager.shared.track(.privacyPolicyViewed) 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: { }, label: {
Text(String(localized: "settings_view_show_privacy")) Text(String(localized: "settings_view_show_privacy"))
.foregroundColor(textColor) .foregroundColor(textColor)