Secretary Tasks (Android)
Android-Client für Aufgaben auf dem secretary-server
(CalDAV/CardDAV). Jede Aufgabe ist ein VTODO-Komponent (RFC 5545 §3.6.2)
in einer .ics-Datei unter einer Kalender-Collection.
What it does
- Tasks as VTODO components (RFC 5545 3.6.2) - plain
.icsfiles on your own server - Title, description, priority, due date, optional start date, tags and percent complete
- Recurrences (RRULE) with a tasks.org-style dialog: pick the interval, then completing an occurrence rolls the series forward
- List chips for Due and Upcoming, so overdue and future work stay separate
- Notifications when a task is due, with quick actions to mark it done or postpone it
- Stale-while-revalidate offline: the cache is shown immediately and refreshed in the background
- Collections are selectable, search covers title, description and tags
Changelog
Every released version: Apps/tasks/changelog
Screenshots

Verzeichnisstruktur
secretary-tasks-android/
├── build.gradle.kts # Root-Build
├── settings.gradle.kts
├── gradle.properties
├── CONTEXT.md
├── doc/ # (GUI-/Protokoll-Notizen: Wiki Apps/tasks/gui-protocol)
├── gradle/wrapper/
└── app/
├── build.gradle.kts
├── proguard-rules.pro
└── src/main/
├── AndroidManifest.xml
├── res/values/
│ ├── strings.xml
│ └── themes.xml
└── java/org/beowulf/secretarytasks/
├── SecretaryTasksApp.kt # Application (Service-Locator)
├── MainActivity.kt # Single-Activity
├── data/
│ ├── Models.kt # TasksCollection, Task, Priority
│ ├── Recurrence.kt # RRULE-Expander (FREQ/INTERVAL/…)
│ ├── VTodo.kt # RFC-5545-Parser/-Writer
│ ├── CaldavClient.kt # CalDAV-Client (PROPFIND/PUT/…)
│ ├── AccountResolver.kt # Zentrales Konto (ContentProvider)
│ ├── TasksRepository.kt # Config-Persistenz
│ └── OfflineStore.kt # JSON-Cache + Write-Behind-Queue
├── notify/
│ ├── NotificationScheduler.kt # AlarmManager-Planung
│ ├── TaskReminderReceiver.kt # fällige Aufgabe → Notification
│ └── BootReceiver.kt # Alarme nach Reboot neu planen
├── vm/
│ └── TasksViewModel.kt # App-State
└── ui/
├── Theme.kt # Material 3
├── LoginScreen.kt # Server + Basic-Auth
├── TaskListScreen.kt # Collections + Aufgabenliste
├── TaskEditScreen.kt # Editor (Titel/Priorität/Fälligkeit/…)
├── SettingsScreen.kt # Verbindung/Benachrichtigungen/Abmelden
└── navigation/NavGraph.kt # Routen: login, tasks, task, settings
Befehle
Voraussetzungen: JDK 21, Android-SDK (Platform 37, Build-Tools 34)
und Gradle 9.x. Im Repository liegt bewusst kein Gradle-Wrapper --
entweder Android Studio nutzen (bringt sein Gradle mit) oder Gradle 9 selbst
installieren; die Beispiele unten rufen deshalb gradle direkt.
export ANDROID_HOME="$HOME/Android/Sdk" # bzw. sdk.dir=... in local.properties
gradle :tasks:assembleDebug # Debug-APK
gradle :tasks:testDebugUnitTest # Unit-Tests
gradle :tasks:lintDebug # Lint
Builds laufen im Hintergrund via setsid, Logging geht ins Syslog.
Alle acht Module werden gemeinsam gebaut; ein Release-Build signiert die
APKs (gradle assembleRelease, siehe Docs).
Datenmodell (VTODO)
| App-Feld | ICS-Eigenschaft | Hinweis |
|---|---|---|
| uid | UID |
Ressourcenname <uid>.ics |
| summary | SUMMARY |
Titel |
| description | DESCRIPTION |
Text, \n-escaped |
| priority | PRIORITY |
0=None, 1=High, 3=Medium, 5=Low, 9=Lowest |
| dueRaw | DUE |
Datum YYYYMMDD oder Datum+Uhrzeit |
| startRaw | DTSTART |
optionaler Beginn |
| rrule | RRULE |
Wiederholung (siehe Recurrence) |
| status | STATUS |
NEEDS-ACTION / IN-PROCESS / COMPLETED / CANCELLED |
| percentComplete | PERCENT-COMPLETE |
0–100 |
| completedRaw | COMPLETED |
UTC-Stempel bei Erledigung |
| categories | CATEGORIES |
Tags, kommagetrennt |
| createdRaw | CREATED |
UTC-Stempel |
| lastModifiedRaw | LAST-MODIFIED |
UTC-Stempel |
| href / etag | – (DAV) | Ressourcen-URL + ETag (If-Match beim PUT) |
Protokoll
PROPFIND /tasks/(Depth 1) → Collections +supported-calendar-component-setPROPFIND /tasks/<id>/(Depth 1) → Mitglieder (getetag)GET /tasks/<id>/<uid>.ics→ VTODO-TextPUTmitIf-Match(412 → Konflikt) zum Anlegen/ÄndernDELETEzum LöschenMKCALENDARmit<comp name="VTODO"/>zum Anlegen der Aufgaben-Sammlung
Der secretary-server (≥ 0.16.0) benötigt keine Server-Änderungen für
VTODO – VTODO wird erkannt (Storage-Sniff) und in
supported-calendar-component-set gemeldet; seit 0.16.0 liegt Aufgaben
unter dem top-level Namespace-Root /tasks/ (zuvor /calendars/tasks-*).
Wiederholungen (RRULE)
Recurrence (app/src/main/java/org/beowulf/secretarytasks/data/Recurrence.kt)
ist ein minimaler RFC-5545-Expander:
FREQ=DAILY|WEEKLY|MONTHLY|YEARLYmitINTERVAL,COUNT,UNTILBYDAYinkl. Ordinal (1MO,-1FR),BYMONTHDAYundBYMONTH(YEARLY)- Die Uhrzeit aller Occurrences ist die des Basis-
DUE - Ungültige/unbekannte Regeln → keine Wiederholung (nur das Basisdatum)
- Exit-Guard (500 000 Iterationen) gegen Endlosschleifen
Wiederholungs-Dialog (tasks.org-Stil)
RecurrenceSpec (dieselbe Datei) baut aus Formularwerten eine RRULE
(toRrule), erzeugt ein lesbares Label (toLabel, z.B. "Alle 2 Wochen
(Mo, Mi)" oder "Monatlich am letzten Freitag") und parst eine bestehende
RRULE zurück (fromRrule, best-effort). Der Dialog in TaskEditScreen.kt
bietet:
- Häufigkeit (Keine / Täglich / Wöchentlich / Monatlich / Jährlich)
- Intervall-Stepper (1–99, "Alle n Tage/Wochen/Monate/Jahre")
- Wöchentlich: Wochentags-Chips (Mo–So, Default = Basistag)
- Monatlich: "Am Tag des Monats" (1–31 oder "Letzter Tag" = -1) oder "Am Wochentag des Monats" (Ordinal Ersten..Letzten + Wochentag)
- Jährlich: "Am Datum" (Monat + Tag) oder "Am Wochentag" (+ Monat)
- Ende: Nie / Nach N Vorkommen (1–999) / Am Datum (DatePicker)
- Vorschau der nächsten Termine (aus
nextOccurrences) - "Benutzerdefinierte Regel …": freies RRULE-Feld, validiert per
Recurrence(...).isValidRule(); eine gültige RRULE überschreibt die Formular-Auswahl
Nicht durch das Formular abbildbare Regeln (z.B. BYMONTHDAY=1,15)
werden aus fromRrule als null geliefert und als benutzerdefinierte
Regel angezeigt statt zerstört.
Abhaken wiederkehrender Aufgaben (tasks.org-Stil)
Task.currentDue(now)– die aktuell relevante Fälligkeit: letzte Occurrence der Serie, die nicht in der Zukunft liegt (sonst das Basisdatum).Task.nextOccurrenceForCompletion(now)– verschiebt bei einer wiederkehrenden Aufgabe die Fälligkeit auf die nächste Occurrence und hält sie offen (NEEDS-ACTION, 0%);null, wenn die Serie endet (dann regulär COMPLETED). Das schnelle Abhaken in der Liste nutzt genau diese Logik.Task.relativeDueLabel(now)– "Überfällig" / "Heute" / "Morgen" (sonst null) für die Listenkarten.
Listen-Chips (Fällig / Zukünftig)
- Standard-Ansicht (Chip "Fällig"): nur Aufgaben, die heute oder früher
fällig sind (
currentDue), sowie Aufgaben ohne Fälligkeit; erledigte bleiben sichtbar. - Chip "Zukünftig" blendet zusätzlich Aufgaben mit künftiger Fälligkeit ein (Standard: ausgeblendet).
Benachrichtigungen
NotificationScheduler plant pro bevorstehender Fälligkeit einen
AlarmManager-Alarm (Channel tasks_due, IMPORTANCE_HIGH). Geplante
Ressourcen-hrefs liegen in SharedPreferences (secretary_tasks_notif),
damit Alarme gelöschter Aufgaben abgeräumt werden.
- Alarm schlägt
NMinuten vor der Fälligkeit (Erinnern (Minuten vor Fälligkeit), Default 15); ohne Uhrzeit (DUEals reines Datum) wird um Mitternacht erinnert. - Wiederkehrende Aufgaben: jeweils der nächste Termin der Serie.
BootReceiverplant alle Alarme nach einem Reboot neu.- Abgeschaltete Erinnerungen (Toggle in den Einstellungen) werden nicht geplant; bestehende Alarme werden bei Abschalten entfernt.
Schnellaktionen in der Benachrichtigung
Jede Fälligkeits-Benachrichtigung hat zwei Aktionen (TaskReminderReceiver):
- Erledigt (
ACTION_DONE): schließt die Aufgabe ab – bei wiederkehrenden Aufgaben wird die Fälligkeit auf die nächste Occurrence verschoben (Task.nextOccurrenceForCompletion, identisch zum schnellen Abhaken in der Liste), sonst COMPLETED/100%. Online via PUT, offline via Pending-Queue (OfflineStore), danachscheduleAll. - In 10 Min. (
ACTION_DELAY): verschiebt die Erinnerung umSNOOZE_MINUTES(10) Minuten. Der Snooze wird in den Notif-Prefs (snoozes,href:millis,…) persistiert und innextFireAtberücksichtigt (reine Funktion, getestet); ein neuer Alarm ersetzt den ausgelösten. Beim Auslösen des Snooze-Alarms verbraucht sich der Snooze (clearSnooze),scheduleAllräumt Snoozes erledigter/gelöschter Aufgaben ab.
Action-PendingIntents nutzen actionRequestCode (getrennt vom Alarm-
Request-Code). Tests: NotificationSchedulerTest (7 Tests, nextFireAt).
Offline (Stale-While-Revalidate)
OfflineStore (app/src/main/java/org/beowulf/secretarytasks/data/OfflineStore.kt)
hält Collections + Aufgaben als JSON-Cache (filesDir/secretary_tasks_cache.json,
atomare Schreibvorgänge) und eine Write-Behind-PendingAction-Warteschlange.
- Lese-Seite: Beim Start wird sofort der Cache angezeigt; NavGraph stößt
danach einen Online-Refresh an. Server-Fehler (
IOException) schalten auf Cache um (offlineMode=true),DavExceptionwird als echter Fehler gemeldet. - Schreib-Seite offline: PUT/DELETE/MKCALENDAR werden in die Queue
(
PutTask/DeleteTask/CreateCollection) eingereiht und lokal angewendet. Dedupe pro Ressource (Put ersetzt Put/Delete derselbenhref, Delete ersetzt Puts). Beim nächsten erfolgreichen Sync spieltflushPending()die Queue in Reihenfolge ab;DavException(z.B. 412-Konflikt) verwirft den Eintrag mit Fehlermeldung,IOExceptionbricht ab (bleibt offline). - UI: Offline-Banner in der Aufgabenliste („Offline – N Änderung(en) warten auf Synchronisierung") mit Sync-Button.
Konventionen
- Kotlin + Jetpack Compose + Material 3
- Single-Activity, AndroidViewModel, manuelle DI (Application als Service-Locator)
- Zentrales Konto: Die Zugangsdaten kommen primär aus
secretary-account-androidüber den ContentProvidercontent://org.beowulf.secretaryaccount/account(AccountResolver, Signature-PermissionREAD_ACCOUNT).TasksRepository.config()migriert das Konto als Cache in die lokalen Prefs; ohne Provider gilt der Legacy-Fallback (eigene Anmeldung).MainActivity.onResumeruftTasksViewModel.refreshAccount()auf, damit Änderungen in der Account-App ohne Neustart übernommen werden. - Passwort in SharedPreferences (wie secretary-notes/podcache-Token); EncryptedSharedPreferences wäre der nächste Schritt
usesCleartextTraffic=true: der secretary-server läuft im LAN ggf. per HTTP- Wiederkehrende Aufgaben werden nicht auf dem Server expandiert – die
Serie wird als
RRULEgespeichert, der Client zeigtnextDuean
Server-Referenz
secretary-server(CalDAV/CardDAV, Rust):~/Projects/secretary-server/- Tremere:
https://tremere:8443(Servicesecretary-server, Daten~/.local/share/secretary-server/calendars/)
Attachments:
- tasks-future.png [download] added by beowulf on 2026-10-06 16:03:28. [details]
- tasks-due.png [download] added by beowulf on 2026-10-06 16:03:28. [details]
- tasks-detail.png [download] added by beowulf on 2026-10-06 16:03:28. [details]