import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:path_provider/path_provider.dart'; import '../modelos/emisora.dart'; import '../modelos/preset_ecualizador.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). 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 final _errorController = StreamController.broadcast(); Stream get errorStream => _errorController.stream; List _populares = []; List _tendencias = []; List _resultadosBusqueda = []; List _listafavoritos = []; List _emisorasCustom = []; // Presets EQ guardados por uuid de emisora final Map _presetsEmisoraMap = {}; PresetEcualizador _presetActual = PresetEcualizador.flat; bool _cargandoPopulares = false; bool _cargandoBusqueda = false; String? _errorCarga; EstadoRadio() { timer = ServicioTimer(audio); _init(); } List get populares => _populares; List get tendencias => _tendencias; List get resultadosBusqueda => _resultadosBusqueda; List get listaFavoritos => _listafavoritos; List get emisorasCustom => _emisorasCustom; bool get cargandoPopulares => _cargandoPopulares; bool get cargandoBusqueda => _cargandoBusqueda; String? get error => _errorCarga; Emisora? get emisoraActual => audio.emisoraActual; Stream get estadoStream => audio.estadoStream; PresetEcualizador get presetEcualizador => _presetActual; bool get ecualizadorDisponible => audio.ecualizadorDisponible; Future _init() async { await Future.wait([ cargarPopulares(), cargarFavoritos(), _cargarEmisoresCustom(), ]); } Future 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 cargarFavoritos() async { _listafavoritos = await favoritos.obtenerTodos(); notifyListeners(); } Future 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) { _errorController.add('Error en la búsqueda. Comprueba tu conexión.'); } finally { _cargandoBusqueda = false; notifyListeners(); } } Future reproducir(Emisora emisora) async { try { await audio.reproducir(emisora); radio.registrarClick(emisora.uuid); // fire & forget // Restaurar preset del ecualizador de esta emisora final preset = _presetsEmisoraMap[emisora.uuid] ?? PresetEcualizador.flat; await cambiarPresetEcualizador(preset, guardPorEmisora: false); notifyListeners(); } catch (e) { _errorController.add('No se puede reproducir "${emisora.nombre}"'); } } Future togglePlay() async { await audio.togglePlay(); notifyListeners(); } Future toggleFavorito(Emisora emisora) async { final esFav = await favoritos.toggleFavorito(emisora); await cargarFavoritos(); notifyListeners(); return esFav; } Future esFavorito(String uuid) => favoritos.esFavorito(uuid); // ── Ecualizador ────────────────────────────────────────────────────────── Future cambiarPresetEcualizador( PresetEcualizador preset, { bool guardPorEmisora = true, }) async { _presetActual = preset; await audio.aplicarPreset(preset); if (guardPorEmisora && emisoraActual != null) { _presetsEmisoraMap[emisoraActual!.uuid] = preset; } notifyListeners(); } Future cambiarBandaEcualizador(int index, double db) async { final bandas = List.from(_presetActual.bandas); if (index >= 0 && index < bandas.length) bandas[index] = db; _presetActual = PresetEcualizador(nombre: 'Personalizado', bandas: bandas); await audio.setBanda(index, db); if (emisoraActual != null) { _presetsEmisoraMap[emisoraActual!.uuid] = _presetActual; } notifyListeners(); } // ── Emisoras personalizadas ─────────────────────────────────────────────── Future _archivoCustom() async { final dir = await getApplicationDocumentsDirectory(); return File('${dir.path}/emisoras_custom.json'); } Future _cargarEmisoresCustom() async { try { final f = await _archivoCustom(); if (!await f.exists()) return; final data = jsonDecode(await f.readAsString()) as List; _emisorasCustom = data .map((e) => Emisora.fromMap(Map.from(e as Map))) .toList(); } catch (_) { _emisorasCustom = []; } notifyListeners(); } Future _guardarEmisoresCustom() async { final f = await _archivoCustom(); await f.writeAsString(jsonEncode(_emisorasCustom.map((e) => e.toMap()).toList())); } Future agregarEmitoraCustom(Emisora emisora) async { _emisorasCustom.removeWhere((e) => e.uuid == emisora.uuid); _emisorasCustom.add(emisora); await _guardarEmisoresCustom(); notifyListeners(); } Future eliminarEmitoraCustom(String uuid) async { _emisorasCustom.removeWhere((e) => e.uuid == uuid); await _guardarEmisoresCustom(); notifyListeners(); } // ── Export / Import ─────────────────────────────────────────────────────── /// Genera el JSON de toda la configuración. Future> exportarConfig() async { final favs = await favoritos.obtenerTodos(); return { 'version': 1, 'exportedAt': DateTime.now().toIso8601String(), 'favoritos': favs.map((e) => e.toMap()).toList(), 'emisorasCustom': _emisorasCustom.map((e) => e.toMap()).toList(), 'presetsEcualizador': _presetsEmisoraMap.map( (uuid, preset) => MapEntry(uuid, preset.toJson()), ), }; } /// Importa configuración desde un JSON exportado previamente. Future importarConfig(Map data) async { final version = data['version'] as int? ?? 1; if (version != 1) throw Exception('Versión de configuración no compatible'); // Importar favoritos final favRaw = data['favoritos'] as List? ?? []; for (final raw in favRaw) { final emisora = Emisora.fromMap(Map.from(raw as Map)); await favoritos.agregar(emisora); } // Importar emisoras custom final customRaw = data['emisorasCustom'] as List? ?? []; _emisorasCustom = customRaw .map((e) => Emisora.fromMap(Map.from(e as Map))) .toList(); await _guardarEmisoresCustom(); // Importar presets EQ final presetsRaw = data['presetsEcualizador'] as Map? ?? {}; _presetsEmisoraMap.clear(); presetsRaw.forEach((uuid, presetJson) { _presetsEmisoraMap[uuid as String] = PresetEcualizador.desdeJson(Map.from(presetJson as Map)); }); await cargarFavoritos(); notifyListeners(); } // ── Timer ───────────────────────────────────────────────────────────────── void iniciarTimer(int minutos) { timer.iniciar(minutos); notifyListeners(); } void cancelarTimer() { timer.cancelar(); notifyListeners(); } @override void dispose() { _errorController.close(); audio.dispose(); timer.dispose(); super.dispose(); } }