fix(radio): persist the last-played station across restarts

EstadoRadio.emisoraActual only ever reflected in-memory state
(_emisoraSeleccionada or the live audio service), so stopping playback
and reopening the app left the Escuchar hero empty even though the
user had a station selected right before closing it.

Persist the station whenever it changes (reproducir(), and the
Android-Auto out-of-band reconciliation path) and restore it as
_emisoraSeleccionada on the next cold start, only when nothing is
already selected. This never touches the audio service directly: no
playback starts and estadoStream/estaSonando stay at their stopped
default, matching how every consumer already gates "is it playing" on
the playback-status stream rather than on emisoraActual itself.
This commit is contained in:
2026-07-30 19:18:14 +02:00
parent c6ab295c54
commit 727e18737a
2 changed files with 339 additions and 229 deletions
+55
View File
@@ -173,6 +173,10 @@ class EstadoRadio extends ChangeNotifier {
static const _keyEmisoraPreferida = 'emisora_preferida_uuid_v1';
static const _keyOrdenListas = 'orden_listas_emisoras_v1';
static const _keyTimerSuenoPresets = 'timer_sueno_presets_segundos_v1';
// Issue 4 (feedback-pruebas): last-played station, so the Escuchar hero
// keeps showing "what I was listening to" (stopped, not playing) after a
// full app restart instead of going empty.
static const _keyUltimaEmisora = 'ultima_emisora_v1';
static const _timerSuenoPresetsDefecto = <int>[
180,
300,
@@ -300,6 +304,50 @@ class EstadoRadio extends ChangeNotifier {
_cargarEmisorasCustom(),
]);
await _normalizarEmisoraPreferida();
await _restaurarUltimaEmisora();
}
/// Issue 4 (feedback-pruebas): restores the last-played station as a
/// STOPPED `emisoraActual` on a cold start. Only fills the gap — if
/// something is ALREADY selected (a real play already ran concurrently),
/// this is a no-op. Never touches `audio`: no playback starts, no network
/// request is made, `estadoStream`/`estaSonando` stay at their fresh
/// "detenido" default, exactly like every other consumer of
/// `emisoraActual` already expects (they gate "is it playing" on the
/// separate playback-status stream, never on `emisoraActual != null`).
Future<void> _restaurarUltimaEmisora() async {
if (_emisoraSeleccionada != null || audio.emisoraActual != null) return;
try {
final prefs = await _resolverPrefs();
final raw = prefs.getString(_keyUltimaEmisora);
if (raw == null) return;
final mapa = jsonDecode(raw) as Map<String, dynamic>;
_emisoraSeleccionada = Emisora.fromMap(mapa);
} catch (e) {
registrarSaltoPersistencia(
subsistema: 'ultima_emisora',
detalle: 'restaurar',
razon: e.toString(),
);
}
}
/// Best-effort remembers [emisora] as the last used station (issue 4) so
/// [_restaurarUltimaEmisora] can bring it back after a restart. Fire-and-
/// forget, same treatment [reproducir] already gives other non-critical
/// side effects (e.g. `radio.registrarClick`) — a failed write here must
/// never block or fail actual playback.
Future<void> _persistirUltimaEmisora(Emisora emisora) async {
try {
final prefs = await _resolverPrefs();
await prefs.setString(_keyUltimaEmisora, jsonEncode(emisora.toMap()));
} catch (e) {
registrarSaltoPersistencia(
subsistema: 'ultima_emisora',
detalle: 'persistir ${emisora.uuid}',
razon: e.toString(),
);
}
}
/// Escucha el stream de estado del audio y gestiona errores de reproducción.
@@ -321,6 +369,9 @@ class EstadoRadio extends ChangeNotifier {
final actual = audio.emisoraActual;
if (actual != null && actual.uuid != _emisoraSeleccionada?.uuid) {
_emisoraSeleccionada = actual;
// Issue 4: an Android-Auto-initiated selection is a real station
// change too — remember it the same way `reproducir` does.
unawaited(_persistirUltimaEmisora(actual));
}
notifyListeners();
});
@@ -508,6 +559,10 @@ class EstadoRadio extends ChangeNotifier {
}
_emisoraSeleccionada = emisora;
notifyListeners();
// Issue 4: remembers the station the user just picked so it survives a
// restart — fire-and-forget, same treatment as `radio.registrarClick`
// below (a persistence failure here must never block playback).
unawaited(_persistirUltimaEmisora(emisora));
try {
await audio.reproducir(emisora);
if (revision != _revisionReproduccion) return;