No description
  • Kotlin 91.1%
  • Shell 8.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
git@dewe.simplelogin.com ff7b6cb864
Some checks failed
release.yml / CI/CD: Tests+Lint bei jedem Push, Release-Pipeline auf den VPS-Runner gebracht (push) Failing after 0s
CI / test-und-lint (push) Successful in 19m1s
CI/CD: Tests+Lint bei jedem Push, Release-Pipeline auf den VPS-Runner gebracht
- ci.yml neu: Unit-Tests + Lint bei jedem Push/PR (Runner-Label docker,
  arm64-aapt2-Override, Speicher-Limits wie im App-Schwesterprojekt)
- release.yml lauffähig gemacht: altes ubuntu-latest/checkout@v4-Setup
  wurde vom Runner nie angezogen; jetzt Tarball/Clone-Checkout per Job-Token,
  Release-Publishing über GITHUB_TOKEN statt FORGEJO_TOKEN-Secret,
  Debug-Signing-Fallback wenn Keystore-Secrets fehlen
- README: Release-Abschnitt auf Tag-Push-Ablauf umgeschrieben
2026-09-18 16:17:49 +00:00
.forgejo/workflows CI/CD: Tests+Lint bei jedem Push, Release-Pipeline auf den VPS-Runner gebracht 2026-09-18 16:17:49 +00:00
app Mehrbenutzer-Feed, Release-Workflow und Härtung 2026-09-11 23:25:24 +02:00
gradle/wrapper Dienstplan-App mit abonnierbarem Kalender-Feed 2026-09-11 15:49:53 +02:00
scripts Mehrbenutzer-Feed, Release-Workflow und Härtung 2026-09-11 23:25:24 +02:00
server Mehrbenutzer-Feed, Release-Workflow und Härtung 2026-09-11 23:25:24 +02:00
.gitignore Mehrbenutzer-Feed, Release-Workflow und Härtung 2026-09-11 23:25:24 +02:00
build.gradle.kts Dienstplan-App mit abonnierbarem Kalender-Feed 2026-09-11 15:49:53 +02:00
gradle.properties Mehrbenutzer-Feed, Release-Workflow und Härtung 2026-09-11 23:25:24 +02:00
gradlew Dienstplan-App mit abonnierbarem Kalender-Feed 2026-09-11 15:49:53 +02:00
gradlew.bat Dienstplan-App mit abonnierbarem Kalender-Feed 2026-09-11 15:49:53 +02:00
README.md CI/CD: Tests+Lint bei jedem Push, Release-Pipeline auf den VPS-Runner gebracht 2026-09-18 16:17:49 +00:00
settings.gradle.kts Dienstplan-App mit abonnierbarem Kalender-Feed 2026-09-11 15:49:53 +02:00
test-auf-emulator.sh Dienstplan-App mit abonnierbarem Kalender-Feed 2026-09-11 15:49:53 +02:00

Dienstplan-ICS

Kleine Android-App (Kotlin + Jetpack Compose), um Schichtdienste schnell im Monatskalender einzutragen. Die Termine bleiben dauerhaft in der App gespeichert und werden als abonnierbarer Kalender-Feed veröffentlicht – Google Kalender aktualisiert sich dann von selbst, ganz ohne manuellen Export und Import. Als Fallback kann die .ics weiterhin als Datei gespeichert oder geteilt werden.

Workflow

  1. Dienst auswählen: Unter dem Kalender liegen die Dienstarten als Chips (Standard: Tag 06:00–14:00, Nacht 22:00–06:00, Doppelschicht 06:00–22:00). Ein Tippen aktiviert den Dienst (hervorgehoben).
  2. Tag antippen: Der aktive Dienst wird an dem Tag eingetragen (farbiger Punkt in der Tagzelle). Derselbe Dienst + derselbe Tag nochmal antippen → wieder entfernt. Verschiedene Dienste dürfen am selben Tag koexistieren.
  3. Dienste verwalten: ✎ am Chip öffnet den Dialog — Name, Start-/Endzeit (TimePicker) oder der Haken „Ganztägig". Ende vor Start = Dienst geht über Mitternacht (wird in der ICS korrekt als Folgetag geschrieben). Im Dialog gibt es auch Löschen.
  4. Veröffentlichen (Abo-Feed): „ICS erstellen" → „Veröffentlichen – Feed aktualisieren" schickt den kompletten Kalender per HTTP-PUT an den eigenen Feed-Server. Google Kalender hat die Abo-URL einmalig hinterlegt („Weitere Kalender" → „+" → „Aus URL") und holt jede Änderung selbst ab (üblich innerhalb von ~12–24 h, Rhythmus steuert Google).
  5. Fallback-Export: „Speichern" (Android-Dateidialog) und „Teilen" (Android-Teilen-Menü) geben die .ics wie bisher als Datei aus.

Die Termine bleiben dauerhaft gespeichert (SharedPreferences) – die App ist die Datenquelle des Feeds. „Kalender leeren" (neben der Dienste-Zähler) entfernt alle Termine auf Wunsch; auch das ändert den Feed erst beim nächsten Veröffentlichen.

Stabile Termine statt Duplikate

Jeder Termin bekommt eine feste UID aus (Datum, Dienstart). Beim Abo-Refresh aktualisiert Google so bestehende Termine, statt sie zu duplizieren; Termine, die aus dem Feed verschwinden, werden im Abo entfernt.

Server einrichten

Der Feed braucht einen Ort, den Google erreichen kann – ein kleines nginx-Setup auf dem eigenen VPS genügt (keine eigene Server-Software): siehe server/README.md. Die URLs trägt man in der App unter dem Zahnrad oben rechts ein (Upload-URL + Abo-URL, Abo-URL wird zusätzlich als webcal:// angezeigt).

Mehrere Personen: Jede Person bekommt auf dem Server ein eigenes Token-Paar (per server/person.sh, ein Befehl, kein nginx-Reload) und damit eine eigene Upload-URL und einen eigenen Abo-Feed. Damit veröffentlicht jede App nur den eigenen Kalender — ein „Veröffentlichen“ überschreibt nie den Kalender einer anderen Person. In der App selbst ist nichts Besonderes nötig: Jedes Gerät speichert einfach seine eigenen URLs.

Installation über Obtainium

Releases werden auf git.weick.io veröffentlicht und lassen sich mit Obtainium direkt installieren und automatisch aktuell halten (Voraussetzung: das Repo ist öffentlich und ein Release enthält die APK).

In Obtainium: App hinzufügen → Quelle „HTML“ → dann:

Einstellung Wert
App-URL https://git.weick.io/dennis/dienstplan-ics/releases
Regex zum Filtern von APK-Links dienstplan-v[0-9.]+\.apk
Nach Linktext filtern an (der Dateiname steckt im Linktext, die URL ist eine UUID)
Ersten Link verwenden an (Forgejo listet neueste Releases zuerst)
Version aus ganzer Seite extrahieren an
Version-Extraktions-Regex + Match-Gruppe dienstplan-v([0-9.]+)\.apk, Gruppe 1

Obtainium prüft die Releases-Seite dann bei jedem Update-Lauf: Erscheint ein neues dienstplan-vX.Y.Z.apk, bietet es das Update an.

Umstieg von der Debug-APK: Die Release-APK ist anders signiert, daher muss die bisherige Installation einmal deinstalliert werden – dabei gehen die in der App gespeicherten Termine verloren. Vorher also einmal „Veröffentlichen“ drücken (Google hat die Termine dann im Abo-Feed) und die Dienstarten notieren.

Bauen & installieren

Benötigt: JDK 17–21 und Android-SDK (auf diesem Rechner vorhanden unter ~/.local/share/jdks/ und ~/Android/Sdk; die nutzerlokale ~/.gradle/gradle.properties verweist per org.gradle.java.home auf das Temurin-JDK 21, da Gradle 8.9 mit dem systemweiten Java 26 nicht läuft — der Pfad ist maschinenspezifisch und liegt bewusst außerhalb des Repos).

./gradlew assembleDebug          # Debug-APK bauen
./gradlew testDebugUnitTest      # Unit-Tests (39 Stück)

Die APK liegt danach unter app/build/outputs/apk/debug/app-debug.apk.

Installation auf dem Handy — entweder per USB mit

adb install app/build/outputs/apk/debug/app-debug.apk

oder die APK aufs Handy kopieren (Mail/Cloud/USB) und dort direkt öffnen (Unbekannte Quellen/Installation aus dieser Quelle zulassen bestätigen). Für den täglichen Gebrauch auf dem Handy siehe oben Installation über Obtainium.

Release veröffentlichen

Ein Release ist ein Git-Tag vX.Y.Z plus signierte APK als Forgejo-Release; versionName wird aus dem Tag abgeleitet, versionCode zählt die Commits — Versionszahlen werden nie manuell gepflegt.

Einmalig eingerichtet:

  • Keystore (keystore/dienstplan-release.jks + keystore.properties, beide gitignored) — ⚠️ unbedingt extern sichern (Passwort-Manager/USB-Stick): Geht der Keystore verloren, kann Android die APK nicht mehr als „dieselbe App“ aktualisieren und alle Installationen müssten mit Datenverlust neu aufgesetzt werden.
  • Repo-Secrets für die Release-Pipeline (Einstellungen → Actions → Secrets): RELEASE_KEYSTORE_BASE64 (base64 -w0 keystore/dienstplan-release.jks), RELEASE_STORE_PASSWORD, RELEASE_KEY_ALIAS, RELEASE_KEY_PASSWORD (Werte wie in keystore.properties). Fehlen sie, baut die Pipeline zwar, signiert aber debug — der Release-Body weist dann deutlich darauf hin.
  • Forgejo-Token in .release-env (gitignored): FORGEJO_TOKEN=… (Profil → Einstellungen → Anwendungen → Token, Recht: Repository Lesen+Schreiben) — nur für scripts/release.sh, die Pipelines nutzen den automatischen Job-Token.
  • Das Repo muss öffentlich sein, sonst sieht Obtainium die Releases nicht.

Automatisch (Forgejo Actions): Bei jedem Push läuft die CI (.forgejo/workflows/ci.yml: Unit-Tests + Lint). Ein Release genügt:

git tag v1.2.3 && git push origin v1.2.3

Die Release-Pipeline (.forgejo/workflows/release.yml) testet, baut die signierte APK und veröffentlicht sie als Forgejo-Release. Getriggert wird sie durch den Tag-Push — erst taggen, dann pushen (Commits mit offengebliebenen Fehlern scheitern dort sichtbar). Die APK heißt dabei dienstplan-v1.2.3.apk (das Namensmuster erwartet die Obtainium-Konfiguration oben — beim Upgrade der Version also nichts anpassen).

Notfall (ohne Runner/ohne Actions): scripts/release.sh macht dasselbe von der Entwicklermaschine aus:

scripts/release.sh 1.2.3    # Tests → Tag v1.2.3 → signierter Build → Forgejo-Release

Struktur

app/src/main/java/de/dennis/dienstplanics/
├── MainActivity.kt            # Einstieg, Compose-Setup, ViewModel-Factory
├── PlanViewModel.kt           # UI-State (Dienstarten, Termine, Monat, Veröffentlichen)
├── model/Model.kt             # ShiftType, Entry, PlanState, MonthGrid (pure Kotlin)
├── model/PublishState.kt      # PublishConfig + PublishState (pure Kotlin)
├── ics/IcsGenerator.kt        # RFC-5545-ICS mit VTIMEZONE Europe/Berlin
├── ics/StableUid.kt           # deterministische UID pro (Datum, Dienstart)
├── data/ShiftTypeStore.kt     # Store-Interface + Standard-Dienste
├── data/SharedPrefsShiftTypeStore.kt
├── data/PlanStore.kt + SharedPrefsPlanStore.kt     # dauerhafte Termine
├── data/PublishConfigStore.kt                      # Server-Konfiguration
├── net/FeedUploader.kt        # HTTP-PUT (java.net, ohne Extra-Abhängigkeit)
└── ui/                        # PlanScreen, CalendarGrid, ShiftChips, ShiftEditDialog
server/                        # nginx-Config, person.sh (Personen anlegen/entfernen) + Anleitung
scripts/release.sh             # Tests → Tag → signierter Build → Forgejo-Release
.forgejo/workflows/release.yml # Optional: CI-Build bei Tag-Push (braucht Actions-Runner)

Die Geschäftslogik (Toggle-Regeln, Monatsraster, ICS-Generierung inkl. Übernacht-, Ganztägig- und Escaping-Fällen, stabile UIDs, Publish-Statusübergänge) ist pure Kotlin und durch Unit-Tests abgedeckt.