support private tabs

This commit is contained in:
Fabian Freund
2025-04-25 20:41:08 +02:00
parent 9064a961ac
commit f95331c654
15 changed files with 212 additions and 74 deletions
+13 -2
View File
@@ -5,7 +5,10 @@ part of 'routes.dart';
path: '/', path: '/',
routes: [ routes: [
TypedGoRoute<WebPageRoute>(name: 'WebPageRoute', path: 'page/:url'), TypedGoRoute<WebPageRoute>(name: 'WebPageRoute', path: 'page/:url'),
TypedGoRoute<SearchRoute>(name: 'SearchRoute', path: 'search/:searchText'), TypedGoRoute<SearchRoute>(
name: 'SearchRoute',
path: 'search/:tabType/:searchText',
),
TypedGoRoute<TorProxyRoute>(name: 'TorProxyRoute', path: 'tor_proxy'), TypedGoRoute<TorProxyRoute>(name: 'TorProxyRoute', path: 'tor_proxy'),
TypedGoRoute<ContextMenuRoute>( TypedGoRoute<ContextMenuRoute>(
name: 'ContextMenuRoute', name: 'ContextMenuRoute',
@@ -50,17 +53,25 @@ class WebPageRoute extends GoRouteData {
} }
} }
enum TabType { regular, private }
class SearchRoute extends GoRouteData { class SearchRoute extends GoRouteData {
static const String emptySearchText = ' '; static const String emptySearchText = ' ';
final TabType tabType;
//This should be nullable but isnt allowed by go_router //This should be nullable but isnt allowed by go_router
final String searchText; final String searchText;
const SearchRoute({this.searchText = SearchRoute.emptySearchText}); const SearchRoute({
required this.tabType,
this.searchText = SearchRoute.emptySearchText,
});
@override @override
Widget build(BuildContext context, GoRouterState state) { Widget build(BuildContext context, GoRouterState state) {
return SearchScreen( return SearchScreen(
tabType: tabType,
initialSearchText: initialSearchText:
(searchText.isEmpty || searchText == emptySearchText) (searchText.isEmpty || searchText == emptySearchText)
? null ? null
+15 -3
View File
@@ -214,7 +214,7 @@ RouteBase get $browserRoute => GoRouteData.$route(
factory: $WebPageRouteExtension._fromState, factory: $WebPageRouteExtension._fromState,
), ),
GoRouteData.$route( GoRouteData.$route(
path: 'search/:searchText', path: 'search/:tabType/:searchText',
name: 'SearchRoute', name: 'SearchRoute',
factory: $SearchRouteExtension._fromState, factory: $SearchRouteExtension._fromState,
@@ -292,12 +292,14 @@ extension $WebPageRouteExtension on WebPageRoute {
extension $SearchRouteExtension on SearchRoute { extension $SearchRouteExtension on SearchRoute {
static SearchRoute _fromState(GoRouterState state) => SearchRoute( static SearchRoute _fromState(GoRouterState state) => SearchRoute(
tabType: _$TabTypeEnumMap._$fromName(state.pathParameters['tabType']!)!,
searchText: searchText:
state.pathParameters['searchText'] ?? SearchRoute.emptySearchText, state.pathParameters['searchText'] ?? SearchRoute.emptySearchText,
); );
String get location => String get location => GoRouteData.$location(
GoRouteData.$location('/search/${Uri.encodeComponent(searchText)}'); '/search/${Uri.encodeComponent(_$TabTypeEnumMap[tabType]!)}/${Uri.encodeComponent(searchText)}',
);
void go(BuildContext context) => context.go(location); void go(BuildContext context) => context.go(location);
@@ -309,6 +311,11 @@ extension $SearchRouteExtension on SearchRoute {
void replace(BuildContext context) => context.replace(location); void replace(BuildContext context) => context.replace(location);
} }
const _$TabTypeEnumMap = {
TabType.regular: 'regular',
TabType.private: 'private',
};
extension $TorProxyRouteExtension on TorProxyRoute { extension $TorProxyRouteExtension on TorProxyRoute {
static TorProxyRoute _fromState(GoRouterState state) => TorProxyRoute(); static TorProxyRoute _fromState(GoRouterState state) => TorProxyRoute();
@@ -394,6 +401,11 @@ extension $ContainerEditRouteExtension on ContainerEditRoute {
context.replace(location, extra: $extra); context.replace(location, extra: $extra);
} }
extension<T extends Enum> on Map<T, String> {
T? _$fromName(String? value) =>
entries.where((element) => element.value == value).firstOrNull?.key;
}
RouteBase get $bangCategoriesRoute => GoRouteData.$route( RouteBase get $bangCategoriesRoute => GoRouteData.$route(
path: '/bangs', path: '/bangs',
name: 'BangRoute', name: 'BangRoute',
@@ -41,7 +41,7 @@ class BangListScreen extends HookConsumerWidget {
.read(selectedBangTriggerProvider().notifier) .read(selectedBangTriggerProvider().notifier)
.setTrigger(bang.trigger); .setTrigger(bang.trigger);
const SearchRoute().go(context); const SearchRoute(tabType: TabType.regular).go(context);
}, },
); );
}, },
@@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/bangs/data/models/bang_data.dart'; import 'package:lensai/features/bangs/data/models/bang_data.dart';
import 'package:lensai/features/bangs/domain/providers/search.dart'; import 'package:lensai/features/bangs/domain/providers/search.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_session.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_session.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart'; import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/browser/domain/providers.dart'; import 'package:lensai/features/geckoview/features/browser/domain/providers.dart';
import 'package:lensai/features/geckoview/features/search/domain/providers/search_suggestions.dart'; import 'package:lensai/features/geckoview/features/search/domain/providers/search_suggestions.dart';
@@ -45,12 +46,21 @@ class SiteSearch extends HookConsumerWidget {
Future<void> submitSearch(String query) async { Future<void> submitSearch(String query) async {
if (activeBang != null && (formKey.currentState?.validate() == true)) { if (activeBang != null && (formKey.currentState?.validate() == true)) {
final searchUri = await ref final isPrivate =
.read(bangSearchProvider.notifier) ref.read(selectedTabStateProvider)?.isPrivate ?? false;
.triggerBangSearch(activeBang, query);
final searchUri = activeBang.getTemplateUrl(query);
if (!isPrivate) {
await ref
.read(bangSearchProvider.notifier)
.triggerBangSearch(activeBang, query);
}
if (searchInNewTab) { if (searchInNewTab) {
await ref.read(tabRepositoryProvider.notifier).addTab(url: searchUri); await ref
.read(tabRepositoryProvider.notifier)
.addTab(url: searchUri, private: isPrivate);
} else { } else {
await ref await ref
.read(tabSessionProvider(tabId: null).notifier) .read(tabSessionProvider(tabId: null).notifier)
@@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:lensai/core/logger.dart'; import 'package:lensai/core/logger.dart';
import 'package:lensai/features/bangs/domain/providers/bangs.dart'; import 'package:lensai/features/bangs/domain/providers/bangs.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart'; import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:riverpod/riverpod.dart'; import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -24,13 +24,14 @@ GeckoSelectionActionService selectionActionService(Ref ref) {
); );
if (defaultSearchBang != null) { if (defaultSearchBang != null) {
final currentTabId = ref.read(selectedTabProvider); final currentTab = ref.read(selectedTabStateProvider);
await ref await ref
.read(tabRepositoryProvider.notifier) .read(tabRepositoryProvider.notifier)
.addTab( .addTab(
url: defaultSearchBang.getTemplateUrl(text), url: defaultSearchBang.getTemplateUrl(text),
parentId: currentTabId, parentId: currentTab?.id,
private: currentTab?.isPrivate ?? false,
); );
} else { } else {
logger.e('No default search bang found'); logger.e('No default search bang found');
@@ -50,7 +51,7 @@ GeckoSelectionActionService selectionActionService(Ref ref) {
// } // }
// }), // }),
ShareAction((text) async { ShareAction((text) async {
await Share.share(text); await SharePlus.instance.share(ShareParams(text: text));
}), }),
CallAction((text) async { CallAction((text) async {
final uri = Uri.tryParse('tel:${text.replaceAll(' ', '')}'); final uri = Uri.tryParse('tel:${text.replaceAll(' ', '')}');
@@ -7,7 +7,7 @@ part of 'providers.dart';
// ************************************************************************** // **************************************************************************
String _$selectionActionServiceHash() => String _$selectionActionServiceHash() =>
r'd907a3c5ab7efde82bb2cc1fb0409fd3fd13bb25'; r'fc7344fa34f0e27772309303083804866336197b';
/// See also [selectionActionService]. /// See also [selectionActionService].
@ProviderFor(selectionActionService) @ProviderFor(selectionActionService)
@@ -161,6 +161,31 @@ class WebPageDialog extends HookConsumerWidget {
} }
}, },
), ),
ListTile(
leading: const Icon(MdiIcons.tabUnselected),
title: const Text('Clone as private tab'),
onTap: () async {
final tabId = await ref
.read(tabRepositoryProvider.notifier)
.addTab(url: url, private: true);
if (context.mounted) {
//save reference before pop `ref` gets disposed
final repo = ref.read(
tabRepositoryProvider.notifier,
);
ui_helper.showTabSwitchMessage(
context,
onSwitch: () {
repo.selectTab(tabId);
},
);
context.pop();
}
},
),
ListTile( ListTile(
leading: const Icon(Icons.share), leading: const Icon(Icons.share),
title: const Text('Share link'), title: const Text('Share link'),
@@ -106,7 +106,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
return child!; return child!;
}, },
menuChildren: [ menuChildren: [
if (selectedTabId != null) if (selectedTabId != null) ...[
MenuItemButton( MenuItemButton(
onPressed: () async { onPressed: () async {
await ref await ref
@@ -116,11 +116,24 @@ class BrowserBottomAppBar extends HookConsumerWidget {
leadingIcon: const Icon(Icons.close), leadingIcon: const Icon(Icons.close),
child: const Text('Close Tab'), child: const Text('Close Tab'),
), ),
const Divider(),
],
MenuItemButton( MenuItemButton(
onPressed: () async { onPressed: () async {
await const SearchRoute().push(context); await const SearchRoute(
tabType: TabType.private,
).push(context);
}, },
leadingIcon: const Icon(Icons.add), leadingIcon: const Icon(MdiIcons.tabUnselected),
child: const Text('Add Private Tab'),
),
MenuItemButton(
onPressed: () async {
await const SearchRoute(
tabType: TabType.regular,
).push(context);
},
leadingIcon: const Icon(MdiIcons.tabPlus),
child: const Text('Add Tab'), child: const Text('Add Tab'),
), ),
], ],
@@ -210,7 +210,7 @@ class _BrowserViewState extends ConsumerState<BrowserView>
showSuggestNewTabMessage( showSuggestNewTabMessage(
context, context,
onAdd: () async { onAdd: () async {
await const SearchRoute().push(context); await const SearchRoute(tabType: TabType.regular).push(context);
}, },
); );
} }
@@ -4,6 +4,7 @@ import 'dart:math' as math;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; 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_reorderable_grid_view/widgets/widgets.dart'; import 'package:flutter_reorderable_grid_view/widgets/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/providers/global_drop.dart'; import 'package:lensai/core/providers/global_drop.dart';
@@ -62,7 +63,7 @@ class _TabSheetHeader extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
TextButton.icon( TextButton.icon(
icon: const Icon(Icons.search), icon: const Icon(MdiIcons.tabSearch),
label: const Text('Search'), label: const Text('Search'),
onPressed: () { onPressed: () {
searchMode.value = true; searchMode.value = true;
@@ -410,7 +411,7 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
), ),
child: FloatingActionButton.small( child: FloatingActionButton.small(
onPressed: () async { onPressed: () async {
await const SearchRoute().push(context); await const SearchRoute(tabType: TabType.regular).push(context);
onClose(); onClose();
}, },
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.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:lensai/features/geckoview/domain/entities/states/tab.dart'; import 'package:lensai/features/geckoview/domain/entities/states/tab.dart';
import 'package:lensai/features/geckoview/domain/providers/tab_state.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
@@ -34,10 +35,13 @@ class TabPreview extends StatelessWidget {
borderRadius: const BorderRadius.all(Radius.circular(16.0)), borderRadius: const BorderRadius.all(Radius.circular(16.0)),
), ),
child: Material( child: Material(
color: colorScheme.surfaceContainerHighest, color:
borderRadius: const BorderRadius.all(Radius.circular(16.0)), tab.isPrivate
? const Color(0xFF25003E)
: colorScheme.surfaceContainerHighest,
borderRadius: const BorderRadius.all(Radius.circular(14.0)),
child: InkWell( child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(16.0)), borderRadius: const BorderRadius.all(Radius.circular(14.0)),
onTap: onTap, onTap: onTap,
onDoubleTap: onDoubleTap, onDoubleTap: onDoubleTap,
child: Column( child: Column(
@@ -46,11 +50,15 @@ class TabPreview extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 6.0), padding: const EdgeInsets.only(left: 6.0, top: 2.0),
child: Text( child: Text(
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
tab.title, tab.title,
maxLines: 2, maxLines: 2,
style:
tab.isPrivate
? const TextStyle(color: Colors.white)
: null,
), ),
), ),
), ),
@@ -72,9 +80,16 @@ class TabPreview extends StatelessWidget {
Expanded( Expanded(
child: Text( child: Text(
tab.url.authority, tab.url.authority,
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: tab.isPrivate ? Colors.white : null,
),
), ),
), ),
if (tab.isPrivate) ...[
const SizedBox(width: 6.0),
const Icon(MdiIcons.dominoMask, color: Color(0xFF8000D7)),
],
const SizedBox(width: 8.0),
], ],
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
@@ -82,8 +97,8 @@ class TabPreview extends StatelessWidget {
Expanded( Expanded(
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(16.0), bottomLeft: Radius.circular(14.0),
bottomRight: Radius.circular(16.0), bottomRight: Radius.circular(14.0),
), ),
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
@@ -3,7 +3,7 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart'; import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart'; import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
import 'package:lensai/utils/ui_helper.dart'; import 'package:lensai/utils/ui_helper.dart';
@@ -23,14 +23,15 @@ class OpenImageInNewTab extends HookConsumerWidget {
leading: const Icon(MdiIcons.tabPlus), leading: const Icon(MdiIcons.tabPlus),
title: const Text('Open image in new tab'), title: const Text('Open image in new tab'),
onTap: () async { onTap: () async {
final currentTabId = ref.read(selectedTabProvider); final currentTab = ref.read(selectedTabStateProvider);
final tabId = await ref final tabId = await ref
.read(tabRepositoryProvider.notifier) .read(tabRepositoryProvider.notifier)
.addTab( .addTab(
url: hitResult.tryGetLink(), url: hitResult.tryGetLink(),
parentId: currentTabId, parentId: currentTab?.id,
selectTab: false, selectTab: false,
private: currentTab?.isPrivate ?? false,
); );
if (context.mounted) { if (context.mounted) {
@@ -3,7 +3,7 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/features/geckoview/domain/providers/selected_tab.dart'; import 'package:lensai/features/geckoview/domain/providers/tab_state.dart';
import 'package:lensai/features/geckoview/domain/repositories/tab.dart'; import 'package:lensai/features/geckoview/domain/repositories/tab.dart';
import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart'; import 'package:lensai/features/geckoview/features/contextmenu/extensions/hit_result.dart';
import 'package:lensai/utils/ui_helper.dart'; import 'package:lensai/utils/ui_helper.dart';
@@ -23,14 +23,15 @@ class OpenInNewTab extends HookConsumerWidget {
leading: const Icon(MdiIcons.tabPlus), leading: const Icon(MdiIcons.tabPlus),
title: const Text('Open in new tab'), title: const Text('Open in new tab'),
onTap: () async { onTap: () async {
final currentTabId = ref.read(selectedTabProvider); final currentTab = ref.read(selectedTabStateProvider);
final tabId = await ref final tabId = await ref
.read(tabRepositoryProvider.notifier) .read(tabRepositoryProvider.notifier)
.addTab( .addTab(
url: hitResult.tryGetLink(), url: hitResult.tryGetLink(),
parentId: currentTabId, parentId: currentTab?.id,
selectTab: false, selectTab: false,
private: currentTab?.isPrivate ?? false,
); );
if (context.mounted) { if (context.mounted) {
@@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; 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:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lensai/core/routing/routes.dart';
import 'package:lensai/features/bangs/data/models/bang_data.dart'; import 'package:lensai/features/bangs/data/models/bang_data.dart';
import 'package:lensai/features/bangs/domain/providers/bangs.dart'; import 'package:lensai/features/bangs/domain/providers/bangs.dart';
import 'package:lensai/features/bangs/domain/providers/search.dart'; import 'package:lensai/features/bangs/domain/providers/search.dart';
@@ -18,13 +20,17 @@ import 'package:lensai/utils/uri_parser.dart' as uri_parser;
class SearchScreen extends HookConsumerWidget { class SearchScreen extends HookConsumerWidget {
final String? initialSearchText; final String? initialSearchText;
final TabType tabType;
const SearchScreen({required this.initialSearchText}); const SearchScreen({required this.initialSearchText, required this.tabType});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final formKey = useMemoized(() => GlobalKey<FormState>()); final formKey = useMemoized(() => GlobalKey<FormState>());
final selectedTabType = useState(tabType);
final isPrivate = selectedTabType.value == TabType.private;
final searchTextController = useTextEditingController( final searchTextController = useTextEditingController(
text: initialSearchText, text: initialSearchText,
); );
@@ -52,11 +58,17 @@ class SearchScreen extends HookConsumerWidget {
Future<void> submitSearch(String query) async { Future<void> submitSearch(String query) async {
if (activeBang != null && (formKey.currentState?.validate() == true)) { if (activeBang != null && (formKey.currentState?.validate() == true)) {
final searchUri = await ref final searchUri = activeBang.getTemplateUrl(query);
.read(bangSearchProvider.notifier)
.triggerBangSearch(activeBang, query);
await ref.read(tabRepositoryProvider.notifier).addTab(url: searchUri); if (!isPrivate) {
await ref
.read(bangSearchProvider.notifier)
.triggerBangSearch(activeBang, query);
}
await ref
.read(tabRepositoryProvider.notifier)
.addTab(url: searchUri, private: isPrivate);
if (context.mounted) { if (context.mounted) {
ref.read(bottomSheetControllerProvider.notifier).dismiss(); ref.read(bottomSheetControllerProvider.notifier).dismiss();
@@ -75,43 +87,72 @@ class SearchScreen extends HookConsumerWidget {
floating: true, floating: true,
pinned: true, pinned: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
title: SearchField( title: Align(
showBangIcon: showBangIcon.value, child: SegmentedButton(
textEditingController: searchTextController, showSelectedIcon: false,
focusNode: searchFocusNode, segments: const [
autofocus: true, ButtonSegment(
onSubmitted: (value) async { value: TabType.regular,
if (value.isNotEmpty) { label: Text('Regular'),
var newUrl = uri_parser.tryParseUrl( icon: Icon(MdiIcons.tab),
value, ),
eagerParsing: true, ButtonSegment(
); value: TabType.private,
label: Text('Private'),
icon: Icon(MdiIcons.tabUnselected),
),
],
selected: {selectedTabType.value},
onSelectionChanged: (value) {
selectedTabType.value = value.first;
},
),
),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Padding(
padding: const EdgeInsets.only(left: 16.0),
child: SearchField(
showBangIcon: showBangIcon.value,
textEditingController: searchTextController,
focusNode: searchFocusNode,
autofocus: true,
onSubmitted: (value) async {
if (value.isNotEmpty) {
var newUrl = uri_parser.tryParseUrl(
value,
eagerParsing: true,
);
if (newUrl == null) { if (newUrl == null) {
final defaultSearchBang = final defaultSearchBang =
ref.read(selectedBangDataProvider()) ?? ref.read(selectedBangDataProvider()) ??
await ref.read(defaultSearchBangDataProvider.future); await ref.read(
defaultSearchBangDataProvider.future,
);
newUrl = defaultSearchBang?.getTemplateUrl(value); newUrl = defaultSearchBang?.getTemplateUrl(value);
} }
if (newUrl != null) { if (newUrl != null) {
await ref await ref
.read(tabRepositoryProvider.notifier) .read(tabRepositoryProvider.notifier)
.addTab(url: newUrl); .addTab(url: newUrl, private: isPrivate);
if (context.mounted) { if (context.mounted) {
ref ref
.read(bottomSheetControllerProvider.notifier) .read(bottomSheetControllerProvider.notifier)
.dismiss(); .dismiss();
context.pop(); context.pop();
}
}
} }
} },
} activeBang: activeBang,
}, showSuggestions: true,
activeBang: activeBang, ),
showSuggestions: true, ),
), ),
), ),
const SliverToBoxAdapter(child: Divider()), const SliverToBoxAdapter(child: Divider()),
@@ -120,10 +161,12 @@ class SearchScreen extends HookConsumerWidget {
activeBang: activeBang, activeBang: activeBang,
submitSearch: submitSearch, submitSearch: submitSearch,
), ),
const SliverToBoxAdapter(child: Divider()),
TabSearch(searchTextListenable: sampledSearchText), TabSearch(searchTextListenable: sampledSearchText),
FeedSearch(searchTextNotifier: sampledSearchText), FeedSearch(searchTextNotifier: sampledSearchText),
HistorySuggestions(searchTextListenable: sampledSearchText), HistorySuggestions(
isPrivate: isPrivate,
searchTextListenable: sampledSearchText,
),
], ],
), ),
), ),
@@ -15,9 +15,14 @@ import 'package:skeletonizer/skeletonizer.dart';
import 'package:sliver_tools/sliver_tools.dart'; import 'package:sliver_tools/sliver_tools.dart';
class HistorySuggestions extends HookConsumerWidget { class HistorySuggestions extends HookConsumerWidget {
final bool isPrivate;
final ValueListenable<TextEditingValue> searchTextListenable; final ValueListenable<TextEditingValue> searchTextListenable;
const HistorySuggestions({super.key, required this.searchTextListenable}); const HistorySuggestions({
super.key,
required this.isPrivate,
required this.searchTextListenable,
});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@@ -89,7 +94,7 @@ class HistorySuggestions extends HookConsumerWidget {
case final Uri url) { case final Uri url) {
await ref await ref
.read(tabRepositoryProvider.notifier) .read(tabRepositoryProvider.notifier)
.addTab(url: url); .addTab(url: url, private: isPrivate);
if (context.mounted) { if (context.mounted) {
ref ref