Release GPS2Audio v2.5.44 POI category fix
This commit is contained in:
@@ -23,11 +23,15 @@ enum class PlaybackMode {
|
||||
* @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 soundUri Legacy-Single-Clip-URI (v2.5.11 und älter). In v2.5.12
|
||||
* ist [audioClips] die führende Repräsentation; das
|
||||
* Feld bleibt zur Rückwärtskompatibilität gefüllt
|
||||
* (typischerweise = primary-Clip-URI), sodass alte
|
||||
* Lese-Pfade unverändert funktionieren.
|
||||
* @param soundName Legacy-Dateiname (v2.5.11 und älter). Wie [soundUri]
|
||||
* aus Kompatibilitätsgründen weiter gepflegt.
|
||||
* @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".
|
||||
* @param tourName Name der Tour/Route (Standard: "Standard").
|
||||
*
|
||||
* --- Abspielregeln (optional, Standardwerte = bisheriges Verhalten) ---
|
||||
*
|
||||
@@ -39,6 +43,14 @@ enum class PlaybackMode {
|
||||
* @param scheduleEndMillis Spätestes Datum/Uhrzeit (Unix-Millisekunden), bis zu dem Abspielen erlaubt ist
|
||||
* @param allowedStartMinutes Tagesminute (0–1439), ab der das tägliche Zeitfenster beginnt
|
||||
* @param allowedEndMinutes Tagesminute (0–1439), bis zu der das tägliche Zeitfenster endet
|
||||
*
|
||||
* --- v2.5.12 Multi-Clip ---
|
||||
*
|
||||
* @param audioClips Liste der Audio-Clips dieses Wegpunkts. Leer =
|
||||
* Migration aus Legacy-Feldern [soundUri]/[soundName]
|
||||
* via [effectiveClips]. Aktive Clips werden in
|
||||
* Reihenfolge ([AudioClip.orderIndex]) abgespielt;
|
||||
* inaktive werden übersprungen.
|
||||
*/
|
||||
data class Waypoint(
|
||||
val id: String = UUID.randomUUID().toString(),
|
||||
@@ -61,10 +73,118 @@ data class Waypoint(
|
||||
val scheduleStartMillis: Long? = null,
|
||||
val scheduleEndMillis: Long? = null,
|
||||
val allowedStartMinutes: Int? = null,
|
||||
val allowedEndMinutes: Int? = null
|
||||
val allowedEndMinutes: Int? = null,
|
||||
|
||||
// v2.5.12 — Multi-Clip-Erweiterung. Default = leer, damit alte JSONs ohne
|
||||
// diesen Key weiterhin laden (Gson füllt den Default ein).
|
||||
val audioClips: List<AudioClip> = emptyList()
|
||||
) {
|
||||
companion object {
|
||||
/** Standard-Tourname für bestehende Wegpunkte ohne tourName-Feld. */
|
||||
const val DEFAULT_TOUR_NAME = "Standard"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* v2.5.12 — Liefert die *effektiv anzuwendende* Cliplist für diesen Wegpunkt.
|
||||
*
|
||||
* Verhalten:
|
||||
* - Ist [Waypoint.audioClips] nicht leer, wird diese Liste in stabiler
|
||||
* Sortierung (orderIndex, dann id) zurückgegeben.
|
||||
* - Andernfalls wird – sofern [Waypoint.soundUri] vorhanden ist – ein
|
||||
* synthetischer primary-Clip aus [Waypoint.soundUri]/[Waypoint.soundName]
|
||||
* erzeugt. Damit funktionieren alle alten Wegpunkte ohne Migration
|
||||
* transparent weiter.
|
||||
* - Hat ein Wegpunkt weder [Waypoint.audioClips] noch [Waypoint.soundUri],
|
||||
* ist die Rückgabe leer.
|
||||
*
|
||||
* Die ID des synthetischen Legacy-Clips wird deterministisch aus der Waypoint-
|
||||
* ID abgeleitet, damit sich Skip-/UI-Zustände über mehrere Aufrufe hinweg
|
||||
* stabil referenzieren lassen.
|
||||
*/
|
||||
fun Waypoint.effectiveClips(): List<AudioClip> {
|
||||
if (audioClips.isNotEmpty()) {
|
||||
return audioClips.sortedWith(
|
||||
compareBy({ it.orderIndex }, { it.id })
|
||||
)
|
||||
}
|
||||
if (soundUri.isBlank()) return emptyList()
|
||||
return listOf(
|
||||
AudioClip(
|
||||
id = "legacy-$id",
|
||||
title = soundName,
|
||||
uri = soundUri,
|
||||
displayFileName = soundName,
|
||||
isActive = true,
|
||||
isPrimary = true,
|
||||
orderIndex = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle aktiven, abspielbaren Clips (für Tour-Play). Reihenfolge wie
|
||||
* [effectiveClips]; ohne deaktivierte und ohne leere URIs.
|
||||
*/
|
||||
fun Waypoint.activeClips(): List<AudioClip> =
|
||||
effectiveClips().filter { it.isActive && it.hasAudio }
|
||||
|
||||
/**
|
||||
* Primärer Clip (Vorschau, Listenansicht, Legacy-Fallback).
|
||||
*
|
||||
* Priorität:
|
||||
* 1. Erster Clip in [effectiveClips] mit isPrimary = true (sofern abspielbar).
|
||||
* 2. Erster *aktiver* Clip mit URI.
|
||||
* 3. Erster Clip mit URI überhaupt.
|
||||
* 4. null, falls kein Audio existiert.
|
||||
*/
|
||||
fun Waypoint.primaryClip(): AudioClip? {
|
||||
val clips = effectiveClips()
|
||||
return clips.firstOrNull { it.isPrimary && it.hasAudio }
|
||||
?: clips.firstOrNull { it.isActive && it.hasAudio }
|
||||
?: clips.firstOrNull { it.hasAudio }
|
||||
}
|
||||
|
||||
/**
|
||||
* URI des primären Clips, oder Legacy-[Waypoint.soundUri] als Fallback.
|
||||
* Praktisch für alle Stellen, die früher direkt `waypoint.soundUri` lasen.
|
||||
*/
|
||||
fun Waypoint.primarySoundUri(): String =
|
||||
primaryClip()?.uri?.ifBlank { soundUri } ?: soundUri
|
||||
|
||||
/**
|
||||
* Anzeigename des primären Clips, oder Legacy-[Waypoint.soundName].
|
||||
*/
|
||||
fun Waypoint.primarySoundName(): String =
|
||||
primaryClip()?.effectiveTitle?.ifBlank { soundName } ?: soundName
|
||||
|
||||
/**
|
||||
* v2.5.12 — Hilfsfunktion zum Setzen der Clipliste mit konsistenten
|
||||
* Legacy-Spiegel-Feldern. Die Felder [Waypoint.soundUri] und
|
||||
* [Waypoint.soundName] werden auf den primären Clip gesetzt, sodass
|
||||
* - alte Lese-Pfade weiter den Hauptclip sehen,
|
||||
* - JSON-Exports (legacy-Format) ein sinnvolles soundUri-Feld enthalten,
|
||||
* - der Tour-Play (multi-clip-aware) trotzdem alle Clips kennt.
|
||||
*
|
||||
* orderIndex wird beim Speichern auf die Listen-Position normalisiert,
|
||||
* damit die Reihenfolge stabil aus der UI übernommen werden kann.
|
||||
*/
|
||||
fun Waypoint.withClips(newClips: List<AudioClip>): Waypoint {
|
||||
if (newClips.isEmpty()) {
|
||||
return copy(audioClips = emptyList())
|
||||
}
|
||||
val normalized = newClips.mapIndexed { idx, c -> c.copy(orderIndex = idx) }
|
||||
val primary = normalized.firstOrNull { it.isPrimary && it.hasAudio }
|
||||
?: normalized.firstOrNull { it.isActive && it.hasAudio }
|
||||
?: normalized.firstOrNull { it.hasAudio }
|
||||
?: normalized.first()
|
||||
// Genau einen Clip als primary markieren (idempotent)
|
||||
val finalized = normalized.map { c ->
|
||||
c.copy(isPrimary = (c.id == primary.id))
|
||||
}
|
||||
return copy(
|
||||
audioClips = finalized,
|
||||
soundUri = primary.uri,
|
||||
soundName = primary.effectiveTitle
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user