Android App — Kotlin (Jetpack Compose) Plan und Abhakliste Kurz: Ziel ist eine native Android-App mit Kotlin + Jetpack Compose, die die Web-UI 1:1 abbildet (Farben, Typografie, Funktionalitäten). Diese Datei enthält Architekturentscheidungen, empfohlene Bibliotheken und eine detaillierte Abhakliste (schrittweise). 1) Zusammenfassung der Entscheidungen - Plattform: Native Android - Sprache: Kotlin - UI-Toolkit: Jetpack Compose (Compose Material3) - Architektur: MVVM mit `ViewModel` + Kotlin Coroutines + Flow - DI: Hilt - HTTP-Client: Ktor Client oder Retrofit + OkHttp (empfohlen: Retrofit für breite Community-Docs) - Bild-Loading: Coil - Lokale DB / Caching: Room + DataStore (Preferences) - Background/Sync: WorkManager - Auth: kurzlebiges JWT-Access-Token plus rotierendes, widerrufbares Refresh-Token pro Android-Gerätesitzung; Speicherung in `EncryptedSharedPreferences`/Android Keystore; Unterstützung für Passkeys (Android Passkeys / WebAuthn Interop über FIDO2 APIs) - Auth-Sicherheitsentscheidung: kein statischer App-Key bzw. kein in der APK hinterlegtes Client-Secret. Native Apps können ein gemeinsames Secret nicht vertraulich halten. Optional später: Refresh-Sitzung an ein pro Installation im Android Keystore erzeugtes Schlüsselpaar binden. - Rich-Text: WebView-basierte Anzeige; Editoren: ggf. hybride Lösung (Server-side HTML editor + WebView) oder `RichEditor`-Libs - Crash-Reporting & Monitoring: Firebase Crashlytics oder Sentry 2) Design & Farben - Material Theme (Material3) mit Farben aus `tailwind.config.js` (Primary + Accent). - Fonts: Inter & Montserrat via Google Fonts (Download/Bundle oder Play-Services-Download at runtime). - Mapping: Tailwind-Token → `colors.xml` / Compose `Color` tokens. 3) Empfohlene Abhängigkeiten (erste Implementierung) - androidx.compose.* (ui, material3, navigation) - androidx.lifecycle:lifecycle-viewmodel-ktx - com.google.dagger:hilt-android - retrofit2 + converter-moshi / kotlinx-serialization - io.coil-kt:coil-compose - androidx.room:room-runtime + room-ktx - androidx.work:work-runtime-ktx - androidx.datastore:datastore-preferences - com.google.android.gms:play-services-auth (für passkeys falls nötig) - io.sentry:sentry-android (optional) 4) Detaillierte Abhakliste (Schritte) [x] 1. Repo-Analyse: Liste der externen Endpunkte und Auth-Anforderungen exportieren [x] 2. Projekt-Scaffold: Android Studio Projekt mit Kotlin + Compose anlegen [x] 3. App-Architektur: Module / Packages anlegen (ui, data, domain, di, util) [x] 4. CI-Build: Gradle-Config und GitHub Actions Skeleton [x] 5. Theme: `Color.kt`, `Typography.kt`, `Theme.kt` erstellen und Tailwind-Farben mappen [x] 6. Fonts: Inter + Montserrat einbinden (res/font oder GoogleFonts) [ ] 7. Navigation: Compose Navigation-Graph mit Routen für alle Web-Seiten anlegen [x] 7a. Umgebungen: Android-Varianten fuer lokal, Test-Instanz und Produktion mit eigener API-Basis konfigurieren [x] 7b. Adaptive Navigation: Tablet mit persistentem Header/Hauptmenue, Smartphone mit bestehender screenbezogener Navigation [x] 7c. Branding: vorhandenes Web-Logo als optimierte Android-Ressource in der App-Navigation verwenden [x] 7d. Tablet-Navigation: öffentliche Haupt- und Subnavigation der Web-UI mit Portierungszielen abbilden [x] 7e. Navigation: dynamische Mannschaftslinks, Galerie-Sichtbarkeit und rollenabhängiges `Intern` wie in der Web-UI anbinden [x] 8. Start-Screen: `HomeScreen` webnah mit Hero, Navigation, Termine, Spielen, News und Aktionen umsetzen [x] 9. Komponenten: NavBar, Footer, Cards, ImageGrid und News-Dialog implementieren [x] 10. Öffentliche Screens aus der Web-Navigation portieren - [x] `/` Startseite - [x] `/termine`: öffentliche Terminliste mit Lade-, Leer- und Fehlerzustand - [x] `/mannschaften/spielplaene`: Saison-, Wettbewerbs- und Mannschaftsfilter mit Spielkarten - [x] `/verein/galerie`: Anzeige-Screen vorhanden - [x] `/kontakt`: Formular-Screen vorhanden - [x] `/mitgliedschaft`: Antrag, Validierung, PDF-Erzeugung und PDF-Öffnen - [x] `/verein/ueber-uns`: CMS-Inhalt aus der öffentlichen Konfiguration - [x] `/vorstand`: öffentliche Vorstandsangaben aus der Konfiguration - [x] `/verein/geschichte`: CMS-Inhalt aus der öffentlichen Konfiguration - [x] `/verein/satzung`: CMS-Inhalt und PDF-Aufruf aus der öffentlichen Konfiguration - [x] `/vereinsmeisterschaften`: Ergebnisliste mit Jahresfilter und Statistik - [x] `/links`: strukturierte CMS-Links mit Fallback-Verweisen - [x] `/mannschaften`: Übersicht aus saisonaler Mannschafts-CSV - [x] `/mannschaften/[slug]`: dynamische Mannschaftsdetails mit aktuellem Spielplan und Umschaltung `Matches`/`Tabelle` - [x] `/spielsysteme`: Spielsystemkarten mit Kategoriefilter aus CSV - [x] `/training`: Trainingsort und gruppierte Trainingszeiten aus der Konfiguration - [x] `/training/trainer` - [x] `/training/anfaenger` - [x] `/tt-regeln`: Regelübersicht mit DTTB- und PDF-Aufruf [ ] 10a. Weitere öffentliche bzw. bestehende Web-Routen prüfen und portieren - [ ] `/anlagen` - [ ] `/impressum` - [ ] Legacy-/Doppelrouten klären: `/galerie`, `/geschichte`, `/satzung`, `/ueber-uns`, `/spielplan`, `/verein/tt-regeln`, `/mannschaft/[slug]`, `/mannschaften/herren`, `/mannschaften/damen`, `/mannschaften/jugend` [ ] 10b. Newsletter-Screens portieren - [ ] `/newsletter/subscribe` - [ ] `/newsletter/unsubscribe` - [ ] `/newsletter/confirm`, `/newsletter/confirmed`, `/newsletter/unsubscribed` [x] 10c. Auth-Screens portieren - [x] `/login`: Passwort-Login und Logout in der laufenden Sitzung - [x] `/registrieren` - [x] `/passwort-vergessen` [ ] 10d. Mitgliederbereich portieren - [ ] `/mitgliederbereich`: Übersicht - [ ] `/mitgliederbereich/mitglieder` - [ ] `/mitgliederbereich/news` - [ ] `/mitgliederbereich/profil` - [ ] `/mitgliederbereich/api` [ ] 10e. CMS-Screens nach Rollenberechtigung portieren - [ ] `/cms`, `/cms/startseite`, `/cms/inhalte`, `/cms/vereinsmeisterschaften` - [ ] `/cms/sportbetrieb`, `/cms/mitgliederverwaltung`, `/cms/kontaktanfragen` - [ ] `/cms/newsletter`, `/cms/einstellungen`, `/cms/benutzer` [ ] 11. API-Client: Retrofit/Ktor-Client implementieren, Auth-Interceptor (Token Refresh) - [x] Retrofit/OkHttp/Moshi und Hilt-Verdrahtung - [x] Öffentliche Endpunkte für Startseite, Termine, Spielplan, Galerie und Kontakt - [x] Mitgliedschafts-PDF-Endpunkte mit Cookie-Jar und `FileProvider` - [x] Passwort-Login-Endpunkt und Token-Übergabe an den Interceptor - [x] Verschlüsselte Token-Persistenz sowie Status/Logout per Bearer-Token - [ ] Bearer-Unterstützung aller später portierten geschützten Bereiche - [x] `POST /api/auth/refresh` anbinden und Access-Token bei Ablauf automatisch erneuern - [x] OkHttp-`Authenticator` mit genau einem synchronisierten Refresh-Versuch pro fehlgeschlagenem Request ergänzen [x] 12. Auth: Login/Register/Logout + sichere Token-Speicherung (EncryptedSharedPreferences) - [x] Login/Logout und verschlüsselte Token-Speicherung - [x] Registrierung und Passwort-Reset - [x] Backend: Android-JWT-Access-Token auf ca. 15 Minuten reduzieren; bestehende Web-Cookie-Sitzungen bis zur Web-Refresh-Integration kompatibel weiterführen - [x] Backend: langlebige, zufällige Refresh-Token pro Gerätesitzung mit serverseitig gespeichertem Token-Hash einführen - [x] Backend: Refresh-Token bei jeder Erneuerung rotieren und Wiederverwendung eines verbrauchten Tokens als Sitzungsdiebstahl behandeln - [x] Backend: Logout, Kontodeaktivierung und Passwortänderung widerrufen betroffene Refresh-Sitzungen - [x] App: Access- und Refresh-Token verschlüsselt speichern, Sitzung beim App-Start durch Refresh wiederherstellen - [ ] Optional nach MVP: pro Installation ein nicht exportierbares Keystore-Schlüsselpaar erzeugen und Refresh-Requests daran binden [ ] 13. Passkeys: Integration prüfen (FIDO2 / Passkeys) und Fallback auf Passwort [ ] 14. Image-Upload: Multipart-Upload + Coil für Anzeige + Bildkompression (u. a. Sharp-Äquivalent evtl. serverseitig) [ ] 15. Rich-Text: Anzeige von HTML (Compose + WebView) und ggf. Editor via WebView-bridge [ ] 16. Formulare: Validierung (clientseitig) und Fehlerdarstellung [ ] 17. Offline & Caching: Room für persistente Daten, Response-Caching, Sync-Strategie [ ] 18. Lokalisierung: `strings.xml` (DE + EN) und i18n-Check [ ] 19. Accessibility: ContentDescription, Focus, Farben/Kontrast prüfen [ ] 20. Tests: Unit-Tests für ViewModels + UI-Tests mit Compose Testing [ ] 21. Performance: Bildoptimierung, LazyLists, Paging (falls große Daten) [ ] 22. Analytics: Firebase / Matomo Integration (je nach Datenschutz) [ ] 23. Crash-Reporting: Sentry / Crashlytics integrieren [ ] 24. Build/Release: App signing, Release-Notes, Play-Store-Metadaten vorbereiten [ ] 25. Dokumentation: `README-android.md` mit Setup, Architektur und Release-Anleitung 5) Kurzzeit-MVP (Priorität für erste Version) - [x] A. Auth (Login/Logout) - [x] Passwort-Login und Logout in der aktuellen App-Sitzung - [x] Persistente Statuswiederherstellung/Logout für die Auth-Endpunkte - [x] Dauerhaftes Eingeloggtbleiben durch rotierendes Refresh-Token pro Android-Gerätesitzung - [x] B. Home, Termine, Spielplan, Galerie (anzeigen) - [x] C. Kontaktformular (absenden) - [ ] D. Bildanzeige + Caching - [x] E. Theme & Fonts 6) Nächste Aktionen (sofort) - Web-Login bei Bedarf auf denselben Refresh-Flow migrieren; bis dahin bleiben Web-Cookie-Sitzungen bewusst kompatibel. - Passkey-Anmeldung über Android Credential Manager anbinden. - Die noch fehlenden öffentlichen Routen aus `10a` und die Newsletter-Screens aus `10b` nativ portieren. - Saisonwahl für Mannschaftsübersicht/-details wie in der Web-UI ergänzen. - Mitgliederbereich/CMS-Screens portieren; dabei rollenabhängige `Intern`-Navigation und Bearer-Unterstützung der verwendeten Backend-Endpunkte ergänzen. 7) Umsetzungsprotokoll - 2026-05-27: Webnahe Startseite mit öffentlichen Live-Daten umgesetzt; Hilt/Moshi-App-Verdrahtung ergänzt. - 2026-05-27: `Termine` und `Spielplan` als native Screens umgesetzt; Spielplan unterstützt Saison, Wettbewerb, Mannschaft, Ergebnis und zweizeilige Gruppeninformation. - 2026-05-27: `Mitgliedschaft` mit Antrag/PDF-Abruf sowie Passwort-`Login`/`Logout` umgesetzt; offene Auth-Härtung separat ausgewiesen. - 2026-05-27: Tokens verschlüsselt persistiert; Session-Wiederherstellung sowie Logout per Bearer-Token in den Auth-Endpunkten ermöglicht. - 2026-05-27: Registrierung und Passwort-Reset an die vorhandenen Auth-Endpunkte angebunden. - 2026-05-27: Product-Flavors `local`, `instantTest` und `production` eingerichtet; lokale Basis-URL ist per Gradle-Parameter überschreibbar. - 2026-05-27: Gradle-Heap/Worker für Flavor-Builds festgelegt, nachdem paralleles D8/KSP mit dem 512-MiB-Standardheap nicht ausreichend Speicher hatte. - 2026-05-27: Lokales Testsetup gegen Emulator geprüft; bei IPv6-gebundenem Nuxt-Dev-Server wird die von Nuxt ausgegebene Network-URL per `LOCAL_API_BASE_URL` verwendet. - 2026-05-27: Adaptive Navigation umgesetzt; Tablet-Layouts ab `600dp` zeigen Header und Hauptmenue dauerhaft, Smartphone-Layouts behalten die vorhandene Navigation. - 2026-05-27: Platzhalterlogo in der Android-Navigation durch das vorhandene Harheimer-TC-Weblogo als skalierte lokale PNG-Ressource ersetzt. - 2026-05-27: Web-Navigation und `pages/` vollständig inventarisiert; Tablet-Haupt-/Subnavigation für die öffentlichen Bereiche strukturell angeglichen und alle fehlenden Screens einzeln in die Portierungsliste aufgenommen. - 2026-05-27: Tablet-Header auf Web-Verhalten angepasst (Bereichswechsel öffnet Startseite und Submenü) und die native ActionBar zugunsten des App-Headers entfernt. - 2026-05-27: Navigation mit Live-Mannschaftslinks, öffentlicher Galerie-Sichtbarkeit und rollenabhängigem `Intern` ergänzt; Mannschaftsübersicht/-detail sowie Training, Trainer und Anfänger nativ portiert. - 2026-05-27: Mannschaftsdetail um die Web-Untertabs `Matches` und `Tabelle` erweitert; Tabellenzeilen werden aus `/api/spielplan/table` geladen und die eigene Mannschaft hervorgehoben. - 2026-05-27: Tabellenraster in den Mannschaftsdetails mit gemeinsamen Spaltenbreiten für Tablet und Smartphone ausgerichtet; die Zustandswiederherstellung dynamischer Mannschaftslinks korrigiert. - 2026-05-27: Die verbleibenden öffentlichen Screens aus Punkt 10 portiert: Verein/CMS-Inhalte, Vorstand, Satzung/PDF, Links, Vereinsmeisterschaften mit Personenbild-Dialog, Spielsysteme und TT-Regeln. - 2026-05-27: Architektur für dauerhaftes Android-Login festgelegt: kein eingebetteter App-Key, sondern kurzlebige Access-Tokens und rotierende, widerrufbare Refresh-Tokens pro Gerätesitzung; optionale spätere Gerätebindung per Keystore-Schlüsselpaar. - 2026-05-27: Dauerhaftes Android-Login umgesetzt: Android-Logins erhalten 15-Minuten-Access-Tokens und rotierende Refresh-Tokens; Token-Hashes, Wiederverwendungswiderruf, Logout-/Reset-/Deaktivierungswiderruf sowie verschlüsselte App-Speicherung und automatischer OkHttp-Refresh sind implementiert. 8) Android-Testumgebungen - Lokal im Emulator: `./gradlew :app:installLocalDebug` verwendet `http://10.0.2.2:3100/` und die App-ID `de.harheimertc.local`. - Lokal, wenn `10.0.2.2` nicht erreichbar ist: `./gradlew :app:installLocalDebug -PLOCAL_API_BASE_URL=http://:3100/`; die passende URL steht in der `npm run dev`-Ausgabe (hier `http://torstens:3100/`). - Test-Instanz: `./gradlew :app:installInstantTestDebug` verwendet `https://harheimertc.tsschulz.de/` und die App-ID `de.harheimertc.test`. - Produktion: `./gradlew :app:installProductionDebug` verwendet `https://harheimertc.de/` und die App-ID `de.harheimertc`. - Nur APKs erzeugen: `./gradlew :app:assembleLocalDebug :app:assembleInstantTestDebug :app:assembleProductionDebug`. 9) Dauerhaftes Android-Login: Architektur und Umsetzung - Stand der Umsetzung: Android-Logins erhalten ein ca. 15 Minuten gültiges JWT und eine serverseitig prüfbare Refresh-Sitzung; Access-Tokens mit Sitzungs-ID werden bei widerrufener Gerätesitzung abgelehnt. Web-Logins verwenden weiterhin das bisherige Cookie-JWT, bis ein browserseitiger Refresh-Flow ergänzt ist. - Ziel: Ein Benutzer bleibt auf einem bekannten Gerät angemeldet, ohne dass ein langfristig gültiges Bearer-JWT oder ein extrahierbares App-Secret verwendet wird. - Token-Modell: - Access-Token: JWT mit kurzer Laufzeit, Zielwert ca. 15 Minuten; wird für normale API-Requests als Bearer-Token verwendet. - Refresh-Token: kryptografisch zufälliges, undurchsichtiges Token mit längerer Laufzeit, Zielwert z. B. 90 Tage mit Erneuerung bei aktiver Nutzung. - Server speichert ausschließlich den Hash des Refresh-Tokens zusammen mit `sessionId`, `userId`, `createdAt`, `lastUsedAt`, `expiresAt`, `revokedAt` und optional Gerätebezeichnung. - Backend-Arbeitspaket: - Login-Antwort um `accessToken`, `refreshToken`, `sessionId` und Ablaufmetadaten erweitern; bestehendes `token` nur befristet kompatibel halten. - `POST /api/auth/refresh` implementieren: gültiges Refresh-Token konsumieren, rotieren und ein neues Token-Paar zurückgeben. - Token-Wiederverwendung erkennen: Wird ein rotiertes Refresh-Token erneut präsentiert, die betroffene Token-Familie bzw. Gerätesitzung widerrufen. - `POST /api/auth/logout` auf Widerruf der Gerätesitzung erweitern; optional Endpunkte zum Anzeigen und Widerrufen eigener Geräte-Sitzungen vorsehen. - Kontodeaktivierung und Passwortänderung müssen sämtliche Refresh-Sitzungen des Benutzers widerrufen. - Rate-Limits und Audit-Events für Login, Refresh-Erfolg/-Fehlschlag, Wiederverwendung und Widerruf ergänzen. - Android-Arbeitspaket: - `AuthRepository` auf Access-Token, Refresh-Token und Session-ID erweitern; Speicherung weiter über Keystore-geschützte Preferences. - `ApiService`/DTOs um Refresh-Request und Token-Paar-Antwort ergänzen. - Einen OkHttp-`Authenticator` einsetzen, der auf `401` einmalig ein Access-Token erneuert, parallele Refreshes synchronisiert und den ursprünglichen Request wiederholt. - Beim App-Start zunächst Access-Token prüfen und bei Ablauf transparent mit dem Refresh-Token erneuern; nur bei fehlgeschlagenem Refresh zum Login zurückkehren. - Beim Logout lokale Tokens auch bei Netzwerkfehler entfernen; serverseitiger Widerruf erfolgt best effort bzw. bei nächster Konnektivität. - Sicherheitsregeln: - Kein gemeinsamer App-Key und kein statisches Client-Secret in Sourcecode, `BuildConfig` oder APK. - Refresh-Tokens nie im Klartext serverseitig speichern oder protokollieren. - Nur HTTPS für Test-/Produktionsumgebungen; Token-Werte nicht in Logging-Interceptors ausgeben. - Optional nach MVP: App erzeugt pro Installation ein Keystore-Schlüsselpaar; Backend bindet Refresh-Sitzungen an den öffentlichen Schlüssel und prüft signierte Refresh-Anfragen. --- Datei: [ANDROID_KOTLIN_PLAN.md](ANDROID_KOTLIN_PLAN.md)