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>
218 lines
7.0 KiB
Swift
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"
|
|
)
|
|
}
|
|
}
|
|
|