import 'dart:async'; import 'package:pluriwave/l10n/gen/app_localizations.dart'; import 'package:pluriwave/modelos/alarma_musical.dart'; import 'package:pluriwave/servicios/servicio_alarmas_android.dart'; import 'package:pluriwave/servicios/servicio_grabacion_radio.dart'; /// Shared fake of the Android alarm bridge for alarm-related tests. class FakePuertoAlarmasAndroid implements PuertoAlarmasAndroid { final programadas = []; final canceladas = []; final detenidas = []; final ocultadas = []; final ejecucionesNativas = []; final snoozesNativos = []; final _eventos = StreamController.broadcast(); bool ignoraOptimizacionBateria = true; int solicitudesExencionBateria = 0; /// Records each [forzarVolumenMediaParaAlarma] call (Slice 2: ring-scoped /// device-volume override). final volumenForzado = []; /// Counts [restaurarVolumenMedia] calls (Slice 2). A plain counter, not a /// list: widget tests only need to assert how many times restore ran. int volumenRestaurado = 0; /// Test-only failure switch (Design D7): when true, [programar] throws /// instead of scheduling, enabling failure-path coverage that the fake /// could not otherwise produce. 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? 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. void emitirEvento(EventoAlarmaAndroid evento) => _eventos.add(evento); @override Stream get eventosAlarma => _eventos.stream; @override void configurarLocalizaciones(AppLocalizations l10n) {} @override Future programar(AlarmaMusical alarma) async { if (fallaProgramar) { throw StateError('fake programar failure'); } programadas.add(alarma); } @override Future cancelar(String alarmaId) async { canceladas.add(alarmaId); } @override Future detenerSonidoNativo(String alarmaId) async { detenidas.add(alarmaId); } @override Future ocultarNotificacionAlarma(String alarmaId) async { ocultadas.add(alarmaId); } @override Future confirmarAudioFlutter(String alarmaId) async { if (puertaConfirmarAudioFlutter != null) { await puertaConfirmarAudioFlutter!.future; } if (fallaConfirmarAudioFlutter) { throw StateError('fake confirmarAudioFlutter failure'); } detenidas.add(alarmaId); } @override Future forzarVolumenMediaParaAlarma(double fraccion) async { volumenForzado.add(fraccion); } @override Future restaurarVolumenMedia() async { volumenRestaurado++; } @override Future diagnostico() async => DiagnosticoAlarmasAndroid( puedeProgramarExactas: true, notificacionesPermitidas: true, puedeUsarPantallaCompleta: true, ignoraOptimizacionBateria: ignoraOptimizacionBateria, alarmasNativasPendientes: 0, fabricante: 'test', versionSdk: 35, ); @override Future solicitarExencionBateria() async { solicitudesExencionBateria++; return true; } @override Future obtenerEventoInicial() async => null; @override Future> obtenerEjecucionesNativasGestionadas() async => ejecucionesNativas; @override Future> obtenerEstadoSnoozeNativo() async => List.of(snoozesNativos); @override Future solicitarPermisoAlarmasExactas() async => true; @override Future solicitarPermisoNotificaciones() async => true; @override Future solicitarPermisoPantallaCompleta() async => true; Future dispose() => _eventos.close(); } /// Inactive recording service fake, safe for widget tests. class FakeServicioGrabacionRadioInactiva extends ServicioGrabacionRadio { final _controller = StreamController.broadcast(); @override EstadoGrabacionRadio get estado => const EstadoGrabacionRadio.inactiva(); @override Stream get estadoStream => _controller.stream; @override Future inicializar() async {} @override Future dispose() => _controller.close(); }