- ServicioAudio: delega a PluriWaveAudioHandler (audio_service) para mantener audio vivo en background. AudioService.init() en main.dart. onTaskRemoved() libera player. mediaItem con nombre/artista/artwork. - ServicioRadio: lastcheckok=1 en todas las peticiones — solo emisoras verificadas como funcionales por Radio Browser API. - EstadoRadio: errorStream (broadcast) para errores de reproducción y búsqueda. App.dart suscribe y muestra SnackBar flotante 3s. Los errores de carga de lista siguen como banner inline. - Icono: generado con SDXL (morado, ondas radio blancas, Material You). 5 densidades Android (48-192px), ic_launcher_round añadido.
145 lines
3.9 KiB
Dart
145 lines
3.9 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/foundation.dart';
|
|
import '../modelos/emisora.dart';
|
|
import '../servicios/servicio_audio.dart';
|
|
import '../servicios/servicio_favoritos.dart';
|
|
import '../servicios/servicio_radio.dart';
|
|
import '../servicios/servicio_timer.dart';
|
|
|
|
/// Estado global de la app con ChangeNotifier (Provider).
|
|
///
|
|
/// Errores de reproducción se emiten por [errorStream] para mostrar como
|
|
/// SnackBar — no bloquean la UI.
|
|
class EstadoRadio extends ChangeNotifier {
|
|
final ServicioAudio audio = ServicioAudio();
|
|
final ServicioFavoritos favoritos = ServicioFavoritos();
|
|
final ServicioRadio radio = ServicioRadio();
|
|
late final ServicioTimer timer;
|
|
|
|
// Errores de reproducción → SnackBar en el UI
|
|
final _errorController = StreamController<String>.broadcast();
|
|
Stream<String> get errorStream => _errorController.stream;
|
|
|
|
List<Emisora> _populares = [];
|
|
List<Emisora> _tendencias = [];
|
|
List<Emisora> _resultadosBusqueda = [];
|
|
List<Emisora> _listafavoritos = [];
|
|
|
|
bool _cargandoPopulares = false;
|
|
bool _cargandoBusqueda = false;
|
|
String? _errorCarga; // solo para errores de carga de lista (banner estático)
|
|
|
|
EstadoRadio() {
|
|
timer = ServicioTimer(audio);
|
|
_init();
|
|
}
|
|
|
|
List<Emisora> get populares => _populares;
|
|
List<Emisora> get tendencias => _tendencias;
|
|
List<Emisora> get resultadosBusqueda => _resultadosBusqueda;
|
|
List<Emisora> get listaFavoritos => _listafavoritos;
|
|
bool get cargandoPopulares => _cargandoPopulares;
|
|
bool get cargandoBusqueda => _cargandoBusqueda;
|
|
String? get error => _errorCarga;
|
|
Emisora? get emisoraActual => audio.emisoraActual;
|
|
Stream<EstadoReproduccion> get estadoStream => audio.estadoStream;
|
|
|
|
Future<void> _init() async {
|
|
await Future.wait([
|
|
cargarPopulares(),
|
|
cargarFavoritos(),
|
|
]);
|
|
}
|
|
|
|
Future<void> cargarPopulares() async {
|
|
_cargandoPopulares = true;
|
|
_errorCarga = null;
|
|
notifyListeners();
|
|
try {
|
|
final results = await Future.wait([
|
|
radio.obtenerPopulares(limit: 30),
|
|
radio.obtenerTendencias(limit: 20),
|
|
]);
|
|
_populares = results[0];
|
|
_tendencias = results[1];
|
|
} catch (e) {
|
|
_errorCarga = 'Sin conexión a la API de radio';
|
|
} finally {
|
|
_cargandoPopulares = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
Future<void> cargarFavoritos() async {
|
|
_listafavoritos = await favoritos.obtenerTodos();
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> buscar({
|
|
String? nombre,
|
|
String? pais,
|
|
String? idioma,
|
|
String? tag,
|
|
}) async {
|
|
_cargandoBusqueda = true;
|
|
_resultadosBusqueda = [];
|
|
notifyListeners();
|
|
try {
|
|
_resultadosBusqueda = await radio.buscar(
|
|
nombre: nombre,
|
|
pais: pais,
|
|
idioma: idioma,
|
|
tag: tag,
|
|
);
|
|
} catch (e) {
|
|
// Error de búsqueda → toast, no bloquear pantalla
|
|
_errorController.add('Error en la búsqueda. Comprueba tu conexión.');
|
|
} finally {
|
|
_cargandoBusqueda = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
Future<void> reproducir(Emisora emisora) async {
|
|
try {
|
|
await audio.reproducir(emisora);
|
|
radio.registrarClick(emisora.uuid); // fire & forget
|
|
notifyListeners();
|
|
} catch (e) {
|
|
// Error de reproducción → SnackBar, no pintar en medio de la UI
|
|
_errorController.add('No se puede reproducir "${emisora.nombre}"');
|
|
}
|
|
}
|
|
|
|
Future<void> togglePlay() async {
|
|
await audio.togglePlay();
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<bool> toggleFavorito(Emisora emisora) async {
|
|
final esFav = await favoritos.toggleFavorito(emisora);
|
|
await cargarFavoritos();
|
|
return esFav;
|
|
}
|
|
|
|
Future<bool> esFavorito(String uuid) => favoritos.esFavorito(uuid);
|
|
|
|
void iniciarTimer(int minutos) {
|
|
timer.iniciar(minutos);
|
|
notifyListeners();
|
|
}
|
|
|
|
void cancelarTimer() {
|
|
timer.cancelar();
|
|
notifyListeners();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_errorController.close();
|
|
audio.dispose();
|
|
timer.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|