diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2396b98d..05fdc571 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -627,6 +627,10 @@ "@shorts": { "description": "Youtube shorts" }, + "searchSortBy": "Sort by", + "@searchSortBy": { + "description": "Search sorting option" + }, "searchSortRelevance": "Relevance", "@searchSortRelevance": { "description": "Sort search by relevance" diff --git a/lib/search/models/search_sort_by.dart b/lib/search/models/search_sort_by.dart index a11b15c4..4d896fd3 100644 --- a/lib/search/models/search_sort_by.dart +++ b/lib/search/models/search_sort_by.dart @@ -1,6 +1,17 @@ +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + enum SearchSortBy { relevance, rating, - upload_date, - view_count; + date, + views; + + String getLable(AppLocalizations locals) { + return switch(this) { + (SearchSortBy.relevance) => locals.searchSortRelevance, + (SearchSortBy.rating) => locals.searchSortRating, + (SearchSortBy.date) => locals.searchSortUploadDate, + (SearchSortBy.views) => locals.searchSortViewCount, + }; + } } diff --git a/lib/search/search_filters.dart b/lib/search/search_filters.dart new file mode 100644 index 00000000..1633ec58 --- /dev/null +++ b/lib/search/search_filters.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:invidious/search/models/search_sort_by.dart'; + +const defaultSearchSortBy = SearchSortBy.relevance; + +class SearchFiltersButton extends StatelessWidget { + final SearchSortBy initialSearchSortBy; + final Function(SearchSortBy) callback; + + const SearchFiltersButton({ + super.key, + required this.initialSearchSortBy, + required this.callback, + }); + + @override + Widget build(BuildContext context) { + final locals = AppLocalizations.of(context)!; + return IconButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + SearchSortBy selectedSearchSortBy = initialSearchSortBy; + return AlertDialog( + content: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + onChanged(SearchSortBy? newSearchSortBy) { + setState(() => selectedSearchSortBy = + newSearchSortBy ?? defaultSearchSortBy); + } + + return SingleChildScrollView( + child: ListBody(children: [ + Text(locals.searchSortBy), + ...SearchSortBy.values.map((value) { + return ListTile( + title: Text(value.getLable(locals)), + leading: Radio( + value: value, + groupValue: selectedSearchSortBy, + onChanged: onChanged, + ), + ); + }), + ]), + ); + }, + ), + actions: [ + TextButton( + child: Text(locals.cancel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: Text(locals.ok), + onPressed: () { + callback(selectedSearchSortBy); + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + }, + icon: const Icon(Icons.filter_list_alt), + ); + } +} diff --git a/lib/search/states/search.dart b/lib/search/states/search.dart index 0990fe59..ceb3b60d 100644 --- a/lib/search/states/search.dart +++ b/lib/search/states/search.dart @@ -84,6 +84,10 @@ class SearchCubit extends Cubit { state.selectedIndex = value; emit(state); } + + void setSearchSortBy(SearchSortBy value) { + emit(state.copyWith(sortBy: value)); + } } abstract class Clonable { diff --git a/lib/search/views/screens/search.dart b/lib/search/views/screens/search.dart index 49151424..4480fe96 100644 --- a/lib/search/views/screens/search.dart +++ b/lib/search/views/screens/search.dart @@ -6,6 +6,7 @@ import 'package:invidious/globals.dart'; import 'package:invidious/playlists/views/components/playlist_list.dart'; import 'package:invidious/router.dart'; import 'package:invidious/search/models/search_type.dart'; +import 'package:invidious/search/search_filters.dart'; import 'package:invidious/utils/views/components/navigation_switcher.dart'; import 'package:invidious/videos/models/video_in_list.dart'; @@ -72,6 +73,15 @@ class SearchScreen extends StatelessWidget { } }, icon: const Icon(Icons.clear)), + SearchFiltersButton( + initialSearchSortBy: _.sortBy, + callback: (newSearchSortBy) { + cubit.setSearchSortBy(newSearchSortBy); + if (_.showResults || _.queryController.text.isNotEmpty) { + cubit.search(_.queryController.text); + } + }, + ), ], ), body: SafeArea( @@ -145,6 +155,7 @@ class SearchScreen extends StatelessWidget { child: NavigationSwitcher( child: [ VideoList( + key: UniqueKey(), paginatedVideoList: PageBasedPaginatedList( getItemsFunc: (page, maxResults) => service .search(_.queryController.value.text, diff --git a/lib/service.dart b/lib/service.dart index b7872a4f..e534f8c4 100644 --- a/lib/service.dart +++ b/lib/service.dart @@ -223,7 +223,13 @@ class Service { Future search(String query, {SearchType? type, int? page, SearchSortBy? sortBy}) async { String countryCode = db.getSettings(BROWSING_COUNTRY)?.value ?? 'US'; - Uri uri = buildUrl(urlSearch, query: {'q': Uri.encodeQueryComponent(query), 'type': type?.name, 'page': page?.toString() ?? '1', 'sort_by': sortBy?.name, 'region': countryCode}); + Uri uri = buildUrl(urlSearch, query: { + 'q': Uri.encodeQueryComponent(query), + 'type': type?.name, + 'page': page?.toString() ?? '1', + 'sort': sortBy?.name, + 'region': countryCode, + }); final response = await http.get(uri); Iterable i = handleResponse(response); // only getting videos for now