From 79f6f8ef38193153118367f229e86193b950da51 Mon Sep 17 00:00:00 2001 From: freetlab Date: Sat, 11 Jul 2026 10:16:25 +0200 Subject: [PATCH] fix(alarm): capture alarm state before dispose so its volume restore works _restaurarVolumenMediaUnaVez() read the BuildContext to reach the alarm port, but dispose() runs after the element is defunct, so the lookup always threw (caught and logged) and the dispose safety-net never actually restored the media volume when it was the sole exit path. The state is now captured once in initState and the restore helper uses the field. Adds the missing dispose-as-sole-caller regression test. --- lib/pantallas/pantalla_alarma_sonando.dart | 7 ++++++- test/pantallas/pantalla_alarma_sonando_test.dart | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/pantallas/pantalla_alarma_sonando.dart b/lib/pantallas/pantalla_alarma_sonando.dart index 91ad629..a8667f9 100644 --- a/lib/pantallas/pantalla_alarma_sonando.dart +++ b/lib/pantallas/pantalla_alarma_sonando.dart @@ -42,9 +42,14 @@ class _PantallaAlarmaSonandoState extends State { bool _audioFlutterConfirmado = false; bool _volumenMediaRestaurado = false; + // Captured while mounted: dispose() also restores the media volume, and by + // then the element is defunct, so context.read() would throw there. + late final EstadoAlarmas _estadoAlarmas; + @override void initState() { super.initState(); + _estadoAlarmas = context.read(); WidgetsBinding.instance.addPostFrameCallback((_) => _iniciarAlarma()); } @@ -164,7 +169,7 @@ class _PantallaAlarmaSonandoState extends State { if (_volumenMediaRestaurado) return; _volumenMediaRestaurado = true; try { - await context.read().android.restaurarVolumenMedia(); + await _estadoAlarmas.android.restaurarVolumenMedia(); } catch (e) { debugPrint('[PluriWave][alarmas] restaurar volumen media fallo: $e'); } diff --git a/test/pantallas/pantalla_alarma_sonando_test.dart b/test/pantallas/pantalla_alarma_sonando_test.dart index ed3ade6..205945b 100644 --- a/test/pantallas/pantalla_alarma_sonando_test.dart +++ b/test/pantallas/pantalla_alarma_sonando_test.dart @@ -228,4 +228,20 @@ void main() { expect(entorno.audio.volumenesAplicados, [0.05, 0.85]); }); }); + + group('restore de volumen de medios con dispose como unico llamador', () { + testWidgets('desmontar la pantalla sin detener ni posponer restaura el ' + 'volumen exactamente una vez', (tester) async { + final entorno = await _montarPantalla(tester); + + // Teardown that bypasses _detener()/_posponer() entirely: dispose() + // must work as a restore path on its own. Reading the BuildContext + // inside dispose() throws (the element is already defunct), so the + // port reference has to be captured while the widget is mounted. + await tester.pumpWidget(const SizedBox.shrink()); + await tester.pumpAndSettle(); + + expect(entorno.android.volumenRestaurado, 1); + }); + }); }