Supported Destinations now includes Mac (Mac Catalyst) on the app and the widget extension. Catalyst requires the iPad device family, so both targets move to TARGETED_DEVICE_FAMILY "1,2". The first Catalyst build failed on CalendarSelectionIntent: WidgetConfigurationIntent requires every @Parameter to be optional, and the macOS SDK enforces that where the iOS one lets a bare array through. selectedCalendars is now optional. Behaviour is unchanged — the timeline provider already treated an empty selection as "show all calendars", so nil folds into the same path. Verified: builds for both Mac Catalyst and iOS Simulator. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
78 lines
3.1 KiB
Swift
78 lines
3.1 KiB
Swift
import WidgetKit
|
||
import AppIntents
|
||
|
||
struct CalendarrEntry: TimelineEntry {
|
||
let date: Date
|
||
let snapshot: WidgetSnapshot?
|
||
}
|
||
|
||
struct CalendarrTimelineProvider: AppIntentTimelineProvider {
|
||
typealias Entry = CalendarrEntry
|
||
typealias Intent = CalendarSelectionIntent
|
||
|
||
func placeholder(in context: Context) -> CalendarrEntry {
|
||
CalendarrEntry(date: .now, snapshot: WidgetStore.read())
|
||
}
|
||
|
||
func snapshot(for configuration: CalendarSelectionIntent, in context: Context) async -> CalendarrEntry {
|
||
let raw = WidgetStore.read()
|
||
return CalendarrEntry(date: .now, snapshot: filtered(raw, by: configuration))
|
||
}
|
||
|
||
func timeline(for configuration: CalendarSelectionIntent, in context: Context) async -> Timeline<CalendarrEntry> {
|
||
let raw = WidgetStore.read()
|
||
let snap = filtered(raw, by: configuration)
|
||
let now = Date()
|
||
|
||
// One entry per hour for 24 h so the widget re-renders as time advances.
|
||
var entries: [CalendarrEntry] = []
|
||
for h in 0..<24 {
|
||
let date = Calendar.current.date(byAdding: .hour, value: h, to: now) ?? now
|
||
entries.append(CalendarrEntry(date: date, snapshot: snap))
|
||
}
|
||
let refreshAt = Calendar.current.date(byAdding: .minute, value: 30, to: now) ?? now
|
||
return Timeline(entries: entries, policy: .after(refreshAt))
|
||
}
|
||
|
||
// MARK: – Filtering
|
||
|
||
private func filtered(_ snapshot: WidgetSnapshot?, by config: CalendarSelectionIntent) -> WidgetSnapshot? {
|
||
guard let snapshot else { return nil }
|
||
let ids = (config.selectedCalendars ?? []).map { $0.id }
|
||
guard !ids.isEmpty else { return snapshot } // nothing selected = show all
|
||
let keep = Set(ids)
|
||
return WidgetSnapshot(
|
||
writtenAt: snapshot.writtenAt,
|
||
events: snapshot.events.filter { keep.contains($0.calendarKey) },
|
||
todayColorHex: snapshot.todayColorHex,
|
||
textColorHex: snapshot.textColorHex,
|
||
backgroundColorHex: snapshot.backgroundColorHex,
|
||
lineColorHex: snapshot.lineColorHex,
|
||
primaryColorHex: snapshot.primaryColorHex,
|
||
accentColorHex: snapshot.accentColorHex,
|
||
language: snapshot.language
|
||
)
|
||
}
|
||
}
|
||
|
||
// MARK: – Shared helpers used by all widget views
|
||
|
||
enum WidgetHelpers {
|
||
static func events(for day: Date, in snapshot: WidgetSnapshot) -> [WidgetEvent] {
|
||
let cal = Calendar.current
|
||
let dayStart = cal.startOfDay(for: day)
|
||
let dayEnd = cal.date(byAdding: .day, value: 1, to: dayStart) ?? dayStart
|
||
return snapshot.events
|
||
.filter { $0.start < dayEnd && $0.end > dayStart }
|
||
.sorted { $0.start < $1.start }
|
||
}
|
||
|
||
static func upcoming(from now: Date, daysAhead: Int, in snapshot: WidgetSnapshot) -> [WidgetEvent] {
|
||
let cal = Calendar.current
|
||
let end = cal.date(byAdding: .day, value: daysAhead, to: cal.startOfDay(for: now)) ?? now
|
||
return snapshot.events
|
||
.filter { $0.end > now && $0.start < end }
|
||
.sorted { $0.start < $1.start }
|
||
}
|
||
}
|