feat(player): add radio recording and real waveform
Build & Deploy Pluriwave / Análisis de código (push) Successful in 12s
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 1m27s

This commit is contained in:
2026-05-21 21:17:51 +02:00
parent 6aa9a59d7b
commit a6a91af402
12 changed files with 1518 additions and 286 deletions
+95 -1
View File
@@ -25,7 +25,8 @@ class PantallaAjustes extends StatelessWidget {
children: const [
PluriScreenHeader(
title: 'Ajustes',
subtitle: 'Control fino de sonido, copias de seguridad y emisoras personalizadas.',
subtitle:
'Control fino de sonido, copias de seguridad y emisoras personalizadas.',
glyph: PluriIconGlyph.settings,
trailing: PluriStatusPill(
icon: Icons.security_rounded,
@@ -50,6 +51,8 @@ class _AjustesContent extends StatelessWidget {
children: const [
_SeccionEcualizador(),
SizedBox(height: 12),
_SeccionGrabaciones(),
SizedBox(height: 12),
_SeccionEmisoras(),
SizedBox(height: 12),
_SeccionBackup(),
@@ -60,6 +63,97 @@ class _AjustesContent extends StatelessWidget {
}
}
class _SeccionGrabaciones extends StatelessWidget {
const _SeccionGrabaciones();
Future<void> _seleccionarRuta(BuildContext context) async {
final estado = context.read<EstadoRadio>();
final messenger = ScaffoldMessenger.of(context);
final ruta = await FilePicker.platform.getDirectoryPath(
dialogTitle: 'Selecciona la carpeta de grabaciones',
);
if (ruta == null) return;
try {
await estado.cambiarDirectorioGrabacion(ruta);
messenger.showSnackBar(
const SnackBar(content: Text('Ruta de grabación actualizada')),
);
} catch (e) {
messenger.showSnackBar(
SnackBar(content: Text('No se pudo guardar la ruta: $e')),
);
}
}
Future<void> _restaurarRuta(BuildContext context) async {
final estado = context.read<EstadoRadio>();
final messenger = ScaffoldMessenger.of(context);
await estado.restaurarDirectorioGrabacion();
messenger.showSnackBar(
const SnackBar(content: Text('Se usará la carpeta interna por defecto')),
);
}
@override
Widget build(BuildContext context) {
final estado = context.watch<EstadoRadio>();
return PluriGlassSurface(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.radio_button_checked),
const SizedBox(width: 12),
Text(
'Grabaciones',
style: Theme.of(context).textTheme.titleMedium,
),
],
),
FutureBuilder<String>(
future: estado.directorioGrabacionEfectivo(),
builder:
(ctx, snap) => ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.folder_outlined),
title: const Text('Carpeta de grabación'),
subtitle: Text(
snap.data ?? 'Calculando ruta...',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
onTap: () => _seleccionarRuta(context),
),
),
Row(
children: [
Expanded(
child: OutlinedButton.icon(
icon: const Icon(Icons.folder_open_rounded),
label: const Text('Cambiar ruta'),
onPressed: () => _seleccionarRuta(context),
),
),
const SizedBox(width: 8),
IconButton.filledTonal(
tooltip: 'Usar ruta por defecto',
icon: const Icon(Icons.restore_rounded),
onPressed: () => _restaurarRuta(context),
),
],
),
const SizedBox(height: 8),
Text(
'La radio se guarda desde el stream original, sin recomprimir.',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
);
}
}
class _SeccionEcualizador extends StatelessWidget {
const _SeccionEcualizador();