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 soloOcultadas = []; final ejecucionesNativas = []; final snoozesNativos = []; final _eventos = StreamController.broadcast(); bool ignoraOptimizacionBateria = true; int solicitudesExencionBateria = 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 failure switch: when true, [detenerSonidoActivo] reports an /// unconfirmed/failed stop instead of a confirmed one. bool fallaDetener = false; /// Simulates [PluriWaveAlarmService.activeRingingId]: the id the fake /// reports as currently ringing, or null if nothing rings. String? alarmaSonandoIdValor; /// Test-only failure switch (Finding 2, fail-toward-silence): when true, /// [alarmaSonandoId] throws instead of returning a value, exercising the /// [detenerSonidoNativo] fallback in `EstadoAlarmas._detenerSiEstaSonando`. bool fallaConsultaSonando = false; /// Every alarm id [detenerSonidoActivo] was invoked for, in call order. final detencionesActivas = []; /// Test-only gate (RES-2 guard test): when set, [detenerSonidoActivo] /// awaits it before resolving, letting tests exercise an overlapping /// in-flight call. Completer? detenerActivoGate; /// 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 alarmaSonandoId() async { if (fallaConsultaSonando) { throw StateError('fake alarmaSonandoId failure'); } return alarmaSonandoIdValor; } @override Future detenerSonidoActivo() async { detencionesActivas.add(alarmaSonandoIdValor ?? ''); final gate = detenerActivoGate; if (gate != null) await gate.future; if (fallaDetener) { return const ResultadoDetencion(detenido: false, estabaSonando: true); } return ResultadoDetencion( detenido: true, estabaSonando: alarmaSonandoIdValor != null, alarmaId: alarmaSonandoIdValor, ); } @override Future ocultarNotificacionAlarma(String alarmaId) async { ocultadas.add(alarmaId); } @override Future ocultarSoloNotificacion(String alarmaId) async { soloOcultadas.add(alarmaId); } @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(); }