v1.1 polish: accessibility, error logging, localization, and code quality sweep
- Wrap 30+ production print() statements in #if DEBUG guards across 18 files - Add VoiceOver labels, hints, and traits to Watch app, Live Activities, widgets - Add .accessibilityAddTraits(.isButton) to 15+ onTapGesture views - Add text alternatives for color-only indicators (progress dots, mood circles) - Localize raw string literals in NoteEditorView, EntryDetailView, widgets - Replace 25+ silent try? with do/catch + AppLogger error logging - Replace hardcoded font sizes with semantic Dynamic Type fonts - Fix FIXME in IconPickerView (log icon change errors) - Extract magic animation delays to named constants across 8 files - Add widget empty state "Log your first mood!" messaging - Hide decorative images from VoiceOver, add labels to ColorPickers - Remove stale TODO in Color+Codable (alpha change deferred for migration) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
enum VotingLayoutStyle: Int, CaseIterable {
|
||||
case horizontal = 0 // Current: 5 buttons in a row
|
||||
@@ -177,6 +178,8 @@ enum DayViewStyle: Int, CaseIterable {
|
||||
}
|
||||
}
|
||||
|
||||
private let userDefaultsLogger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.88oakapps.reflect", category: "UserDefaults")
|
||||
|
||||
class UserDefaultsStore {
|
||||
enum Keys: String {
|
||||
case savedOnboardingData
|
||||
@@ -226,15 +229,18 @@ class UserDefaultsStore {
|
||||
}
|
||||
|
||||
// Decode and cache
|
||||
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.savedOnboardingData.rawValue) as? Data,
|
||||
let model = try? JSONDecoder().decode(OnboardingData.self, from: data) {
|
||||
cachedOnboardingData = model
|
||||
return model
|
||||
} else {
|
||||
let defaultData = OnboardingData()
|
||||
cachedOnboardingData = defaultData
|
||||
return defaultData
|
||||
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.savedOnboardingData.rawValue) as? Data {
|
||||
do {
|
||||
let model = try JSONDecoder().decode(OnboardingData.self, from: data)
|
||||
cachedOnboardingData = model
|
||||
return model
|
||||
} catch {
|
||||
userDefaultsLogger.error("Failed to decode onboarding data: \(error)")
|
||||
}
|
||||
}
|
||||
let defaultData = OnboardingData()
|
||||
cachedOnboardingData = defaultData
|
||||
return defaultData
|
||||
}
|
||||
|
||||
/// Invalidate cached onboarding data (call when data might have changed externally)
|
||||
@@ -251,7 +257,7 @@ class UserDefaultsStore {
|
||||
let data = try JSONEncoder().encode(onboardingData)
|
||||
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.savedOnboardingData.rawValue)
|
||||
} catch {
|
||||
print("Error saving onboarding: \(error)")
|
||||
userDefaultsLogger.error("Failed to encode onboarding data: \(error)")
|
||||
}
|
||||
|
||||
// Re-cache the saved data
|
||||
@@ -314,28 +320,38 @@ class UserDefaultsStore {
|
||||
}
|
||||
|
||||
static func getCustomWidgets() -> [CustomWidgetModel] {
|
||||
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.customWidget.rawValue) as? Data,
|
||||
let model = try? JSONDecoder().decode([CustomWidgetModel].self, from: data) {
|
||||
return model
|
||||
} else {
|
||||
GroupUserDefaults.groupDefaults.removeObject(forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
||||
|
||||
let widget = CustomWidgetModel.randomWidget
|
||||
widget.isSaved = true
|
||||
let widgets = [widget]
|
||||
|
||||
guard let data = try? JSONEncoder().encode(widgets) else {
|
||||
return widgets
|
||||
}
|
||||
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
||||
|
||||
if let savedData = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.customWidget.rawValue) as? Data,
|
||||
let models = try? JSONDecoder().decode([CustomWidgetModel].self, from: savedData) {
|
||||
return models.sorted { $0.createdDate < $1.createdDate }
|
||||
} else {
|
||||
return widgets
|
||||
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.customWidget.rawValue) as? Data {
|
||||
do {
|
||||
let model = try JSONDecoder().decode([CustomWidgetModel].self, from: data)
|
||||
return model
|
||||
} catch {
|
||||
userDefaultsLogger.error("Failed to decode custom widgets: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
GroupUserDefaults.groupDefaults.removeObject(forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
||||
|
||||
let widget = CustomWidgetModel.randomWidget
|
||||
widget.isSaved = true
|
||||
let widgets = [widget]
|
||||
|
||||
do {
|
||||
let data = try JSONEncoder().encode(widgets)
|
||||
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
||||
} catch {
|
||||
userDefaultsLogger.error("Failed to encode default custom widgets: \(error)")
|
||||
return widgets
|
||||
}
|
||||
|
||||
if let savedData = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.customWidget.rawValue) as? Data {
|
||||
do {
|
||||
let models = try JSONDecoder().decode([CustomWidgetModel].self, from: savedData)
|
||||
return models.sorted { $0.createdDate < $1.createdDate }
|
||||
} catch {
|
||||
userDefaultsLogger.error("Failed to decode saved custom widgets: \(error)")
|
||||
}
|
||||
}
|
||||
return widgets
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@@ -366,7 +382,7 @@ class UserDefaultsStore {
|
||||
let data = try JSONEncoder().encode(existingWidgets)
|
||||
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
||||
} catch {
|
||||
print("Error saving custom widget: \(error)")
|
||||
userDefaultsLogger.error("Failed to encode custom widget for save: \(error)")
|
||||
}
|
||||
return UserDefaultsStore.getCustomWidgets()
|
||||
}
|
||||
@@ -396,7 +412,7 @@ class UserDefaultsStore {
|
||||
let data = try JSONEncoder().encode(existingWidgets)
|
||||
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
||||
} catch {
|
||||
print("Error deleting custom widget: \(error)")
|
||||
userDefaultsLogger.error("Failed to encode custom widgets for delete: \(error)")
|
||||
}
|
||||
return UserDefaultsStore.getCustomWidgets()
|
||||
}
|
||||
@@ -407,7 +423,7 @@ class UserDefaultsStore {
|
||||
let model = try JSONDecoder().decode(SavedMoodTint.self, from: data)
|
||||
return model
|
||||
} catch {
|
||||
print(error)
|
||||
userDefaultsLogger.error("Failed to decode custom mood tint: \(error)")
|
||||
}
|
||||
}
|
||||
return SavedMoodTint()
|
||||
@@ -428,7 +444,7 @@ class UserDefaultsStore {
|
||||
let data = try JSONEncoder().encode(customTint)
|
||||
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customMoodTint.rawValue)
|
||||
} catch {
|
||||
print("Error saving custom mood tint: \(error)")
|
||||
userDefaultsLogger.error("Failed to encode custom mood tint: \(error)")
|
||||
}
|
||||
return UserDefaultsStore.getCustomMoodTint()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user