From f667277e35187046a62bcda005902d4593d9ab21 Mon Sep 17 00:00:00 2001 From: freetlab Date: Fri, 22 May 2026 15:54:39 +0200 Subject: [PATCH] feat(stations): add quality filters and list ordering --- TODO.md | 11 ++ lib/estado/estado_radio.dart | 117 +++++++++++++--- lib/pantallas/pantalla_ajustes.dart | 52 ++++++++ lib/pantallas/pantalla_buscar.dart | 126 ++++++++---------- lib/pantallas/pantalla_reproductor.dart | 39 +++++- lib/servicios/servicio_favoritos.dart | 47 ++++++- lib/servicios/servicio_grabacion_radio.dart | 6 + test/estado/estado_radio_test.dart | 7 + .../servicio_grabacion_radio_test.dart | 2 + 9 files changed, 306 insertions(+), 101 deletions(-) diff --git a/TODO.md b/TODO.md index 96c7cd9..8c2a993 100644 --- a/TODO.md +++ b/TODO.md @@ -34,3 +34,14 @@ ## Búsqueda de emisoras - [ ] Añadir filtro de calidad mínima de reproducción en kbps en el buscador de emisoras. +## Favoritos + +- [ ] Revisar el sistema de guardado de favoritos en instalaciones nuevas y migradas: inicializaci?n de SQLite, creaci?n de ruta/base de datos, migraciones de columnas y refresco de estado tras guardar. Reporte: en un m?vil no se est?n guardando favoritos. +- [ ] A?adir tests de regresi?n para favoritos en base de datos real/migrada, incluyendo esquemas antiguos y primera instalaci?n limpia. +## Agrupaciones de favoritos + +- [ ] Permitir crear listas de favoritos con nombre corto configurable por el usuario desde Ajustes. +- [ ] Mantener siempre un grupo interno por defecto traducible llamado "Sin asignar", no editable y no borrable. +- [ ] Gestionar desde la vista Favoritos qu? emisoras pertenecen a cada agrupaci?n/lista. +- [ ] Dise?ar migraci?n SQLite para asociar favoritos existentes al grupo "Sin asignar" sin perder datos. + diff --git a/lib/estado/estado_radio.dart b/lib/estado/estado_radio.dart index e38b6a9..6570f56 100644 --- a/lib/estado/estado_radio.dart +++ b/lib/estado/estado_radio.dart @@ -18,6 +18,8 @@ import '../servicios/servicio_grabacion_radio.dart'; import '../servicios/servicio_radio.dart'; import '../servicios/servicio_timer.dart'; +enum OrdenEmisoras { nombre, calidad } + /// Estado global de la app con ChangeNotifier (Provider). class EstadoRadio extends ChangeNotifier { EstadoRadio({ @@ -86,8 +88,10 @@ class EstadoRadio extends ChangeNotifier { String? _ultimoPaisBusqueda; String? _ultimoIdiomaBusqueda; String? _ultimoTagBusqueda; + int? _ultimoMinBitrateBusqueda; String? _errorCarga; static const _keyEmisoraPreferida = 'emisora_preferida_uuid_v1'; + static const _keyOrdenListas = 'orden_listas_emisoras_v1'; static const _keyTimerSuenoPresets = 'timer_sueno_presets_segundos_v1'; static const _timerSuenoPresetsDefecto = [ 180, @@ -103,13 +107,14 @@ class EstadoRadio extends ChangeNotifier { List _timerSuenoPresetsSegundos = List.from( _timerSuenoPresetsDefecto, ); + OrdenEmisoras _ordenListas = OrdenEmisoras.calidad; - List get populares => _populares; - List get tendencias => _tendencias; - List get resultadosBusqueda => _resultadosBusqueda; - List get emisorasCercanas => _emisorasCercanas; - List get listaFavoritos => _listaFavoritos; - List get emisorasCustom => _emisorasCustom; + List get populares => _ordenarEmisoras(_populares); + List get tendencias => _ordenarEmisoras(_tendencias); + List get resultadosBusqueda => _ordenarEmisoras(_resultadosBusqueda); + List get emisorasCercanas => _ordenarEmisoras(_emisorasCercanas); + List get listaFavoritos => _ordenarEmisoras(_listaFavoritos); + List get emisorasCustom => _ordenarEmisoras(_emisorasCustom); bool get cargandoPopulares => _cargandoPopulares; bool get cargandoBusqueda => _cargandoBusqueda; bool get cargandoMasBusqueda => _cargandoMasBusqueda; @@ -126,6 +131,7 @@ class EstadoRadio extends ChangeNotifier { PresetEcualizador get presetPrincipalEcualizador => _presetPrincipal; bool get ecualizadorActivo => _ecualizadorActivo; bool get ecualizadorDisponible => audio.ecualizadorDisponible; + OrdenEmisoras get ordenListas => _ordenListas; List get timerSuenoPresetsSegundos => List.unmodifiable(_timerSuenoPresetsSegundos); @@ -145,6 +151,7 @@ class EstadoRadio extends ChangeNotifier { bool get grabacionActiva => grabacion.estado.activa; String? get directorioGrabacion => grabacion.directorioConfigurado; int get maxBytesGrabacion => grabacion.maxBytes; + File? get ultimaGrabacion => grabacion.ultimoArchivo; /// Lista principal (home): custom + populares, sin duplicados. List get emisorasInicio { @@ -189,6 +196,7 @@ class EstadoRadio extends ChangeNotifier { Future _init() async { await grabacion.inicializar(); await _cargarEcualizadorPersistido(); + await _cargarOrdenListas(); await _cargarEmisoraPreferida(); await _cargarTimerSuenoPresets(); await Future.wait([ @@ -314,6 +322,23 @@ class EstadoRadio extends ChangeNotifier { _emisoraPreferidaUuid = prefs.getString(_keyEmisoraPreferida); } + Future _cargarOrdenListas() async { + final prefs = await SharedPreferences.getInstance(); + final raw = prefs.getString(_keyOrdenListas); + _ordenListas = switch (raw) { + 'nombre' => OrdenEmisoras.nombre, + 'calidad' => OrdenEmisoras.calidad, + _ => OrdenEmisoras.calidad, + }; + } + + Future cambiarOrdenListas(OrdenEmisoras orden) async { + _ordenListas = orden; + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_keyOrdenListas, orden.name); + notifyListeners(); + } + Future _normalizarEmisoraPreferida() async { final preferida = _resolverEmisoraPreferida(); if (preferida?.uuid == _emisoraPreferidaUuid) return; @@ -351,28 +376,27 @@ class EstadoRadio extends ChangeNotifier { String? pais, String? idioma, String? tag, + int? minBitrate, }) async { _ultimoNombreBusqueda = nombre; _ultimoPaisBusqueda = pais; _ultimoIdiomaBusqueda = idioma; _ultimoTagBusqueda = tag; + _ultimoMinBitrateBusqueda = minBitrate; _offsetBusqueda = 0; _hayMasBusqueda = true; _cargandoBusqueda = true; _resultadosBusqueda = []; notifyListeners(); try { - final pagina = await radio.buscar( + final pagina = await _buscarPaginaFiltrada( nombre: nombre, pais: pais, idioma: idioma, tag: tag, - limit: _tamanoPaginaBusqueda, - offset: _offsetBusqueda, + minBitrate: minBitrate, ); _resultadosBusqueda = pagina; - _offsetBusqueda = pagina.length; - _hayMasBusqueda = pagina.length == _tamanoPaginaBusqueda; } catch (_) { _errorController.add('Error en la busqueda. Comprueba tu conexion.'); } finally { @@ -386,13 +410,12 @@ class EstadoRadio extends ChangeNotifier { _cargandoMasBusqueda = true; notifyListeners(); try { - final pagina = await radio.buscar( + final pagina = await _buscarPaginaFiltrada( nombre: _ultimoNombreBusqueda, pais: _ultimoPaisBusqueda, idioma: _ultimoIdiomaBusqueda, tag: _ultimoTagBusqueda, - limit: _tamanoPaginaBusqueda, - offset: _offsetBusqueda, + minBitrate: _ultimoMinBitrateBusqueda, ); final porUuid = { for (final emisora in _resultadosBusqueda) emisora.uuid: emisora, @@ -407,8 +430,8 @@ class EstadoRadio extends ChangeNotifier { ); } _resultadosBusqueda = nuevaLista; - _offsetBusqueda += pagina.length; - _hayMasBusqueda = pagina.length == _tamanoPaginaBusqueda; + // _buscarPaginaFiltrada actualiza offset/hayMas usando p?ginas crudas. + _hayMasBusqueda = _hayMasBusqueda && pagina.isNotEmpty; } catch (_) { _errorController.add('No se pudieron cargar mas emisoras.'); } finally { @@ -417,6 +440,54 @@ class EstadoRadio extends ChangeNotifier { } } + Future> _buscarPaginaFiltrada({ + String? nombre, + String? pais, + String? idioma, + String? tag, + int? minBitrate, + }) async { + final acumuladas = []; + var intentos = 0; + while (intentos < 4 && acumuladas.isEmpty && _hayMasBusqueda) { + final pagina = await radio.buscar( + nombre: nombre, + pais: pais, + idioma: idioma, + tag: tag, + limit: _tamanoPaginaBusqueda, + offset: _offsetBusqueda, + ); + _offsetBusqueda += pagina.length; + _hayMasBusqueda = pagina.length == _tamanoPaginaBusqueda; + acumuladas.addAll(_filtrarMinBitrate(pagina, minBitrate)); + intentos++; + } + return acumuladas; + } + + List _filtrarMinBitrate(List emisoras, int? minBitrate) { + if (minBitrate == null || minBitrate <= 0) return emisoras; + return emisoras.where((e) => (e.bitrate ?? 0) >= minBitrate).toList(); + } + + List _ordenarEmisoras(List emisoras) { + final ordenadas = List.from(emisoras); + switch (_ordenListas) { + case OrdenEmisoras.nombre: + ordenadas.sort( + (a, b) => a.nombre.toLowerCase().compareTo(b.nombre.toLowerCase()), + ); + case OrdenEmisoras.calidad: + ordenadas.sort((a, b) { + final porBitrate = (b.bitrate ?? 0).compareTo(a.bitrate ?? 0); + if (porBitrate != 0) return porBitrate; + return 0; + }); + } + return ordenadas; + } + Future cargarEmisorasCercanas() async { _cargandoCercanas = true; _errorCercanas = null; @@ -451,7 +522,10 @@ class EstadoRadio extends ChangeNotifier { throw Exception('No se pudo detectar tu region'); } _paisCercanoDetectado = pais; - _emisorasCercanas = await radio.buscar(pais: pais, limit: 30); + _emisorasCercanas = _filtrarMinBitrate( + await radio.buscar(pais: pais, limit: 30), + _ultimoMinBitrateBusqueda, + ); } catch (_) { _errorCercanas = 'No pudimos detectar emisoras cercanas. Usa filtros por pais.'; @@ -527,6 +601,15 @@ class EstadoRadio extends ChangeNotifier { return launchUrl(uri, mode: LaunchMode.externalApplication); } + Future abrirUltimaGrabacion() async { + final archivo = ultimaGrabacion; + if (archivo == null || !await archivo.exists()) return false; + return launchUrl( + Uri.file(archivo.path), + mode: LaunchMode.externalApplication, + ); + } + Future cambiarDirectorioGrabacion(String path) async { await grabacion.guardarDirectorio(path); notifyListeners(); diff --git a/lib/pantallas/pantalla_ajustes.dart b/lib/pantallas/pantalla_ajustes.dart index a417d71..df337d7 100644 --- a/lib/pantallas/pantalla_ajustes.dart +++ b/lib/pantallas/pantalla_ajustes.dart @@ -62,6 +62,8 @@ class _AjustesContent extends StatelessWidget { SizedBox(height: 12), _SeccionIdioma(), SizedBox(height: 12), + _SeccionOrdenListas(), + SizedBox(height: 12), _SeccionEmisoraPreferida(), SizedBox(height: 12), _SeccionEmisoras(), @@ -641,6 +643,56 @@ class _SeccionEcualizador extends StatelessWidget { } } +class _SeccionOrdenListas extends StatelessWidget { + const _SeccionOrdenListas(); + + @override + Widget build(BuildContext context) { + final estado = context.watch(); + return PluriGlassSurface( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.sort_rounded), + const SizedBox(width: 12), + Text( + 'Orden de emisoras', + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ), + const SizedBox(height: 8), + SegmentedButton( + segments: const [ + ButtonSegment( + value: OrdenEmisoras.nombre, + icon: Icon(Icons.sort_by_alpha_rounded), + label: Text('Por nombre'), + ), + ButtonSegment( + value: OrdenEmisoras.calidad, + icon: Icon(Icons.hd_rounded), + label: Text('Por calidad'), + ), + ], + selected: {estado.ordenListas}, + onSelectionChanged: (value) { + estado.cambiarOrdenListas(value.first); + }, + ), + const SizedBox(height: 8), + Text( + 'Se aplica a favoritos, b?squedas, emisoras cercanas y listados r?pidos.', + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ); + } +} + class _SeccionEmisoraPreferida extends StatelessWidget { const _SeccionEmisoraPreferida(); diff --git a/lib/pantallas/pantalla_buscar.dart b/lib/pantallas/pantalla_buscar.dart index 3855793..c6ed31a 100644 --- a/lib/pantallas/pantalla_buscar.dart +++ b/lib/pantallas/pantalla_buscar.dart @@ -47,6 +47,7 @@ class _PantallaBuscarState extends State { final _controller = TextEditingController(); String? _paisSeleccionado; String? _idiomaSeleccionado; + int? _calidadMinima; @override void dispose() { @@ -60,6 +61,7 @@ class _PantallaBuscarState extends State { nombre: q.isNotEmpty ? q : null, pais: _paisSeleccionado, idioma: _idiomaSeleccionado, + minBitrate: _calidadMinima, ); } @@ -108,7 +110,6 @@ class _PantallaBuscarState extends State { ), ), ), - _seccionCercanas(estado), _seccionFiltro( 'Pais', _paises.map((p) => (p.$1, p.$2)).toList(), @@ -127,80 +128,20 @@ class _PantallaBuscarState extends State { _buscar(); }, ), + _seccionFiltroInt( + 'Calidad m?nima', + const [('64 kbps', 64), ('96 kbps', 96), ('128 kbps', 128), ('192 kbps', 192), ('320 kbps', 320)], + _calidadMinima, + (v) { + setState(() => _calidadMinima = v); + _buscar(); + }, + ), _resultados(estado, theme), ], ); } - Widget _seccionCercanas(EstadoRadio estado) { - final theme = Theme.of(context); - final pais = estado.paisCercanoDetectado; - return Padding( - padding: const EdgeInsets.fromLTRB(PluriLayout.horizontal, 8, PluriLayout.horizontal, 0), - child: PluriGlassSurface( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Text( - pais == null ? 'Emisoras cercanas' : 'Emisoras cercanas - $pais', - style: theme.textTheme.labelLarge?.copyWith( - fontWeight: FontWeight.w900, - ), - ), - ), - TextButton.icon( - onPressed: estado.cargandoCercanas - ? null - : estado.cargarEmisorasCercanas, - icon: estado.cargandoCercanas - ? const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : const Icon(Icons.my_location_rounded, size: 18), - label: const Text('Buscar cerca'), - ), - ], - ), - if (estado.errorCercanas != null) - Text( - estado.errorCercanas!, - style: theme.textTheme.bodySmall?.copyWith( - color: theme.colorScheme.error, - ), - ), - if (estado.emisorasCercanas.isNotEmpty) ...[ - const SizedBox(height: 8), - SizedBox( - height: 76, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: estado.emisorasCercanas.length, - separatorBuilder: (_, __) => const SizedBox(width: 8), - itemBuilder: (context, i) { - final emisora = estado.emisorasCercanas[i]; - return SizedBox( - width: 260, - child: TarjetaEmisora( - emisora: emisora, - esCompacta: true, - onTap: () => reproducirMinimizado(context, emisora), - ), - ); - }, - ), - ), - ], - ], - ), - ), - ); - } Widget _seccionFiltro( String titulo, @@ -247,6 +188,51 @@ class _PantallaBuscarState extends State { ); } + Widget _seccionFiltroInt( + String titulo, + List<(String, int)> opciones, + int? seleccionado, + void Function(int?) onChanged, + ) { + final theme = Theme.of(context); + return Padding( + padding: const EdgeInsets.fromLTRB(PluriLayout.horizontal, 8, PluriLayout.horizontal, 0), + child: PluriGlassSurface( + padding: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + titulo, + style: theme.textTheme.labelLarge?.copyWith( + fontWeight: FontWeight.w900, + ), + ), + const SizedBox(height: 6), + SizedBox( + height: 40, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: opciones.length, + separatorBuilder: (_, __) => const SizedBox(width: 8), + itemBuilder: (_, i) { + final (label, value) = opciones[i]; + final sel = seleccionado == value; + return FilterChip( + label: Text(label), + selected: sel, + visualDensity: VisualDensity.compact, + onSelected: (_) => onChanged(sel ? null : value), + ); + }, + ), + ), + ], + ), + ), + ); + } + Widget _resultados(EstadoRadio estado, ThemeData theme) { if (estado.cargandoBusqueda) { return const SizedBox( diff --git a/lib/pantallas/pantalla_reproductor.dart b/lib/pantallas/pantalla_reproductor.dart index 33643ce..86386d8 100644 --- a/lib/pantallas/pantalla_reproductor.dart +++ b/lib/pantallas/pantalla_reproductor.dart @@ -361,6 +361,7 @@ class _GrabacionWidget extends StatelessWidget { final theme = Theme.of(context); final grabacion = estado.estadoGrabacion; final activa = grabacion.activa; + final hayUltimaGrabacion = estado.ultimaGrabacion != null; return PluriGlassSurface( borderRadius: BorderRadius.circular(24), @@ -397,19 +398,43 @@ class _GrabacionWidget extends StatelessWidget { ), ), const SizedBox(width: 8), - FilledButton.tonalIcon( - icon: Icon(activa ? Icons.stop_rounded : Icons.mic_rounded), - label: Text(activa ? 'Parar' : 'Grabar'), - onPressed: - activa - ? estado.detenerGrabacion - : () => _mostrarDialogoGrabacion(context), + Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.end, + children: [ + FilledButton.tonalIcon( + icon: Icon(activa ? Icons.stop_rounded : Icons.mic_rounded), + label: Text(activa ? 'Parar' : 'Grabar'), + onPressed: + activa + ? estado.detenerGrabacion + : () => _mostrarDialogoGrabacion(context), + ), + if (!activa && hayUltimaGrabacion) + IconButton.filledTonal( + tooltip: 'Abrir ?ltima grabaci?n', + icon: const Icon(Icons.audio_file_rounded), + onPressed: () => _abrirUltimaGrabacion(context), + ), + ], ), ], ), ); } + Future _abrirUltimaGrabacion(BuildContext context) async { + final messenger = ScaffoldMessenger.of(context); + final abierto = await estado.abrirUltimaGrabacion(); + if (!context.mounted) return; + if (!abierto) { + messenger.showSnackBar( + const SnackBar(content: Text('No se pudo abrir la ?ltima grabaci?n')), + ); + } + } + void _mostrarDialogoGrabacion(BuildContext context) { showModalBottomSheet( context: context, diff --git a/lib/servicios/servicio_favoritos.dart b/lib/servicios/servicio_favoritos.dart index 6af19a5..f6a28c9 100644 --- a/lib/servicios/servicio_favoritos.dart +++ b/lib/servicios/servicio_favoritos.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; import '../modelos/emisora.dart'; @@ -19,12 +21,14 @@ class ServicioFavoritos { Future _initDb() async { final dbPath = await getDatabasesPath(); + await Directory(dbPath).create(recursive: true); final path = join(dbPath, _dbName); return openDatabase( path, version: _dbVersion, onCreate: _onCreate, onUpgrade: _onUpgrade, + onOpen: _asegurarEsquema, ); } @@ -50,14 +54,43 @@ class ServicioFavoritos { } Future _onUpgrade(Database db, int oldVersion, int newVersion) async { - if (oldVersion < 2) { - // v1→v2: añadir columnas de la Radio Browser API - await db.execute('ALTER TABLE favoritos ADD COLUMN codigo_pais TEXT'); - await db.execute('ALTER TABLE favoritos ADD COLUMN codec TEXT'); - await db.execute('ALTER TABLE favoritos ADD COLUMN bitrate INTEGER'); - await db.execute('ALTER TABLE favoritos ADD COLUMN votes INTEGER NOT NULL DEFAULT 0'); - await db.execute('ALTER TABLE favoritos ADD COLUMN clickcount INTEGER NOT NULL DEFAULT 0'); + await _asegurarEsquema(db); + } + + Future _asegurarEsquema(Database db) async { + final tablas = await db.rawQuery( + "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'favoritos'", + ); + if (tablas.isEmpty) { + await _onCreate(db, _dbVersion); + return; } + + final columnas = await _columnas(db, 'favoritos'); + Future addColumn(String nombre, String sql) async { + if (!columnas.contains(nombre)) { + await db.execute('ALTER TABLE favoritos ADD COLUMN $nombre $sql'); + columnas.add(nombre); + } + } + + // Migración defensiva: algunas instalaciones antiguas pueden venir de + // esquemas intermedios. No asumimos qué columna existe: la verificamos. + await addColumn('favicon', 'TEXT'); + await addColumn('pais', 'TEXT'); + await addColumn('codigo_pais', 'TEXT'); + await addColumn('idioma', 'TEXT'); + await addColumn('tags', 'TEXT'); + await addColumn('codec', 'TEXT'); + await addColumn('bitrate', 'INTEGER'); + await addColumn('votes', 'INTEGER NOT NULL DEFAULT 0'); + await addColumn('clickcount', 'INTEGER NOT NULL DEFAULT 0'); + await addColumn('orden', 'INTEGER NOT NULL DEFAULT 0'); + } + + Future> _columnas(Database db, String tabla) async { + final info = await db.rawQuery('PRAGMA table_info($tabla)'); + return info.map((row) => row['name'] as String).toSet(); } /// Devuelve todas las emisoras favoritas ordenadas por [orden]. diff --git a/lib/servicios/servicio_grabacion_radio.dart b/lib/servicios/servicio_grabacion_radio.dart index 3009f66..11abd62 100644 --- a/lib/servicios/servicio_grabacion_radio.dart +++ b/lib/servicios/servicio_grabacion_radio.dart @@ -100,11 +100,13 @@ class ServicioGrabacionRadio { Timer? _timerAutoStop; String? _directorioConfigurado; int _maxBytes = maxBytesPorDefecto; + File? _ultimoArchivo; EstadoGrabacionRadio get estado => _estado; Stream get estadoStream => _estadoController.stream; String? get directorioConfigurado => _directorioConfigurado; int get maxBytes => _maxBytes; + File? get ultimoArchivo => _ultimoArchivo; Future inicializar() async { try { @@ -244,6 +246,7 @@ class ServicioGrabacionRadio { } Future _finalizar() async { + final archivoFinalizado = _estado.archivo; _timerAutoStop?.cancel(); _timerAutoStop = null; await _subscripcionStream?.cancel(); @@ -255,6 +258,9 @@ class ServicioGrabacionRadio { _clienteActivo?.close(); } _clienteActivo = null; + if (archivoFinalizado != null) { + _ultimoArchivo = archivoFinalizado; + } _emitir(const EstadoGrabacionRadio.inactiva()); } diff --git a/test/estado/estado_radio_test.dart b/test/estado/estado_radio_test.dart index ad81113..f2dbbfa 100644 --- a/test/estado/estado_radio_test.dart +++ b/test/estado/estado_radio_test.dart @@ -7,10 +7,17 @@ import 'package:pluriwave/estado/estado_radio.dart'; import 'package:pluriwave/modelos/emisora.dart'; import 'package:pluriwave/modelos/preset_ecualizador.dart'; import 'package:pluriwave/servicios/servicio_audio.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import '../helpers/fakes.dart'; void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + SharedPreferences.setMockInitialValues({}); + }); + group('EstadoRadio integración de custom + EQ persistente', () { test('incluye emisoras custom en el listado principal de inicio', () async { final archivo = await _crearArchivoCustom([ diff --git a/test/servicios/servicio_grabacion_radio_test.dart b/test/servicios/servicio_grabacion_radio_test.dart index 83d2767..859abc7 100644 --- a/test/servicios/servicio_grabacion_radio_test.dart +++ b/test/servicios/servicio_grabacion_radio_test.dart @@ -44,6 +44,7 @@ void main() { expect(archivos.single.path, endsWith('.mp3')); expect(await File(archivos.single.path).readAsBytes(), [1, 2, 3, 4, 5]); expect(servicio.estado.tipo, EstadoGrabacionRadioTipo.inactiva); + expect(servicio.ultimoArchivo?.path, archivos.single.path); await servicio.dispose(); }, @@ -74,6 +75,7 @@ void main() { await servicio.detener(); expect(servicio.estado.tipo, EstadoGrabacionRadioTipo.inactiva); + expect(servicio.ultimoArchivo, isNotNull); await controller.close(); await servicio.dispose(); });