# Development ## Architektur Phase 0 (abgeschlossen) Die folgenden Punkte entsprechen **Phase 0** in `TODO.md`: festgelegte Entscheidungen und **Ist-Zustand** der Codebasis, ohne dass die App komplett neu strukturiert wird. ### Navigation - **Single Activity**, Haupt-UI in `AppRoot.kt`: eingeloggt → `MainTabs` mit **`MainTab`** (Start, Tagebuch, Mitglieder, …). - **Breit / schmal:** ab ca. 600 dp `MainNavigationRail`, sonst `BottomNavigation` (siehe `MAIN_NAV_RAIL_MIN_WIDTH_DP`). - **Unter-Navigation / Tiefe:** kein zentraler **Navigation-Compose**-`NavHost` für die Shell; stattdessen **expliziter Zustand** (`rememberSaveable` / `remember`), z. B. `diarySelectedEntryId`, `membersNestedOpen`, `billingOrdersSection`. **Zurück** über `BackHandler` und Tab-Wechsel, der Detailzustand zurücksetzt (`selectMainTab`). - **Auth:** eigener Flow (`AuthFlowHost`) vor Club-Auswahl – ebenfalls ohne separates Nav-Graph-Modul. - **Begründung (kein zentraler NavHost):** geringere Migrationskosten, vorhandenes Verhalten beibehalten. Ein späterer Umstieg auf `NavHost` (z. B. pro Tab oder für Deep Links) ist möglich. - **Start-Hub (`HomeScreen`):** Nur **Willkommen** + Vereinsinfos (Karte); keine Navigations-Kacheln mehr (vermeidet leere Abschnitte und Doppelung mit der Rail). - **Navigation Rail (Tablet / breit):** Drei **aufklappbare Blöcke** wie die Web-Sidebar – **Tagesgeschäft**, **Wettbewerb**, **Einstellungen** – mit allen Einträgen inkl. Freigaben, Turnierteilnahmen (Browser), Vereinseinstellungen, Vordefinierte Aktivitäten, Team (Browser), Abrechnung, Bestellungen; oben **Start**, unten **Mehr**. Zustand der Aufklappung per `rememberSaveable`. - **Smartphone:** Weiterhin **untere Tab-Leiste** (`visibleMainTabs`) + vollständige Einstellungen unter **Mehr**; Kurzhinweis auf der Startseite. - **Tab-Sichtbarkeit:** Entspricht grob den Web-`v-if`-Bedingungen (`visibleMainTabs` in `AppRoot.kt`, u. a. Kalender nur bei Tagebuch-, Terminplan- oder Turnier-Leserecht). ### Feature-Paketierung (Richtlinie + Ist) - **`shared`:** HTTP-APIs (`shared/.../api`), DTOs (`api/models`), zustandsführende **Manager** (`shared/.../state`), i18n. - **`composeApp`:** Android-UI unter `composeApp/.../ui/` mit **dateiweise** Feature-Namen (`Diary*`, `Members*`, `Schedule*`, `CalendarScreen`, …) sowie `AppRoot.kt`, `AppDependencies.kt`. - **Zielbild:** Neue Features als eigene Dateien/ Unterpakete `ui//` anlegen; keine weiteren riesigen Monolithen neben `AppRoot` anhäufen, sondern schrittweise auslagern (laufende Arbeit). ### Use-Cases / Geschäftslogik - **Primär `shared`:** `*Manager`-Klassen kapseln API + Caching/Flow (z. B. `DiaryManager`, `ClubManager`). - **Plattformnah Android:** reine UI-Hilfen oder `java.time`-lastige Aggregation bleiben in `composeApp` (z. B. `calendar/CalendarAggregator.kt`), angebunden über `AppDependencies`. - **Composables:** möglichst nur Zustand, `LaunchedEffect`, Aufrufe in Manager/APIs; komplexe Ableitungen in wiederverwendbaren Funktionen/Klassen statt inline riesiger Blöcke. ### Echtzeit (optional / Web-Parität) - **Web** nutzt u. a. Socket-Updates; **Android v1:** bewusst **ohne** parallelen Socket-Client. - **Aktualisierung:** `LaunchedEffect` bei Screen-Fokus / `clubId` / relevanten Keys, nach Schreiboperationen (`dataGeneration++` o. ä.) oder Tabwechsel. **Polling** nur dort, wo bereits im Code nachvollziehbar (kein globales Intervall-Polling). - **Später:** optional `socket.io`-Client im `shared` (Dependency existiert bereits) anbinden oder dokumentiertes Polling – siehe `TODO.md` Backlog. ### Medien (Bilder / PDF) - **Bilder:** **Coil** (`coil-compose`), authentifizierte Requests wo nötig (`diaryAuthHeaders` / `AuthenticatedAsyncImage`). - **PDF / Teilen:** Generierung im App-Code, Ausgabe über **FileProvider** und System-**Intent** (`sharePdfFile` u. a. im Tagebuch-Flow). - **Cache:** Standard Coil-/Systemverhalten; kein separates Offline-Framework in Phase 0. --- ## Backend-URL **Standard:** `https://tt-tagebuch.de` – gesetzt in `gradle.properties` (`backendBaseUrl=…`) und als Fallback in `composeApp/build.gradle.kts`. Für lokale Entwicklung läuft der Backend-Server auf deinem Rechner typischerweise unter **`http://localhost:3005`**. Die **Android-Studio-VM (Emulator)** kann **`localhost` / `127.0.0.1` nicht** für diesen lokalen Server nutzen: dort ist „localhost“ der Emulator selbst, nicht dein PC. Für lokale Emulator-Tests nutze `http://10.0.2.2:3005`. **HTTP / Cleartext:** Ab Android 9 blockiert das System unverschlüsseltes HTTP standardmäßig. Für die lokale Dev-URL ist das in `composeApp/src/androidMain/res/xml/network_security_config.xml` für `10.0.2.2`, `localhost` und `127.0.0.1` freigegeben. Testest du mit der **LAN-IP deines PCs** (echtes Gerät), musst du diese IP dort als weiteres `` ergänzen oder kurzzeitig HTTPS nutzen. ### Wenn die App trotz Rebuild noch `localhost` zeigt 1. **Android Studio überschreibt Gradle:** **Settings → Build, Execution, Deployment → Build Tools → Gradle** beim Projekt `mobile-app` prüfen, ob unter **Command-line options** etwas wie `-PbackendBaseUrl=http://localhost:3005` steht – das entfernen oder bewusst setzen. 2. **Alte Installation / Build-Cache:** Emulator-App deinstallieren, Clean, neu installieren: ```bash cd mobile-app adb uninstall de.tsschulz.tt_tagebuch ./gradlew :composeApp:clean :composeApp:installDebug --no-configuration-cache ``` Oder das Skript: `./scripts/install-debug-emulator.sh` 3. **BuildConfig prüfen:** Nach dem Build in `composeApp/build/generated/.../BuildConfig.java` den Wert von `BACKEND_BASE_URL` ansehen – der muss `https://tt-tagebuch.de` sein (oder deine bewusste Override-URL). ### Android Studio „Run“ geht nicht - Projekt **`mobile-app/`** als Root öffnen (nicht nur den übergeordneten Monorepo-Ordner, wenn Studio das Modul nicht erkennt). - **Run → Edit Configurations → Android App:** Modul **`composeApp`** wählen. - Alternativ immer über die Kommandozeile installieren (siehe oben) und die App im Emulator starten. Überschreiben per Gradle-Property, z. B. lokaler Emulator: ```bash ./gradlew :composeApp:installDebug -PbackendBaseUrl=http://10.0.2.2:3005 ``` **Alternative:** Port vom PC auf den Emulator weiterleiten, dann geht wieder `localhost`: ```bash adb reverse tcp:3005 tcp:3005 ./gradlew :composeApp:installDebug -PbackendBaseUrl=http://localhost:3005 ``` ## Smoke flow (Android) 1. Start backend (default `http://localhost:3005`) 2. Start Android emulator 3. App installieren: `./scripts/install-debug-emulator.sh` oder Run in Studio (Modul **`composeApp`**) 4. Login → Club auswählen → Rolle wird angezeigt 5. Open `Tagebuch` → die letzten Einträge werden angezeigt ## Phase 14 – Unit-Tests, Regressionsliste, R8 - **Automatisierte Tests (shared):** `./gradlew :shared:testDebugUnitTest` (JVM-Unit-Tests unter `shared/src/androidUnitTest/`, u. a. Serialisierung kritischer DTOs). - **Manuelle Regression:** `REGRESSION_CHECKLIST.md` im Ordner `mobile-app/`. - **R8/ProGuard:** `composeApp/proguard-rules.pro` ist an die Release-Variante angebunden; **`isMinifyEnabled`** bleibt vorerst **`false`**, bis ein Release mit Schrumpfung gezielt getestet wurde. ## Gradle Wrapper Use the wrapper from `mobile-app/`: ```bash cd mobile-app ./gradlew tasks ``` Note (Codex sandbox only): the sandbox home directory is read-only, so run with: ```bash GRADLE_USER_HOME=/tmp/gradle-home ./gradlew tasks ``` ## i18n generation ```bash node scripts/generate-mobile-i18n.js ``` This reads all web locale JSON files and writes the generated KMP bundle to `mobile-app/shared/src/commonMain/kotlin/de/tt_tagebuch/shared/i18n/MobileStrings.kt`. ## Build Recommended: - Open `mobile-app/` in Android Studio and run the `composeApp` configuration.