feat(stations): add quality filters and list ordering
Build & Deploy Pluriwave / Análisis de código (push) Successful in 26s
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 1m42s

This commit is contained in:
2026-05-22 15:54:39 +02:00
parent 0114e4805e
commit f667277e35
9 changed files with 306 additions and 101 deletions
+100 -17
View File
@@ -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 = <int>[
180,
@@ -103,13 +107,14 @@ class EstadoRadio extends ChangeNotifier {
List<int> _timerSuenoPresetsSegundos = List<int>.from(
_timerSuenoPresetsDefecto,
);
OrdenEmisoras _ordenListas = OrdenEmisoras.calidad;
List<Emisora> get populares => _populares;
List<Emisora> get tendencias => _tendencias;
List<Emisora> get resultadosBusqueda => _resultadosBusqueda;
List<Emisora> get emisorasCercanas => _emisorasCercanas;
List<Emisora> get listaFavoritos => _listaFavoritos;
List<Emisora> get emisorasCustom => _emisorasCustom;
List<Emisora> get populares => _ordenarEmisoras(_populares);
List<Emisora> get tendencias => _ordenarEmisoras(_tendencias);
List<Emisora> get resultadosBusqueda => _ordenarEmisoras(_resultadosBusqueda);
List<Emisora> get emisorasCercanas => _ordenarEmisoras(_emisorasCercanas);
List<Emisora> get listaFavoritos => _ordenarEmisoras(_listaFavoritos);
List<Emisora> 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<int> get timerSuenoPresetsSegundos =>
List<int>.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<Emisora> get emisorasInicio {
@@ -189,6 +196,7 @@ class EstadoRadio extends ChangeNotifier {
Future<void> _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<void> _cargarOrdenListas() async {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString(_keyOrdenListas);
_ordenListas = switch (raw) {
'nombre' => OrdenEmisoras.nombre,
'calidad' => OrdenEmisoras.calidad,
_ => OrdenEmisoras.calidad,
};
}
Future<void> cambiarOrdenListas(OrdenEmisoras orden) async {
_ordenListas = orden;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_keyOrdenListas, orden.name);
notifyListeners();
}
Future<void> _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 = <String, Emisora>{
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<List<Emisora>> _buscarPaginaFiltrada({
String? nombre,
String? pais,
String? idioma,
String? tag,
int? minBitrate,
}) async {
final acumuladas = <Emisora>[];
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<Emisora> _filtrarMinBitrate(List<Emisora> emisoras, int? minBitrate) {
if (minBitrate == null || minBitrate <= 0) return emisoras;
return emisoras.where((e) => (e.bitrate ?? 0) >= minBitrate).toList();
}
List<Emisora> _ordenarEmisoras(List<Emisora> emisoras) {
final ordenadas = List<Emisora>.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<void> 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<bool> abrirUltimaGrabacion() async {
final archivo = ultimaGrabacion;
if (archivo == null || !await archivo.exists()) return false;
return launchUrl(
Uri.file(archivo.path),
mode: LaunchMode.externalApplication,
);
}
Future<void> cambiarDirectorioGrabacion(String path) async {
await grabacion.guardarDirectorio(path);
notifyListeners();