// // MoodLogger.swift // Feels // // Centralized mood logging service that handles all side effects // import Foundation import WidgetKit /// Centralized service for logging moods with all associated side effects. /// All mood entry points should use this service to ensure consistent behavior. @MainActor final class MoodLogger { static let shared = MoodLogger() private init() {} /// Log a mood entry with all associated side effects. /// This is the single source of truth for mood logging in the app. /// /// - Parameters: /// - mood: The mood to log /// - date: The date for the mood entry /// - entryType: The source of the mood entry (header, widget, siri, etc.) /// - syncHealthKit: Whether to sync to HealthKit (default true, but widget can't access HealthKit) /// - updateTips: Whether to update TipKit parameters (default true, but widget can't access TipKit) func logMood( _ mood: Mood, for date: Date, entryType: EntryType, syncHealthKit: Bool = true, updateTips: Bool = true ) { // 1. Add mood entry to data store DataController.shared.add(mood: mood, forDate: date, entryType: entryType) // Skip side effects for placeholder/missing moods guard mood != .missing && mood != .placeholder else { return } // 2. Sync to HealthKit if enabled and requested if syncHealthKit { let healthKitEnabled = GroupUserDefaults.groupDefaults.bool(forKey: UserDefaultsStore.Keys.healthKitEnabled.rawValue) if healthKitEnabled { Task { try? await HealthKitManager.shared.saveMood(mood, for: date) } } } // 3. Calculate current streak for Live Activity and TipKit let streak = calculateCurrentStreak() // 4. Update Live Activity LiveActivityManager.shared.updateActivity(streak: streak, mood: mood) LiveActivityScheduler.shared.scheduleForNextDay() // 5. Update TipKit parameters if requested if updateTips { TipsManager.shared.onMoodLogged() TipsManager.shared.updateStreak(streak) } // 6. Request app review at moments of delight ReviewRequestManager.shared.onMoodLogged(streak: streak) // 7. Reload widgets WidgetCenter.shared.reloadAllTimelines() // 8. Notify watch to refresh complications WatchConnectivityManager.shared.notifyWatchToReload() } /// Calculate the current mood streak private func calculateCurrentStreak() -> Int { var streak = 0 var checkDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding()) while true { let dayStart = Calendar.current.startOfDay(for: checkDate) let dayEnd = Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: dayStart)! let entry = DataController.shared.getData(startDate: dayStart, endDate: dayEnd, includedDays: []).first if let entry = entry, entry.mood != .missing && entry.mood != .placeholder { streak += 1 checkDate = Calendar.current.date(byAdding: .day, value: -1, to: checkDate)! } else { break } } return streak } }