import 'package:flutter/material.dart'; import '../../l10n/gen/app_localizations.dart'; import '../../servicios/musica_local_auto.dart'; import '../../servicios/servicio_audio.dart' show invalidarArbolAuto; import '../../widgets/pluri_glass_surface.dart'; import '../../widgets/pluri_layout.dart'; import '../../widgets/pluri_push_scaffold.dart'; /// GRABACIONES Y MÚSICA group · "Música local" (design ADR-3). Body moved /// verbatim from the former `_SeccionMusicaLocal` in `pantalla_ajustes.dart` /// — only the panel header's icon and title were removed (the pushed /// screen's title now carries them). Deliberately does NOT use /// `FilePicker.platform` (see tasks.md "Grounding corrections") — /// [FuenteMusicaLocalAutoImpl.elegirCarpeta] calls the native /// `pickMusicFolder` channel method directly, since it needs a /// persistable-grant SAF tree URI, not a plain filesystem path. class PantallaAjustesMusicaLocal extends StatelessWidget { const PantallaAjustesMusicaLocal({super.key}); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); return PluriPushScaffold( title: l10n.localMusicSectionTitle, body: ListView( padding: PluriLayout.pageContentPadding, children: const [_CuerpoMusicaLocal()], ), ); } } class _CuerpoMusicaLocal extends StatefulWidget { const _CuerpoMusicaLocal(); @override State<_CuerpoMusicaLocal> createState() => _CuerpoMusicaLocalState(); } class _CuerpoMusicaLocalState extends State<_CuerpoMusicaLocal> { final _fuente = FuenteMusicaLocalAutoImpl(); late Future _carpetaActual; @override void initState() { super.initState(); _carpetaActual = _fuente.carpetaActual(); } Future _elegirCarpeta(BuildContext context) async { final messenger = ScaffoldMessenger.of(context); final l10n = AppLocalizations.of(context); try { final uri = await _fuente.elegirCarpeta(); if (uri == null) return; // Cancelled — no snackbar, matches the SAF // picker's own "nothing changed" affordance. // fix/android-auto-musica-local, item 4: acaba de aparecer música // local donde antes no había. Android Auto cachea la raíz y no // vuelve a preguntar por su cuenta, así que sin esto el coche seguía // sin ofrecer «Música Local» hasta el siguiente re-bind — que puede // no llegar en toda la sesión. Fuera del `context.mounted` de abajo: // el árbol del coche no depende de que esta pantalla siga viva. invalidarArbolAuto(); if (!context.mounted) return; setState(() { _carpetaActual = Future.value(uri); }); messenger.showSnackBar( SnackBar(content: Text(l10n.localMusicFolderUpdated)), ); } catch (e) { if (!context.mounted) return; messenger.showSnackBar( SnackBar(content: Text(l10n.localMusicFolderSaveError(e.toString()))), ); } } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); return PluriGlassSurface( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 8), Text( l10n.localMusicSectionDescription, style: Theme.of(context).textTheme.bodySmall, ), FutureBuilder( future: _carpetaActual, builder: (ctx, snap) { final carpeta = snap.data; return ListTile( contentPadding: EdgeInsets.zero, leading: const Icon(Icons.folder_outlined), title: Text(l10n.localMusicFolderTitle), subtitle: Text( (carpeta == null || carpeta.isEmpty) ? l10n.localMusicFolderNotConfigured : nombreCarpetaDesdeUri( carpeta, nombreGenerico: l10n.localMusicFolderGenericName, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ); }, ), const SizedBox(height: 8), FutureBuilder( future: _carpetaActual, builder: (ctx, snap) { final configurada = (snap.data ?? '').isNotEmpty; return Align( alignment: Alignment.centerLeft, child: OutlinedButton.icon( icon: const Icon(Icons.folder_open_rounded), label: Text( configurada ? l10n.localMusicChangePath : l10n.localMusicChoosePath, ), onPressed: () => _elegirCarpeta(context), ), ); }, ), ], ), ); } }