510 lines
22 KiB
Kotlin
510 lines
22 KiB
Kotlin
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")
|
|
private val KEY_TRACK_DRAFTS = stringPreferencesKey("track_drafts_json")
|
|
// v2.4.6 — persistente Tourrouten (tourName → TourRoute, JSON).
|
|
// Eigene Schlüssel-Datei: bestehende Backups/JSON ohne diesen Key
|
|
// bleiben gültig (Default = leere Map).
|
|
private val KEY_TOUR_ROUTES = stringPreferencesKey("tour_routes_json")
|
|
// v2.5.5 — Persistente Tour-Cover (tourName → TourCover, JSON).
|
|
// Optional pro Tour; fehlt der Eintrag, bleibt die deterministische
|
|
// Standard-Optik (Akzentfarbe + Icon-Heuristik aus Tournamen) erhalten.
|
|
private val KEY_TOUR_COVERS = stringPreferencesKey("tour_covers_json")
|
|
}
|
|
|
|
private val draftListType = object : TypeToken<List<TrackDraft>>() {}.type
|
|
private val tourRoutesMapType = object : TypeToken<Map<String, TourRoute>>() {}.type
|
|
private val tourCoversMapType = object : TypeToken<Map<String, TourCover>>() {}.type
|
|
|
|
/** 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())
|
|
}
|
|
|
|
/**
|
|
* v2.4.6 — Persistente Tourrouten als Map (tourName → TourRoute).
|
|
* Rückwärtskompatibel: fehlt der Key komplett, ist die Map leer.
|
|
* Auch korrupte JSON werden defensiv auf leer abgefangen, damit eine
|
|
* einzelne kaputte Route nicht den Rest der App blockiert.
|
|
*/
|
|
val tourRoutesFlow: Flow<Map<String, TourRoute>> =
|
|
context.dataStore.data.map { prefs ->
|
|
val json = prefs[KEY_TOUR_ROUTES] ?: "{}"
|
|
runCatching {
|
|
gson.fromJson<Map<String, TourRoute>>(json, tourRoutesMapType) ?: emptyMap()
|
|
}.getOrDefault(emptyMap())
|
|
}
|
|
|
|
/**
|
|
* v2.5.5 — Persistente Tour-Cover als Map (tourName → TourCover).
|
|
* Rückwärtskompatibel: fehlt der Key, ist die Map leer und Touren
|
|
* bekommen ihre bisherige deterministische Hero-Optik.
|
|
*/
|
|
val tourCoversFlow: Flow<Map<String, TourCover>> =
|
|
context.dataStore.data.map { prefs ->
|
|
val json = prefs[KEY_TOUR_COVERS] ?: "{}"
|
|
runCatching {
|
|
gson.fromJson<Map<String, TourCover>>(json, tourCoversMapType) ?: 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())
|
|
}
|
|
|
|
/**
|
|
* Reaktiver Flow der archivierten Track-Drafts.
|
|
* Drafts sind eigenständige, persistierte Aufzeichnungen — neue
|
|
* Aufzeichnungen werden als zusätzliche Einträge gespeichert,
|
|
* niemals durch Überschreiben.
|
|
*/
|
|
val trackDraftsFlow: Flow<List<TrackDraft>> = context.dataStore.data.map { prefs ->
|
|
val json = prefs[KEY_TRACK_DRAFTS] ?: "[]"
|
|
runCatching {
|
|
gson.fromJson<List<TrackDraft>>(json, draftListType) ?: emptyList()
|
|
}.getOrDefault(emptyList())
|
|
}
|
|
|
|
/** Fügt einen neuen Track-Draft hinzu (immer als neuen Eintrag). */
|
|
suspend fun addTrackDraft(draft: TrackDraft) {
|
|
context.dataStore.edit { prefs ->
|
|
val json = prefs[KEY_TRACK_DRAFTS] ?: "[]"
|
|
val list: MutableList<TrackDraft> = runCatching {
|
|
gson.fromJson<List<TrackDraft>>(json, draftListType)?.toMutableList()
|
|
}.getOrNull() ?: mutableListOf()
|
|
list.add(draft)
|
|
prefs[KEY_TRACK_DRAFTS] = gson.toJson(list)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Aktualisiert einen vorhandenen Draft anhand seiner ID.
|
|
* Falls noch nicht vorhanden, wird er hinzugefügt (defensiv).
|
|
*/
|
|
suspend fun upsertTrackDraft(draft: TrackDraft) {
|
|
context.dataStore.edit { prefs ->
|
|
val json = prefs[KEY_TRACK_DRAFTS] ?: "[]"
|
|
val list: MutableList<TrackDraft> = runCatching {
|
|
gson.fromJson<List<TrackDraft>>(json, draftListType)?.toMutableList()
|
|
}.getOrNull() ?: mutableListOf()
|
|
val idx = list.indexOfFirst { it.id == draft.id }
|
|
if (idx >= 0) list[idx] = draft else list.add(draft)
|
|
prefs[KEY_TRACK_DRAFTS] = gson.toJson(list)
|
|
}
|
|
}
|
|
|
|
/** Löscht einen Track-Draft anhand seiner ID. */
|
|
suspend fun deleteTrackDraft(id: String) {
|
|
context.dataStore.edit { prefs ->
|
|
val json = prefs[KEY_TRACK_DRAFTS] ?: "[]"
|
|
val list: MutableList<TrackDraft> = runCatching {
|
|
gson.fromJson<List<TrackDraft>>(json, draftListType)?.toMutableList()
|
|
}.getOrNull() ?: mutableListOf()
|
|
list.removeAll { it.id == id }
|
|
prefs[KEY_TRACK_DRAFTS] = gson.toJson(list)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Speichert die gesamte Wegpunktliste.
|
|
*
|
|
* v2.7.21 — Waypoint-Preservation-Guard:
|
|
* Wenn [waypoints] leer ist, aber im DataStore bereits Wegpunkte vorhanden
|
|
* sind, wird der Schreibvorgang NICHT ausgeführt. Das verhindert, dass eine
|
|
* versehentlich leere oder veraltete In-Memory-Liste (`_waypoints.value`
|
|
* aus einem Race-Condition-Fenster) bestehende Wegpunkte überschreibt.
|
|
*
|
|
* Explizites Leeren (Tour-Löschung, Migration) muss [saveAllForced] verwenden.
|
|
*/
|
|
suspend fun saveAll(waypoints: List<Waypoint>) {
|
|
context.dataStore.edit { prefs ->
|
|
// v2.7.21 — Preservation Guard: nie eine leere Liste über eine
|
|
// nicht-leere Liste schreiben. Schutz gegen Race-Condition-Verlust.
|
|
if (waypoints.isEmpty()) {
|
|
val existingJson = prefs[KEY_WAYPOINTS] ?: "[]"
|
|
val existingList = runCatching {
|
|
gson.fromJson<List<Waypoint>>(existingJson, waypointListType)
|
|
}.getOrNull()
|
|
if (!existingList.isNullOrEmpty()) {
|
|
// Vorhandene Wegpunkte schützen — leere Liste wird NICHT geschrieben.
|
|
android.util.Log.w(
|
|
"WaypointStore",
|
|
"saveAll() mit leerer Liste blockiert: ${existingList.size} bestehende Wegpunkte bleiben erhalten."
|
|
)
|
|
return@edit
|
|
}
|
|
}
|
|
prefs[KEY_WAYPOINTS] = gson.toJson(waypoints)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* v2.7.21 — Forciertes Speichern ohne Preservation-Guard.
|
|
* Nur für explizite Lösch- und Migrationsoperationen verwenden,
|
|
* bei denen eine leere Liste das gewünschte Ergebnis ist.
|
|
*/
|
|
suspend fun saveAllForced(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)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* v2.4.6 — Speichert (oder ersetzt) die gespeicherte Route einer Tour.
|
|
* Setzt [TourRoute.updatedAtMillis] auf die aktuelle Zeit, sofern das
|
|
* übergebene Objekt nicht selbst einen neueren Zeitstempel mitbringt.
|
|
*
|
|
* v2.7.21 — Waypoint-Preservation-Invariante:
|
|
* Diese Funktion schreibt AUSSCHLIESSLICH in [KEY_TOUR_ROUTES].
|
|
* [KEY_WAYPOINTS] (Audio-Wegpunkte) wird durch diesen Aufruf NIEMALS
|
|
* verändert. Tourrouten und Audio-Wegpunkte sind vollständig getrennte
|
|
* Speicherbereiche.
|
|
*/
|
|
suspend fun saveTourRoute(tourName: String, route: TourRoute) {
|
|
context.dataStore.edit { prefs ->
|
|
val json = prefs[KEY_TOUR_ROUTES] ?: "{}"
|
|
val map: MutableMap<String, TourRoute> = runCatching {
|
|
gson.fromJson<Map<String, TourRoute>>(json, tourRoutesMapType)?.toMutableMap()
|
|
}.getOrNull() ?: mutableMapOf()
|
|
val stamped = route.copy(
|
|
updatedAtMillis = if (route.updatedAtMillis > 0) route.updatedAtMillis
|
|
else System.currentTimeMillis()
|
|
)
|
|
map[tourName] = stamped
|
|
prefs[KEY_TOUR_ROUTES] = gson.toJson(map)
|
|
}
|
|
}
|
|
|
|
/** Entfernt die gespeicherte Route einer Tour (falls vorhanden). */
|
|
suspend fun deleteTourRoute(tourName: String) {
|
|
context.dataStore.edit { prefs ->
|
|
val json = prefs[KEY_TOUR_ROUTES] ?: "{}"
|
|
val map: MutableMap<String, TourRoute> = runCatching {
|
|
gson.fromJson<Map<String, TourRoute>>(json, tourRoutesMapType)?.toMutableMap()
|
|
}.getOrNull() ?: mutableMapOf()
|
|
if (map.remove(tourName) != null) {
|
|
prefs[KEY_TOUR_ROUTES] = gson.toJson(map)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Benennt die Tour einer gespeicherten Route um. Wird aus
|
|
* [WaypointViewModel.renameTour]/[deleteTour] aufgerufen, damit die
|
|
* Route nicht verwaist zurückbleibt.
|
|
*/
|
|
suspend fun renameTourRouteKey(oldTour: String, newTour: String) {
|
|
if (oldTour == newTour) return
|
|
context.dataStore.edit { prefs ->
|
|
val json = prefs[KEY_TOUR_ROUTES] ?: "{}"
|
|
val map: MutableMap<String, TourRoute> = runCatching {
|
|
gson.fromJson<Map<String, TourRoute>>(json, tourRoutesMapType)?.toMutableMap()
|
|
}.getOrNull() ?: mutableMapOf()
|
|
val existing = map.remove(oldTour) ?: return@edit
|
|
map[newTour] = existing.copy(updatedAtMillis = System.currentTimeMillis())
|
|
prefs[KEY_TOUR_ROUTES] = gson.toJson(map)
|
|
}
|
|
}
|
|
|
|
/** Bulk-Schreiben der Routen (für Backup-Import). Vorhandene Keys werden überschrieben. */
|
|
suspend fun mergeTourRoutes(routes: Map<String, TourRoute>) {
|
|
if (routes.isEmpty()) return
|
|
context.dataStore.edit { prefs ->
|
|
val json = prefs[KEY_TOUR_ROUTES] ?: "{}"
|
|
val map: MutableMap<String, TourRoute> = runCatching {
|
|
gson.fromJson<Map<String, TourRoute>>(json, tourRoutesMapType)?.toMutableMap()
|
|
}.getOrNull() ?: mutableMapOf()
|
|
map.putAll(routes)
|
|
prefs[KEY_TOUR_ROUTES] = gson.toJson(map)
|
|
}
|
|
}
|
|
|
|
/** v2.5.5 — Speichert das Cover einer Tour (überschreibt vorhandenes). */
|
|
suspend fun saveTourCover(tourName: String, cover: TourCover) {
|
|
context.dataStore.edit { prefs ->
|
|
val json = prefs[KEY_TOUR_COVERS] ?: "{}"
|
|
val map: MutableMap<String, TourCover> = runCatching {
|
|
gson.fromJson<Map<String, TourCover>>(json, tourCoversMapType)?.toMutableMap()
|
|
}.getOrNull() ?: mutableMapOf()
|
|
map[tourName] = cover
|
|
prefs[KEY_TOUR_COVERS] = gson.toJson(map)
|
|
}
|
|
}
|
|
|
|
/** v2.5.5 — Entfernt das Cover einer Tour (falls vorhanden). */
|
|
suspend fun deleteTourCover(tourName: String) {
|
|
context.dataStore.edit { prefs ->
|
|
val json = prefs[KEY_TOUR_COVERS] ?: "{}"
|
|
val map: MutableMap<String, TourCover> = runCatching {
|
|
gson.fromJson<Map<String, TourCover>>(json, tourCoversMapType)?.toMutableMap()
|
|
}.getOrNull() ?: mutableMapOf()
|
|
if (map.remove(tourName) != null) {
|
|
prefs[KEY_TOUR_COVERS] = gson.toJson(map)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* v2.5.5 — Tour-Umbenennung: Cover-Eintrag mitziehen, damit er nicht
|
|
* unter einem verwaisten Tournamen liegen bleibt.
|
|
*/
|
|
suspend fun renameTourCoverKey(oldTour: String, newTour: String) {
|
|
if (oldTour == newTour) return
|
|
context.dataStore.edit { prefs ->
|
|
val json = prefs[KEY_TOUR_COVERS] ?: "{}"
|
|
val map: MutableMap<String, TourCover> = runCatching {
|
|
gson.fromJson<Map<String, TourCover>>(json, tourCoversMapType)?.toMutableMap()
|
|
}.getOrNull() ?: mutableMapOf()
|
|
val existing = map.remove(oldTour) ?: return@edit
|
|
map[newTour] = existing
|
|
prefs[KEY_TOUR_COVERS] = gson.toJson(map)
|
|
}
|
|
}
|
|
|
|
/** v2.5.5 — Bulk-Schreiben der Cover (für Backup-Import). */
|
|
suspend fun mergeTourCovers(covers: Map<String, TourCover>) {
|
|
if (covers.isEmpty()) return
|
|
context.dataStore.edit { prefs ->
|
|
val json = prefs[KEY_TOUR_COVERS] ?: "{}"
|
|
val map: MutableMap<String, TourCover> = runCatching {
|
|
gson.fromJson<Map<String, TourCover>>(json, tourCoversMapType)?.toMutableMap()
|
|
}.getOrNull() ?: mutableMapOf()
|
|
map.putAll(covers)
|
|
prefs[KEY_TOUR_COVERS] = 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
|
|
}
|
|
}
|