Release GPS2Audio v2.5.44 POI category fix

This commit is contained in:
Perplexity Computer
2026-05-30 05:42:44 +00:00
parent 18ea77b21d
commit 014e6c1bf1
100 changed files with 28584 additions and 1083 deletions
@@ -0,0 +1,239 @@
# GPS2Audio v2.5.44 — Handoff: POI Category Fix
**Version:** 2.5.44 · versionCode 98
**Basis:** v2.5.43 (Canvas-Piktogramme, versionCode 97)
**Datum:** 2025-05-30
---
## Artefakte
| Datei | Beschreibung |
|---|---|
| `GPS2Audio_v2_5_44_poi_category_fix_universal_signed.apk` | Signierte Universal-Release-APK (57 MB) |
| `GPS2Audio_v2_5_44_poi_category_fix_source.zip` | Vollständiger Quellcode (ohne Keystore/Credentials) |
| `GPS2Audio_v2_5_44_poi_category_fix_handoff.md` | Dieses Dokument |
**APK-Prüfung:**
- Signatur: APK Signature Scheme v2 ✓ (Verified)
- applicationId: `de.waypointaudio`
- versionCode: 98
- versionName: 2.5.44
---
## Root Cause
### Primärer Bug: Falsche Default-Einstellungen in `PoiLayerSettings`
In `data/MapStyleSettings.kt` war der Default für alle Nicht-SIGHTS-Kategorien `false`:
```kotlin
// v2.5.43 (FALSCH):
data class PoiLayerSettings(
val showSights: Boolean = true,
val showInfrastructure: Boolean = false, // ← Bug: default aus
val showGastronomy: Boolean = false, // ← Bug: default aus
val showMobility: Boolean = false, // ← Bug: default aus
val showPoiNames: Boolean = false,
)
```
Erstnutzer oder Nutzer ohne gespeicherte Preferences sahen deshalb **nur Sehenswürdigkeiten** (SIGHTS). Da `PoiLayerSettings.showSights=true` und der SIGHTS-Filter `historic=*` enthält, wurden ausschließlich historische/touristische POIs geladen und angezeigt.
### Sekundärer Bug: `humanizeTag()` filterte namenlose Nicht-SIGHTS-POIs heraus
In `ExplorerPoiClient.humanizeTag()` wurde für die SIGHTS-Kategorie geprüft, ob der Label **nicht** "Sehenswürdigkeit" ist. Für GASTRONOMY/MOBILITY/INFRASTRUCTURE war der Rückgabewert korrekt. Allerdings bedeutete die Logik bei `tourism=attraction`-Elementen ohne Namen: `null → continue → überspringen`. Das betraf hauptsächlich namenlose Sehenswürdigkeiten, aber auch Grenzfälle mit mehreren Tags.
### Tertiärer Aspekt: Fehlende Kategorie-Diversität bei Default-Ladevorgang
Da nur SIGHTS geladen wurde und `historic=*` ein sehr breiter Wildcard-Filter ist, dominierten Denkmäler/Historisches die Ergebnisse — auch wenn theoretisch andere tourism-Werte dabei sein sollten.
---
## Geänderte Dateien
### 1. `app/src/main/kotlin/de/waypointaudio/data/MapStyleSettings.kt`
**Änderung 1:** Default-Werte in `PoiLayerSettings` auf `true` gesetzt:
```kotlin
// v2.5.44 (FIX):
data class PoiLayerSettings(
val showSights: Boolean = true,
val showInfrastructure: Boolean = true, // ← Fix: default an
val showGastronomy: Boolean = true, // ← Fix: default an
val showMobility: Boolean = true, // ← Fix: default an
val showPoiNames: Boolean = false,
)
```
**Änderung 2:** DataStore-Fallback ebenfalls auf `true` gesetzt (für Nutzer mit bestehender Installation aber noch nicht gespeicherten Preferences für diese Felder):
```kotlin
poiLayers = PoiLayerSettings(
showSights = prefs[KEY_POI_SIGHTS] ?: true,
showInfrastructure = prefs[KEY_POI_INFRASTRUCTURE] ?: true, // ← Fix
showGastronomy = prefs[KEY_POI_GASTRONOMY] ?: true, // ← Fix
showMobility = prefs[KEY_POI_MOBILITY] ?: true, // ← Fix
showPoiNames = prefs[KEY_POI_SHOW_NAMES] ?: false,
)
```
---
### 2. `app/src/main/kotlin/de/waypointaudio/util/ExplorerPoiClient.kt`
**Änderung 1 — `humanizeTag()` korrigiert:**
Namenlose POIs in GASTRONOMY/MOBILITY/INFRASTRUCTURE werden nun nicht mehr verworfen. Für SIGHTS bleibt die Filterlogik erhalten (generisches "Sehenswürdigkeit" ohne spezifischen Tag = überspringen), aber `tourism=*`-Tags werden jetzt ebenfalls als valide Fallback-Basis anerkannt:
```kotlin
private fun humanizeTag(tags: Map<String, String>, cat: PoiCategory): String? {
return when (cat) {
PoiCategory.SIGHTS -> {
val label = resolveCategoryLabel(tags, cat)
when {
tags["historic"] != null -> label // Denkmäler etc. immer behalten
tags["tourism"] != null -> label // viewpoint, museum etc.
tags["amenity"] in setOf("theatre","arts_centre","cinema","place_of_worship") -> label
else -> null // zu generisch → überspringen
}
}
// GASTRONOMY, MOBILITY, INFRASTRUCTURE: immer Fallback-Label zurückgeben
PoiCategory.GASTRONOMY -> resolveCategoryLabel(tags, cat)
PoiCategory.MOBILITY -> resolveCategoryLabel(tags, cat)
PoiCategory.INFRASTRUCTURE -> resolveCategoryLabel(tags, cat)
}
}
```
**Änderung 2 — `resolveCategoryLabel()` Priorität korrigiert:**
Die Kategorie-spezifischen Nicht-SIGHTS-Kategorien werden jetzt zuerst ausgewertet, bevor in den SIGHTS-Block gefallen wird:
```kotlin
private fun resolveCategoryLabel(tags: Map<String, String>, cat: PoiCategory): String {
// Prüfe zuerst spezifische Nicht-SIGHTS-Tags (GASTRONOMY/MOBILITY/INFRASTRUCTURE)
if (cat != PoiCategory.SIGHTS) {
when (cat) {
PoiCategory.GASTRONOMY -> return when (amenity) { ... }
PoiCategory.MOBILITY -> return when { ... }
PoiCategory.INFRASTRUCTURE -> return when (amenity) { ... }
else -> {}
}
}
// Dann SIGHTS-Mapping
return when (cat) { ... }
}
```
**Änderung 3 — `categoryFilters()` erweitert:**
Neue OSM-Tags hinzugefügt:
- SIGHTS: `amenity=place_of_worship`
- MOBILITY: `amenity=ferry_terminal`, `railway=tram_stop`, `aeroway=aerodrome`
- INFRASTRUCTURE: `amenity=drinking_water`, `amenity=shelter`
**Änderung 4 — `summarize()` Hilfsfunktion hinzugefügt:**
```kotlin
fun summarize(result: Map<PoiCategory, List<ExplorerPoiResult>>): String {
// Gibt z.B. "POIs: 23 geladen (12 Sehensw., 5 Gastro, 3 Mobilität, 3 Infra)" zurück
}
```
**Änderung 5 — Logcat-Zusammenfassung nach Laden:**
```
I/ExplorerPoiClient: POI-Load: 23 gesamt — 12 Sehenswürdigkeiten, 5 Gastronomie, 3 Mobilität, 3 Infrastruktur
```
**Änderung 6 — `humanizeHistoric()` erweitert:**
Neue Historic-Werte: `manor`, `wayside_cross`, `wayside_shrine`, `city_gate`, `locomotive`, `aircraft`, `ship`.
---
### 3. `app/src/main/kotlin/de/waypointaudio/ui/ExplorerScreenMapLibre.kt`
**Änderung — Snackbar-Text nach POI-Laden:**
Statt nur "Keine POIs gefunden" oder leer bei Erfolg wird nun die Zusammenfassung als Snackbar angezeigt:
```kotlin
explorerPoiSnackbar = if (total == 0) {
"Keine POIs in diesem Bereich gefunden."
} else {
ExplorerPoiClient.summarize(results)
}
// → "POIs: 23 geladen (12 Sehensw., 5 Gastro, 3 Mobilität, 3 Infra)"
```
---
## Erhaltene Funktionen (keine Regressionen)
Alle Funktionen aus v2.5.40v2.5.43 sind unverändert erhalten:
| Feature | Status |
|---|---|
| Echte Overpass-POIs im Explorer | ✓ unverändert |
| POI-Detail-Sheet (RoutingPoiSheet) | ✓ unverändert |
| POI als Wegpunkt übernehmen | ✓ unverändert |
| Kompass / Nordausrichtung | ✓ unverändert |
| Buttonlayout ohne Überlagerung | ✓ unverändert |
| Layer-Sheet scrollbar | ✓ unverändert |
| Karten-Attribution rechtskonform | ✓ unverändert |
| Persistenter Kartentyp / POI-Layer / POI-Namen-Toggle | ✓ unverändert |
| Phantom-Track-Fixes | ✓ unverändert |
| Routenplaner / Waypoints | ✓ unverändert |
| Lizenzsystem | ✓ unverändert |
| Multi-Clip / Sequenzplayer | ✓ unverändert |
| Canvas-Piktogramme (v2.5.43) | ✓ unverändert |
| POI-Legende (v2.5.41) | ✓ unverändert |
| POI-Namen-Labels Toggle (v2.5.42) | ✓ unverändert |
---
## Tests / Build-Checks
| Schritt | Ergebnis |
|---|---|
| Kotlin-Kompilierung (`assembleRelease`) | BUILD SUCCESSFUL |
| APK-Signatur-Verifikation (`apksigner verify`) | Verifies ✓ |
| APK Scheme v2 | Verified ✓ |
| applicationId | `de.waypointaudio` ✓ |
| versionCode | 98 ✓ |
| versionName | 2.5.44 ✓ |
| Runtime-Test | Nicht möglich (kein verbundenes Gerät im Build-Environment) |
---
## Bekannte Grenzen
1. **Nutzer mit gespeicherten Preferences (v2.5.38v2.5.43):** Wer aktiv Gastronomie/Mobilität/Infrastruktur auf `false` gesetzt hat, behält diese Einstellung (DataStore-Wert ist explizit `false`, Fallback greift nicht). Nur Nutzer, die **nie** die Kategorien geändert haben, bekommen das neue Default.
2. **Keine Laufzeit-Tests auf echtem Gerät:** Build-Environment hat keinen verbundenen Android-Emulator. POI-Abfragen wurden anhand der Overpass-Abfrage-Logik und Code-Review verifiziert.
3. **Overpass-Rate-Limits:** Bei gleichzeitigem Laden aller 4 Kategorien werden 4 serielle HTTP-Anfragen abgeschickt (je Kategorie eine). In Spitzenzeiten kann dies zu Timeouts führen (bestehende Logik, kein Regressionsrisiko).
4. **Cache nicht invalidiert bei Default-Änderung:** Wenn ein Nutzer zuvor bereits POIs geladen hat (nur SIGHTS), zeigt der Cache weiterhin nur SIGHTS bis er manuell leert (App-Neustart oder Tab-Wechsel).
---
## Overpass-Abfragen je Kategorie (Beispiel, Radius 1500 m)
```
[out:json][timeout:25];
(
node[amenity=restaurant](around:1500,...);
way[amenity=restaurant](around:1500,...);
relation[amenity=restaurant](around:1500,...);
node[amenity=cafe](around:1500,...);
... (alle GASTRONOMY-Filter als OR-Union)
);
out center 100;
```
Jede Kategorie ist eine separate Anfrage. Die UI zeigt alle aktivierten Kategorien per OR: wenn GASTRONOMY + MOBILITY aktiv, werden zwei Anfragen abgeschickt und die Ergebnisse zusammengeführt.