Files
Reflect/Shared/AppShortcuts.swift
Trey t 0442eab1f8 Rebrand entire project from Feels to Reflect
Complete rename across all bundle IDs, App Groups, CloudKit containers,
StoreKit product IDs, data store filenames, URL schemes, logger subsystems,
Swift identifiers, user-facing strings (7 languages), file names, directory
names, Xcode project, schemes, assets, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 11:47:16 -06:00

218 lines
7.0 KiB
Swift

//
// AppShortcuts.swift
// Reflect
//
// App Intents and Siri Shortcuts for voice-activated mood logging
//
import AppIntents
import SwiftUI
// MARK: - Mood Entity for App Intents
struct MoodEntity: AppEntity {
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Mood")
static var defaultQuery = MoodEntityQuery()
var id: Int
var name: String
var mood: Mood
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)")
}
static let allMoods: [MoodEntity] = [
MoodEntity(id: 0, name: "Horrible", mood: .horrible),
MoodEntity(id: 1, name: "Bad", mood: .bad),
MoodEntity(id: 2, name: "Average", mood: .average),
MoodEntity(id: 3, name: "Good", mood: .good),
MoodEntity(id: 4, name: "Great", mood: .great)
]
}
struct MoodEntityQuery: EntityQuery {
func entities(for identifiers: [Int]) async throws -> [MoodEntity] {
MoodEntity.allMoods.filter { identifiers.contains($0.id) }
}
func suggestedEntities() async throws -> [MoodEntity] {
MoodEntity.allMoods
}
func defaultResult() async -> MoodEntity? {
MoodEntity.allMoods.first { $0.mood == .average }
}
}
// MARK: - Log Mood Intent
struct LogMoodIntent: AppIntent {
static var title: LocalizedStringResource = "Log Mood"
static var description = IntentDescription("Record your mood for today in Reflect")
static var openAppWhenRun: Bool = false
@Parameter(title: "Mood")
var moodEntity: MoodEntity
static var parameterSummary: some ParameterSummary {
Summary("Log mood as \(\.$moodEntity)")
}
@MainActor
func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView {
let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
// Use centralized mood logger
MoodLogger.shared.logMood(moodEntity.mood, for: votingDate, entryType: .siri)
let moodTint = UserDefaultsStore.moodTintable()
let color = moodTint.color(forMood: moodEntity.mood)
return .result(
dialog: "Got it! Logged \(moodEntity.name) for today.",
view: MoodLoggedSnippetView(moodName: moodEntity.name, color: color)
)
}
}
// MARK: - Check Today's Mood Intent
struct CheckTodaysMoodIntent: AppIntent {
static var title: LocalizedStringResource = "Check Today's Mood"
static var description = IntentDescription("See what mood you logged today in Reflect")
static var openAppWhenRun: Bool = false
@MainActor
func perform() async throws -> some IntentResult & ProvidesDialog {
let votingDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
let dayStart = Calendar.current.startOfDay(for: votingDate)
let dayEnd = Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: dayStart)!
let todayEntry = DataController.shared.getData(startDate: dayStart, endDate: dayEnd, includedDays: []).first
if let entry = todayEntry, entry.mood != .missing && entry.mood != .placeholder {
return .result(dialog: "Today you logged feeling \(entry.mood.widgetDisplayName).")
} else {
return .result(dialog: "You haven't logged your mood today yet. Would you like to log it now?")
}
}
}
// MARK: - Get Mood Streak Intent
struct GetMoodStreakIntent: AppIntent {
static var title: LocalizedStringResource = "Get Mood Streak"
static var description = IntentDescription("Check your current mood logging streak")
static var openAppWhenRun: Bool = false
@MainActor
func perform() async throws -> some IntentResult & ProvidesDialog {
let streak = calculateStreak()
if streak == 0 {
return .result(dialog: "You don't have a streak yet. Log your mood today to start one!")
} else if streak == 1 {
return .result(dialog: "You have a 1 day streak. Keep it going!")
} else {
return .result(dialog: "Amazing! You have a \(streak) day streak. Keep it up!")
}
}
@MainActor
private func calculateStreak() -> 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
}
}
// MARK: - Snippet View for Mood Logged
struct MoodLoggedSnippetView: View {
let moodName: String
let color: Color
var body: some View {
HStack(spacing: 12) {
Circle()
.fill(color)
.frame(width: 44, height: 44)
.overlay {
Image(systemName: "checkmark")
.font(.title2.bold())
.foregroundColor(.white)
}
VStack(alignment: .leading, spacing: 2) {
Text("Mood Logged")
.font(.headline)
Text(moodName)
.font(.subheadline)
.foregroundColor(color)
}
Spacer()
}
.padding()
}
}
// MARK: - App Shortcuts Provider
struct ReflectShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: LogMoodIntent(),
phrases: [
"Log my mood in \(.applicationName)",
"Log mood as \(\.$moodEntity) in \(.applicationName)",
"Record my mood in \(.applicationName)",
"I'm feeling \(\.$moodEntity) in \(.applicationName)",
"Track my mood in \(.applicationName)"
],
shortTitle: "Log Mood",
systemImageName: "face.smiling"
)
AppShortcut(
intent: CheckTodaysMoodIntent(),
phrases: [
"What's my mood today in \(.applicationName)",
"Check today's mood in \(.applicationName)",
"How am I feeling in \(.applicationName)"
],
shortTitle: "Today's Mood",
systemImageName: "calendar"
)
AppShortcut(
intent: GetMoodStreakIntent(),
phrases: [
"What's my mood streak in \(.applicationName)",
"Check my streak in \(.applicationName)",
"How many days in a row in \(.applicationName)"
],
shortTitle: "Mood Streak",
systemImageName: "flame"
)
}
}