import AppIntents import WidgetKit // ────────────────────────────────────────────────────────────────────── // WidgetIntents.swift // TARGET: Widget extension only. // // iOS 17+ interactive widget controls via AppIntents. // Each intent writes a command to App Group UserDefaults, optimistically // updates the widget state, posts a Darwin notification to wake the app, // then reloads timelines so the widget re-renders immediately. // ────────────────────────────────────────────────────────────────────── // MARK: - Play / Pause struct PlayPauseIntent: AppIntent { static var title: LocalizedStringResource = "Toggle Playback" static var description: IntentDescription = "Play or pause the current track." func perform() async throws -> some IntentResult { let state = WidgetSharedState.shared // Optimistic toggle — widget re-renders with new state instantly state.togglePlayingOptimistic() // Enqueue command for the main app let wasPlaying = !state.isPlaying // we already toggled state.enqueueCommand(wasPlaying ? .pause : .play) postDarwinNotification(kWidgetCommandNotification) WidgetCenter.shared.reloadAllTimelines() return .result() } } // MARK: - Next Track struct NextTrackIntent: AppIntent { static var title: LocalizedStringResource = "Next Track" static var description: IntentDescription = "Skip to the next track." func perform() async throws -> some IntentResult { let state = WidgetSharedState.shared state.enqueueCommand(.next) postDarwinNotification(kWidgetCommandNotification) WidgetCenter.shared.reloadAllTimelines() return .result() } } // MARK: - Previous Track struct PreviousTrackIntent: AppIntent { static var title: LocalizedStringResource = "Previous Track" static var description: IntentDescription = "Go to the previous track." func perform() async throws -> some IntentResult { let state = WidgetSharedState.shared state.enqueueCommand(.previous) postDarwinNotification(kWidgetCommandNotification) WidgetCenter.shared.reloadAllTimelines() return .result() } } // MARK: - Seek Forward (+15s) struct SeekForwardIntent: AppIntent { static var title: LocalizedStringResource = "Seek Forward" static var description: IntentDescription = "Skip forward 15 seconds." func perform() async throws -> some IntentResult { let state = WidgetSharedState.shared state.enqueueCommand(.seekForward15) postDarwinNotification(kWidgetCommandNotification) WidgetCenter.shared.reloadAllTimelines() return .result() } } // MARK: - Seek Backward (-15s) struct SeekBackwardIntent: AppIntent { static var title: LocalizedStringResource = "Seek Backward" static var description: IntentDescription = "Skip backward 15 seconds." func perform() async throws -> some IntentResult { let state = WidgetSharedState.shared state.enqueueCommand(.seekBackward15) postDarwinNotification(kWidgetCommandNotification) WidgetCenter.shared.reloadAllTimelines() return .result() } } // MARK: - Seek To Position (tap-to-seek on progress bar) struct SeekToIntent: AppIntent { static var title: LocalizedStringResource = "Seek To Position" static var description: IntentDescription = "Seek to a specific position in the track." @Parameter(title: "Fraction") var fraction: Double init() { self.fraction = 0 } init(fraction: Double) { self.fraction = fraction } func perform() async throws -> some IntentResult { let state = WidgetSharedState.shared let targetTime = state.duration * min(max(fraction, 0), 1) state.enqueueSeekTo(time: targetTime) // Optimistically update position so widget shows new location instantly state.pushPosition(currentTime: targetTime, isPlaying: state.isPlaying) postDarwinNotification(kWidgetCommandNotification) WidgetCenter.shared.reloadAllTimelines() return .result() } }