From 17bcf73f8f8edf084acc4636dd4366fd9a838a18 Mon Sep 17 00:00:00 2001 From: MSOB7YY Date: Sun, 3 Dec 2023 04:11:30 +0200 Subject: [PATCH] feat: separate sorting for search tracks --- .gitmodules | 1 - lib/controller/search_sort_controller.dart | 68 +++++++++++---- lib/controller/settings_controller.dart | 21 +++++ lib/ui/pages/search_page.dart | 42 ++++++++++ lib/ui/widgets/custom_widgets.dart | 11 ++- lib/ui/widgets/sort_by_button.dart | 98 +++++++++++++++++++++- 6 files changed, 220 insertions(+), 21 deletions(-) diff --git a/.gitmodules b/.gitmodules index 76b18495..9878bd4f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,3 @@ [submodule "language"] path = assets/language url = https://github.com/namidaco/namida-translations - ignore = dirty diff --git a/lib/controller/search_sort_controller.dart b/lib/controller/search_sort_controller.dart index 823a7bd4..ec88e09b 100644 --- a/lib/controller/search_sort_controller.dart +++ b/lib/controller/search_sort_controller.dart @@ -171,12 +171,16 @@ class SearchSortController { result.add(tr); } }); - - final finalList = temp ? trackSearchTemp : trackSearchList; - - finalList - ..clear() - ..addAll(result); + if (temp) { + trackSearchTemp + ..clear() + ..addAll(result); + sortTracksSearch(canSkipSorting: true); + } else { + trackSearchList + ..clear() + ..addAll(result); + } } void _searchMediaType({required MediaType type, required String text, bool temp = false}) async { @@ -312,12 +316,48 @@ class SearchSortController { /// Sorts Tracks and Saves automatically to settings void _sortTracks({SortType? sortBy, bool? reverse}) { - sortBy ??= settings.tracksSort.value; - reverse ??= settings.tracksSortReversed.value; + _sortTracksRaw( + sortBy: sortBy ?? settings.tracksSort.value, + reverse: reverse ?? settings.tracksSortReversed.value, + list: tracksInfoList, + onDone: (sortType, isReverse) { + settings.save(tracksSort: sortType, tracksSortReversed: isReverse); + _searchTracks(LibraryTab.tracks.textSearchController?.text ?? ''); + }, + ); + } + + void sortTracksSearch({SortType? sortBy, bool? reverse, bool canSkipSorting = false}) { + final isAuto = settings.tracksSortSearchIsAuto.value; + + sortBy ??= isAuto ? settings.tracksSort.value : settings.tracksSortSearch.value; + reverse ??= isAuto ? settings.tracksSortReversed.value : settings.tracksSortSearchReversed.value; + + if (canSkipSorting) { + final identicalToMainOne = isAuto ? true : sortBy == settings.tracksSort.value && reverse == settings.tracksSortReversed.value; + if (identicalToMainOne) return; // since the looped list already has the same order + } + + _sortTracksRaw( + sortBy: sortBy, + reverse: reverse, + list: trackSearchTemp, + onDone: (sortType, isReverse) { + if (!isAuto) { + settings.save(tracksSortSearch: sortType, tracksSortSearchReversed: isReverse); + } + }, + ); + } - void sortThis(Comparable Function(Track e) comparable) => reverse! ? tracksInfoList.sortByReverse(comparable) : tracksInfoList.sortBy(comparable); - void sortThisAlts(List Function(Track tr)> alternatives) => - reverse! ? tracksInfoList.sortByReverseAlts(alternatives) : tracksInfoList.sortByAlts(alternatives); + void _sortTracksRaw({ + required SortType? sortBy, + required bool reverse, + required List list, + required void Function(SortType? sortType, bool isReverse) onDone, + }) { + void sortThis(Comparable Function(Track e) comparable) => reverse ? list.sortByReverse(comparable) : list.sortBy(comparable); + void sortThisAlts(List Function(Track tr)> alternatives) => reverse ? list.sortByReverseAlts(alternatives) : list.sortByAlts(alternatives); final sameAlbumSorters = getMediaTracksSortingComparables(MediaType.album); final sameArtistSorters = getMediaTracksSortingComparables(MediaType.artist); @@ -394,15 +434,13 @@ class SearchSortController { sortThis((e) => e.stats.rating); break; case SortType.shuffle: - tracksInfoList.shuffle(); + list.shuffle(); break; default: null; } - - settings.save(tracksSort: sortBy, tracksSortReversed: reverse); - _searchTracks(LibraryTab.tracks.textSearchController?.text ?? ''); + onDone(sortBy, reverse); } /// Sorts Albums and Saves automatically to settings diff --git a/lib/controller/settings_controller.dart b/lib/controller/settings_controller.dart index cb30c370..7106bbd0 100644 --- a/lib/controller/settings_controller.dart +++ b/lib/controller/settings_controller.dart @@ -66,6 +66,9 @@ class SettingsController { final RxList trackGenresSeparatorsBlacklist = [].obs; final Rx tracksSort = SortType.title.obs; final RxBool tracksSortReversed = false.obs; + final Rx tracksSortSearch = SortType.title.obs; + final RxBool tracksSortSearchReversed = false.obs; + final RxBool tracksSortSearchIsAuto = true.obs; final Rx albumSort = GroupSortType.album.obs; final RxBool albumSortReversed = false.obs; final Rx artistSort = GroupSortType.artistsList.obs; @@ -347,6 +350,9 @@ class SettingsController { trackGenresSeparatorsBlacklist.value = List.from(json['trackGenresSeparatorsBlacklist'] ?? trackGenresSeparatorsBlacklist); tracksSort.value = SortType.values.getEnum(json['tracksSort']) ?? tracksSort.value; tracksSortReversed.value = json['tracksSortReversed'] ?? tracksSortReversed.value; + tracksSortSearch.value = SortType.values.getEnum(json['tracksSortSearch']) ?? tracksSortSearch.value; + tracksSortSearchReversed.value = json['tracksSortSearchReversed'] ?? tracksSortSearchReversed.value; + tracksSortSearchIsAuto.value = json['tracksSortSearchIsAuto'] ?? tracksSortSearchIsAuto.value; albumSort.value = GroupSortType.values.getEnum(json['albumSort']) ?? albumSort.value; albumSortReversed.value = json['albumSortReversed'] ?? albumSortReversed.value; artistSort.value = GroupSortType.values.getEnum(json['artistSort']) ?? artistSort.value; @@ -568,6 +574,9 @@ class SettingsController { 'trackGenresSeparatorsBlacklist': trackGenresSeparatorsBlacklist.toList(), 'tracksSort': tracksSort.value.convertToString, 'tracksSortReversed': tracksSortReversed.value, + 'tracksSortSearch': tracksSortSearch.value.convertToString, + 'tracksSortSearchReversed': tracksSortSearchReversed.value, + 'tracksSortSearchIsAuto': tracksSortSearchIsAuto.value, 'albumSort': albumSort.value.convertToString, 'albumSortReversed': albumSortReversed.value, 'artistSort': artistSort.value.convertToString, @@ -738,6 +747,9 @@ class SettingsController { List? trackGenresSeparatorsBlacklist, SortType? tracksSort, bool? tracksSortReversed, + SortType? tracksSortSearch, + bool? tracksSortSearchReversed, + bool? tracksSortSearchIsAuto, GroupSortType? albumSort, bool? albumSortReversed, GroupSortType? artistSort, @@ -994,6 +1006,15 @@ class SettingsController { if (tracksSortReversed != null) { this.tracksSortReversed.value = tracksSortReversed; } + if (tracksSortSearch != null) { + this.tracksSortSearch.value = tracksSortSearch; + } + if (tracksSortSearchReversed != null) { + this.tracksSortSearchReversed.value = tracksSortSearchReversed; + } + if (tracksSortSearchIsAuto != null) { + this.tracksSortSearchIsAuto.value = tracksSortSearchIsAuto; + } if (albumSort != null) { this.albumSort.value = albumSort; } diff --git a/lib/ui/pages/search_page.dart b/lib/ui/pages/search_page.dart index 5e8b9636..78a30330 100644 --- a/lib/ui/pages/search_page.dart +++ b/lib/ui/pages/search_page.dart @@ -26,6 +26,7 @@ import 'package:namida/ui/widgets/library/album_card.dart'; import 'package:namida/ui/widgets/library/artist_card.dart'; import 'package:namida/ui/widgets/library/multi_artwork_card.dart'; import 'package:namida/ui/widgets/library/track_tile.dart'; +import 'package:namida/ui/widgets/sort_by_button.dart'; import 'package:namida/youtube/pages/yt_search_results_page.dart'; class SearchPage extends StatelessWidget { @@ -385,6 +386,47 @@ class SearchPage extends StatelessWidget { child: SearchPageTitleRow( title: '${lang.TRACKS} • ${SearchSortController.inst.trackSearchTemp.length}', icon: Broken.music_circle, + subtitleWidget: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + NamidaPopupWrapper( + useRootNavigator: false, + children: const [SortByMenuTracksSearch()], + child: NamidaInkWell( + child: Obx( + () { + final isAuto = settings.tracksSortSearchIsAuto.value; + final activeType = isAuto ? settings.tracksSort.value : settings.tracksSortSearch.value; + return Text( + activeType.toText() + (isAuto ? ' (${lang.AUTO})' : ''), + style: context.textTheme.displaySmall?.copyWith( + color: isAuto ? null : context.theme.colorScheme.secondary, + ), + ); + }, + ), + ), + ), + const SizedBox(width: 4.0), + NamidaInkWell( + onTap: () { + if (settings.tracksSortSearchIsAuto.value) return; + SearchSortController.inst.sortTracksSearch(reverse: !settings.tracksSortSearchReversed.value); + }, + child: Obx( + () { + final isAuto = settings.tracksSortSearchIsAuto.value; + final activeReverse = isAuto ? settings.tracksSortReversed.value : settings.tracksSortSearchReversed.value; + return Icon( + activeReverse ? Broken.arrow_up_3 : Broken.arrow_down_2, + size: 16.0, + color: isAuto ? null : context.theme.colorScheme.secondary, + ); + }, + ), + ), + ], + ), buttonIcon: Broken.play, buttonText: settings.trackPlayMode.value.toText(), onPressed: () { diff --git a/lib/ui/widgets/custom_widgets.dart b/lib/ui/widgets/custom_widgets.dart index c519f88c..f1393afd 100644 --- a/lib/ui/widgets/custom_widgets.dart +++ b/lib/ui/widgets/custom_widgets.dart @@ -776,11 +776,11 @@ class ListTileWithCheckMark extends StatelessWidget { Widget build(BuildContext context) { final tileAlpha = context.isDarkMode ? 5 : 20; return Material( - borderRadius: BorderRadius.circular(16.0.multipliedRadius), + borderRadius: BorderRadius.circular(14.0.multipliedRadius), color: tileColor ?? Color.alphaBlend(context.theme.colorScheme.onBackground.withAlpha(tileAlpha), context.theme.cardTheme.color!), child: ListTile( horizontalTitleGap: 10.0, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0.multipliedRadius)), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0.multipliedRadius)), leading: leading ?? Icon( icon ?? Broken.arrange_circle, @@ -1842,6 +1842,7 @@ class SearchPageTitleRow extends StatelessWidget { final String subtitle; final IconData icon; final Widget? trailing; + final Widget? subtitleWidget; final String? buttonText; final IconData? buttonIcon; final void Function()? onPressed; @@ -1851,6 +1852,7 @@ class SearchPageTitleRow extends StatelessWidget { this.subtitle = '', required this.icon, this.trailing, + this.subtitleWidget, this.buttonText, this.buttonIcon, this.onPressed, @@ -1872,6 +1874,7 @@ class SearchPageTitleRow extends StatelessWidget { title, style: context.textTheme.displayLarge?.copyWith(fontSize: 15.5.multipliedFontScale), ), + if (subtitleWidget != null) subtitleWidget!, if (subtitle != '') Text( subtitle, @@ -2708,6 +2711,7 @@ class NamidaPopupWrapper extends StatelessWidget { final VoidCallback? onPop; final bool openOnTap; final bool openOnLongPress; + final bool useRootNavigator; const NamidaPopupWrapper({ super.key, @@ -2718,6 +2722,7 @@ class NamidaPopupWrapper extends StatelessWidget { this.onPop, this.openOnTap = true, this.openOnLongPress = true, + this.useRootNavigator = true, }); void popMenu({bool handleClosing = true}) { @@ -2738,7 +2743,7 @@ class NamidaPopupWrapper extends StatelessWidget { ); await NamidaNavigator.inst.showMenu( showMenu( - useRootNavigator: true, + useRootNavigator: useRootNavigator, context: context, position: position, items: [ diff --git a/lib/ui/widgets/sort_by_button.dart b/lib/ui/widgets/sort_by_button.dart index d2961a45..e797fdbf 100644 --- a/lib/ui/widgets/sort_by_button.dart +++ b/lib/ui/widgets/sort_by_button.dart @@ -5,7 +5,9 @@ import 'package:get/get.dart'; import 'package:namida/controller/search_sort_controller.dart'; import 'package:namida/controller/settings_controller.dart'; import 'package:namida/core/enums.dart'; +import 'package:namida/core/icon_fonts/broken_icons.dart'; import 'package:namida/core/namida_converter_ext.dart'; +import 'package:namida/core/translations/language.dart'; import 'package:namida/ui/widgets/custom_widgets.dart'; class SortByMenuTracks extends StatelessWidget { @@ -16,11 +18,12 @@ class SortByMenuTracks extends StatelessWidget { return Obx( () { final tracksSort = settings.tracksSort.value; + final reversed = settings.tracksSortReversed.value; return Column( children: [ ListTileWithCheckMark( - active: settings.tracksSortReversed.value, - onTap: () => SearchSortController.inst.sortMedia(MediaType.track, reverse: !settings.tracksSortReversed.value), + active: reversed, + onTap: () => SearchSortController.inst.sortMedia(MediaType.track, reverse: !reversed), ), ...[ SortType.title, @@ -55,6 +58,97 @@ class SortByMenuTracks extends StatelessWidget { } } +class SortByMenuTracksSearch extends StatelessWidget { + const SortByMenuTracksSearch({super.key}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: context.height * 0.5, + child: SingleChildScrollView( + child: Obx( + () { + final tracksSortSearch = settings.tracksSortSearch.value; + final reversed = settings.tracksSortSearchReversed.value; + final isAuto = settings.tracksSortSearchIsAuto.value; + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: ListTileWithCheckMark( + icon: Broken.arrow_swap_horizontal, + title: lang.AUTO, + active: isAuto, + onTap: () { + settings.save(tracksSortSearchIsAuto: !isAuto); + SearchSortController.inst.sortTracksSearch(canSkipSorting: false); + }, + ), + ), + const SizedBox(height: 4.0), + GestureDetector( + onTap: isAuto ? () {} : null, + child: ColoredBox( + color: Colors.transparent, + child: IgnorePointer( + ignoring: isAuto, + child: AnimatedOpacity( + opacity: isAuto ? 0.6 : 1.0, + duration: const Duration(milliseconds: 300), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: ListTileWithCheckMark( + active: isAuto ? settings.tracksSortReversed.value : reversed, + onTap: () { + SearchSortController.inst.sortTracksSearch(reverse: !reversed); + }, + ), + ), + ...[ + SortType.title, + SortType.album, + SortType.artistsList, + SortType.albumArtist, + SortType.composer, + SortType.genresList, + SortType.year, + SortType.dateAdded, + SortType.dateModified, + SortType.bitrate, + SortType.trackNo, + SortType.discNo, + SortType.filename, + SortType.duration, + SortType.sampleRate, + SortType.size, + SortType.rating, + SortType.shuffle, + ].map( + (e) => SmallListTile( + title: e.toText(), + active: (isAuto ? settings.tracksSort.value : tracksSortSearch) == e, + onTap: () { + SearchSortController.inst.sortTracksSearch(sortBy: e); + }, + ), + ), + ], + ), + ), + ), + ), + ) + ], + ); + }, + ), + ), + ); + } +} + class SortByMenuAlbums extends StatelessWidget { const SortByMenuAlbums({super.key});