242 lines
10 KiB
Kotlin
242 lines
10 KiB
Kotlin
package de.waypointaudio.data
|
|
|
|
import android.content.Context
|
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
|
import androidx.datastore.preferences.core.edit
|
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
|
import androidx.datastore.preferences.preferencesDataStore
|
|
import kotlinx.coroutines.flow.Flow
|
|
import kotlinx.coroutines.flow.map
|
|
|
|
/**
|
|
* Basis-Kartenstil — v2.5.38 Explorer Map Styles.
|
|
*
|
|
* Alle Stile nutzen OpenFreeMap (https://openfreemap.org) für Vektor-Tiles,
|
|
* kostenlos ohne API-Key, ohne eigenen Server. Attribution: © OpenFreeMap,
|
|
* © OpenMapTiles, Daten: © OpenStreetMap-Mitwirkende.
|
|
*
|
|
* MODERN_2D — Positron: heller, reduzierter Vektorstil. Ideal als
|
|
* neutraler Hintergrund für Wegpunkte/Routen.
|
|
* URL: https://tiles.openfreemap.org/styles/positron
|
|
*
|
|
* DARK — Dark Matter: dunkler Vektorstil.
|
|
* URL: https://tiles.openfreemap.org/styles/dark
|
|
*
|
|
* OUTDOOR — Fiord/Outdoor: Liberty-Vektorstil mit Outdoor-/Geländecharakter.
|
|
* URL: https://tiles.openfreemap.org/styles/liberty
|
|
* (Liberty ist der vollständigste, lebendigste OpenFreeMap-Style
|
|
* mit deutlicher Grün-/Geländeanmutung.)
|
|
*
|
|
* CITY_3D — 3D Stadt: Positron-Basis + MapLibre fill-extrusion für
|
|
* Gebäude-Layer (building). Wenn kein building-Layer in der
|
|
* Style-Source, automatischer 2D-Fallback ohne Crash.
|
|
* URL: https://tiles.openfreemap.org/styles/positron
|
|
* + programmatische fill-extrusion nachträglich per addLayer.
|
|
*
|
|
* CLASSIC — Klassisch: OSM-Raster-Tiles (tile.openstreetmap.org),
|
|
* raster-basierter Fallback. Kein Vektor.
|
|
*
|
|
* SATELLITE — Legacy aus v2.4.x. Wird auf MODERN_2D migriert.
|
|
* Satellit ist ab v2.5.37 nicht mehr im UI angeboten.
|
|
* TERRAIN_3D — Legacy aus v2.4.x. Wird auf CITY_3D migriert.
|
|
* TOPO — Legacy aus v2.4.x. Wird auf OUTDOOR migriert.
|
|
* PERSPECTIVE — Legacy aus v2.3.x. Wird auf CITY_3D migriert.
|
|
*/
|
|
enum class MapBaseStyle(val key: String) {
|
|
MODERN_2D("modern_2d"),
|
|
DARK("dark"),
|
|
OUTDOOR("outdoor"),
|
|
CITY_3D("city_3d"),
|
|
CLASSIC("classic"),
|
|
// Legacy — für Rückwärtskompatibilität gespeicherter Settings:
|
|
SATELLITE("satellite"),
|
|
TERRAIN_3D("terrain_3d"),
|
|
TOPO("topo"),
|
|
PERSPECTIVE("perspective");
|
|
|
|
companion object {
|
|
val DEFAULT = MODERN_2D
|
|
fun fromKey(key: String?): MapBaseStyle {
|
|
return when (key) {
|
|
"modern_2d" -> MODERN_2D
|
|
"dark" -> DARK
|
|
"outdoor" -> OUTDOOR
|
|
"city_3d" -> CITY_3D
|
|
"classic" -> CLASSIC
|
|
// Legacy migration:
|
|
"satellite" -> MODERN_2D
|
|
"terrain_3d" -> CITY_3D
|
|
"topo" -> OUTDOOR
|
|
"perspective"-> CITY_3D
|
|
else -> DEFAULT
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POI-Kategorie-Auswahl — v2.5.38.
|
|
*
|
|
* Jede Kategorie schaltet eine Gruppe von MapLibre-Symbol-Layern sichtbar/unsichtbar.
|
|
* Wenn der Style keinen passenden Layer enthält, passiert nichts (kein Crash).
|
|
*
|
|
* Kategorien:
|
|
* SIGHTS — Sehenswürdigkeiten / Tourismus (tourism, historic, amenity:place_of_worship)
|
|
* INFRASTRUCTURE — Infrastruktur (hospital, pharmacy, police, post, atm, bank, fuel)
|
|
* GASTRONOMY — Gastronomie (restaurant, cafe, fast_food, bar, pub, ice_cream)
|
|
* MOBILITY — Mobilität (bus_stop, train_station, parking, bicycle, car_sharing)
|
|
*/
|
|
enum class PoiCategory(val key: String) {
|
|
SIGHTS("sights"),
|
|
INFRASTRUCTURE("infrastructure"),
|
|
GASTRONOMY("gastronomy"),
|
|
MOBILITY("mobility");
|
|
|
|
companion object {
|
|
fun fromKey(key: String?): PoiCategory? = values().firstOrNull { it.key == key }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Persistierte POI-Sichtbarkeit je Kategorie — v2.5.44.
|
|
* Default: Alle vier Hauptkategorien aktiv, damit der Nutzer sofort
|
|
* alle POI-Typen sieht (Fix: vorher nur Sehenswürdigkeiten sichtbar).
|
|
* Namen-Labels bleiben standardmäßig aus (Performance/Übersichtlichkeit).
|
|
*/
|
|
data class PoiLayerSettings(
|
|
val showSights: Boolean = true,
|
|
val showInfrastructure: Boolean = true,
|
|
val showGastronomy: Boolean = true,
|
|
val showMobility: Boolean = true,
|
|
// v2.5.42 — Namen-Labels auf der Karte standardmäßig deaktiviert
|
|
val showPoiNames: Boolean = false,
|
|
)
|
|
|
|
/**
|
|
* Gespeicherte Karten-Stil-Einstellungen — v2.5.38.
|
|
*
|
|
* @param baseStyle gewählter Basis-Stil (wird persistent gespeichert)
|
|
* @param showAudioLength ob die visuelle Audio-/Trigger-Länge als
|
|
* Ring/Indikator je Wegpunkt eingeblendet wird.
|
|
* @param perspectiveTilt Kamera-Pitch für CITY_3D (Grad, 0..60).
|
|
* @param showAudioLines Clip-Längen als projizierte Liniensegmente.
|
|
* @param routingEndpoint optionaler OSRM-Routing-Endpoint.
|
|
* @param poiLayers POI-Kategorien-Sichtbarkeit (persistent).
|
|
*
|
|
* Legacy-Felder (customSatelliteUrl, customTopoUrl, customTerrainDemUrl) werden
|
|
* weiterhin gelesen, aber nicht mehr in der UI angeboten — für Rückwärts-
|
|
* kompatibilität mit gespeicherten Preferences aus v2.4.x.
|
|
*/
|
|
data class MapStyleSettings(
|
|
val baseStyle: MapBaseStyle = MapBaseStyle.DEFAULT,
|
|
val customSatelliteUrl: String = "", // legacy, wird ignoriert
|
|
val customTopoUrl: String = "", // legacy, wird ignoriert
|
|
val customTerrainDemUrl: String = "", // legacy, wird ignoriert
|
|
val showAudioLength: Boolean = true,
|
|
val perspectiveTilt: Int = 35,
|
|
val showAudioLines: Boolean = true,
|
|
val routingEndpoint: String = "",
|
|
val poiLayers: PoiLayerSettings = PoiLayerSettings(),
|
|
)
|
|
|
|
private val Context.mapStyleDataStore by preferencesDataStore(name = "map_style_settings")
|
|
|
|
class MapStyleStore(private val context: Context) {
|
|
|
|
private companion object {
|
|
val KEY_BASE_STYLE = stringPreferencesKey("base_style")
|
|
val KEY_SAT_URL = stringPreferencesKey("custom_satellite_url")
|
|
val KEY_TOPO_URL = stringPreferencesKey("custom_topo_url")
|
|
val KEY_TERRAIN_DEM_URL = stringPreferencesKey("custom_terrain_dem_url")
|
|
val KEY_SHOW_AUDIO_LEN = booleanPreferencesKey("show_audio_length")
|
|
val KEY_PERSPECTIVE_TILT = stringPreferencesKey("perspective_tilt")
|
|
val KEY_SHOW_AUDIO_LINES = booleanPreferencesKey("show_audio_lines")
|
|
val KEY_ROUTING_ENDPOINT = stringPreferencesKey("routing_endpoint")
|
|
// v2.5.38 — POI-Layer-Persistenz
|
|
val KEY_POI_SIGHTS = booleanPreferencesKey("poi_sights")
|
|
val KEY_POI_INFRASTRUCTURE = booleanPreferencesKey("poi_infrastructure")
|
|
val KEY_POI_GASTRONOMY = booleanPreferencesKey("poi_gastronomy")
|
|
val KEY_POI_MOBILITY = booleanPreferencesKey("poi_mobility")
|
|
// v2.5.42
|
|
val KEY_POI_SHOW_NAMES = booleanPreferencesKey("poi_show_names")
|
|
}
|
|
|
|
val settings: Flow<MapStyleSettings> = context.mapStyleDataStore.data.map { prefs ->
|
|
MapStyleSettings(
|
|
baseStyle = MapBaseStyle.fromKey(prefs[KEY_BASE_STYLE]),
|
|
customSatelliteUrl = prefs[KEY_SAT_URL].orEmpty(),
|
|
customTopoUrl = prefs[KEY_TOPO_URL].orEmpty(),
|
|
customTerrainDemUrl = prefs[KEY_TERRAIN_DEM_URL].orEmpty(),
|
|
showAudioLength = prefs[KEY_SHOW_AUDIO_LEN] ?: true,
|
|
perspectiveTilt = prefs[KEY_PERSPECTIVE_TILT]?.toIntOrNull()?.coerceIn(0, 60) ?: 35,
|
|
showAudioLines = prefs[KEY_SHOW_AUDIO_LINES] ?: true,
|
|
routingEndpoint = prefs[KEY_ROUTING_ENDPOINT].orEmpty(),
|
|
poiLayers = PoiLayerSettings(
|
|
showSights = prefs[KEY_POI_SIGHTS] ?: true,
|
|
// v2.5.44: Default auf true geändert — Nutzer ohne gespeicherte
|
|
// Einstellung bekommen jetzt alle Kategorien aktiv
|
|
showInfrastructure = prefs[KEY_POI_INFRASTRUCTURE] ?: true,
|
|
showGastronomy = prefs[KEY_POI_GASTRONOMY] ?: true,
|
|
showMobility = prefs[KEY_POI_MOBILITY] ?: true,
|
|
showPoiNames = prefs[KEY_POI_SHOW_NAMES] ?: false,
|
|
)
|
|
)
|
|
}
|
|
|
|
suspend fun setBaseStyle(style: MapBaseStyle) {
|
|
context.mapStyleDataStore.edit { it[KEY_BASE_STYLE] = style.key }
|
|
}
|
|
|
|
suspend fun setShowAudioLength(enabled: Boolean) {
|
|
context.mapStyleDataStore.edit { it[KEY_SHOW_AUDIO_LEN] = enabled }
|
|
}
|
|
|
|
suspend fun setPerspectiveTilt(tilt: Int) {
|
|
context.mapStyleDataStore.edit {
|
|
it[KEY_PERSPECTIVE_TILT] = tilt.coerceIn(0, 60).toString()
|
|
}
|
|
}
|
|
|
|
suspend fun setShowAudioLines(enabled: Boolean) {
|
|
context.mapStyleDataStore.edit { it[KEY_SHOW_AUDIO_LINES] = enabled }
|
|
}
|
|
|
|
// v2.5.38 — POI-Layer-Persistenz
|
|
suspend fun setPoiLayers(poi: PoiLayerSettings) {
|
|
context.mapStyleDataStore.edit {
|
|
it[KEY_POI_SIGHTS] = poi.showSights
|
|
it[KEY_POI_INFRASTRUCTURE] = poi.showInfrastructure
|
|
it[KEY_POI_GASTRONOMY] = poi.showGastronomy
|
|
it[KEY_POI_MOBILITY] = poi.showMobility
|
|
it[KEY_POI_SHOW_NAMES] = poi.showPoiNames
|
|
}
|
|
}
|
|
|
|
// v2.5.37 — Legacy-Stubs für Aufrufstellen aus v2.4.x die
|
|
// noch customSatelliteUrl / customTopoUrl / customTerrainDemUrl schreiben.
|
|
// Diese Werte werden weiterhin gelesen (Preferences bleiben),
|
|
// aber im UI nicht mehr angeboten und vom Style-Builder ignoriert.
|
|
suspend fun setCustomSatelliteUrl(url: String) {
|
|
context.mapStyleDataStore.edit { it[KEY_SAT_URL] = url.trim() }
|
|
}
|
|
suspend fun setCustomTopoUrl(url: String) {
|
|
context.mapStyleDataStore.edit { it[KEY_TOPO_URL] = url.trim() }
|
|
}
|
|
suspend fun setCustomTerrainDemUrl(url: String) {
|
|
context.mapStyleDataStore.edit { it[KEY_TERRAIN_DEM_URL] = url.trim() }
|
|
}
|
|
|
|
/**
|
|
* Speichert den Routing-Endpunkt — v2.4.4: nutzt
|
|
* [de.waypointaudio.util.OsrmRoutingClient.normalizeBase], sodass der
|
|
* Nutzer sowohl die reine Basis-URL (`https://router.project-osrm.org`)
|
|
* als auch die vollständige `/route/v1/driving`-Variante speichern darf.
|
|
* Intern landet in den Preferences immer die normalisierte Basis-URL,
|
|
* was Doppelprefixe bei der eigentlichen Request-URL ausschließt.
|
|
*/
|
|
suspend fun setRoutingEndpoint(url: String) {
|
|
val normalized = de.waypointaudio.util.OsrmRoutingClient.normalizeBase(url)
|
|
context.mapStyleDataStore.edit { it[KEY_ROUTING_ENDPOINT] = normalized }
|
|
}
|
|
}
|