Fix boundary bugs, route side effects through MoodLogger, add data listeners
- Fix ExtensionDataProvider <= boundary to < in date queries (prevented cross-day leaks) - Replace force-unwraps with guards and add error logging in DataControllerGET and ExtensionDataProvider - Route DayViewViewModel update/delete through MoodLogger.shared (was duplicating side effects) - Add data listeners to InsightsViewModel and YearViewModel for cross-tab refresh - Add HealthKitManager.deleteMood(for:) for single-date cleanup - Add SharedModelContainer.isUsingInMemoryFallback flag with critical logging - Add analytics events: entryDeleted, allDataCleared, duplicatesRemoved, storageFallbackActivated Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,6 @@
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import WidgetKit
|
||||
|
||||
@MainActor
|
||||
class DayViewViewModel: ObservableObject {
|
||||
@@ -61,25 +60,8 @@ class DayViewViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
public func update(entry: MoodEntryModel, toMood mood: Mood) {
|
||||
if !DataController.shared.update(entryDate: entry.forDate, withMood: mood) {
|
||||
if !MoodLogger.shared.updateMood(entryDate: entry.forDate, withMood: mood) {
|
||||
print("Failed to update mood entry")
|
||||
return
|
||||
}
|
||||
|
||||
// Sync to HealthKit for past day updates (only if user has full access)
|
||||
guard mood != .missing && mood != .placeholder else { return }
|
||||
|
||||
let healthKitEnabled = GroupUserDefaults.groupDefaults.bool(forKey: UserDefaultsStore.Keys.healthKitEnabled.rawValue)
|
||||
let hasAccess = !IAPManager.shared.shouldShowPaywall
|
||||
if healthKitEnabled && hasAccess {
|
||||
Task {
|
||||
try? await HealthKitManager.shared.saveMood(mood, for: entry.forDate)
|
||||
}
|
||||
}
|
||||
|
||||
// Reload widgets asynchronously to avoid UI delay
|
||||
Task { @MainActor in
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,13 +77,9 @@ class DayViewViewModel: ObservableObject {
|
||||
entriesToDelete.append(obj)
|
||||
}
|
||||
entriesToDelete.forEach({ entry in
|
||||
let entryDate = entry.forDate
|
||||
DataController.shared.modelContext.delete(entry)
|
||||
self.add(mood: .missing, forDate: entryDate, entryType: .listView)
|
||||
MoodLogger.shared.deleteMood(forDate: entry.forDate)
|
||||
})
|
||||
}
|
||||
|
||||
DataController.shared.save()
|
||||
}
|
||||
|
||||
static func updateTitleHeader(forEntry entry: MoodEntryModel?) -> String {
|
||||
|
||||
@@ -51,6 +51,19 @@ class InsightsViewModel: ObservableObject {
|
||||
|
||||
init() {
|
||||
isAIAvailable = insightService.isAvailable
|
||||
|
||||
DataController.shared.addNewDataListener { [weak self] in
|
||||
self?.onDataChanged()
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when mood data changes in another tab. Invalidates cached insights
|
||||
/// so they are regenerated with fresh data on next view appearance.
|
||||
private func onDataChanged() {
|
||||
insightService.invalidateCache()
|
||||
monthLoadingState = .idle
|
||||
yearLoadingState = .idle
|
||||
allTimeLoadingState = .idle
|
||||
}
|
||||
|
||||
// MARK: - Public Methods
|
||||
|
||||
@@ -25,9 +25,19 @@ class YearViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
init() {
|
||||
DataController.shared.addNewDataListener { [weak self] in
|
||||
self?.refreshData()
|
||||
}
|
||||
updateData()
|
||||
}
|
||||
|
||||
/// Re-fetch data using the current date range. Called by the data listener
|
||||
/// when mood entries change in other tabs.
|
||||
public func refreshData() {
|
||||
updateData()
|
||||
filterEntries(startDate: entryStartDate, endDate: entryEndDate)
|
||||
}
|
||||
|
||||
private func updateData() {
|
||||
let filteredEntries = DataController.shared.getData(startDate: Date(timeIntervalSince1970: 0),
|
||||
endDate: Date(),
|
||||
|
||||
Reference in New Issue
Block a user