- Bundle IDs: com.tt.ifeel* → com.tt.feels* - App Groups: group.com.tt.ifeel* → group.com.tt.feels* - iCloud containers: iCloud.com.tt.ifeel* → iCloud.com.tt.feels* - IAP product IDs: com.tt.ifeel.IAP.* → com.tt.feels.IAP.* - URLs: ifeels.app → feels.app - Logger subsystems and dispatch queues - Product names and display names 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
125 lines
4.1 KiB
Swift
125 lines
4.1 KiB
Swift
//
|
|
// 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.feels.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
|