From 7eb0ed2fdb0c5f018d81eb35ce21d5490436bdd8 Mon Sep 17 00:00:00 2001 From: zarzet Date: Tue, 28 Jul 2026 17:39:58 +0700 Subject: [PATCH] fix(metadata): dismiss autofill preview and surface feedback --- lib/screens/track_metadata_edit_sheet.dart | 416 +++++++++++---------- 1 file changed, 211 insertions(+), 205 deletions(-) diff --git a/lib/screens/track_metadata_edit_sheet.dart b/lib/screens/track_metadata_edit_sheet.dart index 0aecb8bb..d5d71462 100644 --- a/lib/screens/track_metadata_edit_sheet.dart +++ b/lib/screens/track_metadata_edit_sheet.dart @@ -60,6 +60,8 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { bool _loadingCurrentCover = false; String? _selectedMetadataProviderId; _AutoFillPreview? _autoFillPreview; + final GlobalKey _sheetMessengerKey = + GlobalKey(); final Set _autoFillFields = {}; @@ -224,6 +226,18 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { _autoFillPreview = null; } + void _showSheetSnackBar(String message) { + final snackBar = SnackBar(content: Text(message)); + final messenger = _sheetMessengerKey.currentState; + if (messenger != null) { + messenger + ..hideCurrentSnackBar() + ..showSnackBar(snackBar); + return; + } + ScaffoldMessenger.maybeOf(context)?.showSnackBar(snackBar); + } + static const _fieldDefs = { 'title': 'title', 'artist': 'artist', @@ -420,9 +434,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { }); } catch (e) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.snackbarError(e.toString()))), - ); + _showSheetSnackBar(context.l10n.snackbarError(e.toString())); } } @@ -838,9 +850,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { Future _fetchAutoFillPreview() async { if (_autoFillFields.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.editMetadataAutoFillNoneSelected)), - ); + _showSheetSnackBar(context.l10n.editMetadataAutoFillNoneSelected); return; } @@ -902,9 +912,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { if (needsTrackLookup && best == null && queryParts.isEmpty) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.editMetadataAutoFillNoResults)), - ); + _showSheetSnackBar(context.l10n.editMetadataAutoFillNoResults); } return; } @@ -929,9 +937,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { if (!mounted) return; if (results.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.editMetadataAutoFillNoResults)), - ); + _showSheetSnackBar(context.l10n.editMetadataAutoFillNoResults); return; } @@ -964,9 +970,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { } if (best == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.editMetadataAutoFillNoResults)), - ); + _showSheetSnackBar(context.l10n.editMetadataAutoFillNoResults); return; } @@ -1167,9 +1171,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { : availableValues.containsKey(key), ); if (!hasSelectedValue) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.editMetadataAutoFillNoResults)), - ); + _showSheetSnackBar(context.l10n.editMetadataAutoFillNoResults); return; } @@ -1189,9 +1191,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { }); } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.snackbarError(e.toString()))), - ); + _showSheetSnackBar(context.l10n.snackbarError(e.toString())); } } finally { if (mounted) setState(() => _fetching = false); @@ -1246,24 +1246,22 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { } if (!mounted) return; - setState(() {}); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - filledCount > 0 - ? context.l10n.editMetadataAutoFillDoneFromSource( - filledCount, - preview.sourceName, - ) - : context.l10n.editMetadataAutoFillNoResults, - ), - ), + setState(() { + if (filledCount > 0) { + _invalidateAutoFillPreview(); + } + }); + _showSheetSnackBar( + filledCount > 0 + ? context.l10n.editMetadataAutoFillDoneFromSource( + filledCount, + preview.sourceName, + ) + : context.l10n.editMetadataAutoFillNoResults, ); } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.snackbarError(e.toString()))), - ); + _showSheetSnackBar(context.l10n.snackbarError(e.toString())); } } finally { if (mounted) setState(() => _fetching = false); @@ -1348,9 +1346,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { if (result['error'] != null) { if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('${result['error']}'))); + _showSheetSnackBar('${result['error']}'); } setState(() => _saving = false); return; @@ -1492,9 +1488,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { if (ffmpegResult == null) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.metadataSaveFailedFfmpeg)), - ); + _showSheetSnackBar(context.l10n.metadataSaveFailedFfmpeg); } setState(() => _saving = false); return; @@ -1503,9 +1497,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { if (tempPath != null && safUri != null) { final ok = await PlatformBridge.writeTempToSaf(ffmpegResult, safUri); if (!ok && mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.metadataSaveFailedStorage)), - ); + _showSheetSnackBar(context.l10n.metadataSaveFailedStorage); setState(() => _saving = false); return; } @@ -1517,9 +1509,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { } } catch (e) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.snackbarError(e.toString()))), - ); + _showSheetSnackBar(context.l10n.snackbarError(e.toString())); } } finally { if (mounted) setState(() => _saving = false); @@ -1537,165 +1527,189 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { minChildSize: 0.5, maxChildSize: 0.95, expand: false, - builder: (context, scrollController) => Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 12, bottom: 8), - child: Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: cs.onSurfaceVariant.withValues(alpha: 0.4), - borderRadius: BorderRadius.circular(2), + builder: (context, scrollController) => ScaffoldMessenger( + key: _sheetMessengerKey, + child: Scaffold( + backgroundColor: Colors.transparent, + resizeToAvoidBottomInset: false, + body: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 12, bottom: 8), + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: cs.onSurfaceVariant.withValues(alpha: 0.4), + borderRadius: BorderRadius.circular(2), + ), + ), ), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(20, 4, 20, 12), - child: Row( - children: [ - Expanded( - child: Text( - context.l10n.trackEditMetadata, - style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - ), - if (_saving) - const Padding( - padding: EdgeInsets.symmetric(horizontal: 12), - child: SizedBox( - width: 22, - height: 22, - child: CircularProgressIndicator(strokeWidth: 2), - ), - ) - else - FilledButton.icon( - onPressed: _save, - icon: const Icon(Icons.check, size: 18), - label: Text(context.l10n.dialogSave), - style: FilledButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 18, - vertical: 12, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - ), - ], - ), - ), - Divider(height: 1, color: cs.outlineVariant.withValues(alpha: 0.5)), - Expanded( - child: ListView( - controller: scrollController, - padding: const EdgeInsets.fromLTRB(20, 6, 20, 24), - children: [ - _buildCoverEditor(cs), - _buildAutoFillSection(cs), - _sectionCard( - icon: Icons.info_outline, - title: context.l10n.trackMetadata, + Padding( + padding: const EdgeInsets.fromLTRB(20, 4, 20, 12), + child: Row( children: [ - _field(context.l10n.editMetadataFieldTitle, _titleCtrl), - _field(context.l10n.editMetadataFieldArtist, _artistCtrl), - _field(context.l10n.editMetadataFieldAlbum, _albumCtrl), - _field( - context.l10n.editMetadataFieldAlbumArtist, - _albumArtistCtrl, + Expanded( + child: Text( + context.l10n.trackEditMetadata, + style: Theme.of(context).textTheme.titleLarge + ?.copyWith(fontWeight: FontWeight.bold), + ), ), - _field( - context.l10n.editMetadataFieldDate, - _dateCtrl, - hint: context.l10n.editMetadataFieldDateHint, - ), - Row( - children: [ - Expanded( - child: _field( - context.l10n.editMetadataFieldTrackNum, - _trackNumCtrl, - keyboard: TextInputType.number, + if (_saving) + const Padding( + padding: EdgeInsets.symmetric(horizontal: 12), + child: SizedBox( + width: 22, + height: 22, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ) + else + FilledButton.icon( + onPressed: _save, + icon: const Icon(Icons.check, size: 18), + label: Text(context.l10n.dialogSave), + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 18, + vertical: 12, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), ), ), - const SizedBox(width: 12), - Expanded( - child: _field( - context.l10n.editMetadataFieldTrackTotal, - _trackTotalCtrl, - keyboard: TextInputType.number, - ), + ), + ], + ), + ), + Divider( + height: 1, + color: cs.outlineVariant.withValues(alpha: 0.5), + ), + Expanded( + child: ListView( + controller: scrollController, + padding: const EdgeInsets.fromLTRB(20, 6, 20, 24), + children: [ + _buildCoverEditor(cs), + _buildAutoFillSection(cs), + _sectionCard( + icon: Icons.info_outline, + title: context.l10n.trackMetadata, + children: [ + _field( + context.l10n.editMetadataFieldTitle, + _titleCtrl, + ), + _field( + context.l10n.editMetadataFieldArtist, + _artistCtrl, + ), + _field( + context.l10n.editMetadataFieldAlbum, + _albumCtrl, + ), + _field( + context.l10n.editMetadataFieldAlbumArtist, + _albumArtistCtrl, + ), + _field( + context.l10n.editMetadataFieldDate, + _dateCtrl, + hint: context.l10n.editMetadataFieldDateHint, + ), + Row( + children: [ + Expanded( + child: _field( + context.l10n.editMetadataFieldTrackNum, + _trackNumCtrl, + keyboard: TextInputType.number, + ), + ), + const SizedBox(width: 12), + Expanded( + child: _field( + context.l10n.editMetadataFieldTrackTotal, + _trackTotalCtrl, + keyboard: TextInputType.number, + ), + ), + ], + ), + Row( + children: [ + Expanded( + child: _field( + context.l10n.editMetadataFieldDiscNum, + _discNumCtrl, + keyboard: TextInputType.number, + ), + ), + const SizedBox(width: 12), + Expanded( + child: _field( + context.l10n.editMetadataFieldDiscTotal, + _discTotalCtrl, + keyboard: TextInputType.number, + ), + ), + ], + ), + _field( + context.l10n.editMetadataFieldGenre, + _genreCtrl, + ), + _field(context.l10n.editMetadataFieldIsrc, _isrcCtrl), + ], + ), + _sectionCard( + icon: Icons.lyrics_outlined, + title: context.l10n.trackLyrics, + children: [ + _field( + context.l10n.trackLyrics, + _lyricsCtrl, + maxLines: 8, + keyboard: TextInputType.multiline, ), ], ), - Row( + _sectionCard( + icon: Icons.tune, + title: context.l10n.editMetadataAdvanced, + onHeaderTap: () => + setState(() => _showAdvanced = !_showAdvanced), + expanded: _showAdvanced, children: [ - Expanded( - child: _field( - context.l10n.editMetadataFieldDiscNum, - _discNumCtrl, - keyboard: TextInputType.number, + if (_showAdvanced) ...[ + _field( + context.l10n.editMetadataFieldLabel, + _labelCtrl, ), - ), - const SizedBox(width: 12), - Expanded( - child: _field( - context.l10n.editMetadataFieldDiscTotal, - _discTotalCtrl, - keyboard: TextInputType.number, + _field( + context.l10n.editMetadataFieldCopyright, + _copyrightCtrl, ), - ), + _field( + context.l10n.editMetadataFieldComposer, + _composerCtrl, + ), + _field( + context.l10n.editMetadataFieldComment, + _commentCtrl, + maxLines: 3, + ), + ], ], ), - _field(context.l10n.editMetadataFieldGenre, _genreCtrl), - _field(context.l10n.editMetadataFieldIsrc, _isrcCtrl), ], ), - _sectionCard( - icon: Icons.lyrics_outlined, - title: context.l10n.trackLyrics, - children: [ - _field( - context.l10n.trackLyrics, - _lyricsCtrl, - maxLines: 8, - keyboard: TextInputType.multiline, - ), - ], - ), - _sectionCard( - icon: Icons.tune, - title: context.l10n.editMetadataAdvanced, - onHeaderTap: () => - setState(() => _showAdvanced = !_showAdvanced), - expanded: _showAdvanced, - children: [ - if (_showAdvanced) ...[ - _field(context.l10n.editMetadataFieldLabel, _labelCtrl), - _field( - context.l10n.editMetadataFieldCopyright, - _copyrightCtrl, - ), - _field( - context.l10n.editMetadataFieldComposer, - _composerCtrl, - ), - _field( - context.l10n.editMetadataFieldComment, - _commentCtrl, - maxLines: 3, - ), - ], - ], - ), - ], - ), + ), + ], ), - ], + ), ), ), ); @@ -1930,9 +1944,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { Future _fetchFromMusicBrainz() async { final isrc = _isrcCtrl.text.trim(); if (isrc.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.editMetadataMusicBrainzNeedsIsrc)), - ); + _showSheetSnackBar(context.l10n.editMetadataMusicBrainzNeedsIsrc); return; } setState(() => _fetchingMusicBrainz = true); @@ -1951,20 +1963,14 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { _albumArtistCtrl.text = tags.albumArtist; filled++; } - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - filled > 0 - ? context.l10n.editMetadataMusicBrainzFilled - : context.l10n.editMetadataMusicBrainzNothing, - ), - ), + _showSheetSnackBar( + filled > 0 + ? context.l10n.editMetadataMusicBrainzFilled + : context.l10n.editMetadataMusicBrainzNothing, ); } catch (_) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.editMetadataMusicBrainzNothing)), - ); + _showSheetSnackBar(context.l10n.editMetadataMusicBrainzNothing); } } finally { if (mounted) setState(() => _fetchingMusicBrainz = false);