feat(recording): add safety limits and adaptive headers
This commit is contained in:
@@ -87,10 +87,12 @@ class _SeccionGrabaciones extends StatelessWidget {
|
||||
if (ruta == null) return;
|
||||
try {
|
||||
await estado.cambiarDirectorioGrabacion(ruta);
|
||||
if (!context.mounted) return;
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(content: Text('Ruta de grabación actualizada')),
|
||||
SnackBar(content: Text(l10n.recordingsPathUpdated)),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!context.mounted) return;
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text(l10n.recordingsPathSaveError(e.toString()))),
|
||||
);
|
||||
@@ -100,15 +102,97 @@ class _SeccionGrabaciones extends StatelessWidget {
|
||||
Future<void> _restaurarRuta(BuildContext context) async {
|
||||
final estado = context.read<EstadoRadio>();
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final l10n = AppLocalizations.of(context);
|
||||
await estado.restaurarDirectorioGrabacion();
|
||||
if (!context.mounted) return;
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(content: Text('Se usará la carpeta interna por defecto')),
|
||||
SnackBar(content: Text(l10n.recordingsDefaultFolderRestored)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _abrirCarpeta(BuildContext context) async {
|
||||
final estado = context.read<EstadoRadio>();
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final l10n = AppLocalizations.of(context);
|
||||
try {
|
||||
final abierto = await estado.abrirDirectorioGrabacion();
|
||||
if (!context.mounted) return;
|
||||
if (!abierto) {
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text(l10n.recordingsOpenFolderError(l10n.dash))),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!context.mounted) return;
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text(l10n.recordingsOpenFolderError(e.toString()))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _editarTamanoMaximo(BuildContext context) async {
|
||||
final estado = context.read<EstadoRadio>();
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final actualMb = _bytesAMegabytes(estado.maxBytesGrabacion);
|
||||
final controller = TextEditingController(text: actualMb.toString());
|
||||
|
||||
final nuevoMb = await showModalBottomSheet<int>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
showDragHandle: true,
|
||||
builder: (ctx) {
|
||||
final bottom = MediaQuery.viewInsetsOf(ctx).bottom;
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(20, 0, 20, bottom + 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.recordingsMaxSizeDialogTitle,
|
||||
style: Theme.of(ctx).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.recordingsMaxSizeMbLabel,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton.icon(
|
||||
onPressed: () {
|
||||
final value = int.tryParse(controller.text.trim());
|
||||
if (value == null || value <= 0) return;
|
||||
Navigator.of(ctx).pop(value);
|
||||
},
|
||||
icon: const Icon(Icons.save_rounded),
|
||||
label: Text(l10n.saveQuickAccessButton),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
controller.dispose();
|
||||
if (nuevoMb == null || !context.mounted) return;
|
||||
await estado.cambiarMaxBytesGrabacion(nuevoMb * 1024 * 1024);
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.recordingsMaxSizeSaved(nuevoMb))),
|
||||
);
|
||||
}
|
||||
|
||||
int _bytesAMegabytes(int bytes) =>
|
||||
(bytes / (1024 * 1024)).round().clamp(1, 1048576);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final estado = context.watch<EstadoRadio>();
|
||||
final l10n = AppLocalizations.of(context);
|
||||
|
||||
return PluriGlassSurface(
|
||||
child: Column(
|
||||
@@ -119,7 +203,7 @@ class _SeccionGrabaciones extends StatelessWidget {
|
||||
const Icon(Icons.radio_button_checked),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
AppLocalizations.of(context).recordingsSectionTitle,
|
||||
l10n.recordingsSectionTitle,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
@@ -130,35 +214,51 @@ class _SeccionGrabaciones extends StatelessWidget {
|
||||
(ctx, snap) => ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: const Icon(Icons.folder_outlined),
|
||||
title: const Text('Carpeta de grabación'),
|
||||
title: Text(l10n.recordingsFolderTitle),
|
||||
subtitle: Text(
|
||||
snap.data ?? AppLocalizations.of(context).recordingsPathCalculating,
|
||||
snap.data ?? l10n.recordingsPathCalculating,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onTap: () => _seleccionarRuta(context),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
icon: const Icon(Icons.folder_open_rounded),
|
||||
label: Text(AppLocalizations.of(context).recordingsChangePath),
|
||||
onPressed: () => _seleccionarRuta(context),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
icon: const Icon(Icons.folder_open_rounded),
|
||||
label: Text(l10n.recordingsChangePath),
|
||||
onPressed: () => _seleccionarRuta(context),
|
||||
),
|
||||
FilledButton.tonalIcon(
|
||||
icon: const Icon(Icons.folder_copy_rounded),
|
||||
label: Text(l10n.recordingsOpenFolder),
|
||||
onPressed: () => _abrirCarpeta(context),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton.filledTonal(
|
||||
tooltip: AppLocalizations.of(context).recordingsUseDefaultPath,
|
||||
tooltip: l10n.recordingsUseDefaultPath,
|
||||
icon: const Icon(Icons.restore_rounded),
|
||||
onPressed: () => _restaurarRuta(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: const Icon(Icons.sd_storage_rounded),
|
||||
title: Text(l10n.recordingsMaxSizeTitle),
|
||||
subtitle: Text(
|
||||
l10n.recordingsMaxSizeSubtitle(
|
||||
_bytesAMegabytes(estado.maxBytesGrabacion),
|
||||
),
|
||||
),
|
||||
onTap: () => _editarTamanoMaximo(context),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context).recordingsOriginalStreamHint,
|
||||
l10n.recordingsOriginalStreamHint,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user