2026-04-12 12:57:42 -07:00
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-12 16:16:32 -07:00
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
|
}
|
|
|
|
|
}
|