Files
onyx/Packages/OnyxKit/Sources/CalendarProvider/CalendarSourceSettings.swift
T
ScarriffleandClaude Opus 5 edb327cabe Kacheln und Fenster sprechen dieselbe Sprache
Alle Kacheln, Popovers und Fenster auf die Designschicht umgestellt: rund
hundert gesetzte Schriftgrößen und 159 gesetzte Abstände und Radien sind jetzt
Token. Übrig bleibt `spacing: 0` — das ist eine Aussage, keine geratene Zahl.

Die Einstellungen bekommen eine Seitenleiste statt sieben Reitern, deren
deutsche Beschriftungen zusammen 880 Punkte brauchten. Panel-Token stehen nicht
mehr in Fenstern: dort gilt Systemsemantik, damit der Hellmodus nicht schwarz
auf schwarz endet.

Neu ist ein Schaufenster, das alle Bauteile nebeneinander zeigt — ohne das
lässt sich eine Änderung an einem Token nur prüfen, indem man die App bedient,
bis jede Kachel einmal zu sehen war.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:05:51 +02:00

108 lines
4.2 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 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: Onyx.Space.xs) {
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: Onyx.Space.xxs) {
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)))
}
}