import SwiftUI import OnyxDesign /// Die Wahl der Terminquelle — samt ehrlicher Auskunft darüber, was Calendarr /// gerade liefert. /// /// Der Zustandstext ist kein Beiwerk: bleibt das Widget leer, ist genau hier /// die Antwort auf das Warum. Ohne ihn sieht „Calendarr nie geöffnet" genauso /// aus wie „keine Termine". public struct CalendarSourceSettings: View { @Bindable private var model: CalendarModel @State private var calendarrState: CalendarSourceState? public init(model: CalendarModel) { self.model = model } public var body: some View { Form { Section { Picker(selection: $model.kind) { ForEach(CalendarSourceKind.allCases, id: \.self) { kind in Text(kind.localizedName).tag(kind) } } label: { Text("calendar.source.title", bundle: .module) } .pickerStyle(.radioGroup) } if model.kind != .appleCalendar { Section { LabeledContent { VStack(alignment: .leading, spacing: 4) { statusText Text("calendar.source.readOnly", bundle: .module) .font(.caption) .foregroundStyle(.tertiary) .fixedSize(horizontal: false, vertical: true) } } label: { Text(verbatim: "Calendarr") } } } } .formStyle(.grouped) .task(id: model.kind) { await loadCalendarrState() } } @ViewBuilder private var statusText: some View { switch calendarrState { case .ready(let window): VStack(alignment: .leading, spacing: 2) { Text("calendar.source.status.ready \(window.events.count.formatted()) \(asOfText(window.asOf))", bundle: .module) if let coverage = window.coverage { // Die Grenze der Abdeckung gehört sichtbar hin: dahinter // zeigt Onyx bewusst „unbekannt" statt „frei", und das // soll niemand für einen Fehler halten. Text("calendar.source.status.coverage \(coverage.end.formatted(date: .abbreviated, time: .omitted))", bundle: .module) .foregroundStyle(.secondary) } } .font(.callout) case .neverWritten: notice("calendar.source.status.neverWritten") case .loggedOut: notice("calendar.source.status.loggedOut") case .incompatible: notice("calendar.source.status.incompatible") case .unreadable(let reason): Text("calendar.source.status.unreadable \(reason)", bundle: .module) .font(.callout).foregroundStyle(Onyx.Color.warning) .fixedSize(horizontal: false, vertical: true) case .denied, .none: ProgressView().controlSize(.small) } } private func notice(_ key: String.LocalizationValue) -> some View { Text(String(localized: key, bundle: .module)) .font(.callout) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } private func asOfText(_ date: Date?) -> String { date?.formatted(date: .abbreviated, time: .shortened) ?? "–" } private func loadCalendarrState() async { guard model.kind != .appleCalendar else { return } // Bewusst über die Quelle selbst und nicht über das Modell: hier soll // der Zustand von Calendarr allein stehen, nicht die Mischung aus // beiden Quellen. Sonst verdeckt ein funktionierender Apple-Kalender, // dass Calendarr gar nichts liefert. let source = CalendarrSource() let now = Date() calendarrState = await source.load( DateInterval(start: now.addingTimeInterval(-14 * 86_400), end: now.addingTimeInterval(60 * 86_400))) } }