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:
@@ -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;
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user