Initial release of GPS2Audio
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
package de.waypointaudio.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
// DataStore-Instanz als Extension Property (Singleton pro Context)
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "waypoints")
|
||||
|
||||
/**
|
||||
* Lokaler persistenter Speicher für Wegpunkte und Touren.
|
||||
* Nutzt Jetpack DataStore mit JSON-Serialisierung via Gson.
|
||||
*
|
||||
* tourList speichert die geordnete Liste aller Tournamen (inkl. Standardtour).
|
||||
* Touren werden zusätzlich aus den Wegpunkt-tourName-Feldern abgeleitet,
|
||||
* damit auch leere Touren (ohne Wegpunkte) persistiert werden.
|
||||
*
|
||||
* tourPlaybackDefaults speichert die tour-weiten Abspiel-Vorgaben als JSON-Map.
|
||||
*/
|
||||
class WaypointStore(private val context: Context) {
|
||||
|
||||
private val gson: Gson = GsonBuilder().create()
|
||||
private val waypointListType = object : TypeToken<List<Waypoint>>() {}.type
|
||||
private val stringListType = object : TypeToken<List<String>>() {}.type
|
||||
private val defaultsMapType = object : TypeToken<Map<String, TourPlaybackDefaults>>() {}.type
|
||||
|
||||
companion object {
|
||||
private val KEY_WAYPOINTS = stringPreferencesKey("waypoints_json")
|
||||
private val KEY_TOURS = stringPreferencesKey("tours_json")
|
||||
private val KEY_TOUR_DEFAULTS = stringPreferencesKey("tour_playback_defaults_json")
|
||||
private val KEY_GPS_TRACKS = stringPreferencesKey("gps_tracks_json")
|
||||
}
|
||||
|
||||
/** Liefert einen Flow mit der aktuellen Wegpunktliste. */
|
||||
val waypointsFlow: Flow<List<Waypoint>> = context.dataStore.data.map { prefs ->
|
||||
val json = prefs[KEY_WAYPOINTS] ?: "[]"
|
||||
runCatching {
|
||||
gson.fromJson<List<Waypoint>>(json, waypointListType) ?: emptyList()
|
||||
}.getOrDefault(emptyList())
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert einen Flow mit der persistierten Tourliste.
|
||||
* Die Liste enthält mindestens die Standardtour.
|
||||
* Touren aus Wegpunkten werden ggf. ergänzt, aber die Reihenfolge
|
||||
* (und leere Touren) stammen aus diesem Key.
|
||||
*/
|
||||
val toursFlow: Flow<List<String>> = context.dataStore.data.map { prefs ->
|
||||
val json = prefs[KEY_TOURS] ?: "[]"
|
||||
val stored = runCatching {
|
||||
gson.fromJson<List<String>>(json, stringListType) ?: emptyList()
|
||||
}.getOrDefault(emptyList())
|
||||
// Immer mindestens die Standardtour enthalten
|
||||
if (stored.isEmpty()) listOf(Waypoint.DEFAULT_TOUR_NAME) else stored
|
||||
}
|
||||
|
||||
/** Liefert einen Flow mit der Map tour → TourPlaybackDefaults. */
|
||||
val tourDefaultsFlow: Flow<Map<String, TourPlaybackDefaults>> =
|
||||
context.dataStore.data.map { prefs ->
|
||||
val json = prefs[KEY_TOUR_DEFAULTS] ?: "{}"
|
||||
runCatching {
|
||||
gson.fromJson<Map<String, TourPlaybackDefaults>>(json, defaultsMapType) ?: emptyMap()
|
||||
}.getOrDefault(emptyMap())
|
||||
}
|
||||
|
||||
/** GPS-Track-Flow: Map tour → Liste von TrackPoint. */
|
||||
val gpsTracksFlow: Flow<Map<String, List<GpsTrackPoint>>> =
|
||||
context.dataStore.data.map { prefs ->
|
||||
val json = prefs[KEY_GPS_TRACKS] ?: "{}"
|
||||
runCatching {
|
||||
val mapType = object : TypeToken<Map<String, List<GpsTrackPoint>>>() {}.type
|
||||
gson.fromJson<Map<String, List<GpsTrackPoint>>>(json, mapType) ?: emptyMap()
|
||||
}.getOrDefault(emptyMap())
|
||||
}
|
||||
|
||||
/** Speichert die gesamte Wegpunktliste. */
|
||||
suspend fun saveAll(waypoints: List<Waypoint>) {
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[KEY_WAYPOINTS] = gson.toJson(waypoints)
|
||||
}
|
||||
}
|
||||
|
||||
/** Speichert die gesamte Tourliste (Reihenfolge bleibt erhalten). */
|
||||
suspend fun saveTours(tours: List<String>) {
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[KEY_TOURS] = gson.toJson(tours)
|
||||
}
|
||||
}
|
||||
|
||||
/** Speichert oder aktualisiert die Abspiel-Vorgaben einer Tour. */
|
||||
suspend fun saveTourDefaults(tourName: String, defaults: TourPlaybackDefaults) {
|
||||
context.dataStore.edit { prefs ->
|
||||
val json = prefs[KEY_TOUR_DEFAULTS] ?: "{}"
|
||||
val map: MutableMap<String, TourPlaybackDefaults> = runCatching {
|
||||
gson.fromJson<Map<String, TourPlaybackDefaults>>(json, defaultsMapType)
|
||||
?.toMutableMap()
|
||||
}.getOrNull() ?: mutableMapOf()
|
||||
map[tourName] = defaults
|
||||
prefs[KEY_TOUR_DEFAULTS] = gson.toJson(map)
|
||||
}
|
||||
}
|
||||
|
||||
/** Liest die Abspiel-Vorgaben einer Tour einmalig (ohne Flow). */
|
||||
suspend fun getTourDefaults(tourName: String): TourPlaybackDefaults {
|
||||
val json = context.dataStore.data.first()[KEY_TOUR_DEFAULTS] ?: "{}"
|
||||
val map: Map<String, TourPlaybackDefaults> = runCatching {
|
||||
gson.fromJson<Map<String, TourPlaybackDefaults>>(json, defaultsMapType) ?: emptyMap()
|
||||
}.getOrDefault(emptyMap())
|
||||
return map[tourName] ?: TourPlaybackDefaults(tourName = tourName)
|
||||
}
|
||||
|
||||
/** Speichert den GPS-Track einer Tour. */
|
||||
suspend fun saveGpsTrack(tourName: String, points: List<GpsTrackPoint>) {
|
||||
context.dataStore.edit { prefs ->
|
||||
val json = prefs[KEY_GPS_TRACKS] ?: "{}"
|
||||
val mapType = object : TypeToken<Map<String, List<GpsTrackPoint>>>() {}.type
|
||||
val map: MutableMap<String, List<GpsTrackPoint>> = runCatching {
|
||||
gson.fromJson<Map<String, List<GpsTrackPoint>>>(json, mapType)?.toMutableMap()
|
||||
}.getOrNull() ?: mutableMapOf()
|
||||
map[tourName] = points
|
||||
prefs[KEY_GPS_TRACKS] = gson.toJson(map)
|
||||
}
|
||||
}
|
||||
|
||||
/** Löscht den GPS-Track einer Tour. */
|
||||
suspend fun clearGpsTrack(tourName: String) {
|
||||
context.dataStore.edit { prefs ->
|
||||
val json = prefs[KEY_GPS_TRACKS] ?: "{}"
|
||||
val mapType = object : TypeToken<Map<String, List<GpsTrackPoint>>>() {}.type
|
||||
val map: MutableMap<String, List<GpsTrackPoint>> = runCatching {
|
||||
gson.fromJson<Map<String, List<GpsTrackPoint>>>(json, mapType)?.toMutableMap()
|
||||
}.getOrNull() ?: mutableMapOf()
|
||||
map.remove(tourName)
|
||||
prefs[KEY_GPS_TRACKS] = gson.toJson(map)
|
||||
}
|
||||
}
|
||||
|
||||
/** Fügt einen Wegpunkt hinzu oder aktualisiert einen vorhandenen (matching id). */
|
||||
suspend fun upsert(waypoint: Waypoint) {
|
||||
context.dataStore.edit { prefs ->
|
||||
val json = prefs[KEY_WAYPOINTS] ?: "[]"
|
||||
val list: MutableList<Waypoint> = runCatching {
|
||||
gson.fromJson<List<Waypoint>>(json, waypointListType)?.toMutableList()
|
||||
}.getOrNull() ?: mutableListOf()
|
||||
|
||||
val index = list.indexOfFirst { it.id == waypoint.id }
|
||||
if (index >= 0) list[index] = waypoint else list.add(waypoint)
|
||||
prefs[KEY_WAYPOINTS] = gson.toJson(list)
|
||||
}
|
||||
}
|
||||
|
||||
/** Löscht einen Wegpunkt anhand seiner ID. */
|
||||
suspend fun delete(id: String) {
|
||||
context.dataStore.edit { prefs ->
|
||||
val json = prefs[KEY_WAYPOINTS] ?: "[]"
|
||||
val list: MutableList<Waypoint> = runCatching {
|
||||
gson.fromJson<List<Waypoint>>(json, waypointListType)?.toMutableList()
|
||||
}.getOrNull() ?: mutableListOf()
|
||||
list.removeAll { it.id == id }
|
||||
prefs[KEY_WAYPOINTS] = gson.toJson(list)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inkrementiert den playCount eines Wegpunkts atomar.
|
||||
* Wird vom WaypointLocationService nach erfolgreicher Wiedergabe aufgerufen.
|
||||
* @return den neuen Wegpunkt mit inkrementiertem playCount, oder null wenn nicht gefunden.
|
||||
*/
|
||||
suspend fun incrementPlayCount(waypointId: String): Waypoint? {
|
||||
var updated: Waypoint? = null
|
||||
context.dataStore.edit { prefs ->
|
||||
val json = prefs[KEY_WAYPOINTS] ?: "[]"
|
||||
val list: MutableList<Waypoint> = runCatching {
|
||||
gson.fromJson<List<Waypoint>>(json, waypointListType)?.toMutableList()
|
||||
}.getOrNull() ?: mutableListOf()
|
||||
|
||||
val index = list.indexOfFirst { it.id == waypointId }
|
||||
if (index >= 0) {
|
||||
val wp = list[index].copy(playCount = list[index].playCount + 1)
|
||||
list[index] = wp
|
||||
updated = wp
|
||||
}
|
||||
prefs[KEY_WAYPOINTS] = gson.toJson(list)
|
||||
}
|
||||
return updated
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den playCount aller Wegpunkte einer Tour auf 0 zurück.
|
||||
* @return Anzahl der zurückgesetzten Wegpunkte.
|
||||
*/
|
||||
suspend fun resetPlayCountsForTour(tourName: String): Int {
|
||||
var count = 0
|
||||
context.dataStore.edit { prefs ->
|
||||
val json = prefs[KEY_WAYPOINTS] ?: "[]"
|
||||
val list: MutableList<Waypoint> = runCatching {
|
||||
gson.fromJson<List<Waypoint>>(json, waypointListType)?.toMutableList()
|
||||
}.getOrNull() ?: mutableListOf()
|
||||
|
||||
for (i in list.indices) {
|
||||
val wp = list[i]
|
||||
if (wp.tourName.ifBlank { Waypoint.DEFAULT_TOUR_NAME } == tourName && wp.playCount != 0) {
|
||||
list[i] = wp.copy(playCount = 0)
|
||||
count++
|
||||
}
|
||||
}
|
||||
prefs[KEY_WAYPOINTS] = gson.toJson(list)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
/**
|
||||
* Wendet einen Abspiel-Modus auf alle Wegpunkte einer Tour an.
|
||||
* Optional: setzt auch maxPlayCount, optional: setzt playCount zurück.
|
||||
* @return Anzahl der aktualisierten Wegpunkte.
|
||||
*/
|
||||
suspend fun applyPlaybackModeForTour(
|
||||
tourName: String,
|
||||
mode: PlaybackMode,
|
||||
maxPlayCount: Int?,
|
||||
resetCounts: Boolean
|
||||
): Int {
|
||||
var count = 0
|
||||
context.dataStore.edit { prefs ->
|
||||
val json = prefs[KEY_WAYPOINTS] ?: "[]"
|
||||
val list: MutableList<Waypoint> = runCatching {
|
||||
gson.fromJson<List<Waypoint>>(json, waypointListType)?.toMutableList()
|
||||
}.getOrNull() ?: mutableListOf()
|
||||
|
||||
for (i in list.indices) {
|
||||
val wp = list[i]
|
||||
if (wp.tourName.ifBlank { Waypoint.DEFAULT_TOUR_NAME } == tourName) {
|
||||
list[i] = wp.copy(
|
||||
playbackMode = mode,
|
||||
maxPlayCount = if (mode == PlaybackMode.LIMITED_COUNT) maxPlayCount else null,
|
||||
playCount = if (resetCounts) 0 else wp.playCount
|
||||
)
|
||||
count++
|
||||
}
|
||||
}
|
||||
prefs[KEY_WAYPOINTS] = gson.toJson(list)
|
||||
}
|
||||
return count
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user