Phase 3: Kalender-Widget mit echten EventKit-Daten

Die Unterscheidung zwischen "keine Termine" und "keine Information" steckt von
Anfang an in den Grundlagen, nicht als Nachtrag für Calendarr. 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.

EventWindow trägt deshalb eine coverage mit. Bei EventKit ist sie der
angefragte Zeitraum, weil direkt abgefragt wird; bei Calendarr wird sie später
aus dem Snapshot kommen. Tage außerhalb werden im Mini-Monat ausgegraut und
tragen einen erklärenden Hinweis, statt als frei durchzugehen.

Die Abdeckung gewinnt auch gegen widersprüchliche Eingaben: liefert eine Quelle
einen Termin für einen Tag, den sie nach eigener Angabe nicht abdeckt, bleibt
der Tag unbekannt. Sonst behauptet die Anzeige mehr zu wissen, als belegt ist.
Als Test festgehalten.

CalendarSourceState statt eines Optionals: denied, neverWritten, loggedOut,
incompatible und unreadable bekommen jeweils eine eigene Antwort in der
Oberfläche. "Nichts anzuzeigen" ist keine.

Serientermine bekommen eine aus Kennung und Beginn zusammengesetzte ID —
EKEvent.eventIdentifier ist für alle Vorkommen gleich, eine Terminliste mit
einer täglichen Serie fiele sonst auf einen Eintrag zusammen.

Monatsraster testgetrieben, inklusive Schaltjahr, Wochenstart Montag wie
Sonntag und der Regel, dass das Ende der Abdeckung nicht mehr dazugehört.

93 Tests grün.
This commit is contained in:
Guido Schmit
2026-08-10 18:07:44 +02:00
parent dd25d788c0
commit 70d4d67cca
11 changed files with 813 additions and 3 deletions

View File

@@ -0,0 +1,62 @@
{
"sourceLanguage" : "en",
"strings" : {
"widget.calendar.name" : {
"localizations" : {
"de" : { "stringUnit" : { "state" : "translated", "value" : "Kalender" } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "Calendar" } }
}
},
"widget.calendar.noUpcoming" : {
"comment" : "Nur zeigen, wenn der Zeitraum wirklich abgedeckt ist",
"localizations" : {
"de" : { "stringUnit" : { "state" : "translated", "value" : "Nichts mehr für heute" } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "Nothing left today" } }
}
},
"widget.calendar.allDay" : {
"localizations" : {
"de" : { "stringUnit" : { "state" : "translated", "value" : "Ganztägig" } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "All day" } }
}
},
"widget.calendar.day.unknown" : {
"comment" : "Erklärt einen ausgegrauten Tag: keine Information, nicht keine Termine",
"localizations" : {
"de" : { "stringUnit" : { "state" : "translated", "value" : "Für diesen Tag liegen keine Daten vor" } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "No data available for this day" } }
}
},
"widget.calendar.denied" : {
"localizations" : {
"de" : { "stringUnit" : { "state" : "translated", "value" : "Kein Kalenderzugriff.\nIn den Systemeinstellungen erlauben." } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "No calendar access.\nAllow it in System Settings." } }
}
},
"widget.calendar.neverWritten" : {
"localizations" : {
"de" : { "stringUnit" : { "state" : "translated", "value" : "Calendarr einmal öffnen,\ndamit Termine bereitstehen." } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "Open Calendarr once\nso events become available." } }
}
},
"widget.calendar.loggedOut" : {
"localizations" : {
"de" : { "stringUnit" : { "state" : "translated", "value" : "In Calendarr anmelden" } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "Sign in to Calendarr" } }
}
},
"widget.calendar.incompatible" : {
"localizations" : {
"de" : { "stringUnit" : { "state" : "translated", "value" : "Onyx ist zu alt für diese\nCalendarr-Version. Bitte aktualisieren." } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "Onyx is too old for this\nCalendarr version. Please update." } }
}
},
"widget.calendar.unreadable" : {
"localizations" : {
"de" : { "stringUnit" : { "state" : "translated", "value" : "Termine nicht lesbar.\nKonfigurationsfehler." } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "Events unreadable.\nConfiguration error." } }
}
}
},
"version" : "1.0"
}