Release GPS2Audio v2.5.44 POI category fix
This commit is contained in:
@@ -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.40–v2.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.38–v2.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.
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user