feat(audio): audio session integration and runtime robustness

- Integrate audio_session (new servicio_audio_session.dart): incoming calls pause the radio and resume on end, headphone unplug pauses without auto-resume, permanent focus loss never auto-resumes, duck lowers volume
- Add play-intent flag to ServicioAudio so interruption handling and future reconnect logic can distinguish user pause from system-driven stops
- Eliminate read-modify-write race in ServicioAlarmas with an in-memory cache and single-writer queue across all mutations; recalcularTodas persists only when state actually changed
- Convert ServicioAlarmasAndroid static StreamController/handler to injectable instance fields, restoring test isolation
- Inject a single cached SharedPreferences from main.dart across services and state (removes 23 inline getInstance() calls)
- Move configurarLocalizaciones out of MiniReproductor.build() (was running on every rebuild during playback)
- Bound the alarm fire-dedup set (cap 200 entries, 24h pruning)
- 12 new tests (89 total green), flutter analyze clean
This commit is contained in:
2026-06-11 16:25:09 +02:00
parent f3e9487215
commit 079e19f0ee
21 changed files with 1059 additions and 151 deletions
+12 -5
View File
@@ -82,9 +82,11 @@ class ServicioGrabacionRadio {
http.Client? cliente,
Future<Directory> Function()? resolverDirectorioBase,
DateTime Function()? reloj,
SharedPreferences? prefs,
}) : _clienteExterno = cliente,
_resolverDirectorioBase = resolverDirectorioBase,
_reloj = reloj ?? DateTime.now;
_reloj = reloj ?? DateTime.now,
_prefs = prefs;
static const _claveDirectorio = 'grabacion_radio_directorio';
static const _claveMaxBytes = 'grabacion_radio_max_bytes_v1';
@@ -93,6 +95,11 @@ class ServicioGrabacionRadio {
final http.Client? _clienteExterno;
final Future<Directory> Function()? _resolverDirectorioBase;
final DateTime Function() _reloj;
final SharedPreferences? _prefs;
/// Injected startup instance (S3-R4); getInstance() is only a fallback.
Future<SharedPreferences> _resolverPrefs() async =>
_prefs ?? SharedPreferences.getInstance();
final _estadoController = StreamController<EstadoGrabacionRadio>.broadcast();
AppLocalizations? _l10n;
@@ -123,7 +130,7 @@ class ServicioGrabacionRadio {
Future<void> inicializar() async {
try {
final prefs = await SharedPreferences.getInstance();
final prefs = await _resolverPrefs();
_directorioConfigurado = prefs.getString(_claveDirectorio);
_maxBytes = prefs.getInt(_claveMaxBytes) ?? maxBytesPorDefecto;
} catch (_) {
@@ -151,7 +158,7 @@ class ServicioGrabacionRadio {
}
_directorioConfigurado = normalizado;
try {
final prefs = await SharedPreferences.getInstance();
final prefs = await _resolverPrefs();
await prefs.setString(_claveDirectorio, normalizado);
} catch (_) {}
_emitir(_estado);
@@ -160,7 +167,7 @@ class ServicioGrabacionRadio {
Future<void> limpiarDirectorioConfigurado() async {
_directorioConfigurado = null;
try {
final prefs = await SharedPreferences.getInstance();
final prefs = await _resolverPrefs();
await prefs.remove(_claveDirectorio);
} catch (_) {}
_emitir(_estado);
@@ -172,7 +179,7 @@ class ServicioGrabacionRadio {
}
_maxBytes = bytes;
try {
final prefs = await SharedPreferences.getInstance();
final prefs = await _resolverPrefs();
await prefs.setInt(_claveMaxBytes, bytes);
} catch (_) {}
_emitir(_estado);