# GPS2Audio v2.7.4 – Planlinie Auto Render – Handoff ## Version - **versionName:** 2.7.4 - **versionCode:** 110 - **Basis:** GPS2Audio_v2_7_3_planline_autoshow_source.zip --- ## Tatsächlicher Root Cause im Code ### Variable, die auf der Karte gezeichnet wird Es gibt **drei separate Zeichenpfade** in `ExplorerScreenMapLibre.kt`: | Variable | Beschreibung | Pfad | |---|---|---| | `routeLine` | Live OSRM-Berechnung (blau, breit) | `routePolylines` | | `savedRouteLine` | Aus `activeTourRoute.polyline` (DB), grün oder orange | `routePolylines` | | `routePlanline` | Aus `routePlan.orderedStops()` abgeleitet (orange, dünn) | `planlinePolylines` | | `manualRoutePoints` | Manuell gezeichnete Route | eigene Schicht | Alle vier werden im selben `LaunchedEffect` verwaltet (Zeile ~835 in v2.7.3), mit Priorität: `routeLine > savedRouteLine > routePlanline`. --- ### Root Cause 1 – Phantom-Guard schloss PLANLINE auf der Standardtour aus **Datei:** `ExplorerScreenMapLibre.kt`, `savedRouteLine`-Berechnung (~Zeile 267) ```kotlin // v2.7.3 (fehlerhaft für PLANLINE): val savedRouteLine: List = remember(...) { val r = activeTourRoute if (r == null || r.source == MANUAL) emptyList() else if (isNeutralStandard && !isRecording) emptyList() // ← Problem else r.polyline.map { LatLng(it.lat, it.lon) } } ``` `isNeutralStandard = true` wenn `selectedTour == DEFAULT_TOUR_NAME` (die Standard-/Haupttour). Der Phantom-Guard wurde in v2.5.35 eingeführt, um alte OSRM-Routen auf der Standardtour zu unterdrücken. Aber er traf auch PLANLINE-Quellen, obwohl diese explizit vom Nutzer gesetzt wurden. Ergebnis: `savedRouteLine` war immer leer für PLANLINE auf der Standardtour → keine DB-gespeicherte Linie sichtbar. ### Root Cause 2 – `mapLibreMap` fehlte als Key im Drawing-LaunchedEffect **Datei:** `ExplorerScreenMapLibre.kt`, ~Zeile 832 ```kotlin // v2.7.3 (fehlerhaft): LaunchedEffect(routeLine, routePlanline, routePlan, showRouteOverlay, styleReady, savedRouteLine) { ``` `mapLibreMap` war kein Key. Nach einem Kartenstil-Wechsel, Workspace-Rückkehr oder Map-Neuinitialisierung ändert sich `mapLibreMap` (null → neue Instanz). Da keiner der Keys sich änderte (routePlanline war bereits befüllt), feuerte das Effect nicht erneut → Karte zeigte leere Zeichenschicht, obwohl alle Daten vorhanden waren. ### Root Cause 3 – Fehlende Auto-Persistenz beim Plan-Aufbau In v2.7.3 wurde die Planlinie erst dann in die DB gespeichert, wenn der Nutzer auf **„Planlinie zeigen"** tippte (`onShowPlanline`). Wenn der Nutzer Start+Ziel setzte, aber NICHT den Button drückte, gab es: - `routePlanline` im RAM (korrekt) - Kein `activeTourRoute` in der DB - `savedRouteLine` = leer - Bei Workspace-Wechsel oder Stylewechsel: `mapLibreMap`-Neustart → Effect feuerte, aber `routePlanline` war (nach async Restore) noch nicht befüllt → keine Linie ### Wann wird `routePlanline` gesetzt? `routePlanline` ist ein `remember(routePlan)` – es wird **synchron** aus `routePlan` abgeleitet. `routePlan` selbst wird durch drei Restore-Pfade gesetzt, alle asynchron via `LaunchedEffect`: 1. `LaunchedEffect(selectedTour)` → aus `currentSessionPlan` (RAM) 2. `LaunchedEffect(currentSessionPlan, selectedTour)` → Plan-Restore nach Workspace-Wechsel 3. `LaunchedEffect(activeTourRoute, selectedTour)` → Cold-Start aus DB-TourRoute Das bedeutet: beim ersten Render ist `routePlanline` leer. Das Drawing-LaunchedEffect feuert mit `styleReady=true` und `mapLibreMap!=null`, aber `routePlanline.size < 2`. Kurz danach werden die Stops restored → `routePlanline` befüllt → Effect feuert erneut. Das funktioniert, SOLANGE `mapLibreMap` sich nicht danach ändert (was bei Stylewechsel passiert). ### Anzeigeschalter `showRouteOverlay` `showRouteOverlay` ist ein lokaler `var` State (kein DB-Wert). Er startet als `true` und bleibt beim Workspace-Wechsel im RAM erhalten (weil er in der Composable deklariert ist und deren Lifetime folgt). Bei Composable-Neustart (Prozess-Kill) startet er wieder auf `true`. Kein Bug hier. ### Wird `activeTourRoute` PLANLINE tatsächlich geladen? Ja – `activeTourRoute` wird in `WaypointViewModel` aus dem `tourRoutes`-StateFlow befüllt: ```kotlin _activeTourRoute.value = map[_selectedTour.value] ``` `tourRoutes` kommt direkt aus dem `WaypointRepository` (DataStore). PLANLINE wird nicht ausgeschlossen. Aber der Phantom-Guard in `savedRouteLine` unterdrückte die Anzeige (Root Cause 1). --- ## Fixes in v2.7.4 ### Fix 1 – `savedRouteLine`: PLANLINE-Ausnahme vom Phantom-Guard ```kotlin val savedRouteLine: List = remember(activeTourRoute, isNeutralStandard, isRecording) { val r = activeTourRoute if (r == null || r.source == RouteSourceKind.MANUAL) emptyList() // v2.7.4: PLANLINE ist explizite Nutzeraktion, kein Phantom else if (r.source == RouteSourceKind.PLANLINE) r.polyline.map { LatLng(it.lat, it.lon) } // Phantom-Guard gilt weiterhin für OSRM/TRACK/IMPORTED else if (isNeutralStandard && !isRecording) emptyList() else r.polyline.map { LatLng(it.lat, it.lon) } } ``` Phantom-Guard bleibt für OSRM/TRACK/IMPORTED aktiv. PLANLINE ist explizit immer sichtbar. ### Fix 2 – `mapLibreMap` als Key im Drawing-LaunchedEffect ```kotlin LaunchedEffect(routeLine, routePlanline, routePlan, showRouteOverlay, styleReady, savedRouteLine, mapLibreMap) { // ← mapLibreMap neu ``` Jetzt feuert das Effect auch nach Kartenneuinitialisierung, Stylewechsel und Workspace-Rückkehr. ### Fix 3 – Auto-Persistenz bei `routePlanline`-Änderung Neues `LaunchedEffect(routePlanline, selectedTour)` direkt nach der `routePlanline`-Ableitung: ```kotlin LaunchedEffect(routePlanline, selectedTour) { if (routePlanline.size < 2) return@LaunchedEffect if (routeLine.size >= 2) return@LaunchedEffect // OSRM hat Vorrang val current = activeTourRoute // Nicht überschreiben wenn OSRM/MANUAL aktiv if (current != null && current.source != RouteSourceKind.PLANLINE) return@LaunchedEffect val label = routingPoiCtx.getString(R.string.tour_route_source_planline) val planlineRoute = buildTourRouteFromState( plan = routePlan, routeLine = emptyList(), routePlanline = routePlanline, ... ) ?: return@LaunchedEffect viewModel.saveTourRouteFor(selectedTour, planlineRoute) } ``` Sobald der User Start+Ziel setzt (oder Restore erfolgt), wird `routePlanline` befüllt und automatisch in die DB geschrieben. `activeTourRoute` wird damit sofort aktuell, `savedRouteLine` befüllt sich und die Linie erscheint beim nächsten Kartenneustart ohne Button. ### Fix 4 – Zeichnungsfarbe PLANLINE konsistent Im Drawing-Block: PLANLINE-Quelle bekommt dieselbe orangene Farbe (0xFFFF9800) wie `routePlanline`, kein falsches Grün (0xFF2E7D32, das für OSRM reserviert bleibt). ### Fix 5 – Zeichnungspriorität für PLANLINE-Quelle ```kotlin val hasFreshPlanline = routePlanline.size >= 2 && activeTourRoute?.source == RouteSourceKind.PLANLINE val hasSavedRoute = savedRouteLine.size >= 2 && !hasFreshPlanline ``` Wenn PLANLINE-Quelle aktiv und `routePlanline` befüllt → `routePlanline` hat Vorrang vor `savedRouteLine`. Das verhindert, dass eine veraltete DB-Linie gezeigt wird, während der User gerade neue Stops eingegeben hat. --- ## Betroffene Variablen und Layers | Variable | v2.7.3 | v2.7.4 | |---|---|---| | `savedRouteLine` | leer für PLANLINE+Standardtour | befüllt für PLANLINE immer | | Drawing-LaunchedEffect Keys | ohne `mapLibreMap` | mit `mapLibreMap` | | Auto-Persistenz | nur bei Button-Druck | bei `routePlanline`-Änderung | | PLANLINE Farbe in `savedRouteLine` | grün (falsch) | orange (korrekt) | | `hasSavedRoute` | immer aus `savedRouteLine.size` | PLANLINE-Quelle → `routePlanline` bevorzugt | --- ## Prioritätsregel v2.7.4 1. **MANUAL** – `manualRoutePoints` (eigene Schicht, immer separat) 2. **OSRM Live** – `routeLine` (blau, breit), wenn OSRM-Berechnung aktiv 3. **PLANLINE / routePlanline** – wenn `routePlan` ≥ 2 Stopps und PLANLINE-Quelle 4. **OSRM gespeichert** – `savedRouteLine` (grün), wenn OSRM-`activeTourRoute` 5. **routePlanline Fallback** – wenn kein savedRouteLine und routePlanline ≥ 2 6. **Keine Linie** – wenn weder Plan noch Route vorhanden --- ## Button „Planlinie zeigen" Der Button `onShowPlanline` **bleibt unverändert** in der UI und im Code. Er ist aber nicht mehr die Voraussetzung für Sichtbarkeit. Seine neue Rolle: - Kamera auf Planmittelpunkt fokussieren - `routeLine` leeren (bei vorhandener OSRM-Route zurück auf Planlinie wechseln) - DB-Persistenz (weiterhin als Fallback, jetzt redundant zu Auto-Persistenz) --- ## Was unverändert blieb - Datenmodell (`TourRoute`, `RouteSourceKind`, `RoutePlan`, `RouteStop`) - Backup-Export/Import-Logik - Playback-/GPS-Aufzeichnungs-Logik - Lizenzsystem - MANUAL-Routenlogik - OSRM-Routing-Logik - `onClear`-Verhalten (löscht PLANLINE aus DB, unverändert) - Alle anderen Screens/Composables --- ## Build & Signierung - **Build:** `./gradlew assembleRelease` (BUILD SUCCESSFUL) - **Signatur:** APK Signature Scheme v2 ✓ - **Keystore:** `GPS2Audio_release_keystore_v2.jks`, Alias `gps2audio_release_v2` - **applicationId:** `de.waypointaudio` (unverändert) --- ## Artefakte | Datei | Beschreibung | |---|---| | `GPS2Audio_v2_7_4_planline_auto_render_universal_signed.apk` | Signiertes Release APK (57 MB) | | `GPS2Audio_v2_7_4_planline_auto_render_source.zip` | Quellcode ohne Build-Artefakte | | `GPS2Audio_v2_7_4_planline_auto_render_handoff.md` | Dieses Dokument | --- ## Testszenarien 1. **Tour mit Start+Ziel neu anlegen → Karte öffnen**: Planlinie erscheint sofort ohne Button. 2. **App schließen (Prozess-Kill) → App neu starten → Tour öffnen**: Planlinie ist sichtbar (savedRouteLine aus DB). 3. **Kartenstil wechseln**: Planlinie bleibt nach Stylewechsel sichtbar. 4. **Anderer Workspace → zurück**: Planlinie ist sofort sichtbar. 5. **Standard-Tour mit PLANLINE**: Phantom-Guard greift nicht mehr, Linie erscheint. 6. **OSRM-Route berechnen**: Blaue OSRM-Linie hat weiterhin Vorrang, verdrängt Planlinie. 7. **MANUAL-Route**: Eigene orangene Linie, unabhängig von Planlinie. 8. **Planer leeren (onClear)**: PLANLINE aus DB gelöscht, Linie verschwindet.