diff --git a/lib/search/states/search.dart b/lib/search/states/search.dart index 38ca7280..65886f1c 100644 --- a/lib/search/states/search.dart +++ b/lib/search/states/search.dart @@ -3,6 +3,7 @@ import 'package:easy_debounce/easy_debounce.dart'; import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:invidious/globals.dart'; +import 'package:invidious/search/models/db/search_history_item.dart'; import 'package:invidious/search/models/search_sort_by.dart'; import 'package:invidious/search/states/search_filter.dart'; @@ -22,6 +23,7 @@ class SearchCubit extends Cubit { void onInit() { state.queryController.addListener(getSuggestions); + getHistory(); if (state.searchNow) { search(state.queryController.value.text); } @@ -70,18 +72,32 @@ class SearchCubit extends Cubit { } } - List getHistory() { - return settings.state.useSearchHistory ? db.getSearchHistory() : []; + getHistory() { + emit(state.copyWith( + searchHistory: + settings.state.useSearchHistory ? db.getSearchHistory() : [])); } search(String value) async { emit(state.copyWith(showResults: true)); + + final query = state.queryController.text; + if (query.isNotEmpty && settings.state.useSearchHistory) { + await db.addToSearchHistory(SearchHistoryItem( + query, (DateTime.now().millisecondsSinceEpoch / 1000).round())); + } + getHistory(); } setSearchQuery(String e) { state.queryController.text = e; search(e); } + + removeFromHistory(String e) async { + await db.deleteFromSearchHistory(e); + getHistory(); + } } @freezed @@ -95,6 +111,7 @@ class SearchState with _$SearchState { @Default(1) int videoPage, @Default(1) int channelPage, @Default(1) int playlistPage, + @Default([]) List searchHistory, @Default(SearchFiltersState()) SearchFiltersState filters}) = _SearchState; diff --git a/lib/search/states/search.freezed.dart b/lib/search/states/search.freezed.dart index 65692e9a..6093c21a 100644 --- a/lib/search/states/search.freezed.dart +++ b/lib/search/states/search.freezed.dart @@ -25,6 +25,7 @@ mixin _$SearchState { int get videoPage => throw _privateConstructorUsedError; int get channelPage => throw _privateConstructorUsedError; int get playlistPage => throw _privateConstructorUsedError; + List get searchHistory => throw _privateConstructorUsedError; SearchFiltersState get filters => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -47,6 +48,7 @@ abstract class $SearchStateCopyWith<$Res> { int videoPage, int channelPage, int playlistPage, + List searchHistory, SearchFiltersState filters}); $SearchFiltersStateCopyWith<$Res> get filters; @@ -73,6 +75,7 @@ class _$SearchStateCopyWithImpl<$Res, $Val extends SearchState> Object? videoPage = null, Object? channelPage = null, Object? playlistPage = null, + Object? searchHistory = null, Object? filters = null, }) { return _then(_value.copyWith( @@ -108,6 +111,10 @@ class _$SearchStateCopyWithImpl<$Res, $Val extends SearchState> ? _value.playlistPage : playlistPage // ignore: cast_nullable_to_non_nullable as int, + searchHistory: null == searchHistory + ? _value.searchHistory + : searchHistory // ignore: cast_nullable_to_non_nullable + as List, filters: null == filters ? _value.filters : filters // ignore: cast_nullable_to_non_nullable @@ -141,6 +148,7 @@ abstract class _$$SearchStateImplCopyWith<$Res> int videoPage, int channelPage, int playlistPage, + List searchHistory, SearchFiltersState filters}); @override @@ -166,6 +174,7 @@ class __$$SearchStateImplCopyWithImpl<$Res> Object? videoPage = null, Object? channelPage = null, Object? playlistPage = null, + Object? searchHistory = null, Object? filters = null, }) { return _then(_$SearchStateImpl( @@ -201,6 +210,10 @@ class __$$SearchStateImplCopyWithImpl<$Res> ? _value.playlistPage : playlistPage // ignore: cast_nullable_to_non_nullable as int, + searchHistory: null == searchHistory + ? _value._searchHistory + : searchHistory // ignore: cast_nullable_to_non_nullable + as List, filters: null == filters ? _value.filters : filters // ignore: cast_nullable_to_non_nullable @@ -221,8 +234,10 @@ class _$SearchStateImpl implements _SearchState { this.videoPage = 1, this.channelPage = 1, this.playlistPage = 1, + final List searchHistory = const [], this.filters = const SearchFiltersState()}) - : _suggestions = suggestions; + : _suggestions = suggestions, + _searchHistory = searchHistory; @override final TextEditingController queryController; @@ -253,13 +268,22 @@ class _$SearchStateImpl implements _SearchState { @override @JsonKey() final int playlistPage; + final List _searchHistory; + @override + @JsonKey() + List get searchHistory { + if (_searchHistory is EqualUnmodifiableListView) return _searchHistory; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_searchHistory); + } + @override @JsonKey() final SearchFiltersState filters; @override String toString() { - return 'SearchState(queryController: $queryController, searchNow: $searchNow, suggestions: $suggestions, sortBy: $sortBy, showResults: $showResults, videoPage: $videoPage, channelPage: $channelPage, playlistPage: $playlistPage, filters: $filters)'; + return 'SearchState(queryController: $queryController, searchNow: $searchNow, suggestions: $suggestions, sortBy: $sortBy, showResults: $showResults, videoPage: $videoPage, channelPage: $channelPage, playlistPage: $playlistPage, searchHistory: $searchHistory, filters: $filters)'; } @override @@ -282,6 +306,8 @@ class _$SearchStateImpl implements _SearchState { other.channelPage == channelPage) && (identical(other.playlistPage, playlistPage) || other.playlistPage == playlistPage) && + const DeepCollectionEquality() + .equals(other._searchHistory, _searchHistory) && (identical(other.filters, filters) || other.filters == filters)); } @@ -296,6 +322,7 @@ class _$SearchStateImpl implements _SearchState { videoPage, channelPage, playlistPage, + const DeepCollectionEquality().hash(_searchHistory), filters); @JsonKey(ignore: true) @@ -315,6 +342,7 @@ abstract class _SearchState implements SearchState { final int videoPage, final int channelPage, final int playlistPage, + final List searchHistory, final SearchFiltersState filters}) = _$SearchStateImpl; @override @@ -334,6 +362,8 @@ abstract class _SearchState implements SearchState { @override int get playlistPage; @override + List get searchHistory; + @override SearchFiltersState get filters; @override @JsonKey(ignore: true) diff --git a/lib/search/views/screens/search.dart b/lib/search/views/screens/search.dart index 82741014..c74acaad 100644 --- a/lib/search/views/screens/search.dart +++ b/lib/search/views/screens/search.dart @@ -32,7 +32,7 @@ class SearchScreen extends StatelessWidget { create: (context) => SearchCubit( SearchState.init(query: query, searchNow: searchNow), settings), child: BlocBuilder( - builder: (context, _) { + builder: (context, state) { var cubit = context.read(); return AutoTabsRouter.tabBar( routes: const [ @@ -44,7 +44,7 @@ class SearchScreen extends StatelessWidget { final tabsController = AutoTabsRouter.of(context); return Scaffold( bottomNavigationBar: - _.showResults && deviceType == DeviceType.phone + state.showResults && deviceType == DeviceType.phone ? NavigationBar( selectedIndex: tabsController.activeIndex, onDestinationSelected: @@ -65,7 +65,7 @@ class SearchScreen extends StatelessWidget { appBar: AppBar( title: TextField( autofocus: query == null, - controller: _.queryController, + controller: state.queryController, textInputAction: TextInputAction.search, onSubmitted: cubit.search, ), @@ -73,17 +73,17 @@ class SearchScreen extends StatelessWidget { IconButton( onPressed: () { if (cubit.searchCleared()) { - AutoRouter.of(context).pop(); + AutoRouter.of(context).maybePop(); } }, icon: const Icon(Icons.clear)), SearchFiltersButton( - initialFilters: _.filters, + initialFilters: state.filters, onChanged: (newFilters) { cubit.onFiltersChanged(newFilters); - if (_.showResults || - _.queryController.text.isNotEmpty) { - cubit.search(_.queryController.text); + if (state.showResults || + state.queryController.text.isNotEmpty) { + cubit.search(state.queryController.text); } }, ), @@ -93,11 +93,11 @@ class SearchScreen extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric( horizontal: innerHorizontalPadding), - child: !_.showResults + child: !state.showResults ? ListView( - children: _.queryController.value.text.isEmpty - ? cubit - .getHistory() + children: state + .queryController.value.text.isEmpty + ? state.searchHistory .map((e) => InkWell( onTap: () => cubit.setSearchQuery(e), @@ -106,16 +106,29 @@ class SearchScreen extends StatelessWidget { const EdgeInsets.all(8.0), child: Row(children: [ const Icon(Icons.history), - Padding( - padding: - const EdgeInsets - .only(left: 8), - child: Text(e)) + Expanded( + child: Padding( + padding: + const EdgeInsets + .only( + left: 8), + child: Text(e)), + ), + IconButton( + visualDensity: + VisualDensity + .compact, + onPressed: () => cubit + .removeFromHistory( + e), + iconSize: 17, + icon: const Icon( + Icons.delete)) ]), ), )) .toList() - : _.suggestions + : state.suggestions .map((e) => InkWell( onTap: () => cubit.setSearchQuery(e), diff --git a/lib/service.dart b/lib/service.dart index 66f9b09c..d4d5681e 100644 --- a/lib/service.dart +++ b/lib/service.dart @@ -8,7 +8,6 @@ import 'package:invidious/channels/models/channel_sort_by.dart'; import 'package:invidious/extensions.dart'; import 'package:invidious/globals.dart'; import 'package:invidious/playlists/models/playlist.dart'; -import 'package:invidious/search/models/db/search_history_item.dart'; import 'package:invidious/search/models/search_date.dart'; import 'package:invidious/search/models/search_duration.dart'; import 'package:invidious/search/models/search_results.dart'; @@ -293,12 +292,6 @@ class Service { } log.info(results); - if (query.isNotEmpty && - db.getSettings(useSearchHistorySettingName)?.value == 'true') { - await db.addToSearchHistory(SearchHistoryItem( - query, (DateTime.now().millisecondsSinceEpoch / 1000).round())); - } - results.videos = (await postProcessVideos(results.videos)).cast(); return results; } diff --git a/lib/utils/file_db.dart b/lib/utils/file_db.dart index e32f99d3..bf4637eb 100644 --- a/lib/utils/file_db.dart +++ b/lib/utils/file_db.dart @@ -462,6 +462,12 @@ class FileDB extends IDbClient { upsertServer(Server server) async { await super.upsertServer(server); } + + @override + Future deleteFromSearchHistory(String search) { + // TODO: implement deleteFromSearchHistory + throw UnimplementedError(); + } } @JsonSerializable() diff --git a/lib/utils/interfaces/db.dart b/lib/utils/interfaces/db.dart index d109ccbb..0214ac9f 100644 --- a/lib/utils/interfaces/db.dart +++ b/lib/utils/interfaces/db.dart @@ -126,4 +126,6 @@ abstract class IDbClient { DeArrowCache? getDeArrowCache(String videoId); Future upsertDeArrowCache(DeArrowCache cache); + + Future deleteFromSearchHistory(String search); } diff --git a/lib/utils/sembast_sqflite_database.dart b/lib/utils/sembast_sqflite_database.dart index 8a34d566..94df8627 100644 --- a/lib/utils/sembast_sqflite_database.dart +++ b/lib/utils/sembast_sqflite_database.dart @@ -83,6 +83,11 @@ class SembastSqfDb extends IDbClient { await clearExcessSearchHistory(); } + @override + Future deleteFromSearchHistory(String item) async { + await searchHistoryStore.record(item).delete(db); + } + @override Future cleanOldLogs() async { // TODO: implement cleanOldLogs @@ -209,10 +214,12 @@ class SembastSqfDb extends IDbClient { @override List getSearchHistory() { - return searchHistoryStore + var list = searchHistoryStore .findSync(db) - .map((e) => SearchHistoryItem.fromJson(e.value).search) + .map((e) => SearchHistoryItem.fromJson(e.value)) .toList(); + list.sort((a, b) => b.time.compareTo(a.time)); + return list.map((e) => e.search).toList(); } @override diff --git a/pubspec.yaml b/pubspec.yaml index daeb3f61..caaaf26c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: invidious -version: 1.19.4+4049 +version: 1.19.5+4050 publish_to: none description: A new Flutter project. environment: