import 'dart:async'; import 'package:pluriwave/modelos/emisora.dart'; import 'package:pluriwave/modelos/grupo_favoritos.dart'; import 'package:pluriwave/modelos/preset_ecualizador.dart'; import 'package:pluriwave/servicios/servicio_audio.dart'; import 'package:pluriwave/servicios/servicio_ecualizador.dart'; import 'package:pluriwave/servicios/servicio_favoritos.dart'; import 'package:pluriwave/servicios/servicio_radio.dart'; class FakeServicioAudio extends ServicioAudio { FakeServicioAudio({this.ecualizadorActivo = true}) { _estadoController.add(EstadoReproduccion.detenido); } final bool ecualizadorActivo; final _estadoController = StreamController.broadcast(); final List presetsAplicados = []; final List emisorasReproducidas = []; final List cambiosEcualizadorActivo = []; Emisora? _emisoraActual; EstadoReproduccion _estadoActual = EstadoReproduccion.detenido; @override Emisora? get emisoraActual => _emisoraActual; @override bool get ecualizadorDisponible => ecualizadorActivo; @override Stream get estadoStream => _estadoController.stream; @override bool get estaSonando => _estadoActual == EstadoReproduccion.reproduciendo; @override Future reproducir(Emisora emisora) async { _emisoraActual = emisora; emisorasReproducidas.add(emisora); emitirEstado(EstadoReproduccion.reproduciendo); } @override Future detener() async { _emisoraActual = null; emitirEstado(EstadoReproduccion.detenido); } void emitirEstado(EstadoReproduccion estado) { _estadoActual = estado; _estadoController.add(estado); } @override Future aplicarPreset(PresetEcualizador preset) async { presetsAplicados.add(preset); } @override Future setBanda(int index, double db) async {} @override Future setEcualizadorActivo(bool activo) async { cambiosEcualizadorActivo.add(activo); } @override Future dispose() async { await _estadoController.close(); } } class FakeServicioFavoritos extends ServicioFavoritos { final List _favoritos = []; final List _grupos = [ const GrupoFavoritos( id: GrupoFavoritos.sinAsignarId, nombre: 'Sin asignar', orden: 0, protegido: true, ), ]; int toggleCalls = 0; @override Future> obtenerTodos() async => _favoritos.map((e) => e.copyWith()).toList(); @override Future agregar(Emisora emisora) async { _favoritos.removeWhere((e) => e.uuid == emisora.uuid); _favoritos.add(emisora.copyWith(orden: _favoritos.length)); } @override Future eliminar(String uuid) async { _favoritos.removeWhere((e) => e.uuid == uuid); for (var i = 0; i < _favoritos.length; i++) { _favoritos[i] = _favoritos[i].copyWith(orden: i); } } @override Future esFavorito(String uuid) async => _favoritos.any((e) => e.uuid == uuid); @override Future toggleFavorito(Emisora emisora) async { toggleCalls += 1; if (_favoritos.any((e) => e.uuid == emisora.uuid)) { await eliminar(emisora.uuid); return false; } await agregar(emisora); return true; } @override Future> obtenerGrupos() async => List.unmodifiable(_grupos); @override Future crearGrupo(String nombre) async { final grupo = GrupoFavoritos( id: 'grupo_${_grupos.length}', nombre: nombre.trim(), orden: _grupos.length, ); _grupos.add(grupo); return grupo; } @override Future renombrarGrupo(String id, String nombre) async { final index = _grupos.indexWhere((g) => g.id == id && !g.protegido); if (index == -1) return; _grupos[index] = _grupos[index].copyWith(nombre: nombre.trim()); } @override Future eliminarGrupo(String id) async { if (id == GrupoFavoritos.sinAsignarId) return; _grupos.removeWhere((g) => g.id == id && !g.protegido); for (var i = 0; i < _favoritos.length; i++) { if (_favoritos[i].grupoFavoritosId == id) { _favoritos[i] = _favoritos[i].copyWith( grupoFavoritosId: GrupoFavoritos.sinAsignarId, ); } } } @override Future asignarGrupo(String uuid, String grupoId) async { final destino = _grupos.any((g) => g.id == grupoId) ? grupoId : GrupoFavoritos.sinAsignarId; final index = _favoritos.indexWhere((e) => e.uuid == uuid); if (index != -1) { _favoritos[index] = _favoritos[index].copyWith(grupoFavoritosId: destino); } } @override Future reordenar(String uuid, int nuevoOrden) async { final oldIndex = _favoritos.indexWhere((e) => e.uuid == uuid); if (oldIndex == -1 || _favoritos.isEmpty) return; final targetIndex = nuevoOrden.clamp(0, _favoritos.length - 1); final moved = _favoritos.removeAt(oldIndex); _favoritos.insert(targetIndex, moved); for (var i = 0; i < _favoritos.length; i++) { _favoritos[i] = _favoritos[i].copyWith(orden: i); } } } class FakeServicioRadio extends ServicioRadio { FakeServicioRadio({ List? populares, List? tendencias, List? busqueda, List>? popularesPorLlamada, List>? tendenciasPorLlamada, List? erroresPopularesPorLlamada, List? erroresTendenciasPorLlamada, }) : _populares = populares ?? [], _tendencias = tendencias ?? [], _busqueda = busqueda ?? [], _popularesPorLlamada = popularesPorLlamada ?? const [], _tendenciasPorLlamada = tendenciasPorLlamada ?? const [], _erroresPopularesPorLlamada = erroresPopularesPorLlamada ?? const [], _erroresTendenciasPorLlamada = erroresTendenciasPorLlamada ?? const []; final List _populares; final List _tendencias; final List _busqueda; final List> _popularesPorLlamada; final List> _tendenciasPorLlamada; final List _erroresPopularesPorLlamada; final List _erroresTendenciasPorLlamada; int obtenerPopularesCalls = 0; int obtenerTendenciasCalls = 0; int registrarClickCalls = 0; String? ultimoUuidClick; Exception _normalizarError(Object error) => error is Exception ? error : Exception(error.toString()); @override Future> obtenerPopulares({ int limit = 30, int offset = 0, }) async { final llamada = obtenerPopularesCalls++; if (llamada < _erroresPopularesPorLlamada.length) { throw _normalizarError(_erroresPopularesPorLlamada[llamada]); } final data = llamada < _popularesPorLlamada.length ? _popularesPorLlamada[llamada] : _populares; return data.take(limit).toList(); } @override Future> obtenerTendencias({int limit = 20}) async { final llamada = obtenerTendenciasCalls++; if (llamada < _erroresTendenciasPorLlamada.length) { throw _normalizarError(_erroresTendenciasPorLlamada[llamada]); } final data = llamada < _tendenciasPorLlamada.length ? _tendenciasPorLlamada[llamada] : _tendencias; return data.take(limit).toList(); } @override Future> buscar({ String? nombre, String? pais, String? idioma, String? tag, int limit = 30, int offset = 0, }) async { return _busqueda.skip(offset).take(limit).toList(); } @override Future registrarClick(String uuid) async { registrarClickCalls += 1; ultimoUuidClick = uuid; } } class FakeServicioEcualizador extends ServicioEcualizador { FakeServicioEcualizador({ PresetEcualizador? principal, Map? porEmisora, bool activo = true, }) : _config = ConfiguracionEcualizador( principal: principal ?? PresetEcualizador.flat, porEmisora: porEmisora ?? {}, activo: activo, ); ConfiguracionEcualizador _config; ConfiguracionEcualizador get config => _config; @override Future cargar() async => _config; @override Future guardarPrincipal(PresetEcualizador preset) async { _config = ConfiguracionEcualizador( principal: preset, porEmisora: _config.porEmisora, activo: _config.activo, ); } @override Future guardarActivo(bool activo) async { _config = ConfiguracionEcualizador( principal: _config.principal, porEmisora: _config.porEmisora, activo: activo, ); } @override Future guardarPorEmisora(String uuid, PresetEcualizador preset) async { final mapa = Map.from(_config.porEmisora); mapa[uuid] = preset; _config = ConfiguracionEcualizador( principal: _config.principal, porEmisora: mapa, activo: _config.activo, ); } @override Future eliminarPorEmisora(String uuid) async { final mapa = Map.from(_config.porEmisora); mapa.remove(uuid); _config = ConfiguracionEcualizador( principal: _config.principal, porEmisora: mapa, activo: _config.activo, ); } } Emisora emisoraDemo({ required String uuid, required String nombre, String url = 'https://stream.demo/radio', }) { return Emisora(uuid: uuid, nombre: nombre, url: url); }