Initial release of GPS2Audio

This commit is contained in:
Marcel Mayer
2026-05-05 20:54:05 +02:00
commit b2aa1c167c
48 changed files with 11570 additions and 0 deletions
@@ -0,0 +1,67 @@
package de.waypointaudio.data
import android.content.Context
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/**
* Globale Audio-Routing-Einstellungen für Live/PTT.
*
* Gerätekennungen (AudioDeviceInfo.id) sind Sitzungs-spezifisch und können sich nach
* einem Neustart oder nach dem Trennen/Verbinden von Bluetooth-Geräten ändern.
* Wenn ein gespeichertes Gerät nicht mehr verfügbar ist, wird automatisch der
* Systemstandard verwendet.
*
* @param selectedInputDeviceId ID des Eingabegeräts, null = Systemstandard
* @param selectedOutputDeviceId ID des Ausgabegeräts, null = Systemstandard
*/
data class AudioRoutingSettings(
val selectedInputDeviceId: Int? = null,
val selectedOutputDeviceId: Int? = null
)
private val Context.audioRoutingDataStore by preferencesDataStore(name = "audio_routing_settings")
/**
* Persistenz-Schicht für Audio-Routing-Einstellungen via DataStore.
*/
class AudioRoutingStore(private val context: Context) {
private companion object {
val KEY_INPUT_DEVICE_ID = intPreferencesKey("selected_input_device_id")
val KEY_OUTPUT_DEVICE_ID = intPreferencesKey("selected_output_device_id")
// Sentinel: -1 means "use system default" (null cannot be stored as Int)
const val NO_DEVICE = -1
}
val settings: Flow<AudioRoutingSettings> = context.audioRoutingDataStore.data
.map { prefs ->
AudioRoutingSettings(
selectedInputDeviceId = prefs[KEY_INPUT_DEVICE_ID].takeIf { it != null && it != NO_DEVICE },
selectedOutputDeviceId = prefs[KEY_OUTPUT_DEVICE_ID].takeIf { it != null && it != NO_DEVICE }
)
}
suspend fun save(settings: AudioRoutingSettings) {
context.audioRoutingDataStore.edit { prefs ->
prefs[KEY_INPUT_DEVICE_ID] = settings.selectedInputDeviceId ?: NO_DEVICE
prefs[KEY_OUTPUT_DEVICE_ID] = settings.selectedOutputDeviceId ?: NO_DEVICE
}
}
suspend fun clearInputDevice() {
context.audioRoutingDataStore.edit { prefs ->
prefs[KEY_INPUT_DEVICE_ID] = NO_DEVICE
}
}
suspend fun clearOutputDevice() {
context.audioRoutingDataStore.edit { prefs ->
prefs[KEY_OUTPUT_DEVICE_ID] = NO_DEVICE
}
}
}
@@ -0,0 +1,14 @@
package de.waypointaudio.data
/**
* Ein einzelner Punkt eines aufgezeichneten GPS-Tracks.
*
* @param latitude Breitengrad in Dezimalgrad
* @param longitude Längengrad in Dezimalgrad
* @param timestamp Unix-Millisekunden zum Zeitpunkt der Aufzeichnung
*/
data class GpsTrackPoint(
val latitude: Double = 0.0,
val longitude: Double = 0.0,
val timestamp: Long = System.currentTimeMillis()
)
@@ -0,0 +1,60 @@
package de.waypointaudio.data
/**
* Quelle der Begleitmusik für eine Tour.
*/
enum class MusicSourceType {
LOCAL_PLAYLIST,
STREAM_URL
}
/**
* Verhalten der Begleitmusik, wenn ein Wegpunkt-Audio abgespielt wird.
*/
enum class WaypointMusicBehavior {
/** Begleitmusik pausieren, danach fortsetzen. */
PAUSE_RESUME,
/** Begleitmusik ausblenden (Fade-out), Wegpunkt abspielen, danach einblenden (Fade-in). */
FADE_OUT_IN,
/** Begleitmusik auf duckVolume leise stellen, Wegpunkt parallel abspielen, danach wiederherstellen. */
DUCK_UNDERLAY,
/** Begleitmusik unveränderter Lautstärke weiterspielen, Wegpunkt parallel abspielen. */
CONTINUE_UNDERLAY
}
/**
* Ein einzelnes Element in der lokalen Playlist.
*/
data class PlaylistItem(
val uriString: String,
val displayName: String
)
/**
* Begleitmusik-Einstellungen für eine Tour.
*
* @param enabled Ob Begleitmusik für diese Tour aktiv ist.
* @param sourceType Quelle: lokale Playlist oder Stream-URL.
* @param localPlaylist Liste von Audiodateien (content:// URIs) aus dem lokalen Speicher.
* @param streamUrl Direkte HTTP/HTTPS-Stream-URL (z. B. Internetradio).
* @param behavior Verhalten wenn Wegpunkt-Audio abgespielt wird.
* @param fadeDurationMs Dauer des Fade-Effekts in Millisekunden.
* @param duckVolume Lautstärke (0.01.0) während des Duck-Modus.
* @param shuffle Playlist zufällig abspielen.
* @param autoStartAfterWaypoint Nach Wegpunkt-Audio Begleitmusik automatisch starten,
* auch wenn sie vorher nicht spielte.
*/
data class TourAudioSettings(
val enabled: Boolean = false,
val sourceType: MusicSourceType = MusicSourceType.LOCAL_PLAYLIST,
val localPlaylist: List<PlaylistItem> = emptyList(),
val streamUrl: String? = null,
val behavior: WaypointMusicBehavior = WaypointMusicBehavior.PAUSE_RESUME,
val fadeDurationMs: Long = 1500L,
val duckVolume: Float = 0.25f,
val shuffle: Boolean = false,
val autoStartAfterWaypoint: Boolean = false
)
@@ -0,0 +1,122 @@
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.map
// Separater DataStore für Begleitmusik-Einstellungen
private val Context.musicDataStore: DataStore<Preferences> by preferencesDataStore(name = "tour_music")
/**
* Persistiert Begleitmusik-Einstellungen pro Tour (Key: Tourname).
* Serialisiert als JSON Map<String, TourAudioSettings>.
*/
class TourMusicStore(private val context: Context) {
private val gson: Gson = GsonBuilder().create()
private val mapType = object : TypeToken<Map<String, TourAudioSettingsJson>>() {}.type
companion object {
private val KEY_MUSIC_SETTINGS = stringPreferencesKey("music_settings_json")
}
/** Flow der Begleitmusik-Einstellungen für alle Touren. */
val settingsFlow: Flow<Map<String, TourAudioSettings>> =
context.musicDataStore.data.map { prefs ->
val json = prefs[KEY_MUSIC_SETTINGS] ?: "{}"
runCatching {
val raw: Map<String, TourAudioSettingsJson>? = gson.fromJson(json, mapType)
raw?.mapValues { (_, v) -> v.toDomain() } ?: emptyMap()
}.getOrDefault(emptyMap())
}
/** Liefert die Einstellungen für eine spezifische Tour. */
fun settingsForTour(tourName: String): Flow<TourAudioSettings> =
settingsFlow.map { map -> map[tourName] ?: TourAudioSettings() }
/** Speichert oder aktualisiert die Einstellungen für eine Tour. */
suspend fun save(tourName: String, settings: TourAudioSettings) {
context.musicDataStore.edit { prefs ->
val json = prefs[KEY_MUSIC_SETTINGS] ?: "{}"
val map: MutableMap<String, TourAudioSettingsJson> = runCatching {
gson.fromJson<Map<String, TourAudioSettingsJson>>(json, mapType)?.toMutableMap()
}.getOrNull() ?: mutableMapOf()
map[tourName] = TourAudioSettingsJson.fromDomain(settings)
prefs[KEY_MUSIC_SETTINGS] = gson.toJson(map)
}
}
/** Löscht die Einstellungen für eine Tour (z. B. beim Tour-Löschen). */
suspend fun delete(tourName: String) {
context.musicDataStore.edit { prefs ->
val json = prefs[KEY_MUSIC_SETTINGS] ?: "{}"
val map: MutableMap<String, TourAudioSettingsJson> = runCatching {
gson.fromJson<Map<String, TourAudioSettingsJson>>(json, mapType)?.toMutableMap()
}.getOrNull() ?: mutableMapOf()
map.remove(tourName)
prefs[KEY_MUSIC_SETTINGS] = gson.toJson(map)
}
}
}
// ---------------------------------------------------------------------------
// JSON-Repräsentation (Gson-kompatibel, ohne Kotlin-Enums direkt serialisieren)
// ---------------------------------------------------------------------------
internal data class PlaylistItemJson(
val uriString: String = "",
val displayName: String = ""
) {
fun toDomain() = PlaylistItem(uriString, displayName)
companion object {
fun fromDomain(item: PlaylistItem) = PlaylistItemJson(item.uriString, item.displayName)
}
}
internal data class TourAudioSettingsJson(
val enabled: Boolean = false,
val sourceType: String = "LOCAL_PLAYLIST",
val localPlaylist: List<PlaylistItemJson> = emptyList(),
val streamUrl: String? = null,
val behavior: String = "PAUSE_RESUME",
val fadeDurationMs: Long = 1500L,
val duckVolume: Float = 0.25f,
val shuffle: Boolean = false,
val autoStartAfterWaypoint: Boolean = false
) {
fun toDomain() = TourAudioSettings(
enabled = enabled,
sourceType = runCatching { MusicSourceType.valueOf(sourceType) }
.getOrDefault(MusicSourceType.LOCAL_PLAYLIST),
localPlaylist = localPlaylist.map { it.toDomain() },
streamUrl = streamUrl,
behavior = runCatching { WaypointMusicBehavior.valueOf(behavior) }
.getOrDefault(WaypointMusicBehavior.PAUSE_RESUME),
fadeDurationMs = fadeDurationMs,
duckVolume = duckVolume,
shuffle = shuffle,
autoStartAfterWaypoint = autoStartAfterWaypoint
)
companion object {
fun fromDomain(s: TourAudioSettings) = TourAudioSettingsJson(
enabled = s.enabled,
sourceType = s.sourceType.name,
localPlaylist = s.localPlaylist.map { PlaylistItemJson.fromDomain(it) },
streamUrl = s.streamUrl,
behavior = s.behavior.name,
fadeDurationMs = s.fadeDurationMs,
duckVolume = s.duckVolume,
shuffle = s.shuffle,
autoStartAfterWaypoint = s.autoStartAfterWaypoint
)
}
}
@@ -0,0 +1,15 @@
package de.waypointaudio.data
/**
* Tour-weite Abspiel-Vorgaben, die bei neuen Wegpunkten als Standardwerte verwendet werden
* und per Massen-Aktion auf alle vorhandenen Wegpunkte einer Tour angewendet werden können.
*
* @param tourName Name der Tour
* @param playbackMode Standard-Abspiel-Modus für neue und alle vorhandenen Wegpunkte
* @param maxPlayCount Standard-Maximale Abspielanzahl (nur für LIMITED_COUNT)
*/
data class TourPlaybackDefaults(
val tourName: String = Waypoint.DEFAULT_TOUR_NAME,
val playbackMode: PlaybackMode = PlaybackMode.EVERY_ENTRY,
val maxPlayCount: Int = 3
)
@@ -0,0 +1,70 @@
package de.waypointaudio.data
import java.util.UUID
/**
* Abspiel-Modus für einen Wegpunkt.
*
* EVERY_ENTRY Standardverhalten: Ton bei jedem erneuten Betreten.
* ONCE Ton nur beim ersten Betreten (playCount == 0).
* LIMITED_COUNT Ton maximal [maxPlayCount] Mal insgesamt.
*/
enum class PlaybackMode {
EVERY_ENTRY,
ONCE,
LIMITED_COUNT
}
/**
* Datenmodell für einen Wegpunkt.
*
* @param id Eindeutige ID (UUID)
* @param name Anzeigename des Wegpunkts
* @param latitude Breitengrad in Dezimalgrad
* @param longitude Längengrad in Dezimalgrad
* @param radiusMeters Erkennungsradius in Metern
* @param soundUri URI zur Audiodatei (content:// oder file://)
* @param soundName Anzeigename der Audiodatei
* @param isActive Ob der Wegpunkt aktiv überwacht wird
* @param tourName Name der Tour/Route (Standard: "Standard"). Rückwärtskompatibel:
* bestehende Wegpunkte ohne dieses Feld erhalten automatisch "Standard".
*
* --- Abspielregeln (optional, Standardwerte = bisheriges Verhalten) ---
*
* @param playbackMode Abspiel-Modus (Standard: EVERY_ENTRY = bisheriges Verhalten)
* @param maxPlayCount Maximale Abspielanzahl für LIMITED_COUNT; null = kein Limit
* @param playCount Bisherige Abspielanzahl (persistent, wird vom Service inkrementiert)
* @param scheduleEnabled Ob der Zeitplan aktiv ist
* @param scheduleStartMillis Frühestes Datum/Uhrzeit (Unix-Millisekunden), ab dem Abspielen erlaubt ist
* @param scheduleEndMillis Spätestes Datum/Uhrzeit (Unix-Millisekunden), bis zu dem Abspielen erlaubt ist
* @param allowedStartMinutes Tagesminute (01439), ab der das tägliche Zeitfenster beginnt
* @param allowedEndMinutes Tagesminute (01439), bis zu der das tägliche Zeitfenster endet
*/
data class Waypoint(
val id: String = UUID.randomUUID().toString(),
val name: String = "",
val latitude: Double = 0.0,
val longitude: Double = 0.0,
val radiusMeters: Float = 50f,
val soundUri: String = "",
val soundName: String = "",
val isActive: Boolean = true,
// Tour-Zuordnung safe default "Standard" für Rückwärtskompatibilität
val tourName: String = DEFAULT_TOUR_NAME,
// Abspielregeln alle optional, Standardwerte entsprechen bisherigem Verhalten
val playbackMode: PlaybackMode = PlaybackMode.EVERY_ENTRY,
val maxPlayCount: Int? = null,
val playCount: Int = 0,
val scheduleEnabled: Boolean = false,
val scheduleStartMillis: Long? = null,
val scheduleEndMillis: Long? = null,
val allowedStartMinutes: Int? = null,
val allowedEndMinutes: Int? = null
) {
companion object {
/** Standard-Tourname für bestehende Wegpunkte ohne tourName-Feld. */
const val DEFAULT_TOUR_NAME = "Standard"
}
}
@@ -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
}
}