diff --git a/app/lib/features/geckoview/features/history/domain/repositories/history.g.dart b/app/lib/features/geckoview/features/history/domain/repositories/history.g.dart index 37001d9e..32be288d 100644 --- a/app/lib/features/geckoview/features/history/domain/repositories/history.g.dart +++ b/app/lib/features/geckoview/features/history/domain/repositories/history.g.dart @@ -41,7 +41,7 @@ final class HistoryRepositoryProvider } } -String _$historyRepositoryHash() => r'19f883fdb644e34bc89483b4a8ddb60f54c52114'; +String _$historyRepositoryHash() => r'414b68ec6be3cc2eca3681cbc00990e53e6127ce'; abstract class _$HistoryRepository extends $Notifier { void build(); diff --git a/app/lib/features/geckoview/features/search/domain/providers/search_module_order.g.dart b/app/lib/features/geckoview/features/search/domain/providers/search_module_order.g.dart index 39fd77f0..1cc176e7 100644 --- a/app/lib/features/geckoview/features/search/domain/providers/search_module_order.g.dart +++ b/app/lib/features/geckoview/features/search/domain/providers/search_module_order.g.dart @@ -58,7 +58,7 @@ final class SearchModuleOrderProvider } } -String _$searchModuleOrderHash() => r'7e7822de86688cf87dff1c9991bc2551dc44e002'; +String _$searchModuleOrderHash() => r'eeea86534497671a12c1383cbf251a8df797c1fc'; final class SearchModuleOrderFamily extends $Family with diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/empty_state/top_sites_section.dart b/app/lib/features/geckoview/features/search/presentation/widgets/empty_state/top_sites_section.dart index 67902682..33329a46 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/empty_state/top_sites_section.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/empty_state/top_sites_section.dart @@ -378,7 +378,7 @@ class _TopSiteGridTile extends StatelessWidget { const textLines = 2; const gap = 6.0; const minIconSize = 18.0; - const textHeightPadding = 2.0; + const textHeightPadding = 16.0; final minTextHeight = lineHeight + textHeightPadding; final maxTextHeight = lineHeight * textLines + textHeightPadding; diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/search_field.dart b/app/lib/features/geckoview/features/search/presentation/widgets/search_field.dart index 6d0c2160..8688c46b 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/search_field.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/search_field.dart @@ -26,6 +26,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:weblibre/features/bangs/data/models/bang_data.dart'; import 'package:weblibre/features/geckoview/features/search/domain/providers/engine_suggestions.dart'; import 'package:weblibre/features/user/domain/providers.dart'; +import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart'; import 'package:weblibre/presentation/widgets/auto_suggest_text_field.dart'; import 'package:weblibre/presentation/widgets/qr_scanner_button.dart'; import 'package:weblibre/presentation/widgets/speech_to_text_button.dart'; @@ -79,26 +80,31 @@ class SearchField extends HookConsumerWidget { final lastText = useRef(textEditingController.text); if (showSuggestions) { - useOnListenableChange(textEditingController, () async { - if (textEditingController.text.isEmpty) { - suggestion.value = null; - } else if (textEditingController.text != lastText.value) { - final isDeleting = - textEditingController.text.length < lastText.value.length; - - if (isDeleting) { + useOnListenableChangeSelector( + textEditingController, + () => textEditingController.text, + () async { + final text = textEditingController.text; + if (text.isEmpty) { suggestion.value = null; } else { - final result = await ref - .read(engineSuggestionsProvider.notifier) - .getAutocompleteSuggestion(textEditingController.text); + final isDeleting = text.length < lastText.value.length; - suggestion.value = result; + if (isDeleting) { + suggestion.value = null; + } else { + await ref + .read(engineSuggestionsProvider.notifier) + .getAutocompleteSuggestion(text) + .then((result) { + suggestion.value = result; + }); + } } - } - lastText.value = textEditingController.text; - }); + lastText.value = text; + }, + ); } return AutoSuggestTextField( diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/bookmark_search.dart b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/bookmark_search.dart index a010daa2..06ce6f0a 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/bookmark_search.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/bookmark_search.dart @@ -19,11 +19,11 @@ */ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:weblibre/features/geckoview/features/bookmarks/domain/providers/bookmarks.dart'; import 'package:weblibre/features/geckoview/features/search/domain/providers/search_modules_view.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart'; +import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart'; import 'package:weblibre/presentation/widgets/uri_breadcrumb.dart'; import 'package:weblibre/presentation/widgets/url_icon.dart'; @@ -42,11 +42,15 @@ class BookmarkSearch extends HookConsumerWidget { final bookmarkResults = ref.watch(bookmarkSearchResultsProvider); final totalResults = bookmarkResults.length; - useOnListenableChange(searchTextListenable, () async { - await ref - .read(bookmarkSearchResultsProvider.notifier) - .search(searchTextListenable.value.text); - }); + useOnListenableChangeSelector( + searchTextListenable, + () => searchTextListenable.value.text, + () async { + await ref + .read(bookmarkSearchResultsProvider.notifier) + .search(searchTextListenable.value.text); + }, + ); if (bookmarkResults.isEmpty) { return const SliverToBoxAdapter(child: SizedBox.shrink()); diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart index 9cbe1ba3..1958ff37 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/feed_search.dart @@ -19,7 +19,6 @@ */ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nullability/nullability.dart'; import 'package:skeletonizer/skeletonizer.dart'; @@ -33,6 +32,7 @@ import 'package:weblibre/features/web_feed/data/models/feed_link.dart'; import 'package:weblibre/features/web_feed/domain/providers.dart'; import 'package:weblibre/features/web_feed/extensions/atom.dart'; import 'package:weblibre/features/web_feed/extensions/feed_article.dart'; +import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; import 'package:weblibre/presentation/widgets/url_icon.dart'; import 'package:weblibre/utils/text_highlight.dart'; @@ -52,17 +52,21 @@ class FeedSearch extends HookConsumerWidget { final articlesAsync = ref.watch(articleSearchProvider(null)); final totalResults = articlesAsync.value?.length ?? 0; - useOnListenableChange(searchTextNotifier, () async { - await ref - .read(articleSearchProvider(null).notifier) - .search( - searchTextNotifier.value.text, - // ignore: avoid_redundant_argument_values dont break things - matchPrefix: _matchPrefix, - // ignore: avoid_redundant_argument_values dont break things - matchSuffix: _matchSuffix, - ); - }); + useOnListenableChangeSelector( + searchTextNotifier, + () => searchTextNotifier.value.text, + () async { + await ref + .read(articleSearchProvider(null).notifier) + .search( + searchTextNotifier.value.text, + // ignore: avoid_redundant_argument_values dont break things + matchPrefix: _matchPrefix, + // ignore: avoid_redundant_argument_values dont break things + matchSuffix: _matchSuffix, + ); + }, + ); if (articlesAsync.hasValue && (articlesAsync.value.isEmpty)) { return const SliverToBoxAdapter(child: SizedBox.shrink()); diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/fixed_search_suggestions.dart b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/fixed_search_suggestions.dart index 0fa1651f..f4f0a20e 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/fixed_search_suggestions.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/fixed_search_suggestions.dart @@ -25,6 +25,7 @@ import 'package:nullability/nullability.dart'; import 'package:weblibre/extensions/uri.dart'; import 'package:weblibre/features/bangs/data/models/bang_data.dart'; import 'package:weblibre/features/geckoview/features/search/domain/providers/search_suggestions.dart'; +import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart'; import 'package:weblibre/utils/uri_parser.dart' as uri_parser; class FixedSearchTermSuggestions extends HookConsumerWidget { @@ -59,13 +60,17 @@ class FixedSearchTermSuggestions extends HookConsumerWidget { final searchSuggestions = ref.watch(searchSuggestionsProvider()); - useOnListenableChange(searchTextController, () { - if (ref.exists(searchSuggestionsProvider())) { - ref - .read(searchSuggestionsProvider().notifier) - .addQuery(searchTextController.text); - } - }); + useOnListenableChangeSelector( + searchTextController, + () => searchTextController.text, + () { + if (ref.exists(searchSuggestionsProvider())) { + ref + .read(searchSuggestionsProvider().notifier) + .addQuery(searchTextController.text); + } + }, + ); final prioritizedSuggestions = isSuggestableText ? [ diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/full_search_suggestions.dart b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/full_search_suggestions.dart index c1402f64..13cf89b5 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/full_search_suggestions.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/full_search_suggestions.dart @@ -30,6 +30,7 @@ import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; import 'package:weblibre/features/bangs/domain/repositories/data.dart'; import 'package:weblibre/features/geckoview/features/search/domain/providers/search_suggestions.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/widgets/smart_bang_selector.dart'; +import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart'; class FullSearchTermSuggestions extends HookConsumerWidget { final TextEditingController searchTextController; @@ -62,11 +63,15 @@ class FullSearchTermSuggestions extends HookConsumerWidget { persistedBoolProvider(PersistedBoolKey.searchSuggestionsExpanded), ); - useOnListenableChange(searchTextController, () { - ref - .read(searchSuggestionsProvider().notifier) - .addQuery(searchTextController.text); - }); + useOnListenableChangeSelector( + searchTextController, + () => searchTextController.text, + () { + ref + .read(searchSuggestionsProvider().notifier) + .addQuery(searchTextController.text); + }, + ); final showHistory = !searchTextIsNotEmpty && (searchHistory.value.isNotEmpty); diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart index d0a9743e..fd329269 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/history_suggestions.dart @@ -29,6 +29,7 @@ import 'package:weblibre/features/geckoview/features/search/domain/providers/sea import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/search_module_section.dart'; import 'package:weblibre/features/geckoview/utils/image_helper.dart'; import 'package:weblibre/presentation/hooks/cached_future.dart'; +import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; import 'package:weblibre/presentation/widgets/safe_raw_image.dart'; import 'package:weblibre/presentation/widgets/uri_breadcrumb.dart'; @@ -48,13 +49,17 @@ class HistorySuggestions extends HookConsumerWidget { final historySuggestionsAsync = ref.watch(engineHistorySuggestionsProvider); final totalResults = historySuggestionsAsync.value?.length ?? 0; - useOnListenableChange(searchTextListenable, () async { - if (ref.exists(engineSuggestionsProvider)) { - await ref - .watch(engineSuggestionsProvider.notifier) - .addQuery(searchTextListenable.value.text); - } - }); + useOnListenableChangeSelector( + searchTextListenable, + () => searchTextListenable.value.text, + () async { + if (ref.exists(engineSuggestionsProvider)) { + await ref + .read(engineSuggestionsProvider.notifier) + .addQuery(searchTextListenable.value.text); + } + }, + ); if (historySuggestionsAsync.hasValue && (historySuggestionsAsync.value.isEmpty)) { diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart index 8920a00c..e037133a 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/tab_search.dart @@ -40,6 +40,7 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selec import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab_search.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_chips.dart'; +import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart'; import 'package:weblibre/presentation/widgets/safe_raw_image.dart'; import 'package:weblibre/presentation/widgets/uri_breadcrumb.dart'; import 'package:weblibre/presentation/widgets/url_icon.dart'; @@ -109,21 +110,27 @@ class TabSearch extends HookConsumerWidget { }), ); - useOnListenableChange(searchTextListenable, () async { - if (ref.exists(tabSearchRepositoryProvider(TabSearchPartition.search))) { - await ref - .read( - tabSearchRepositoryProvider(TabSearchPartition.search).notifier, - ) - .addQuery( - searchTextListenable.value.text, - // ignore: avoid_redundant_argument_values dont break things - matchPrefix: _matchPrefix, - // ignore: avoid_redundant_argument_values dont break things - matchSuffix: _matchSuffix, - ); - } - }); + useOnListenableChangeSelector( + searchTextListenable, + () => searchTextListenable.value.text, + () async { + if (ref.exists( + tabSearchRepositoryProvider(TabSearchPartition.search), + )) { + await ref + .read( + tabSearchRepositoryProvider(TabSearchPartition.search).notifier, + ) + .addQuery( + searchTextListenable.value.text, + // ignore: avoid_redundant_argument_values dont break things + matchPrefix: _matchPrefix, + // ignore: avoid_redundant_argument_values dont break things + matchSuffix: _matchSuffix, + ); + } + }, + ); if (tabSearchResults.value.isEmpty) { return const SliverToBoxAdapter(child: SizedBox.shrink()); diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/smart_bang_selector.dart b/app/lib/features/geckoview/features/search/presentation/widgets/smart_bang_selector.dart index 1d105323..9289abab 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/smart_bang_selector.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/smart_bang_selector.dart @@ -30,6 +30,7 @@ import 'package:weblibre/features/bangs/domain/providers/search.dart'; import 'package:weblibre/features/bangs/domain/repositories/data.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/search/presentation/dialogs/reset_bang_dialog.dart'; +import 'package:weblibre/presentation/hooks/on_listenable_change_selector.dart'; import 'package:weblibre/presentation/widgets/selectable_chips.dart'; import 'package:weblibre/presentation/widgets/url_icon.dart'; import 'package:weblibre/utils/uri_parser.dart' as uri_parser; @@ -94,9 +95,15 @@ class SmartBangSelector extends HookConsumerWidget { final globalBangs = searchBangs.isNotEmpty ? searchBangs : frequentBangs; // Trigger search when text changes - useOnListenableChange(searchTextController, () { - ref.read(seamlessBangProvider.notifier).search(searchTextController.text); - }); + useOnListenableChangeSelector( + searchTextController, + () => searchTextController.text, + () { + ref + .read(seamlessBangProvider.notifier) + .search(searchTextController.text); + }, + ); // Determine if we should show tabs final showTabs = isEditMode && siteBangs.isNotEmpty; diff --git a/app/lib/features/geckoview/features/top_sites/domain/repositories/top_site_repository.g.dart b/app/lib/features/geckoview/features/top_sites/domain/repositories/top_site_repository.g.dart index ba9eee7d..b95dc679 100644 --- a/app/lib/features/geckoview/features/top_sites/domain/repositories/top_site_repository.g.dart +++ b/app/lib/features/geckoview/features/top_sites/domain/repositories/top_site_repository.g.dart @@ -41,7 +41,7 @@ final class TopSiteRepositoryProvider } } -String _$topSiteRepositoryHash() => r'e32ddb20e61fcd3830c4eb88704824a57e7a536a'; +String _$topSiteRepositoryHash() => r'8aa231fadabe1e115d92cd37e032f27ba8da55e6'; abstract class _$TopSiteRepository extends $Notifier { void build();