Add Apple Watch companion app with complications and WCSession sync

- Add watchOS app target with mood voting UI (5 mood buttons)
- Add WidgetKit complications (circular, corner, inline, rectangular)
- Add WatchConnectivityManager for bidirectional sync between iOS and watch
- iOS app acts as central coordinator - all mood logging flows through MoodLogger
- Watch votes send to iPhone via WCSession, iPhone logs and notifies watch back
- Widget votes use openAppWhenRun=true to run MoodLogger in main app process
- Add #if !os(watchOS) guards to Mood.swift and Random.swift for compatibility
- Update SKStoreReviewController to AppStore.requestReview (iOS 18 deprecation fix)
- Watch reads user's moodImages preference from GroupUserDefaults for emoji style

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-21 17:19:17 -06:00
parent d902694cdd
commit 224c00423a
20 changed files with 1148 additions and 57 deletions

View File

@@ -14,7 +14,9 @@ import AppIntents
struct VoteMoodIntent: AppIntent {
static var title: LocalizedStringResource = "Vote Mood"
static var description = IntentDescription("Record your mood for today")
static var openAppWhenRun: Bool { false }
// Run in main app process - enables full MoodLogger with watch sync
static var openAppWhenRun: Bool { true }
@Parameter(title: "Mood")
var moodValue: Int
@@ -32,30 +34,23 @@ struct VoteMoodIntent: AppIntent {
let mood = Mood(rawValue: moodValue) ?? .average
let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
// Widget uses simplified mood logging since it can't access HealthKitManager/TipsManager
// Full side effects (HealthKit sync, TipKit) will run when main app opens via MoodLogger
// This code runs in the main app process (openAppWhenRun = true)
// Use conditional compilation for widget extension to compile
#if !WIDGET_EXTENSION
// Main app: use MoodLogger for all side effects including watch sync
MoodLogger.shared.logMood(mood, for: votingDate, entryType: .widget)
#else
// Widget extension compilation path (never executed at runtime)
WidgetDataProvider.shared.add(mood: mood, forDate: votingDate, entryType: .widget)
WidgetCenter.shared.reloadAllTimelines()
#endif
// Store last voted date
let dateString = ISO8601DateFormatter().string(from: Calendar.current.startOfDay(for: votingDate))
GroupUserDefaults.groupDefaults.set(dateString, forKey: UserDefaultsStore.Keys.lastVotedDate.rawValue)
// Update Live Activity
let streak = calculateCurrentStreak()
LiveActivityManager.shared.updateActivity(streak: streak, mood: mood)
LiveActivityScheduler.shared.scheduleForNextDay()
// Reload widget timeline
WidgetCenter.shared.reloadTimelines(ofKind: "FeelsVoteWidget")
return .result()
}
@MainActor
private func calculateCurrentStreak() -> Int {
// Use WidgetDataProvider for read operations
return WidgetDataProvider.shared.getCurrentStreak()
}
}
// MARK: - Vote Widget Provider