Files
pluriwave/lib/estado/estado_radio.dart
Kira (Agent) 81db383a47 fix(v0.3.0): audio background + emisoras rotas + errores toast + icono
- 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.
2026-04-04 18:09:59 +02:00

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();
}
}