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.
This commit is contained in:
zarzet committed 2026-10-18 22:26:54 +07:00
1 parent 96568f720a
commit 4009855ba5
2 files changed
+263 -413

No files matched your search

+1 -413
View File
@@ -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<QueueTab> {
});
}
void _showFilterSheet(BuildContext context) {
String? tempSource = _filterSource;
String? tempQuality = _filterQuality;
String? tempFormat = _filterFormat;
String? tempMetadata = _filterMetadata;
String tempSortMode = _sortMode;
_showLibraryOptionsSheet<void>(
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) {
+262
View File
@@ -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<void>(
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),
),
),
],
),
),
);
},
),
);
}
}