import Foundation /// Ein Termin, unabhängig davon, woher er stammt. public struct OnyxEvent: Identifiable, Equatable, Sendable { public let id: String public let title: String public let start: Date public let end: Date public let isAllDay: Bool /// `#RRGGBB` der Quelle. Kalenderfarben sind die eine Stelle, an der Onyx /// fremde Farben übernimmt statt eigene zu setzen — sonst erkennt man /// seine Kalender nicht wieder. public let colorHex: String? public let location: String? public let calendarID: String public let calendarTitle: String public init(id: String, title: String, start: Date, end: Date, isAllDay: Bool, colorHex: String?, location: String?, calendarID: String, calendarTitle: String) { self.id = id self.title = title self.start = start self.end = end self.isAllDay = isAllDay self.colorHex = colorHex self.location = location self.calendarID = calendarID self.calendarTitle = calendarTitle } } public struct OnyxCalendar: Identifiable, Equatable, Sendable { public let id: String public let title: String public let colorHex: String? public init(id: String, title: String, colorHex: String?) { self.id = id self.title = title self.colorHex = colorHex } } /// Termine **samt der Angabe, wofür überhaupt Daten vorliegen.** /// /// Das ist der Kern und nicht ein Detail: ein Kalender ohne Termine und ein /// Kalender, über den nichts bekannt ist, sehen in einer Liste identisch aus — /// nämlich leer. Ein leerer März ist überzeugend und schlicht falsch, wenn die /// Quelle nur 49 Tage abdeckt. `coverage` trennt beides. public struct EventWindow: Equatable, Sendable { public let events: [OnyxEvent] /// Zeitraum mit gesicherter Information. `nil` bedeutet unbegrenzt — /// so verhält sich der Apple-Kalender, weil er direkt abgefragt wird. public let coverage: DateInterval? /// Stand der Daten, falls die Quelle zwischengespeichert liefert. public let asOf: Date? public init(events: [OnyxEvent], coverage: DateInterval? = nil, asOf: Date? = nil) { self.events = events self.coverage = coverage self.asOf = asOf } public static let empty = EventWindow(events: []) } /// Warum gerade keine Termine da sind. Jeder Fall braucht eine eigene Antwort /// in der Oberfläche — „nichts anzuzeigen" ist keine. public enum CalendarSourceState: Equatable, Sendable { case ready(EventWindow) /// Berechtigung nicht erteilt. case denied /// Die Quelle wurde eingerichtet, hat aber noch nie geschrieben. case neverWritten /// Angemeldet sein wäre nötig. case loggedOut(asOf: Date?) /// Die Quelle ist neuer als das, was Onyx lesen kann. case incompatible(found: Int, maxSupported: Int) case unreadable(reason: String) } /// Der Berechtigungszustand einer Quelle. /// /// `undetermined` und `denied` müssen getrennt bleiben: im ersten Fall lohnt /// eine Anfrage, im zweiten zeigt macOS nie wieder einen Dialog und es hilft /// nur der Weg über die Systemeinstellungen. public enum CalendarAuthorization: Equatable, Sendable { case granted case denied case undetermined /// Die Quelle braucht gar keine Berechtigung (z. B. Calendarr). case notRequired } /// Eine Quelle für Termine. Apple-Kalender und Calendarr liefern beide dies. public protocol CalendarSource: Sendable { var id: String { get } /// Der Zustand — **ohne** dabei nachzufragen. Die Einstellungen brauchen /// das, um ihn anzuzeigen, ohne nebenbei einen Systemdialog auszulösen. var authorization: CalendarAuthorization { get } var isAuthorized: Bool { get } func requestAccess() async -> Bool func calendars() async -> [OnyxCalendar] func load(_ interval: DateInterval) async -> CalendarSourceState }