fix(alarm): defer the Dart fade-in until the native handoff confirms
The native service and the Flutter player each ran their own 5%-to- target fade-in, and both could drive audible volume at the handoff, producing a jump or ramp reset. The Dart ramp now starts exactly once from the handoff-confirmation path: the player still pre-starts at 5%, and _confirmarAudioFlutterListo() starts the ramp in a finally block so it runs whether the native confirmation succeeds or fails — the alarm can never stay stuck at 5% if the native side is already gone. Work unit 3/3 of alarm-volume-ramp-restore (fade-in dedup).
This commit is contained in:
@@ -64,7 +64,6 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
|||||||
if (!widget.audioPrearrancado) {
|
if (!widget.audioPrearrancado) {
|
||||||
unawaited(radio.reproducir(emisora));
|
unawaited(radio.reproducir(emisora));
|
||||||
}
|
}
|
||||||
_iniciarFadeIn();
|
|
||||||
|
|
||||||
// S7-R4 boundary: only `reproduciendo` cancels the fallback timer —
|
// S7-R4 boundary: only `reproduciendo` cancels the fallback timer —
|
||||||
// `reconectando`/`cargando` do NOT count as playing, so the 12-second
|
// `reconectando`/`cargando` do NOT count as playing, so the 12-second
|
||||||
@@ -94,7 +93,6 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
|||||||
_fallbackActivo = true;
|
_fallbackActivo = true;
|
||||||
await _fallbackPlayer.setAsset(_assetFallback(widget.alarma.sonidoInterno));
|
await _fallbackPlayer.setAsset(_assetFallback(widget.alarma.sonidoInterno));
|
||||||
await _fallbackPlayer.play();
|
await _fallbackPlayer.play();
|
||||||
_iniciarFadeIn();
|
|
||||||
await _confirmarAudioFlutterListo();
|
await _confirmarAudioFlutterListo();
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
@@ -131,12 +129,28 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
|
|||||||
await _fallbackPlayer.setVolume(volumen.clamp(0.0, 1.0));
|
await _fallbackPlayer.setVolume(volumen.clamp(0.0, 1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Confirms the native-to-Flutter audio handoff at most once per screen
|
||||||
|
/// instance, then starts the single audible Dart fade-in ramp (Slice 3:
|
||||||
|
/// fade-in dedup at handoff). The native ramp
|
||||||
|
/// (`PluriWaveAlarmService.startFadeIn`) owns audio until this
|
||||||
|
/// confirmation lands; starting the Dart ramp any earlier would
|
||||||
|
/// interleave both ramps and produce an audible jump/reset. If the
|
||||||
|
/// native confirmation channel call fails (dead or never-there native
|
||||||
|
/// side), the fade-in still starts in the `finally` block below — Dart
|
||||||
|
/// is the only audible source either way, so the ring must never stay
|
||||||
|
/// stuck at [_volumenInicialFadeIn] forever.
|
||||||
Future<void> _confirmarAudioFlutterListo() async {
|
Future<void> _confirmarAudioFlutterListo() async {
|
||||||
if (_audioFlutterConfirmado) return;
|
if (_audioFlutterConfirmado) return;
|
||||||
_audioFlutterConfirmado = true;
|
_audioFlutterConfirmado = true;
|
||||||
await context.read<EstadoAlarmas>().android.confirmarAudioFlutter(
|
try {
|
||||||
widget.alarma.id,
|
await context.read<EstadoAlarmas>().android.confirmarAudioFlutter(
|
||||||
);
|
widget.alarma.id,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[PluriWave][alarmas] confirmar audio flutter fallo: $e');
|
||||||
|
} finally {
|
||||||
|
_iniciarFadeIn();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restores the ring-scoped `STREAM_MUSIC` override at most once per
|
/// Restores the ring-scoped `STREAM_MUSIC` override at most once per
|
||||||
|
|||||||
@@ -142,11 +142,46 @@ Chain strategy: pending
|
|||||||
|
|
||||||
## Phase 4: Fade-In Dedup at Handoff (Slice 3, strict TDD)
|
## Phase 4: Fade-In Dedup at Handoff (Slice 3, strict TDD)
|
||||||
|
|
||||||
- [ ] 4.1 (RED) In `test/pantallas/pantalla_alarma_sonando_test.dart` (or a new focused fade-in test file), add a widget test asserting the Dart fade-in ramp (observable via `FakeServicioAudio`/fallback player volume changes) does NOT start before `confirmarAudioFlutter` has been invoked on the android port (i.e., before `_confirmarAudioFlutterListo()` runs) — assert `env.android.detenidas` (which `confirmarAudioFlutter` appends to, per `FakePuertoAlarmasAndroid.confirmarAudioFlutter`) is non-empty before any volume-ramp step is observed. Run `flutter test` — confirm it fails (current code starts the ramp at L66 immediately after `radio.reproducir`, before confirmation).
|
- [x] 4.1 (RED) In `test/pantallas/pantalla_alarma_sonando_test.dart` (or a new focused fade-in test file), add a widget test asserting the Dart fade-in ramp (observable via `FakeServicioAudio`/fallback player volume changes) does NOT start before `confirmarAudioFlutter` has been invoked on the android port (i.e., before `_confirmarAudioFlutterListo()` runs) — assert `env.android.detenidas` (which `confirmarAudioFlutter` appends to, per `FakePuertoAlarmasAndroid.confirmarAudioFlutter`) is non-empty before any volume-ramp step is observed. Run `flutter test` — confirm it fails (current code starts the ramp at L66 immediately after `radio.reproducir`, before confirmation).
|
||||||
- [ ] 4.2 (RED) Add a test asserting the fade-in DOES start once `_confirmarAudioFlutterListo()` has run (radio path via `estadoStream` emitting `reproduciendo`, or fallback path via `_iniciarFallback`) — the ramp must still function end-to-end after the gate. Run `flutter test` — confirm it fails or is trivially satisfied depending on 4.1's fixture; treat 4.1+4.2 as one RED pair validating gate correctness both ways.
|
- [x] 4.2 (RED) Add a test asserting the fade-in DOES start once `_confirmarAudioFlutterListo()` has run (radio path via `estadoStream` emitting `reproduciendo`, or fallback path via `_iniciarFallback`) — the ramp must still function end-to-end after the gate. Run `flutter test` — confirm it fails or is trivially satisfied depending on 4.1's fixture; treat 4.1+4.2 as one RED pair validating gate correctness both ways.
|
||||||
- [ ] 4.3 (GREEN) In `lib/pantallas/pantalla_alarma_sonando.dart`, remove the `_iniciarFadeIn()` call at L66 (radio path, currently fires immediately after `radio.reproducir(emisora)`) and the one at L96 (fallback path, currently fires before `_confirmarAudioFlutterListo()` at L97); move the single `_iniciarFadeIn()` invocation INTO `_confirmarAudioFlutterListo()` (L133-139) so it fires exactly once, after the `_audioFlutterConfirmado` guard, for both the radio and fallback paths. Run `flutter test` — confirm 4.1-4.2 pass.
|
|
||||||
- [ ] 4.4 (REFACTOR) Re-run the full `pantalla_alarma_sonando_test.dart` and `pantalla_alarma_sonando_dismiss_guard_test.dart` suites — confirm no existing fade/dismiss/snooze assertions regressed from moving the ramp start point.
|
> Implementation note: 4.1+4.2 were implemented as a single RED pair (one `testWidgets`, two
|
||||||
- [ ] 4.5 `flutter analyze` and `dart format .` — confirm clean formatting/lint state for all Slice 3 edits.
|
> assertion stages) in `test/pantallas/pantalla_alarma_sonando_test.dart`, radio path. Rather than
|
||||||
|
> relying on real `estadoStream` subscription timing (existing `_montarPantalla` pre-emits
|
||||||
|
> `reproduciendo` BEFORE mount in all prior tests, which — traced via code inspection — means
|
||||||
|
> `_confirmarAudioFlutterListo()` never actually fires in those fixtures: the broadcast stream drops
|
||||||
|
> the pre-mount event, and the fallback timer's immediate-cancel branch also short-circuits before
|
||||||
|
> any listener would see it), added a new opt-out param `audioYaReproduciendo` (default `true`,
|
||||||
|
> preserves all existing tests byte-for-byte) plus a test-only `Completer`-based gate
|
||||||
|
> (`FakePuertoAlarmasAndroid.puertaConfirmarAudioFlutter`) on `confirmarAudioFlutter`, so the test
|
||||||
|
> can deterministically observe the pre-confirm state, release the gate, then observe the
|
||||||
|
> post-confirm state — independent of stream/timer race conditions. Confirmed RED against the
|
||||||
|
> pre-4.3 code: the gate-pending assertion failed with `Actual: [0.05, 0.85]` (ramp already fired
|
||||||
|
> before the gate was ever released), proving the ramp was ungated.
|
||||||
|
>
|
||||||
|
> Also added a THIRD test beyond the literal 4.1/4.2 wording, covering the explicit edge case in
|
||||||
|
> this batch's own dispatch instructions: if `confirmarAudioFlutter` FAILS (dead/never-there native
|
||||||
|
> channel), the Dart ramp must still start — the ring must never stay stuck at
|
||||||
|
> `_volumenInicialFadeIn` forever, since Dart is the only audible source once native is gone. Added
|
||||||
|
> `FakePuertoAlarmasAndroid.fallaConfirmarAudioFlutter` to simulate this. Confirmed RED against
|
||||||
|
> pre-4.3 code too: the fake's `StateError` propagated UNCAUGHT out of `_confirmarAudioFlutterListo()`
|
||||||
|
> (no try/catch existed), crashing the test — proving the failure path was unhandled before this
|
||||||
|
> slice.
|
||||||
|
|
||||||
|
- [x] 4.3 (GREEN) In `lib/pantallas/pantalla_alarma_sonando.dart`, remove the `_iniciarFadeIn()` call at L66 (radio path, currently fires immediately after `radio.reproducir(emisora)`) and the one at L96 (fallback path, currently fires before `_confirmarAudioFlutterListo()` at L97); move the single `_iniciarFadeIn()` invocation INTO `_confirmarAudioFlutterListo()` (L133-139) so it fires exactly once, after the `_audioFlutterConfirmado` guard, for both the radio and fallback paths. Run `flutter test` — confirm 4.1-4.2 pass.
|
||||||
|
|
||||||
|
> Implementation note: moved as specified. `_confirmarAudioFlutterListo()` now wraps the native
|
||||||
|
> channel call in `try { await ... } catch (e) { debugPrint(...); } finally { _iniciarFadeIn(); }` —
|
||||||
|
> matching the existing `try/catch/finally` idiom already used in `_detener()`/`_posponer()` in the
|
||||||
|
> same file. The `finally` block is what satisfies the failure-edge case above: whether the native
|
||||||
|
> confirmation succeeds or throws, the fade-in always starts exactly once (still gated by the
|
||||||
|
> pre-existing `_audioFlutterConfirmado` guard at the top of the method, unchanged). All 3 new tests
|
||||||
|
> (4.1+4.2 combined, plus the failure-edge test) pass after this change; `rg '_iniciarFadeIn'
|
||||||
|
> lib/` confirms exactly one call site remains (inside `_confirmarAudioFlutterListo`) plus the
|
||||||
|
> function definition itself.
|
||||||
|
|
||||||
|
- [x] 4.4 (REFACTOR) Re-run the full `pantalla_alarma_sonando_test.dart` and `pantalla_alarma_sonando_dismiss_guard_test.dart` suites — confirm no existing fade/dismiss/snooze assertions regressed from moving the ramp start point. (Also included `pantalla_alarma_sonando_scaffold_test.dart` and `servicio_alarmas_android_test.dart` for extra confidence since a shared fake was extended. 22/22 tests green, no regressions — none of the pre-existing tests assert on fade/volume timing, since none of them previously exercised `_confirmarAudioFlutterListo()` at all under the old pre-mount-emit fixture pattern, as traced in the 4.1/4.2 note above.)
|
||||||
|
- [x] 4.5 `flutter analyze` and `dart format .` — confirm clean formatting/lint state for all Slice 3 edits. (`flutter analyze`: 0 issues. `dart format .` reformatted the 3 Slice 3 files cleanly; it also touched 8 files unrelated to this change — pre-existing formatter-version drift on `main`, confirmed reproducible and out of scope per this batch's file-scope constraint — reverted via `git checkout --` both times it recurred, see Phase 6 6.3 note.)
|
||||||
|
|
||||||
## Phase 5: Manual/On-Device QA (mandatory human gate — Android 14+ physical or emulator device)
|
## Phase 5: Manual/On-Device QA (mandatory human gate — Android 14+ physical or emulator device)
|
||||||
|
|
||||||
@@ -161,6 +196,48 @@ Chain strategy: pending
|
|||||||
|
|
||||||
## Phase 6: Final Static Sweep
|
## Phase 6: Final Static Sweep
|
||||||
|
|
||||||
- [ ] 6.1 Full-repo `rg 'systemExempted|SYSTEM_EXEMPTED'` across `android/app/src/main` — confirm zero remaining references (fully dropped, not kept alongside `alarm`).
|
- [x] 6.1 Full-repo `rg 'systemExempted|SYSTEM_EXEMPTED'` across `android/app/src/main` — confirm zero remaining references (fully dropped, not kept alongside `alarm`).
|
||||||
- [ ] 6.2 `flutter test` (full suite) and `flutter analyze` — final clean run before requesting review.
|
|
||||||
- [ ] 6.3 `dart format .` — confirm no formatting diffs remain uncommitted.
|
> **INTERPRETATION FLIP (sdd-apply, 2026-07-11):** this check's original "zero remaining
|
||||||
|
> references" wording was written under the Phase 1 premise that later proved false. Ran it anyway:
|
||||||
|
> 3 references found (`AndroidManifest.xml:6` permission, `AndroidManifest.xml:58`
|
||||||
|
> `foregroundServiceType`, `PluriWaveAlarmService.kt:124` runtime constant) — all `systemExempted`/
|
||||||
|
> `SYSTEM_EXEMPTED`, none `alarm`. This is the CORRECT and EXPECTED state, not a failure: per the
|
||||||
|
> Phase 1 cancellation banner (design.md correction, confirmed via `javap -constants` SDK
|
||||||
|
> inspection), `FOREGROUND_SERVICE_TYPE_ALARM`/`FOREGROUND_SERVICE_ALARM` are fictional and
|
||||||
|
> `mediaPlayback|systemExempted` is confirmed the correct, intentional declaration — so
|
||||||
|
> "fully dropped" is no longer the right target; "present and internally consistent between
|
||||||
|
> manifest and runtime" is. Manifest (`systemExempted` at L6+L58) and Kotlin runtime
|
||||||
|
> (`FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED` at L124) match each other exactly, same as before this
|
||||||
|
> entire change — Slice 1 shipped no code (cancelled), so this file pair was never touched by any
|
||||||
|
> batch of this change. Marking complete because the check RAN and its result was interpreted
|
||||||
|
> correctly against the corrected design, not because the literal original wording's outcome was
|
||||||
|
> achieved.
|
||||||
|
|
||||||
|
- [x] 6.2 `flutter test` (full suite) and `flutter analyze` — final clean run before requesting review.
|
||||||
|
|
||||||
|
> Implementation note: the full-suite invocation reproduced the documented intermittent hang (this
|
||||||
|
> task's own dispatch instructions named `estado_alarmas_ejecuciones_test.dart` and
|
||||||
|
> `servicio_grabacion_radio_test.dart` as known culprits) — it stalled past `+276` tests for several
|
||||||
|
> minutes of real time with zero further progress and had to be killed. Fell back exactly as
|
||||||
|
> instructed: ran all 44 other test files (everything under `test/` except those two) together —
|
||||||
|
> `01:02 +272: All tests passed!`, zero failures. Then ran the two known-flaky files standalone —
|
||||||
|
> `+7: All tests passed!`, zero failures, confirming (again) they only misbehave under full-suite
|
||||||
|
> load, not on their own — same conclusion already recorded in Batch 2's apply-progress for
|
||||||
|
> `servicio_grabacion_radio_test.dart` (`_fallar must clear activa flag immediately`, timing-
|
||||||
|
> sensitive). 272 + 7 = 279 tests total across all 46 files in the suite, all green. `flutter
|
||||||
|
> analyze`: 0 issues.
|
||||||
|
|
||||||
|
- [x] 6.3 `dart format .` — confirm no formatting diffs remain uncommitted.
|
||||||
|
|
||||||
|
> Implementation note: `dart format .` left the 3 files this batch touched
|
||||||
|
> (`lib/pantallas/pantalla_alarma_sonando.dart`, `test/helpers/fakes_alarmas.dart`,
|
||||||
|
> `test/pantallas/pantalla_alarma_sonando_test.dart`) untouched on its second run (already clean
|
||||||
|
> from the 4.5 pass) — confirmed via `git status --short` showing only those 3 files modified. Both
|
||||||
|
> times `dart format .` ran in this batch (4.5 and 6.3) it ALSO reformatted 8 files this batch never
|
||||||
|
> edited (`lib/pantallas/pantalla_ajustes.dart`, `lib/servicios/servicio_ecualizador.dart`, and 6
|
||||||
|
> test files under `test/estado`, `test/pantallas`, `test/servicios`) — reverted both times via `git
|
||||||
|
> checkout --` to stay within this batch's file-scope constraint (only
|
||||||
|
> `pantalla_alarma_sonando.dart` + test files). This is pre-existing formatter-version drift already
|
||||||
|
> present on `main` before this change started, unrelated to Slice 2 or Slice 3 — worth a
|
||||||
|
> follow-up `dart format .` cleanup commit on its own, out of scope here.
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ class FakePuertoAlarmasAndroid implements PuertoAlarmasAndroid {
|
|||||||
/// could not otherwise produce.
|
/// could not otherwise produce.
|
||||||
bool fallaProgramar = false;
|
bool fallaProgramar = false;
|
||||||
|
|
||||||
|
/// Test-only gate (Slice 3: fade-in dedup at handoff). When set,
|
||||||
|
/// [confirmarAudioFlutter] suspends on this completer before resolving,
|
||||||
|
/// letting a test observe the pre-confirm state deterministically instead
|
||||||
|
/// of racing real stream/timer scheduling. Complete it to let the call
|
||||||
|
/// proceed.
|
||||||
|
Completer<void>? puertaConfirmarAudioFlutter;
|
||||||
|
|
||||||
|
/// Test-only failure switch (Slice 3 edge case): when true,
|
||||||
|
/// [confirmarAudioFlutter] throws after the gate above (if any) resolves,
|
||||||
|
/// simulating a dead/never-there native channel at handoff.
|
||||||
|
bool fallaConfirmarAudioFlutter = false;
|
||||||
|
|
||||||
/// Simulates a native -> Flutter `alarmFired` MethodChannel event.
|
/// Simulates a native -> Flutter `alarmFired` MethodChannel event.
|
||||||
void emitirEvento(EventoAlarmaAndroid evento) => _eventos.add(evento);
|
void emitirEvento(EventoAlarmaAndroid evento) => _eventos.add(evento);
|
||||||
|
|
||||||
@@ -64,6 +76,12 @@ class FakePuertoAlarmasAndroid implements PuertoAlarmasAndroid {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> confirmarAudioFlutter(String alarmaId) async {
|
Future<void> confirmarAudioFlutter(String alarmaId) async {
|
||||||
|
if (puertaConfirmarAudioFlutter != null) {
|
||||||
|
await puertaConfirmarAudioFlutter!.future;
|
||||||
|
}
|
||||||
|
if (fallaConfirmarAudioFlutter) {
|
||||||
|
throw StateError('fake confirmarAudioFlutter failure');
|
||||||
|
}
|
||||||
detenidas.add(alarmaId);
|
detenidas.add(alarmaId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ class _Entorno {
|
|||||||
Future<_Entorno> _montarPantalla(
|
Future<_Entorno> _montarPantalla(
|
||||||
WidgetTester tester, {
|
WidgetTester tester, {
|
||||||
int snoozeMinutos = 5,
|
int snoozeMinutos = 5,
|
||||||
|
// Slice 3 (fade-in dedup): existing callers rely on the radio already
|
||||||
|
// being "reproduciendo" by mount time, which cancels the fallback timer
|
||||||
|
// synchronously and leaves nothing to observe mid-handoff. Fade-in-gate
|
||||||
|
// tests need a live `_estadoSub` subscriber instead, so they set this to
|
||||||
|
// false and emit `reproduciendo` themselves after the widget mounts.
|
||||||
|
bool audioYaReproduciendo = true,
|
||||||
}) async {
|
}) async {
|
||||||
tester.view.physicalSize = const Size(1440, 3200);
|
tester.view.physicalSize = const Size(1440, 3200);
|
||||||
tester.view.devicePixelRatio = 1.0;
|
tester.view.devicePixelRatio = 1.0;
|
||||||
@@ -38,7 +44,9 @@ Future<_Entorno> _montarPantalla(
|
|||||||
addTearDown(tester.view.resetDevicePixelRatio);
|
addTearDown(tester.view.resetDevicePixelRatio);
|
||||||
|
|
||||||
final audio = FakeServicioAudio();
|
final audio = FakeServicioAudio();
|
||||||
audio.emitirEstado(EstadoReproduccion.reproduciendo);
|
if (audioYaReproduciendo) {
|
||||||
|
audio.emitirEstado(EstadoReproduccion.reproduciendo);
|
||||||
|
}
|
||||||
final radio = EstadoRadio(
|
final radio = EstadoRadio(
|
||||||
audio: audio,
|
audio: audio,
|
||||||
favoritos: FakeServicioFavoritos(),
|
favoritos: FakeServicioFavoritos(),
|
||||||
@@ -158,4 +166,66 @@ void main() {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
group('fade-in dedup en el handoff (Slice 3)', () {
|
||||||
|
testWidgets('el fade-in de Dart se retiene hasta que el nativo confirma el '
|
||||||
|
'handoff, y arranca justo despues (camino radio)', (tester) async {
|
||||||
|
final entorno = await _montarPantalla(
|
||||||
|
tester,
|
||||||
|
audioYaReproduciendo: false,
|
||||||
|
);
|
||||||
|
entorno.android.puertaConfirmarAudioFlutter = Completer<void>();
|
||||||
|
|
||||||
|
// Antes de que la radio confirme "reproduciendo", solo debe existir
|
||||||
|
// el volumen de arranque previo (0.05): el ramp real hacia
|
||||||
|
// alarma.volumen todavia NO debe haber arrancado.
|
||||||
|
expect(entorno.android.detenidas, isEmpty);
|
||||||
|
expect(entorno.audio.volumenesAplicados, [0.05]);
|
||||||
|
|
||||||
|
// La radio confirma que esta reproduciendo -> dispara
|
||||||
|
// _confirmarAudioFlutterListo(), que queda bloqueado en la puerta
|
||||||
|
// (todavia no hay confirmacion nativa real).
|
||||||
|
entorno.audio.emitirEstado(EstadoReproduccion.reproduciendo);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(
|
||||||
|
entorno.android.detenidas,
|
||||||
|
isEmpty,
|
||||||
|
reason: 'confirmarAudioFlutter sigue bloqueado en la puerta de prueba',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
entorno.audio.volumenesAplicados,
|
||||||
|
[0.05],
|
||||||
|
reason: 'el fade-in de Dart no debe arrancar antes del handoff',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Se libera la puerta: recien ahi "confirma" el nativo, y solo
|
||||||
|
// entonces debe arrancar el fade-in de Dart (una unica rampa
|
||||||
|
// audible a la vez).
|
||||||
|
entorno.android.puertaConfirmarAudioFlutter!.complete();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(entorno.android.detenidas, contains('ring1'));
|
||||||
|
expect(entorno.audio.volumenesAplicados, [0.05, 0.85]);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('si confirmar el audio con el nativo falla, el fade-in de Dart '
|
||||||
|
'arranca igual (el nativo esta muerto o nunca corrio)', (tester) async {
|
||||||
|
final entorno = await _montarPantalla(
|
||||||
|
tester,
|
||||||
|
audioYaReproduciendo: false,
|
||||||
|
);
|
||||||
|
entorno.android.fallaConfirmarAudioFlutter = true;
|
||||||
|
|
||||||
|
entorno.audio.emitirEstado(EstadoReproduccion.reproduciendo);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// La confirmacion nativa fallo (el fake lanza antes de registrar en
|
||||||
|
// `detenidas`), pero el fade-in de Dart debe arrancar de todas
|
||||||
|
// formas: si el lado nativo esta muerto o nunca corrio, Dart es la
|
||||||
|
// unica fuente audible, y el ring no debe quedar pegado en
|
||||||
|
// _volumenInicialFadeIn para siempre.
|
||||||
|
expect(entorno.android.detenidas, isEmpty);
|
||||||
|
expect(entorno.audio.volumenesAplicados, [0.05, 0.85]);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user