From 4009855ba541ffa8a46923742d15df871038b050 Mon Sep 17 00:00:00 2001 From: zarzet <42882290+zarzet@users.noreply.github.com> Date: Sun, 18 Oct 2026 22:21:23 +0700 Subject: [PATCH] refactor(library): build the filter sheet from the sort-mode table The filter sheet moves into its own part. The sort chips now iterate _librarySortModes with _sortLabel (same modes, labels and order), and the source, quality and metadata groups use value/label tables since their chips differ only in value and label. --- lib/screens/queue_tab.dart | 414 +----------------------- lib/screens/queue_tab_filter_sheet.dart | 262 +++++++++++++++ 2 files changed, 263 insertions(+), 413 deletions(-) create mode 100644 lib/screens/queue_tab_filter_sheet.dart diff --git a/lib/screens/queue_tab.dart b/lib/screens/queue_tab.dart index 5f132c12..73b59d14 100644 --- a/lib/screens/queue_tab.dart +++ b/lib/screens/queue_tab.dart @@ -102,6 +102,7 @@ part 'queue_tab_batch_actions.dart'; part 'queue_tab_item_widgets.dart'; part 'queue_tab_download_labels.dart'; part 'queue_tab_download_dialogs.dart'; +part 'queue_tab_filter_sheet.dart'; DownloadHistoryItem? _historyItemForCompletionBridge( DownloadItem item, @@ -679,419 +680,6 @@ class _QueueTabState extends ConsumerState { }); } - void _showFilterSheet(BuildContext context) { - String? tempSource = _filterSource; - String? tempQuality = _filterQuality; - String? tempFormat = _filterFormat; - String? tempMetadata = _filterMetadata; - String tempSortMode = _sortMode; - - _showLibraryOptionsSheet( - context, - builder: (context, scrollController) => StatefulBuilder( - builder: (context, setSheetState) { - return AppBottomSheet( - title: context.l10n.libraryFilterTitle, - titleTrailing: ExpressiveButton( - text: true, - onPressed: () { - setSheetState(() { - tempSource = null; - tempQuality = null; - tempFormat = null; - tempMetadata = null; - tempSortMode = 'latest'; - }); - }, - child: Text(context.l10n.libraryFilterReset), - ), - child: SingleChildScrollView( - controller: scrollController, - padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.libraryFilterSource, - style: Theme.of(context).textTheme.titleSmall?.copyWith( - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 8), - Wrap( - spacing: 8, - runSpacing: 8, - children: [ - AppChoiceChip( - label: Text(context.l10n.libraryFilterAll), - selected: tempSource == null, - onSelected: (_) => - setSheetState(() => tempSource = null), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterDownloaded), - selected: tempSource == 'downloaded', - onSelected: (_) => - setSheetState(() => tempSource = 'downloaded'), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterLocal), - selected: tempSource == 'local', - onSelected: (_) => - setSheetState(() => tempSource = 'local'), - ), - ], - ), - const SizedBox(height: 16), - - Text( - context.l10n.libraryFilterQuality, - style: Theme.of(context).textTheme.titleSmall?.copyWith( - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 8), - Wrap( - spacing: 8, - runSpacing: 8, - children: [ - AppChoiceChip( - label: Text(context.l10n.libraryFilterAll), - selected: tempQuality == null, - onSelected: (_) => - setSheetState(() => tempQuality = null), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterQualityHiRes), - selected: tempQuality == 'hires', - onSelected: (_) => - setSheetState(() => tempQuality = 'hires'), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterQualityCD), - selected: tempQuality == 'cd', - onSelected: (_) => - setSheetState(() => tempQuality = 'cd'), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterQualityLossy), - selected: tempQuality == 'lossy', - onSelected: (_) => - setSheetState(() => tempQuality = 'lossy'), - ), - ], - ), - const SizedBox(height: 16), - - Text( - context.l10n.libraryFilterFormat, - style: Theme.of(context).textTheme.titleSmall?.copyWith( - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 8), - Wrap( - spacing: 8, - runSpacing: 8, - children: [ - AppChoiceChip( - label: Text(context.l10n.libraryFilterAll), - selected: tempFormat == null, - onSelected: (_) => - setSheetState(() => tempFormat = null), - ), - for (final format in libraryFormatFilterAliases.keys) - AppChoiceChip( - label: Text(switch (format) { - 'm4a' => 'M4A / MP4', - 'wv' => 'WavPack', - _ => format.toUpperCase(), - }), - selected: tempFormat == format, - onSelected: (_) => - setSheetState(() => tempFormat = format), - ), - ], - ), - const SizedBox(height: 16), - - Text( - context.l10n.libraryFilterMetadata, - style: Theme.of(context).textTheme.titleSmall?.copyWith( - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 8), - Wrap( - spacing: 8, - runSpacing: 8, - children: [ - AppChoiceChip( - label: Text(context.l10n.libraryFilterAll), - selected: tempMetadata == null, - onSelected: (_) => - setSheetState(() => tempMetadata = null), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterMetadataComplete), - selected: tempMetadata == 'complete', - onSelected: (_) => - setSheetState(() => tempMetadata = 'complete'), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataMissingAny, - ), - selected: tempMetadata == 'missing-any', - onSelected: (_) => - setSheetState(() => tempMetadata = 'missing-any'), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataMissingYear, - ), - selected: tempMetadata == 'missing-year', - onSelected: (_) => - setSheetState(() => tempMetadata = 'missing-year'), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataMissingGenre, - ), - selected: tempMetadata == 'missing-genre', - onSelected: (_) => - setSheetState(() => tempMetadata = 'missing-genre'), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataMissingAlbumArtist, - ), - selected: tempMetadata == 'missing-album-artist', - onSelected: (_) => setSheetState( - () => tempMetadata = 'missing-album-artist', - ), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataMissingTrackNumber, - ), - selected: tempMetadata == 'missing-track-number', - onSelected: (_) => setSheetState( - () => tempMetadata = 'missing-track-number', - ), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataMissingDiscNumber, - ), - selected: tempMetadata == 'missing-disc-number', - onSelected: (_) => setSheetState( - () => tempMetadata = 'missing-disc-number', - ), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataMissingArtist, - ), - selected: tempMetadata == 'missing-artist', - onSelected: (_) => setSheetState( - () => tempMetadata = 'missing-artist', - ), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataIncorrectIsrcFormat, - ), - selected: tempMetadata == 'incorrect-isrc-format', - onSelected: (_) => setSheetState( - () => tempMetadata = 'incorrect-isrc-format', - ), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataMissingIsrc, - ), - selected: tempMetadata == 'missing-isrc', - onSelected: (_) => - setSheetState(() => tempMetadata = 'missing-isrc'), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataMissingLabel, - ), - selected: tempMetadata == 'missing-label', - onSelected: (_) => - setSheetState(() => tempMetadata = 'missing-label'), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataMissingLyrics, - ), - selected: tempMetadata == 'missing-lyrics', - onSelected: (_) => setSheetState( - () => tempMetadata = 'missing-lyrics', - ), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataHasReplayGain, - ), - selected: tempMetadata == 'has-replaygain', - onSelected: (_) => setSheetState( - () => tempMetadata = 'has-replaygain', - ), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryFilterMetadataMissingReplayGain, - ), - selected: tempMetadata == 'missing-replaygain', - onSelected: (_) => setSheetState( - () => tempMetadata = 'missing-replaygain', - ), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterIncompleteAlbums), - selected: tempMetadata == incompleteAlbumFilter, - onSelected: (_) => setSheetState( - () => tempMetadata = incompleteAlbumFilter, - ), - ), - AppChoiceChip( - label: Text( - context.l10n.libraryAlbumCompletenessUnknown, - ), - selected: - tempMetadata == unknownAlbumCompletenessFilter, - onSelected: (_) => setSheetState( - () => tempMetadata = unknownAlbumCompletenessFilter, - ), - ), - ], - ), - const SizedBox(height: 16), - - Text( - context.l10n.libraryFilterSort, - style: Theme.of(context).textTheme.titleSmall?.copyWith( - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 8), - Wrap( - spacing: 8, - runSpacing: 8, - children: [ - AppChoiceChip( - label: Text(context.l10n.libraryFilterSortLatest), - selected: tempSortMode == 'latest', - onSelected: (_) => - setSheetState(() => tempSortMode = 'latest'), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterSortOldest), - selected: tempSortMode == 'oldest', - onSelected: (_) => - setSheetState(() => tempSortMode = 'oldest'), - ), - AppChoiceChip( - label: Text(context.l10n.searchSortTitleAZ), - selected: tempSortMode == 'a-z', - onSelected: (_) => - setSheetState(() => tempSortMode = 'a-z'), - ), - AppChoiceChip( - label: Text(context.l10n.searchSortTitleZA), - selected: tempSortMode == 'z-a', - onSelected: (_) => - setSheetState(() => tempSortMode = 'z-a'), - ), - AppChoiceChip( - label: Text(context.l10n.searchSortArtistAZ), - selected: tempSortMode == 'artist-asc', - onSelected: (_) => - setSheetState(() => tempSortMode = 'artist-asc'), - ), - AppChoiceChip( - label: Text(context.l10n.searchSortArtistZA), - selected: tempSortMode == 'artist-desc', - onSelected: (_) => - setSheetState(() => tempSortMode = 'artist-desc'), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterSortAlbumAsc), - selected: tempSortMode == 'album-asc', - onSelected: (_) => - setSheetState(() => tempSortMode = 'album-asc'), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterSortAlbumDesc), - selected: tempSortMode == 'album-desc', - onSelected: (_) => - setSheetState(() => tempSortMode = 'album-desc'), - ), - AppChoiceChip( - label: Text(context.l10n.searchSortDateNewest), - selected: tempSortMode == 'release-newest', - onSelected: (_) => setSheetState( - () => tempSortMode = 'release-newest', - ), - ), - AppChoiceChip( - label: Text(context.l10n.searchSortDateOldest), - selected: tempSortMode == 'release-oldest', - onSelected: (_) => setSheetState( - () => tempSortMode = 'release-oldest', - ), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterSortGenreAsc), - selected: tempSortMode == 'genre-asc', - onSelected: (_) => - setSheetState(() => tempSortMode = 'genre-asc'), - ), - AppChoiceChip( - label: Text(context.l10n.libraryFilterSortGenreDesc), - selected: tempSortMode == 'genre-desc', - onSelected: (_) => - setSheetState(() => tempSortMode = 'genre-desc'), - ), - ], - ), - const SizedBox(height: 24), - - SizedBox( - width: double.infinity, - child: AppDialogAction( - filled: true, - onPressed: () { - setState(() { - _filterSource = tempSource; - _filterQuality = tempQuality; - _filterFormat = tempFormat; - _filterMetadata = tempMetadata; - _sortMode = tempSortMode; - _resetLibraryPaging(); - }); - Navigator.pop(context); - if (widget.librarySection == null && - isAlbumCompletenessFilter(tempMetadata)) { - _animateToFilterPage(_filterModes.indexOf('albums')); - } - }, - child: Text(context.l10n.libraryFilterApply), - ), - ), - ], - ), - ), - ); - }, - ), - ); - } - @override Widget build(BuildContext context) { if (context.isMornye && widget.librarySection == null) { diff --git a/lib/screens/queue_tab_filter_sheet.dart b/lib/screens/queue_tab_filter_sheet.dart new file mode 100644 index 00000000..348961ad --- /dev/null +++ b/lib/screens/queue_tab_filter_sheet.dart @@ -0,0 +1,262 @@ +part of 'queue_tab.dart'; + +extension _QueueTabFilterSheet on _QueueTabState { + void _showFilterSheet(BuildContext context) { + String? tempSource = _filterSource; + String? tempQuality = _filterQuality; + String? tempFormat = _filterFormat; + String? tempMetadata = _filterMetadata; + String tempSortMode = _sortMode; + + _showLibraryOptionsSheet( + context, + builder: (context, scrollController) => StatefulBuilder( + builder: (context, setSheetState) { + return AppBottomSheet( + title: context.l10n.libraryFilterTitle, + titleTrailing: ExpressiveButton( + text: true, + onPressed: () { + setSheetState(() { + tempSource = null; + tempQuality = null; + tempFormat = null; + tempMetadata = null; + tempSortMode = 'latest'; + }); + }, + child: Text(context.l10n.libraryFilterReset), + ), + child: SingleChildScrollView( + controller: scrollController, + padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.libraryFilterSource, + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + for (final (value, label) in <(String?, String)>[ + (null, context.l10n.libraryFilterAll), + ('downloaded', context.l10n.libraryFilterDownloaded), + ('local', context.l10n.libraryFilterLocal), + ]) + AppChoiceChip( + label: Text(label), + selected: tempSource == value, + onSelected: (_) => + setSheetState(() => tempSource = value), + ), + ], + ), + const SizedBox(height: 16), + + Text( + context.l10n.libraryFilterQuality, + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + for (final (value, label) in <(String?, String)>[ + (null, context.l10n.libraryFilterAll), + ('hires', context.l10n.libraryFilterQualityHiRes), + ('cd', context.l10n.libraryFilterQualityCD), + ('lossy', context.l10n.libraryFilterQualityLossy), + ]) + AppChoiceChip( + label: Text(label), + selected: tempQuality == value, + onSelected: (_) => + setSheetState(() => tempQuality = value), + ), + ], + ), + const SizedBox(height: 16), + + Text( + context.l10n.libraryFilterFormat, + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + AppChoiceChip( + label: Text(context.l10n.libraryFilterAll), + selected: tempFormat == null, + onSelected: (_) => + setSheetState(() => tempFormat = null), + ), + for (final format in libraryFormatFilterAliases.keys) + AppChoiceChip( + label: Text(switch (format) { + 'm4a' => 'M4A / MP4', + 'wv' => 'WavPack', + _ => format.toUpperCase(), + }), + selected: tempFormat == format, + onSelected: (_) => + setSheetState(() => tempFormat = format), + ), + ], + ), + const SizedBox(height: 16), + + Text( + context.l10n.libraryFilterMetadata, + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + for (final (value, label) in <(String?, String)>[ + (null, context.l10n.libraryFilterAll), + ( + 'complete', + context.l10n.libraryFilterMetadataComplete, + ), + ( + 'missing-any', + context.l10n.libraryFilterMetadataMissingAny, + ), + ( + 'missing-year', + context.l10n.libraryFilterMetadataMissingYear, + ), + ( + 'missing-genre', + context.l10n.libraryFilterMetadataMissingGenre, + ), + ( + 'missing-album-artist', + context.l10n.libraryFilterMetadataMissingAlbumArtist, + ), + ( + 'missing-track-number', + context.l10n.libraryFilterMetadataMissingTrackNumber, + ), + ( + 'missing-disc-number', + context.l10n.libraryFilterMetadataMissingDiscNumber, + ), + ( + 'missing-artist', + context.l10n.libraryFilterMetadataMissingArtist, + ), + ( + 'incorrect-isrc-format', + context.l10n.libraryFilterMetadataIncorrectIsrcFormat, + ), + ( + 'missing-isrc', + context.l10n.libraryFilterMetadataMissingIsrc, + ), + ( + 'missing-label', + context.l10n.libraryFilterMetadataMissingLabel, + ), + ( + 'missing-lyrics', + context.l10n.libraryFilterMetadataMissingLyrics, + ), + ( + 'has-replaygain', + context.l10n.libraryFilterMetadataHasReplayGain, + ), + ( + 'missing-replaygain', + context.l10n.libraryFilterMetadataMissingReplayGain, + ), + ( + incompleteAlbumFilter, + context.l10n.libraryFilterIncompleteAlbums, + ), + ( + unknownAlbumCompletenessFilter, + context.l10n.libraryAlbumCompletenessUnknown, + ), + ]) + AppChoiceChip( + label: Text(label), + selected: tempMetadata == value, + onSelected: (_) => + setSheetState(() => tempMetadata = value), + ), + ], + ), + const SizedBox(height: 16), + + Text( + context.l10n.libraryFilterSort, + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + for (final mode in _librarySortModes) + AppChoiceChip( + label: Text(_sortLabel(context, mode)), + selected: tempSortMode == mode, + onSelected: (_) => + setSheetState(() => tempSortMode = mode), + ), + ], + ), + const SizedBox(height: 24), + + SizedBox( + width: double.infinity, + child: AppDialogAction( + filled: true, + onPressed: () { + _setState(() { + _filterSource = tempSource; + _filterQuality = tempQuality; + _filterFormat = tempFormat; + _filterMetadata = tempMetadata; + _sortMode = tempSortMode; + _resetLibraryPaging(); + }); + Navigator.pop(context); + if (widget.librarySection == null && + isAlbumCompletenessFilter(tempMetadata)) { + _animateToFilterPage(_filterModes.indexOf('albums')); + } + }, + child: Text(context.l10n.libraryFilterApply), + ), + ), + ], + ), + ), + ); + }, + ), + ); + } +}