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:
Trey T
2026-03-26 20:09:14 -05:00
parent 4d9e906c4d
commit 1f040ab676
41 changed files with 427 additions and 107 deletions

View File

@@ -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()
}