improve search flow
This commit is contained in:
+36
-15
@@ -23,7 +23,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:nullability/nullability.dart';
|
|
||||||
import 'package:weblibre/core/design/app_colors.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/entities/states/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||||
@@ -844,16 +843,27 @@ class SingleGridTabPreview extends HookConsumerWidget {
|
|||||||
depth: depth,
|
depth: depth,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (tabId != activeTabId) {
|
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
|
//Close first to avoid rebuilds
|
||||||
onClose();
|
onClose();
|
||||||
await ref.read(tabRepositoryProvider.notifier).selectTab(tabId);
|
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 {
|
} else {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
@@ -989,16 +999,27 @@ class SingleListTabPreview extends HookConsumerWidget {
|
|||||||
depth: depth,
|
depth: depth,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (tabId != activeTabId) {
|
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
|
//Close first to avoid rebuilds
|
||||||
onClose();
|
onClose();
|
||||||
await ref.read(tabRepositoryProvider.notifier).selectTab(tabId);
|
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 {
|
} else {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
|
|||||||
+23
@@ -17,6 +17,7 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@@ -268,6 +269,28 @@ class TabViewHeader extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}, [tabsReorderable, canManualReorder]);
|
}, [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 {
|
useOnListenableChange(searchTextController, () async {
|
||||||
if (ref.exists(tabSearchRepositoryProvider(TabSearchPartition.preview))) {
|
if (ref.exists(tabSearchRepositoryProvider(TabSearchPartition.preview))) {
|
||||||
await ref
|
await ref
|
||||||
|
|||||||
+17
-7
@@ -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/uri_breadcrumb.dart';
|
||||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||||
import 'package:weblibre/utils/text_highlight.dart';
|
import 'package:weblibre/utils/text_highlight.dart';
|
||||||
|
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||||
|
|
||||||
class TabSearch extends HookConsumerWidget {
|
class TabSearch extends HookConsumerWidget {
|
||||||
static const _matchPrefix = '***';
|
static const _matchPrefix = '***';
|
||||||
@@ -247,14 +248,23 @@ class TabSearch extends HookConsumerWidget {
|
|||||||
await ref
|
await ref
|
||||||
.read(tabRepositoryProvider.notifier)
|
.read(tabRepositoryProvider.notifier)
|
||||||
.selectTab(result.id);
|
.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)) ==
|
ref.read(findInPageControllerProvider(result.id)) ==
|
||||||
FindInPageState.hidden()) {
|
FindInPageState.hidden() &&
|
||||||
await ref
|
context.mounted) {
|
||||||
.read(
|
final findController = ref.read(
|
||||||
findInPageControllerProvider(result.id).notifier,
|
findInPageControllerProvider(result.id).notifier,
|
||||||
)
|
);
|
||||||
.findAll(text: result.sourceSearchQuery!);
|
ui_helper.showFindInPageSuggestion(
|
||||||
|
context,
|
||||||
|
query: query,
|
||||||
|
onFind: () => findController.findAll(text: query),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|||||||
@@ -101,6 +101,27 @@ void showInfoMessage(
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
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(
|
void showOpenedTabsFromAnotherDeviceMessage(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
int openedTabs, {
|
int openedTabs, {
|
||||||
|
|||||||
Reference in New Issue
Block a user