Files
Calendarr-IOS/CalendarrWidgets/CalendarrTimelineProvider.swift
Scarriffle 7fa8dbdbdf Enable Mac Catalyst on both targets; make the widget intent parameter optional
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>
2026-08-10 16:57:10 +02:00

78 lines
3.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 }
}
}