diff --git a/app/lib/features/bangs/presentation/widgets/site_search.dart b/app/lib/features/bangs/presentation/widgets/site_search.dart
deleted file mode 100644
index 6511ac34..00000000
--- a/app/lib/features/bangs/presentation/widgets/site_search.dart
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (c) 2024-2025 Fabian Freund.
- *
- * This file is part of WebLibre
- * (see https://weblibre.eu).
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-import 'dart:async';
-
-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:weblibre/core/routing/routes.dart';
-import 'package:weblibre/extensions/uri.dart';
-import 'package:weblibre/features/bangs/data/models/bang_data.dart';
-import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
-import 'package:weblibre/features/bangs/domain/providers/search.dart';
-import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
-import 'package:weblibre/features/geckoview/domain/providers/tab_session.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/domain/providers.dart';
-import 'package:weblibre/features/geckoview/features/search/domain/providers/search_suggestions.dart';
-import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_field.dart';
-import 'package:weblibre/features/geckoview/features/search/presentation/widgets/search_modules/fixed_search_suggestions.dart';
-import 'package:weblibre/features/user/domain/repositories/general_settings.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;
-
-class SiteSearch extends HookConsumerWidget {
- final String domain;
- final List availableBangs;
- final TextEditingController? controller;
- final bool searchInNewTab;
-
- const SiteSearch({
- required this.domain,
- required this.availableBangs,
- super.key,
- this.controller,
- this.searchInNewTab = false,
- });
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final searchTextController = controller ?? useTextEditingController();
- final searchFocusNode = useFocusNode();
-
- useOnListenableChangeSelector(
- searchFocusNode,
- () => searchFocusNode.hasFocus,
- () {
- if (searchFocusNode.hasFocus) {
- // Select all text when the field is focused
- searchTextController.selection = TextSelection(
- baseOffset: 0,
- extentOffset: searchTextController.text.length,
- );
- }
- },
- );
-
- //Request initial focus in a way our useOnListenableChangeSelector is triggered
- useEffect(() {
- //Wait for first frame then request focus
- unawaited(
- Future.delayed(const Duration(milliseconds: 1000 ~/ 60)).whenComplete(
- () {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- searchFocusNode.requestFocus();
- });
- },
- ),
- );
-
- return null;
- }, []);
-
- useOnAppLifecycleStateChange((previous, current) {
- switch (current) {
- case AppLifecycleState.detached:
- case AppLifecycleState.inactive:
- case AppLifecycleState.hidden:
- case AppLifecycleState.paused:
- //Fixes issue with disappearing keyboard after resume (even we request focus)
- searchFocusNode.unfocus();
- case AppLifecycleState.resumed:
- WidgetsBinding.instance.addPostFrameCallback((_) {
- searchFocusNode.requestFocus();
- });
- }
- });
-
- useOnListenableChange(searchTextController, () {
- if (ref.exists(searchSuggestionsProvider())) {
- ref
- .read(searchSuggestionsProvider().notifier)
- .addQuery(searchTextController.text);
- }
- });
-
- final selectedBang = ref.watch(selectedBangDataProvider(domain: domain));
- final defaultSearchBang = ref.watch(
- defaultSearchBangDataProvider.select((value) => value.value),
- );
-
- Future submitSearch(String value) async {
- if (value.isNotEmpty) {
- final bang = selectedBang ?? defaultSearchBang;
-
- final newUrl = uri_parser.tryParseUrl(value, eagerParsing: true);
-
- if (newUrl != null) {
- await ref
- .read(tabSessionProvider(tabId: null).notifier)
- .loadUrl(url: newUrl);
-
- ref.read(bottomSheetControllerProvider.notifier).requestDismiss();
- } else if (bang != null) {
- final isPrivate =
- ref.read(selectedTabStateProvider)?.isPrivate ??
- ref
- .read(generalSettingsWithDefaultsProvider)
- .defaultCreateTabType ==
- TabType.private;
-
- final searchUri = bang.getTemplateUrl(value);
-
- if (!isPrivate) {
- await ref
- .read(bangSearchProvider.notifier)
- .triggerBangSearch(bang, value);
- }
-
- if (searchInNewTab) {
- await ref
- .read(tabRepositoryProvider.notifier)
- .addTab(url: searchUri, private: isPrivate, selectTab: true);
- } else {
- await ref
- .read(tabSessionProvider(tabId: null).notifier)
- .loadUrl(url: searchUri);
- }
-
- ref.read(bottomSheetControllerProvider.notifier).requestDismiss();
- }
- }
- }
-
- return Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Visibility(
- visible: availableBangs.isNotEmpty,
- child: SizedBox(
- height: 48,
- width: double.maxFinite,
- child: SelectableChips(
- itemId: (bang) => bang.trigger,
- itemAvatar: (bang) =>
- UrlIcon([bang.getDefaultUrl()], iconSize: 20),
- itemLabel: (bang) => Text(bang.websiteName),
- itemTooltip: (bang) => bang.trigger,
- availableItems: availableBangs,
- selectedItem: selectedBang,
- onSelected: (bang) {
- if (selectedBang == null) {
- final hasSupportedScheme =
- uri_parser
- .tryParseUrl(searchTextController.text)
- .mapNotNull((uri) => uri.hasSupportedScheme) ??
- false;
-
- if (hasSupportedScheme) {
- searchTextController.clear();
- }
- }
-
- ref
- .read(selectedBangTriggerProvider(domain: domain).notifier)
- .setTrigger(bang.toKey());
- },
- onDeleted: (bang) {
- if (ref.read(selectedBangTriggerProvider(domain: domain)) ==
- bang.toKey()) {
- ref
- .read(
- selectedBangTriggerProvider(domain: domain).notifier,
- )
- .clearTrigger();
- }
- },
- ),
- ),
- ),
- SearchField(
- textEditingController: searchTextController,
- focusNode: searchFocusNode,
- maxLines: null,
- activeBang: selectedBang,
- showSuggestions: true,
- label: (selectedBang != null)
- ? const Text('Search')
- : const Text('Address / Search'),
- unfocusOnTapOutside: false,
- onSubmitted: (value) async {
- await submitSearch(value);
- },
- ),
- FixedSearchTermSuggestions(
- searchTextController: searchTextController,
- activeBang: selectedBang,
- submitSearch: submitSearch,
- limit: 25,
- ),
- ],
- );
- }
-}
diff --git a/app/lib/features/geckoview/features/search/presentation/screens/search.dart b/app/lib/features/geckoview/features/search/presentation/screens/search.dart
index 5bb4ea5a..4e111e78 100644
--- a/app/lib/features/geckoview/features/search/presentation/screens/search.dart
+++ b/app/lib/features/geckoview/features/search/presentation/screens/search.dart
@@ -426,8 +426,36 @@ class SearchScreen extends HookConsumerWidget {
TabSearch(searchTextListenable: sampledSearchText),
FeedSearch(searchTextNotifier: sampledSearchText),
HistorySuggestions(
- isPrivate: privateTabMode,
searchTextListenable: sampledSearchText,
+ onUriSelected: (uri) async {
+ if (isEditMode) {
+ // Load into existing tab
+ await ref
+ .read(tabSessionProvider(tabId: tabId).notifier)
+ .loadUrl(url: uri);
+ } else {
+ // Create new tab
+ await ref
+ .read(tabRepositoryProvider.notifier)
+ .addTab(
+ url: uri,
+ private: privateTabMode,
+ parentId: (selectedTabType.value == TabType.child)
+ ? ref.read(selectedTabProvider)
+ : null,
+ launchedFromIntent: launchedFromIntent,
+ selectTab: true,
+ );
+ }
+
+ if (context.mounted) {
+ ref
+ .read(bottomSheetControllerProvider.notifier)
+ .requestDismiss();
+
+ const BrowserRoute().go(context);
+ }
+ },
),
],
);
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 8a73b0fd..b6c45e98 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
@@ -25,9 +25,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:sliver_tools/sliver_tools.dart';
-import 'package:weblibre/core/routing/routes.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/search/domain/providers/engine_suggestions.dart';
import 'package:weblibre/features/geckoview/utils/image_helper.dart';
import 'package:weblibre/presentation/hooks/cached_future.dart';
@@ -35,12 +32,12 @@ import 'package:weblibre/presentation/widgets/failure_widget.dart';
import 'package:weblibre/presentation/widgets/uri_breadcrumb.dart';
class HistorySuggestions extends HookConsumerWidget {
- final bool isPrivate;
final ValueListenable searchTextListenable;
+ final void Function(Uri uri) onUriSelected;
const HistorySuggestions({
super.key,
- required this.isPrivate,
+ required this.onUriSelected,
required this.searchTextListenable,
});
@@ -121,23 +118,9 @@ class HistorySuggestions extends HookConsumerWidget {
overflow: TextOverflow.ellipsis,
),
),
- onTap: () async {
+ onTap: () {
if (uri != null) {
- await ref
- .read(tabRepositoryProvider.notifier)
- .addTab(
- url: uri,
- private: isPrivate,
- selectTab: true,
- );
-
- if (context.mounted) {
- ref
- .read(bottomSheetControllerProvider.notifier)
- .requestDismiss();
-
- const BrowserRoute().go(context);
- }
+ onUriSelected(uri);
}
},
);