support private tabs
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
+16
-3
@@ -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'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
+1
-1
@@ -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);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -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,
|
||||||
|
|||||||
+4
-3
@@ -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) {
|
||||||
|
|||||||
+4
-3
@@ -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,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
+7
-2
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user