Consolidate extension data providers and add side effects catch-up
- Create unified ExtensionDataProvider for Widget and Watch targets - Remove duplicate WatchDataProvider and WatchConnectivityManager from Watch App - Add side effects catch-up mechanism in MoodLogger for widget votes - Process pending side effects on app launch and midnight background task - Reduce ~450 lines of duplicated code across targets 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
124
Shared/SharedMoodIntent.swift
Normal file
124
Shared/SharedMoodIntent.swift
Normal file
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// SharedMoodIntent.swift
|
||||
// Feels
|
||||
//
|
||||
// Single VoteMoodIntent for all targets.
|
||||
// Main app uses ForegroundContinuableIntent to run widget intents in app process.
|
||||
//
|
||||
// Add this file to ALL targets: Feels (iOS), FeelsWidgetExtension, Feels Watch App
|
||||
//
|
||||
|
||||
import AppIntents
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import WidgetKit
|
||||
import os.log
|
||||
|
||||
// MARK: - Vote Mood Intent
|
||||
|
||||
struct VoteMoodIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Vote Mood"
|
||||
static var description = IntentDescription("Record your mood for today")
|
||||
|
||||
static var openAppWhenRun: Bool = false
|
||||
|
||||
@Parameter(title: "Mood")
|
||||
var moodValue: Int
|
||||
|
||||
init() {
|
||||
self.moodValue = 2
|
||||
}
|
||||
|
||||
init(mood: Mood) {
|
||||
self.moodValue = mood.rawValue
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func perform() async throws -> some IntentResult {
|
||||
let mood = Mood(rawValue: moodValue) ?? .average
|
||||
|
||||
#if os(watchOS)
|
||||
// Watch: Send to iPhone via WatchConnectivity
|
||||
let date = Date()
|
||||
_ = WatchConnectivityManager.shared.sendMoodToPhone(mood: moodValue, date: date)
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
|
||||
#elseif WIDGET_EXTENSION
|
||||
// Widget: Save to shared container, main app handles side effects
|
||||
let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
|
||||
WidgetMoodSaver.save(mood: mood, date: votingDate)
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
|
||||
#else
|
||||
// Main app: Full logging with all side effects
|
||||
let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
|
||||
MoodLogger.shared.logMood(mood, for: votingDate, entryType: .widget)
|
||||
|
||||
let dateString = ISO8601DateFormatter().string(from: Calendar.current.startOfDay(for: votingDate))
|
||||
GroupUserDefaults.groupDefaults.set(dateString, forKey: UserDefaultsStore.Keys.lastVotedDate.rawValue)
|
||||
#endif
|
||||
|
||||
return .result()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Main App: Run widget intents in app process
|
||||
|
||||
#if !WIDGET_EXTENSION && !os(watchOS)
|
||||
extension VoteMoodIntent: ForegroundContinuableIntent {}
|
||||
#endif
|
||||
|
||||
// MARK: - Widget: Save to shared container
|
||||
|
||||
#if WIDGET_EXTENSION
|
||||
enum WidgetMoodSaver {
|
||||
private static let logger = Logger(subsystem: "com.tt.ifeel.widget", category: "WidgetMoodSaver")
|
||||
|
||||
@MainActor
|
||||
static func save(mood: Mood, date: Date) {
|
||||
let schema = Schema([MoodEntryModel.self])
|
||||
let appGroupID = Constants.currentGroupShareId
|
||||
|
||||
guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID) else {
|
||||
logger.error("App Group not available")
|
||||
return
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
let storeURL = containerURL.appendingPathComponent("Feels-Debug.store")
|
||||
#else
|
||||
let storeURL = containerURL.appendingPathComponent("Feels.store")
|
||||
#endif
|
||||
|
||||
do {
|
||||
let config = ModelConfiguration(schema: schema, url: storeURL, cloudKitDatabase: .none)
|
||||
let container = try ModelContainer(for: schema, configurations: [config])
|
||||
let context = container.mainContext
|
||||
|
||||
// Delete existing entry for this date
|
||||
let startDate = Calendar.current.startOfDay(for: date)
|
||||
let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)!
|
||||
|
||||
var descriptor = FetchDescriptor<MoodEntryModel>(
|
||||
predicate: #Predicate { entry in
|
||||
entry.forDate >= startDate && entry.forDate <= endDate
|
||||
}
|
||||
)
|
||||
descriptor.fetchLimit = 1
|
||||
|
||||
if let existing = try? context.fetch(descriptor).first {
|
||||
context.delete(existing)
|
||||
try? context.save()
|
||||
}
|
||||
|
||||
// Create new entry
|
||||
let entry = MoodEntryModel(forDate: date, mood: mood, entryType: .widget)
|
||||
context.insert(entry)
|
||||
try context.save()
|
||||
logger.info("Saved mood \(mood.rawValue) from widget")
|
||||
} catch {
|
||||
logger.error("Failed to save mood: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user