From 36523334c4d7ab0e05fb05b80fc21296bed5bc24 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Fri, 27 Feb 2026 22:01:46 -0800 Subject: [PATCH] =?UTF-8?q?feat(siri):=20add=20App=20Intents=20for=20Siri?= =?UTF-8?q?=20Shortcuts=20=E2=80=94=20set=20timer,=20start=20pomodoro,=20n?= =?UTF-8?q?ext=20timer=20info?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SiriShortcuts/TimerAppIntents.swift | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 ios/ChronoMind/SiriShortcuts/TimerAppIntents.swift diff --git a/ios/ChronoMind/SiriShortcuts/TimerAppIntents.swift b/ios/ChronoMind/SiriShortcuts/TimerAppIntents.swift new file mode 100644 index 0000000..bdd370d --- /dev/null +++ b/ios/ChronoMind/SiriShortcuts/TimerAppIntents.swift @@ -0,0 +1,167 @@ +// ── Siri Shortcuts (App Intents) ────────────────────────────── +// "Set a timer for 25 minutes", "Start Pomodoro", etc. +// iOS 17+ App Intents framework + +import AppIntents +import Foundation + +// MARK: - Set Timer Intent + +struct SetTimerIntent: AppIntent { + static var title: LocalizedStringResource = "Set a Timer" + static var description: IntentDescription = "Creates a countdown timer for the specified duration" + static var openAppWhenRun: Bool = false + + @Parameter(title: "Duration (minutes)", default: 25) + var minutes: Int + + @Parameter(title: "Label", default: "Timer") + var label: String + + static var parameterSummary: some ParameterSummary { + Summary("Set a \(\.$minutes) minute timer called \(\.$label)") + } + + func perform() async throws -> some IntentResult & ProvidesDialog { + let now = Date() + let targetTime = now.addingTimeInterval(TimeInterval(minutes * 60)) + let intervals = CascadePreset.minimal.defaultIntervals + + let snapshot = TimerSnapshot( + id: UUID().uuidString, + label: label, + type: .countdown, + urgency: .standard, + state: .active, + targetTime: targetTime, + duration: TimeInterval(minutes * 60), + startedAt: now, + elapsedBeforePause: 0, + snoozeCount: 0, + category: nil, + pomodoroCurrentRound: nil, + pomodoroTotalRounds: nil, + pomodoroIsBreak: nil, + nextWarningTime: intervals.first.map { targetTime.addingTimeInterval(-Double($0) * 60) }, + totalWarnings: intervals.count, + firedWarnings: 0 + ) + + // Write to shared data + let manager = SharedTimerDataManager.shared + var timers = manager.readSnapshots() + timers.append(snapshot) + manager.writeSnapshots(timers) + manager.writeActiveTimer(nil) // Let iOS app recalculate + + return .result(dialog: "Timer set for \(minutes) minutes: \(label)") + } +} + +// MARK: - Start Pomodoro Intent + +struct StartPomodoroIntent: AppIntent { + static var title: LocalizedStringResource = "Start Pomodoro" + static var description: IntentDescription = "Starts a 25-minute Pomodoro focus session" + static var openAppWhenRun: Bool = true + + @Parameter(title: "Work Minutes", default: 25) + var workMinutes: Int + + static var parameterSummary: some ParameterSummary { + Summary("Start a \(\.$workMinutes) minute Pomodoro session") + } + + func perform() async throws -> some IntentResult & ProvidesDialog { + let now = Date() + let durationSeconds = TimeInterval(workMinutes * 60) + let targetTime = now.addingTimeInterval(durationSeconds) + + let snapshot = TimerSnapshot( + id: UUID().uuidString, + label: "Pomodoro Focus", + type: .pomodoro, + urgency: .standard, + state: .active, + targetTime: targetTime, + duration: durationSeconds, + startedAt: now, + elapsedBeforePause: 0, + snoozeCount: 0, + category: nil, + pomodoroCurrentRound: 1, + pomodoroTotalRounds: 4, + pomodoroIsBreak: false, + nextWarningTime: targetTime.addingTimeInterval(-60), + totalWarnings: 1, + firedWarnings: 0 + ) + + let manager = SharedTimerDataManager.shared + var timers = manager.readSnapshots() + timers.append(snapshot) + manager.writeSnapshots(timers) + + return .result(dialog: "Pomodoro started: \(workMinutes) minutes of focus time!") + } +} + +// MARK: - What's My Next Timer Intent + +struct NextTimerInfoIntent: AppIntent { + static var title: LocalizedStringResource = "What's My Next Timer?" + static var description: IntentDescription = "Tells you about your next upcoming timer" + static var openAppWhenRun: Bool = false + + func perform() async throws -> some IntentResult & ProvidesDialog { + let manager = SharedTimerDataManager.shared + guard let next = manager.readNextFiringTimer() else { + return .result(dialog: "You don't have any active timers right now.") + } + + let remaining = next.remainingSeconds() + let compact = formatDurationCompact(remaining) + let timeStr = formatTime(next.targetTime) + + return .result(dialog: "\(next.label) fires in \(compact), at \(timeStr).") + } +} + +// MARK: - Shortcuts Provider + +struct ChronoMindShortcutsProvider: AppShortcutsProvider { + static var appShortcuts: [AppShortcut] { + AppShortcut( + intent: SetTimerIntent(), + phrases: [ + "Set a timer in \(.applicationName)", + "Set a \(\.$minutes) minute timer in \(.applicationName)", + "Start a \(\.$minutes) minute timer called \(\.$label) in \(.applicationName)", + ], + shortTitle: "Set Timer", + systemImageName: "timer" + ) + + AppShortcut( + intent: StartPomodoroIntent(), + phrases: [ + "Start Pomodoro in \(.applicationName)", + "Start a focus session in \(.applicationName)", + "Start a \(\.$workMinutes) minute Pomodoro in \(.applicationName)", + ], + shortTitle: "Start Pomodoro", + systemImageName: "target" + ) + + AppShortcut( + intent: NextTimerInfoIntent(), + phrases: [ + "What's my next timer in \(.applicationName)", + "When's my next alarm in \(.applicationName)", + "Next timer in \(.applicationName)", + ], + shortTitle: "Next Timer", + systemImageName: "clock" + ) + } +}