improve search flow

This commit is contained in:
Fabian Freund
2026-06-02 12:53:29 +02:00
parent 84ae5e71dd
commit 661e3c29d9
4 changed files with 97 additions and 22 deletions
@@ -23,7 +23,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/core/design/app_colors.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
@@ -844,16 +843,27 @@ class SingleGridTabPreview extends HookConsumerWidget {
depth: depth,
onTap: () async {
if (tabId != activeTabId) {
// Offer to locate the match within the page instead of opening
// Find in Page unprompted (see #421). Shown before onClose so it
// surfaces on the root messenger and survives the tray closing.
final query = sourceSearchQuery;
if (query != null &&
query.isNotEmpty &&
ref.read(findInPageControllerProvider(tabId)) ==
FindInPageState.hidden()) {
final findController = ref.read(
findInPageControllerProvider(tabId).notifier,
);
ui_helper.showFindInPageSuggestion(
context,
query: query,
onFind: () => findController.findAll(text: query),
);
}
//Close first to avoid rebuilds
onClose();
await ref.read(tabRepositoryProvider.notifier).selectTab(tabId);
if (sourceSearchQuery.isNotEmpty &&
ref.read(findInPageControllerProvider(tabId)) ==
FindInPageState.hidden()) {
await ref
.read(findInPageControllerProvider(tabId).notifier)
.findAll(text: sourceSearchQuery!);
}
} else {
onClose();
}
@@ -989,16 +999,27 @@ class SingleListTabPreview extends HookConsumerWidget {
depth: depth,
onTap: () async {
if (tabId != activeTabId) {
// Offer to locate the match within the page instead of opening
// Find in Page unprompted (see #421). Shown before onClose so it
// surfaces on the root messenger and survives the tray closing.
final query = sourceSearchQuery;
if (query != null &&
query.isNotEmpty &&
ref.read(findInPageControllerProvider(tabId)) ==
FindInPageState.hidden()) {
final findController = ref.read(
findInPageControllerProvider(tabId).notifier,
);
ui_helper.showFindInPageSuggestion(
context,
query: query,
onFind: () => findController.findAll(text: query),
);
}
//Close first to avoid rebuilds
onClose();
await ref.read(tabRepositoryProvider.notifier).selectTab(tabId);
if (sourceSearchQuery.isNotEmpty &&
ref.read(findInPageControllerProvider(tabId)) ==
FindInPageState.hidden()) {
await ref
.read(findInPageControllerProvider(tabId).notifier)
.findAll(text: sourceSearchQuery!);
}
} else {
onClose();
}
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'dart:convert';
import 'package:collection/collection.dart';
@@ -268,6 +269,28 @@ class TabViewHeader extends HookConsumerWidget {
return null;
}, [tabsReorderable, canManualReorder]);
// Keep the in-place tab filter in lockstep with the search field. The
// preview query lives in a provider whose lifetime is independent of this
// header and of [searchMode], so it can outlive the search UI and leave the
// tab list filtered with no visible search box (#421). Whenever we are not
// searching, drop any lingering query so all tabs are shown again.
useEffect(() {
if (!searchMode.value &&
ref.exists(
tabSearchRepositoryProvider(TabSearchPartition.preview),
)) {
unawaited(
ref
.read(
tabSearchRepositoryProvider(TabSearchPartition.preview).notifier,
)
.addQuery(''),
);
}
return null;
}, [searchMode.value]);
useOnListenableChange(searchTextController, () async {
if (ref.exists(tabSearchRepositoryProvider(TabSearchPartition.preview))) {
await ref
@@ -45,6 +45,7 @@ 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';
import 'package:weblibre/utils/text_highlight.dart';
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
class TabSearch extends HookConsumerWidget {
static const _matchPrefix = '***';
@@ -247,14 +248,23 @@ class TabSearch extends HookConsumerWidget {
await ref
.read(tabRepositoryProvider.notifier)
.selectTab(result.id);
if (result.sourceSearchQuery.isNotEmpty &&
// Offer to locate the match within the page instead of
// opening Find in Page unprompted (see #421).
final query = result.sourceSearchQuery;
if (query != null &&
query.isNotEmpty &&
ref.read(findInPageControllerProvider(result.id)) ==
FindInPageState.hidden()) {
await ref
.read(
findInPageControllerProvider(result.id).notifier,
)
.findAll(text: result.sourceSearchQuery!);
FindInPageState.hidden() &&
context.mounted) {
final findController = ref.read(
findInPageControllerProvider(result.id).notifier,
);
ui_helper.showFindInPageSuggestion(
context,
query: query,
onFind: () => findController.findAll(text: query),
);
}
if (context.mounted) {
+21
View File
@@ -101,6 +101,27 @@ void showInfoMessage(
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
void showFindInPageSuggestion(
BuildContext context, {
required String query,
required VoidCallback onFind,
Duration duration = const Duration(seconds: 5),
bool persist = false,
}) {
final snackBar = _createFloatingSnackBar(
content: Text(
'Find "${_truncateForSnackBar(query)}" on this page?',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
action: SnackBarAction(label: 'Find', onPressed: onFind),
duration: duration,
persist: persist,
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
void showOpenedTabsFromAnotherDeviceMessage(
BuildContext context,
int openedTabs, {