From 46149d196570d80d113dc052f3d41f0bbac71119 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Wed, 2 Jul 2025 01:41:41 +0200 Subject: [PATCH] automatic find in page for search results --- .../features/geckoview/domain/providers.dart | 10 +- .../geckoview/domain/providers.g.dart | 2 +- .../features/browser/domain/providers.dart | 28 ++- .../features/browser/domain/providers.g.dart | 4 +- .../presentation/controllers/tree_view.g.dart | 2 +- .../presentation/dialogs/tab_tree.dart | 1 + .../browser/presentation/screens/browser.dart | 8 +- .../browser_modules/bottom_app_bar.dart | 7 +- .../widgets/sheets/view_tabs.dart | 7 +- .../presentation/widgets/tab_preview.dart | 11 ++ .../controllers/find_in_page.dart | 14 +- .../controllers/find_in_page.g.dart | 161 ++++++++++++++++-- .../presentation/widgets/find_in_page.dart | 18 +- .../search/domain/entities/tab_preview.dart | 4 + .../widgets/search_modules/tab_search.dart | 9 + .../tabs/data/entities/tab_entity.dart | 16 +- .../tabs/domain/repositories/tab_search.dart | 33 ++-- .../domain/repositories/tab_search.g.dart | 27 ++- 18 files changed, 292 insertions(+), 70 deletions(-) diff --git a/app/lib/features/geckoview/domain/providers.dart b/app/lib/features/geckoview/domain/providers.dart index ce3c40fd..f5d964b4 100644 --- a/app/lib/features/geckoview/domain/providers.dart +++ b/app/lib/features/geckoview/domain/providers.dart @@ -9,6 +9,7 @@ import 'package:weblibre/core/logger.dart'; import 'package:weblibre/core/providers/router.dart'; import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; +import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart'; @@ -50,9 +51,12 @@ GeckoSelectionActionService selectionActionService(Ref ref) { } }), FindInPageAction((text) async { - await ref - .read(findInPageControllerProvider.notifier) - .findAll(text: text); + final tabId = ref.read(selectedTabProvider); + if (tabId != null) { + await ref + .read(findInPageControllerProvider(tabId).notifier) + .findAll(text: text); + } }), ShareAction((text) async { await SharePlus.instance.share(ShareParams(text: text)); diff --git a/app/lib/features/geckoview/domain/providers.g.dart b/app/lib/features/geckoview/domain/providers.g.dart index d924c5e6..e8888022 100644 --- a/app/lib/features/geckoview/domain/providers.g.dart +++ b/app/lib/features/geckoview/domain/providers.g.dart @@ -7,7 +7,7 @@ part of 'providers.dart'; // ************************************************************************** String _$selectionActionServiceHash() => - r'a3682b3f97d9798aadbca3be882d5be74085b762'; + r'ab6aeb105020a16ed24911acd73ba8553e5b3905'; /// See also [selectionActionService]. @ProviderFor(selectionActionService) diff --git a/app/lib/features/geckoview/features/browser/domain/providers.dart b/app/lib/features/geckoview/features/browser/domain/providers.dart index a7297c90..d3ed6579 100644 --- a/app/lib/features/geckoview/features/browser/domain/providers.dart +++ b/app/lib/features/geckoview/features/browser/domain/providers.dart @@ -1,4 +1,5 @@ import 'package:fast_equatable/fast_equatable.dart'; +import 'package:nullability/nullability.dart'; import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:weblibre/features/bangs/data/models/bang_data.dart'; @@ -98,9 +99,16 @@ EquatableValue> seamlessFilteredTabEntities( .watch( tabSearchRepositoryProvider(searchPartition).select( (value) => EquatableValue( - value.valueOrNull - ?.map((tab) => SingleTabEntity(tabId: tab.id)) - .toList(), + value.valueOrNull.mapNotNull( + (result) => result.results + .map( + (tab) => SearchResultTabEntity( + tabId: tab.id, + searchQuery: result.query, + ), + ) + .toList(), + ), ), ), ) @@ -109,7 +117,7 @@ EquatableValue> seamlessFilteredTabEntities( final availableTabs = ref.watch( availableTabIdsProvider(containerFilter).select( (value) => EquatableValue( - value.value.map((tab) => SingleTabEntity(tabId: tab)).toList(), + value.value.map((tab) => DefaultTabEntity(tabId: tab)).toList(), ), ), ); @@ -148,7 +156,13 @@ EquatableValue> seamlessFilteredTabEntities( } return EquatableValue( - tabSearchResults.where((tab) => availableTabs.value.contains(tab)).toList(), + tabSearchResults + .where( + (tab) => availableTabs.value.any( + (available) => available.tabId == tab.tabId, + ), + ) + .toList(), ); } @@ -181,6 +195,7 @@ EquatableValue> seamlessFilteredTabPreviews( url: state.url, highlightedUrl: null, content: null, + sourceSearchQuery: null, ), ) .toList(), @@ -188,7 +203,7 @@ EquatableValue> seamlessFilteredTabPreviews( } return EquatableValue( - tabSearchResults + tabSearchResults.results .where((tab) => availableTabStates.value.containsKey(tab.id)) .map((tab) { return TabPreview( @@ -198,6 +213,7 @@ EquatableValue> seamlessFilteredTabPreviews( url: tab.cleanUrl ?? availableTabStates.value[tab.id]!.url, highlightedUrl: tab.url, content: tab.extractedContent ?? tab.fullContent, + sourceSearchQuery: tabSearchResults.query, ); }) .whereType() diff --git a/app/lib/features/geckoview/features/browser/domain/providers.g.dart b/app/lib/features/geckoview/features/browser/domain/providers.g.dart index 29b1ae79..32c528fc 100644 --- a/app/lib/features/geckoview/features/browser/domain/providers.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/providers.g.dart @@ -283,7 +283,7 @@ class _AvailableTabStatesProviderElement } String _$seamlessFilteredTabEntitiesHash() => - r'0ef5c14f3fa018e3417e097759817bc871be3e0d'; + r'2cd698d4e7c9d9cda9e17abbf9a99e512425e4cd'; /// See also [seamlessFilteredTabEntities]. @ProviderFor(seamlessFilteredTabEntities) @@ -456,7 +456,7 @@ class _SeamlessFilteredTabEntitiesProviderElement } String _$seamlessFilteredTabPreviewsHash() => - r'c59c2819eda4f132e1a0f5f7784ddb3b5ceec817'; + r'c50a2d0f9f3ba2bc3db7c96e74d9acdcac2e84e4'; /// See also [seamlessFilteredTabPreviews]. @ProviderFor(seamlessFilteredTabPreviews) diff --git a/app/lib/features/geckoview/features/browser/presentation/controllers/tree_view.g.dart b/app/lib/features/geckoview/features/browser/presentation/controllers/tree_view.g.dart index b5de5332..856b4e10 100644 --- a/app/lib/features/geckoview/features/browser/presentation/controllers/tree_view.g.dart +++ b/app/lib/features/geckoview/features/browser/presentation/controllers/tree_view.g.dart @@ -7,7 +7,7 @@ part of 'tree_view.dart'; // ************************************************************************** String _$treeViewControllerHash() => - r'53da9cc3cc89eab2fee4a3aaf4feccc5ecbfeb20'; + r'1fac2a133d369e3bd3f8f75cdd2d0c862a6ab942'; /// See also [TreeViewController]. @ProviderFor(TreeViewController) diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart index 6a976ac7..69892929 100644 --- a/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart +++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart @@ -172,6 +172,7 @@ class TabTreeDialog extends HookConsumerWidget { .read(bottomSheetControllerProvider.notifier) .dismiss(); }, + sourceSearchQuery: null, ), ); }, diff --git a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart index 3c1dce89..7b9995ad 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart @@ -12,6 +12,7 @@ import 'package:weblibre/extensions/media_query.dart'; import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart'; import 'package:weblibre/features/geckoview/domain/controllers/overlay.dart'; import 'package:weblibre/features/geckoview/domain/providers.dart'; +import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; @@ -311,7 +312,12 @@ class _BrowserView extends StatelessWidget { ), ); - return FindInPageWidget(padding: value); + final tabId = ref.watch(selectedTabProvider); + if (tabId == null) { + return const SizedBox.shrink(); + } + + return FindInPageWidget(tabId: tabId, padding: value); }, ), ), diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart index a034a134..fe08951f 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart @@ -286,7 +286,12 @@ class BrowserBottomAppBar extends HookConsumerWidget { if (selectedTabId != null) MenuItemButton( onPressed: () { - ref.read(findInPageControllerProvider.notifier).show(); + final tabId = ref.read(selectedTabProvider); + if (tabId != null) { + ref + .read(findInPageControllerProvider(tabId).notifier) + .show(); + } }, leadingIcon: const Icon(Icons.search), child: const Text('Find in page'), diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart index cfcb5bdf..6063d8a4 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart @@ -314,13 +314,18 @@ class ViewTabsSheetWidget extends HookConsumerWidget { final tabs = useMemoized(() { return filteredTabIds.value - .whereType() + .where((entity) => entity is! TabTreeEntity) .mapIndexed((index, entity) { final child = Consumer( child: SingleTabPreview( tabId: entity.tabId, activeTabId: activeTab, onClose: onClose, + sourceSearchQuery: switch (entity) { + DefaultTabEntity() => null, + SearchResultTabEntity() => entity.searchQuery, + TabTreeEntity() => throw UnimplementedError(), + }, ), builder: (context, ref, child) { final dragData = ref.watch( diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_preview.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_preview.dart index babd9486..6ec159b8 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_preview.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_preview.dart @@ -10,6 +10,8 @@ import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart'; +import 'package:weblibre/features/geckoview/features/find_in_page/domain/entities/find_in_page_state.dart'; +import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_entity.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart'; @@ -194,6 +196,7 @@ class TabPreview extends HookWidget { class SingleTabPreview extends HookConsumerWidget { final String tabId; final String? activeTabId; + final String? sourceSearchQuery; final void Function() onClose; @@ -201,6 +204,7 @@ class SingleTabPreview extends HookConsumerWidget { required this.tabId, required this.activeTabId, required this.onClose, + required this.sourceSearchQuery, }) : super(key: ValueKey(tabId)); @override @@ -219,6 +223,13 @@ class SingleTabPreview extends HookConsumerWidget { //Close first to avoid rebuilds onClose(); await ref.read(tabRepositoryProvider.notifier).selectTab(tab.id); + if (sourceSearchQuery.isNotEmpty && + ref.read(findInPageControllerProvider(tabId)) == + FindInPageState.hidden()) { + await ref + .read(findInPageControllerProvider(tabId).notifier) + .findAll(text: sourceSearchQuery!); + } } else { onClose(); } diff --git a/app/lib/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart b/app/lib/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart index f11a81df..bd051e51 100644 --- a/app/lib/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart +++ b/app/lib/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart @@ -1,14 +1,13 @@ import 'package:nullability/nullability.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:weblibre/core/logger.dart'; -import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; import 'package:weblibre/features/geckoview/features/find_in_page/domain/entities/find_in_page_state.dart'; import 'package:weblibre/features/geckoview/features/find_in_page/domain/repositories/find_in_page.dart'; part 'find_in_page.g.dart'; -@Riverpod() +@Riverpod(keepAlive: true) class FindInPageController extends _$FindInPageController { void show() { state = state.copyWith.visible(true); @@ -20,7 +19,6 @@ class FindInPageController extends _$FindInPageController { } Future findAll({required String text}) { - final tabId = ref.read(selectedTabProvider); final service = ref.read(findInPageRepositoryProvider(tabId).notifier); state = FindInPageState(visible: true, lastSearchText: text); @@ -29,7 +27,6 @@ class FindInPageController extends _$FindInPageController { } Future findNext({required String fallbackText, bool forward = true}) { - final tabId = ref.read(selectedTabProvider); final service = ref.read(findInPageRepositoryProvider(tabId).notifier); final hasMatches = @@ -45,17 +42,16 @@ class FindInPageController extends _$FindInPageController { } Future clearMatches() { - final tabId = ref.read(selectedTabProvider); final service = ref.read(findInPageRepositoryProvider(tabId).notifier); return service.clearMatches(); } @override - FindInPageState build() { + FindInPageState build(String tabId) { ref.listen( fireImmediately: true, - selectedTabStateProvider, + tabStateProvider(tabId), (previous, next) async { //Ensure state is already initialized if (stateOrNull != null) { @@ -63,10 +59,8 @@ class FindInPageController extends _$FindInPageController { if (previous != null && next != null) { final loadingOrReloading = previous.isLoading == true && next.isLoading == false; - final tabSwitchWithoutResults = - previous.id != next.id && !next.findResultState.hasMatches; - if (loadingOrReloading || tabSwitchWithoutResults) { + if (loadingOrReloading) { await findAll(text: state.lastSearchText!); } } diff --git a/app/lib/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.g.dart b/app/lib/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.g.dart index 5a6ff99c..cfe8a5b4 100644 --- a/app/lib/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.g.dart +++ b/app/lib/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.g.dart @@ -7,21 +7,158 @@ part of 'find_in_page.dart'; // ************************************************************************** String _$findInPageControllerHash() => - r'fd335be9345b405856cf2aa68312d4d1535d8c5e'; + r'da72e38225809af5c78a372e949a80207d7ce7c9'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$FindInPageController + extends BuildlessNotifier { + late final String tabId; + + FindInPageState build(String tabId); +} /// See also [FindInPageController]. @ProviderFor(FindInPageController) -final findInPageControllerProvider = - AutoDisposeNotifierProvider.internal( - FindInPageController.new, - name: r'findInPageControllerProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$findInPageControllerHash, - dependencies: null, - allTransitiveDependencies: null, - ); +const findInPageControllerProvider = FindInPageControllerFamily(); + +/// See also [FindInPageController]. +class FindInPageControllerFamily extends Family { + /// See also [FindInPageController]. + const FindInPageControllerFamily(); + + /// See also [FindInPageController]. + FindInPageControllerProvider call(String tabId) { + return FindInPageControllerProvider(tabId); + } + + @override + FindInPageControllerProvider getProviderOverride( + covariant FindInPageControllerProvider provider, + ) { + return call(provider.tabId); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'findInPageControllerProvider'; +} + +/// See also [FindInPageController]. +class FindInPageControllerProvider + extends NotifierProviderImpl { + /// See also [FindInPageController]. + FindInPageControllerProvider(String tabId) + : this._internal( + () => FindInPageController()..tabId = tabId, + from: findInPageControllerProvider, + name: r'findInPageControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$findInPageControllerHash, + dependencies: FindInPageControllerFamily._dependencies, + allTransitiveDependencies: + FindInPageControllerFamily._allTransitiveDependencies, + tabId: tabId, + ); + + FindInPageControllerProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.tabId, + }) : super.internal(); + + final String tabId; + + @override + FindInPageState runNotifierBuild(covariant FindInPageController notifier) { + return notifier.build(tabId); + } + + @override + Override overrideWith(FindInPageController Function() create) { + return ProviderOverride( + origin: this, + override: FindInPageControllerProvider._internal( + () => create()..tabId = tabId, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + tabId: tabId, + ), + ); + } + + @override + NotifierProviderElement + createElement() { + return _FindInPageControllerProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is FindInPageControllerProvider && other.tabId == tabId; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, tabId.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin FindInPageControllerRef on NotifierProviderRef { + /// The parameter `tabId` of this provider. + String get tabId; +} + +class _FindInPageControllerProviderElement + extends NotifierProviderElement + with FindInPageControllerRef { + _FindInPageControllerProviderElement(super.provider); + + @override + String get tabId => (origin as FindInPageControllerProvider).tabId; +} -typedef _$FindInPageController = AutoDisposeNotifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/app/lib/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart b/app/lib/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart index 739f9ac0..712cabac 100644 --- a/app/lib/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart +++ b/app/lib/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart @@ -5,13 +5,14 @@ import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart'; class FindInPageWidget extends HookConsumerWidget { + final String tabId; final EdgeInsetsGeometry padding; - const FindInPageWidget({this.padding = EdgeInsets.zero}); + const FindInPageWidget({required this.tabId, this.padding = EdgeInsets.zero}); @override Widget build(BuildContext context, WidgetRef ref) { - final findInPageState = ref.watch(findInPageControllerProvider); + final findInPageState = ref.watch(findInPageControllerProvider(tabId)); final searchResult = ref.watch( selectedTabStateProvider.select((state) => state?.findResultState), ); @@ -43,11 +44,11 @@ class FindInPageWidget extends HookConsumerWidget { onSubmitted: (value) async { if (value == '') { await ref - .read(findInPageControllerProvider.notifier) + .read(findInPageControllerProvider(tabId).notifier) .clearMatches(); } else { await ref - .read(findInPageControllerProvider.notifier) + .read(findInPageControllerProvider(tabId).notifier) .findAll(text: value); } }, @@ -62,7 +63,7 @@ class FindInPageWidget extends HookConsumerWidget { icon: const Icon(Icons.arrow_upward), onPressed: () async { await ref - .read(findInPageControllerProvider.notifier) + .read(findInPageControllerProvider(tabId).notifier) .findNext( forward: false, fallbackText: textController.text, @@ -73,14 +74,17 @@ class FindInPageWidget extends HookConsumerWidget { icon: const Icon(Icons.arrow_downward), onPressed: () async { await ref - .read(findInPageControllerProvider.notifier) + .read(findInPageControllerProvider(tabId).notifier) .findNext(fallbackText: textController.text); }, ), IconButton( icon: const Icon(Icons.clear), onPressed: () async { - await ref.read(findInPageControllerProvider.notifier).hide(); + await ref + .read(findInPageControllerProvider(tabId).notifier) + .hide(); + textController.clear(); focusNode.requestFocus(); }, diff --git a/app/lib/features/geckoview/features/search/domain/entities/tab_preview.dart b/app/lib/features/geckoview/features/search/domain/entities/tab_preview.dart index 8878d0f8..5624a42f 100644 --- a/app/lib/features/geckoview/features/search/domain/entities/tab_preview.dart +++ b/app/lib/features/geckoview/features/search/domain/entities/tab_preview.dart @@ -12,6 +12,8 @@ class TabPreview with FastEquatable { final String? content; + final String? sourceSearchQuery; + TabPreview({ required this.id, required this.title, @@ -19,6 +21,7 @@ class TabPreview with FastEquatable { required this.url, required this.highlightedUrl, required this.content, + required this.sourceSearchQuery, }); @override @@ -29,5 +32,6 @@ class TabPreview with FastEquatable { url, highlightedUrl, content, + sourceSearchQuery, ]; } 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 97953086..e37e5259 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 @@ -9,6 +9,8 @@ import 'package:sliver_tools/sliver_tools.dart'; import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart'; import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart'; +import 'package:weblibre/features/geckoview/features/find_in_page/domain/entities/find_in_page_state.dart'; +import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/entities/container_filter.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart'; @@ -139,6 +141,13 @@ class TabSearch extends HookConsumerWidget { await ref .read(tabRepositoryProvider.notifier) .selectTab(result.id); + if (result.sourceSearchQuery.isNotEmpty && + ref.read(findInPageControllerProvider(result.id)) == + FindInPageState.hidden()) { + await ref + .read(findInPageControllerProvider(result.id).notifier) + .findAll(text: result.sourceSearchQuery!); + } if (context.mounted) { ref.read(bottomSheetControllerProvider.notifier).dismiss(); diff --git a/app/lib/features/geckoview/features/tabs/data/entities/tab_entity.dart b/app/lib/features/geckoview/features/tabs/data/entities/tab_entity.dart index 94da0c43..13d07229 100644 --- a/app/lib/features/geckoview/features/tabs/data/entities/tab_entity.dart +++ b/app/lib/features/geckoview/features/tabs/data/entities/tab_entity.dart @@ -4,16 +4,28 @@ sealed class TabEntity with FastEquatable { String get tabId; } -class SingleTabEntity extends TabEntity { +class DefaultTabEntity extends TabEntity { @override final String tabId; - SingleTabEntity({required this.tabId}); + DefaultTabEntity({required this.tabId}); @override List get hashParameters => [tabId]; } +class SearchResultTabEntity extends TabEntity { + @override + final String tabId; + + final String searchQuery; + + SearchResultTabEntity({required this.tabId, required this.searchQuery}); + + @override + List get hashParameters => [tabId, searchQuery]; +} + class TabTreeEntity extends TabEntity { @override final String tabId; diff --git a/app/lib/features/geckoview/features/tabs/domain/repositories/tab_search.dart b/app/lib/features/geckoview/features/tabs/domain/repositories/tab_search.dart index 1b5ffc65..8b69c106 100644 --- a/app/lib/features/geckoview/features/tabs/domain/repositories/tab_search.dart +++ b/app/lib/features/geckoview/features/tabs/domain/repositories/tab_search.dart @@ -18,26 +18,31 @@ class TabSearchRepository extends _$TabSearchRepository { String ellipsis = '…', }) async { if (input.isNotEmpty) { - state = await AsyncValue.guard( - () => ref - .read(tabDatabaseProvider) - .tabDao - .queryTabs( - matchPrefix: matchPrefix, - matchSuffix: matchSuffix, - ellipsis: ellipsis, - snippetLength: snippetLength, - searchString: input, - ) - .get(), - ); + state = await AsyncValue.guard(() async { + return ( + query: input, + results: await ref + .read(tabDatabaseProvider) + .tabDao + .queryTabs( + matchPrefix: matchPrefix, + matchSuffix: matchSuffix, + ellipsis: ellipsis, + snippetLength: snippetLength, + searchString: input, + ) + .get(), + ); + }); } else { state = const AsyncValue.data(null); } } @override - Future?> build(TabSearchPartition partition) { + Future<({String query, List results})?> build( + TabSearchPartition partition, + ) { return Future.value(); } } diff --git a/app/lib/features/geckoview/features/tabs/domain/repositories/tab_search.g.dart b/app/lib/features/geckoview/features/tabs/domain/repositories/tab_search.g.dart index 3a73c5e9..a2dde486 100644 --- a/app/lib/features/geckoview/features/tabs/domain/repositories/tab_search.g.dart +++ b/app/lib/features/geckoview/features/tabs/domain/repositories/tab_search.g.dart @@ -7,7 +7,7 @@ part of 'tab_search.dart'; // ************************************************************************** String _$tabSearchRepositoryHash() => - r'd442c384a5a867f4ff28465f524a6ef67521d5ae'; + r'ac2381c692b9caf93f26f302d15e0160c098917a'; /// Copied from Dart SDK class _SystemHash { @@ -31,10 +31,15 @@ class _SystemHash { } abstract class _$TabSearchRepository - extends BuildlessAutoDisposeAsyncNotifier?> { + extends + BuildlessAutoDisposeAsyncNotifier< + ({String query, List results})? + > { late final TabSearchPartition partition; - FutureOr?> build(TabSearchPartition partition); + FutureOr<({String query, List results})?> build( + TabSearchPartition partition, + ); } /// See also [TabSearchRepository]. @@ -43,7 +48,8 @@ const tabSearchRepositoryProvider = TabSearchRepositoryFamily(); /// See also [TabSearchRepository]. class TabSearchRepositoryFamily - extends Family?>> { + extends + Family results})?>> { /// See also [TabSearchRepository]. const TabSearchRepositoryFamily(); @@ -79,7 +85,7 @@ class TabSearchRepositoryProvider extends AutoDisposeAsyncNotifierProviderImpl< TabSearchRepository, - List? + ({String query, List results})? > { /// See also [TabSearchRepository]. TabSearchRepositoryProvider(TabSearchPartition partition) @@ -109,7 +115,7 @@ class TabSearchRepositoryProvider final TabSearchPartition partition; @override - FutureOr?> runNotifierBuild( + FutureOr<({String query, List results})?> runNotifierBuild( covariant TabSearchRepository notifier, ) { return notifier.build(partition); @@ -134,7 +140,7 @@ class TabSearchRepositoryProvider @override AutoDisposeAsyncNotifierProviderElement< TabSearchRepository, - List? + ({String query, List results})? > createElement() { return _TabSearchRepositoryProviderElement(this); @@ -157,7 +163,10 @@ class TabSearchRepositoryProvider @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element mixin TabSearchRepositoryRef - on AutoDisposeAsyncNotifierProviderRef?> { + on + AutoDisposeAsyncNotifierProviderRef< + ({String query, List results})? + > { /// The parameter `partition` of this provider. TabSearchPartition get partition; } @@ -166,7 +175,7 @@ class _TabSearchRepositoryProviderElement extends AutoDisposeAsyncNotifierProviderElement< TabSearchRepository, - List? + ({String query, List results})? > with TabSearchRepositoryRef { _TabSearchRepositoryProviderElement(super.provider);