Some checks failed
Flutter CI/CD — PluriWave / Test + Build (push) Has been cancelled
- MainActivity: extiende AudioServiceActivity (fix pantalla en blanco) - ServicioAudio: AndroidEqualizer en AudioPipeline, aplicarPreset(), setBanda() - PresetEcualizador: modelo independiente (Flat/Rock/Pop/BassBoost/Jazz/Voz) - EcualizadorWidget: 5 sliders verticales + PresetsEcualizadorWidget - TarjetaEmisora: botón favorito visible en grid y lista (toggle con SnackBar) - EstadoRadio: emisoras custom (CRUD), export/import JSON v1, presets por emisora - PantallaAjustes: ecualizador interactivo, form añadir emisora, backup export/import - pubspec: +file_picker ^8.1.7, +uuid ^4.5.1
271 lines
8.6 KiB
Dart
271 lines
8.6 KiB
Dart
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<String>.broadcast();
|
|
Stream<String> get errorStream => _errorController.stream;
|
|
|
|
List<Emisora> _populares = [];
|
|
List<Emisora> _tendencias = [];
|
|
List<Emisora> _resultadosBusqueda = [];
|
|
List<Emisora> _listafavoritos = [];
|
|
List<Emisora> _emisorasCustom = [];
|
|
|
|
// Presets EQ guardados por uuid de emisora
|
|
final Map<String, PresetEcualizador> _presetsEmisoraMap = {};
|
|
PresetEcualizador _presetActual = PresetEcualizador.flat;
|
|
|
|
bool _cargandoPopulares = false;
|
|
bool _cargandoBusqueda = false;
|
|
String? _errorCarga;
|
|
|
|
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;
|
|
List<Emisora> get emisorasCustom => _emisorasCustom;
|
|
bool get cargandoPopulares => _cargandoPopulares;
|
|
bool get cargandoBusqueda => _cargandoBusqueda;
|
|
String? get error => _errorCarga;
|
|
Emisora? get emisoraActual => audio.emisoraActual;
|
|
Stream<EstadoReproduccion> get estadoStream => audio.estadoStream;
|
|
PresetEcualizador get presetEcualizador => _presetActual;
|
|
bool get ecualizadorDisponible => audio.ecualizadorDisponible;
|
|
|
|
Future<void> _init() async {
|
|
await Future.wait([
|
|
cargarPopulares(),
|
|
cargarFavoritos(),
|
|
_cargarEmisoresCustom(),
|
|
]);
|
|
}
|
|
|
|
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) {
|
|
_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
|
|
// 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<void> togglePlay() async {
|
|
await audio.togglePlay();
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<bool> toggleFavorito(Emisora emisora) async {
|
|
final esFav = await favoritos.toggleFavorito(emisora);
|
|
await cargarFavoritos();
|
|
notifyListeners();
|
|
return esFav;
|
|
}
|
|
|
|
Future<bool> esFavorito(String uuid) => favoritos.esFavorito(uuid);
|
|
|
|
// ── Ecualizador ──────────────────────────────────────────────────────────
|
|
|
|
Future<void> cambiarPresetEcualizador(
|
|
PresetEcualizador preset, {
|
|
bool guardPorEmisora = true,
|
|
}) async {
|
|
_presetActual = preset;
|
|
await audio.aplicarPreset(preset);
|
|
if (guardPorEmisora && emisoraActual != null) {
|
|
_presetsEmisoraMap[emisoraActual!.uuid] = preset;
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> cambiarBandaEcualizador(int index, double db) async {
|
|
final bandas = List<double>.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<File> _archivoCustom() async {
|
|
final dir = await getApplicationDocumentsDirectory();
|
|
return File('${dir.path}/emisoras_custom.json');
|
|
}
|
|
|
|
Future<void> _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<String, dynamic>.from(e as Map)))
|
|
.toList();
|
|
} catch (_) {
|
|
_emisorasCustom = [];
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> _guardarEmisoresCustom() async {
|
|
final f = await _archivoCustom();
|
|
await f.writeAsString(jsonEncode(_emisorasCustom.map((e) => e.toMap()).toList()));
|
|
}
|
|
|
|
Future<void> agregarEmitoraCustom(Emisora emisora) async {
|
|
_emisorasCustom.removeWhere((e) => e.uuid == emisora.uuid);
|
|
_emisorasCustom.add(emisora);
|
|
await _guardarEmisoresCustom();
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> eliminarEmitoraCustom(String uuid) async {
|
|
_emisorasCustom.removeWhere((e) => e.uuid == uuid);
|
|
await _guardarEmisoresCustom();
|
|
notifyListeners();
|
|
}
|
|
|
|
// ── Export / Import ───────────────────────────────────────────────────────
|
|
|
|
/// Genera el JSON de toda la configuración.
|
|
Future<Map<String, dynamic>> 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<void> importarConfig(Map<String, dynamic> 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<String, dynamic>.from(raw as Map));
|
|
await favoritos.agregar(emisora);
|
|
}
|
|
|
|
// Importar emisoras custom
|
|
final customRaw = data['emisorasCustom'] as List? ?? [];
|
|
_emisorasCustom = customRaw
|
|
.map((e) => Emisora.fromMap(Map<String, dynamic>.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<String, dynamic>.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();
|
|
}
|
|
}
|