From 5fd3d6deb9fe0cb405b55d2b331c1a81a1e17bec Mon Sep 17 00:00:00 2001 From: ShanaiaBot Date: Sat, 4 Apr 2026 19:17:40 +0200 Subject: [PATCH] feat(v0.3.0): ecualizador + favoritos en tarjeta + emisoras custom + export/import + fix MainActivity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../es/freetimelab/pluriwave/MainActivity.kt | 4 +- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3236 -> 656 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 3236 -> 656 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2001 -> 452 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2001 -> 452 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4670 -> 838 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 4670 -> 838 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 8668 -> 1257 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 8668 -> 1257 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 13528 -> 1752 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 13528 -> 1752 bytes lib/app.dart | 2 +- lib/estado/estado_radio.dart | 140 +++- lib/main.dart | 2 - lib/modelos/preset_ecualizador.dart | 29 + lib/pantallas/pantalla_ajustes.dart | 405 ++++++++++-- lib/servicios/servicio_audio.dart | 118 ++-- lib/widgets/ecualizador_widget.dart | 120 ++++ lib/widgets/tarjeta_emisora.dart | 143 +++- pubspec.lock | 622 +++++++++++++++++- pubspec.yaml | 2 + 21 files changed, 1449 insertions(+), 138 deletions(-) create mode 100644 lib/modelos/preset_ecualizador.dart create mode 100644 lib/widgets/ecualizador_widget.dart diff --git a/android/app/src/main/kotlin/es/freetimelab/pluriwave/MainActivity.kt b/android/app/src/main/kotlin/es/freetimelab/pluriwave/MainActivity.kt index 6fbac41..3c70ffc 100644 --- a/android/app/src/main/kotlin/es/freetimelab/pluriwave/MainActivity.kt +++ b/android/app/src/main/kotlin/es/freetimelab/pluriwave/MainActivity.kt @@ -1,5 +1,5 @@ package es.freetimelab.pluriwave -import io.flutter.embedding.android.FlutterActivity +import com.ryanheise.audioservice.AudioServiceActivity -class MainActivity : FlutterActivity() +class MainActivity : AudioServiceActivity() diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 1c007ec6971b9b4951173f30332aec7cbe678843..76091e49d6c35f435fd467f54bbaae82690b8a55 100644 GIT binary patch literal 656 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 Z%|*v6Ck0F71e4$;^pKwR?jh>%zX11d0L1_R diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 1c007ec6971b9b4951173f30332aec7cbe678843..76091e49d6c35f435fd467f54bbaae82690b8a55 100644 GIT binary patch literal 656 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 Z%|*v6Ck0F71e4$;^pKwR?jh>%zX11d0L1_R diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 3da2c836e8db422c5d33104c8f956ee92fbe8dff..6a0086a84ac06a5a5bf6604568402e35e4b70fe9 100644 GIT binary patch literal 452 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 literal 4670 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 literal 4670 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 I|5)Mw0ai-qr~m)} literal 13528 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 I|5)Mw0ai-qr~m)} literal 13528 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.broadcast(); Stream get errorStream => _errorController.stream; @@ -24,10 +26,15 @@ class EstadoRadio extends ChangeNotifier { 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; // solo para errores de carga de lista (banner estático) + String? _errorCarga; EstadoRadio() { timer = ServicioTimer(audio); @@ -38,16 +45,20 @@ class EstadoRadio extends ChangeNotifier { 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(), ]); } @@ -92,7 +103,6 @@ class EstadoRadio extends ChangeNotifier { 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; @@ -104,9 +114,11 @@ class EstadoRadio extends ChangeNotifier { 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) { - // Error de reproducción → SnackBar, no pintar en medio de la UI _errorController.add('No se puede reproducir "${emisora.nombre}"'); } } @@ -119,11 +131,125 @@ class EstadoRadio extends ChangeNotifier { 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(); diff --git a/lib/main.dart b/lib/main.dart index fd1edca..090a9fb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,8 +6,6 @@ import 'servicios/servicio_audio.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - // Inicializar audio_service para reproducción en background. - // El handler se registra globalmente para que ServicioAudio lo use. final handler = await AudioService.init( builder: () => PluriWaveAudioHandler(), config: const AudioServiceConfig( diff --git a/lib/modelos/preset_ecualizador.dart b/lib/modelos/preset_ecualizador.dart new file mode 100644 index 0000000..147b0d7 --- /dev/null +++ b/lib/modelos/preset_ecualizador.dart @@ -0,0 +1,29 @@ +/// Modelo de preset de ecualizador. +/// 5 bandas: 60Hz, 250Hz, 1kHz, 4kHz, 16kHz +class PresetEcualizador { + final String nombre; + final List bandas; // 5 valores entre -12.0 y +12.0 dB + + const PresetEcualizador({required this.nombre, required this.bandas}) + : assert(bandas.length == 5); + + static final flat = PresetEcualizador(nombre: 'Flat', bandas: [0.0, 0.0, 0.0, 0.0, 0.0]); + static final rock = PresetEcualizador(nombre: 'Rock', bandas: [2.0, 1.0, -1.0, 2.0, 3.0]); + static final pop = PresetEcualizador(nombre: 'Pop', bandas: [1.0, 1.5, 0.5, 1.0, 1.5]); + static final bassBoost = PresetEcualizador(nombre: 'Bass Boost', bandas: [5.0, 3.0, -1.0, 0.5, 0.0]); + static final jazz = PresetEcualizador(nombre: 'Jazz', bandas: [3.0, -1.0, -1.5, 2.0, 4.0]); + static final voz = PresetEcualizador(nombre: 'Voz', bandas: [-2.0, -1.0, 2.0, 3.0, 1.0]); + + static final presets = [flat, rock, pop, bassBoost, jazz, voz]; + + factory PresetEcualizador.desdeJson(Map json) { + final raw = (json['bandas'] as List?)?.map((e) => (e as num).toDouble()).toList() ?? []; + final bandas = List.generate(5, (i) => i < raw.length ? raw[i] : 0.0); + return PresetEcualizador(nombre: json['nombre'] as String? ?? 'Personalizado', bandas: bandas); + } + + Map toJson() => {'nombre': nombre, 'bandas': bandas}; + + PresetEcualizador copyWithBandas(List bandas) => + PresetEcualizador(nombre: 'Personalizado', bandas: bandas); +} diff --git a/lib/pantallas/pantalla_ajustes.dart b/lib/pantallas/pantalla_ajustes.dart index 68d1ba8..44b9813 100644 --- a/lib/pantallas/pantalla_ajustes.dart +++ b/lib/pantallas/pantalla_ajustes.dart @@ -1,42 +1,387 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:share_plus/share_plus.dart' show Share, XFile; +import 'package:path_provider/path_provider.dart'; +import 'package:uuid/uuid.dart'; import '../estado/estado_radio.dart'; +import '../modelos/emisora.dart'; +import '../modelos/preset_ecualizador.dart'; +import '../widgets/ecualizador_widget.dart'; -/// Pantalla de ajustes — por ahora muestra info de la app. -/// En Fase 3 se añadirá Export/Import config y gestión PRO. class PantallaAjustes extends StatelessWidget { const PantallaAjustes({super.key}); @override Widget build(BuildContext context) { - final theme = Theme.of(context); - final estado = context.read(); - return Scaffold( appBar: AppBar(title: const Text('Ajustes')), body: ListView( + children: const [ + _SeccionEcualizador(), + Divider(), + _SeccionEmisoras(), + Divider(), + _SeccionBackup(), + Divider(), + _SeccionInfo(), + ], + ), + ); + } +} + +// ── Sección Ecualizador ─────────────────────────────────────────────────────── + +class _SeccionEcualizador extends StatelessWidget { + const _SeccionEcualizador(); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (ctx, estado, _) { + final disponible = estado.ecualizadorDisponible; + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + const Icon(Icons.equalizer), + const SizedBox(width: 12), + Text('Ecualizador', style: Theme.of(ctx).textTheme.titleMedium), + const Spacer(), + if (!disponible) + Chip( + label: const Text('Reproduce una emisora para activar'), + visualDensity: VisualDensity.compact, + ), + ], + ), + if (disponible) ...[ + const SizedBox(height: 8), + PresetsEcualizadorWidget( + presetActual: estado.presetEcualizador, + onSeleccionar: (p) => estado.cambiarPresetEcualizador(p), + ), + const SizedBox(height: 12), + EcualizadorWidget( + preset: estado.presetEcualizador, + onCambio: (p) { + for (int i = 0; i < p.bandas.length; i++) { + estado.cambiarBandaEcualizador(i, p.bandas[i]); + } + }, + ), + ], + ], + ), + ); + }, + ); + } +} + +// ── Sección Emisoras personalizadas ────────────────────────────────────────── + +class _SeccionEmisoras extends StatelessWidget { + const _SeccionEmisoras(); + + @override + Widget build(BuildContext context) { + final estado = context.watch(); + final custom = estado.emisorasCustom; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), + child: Row( + children: [ + const Icon(Icons.add_circle_outline), + const SizedBox(width: 12), + Text('Emisoras personalizadas', + style: Theme.of(context).textTheme.titleMedium), + const Spacer(), + TextButton.icon( + icon: const Icon(Icons.add), + label: const Text('Añadir'), + onPressed: () => _mostrarFormularioAnadir(context), + ), + ], + ), + ), + if (custom.isEmpty) + const Padding( + padding: EdgeInsets.fromLTRB(16, 4, 16, 12), + child: Text('No hay emisoras personalizadas.', + style: TextStyle(color: Colors.grey)), + ) + else + for (final emisora in custom) + ListTile( + leading: const Icon(Icons.radio), + title: Text(emisora.nombre), + subtitle: Text(emisora.url, maxLines: 1, overflow: TextOverflow.ellipsis), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.play_arrow), + tooltip: 'Reproducir', + onPressed: () => context.read().reproducir(emisora), + ), + IconButton( + icon: const Icon(Icons.delete_outline), + tooltip: 'Eliminar', + onPressed: () => context.read().eliminarEmitoraCustom(emisora.uuid), + ), + ], + ), + ), + ], + ); + } + + Future _mostrarFormularioAnadir(BuildContext context) async { + await showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (ctx) => const _FormularioEmisora(), + ); + } +} + +class _FormularioEmisora extends StatefulWidget { + const _FormularioEmisora(); + + @override + State<_FormularioEmisora> createState() => _FormularioEmisoraState(); +} + +class _FormularioEmisoraState extends State<_FormularioEmisora> { + final _formKey = GlobalKey(); + final _nombreCtrl = TextEditingController(); + final _urlCtrl = TextEditingController(); + final _paisCtrl = TextEditingController(); + bool _guardando = false; + + @override + void dispose() { + _nombreCtrl.dispose(); + _urlCtrl.dispose(); + _paisCtrl.dispose(); + super.dispose(); + } + + Future _guardar() async { + if (!_formKey.currentState!.validate()) return; + setState(() => _guardando = true); + + final emisora = Emisora( + uuid: const Uuid().v4(), + nombre: _nombreCtrl.text.trim(), + url: _urlCtrl.text.trim(), + pais: _paisCtrl.text.trim().isEmpty ? null : _paisCtrl.text.trim(), + ); + + await context.read().agregarEmitoraCustom(emisora); + if (mounted) Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + final bottom = MediaQuery.of(context).viewInsets.bottom; + return Padding( + padding: EdgeInsets.fromLTRB(16, 16, 16, 16 + bottom), + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('Añadir emisora', style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 16), + TextFormField( + controller: _nombreCtrl, + decoration: const InputDecoration(labelText: 'Nombre *', border: OutlineInputBorder()), + validator: (v) => v == null || v.trim().isEmpty ? 'Campo obligatorio' : null, + ), + const SizedBox(height: 12), + TextFormField( + controller: _urlCtrl, + decoration: const InputDecoration( + labelText: 'URL del stream *', + hintText: 'http://stream.ejemplo.com:8000/radio', + border: OutlineInputBorder(), + ), + keyboardType: TextInputType.url, + validator: (v) { + if (v == null || v.trim().isEmpty) return 'Campo obligatorio'; + final uri = Uri.tryParse(v.trim()); + if (uri == null || !uri.hasScheme) return 'URL no válida'; + return null; + }, + ), + const SizedBox(height: 12), + TextFormField( + controller: _paisCtrl, + decoration: const InputDecoration( + labelText: 'País (opcional)', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + FilledButton( + onPressed: _guardando ? null : _guardar, + child: _guardando + ? const SizedBox(height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2)) + : const Text('Guardar emisora'), + ), + ], + ), + ), + ); + } +} + +// ── Sección Backup ──────────────────────────────────────────────────────────── + +class _SeccionBackup extends StatelessWidget { + const _SeccionBackup(); + + Future _exportar(BuildContext context) async { + try { + final estado = context.read(); + final config = await estado.exportarConfig(); + final json = const JsonEncoder.withIndent(' ').convert(config); + + final dir = await getTemporaryDirectory(); + final file = File('${dir.path}/pluriwave-backup.json'); + await file.writeAsString(json); + + await Share.shareXFiles( + [XFile(file.path)], + subject: 'PluriWave — copia de seguridad', + text: 'Configuración de PluriWave exportada el ${DateTime.now().toLocal()}', + ); + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error al exportar: $e')), + ); + } + } + } + + Future _importar(BuildContext context) async { + try { + final result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['json'], + ); + if (result == null || result.files.single.path == null) return; + + final file = File(result.files.single.path!); + final json = jsonDecode(await file.readAsString()) as Map; + + if (context.mounted) { + final confirmar = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text('Importar configuración'), + content: const Text( + 'Esto añadirá los favoritos, emisoras y presets del fichero. ' + '¿Continuar?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('Cancelar')), + FilledButton( + onPressed: () => Navigator.pop(ctx, true), + child: const Text('Importar')), + ], + ), + ); + if (confirmar != true) return; + if (context.mounted) { + await context.read().importarConfig(json); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Configuración importada correctamente')), + ); + } + } + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error al importar: $e')), + ); + } + } + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), + child: Row( + children: [ + const Icon(Icons.backup_outlined), + const SizedBox(width: 12), + Text('Copia de seguridad', + style: Theme.of(context).textTheme.titleMedium), + ], + ), + ), + ListTile( + leading: const Icon(Icons.upload_outlined), + title: const Text('Exportar configuración'), + subtitle: const Text('Favoritos, emisoras custom y presets de EQ'), + onTap: () => _exportar(context), + ), + ListTile( + leading: const Icon(Icons.download_outlined), + title: const Text('Importar configuración'), + subtitle: const Text('Restaurar desde un fichero de copia de seguridad'), + onTap: () => _importar(context), + ), + ], + ); + } +} + +// ── Sección Info ────────────────────────────────────────────────────────────── + +class _SeccionInfo extends StatelessWidget { + const _SeccionInfo(); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (ctx, estado, _) => Column( children: [ - // Info app ListTile( leading: const Icon(Icons.info_outline), title: const Text('PluriWave'), subtitle: const Text('v0.3.0 — Radio mundial'), ), - const Divider(), - // Favoritos FutureBuilder( future: estado.favoritos.obtenerTodos().then((l) => l.length), builder: (ctx, snap) => ListTile( leading: const Icon(Icons.favorite_outline), title: const Text('Favoritos guardados'), - trailing: Text( - snap.data?.toString() ?? '—', - style: theme.textTheme.bodyLarge, - ), + trailing: Text(snap.data?.toString() ?? '—', + style: Theme.of(ctx).textTheme.bodyLarge), ), ), - const Divider(), - // Filtro emisoras ListTile( leading: const Icon(Icons.verified_outlined), title: const Text('Filtro de emisoras'), @@ -44,39 +389,11 @@ class PantallaAjustes extends StatelessWidget { trailing: const Icon(Icons.check_circle, color: Colors.green), ), ListTile( - leading: const Icon(Icons.music_off_outlined), + leading: const Icon(Icons.music_note_outlined), title: const Text('Audio en background'), - subtitle: const Text('Activo — continúa al apagar pantalla'), + subtitle: const Text('Continúa al apagar la pantalla'), trailing: const Icon(Icons.check_circle, color: Colors.green), ), - const Divider(), - // Próximamente - const Padding( - padding: EdgeInsets.fromLTRB(16, 12, 16, 4), - child: Text('PRÓXIMAMENTE', style: TextStyle(fontSize: 12, letterSpacing: 1.2)), - ), - ListTile( - leading: const Icon(Icons.upload_outlined), - title: const Text('Exportar configuración'), - subtitle: const Text('Favoritos, radios custom, presets EQ'), - enabled: false, - ), - ListTile( - leading: const Icon(Icons.download_outlined), - title: const Text('Importar configuración'), - enabled: false, - ), - ListTile( - leading: const Icon(Icons.add_circle_outline), - title: const Text('Añadir radio personalizada'), - enabled: false, - ), - ListTile( - leading: const Icon(Icons.equalizer_outlined), - title: const Text('Ecualizador'), - subtitle: const Text('5 bandas, presets por emisora'), - enabled: false, - ), ], ), ); diff --git a/lib/servicios/servicio_audio.dart b/lib/servicios/servicio_audio.dart index d1b8cd6..d0ad12e 100644 --- a/lib/servicios/servicio_audio.dart +++ b/lib/servicios/servicio_audio.dart @@ -1,6 +1,7 @@ import 'package:audio_service/audio_service.dart'; import 'package:just_audio/just_audio.dart'; import '../modelos/emisora.dart'; +import '../modelos/preset_ecualizador.dart'; /// Estado de reproducción expuesto al UI. enum EstadoReproduccion { detenido, cargando, reproduciendo, pausado, error } @@ -10,20 +11,14 @@ enum EstadoReproduccion { detenido, cargando, reproduciendo, pausado, error } // ───────────────────────────────────────────────────────────── PluriWaveAudioHandler? _handlerGlobal; -/// Registra el handler. Llamar desde main.dart tras AudioService.init. void registrarHandler(PluriWaveAudioHandler handler) { _handlerGlobal = handler; } /// Wrapper de alto nivel para el UI. -/// -/// Delega TODA la reproducción al [PluriWaveAudioHandler] para garantizar -/// que el audio siga vivo en background con notificación foreground. class ServicioAudio { PluriWaveAudioHandler get _handler { - assert(_handlerGlobal != null, - 'ServicioAudio: handler no registrado. ' - 'Llama registrarHandler() en main.dart tras AudioService.init.'); + assert(_handlerGlobal != null, 'registrarHandler() no fue llamado en main.dart'); return _handlerGlobal!; } @@ -68,55 +63,52 @@ class ServicioAudio { } Future detener() => _handler.stop(); - - Future setVolumen(double vol) => _handler.setVolume(vol.clamp(0.0, 1.0)); - + Future setVolumen(double vol) => _handler.setVolumen(vol); double get volumen => _handler.volumen; bool get estaSonando => _handler.playbackState.value.playing; - - /// No-op: el handler se limpia en main.dart al cerrar la app. Future dispose() async {} + + // ── Ecualizador ────────────────────────────────────────────────────────── + AndroidEqualizer? get ecualizador => _handler.ecualizador; + bool get ecualizadorDisponible => _handler.ecualizadorDisponible; + PresetEcualizador get presetActual => _handler.presetActual; + + Future aplicarPreset(PresetEcualizador preset) => + _handler.aplicarPreset(preset); + + Future setBanda(int index, double db) => + _handler.setBanda(index, db); } // ───────────────────────────────────────────────────────────── -// AudioHandler — núcleo del audio en background +// AudioHandler // ───────────────────────────────────────────────────────────── - -/// Handler de audio_service. -/// -/// Gestiona la reproducción con `just_audio` y mantiene la notificación -/// foreground activa mientras hay audio reproduciéndose. -/// -/// ### Inicialización en main.dart -/// ```dart -/// final handler = await AudioService.init( -/// builder: () => PluriWaveAudioHandler(), -/// config: const AudioServiceConfig( -/// androidNotificationChannelId: 'es.freetimelab.pluriwave.audio', -/// androidNotificationChannelName: 'PluriWave Radio', -/// androidNotificationOngoing: true, -/// androidStopForegroundOnPause: true, -/// androidNotificationIcon: 'drawable/ic_stat_radio', -/// ), -/// ); -/// registrarHandler(handler); -/// ``` class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler { - final AudioPlayer _player = AudioPlayer(); + final AndroidEqualizer _eq = AndroidEqualizer(); + + late final AudioPlayer _player = AudioPlayer( + audioPipeline: AudioPipeline(androidAudioEffects: [_eq]), + ); + Emisora? emisoraActual; double _volumen = 1.0; double get volumen => _volumen; + AndroidEqualizer? get ecualizador => _eq; + bool _eqDisponible = false; + bool get ecualizadorDisponible => _eqDisponible; + + PresetEcualizador _presetActual = PresetEcualizador.flat; + PresetEcualizador get presetActual => _presetActual; + PluriWaveAudioHandler() { _setupStreams(); } void _setupStreams() { - // Propagar estado del player → playbackState (lo que ve la notificación) _player.playerStateStream.listen((state) { final playing = state.playing; final proc = state.processingState; - playbackState.add(playbackState.value.copyWith( controls: [ if (playing) MediaControl.pause else MediaControl.play, @@ -131,7 +123,6 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler { )); }); - // Actualizar bufferedPosition _player.bufferedPositionStream.listen((pos) { playbackState.add(playbackState.value.copyWith(bufferedPosition: pos)); }); @@ -154,6 +145,8 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler { await _player.stop(); await _player.setUrl(item.id); await _player.play(); + // Habilitar ecualizador tras reproducir (necesita audio activo) + await _activarEcualizador(); } on PlayerException catch (e) { playbackState.add(playbackState.value.copyWith( processingState: AudioProcessingState.error, @@ -164,6 +157,52 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler { } } + Future _activarEcualizador() async { + try { + final params = await _eq.parameters; + _eqDisponible = params.bands.isNotEmpty; + if (_eqDisponible) { + await _eq.setEnabled(true); + await aplicarPreset(_presetActual); + } + } catch (_) { + _eqDisponible = false; + } + } + + /// Aplica un preset al ecualizador nativo Android. + Future aplicarPreset(PresetEcualizador preset) async { + _presetActual = preset; + if (!_eqDisponible) return; + try { + final params = await _eq.parameters; + for (int i = 0; i < params.bands.length && i < preset.bandas.length; i++) { + await params.bands[i].setGain(preset.bandas[i]); + } + } catch (_) {} + } + + /// Ajusta una banda individual. + Future setBanda(int index, double db) async { + if (!_eqDisponible) return; + final bandas = List.from(_presetActual.bandas); + if (index >= 0 && index < bandas.length) { + bandas[index] = db; + _presetActual = _presetActual.copyWithBandas(bandas); + } + try { + final params = await _eq.parameters; + if (index < params.bands.length) { + await params.bands[index].setGain(db); + } + } catch (_) {} + } + + Future setVolumen(double vol) async { + _volumen = vol.clamp(0.0, 1.0); + await _player.setVolume(_volumen); + } + @override Future play() => _player.play(); @@ -181,11 +220,6 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler { @override Future seek(Duration position) => _player.seek(position); - Future setVolume(double vol) async { - _volumen = vol.clamp(0.0, 1.0); - await _player.setVolume(_volumen); - } - @override Future onTaskRemoved() async { await stop(); diff --git a/lib/widgets/ecualizador_widget.dart b/lib/widgets/ecualizador_widget.dart new file mode 100644 index 0000000..472e953 --- /dev/null +++ b/lib/widgets/ecualizador_widget.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import '../modelos/preset_ecualizador.dart'; + +/// Widget de ecualizador con 5 sliders verticales. +/// Basado en JaviHogar EcualizadorWidget, adaptado a Material You. +class EcualizadorWidget extends StatefulWidget { + final PresetEcualizador preset; + final void Function(PresetEcualizador) onCambio; + + const EcualizadorWidget({super.key, required this.preset, required this.onCambio}); + + @override + State createState() => _EcualizadorWidgetState(); +} + +class _EcualizadorWidgetState extends State { + late List _bandas; + final List _etiquetas = ['60Hz', '250Hz', '1kHz', '4kHz', '16kHz']; + + @override + void initState() { + super.initState(); + _bandas = List.from(widget.preset.bandas); + } + + @override + void didUpdateWidget(EcualizadorWidget old) { + super.didUpdateWidget(old); + if (old.preset.nombre != widget.preset.nombre) { + setState(() => _bandas = List.from(widget.preset.bandas)); + } + } + + void _actualizarBanda(int index, double valor) { + setState(() => _bandas[index] = valor); + widget.onCambio(PresetEcualizador(nombre: 'Personalizado', bandas: List.from(_bandas))); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('Ecualizador', style: theme.textTheme.titleMedium), + const SizedBox(height: 16), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + for (int i = 0; i < 5; i++) + Expanded( + child: Column( + children: [ + SizedBox( + height: 160, + child: RotatedBox( + quarterTurns: 3, + child: Slider( + value: _bandas[i], + min: -12.0, + max: 12.0, + divisions: 24, + onChanged: (v) => _actualizarBanda(i, v), + ), + ), + ), + Text( + '${_bandas[i].toStringAsFixed(1)}dB', + style: theme.textTheme.labelSmall, + textAlign: TextAlign.center, + ), + Text( + _etiquetas[i], + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ], + ), + ], + ), + ), + ); + } +} + +/// Chips de presets predefinidos. +class PresetsEcualizadorWidget extends StatelessWidget { + final PresetEcualizador presetActual; + final void Function(PresetEcualizador) onSeleccionar; + + const PresetsEcualizadorWidget({ + super.key, + required this.presetActual, + required this.onSeleccionar, + }); + + @override + Widget build(BuildContext context) { + return Wrap( + spacing: 8, + runSpacing: 4, + children: PresetEcualizador.presets.map((p) { + return ChoiceChip( + label: Text(p.nombre), + selected: p.nombre == presetActual.nombre, + onSelected: (_) => onSeleccionar(p), + ); + }).toList(), + ); + } +} diff --git a/lib/widgets/tarjeta_emisora.dart b/lib/widgets/tarjeta_emisora.dart index 008d4d2..b2faf29 100644 --- a/lib/widgets/tarjeta_emisora.dart +++ b/lib/widgets/tarjeta_emisora.dart @@ -1,10 +1,13 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:shimmer/shimmer.dart'; +import '../estado/estado_radio.dart'; import '../modelos/emisora.dart'; /// Tarjeta compacta para mostrar una emisora en listas y grids. -class TarjetaEmisora extends StatelessWidget { +/// Incluye botón de favorito visible en ambos modos. +class TarjetaEmisora extends StatefulWidget { final Emisora emisora; final VoidCallback? onTap; final bool esCompacta; @@ -16,48 +19,96 @@ class TarjetaEmisora extends StatelessWidget { this.esCompacta = false, }); + @override + State createState() => _TarjetaEmisoraState(); +} + +class _TarjetaEmisoraState extends State { + bool _esFavorito = false; + bool _toggling = false; + + @override + void initState() { + super.initState(); + _checkFavorito(); + } + + Future _checkFavorito() async { + final fav = await context.read().esFavorito(widget.emisora.uuid); + if (mounted) setState(() => _esFavorito = fav); + } + + Future _toggle() async { + if (_toggling) return; + _toggling = true; + final estado = context.read(); + final esFav = await estado.toggleFavorito(widget.emisora); + if (mounted) setState(() => _esFavorito = esFav); + _toggling = false; + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(esFav + ? '${widget.emisora.nombre} añadida a favoritos' + : '${widget.emisora.nombre} eliminada de favoritos'), + duration: const Duration(seconds: 2), + )); + } + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); return Card( clipBehavior: Clip.antiAlias, child: InkWell( - onTap: onTap, - child: esCompacta ? _buildCompacta(theme) : _buildCompleta(theme), + onTap: widget.onTap, + child: widget.esCompacta + ? _buildCompacta(theme) + : _buildCompleta(theme), ), ); } Widget _buildCompleta(ThemeData theme) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + return Stack( children: [ - AspectRatio( - aspectRatio: 1, - child: _logo(theme, 60), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - emisora.nombre, - style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - if (emisora.pais != null) - Text( - emisora.pais!, - style: theme.textTheme.bodySmall?.copyWith( - color: theme.colorScheme.onSurfaceVariant, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: 1, + child: _logo(theme, 60), + ), + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.emisora.nombre, + style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), + if (widget.emisora.pais != null) + Text( + widget.emisora.pais!, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + // Botón favorito superpuesto (esquina superior derecha) + Positioned( + top: 4, + right: 4, + child: _botonFavorito(theme, mini: true), ), ], ); @@ -67,22 +118,46 @@ class TarjetaEmisora extends StatelessWidget { return ListTile( leading: SizedBox(width: 48, height: 48, child: _logo(theme, 24)), title: Text( - emisora.nombre, + widget.emisora.nombre, maxLines: 1, overflow: TextOverflow.ellipsis, ), subtitle: Text( - [emisora.pais, emisora.idioma].where((s) => s != null).join(' · '), + [widget.emisora.pais, widget.emisora.idioma] + .where((s) => s != null && s.isNotEmpty) + .join(' · '), maxLines: 1, overflow: TextOverflow.ellipsis, ), + trailing: _botonFavorito(theme, mini: false), + ); + } + + Widget _botonFavorito(ThemeData theme, {required bool mini}) { + return Material( + color: mini + ? theme.colorScheme.surface.withValues(alpha: 0.8) + : Colors.transparent, + shape: const CircleBorder(), + child: InkWell( + customBorder: const CircleBorder(), + onTap: _toggle, + child: Padding( + padding: EdgeInsets.all(mini ? 6 : 4), + child: Icon( + _esFavorito ? Icons.favorite_rounded : Icons.favorite_outline_rounded, + color: _esFavorito ? theme.colorScheme.error : theme.colorScheme.onSurfaceVariant, + size: mini ? 18 : 22, + ), + ), + ), ); } Widget _logo(ThemeData theme, double iconSize) { - if (emisora.favicon != null && emisora.favicon!.isNotEmpty) { + if (widget.emisora.favicon != null && widget.emisora.favicon!.isNotEmpty) { return CachedNetworkImage( - imageUrl: emisora.favicon!, + imageUrl: widget.emisora.favicon!, fit: BoxFit.cover, placeholder: (_, __) => _shimmer(theme), errorWidget: (_, __, ___) => _iconoFallback(theme, iconSize), diff --git a/pubspec.lock b/pubspec.lock index 3b0a6da..2a1b4c7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.1" + audio_service: + dependency: "direct main" + description: + name: audio_service + sha256: cb122c7c2639d2a992421ef96b67948ad88c5221da3365ccef1031393a76e044 + url: "https://pub.dev" + source: hosted + version: "0.18.18" + audio_service_platform_interface: + dependency: transitive + description: + name: audio_service_platform_interface + sha256: "6283782851f6c8b501b60904a32fc7199dc631172da0629d7301e66f672ab777" + url: "https://pub.dev" + source: hosted + version: "0.1.3" + audio_service_web: + dependency: transitive + description: + name: audio_service_web + sha256: b8ea9243201ee53383157fbccf13d5d2a866b5dda922ec19d866d1d5d70424df + url: "https://pub.dev" + source: hosted + version: "0.1.4" + audio_session: + dependency: "direct main" + description: + name: audio_session + sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" + url: "https://pub.dev" + source: hosted + version: "0.1.25" boolean_selector: dependency: transitive description: @@ -17,6 +49,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" characters: dependency: transitive description: @@ -33,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" collection: dependency: transitive description: @@ -41,6 +105,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.dev" + source: hosted + version: "0.3.5+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" cupertino_icons: dependency: "direct main" description: @@ -57,24 +137,178 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810 + url: "https://pub.dev" + source: hosted + version: "8.3.7" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_animate: + dependency: "direct main" + description: + name: flutter_animate + sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" + url: "https://pub.dev" + source: hosted + version: "4.5.2" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "5.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + url: "https://pub.dev" + source: hosted + version: "2.0.34" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" + url: "https://pub.dev" + source: hosted + version: "0.1.3" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055 + url: "https://pub.dev" + source: hosted + version: "6.3.3" + hooks: + dependency: transitive + description: + name: hooks + sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + just_audio: + dependency: "direct main" + description: + name: just_audio + sha256: f978d5b4ccea08f267dae0232ec5405c1b05d3f3cd63f82097ea46c015d5c09e + url: "https://pub.dev" + source: hosted + version: "0.9.46" + just_audio_platform_interface: + dependency: transitive + description: + name: just_audio_platform_interface + sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + just_audio_web: + dependency: transitive + description: + name: just_audio_web + sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" + url: "https://pub.dev" + source: hosted + version: "0.4.16" leak_tracker: dependency: transitive description: @@ -103,10 +337,18 @@ packages: dependency: transitive description: name: lints - sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -131,6 +373,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + url: "https://pub.dev" + source: hosted + version: "0.17.6" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" path: dependency: transitive description: @@ -139,6 +421,174 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" + url: "https://pub.dev" + source: hosted + version: "2.2.23" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da + url: "https://pub.dev" + source: hosted + version: "10.1.4" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b + url: "https://pub.dev" + source: hosted + version: "5.0.2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + url: "https://pub.dev" + source: hosted + version: "2.4.23" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter @@ -152,6 +602,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40" + url: "https://pub.dev" + source: hosted + version: "2.4.2+3" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -176,6 +666,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: @@ -192,6 +690,86 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.9" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + url: "https://pub.dev" + source: hosted + version: "6.3.29" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + url: "https://pub.dev" + source: hosted + version: "2.4.2" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" + source: hosted + version: "4.5.3" vector_math: dependency: transitive description: @@ -208,6 +786,38 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: - dart: ">=3.11.1 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml index 26feb52..9032e12 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,8 @@ dependencies: # Utils share_plus: ^10.1.3 + file_picker: ^8.1.7 + uuid: ^4.5.1 url_launcher: ^6.3.1 # Ads (activar cuando tengamos Ad Unit IDs)