218 lines
7.7 KiB
Dart
218 lines
7.7 KiB
Dart
import 'package:file_picker/file_picker.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../../estado/estado_grabacion.dart';
|
|
import '../../l10n/gen/app_localizations.dart';
|
|
import '../../widgets/pluri_glass_surface.dart';
|
|
import '../../widgets/pluri_layout.dart';
|
|
import '../../widgets/pluri_push_scaffold.dart';
|
|
|
|
/// GRABACIONES Y MÚSICA group · "Grabaciones" (design ADR-3). Body moved
|
|
/// verbatim from the former `_SeccionGrabaciones` in `pantalla_ajustes.dart`
|
|
/// — only the panel header's icon and title were removed (the pushed
|
|
/// screen's title now carries them); every method below is unchanged.
|
|
///
|
|
/// Known pre-existing bug, deliberately NOT fixed here (out of scope, moved
|
|
/// verbatim, tracked separately): [_editarTamanoMaximo] disposes its
|
|
/// [TextEditingController] immediately after `showModalBottomSheet` resolves,
|
|
/// racing the sheet's own close animation — the same shape of bug already
|
|
/// documented for `_editarGrupo` in `pantalla_ajustes_grupos_favoritos.dart`.
|
|
class PantallaAjustesGrabaciones extends StatelessWidget {
|
|
const PantallaAjustesGrabaciones({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context);
|
|
return PluriPushScaffold(
|
|
title: l10n.recordingsSectionTitle,
|
|
body: ListView(
|
|
padding: PluriLayout.pageContentPadding,
|
|
children: const [_CuerpoGrabaciones()],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CuerpoGrabaciones extends StatelessWidget {
|
|
const _CuerpoGrabaciones();
|
|
|
|
Future<void> _seleccionarRuta(BuildContext context) async {
|
|
final estado = context.read<EstadoGrabacion>();
|
|
final messenger = ScaffoldMessenger.of(context);
|
|
final l10n = AppLocalizations.of(context);
|
|
final ruta = await FilePicker.platform.getDirectoryPath(
|
|
dialogTitle: l10n.recordingsFolderDialogTitle,
|
|
);
|
|
if (ruta == null) return;
|
|
try {
|
|
await estado.cambiarDirectorio(ruta);
|
|
if (!context.mounted) return;
|
|
messenger.showSnackBar(
|
|
SnackBar(content: Text(l10n.recordingsPathUpdated)),
|
|
);
|
|
} catch (e) {
|
|
if (!context.mounted) return;
|
|
messenger.showSnackBar(
|
|
SnackBar(content: Text(l10n.recordingsPathSaveError(e.toString()))),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _restaurarRuta(BuildContext context) async {
|
|
final estado = context.read<EstadoGrabacion>();
|
|
final messenger = ScaffoldMessenger.of(context);
|
|
final l10n = AppLocalizations.of(context);
|
|
await estado.restaurarDirectorio();
|
|
if (!context.mounted) return;
|
|
messenger.showSnackBar(
|
|
SnackBar(content: Text(l10n.recordingsDefaultFolderRestored)),
|
|
);
|
|
}
|
|
|
|
Future<void> _abrirCarpeta(BuildContext context) async {
|
|
final estado = context.read<EstadoGrabacion>();
|
|
final messenger = ScaffoldMessenger.of(context);
|
|
final l10n = AppLocalizations.of(context);
|
|
try {
|
|
final abierto = await estado.abrirDirectorio();
|
|
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<EstadoGrabacion>();
|
|
final l10n = AppLocalizations.of(context);
|
|
final actualMb = _bytesAMegabytes(estado.maxBytes);
|
|
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.cambiarMaxBytes(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) {
|
|
// Recording state lives in EstadoGrabacion (S4-R2): this section only
|
|
// rebuilds on recording changes, never on playback notifications.
|
|
final estado = context.watch<EstadoGrabacion>();
|
|
final l10n = AppLocalizations.of(context);
|
|
|
|
return PluriGlassSurface(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
FutureBuilder<String>(
|
|
future: estado.directorioEfectivo(),
|
|
builder:
|
|
(ctx, snap) => ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
leading: const Icon(Icons.folder_outlined),
|
|
title: Text(l10n.recordingsFolderTitle),
|
|
subtitle: Text(
|
|
snap.data ?? l10n.recordingsPathCalculating,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
),
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children: [
|
|
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),
|
|
),
|
|
IconButton.filledTonal(
|
|
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.maxBytes)),
|
|
),
|
|
onTap: () => _editarTamanoMaximo(context),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
l10n.recordingsOriginalStreamHint,
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|