Files
onyx/Packages/OnyxKit/Sources/CalendarProvider/CalendarSourceSettings.swift
Scarriffle 2aa60eedf9 Farben einstellbar — und erst mal überhaupt eine Farbe
Das Grün, das überall auftauchte, war die System-Akzentfarbe: `.tint` und
`.accentColor` werden ohne eigene Angabe dorthin durchgereicht, und das
betraf alle Popovers und das ganze Einstellungsfenster. Die Notch-Widgets
dagegen benutzten Onyx' eigenes Blau. Zwei Farben, ungewollt.

Jetzt eine, und die ist einstellbar. Ein neuer Reiter „Farben": Akzentfarbe
mit sechs Vorschlägen und einem Knopf, der die Systemfarbe übernimmt, dazu
das Paar für Herunter- und Hochladen samt Tauschknopf.

Die Signalfarben bleiben fest und stehen nur zur Ansicht dort. Gelb heißt
Warnung, Rot heißt kritisch, Grün heißt in Ordnung — bei Temperatur, Akku,
Lüfter und Fehlern. Wer sie umstellen kann, kann ihre Bedeutung zerstören;
ein rotes „alles gut" liest niemand richtig. Sichtbar sind sie trotzdem,
sonst sucht man die Warnfarbe im Reiter und findet sie nirgends.

Umgesetzt über die Tokens statt über vierzig Fundstellen: `Onyx.Color.accent`
ist keine Konstante mehr, sondern liest bei jedem Zugriff aus `OnyxTheme`.
Damit merkt SwiftUI die Abhängigkeit und zeichnet neu, sobald die Farbe sich
ändert. Dazu ein `.tint` an den vier Wurzeln — Panel, Popovers, Einstellungen,
Einrichtung —, damit auch Haken, Regler und Auswahlfelder mitziehen.

Nebenbei die Beschriftung: „Je Programm" heißt jetzt „Programme". Großgesetzt
las sich das als englisches „J-E".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 23:24:40 +02:00

108 lines
4.2 KiB
Swift
Raw Permalink 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: 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)))
}
}