From 0b343835107069d1b8a870cd8c09981bd38fddf4 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Thu, 11 Sep 2025 10:16:34 +0200 Subject: [PATCH] setting to show link open prompt --- app/lib/core/routing/routes.browser.dart | 19 ++++ app/lib/core/routing/routes.dart | 1 + app/lib/core/routing/routes.g.dart | 42 +++++++- .../geckoview/domain/repositories/tab.dart | 68 ++++--------- .../geckoview/domain/repositories/tab.g.dart | 2 +- .../dialogs/open_shared_content.dart | 99 +++++++++++++++++++ .../browser_modules/bottom_app_bar.dart | 3 + .../search/presentation/screens/search.dart | 9 +- .../onboarding/presentation/onboarding.dart | 2 + .../screens/general_settings.dart | 26 +++-- .../user/data/models/general_settings.dart | 12 ++- .../user/data/models/general_settings.g.dart | 34 ++++--- .../domain/repositories/general_settings.dart | 2 +- .../repositories/general_settings.g.dart | 2 +- .../presentation/screens/feed_article.dart | 2 + app/lib/presentation/main_app.dart | 75 ++++++++++++++ 16 files changed, 312 insertions(+), 86 deletions(-) create mode 100644 app/lib/features/geckoview/features/browser/presentation/dialogs/open_shared_content.dart diff --git a/app/lib/core/routing/routes.browser.dart b/app/lib/core/routing/routes.browser.dart index 6a14cb27..c104b48c 100644 --- a/app/lib/core/routing/routes.browser.dart +++ b/app/lib/core/routing/routes.browser.dart @@ -54,6 +54,10 @@ part of 'routes.dart'; name: 'TabTreeRoute', path: 'tab_tree/:rootTabId', ), + TypedGoRoute( + name: 'OpenSharedContentRoute', + path: 'open_content', + ), ], ) class BrowserRoute extends GoRouteData with _$BrowserRoute { @@ -75,9 +79,12 @@ class SearchRoute extends GoRouteData with _$SearchRoute { //This should be nullable but isnt allowed by go_router final String searchText; + final bool $extra; + const SearchRoute({ required this.tabType, this.searchText = SearchRoute.emptySearchText, + this.$extra = false, }); @override @@ -87,6 +94,7 @@ class SearchRoute extends GoRouteData with _$SearchRoute { initialSearchText: (searchText.isEmpty || searchText == emptySearchText) ? null : searchText, + launchedFromIntent: $extra, ); } } @@ -159,3 +167,14 @@ class TabTreeRoute extends GoRouteData with _$TabTreeRoute { return DialogPage(builder: (_) => TabTreeDialog(rootTabId)); } } + +class OpenSharedContentRoute extends GoRouteData with _$OpenSharedContentRoute { + final Uri $extra; + + const OpenSharedContentRoute(this.$extra); + + @override + Page buildPage(BuildContext context, GoRouterState state) { + return DialogPage(builder: (_) => OpenSharedContent(sharedUrl: $extra)); + } +} diff --git a/app/lib/core/routing/routes.dart b/app/lib/core/routing/routes.dart index b652c384..2214ace5 100644 --- a/app/lib/core/routing/routes.dart +++ b/app/lib/core/routing/routes.dart @@ -26,6 +26,7 @@ import 'package:weblibre/features/about/presentation/screens/about.dart'; import 'package:weblibre/features/bangs/presentation/screens/categories.dart'; import 'package:weblibre/features/bangs/presentation/screens/list.dart'; import 'package:weblibre/features/bangs/presentation/screens/search.dart'; +import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/open_shared_content.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/screens/browser.dart'; import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart'; diff --git a/app/lib/core/routing/routes.g.dart b/app/lib/core/routing/routes.g.dart index d845a288..48760524 100644 --- a/app/lib/core/routing/routes.g.dart +++ b/app/lib/core/routing/routes.g.dart @@ -333,6 +333,12 @@ RouteBase get $browserRoute => GoRouteData.$route( factory: _$TabTreeRoute._fromState, ), + GoRouteData.$route( + path: 'open_content', + name: 'OpenSharedContentRoute', + + factory: _$OpenSharedContentRoute._fromState, + ), ], ); @@ -361,6 +367,7 @@ mixin _$SearchRoute on GoRouteData { tabType: _$TabTypeEnumMap._$fromName(state.pathParameters['tabType']!)!, searchText: state.pathParameters['searchText'] ?? SearchRoute.emptySearchText, + $extra: state.extra as bool, ); SearchRoute get _self => this as SearchRoute; @@ -371,17 +378,19 @@ mixin _$SearchRoute on GoRouteData { ); @override - void go(BuildContext context) => context.go(location); + void go(BuildContext context) => context.go(location, extra: _self.$extra); @override - Future push(BuildContext context) => context.push(location); + Future push(BuildContext context) => + context.push(location, extra: _self.$extra); @override void pushReplacement(BuildContext context) => - context.pushReplacement(location); + context.pushReplacement(location, extra: _self.$extra); @override - void replace(BuildContext context) => context.replace(location); + void replace(BuildContext context) => + context.replace(location, extra: _self.$extra); } const _$TabTypeEnumMap = { @@ -552,6 +561,31 @@ mixin _$TabTreeRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } +mixin _$OpenSharedContentRoute on GoRouteData { + static OpenSharedContentRoute _fromState(GoRouterState state) => + OpenSharedContentRoute(state.extra as Uri); + + OpenSharedContentRoute get _self => this as OpenSharedContentRoute; + + @override + String get location => GoRouteData.$location('/open_content'); + + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + + @override + Future push(BuildContext context) => + context.push(location, extra: _self.$extra); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: _self.$extra); + + @override + void replace(BuildContext context) => + context.replace(location, extra: _self.$extra); +} + extension on Map { T? _$fromName(String? value) => entries.where((element) => element.value == value).firstOrNull?.key; diff --git a/app/lib/features/geckoview/domain/repositories/tab.dart b/app/lib/features/geckoview/domain/repositories/tab.dart index 2e87bbaf..a527073f 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.dart @@ -25,21 +25,16 @@ import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:nullability/nullability.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:weblibre/core/logger.dart'; -import 'package:weblibre/core/routing/routes.dart'; -import 'package:weblibre/features/bangs/domain/providers/bangs.dart'; import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart'; import 'package:weblibre/features/geckoview/domain/providers.dart'; import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; -import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart'; -import 'package:weblibre/features/geckoview/features/browser/domain/providers/intent.dart'; +import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart'; -import 'package:weblibre/features/share_intent/domain/entities/shared_content.dart'; -import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/utils/debouncer.dart'; part 'tab.g.dart'; @@ -68,12 +63,16 @@ class TabRepository extends _$TabRepository { required bool private, HistoryMetadataKey? historyMetadata, Map? additionalHeaders, + Value? container, + bool launchedFromIntent = false, }) async { - final selectedContainer = await ref - .read(selectedContainerProvider.notifier) - .fetchData(); + final assingedContainer = + container ?? + Value( + await ref.read(selectedContainerProvider.notifier).fetchData(), + ); - return ref + final newTabId = await ref .read(tabDatabaseProvider) .tabDao .upsertContainerTabTransactional( @@ -84,7 +83,7 @@ class TabRepository extends _$TabRepository { startLoading: startLoading, parentId: parentId, flags: flags, - contextId: selectedContainer?.metadata.contextualIdentity, + contextId: assingedContainer.value?.metadata.contextualIdentity, source: source, private: private, historyMetadata: historyMetadata, @@ -92,8 +91,14 @@ class TabRepository extends _$TabRepository { ); }, parentId: Value(parentId), - containerId: Value(selectedContainer?.id), + containerId: Value(assingedContainer.value?.id), ); + + if (launchedFromIntent) { + _tabFromIntent.add(newTabId); + } + + return newTabId; } Future duplicateTab({ @@ -371,45 +376,6 @@ class TabRepository extends _$TabRepository { }, ); - ref.listen( - fireImmediately: true, - engineBoundIntentStreamProvider, - (previous, next) { - next.whenData((value) async { - final isPrivate = - ref - .read(generalSettingsWithDefaultsProvider) - .defaultIntentTabType == - TabType.private; - - switch (value) { - case SharedUrl(): - _tabFromIntent.add( - await addTab(url: value.url, private: isPrivate), - ); - case SharedText(): - final defaultSearchBang = - ref.read(selectedBangDataProvider()) ?? - await ref.read(defaultSearchBangDataProvider.future); - - _tabFromIntent.add( - await addTab( - url: defaultSearchBang?.getTemplateUrl(value.text), - private: isPrivate, - ), - ); - } - }); - }, - onError: (error, stackTrace) { - logger.e( - 'Error listening to engineBoundIntentStreamProvider', - error: error, - stackTrace: stackTrace, - ); - }, - ); - ref.onDispose(() async { tabStateDebouncer.dispose(); await tabAddedSub.cancel(); diff --git a/app/lib/features/geckoview/domain/repositories/tab.g.dart b/app/lib/features/geckoview/domain/repositories/tab.g.dart index 12642bf0..889ccfa8 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.g.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.g.dart @@ -6,7 +6,7 @@ part of 'tab.dart'; // RiverpodGenerator // ************************************************************************** -String _$tabRepositoryHash() => r'4f562c1456eadcda7986ea2048667dd083f37618'; +String _$tabRepositoryHash() => r'45af26417881bca38973cf702073b1c7857f1cfa'; /// See also [TabRepository]. @ProviderFor(TabRepository) diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/open_shared_content.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/open_shared_content.dart new file mode 100644 index 00000000..ce817a6d --- /dev/null +++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/open_shared_content.dart @@ -0,0 +1,99 @@ +import 'package:drift/drift.dart'; +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:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; +import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; +import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_chips.dart'; +import 'package:weblibre/utils/form_validators.dart'; + +class OpenSharedContent extends HookConsumerWidget { + final Uri sharedUrl; + + const OpenSharedContent({required this.sharedUrl}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final formKey = useMemoized(() => GlobalKey()); + final textController = useTextEditingController(text: sharedUrl.toString()); + + final selectedContainer = useState(null); + + Future openTab(bool isPrivate) async { + if (formKey.currentState?.validate() == true) { + await ref + .read(tabRepositoryProvider.notifier) + .addTab( + url: Uri.parse(textController.text), + private: isPrivate, + container: Value(selectedContainer.value), + launchedFromIntent: true, + ); + } + } + + return Form( + key: formKey, + child: SimpleDialog( + title: const Text('Open URL'), + children: [ + Padding( + padding: const EdgeInsetsDirectional.symmetric(horizontal: 16.0), + child: SizedBox( + height: 48, + width: double.maxFinite, + child: ContainerChips( + displayMenu: false, + selectedContainer: selectedContainer.value, + onSelected: (container) { + selectedContainer.value = container; + }, + onDeleted: (container) { + selectedContainer.value = null; + }, + ), + ), + ), + Padding( + padding: const EdgeInsetsDirectional.symmetric(horizontal: 16.0), + child: TextFormField( + controller: textController, + keyboardType: TextInputType.url, + minLines: 1, + maxLines: 10, + validator: (value) { + return validateUrl( + value, + onlyHttpProtocol: true, + eagerParsing: false, + ); + }, + ), + ), + ListTile( + title: const Text('Open Regular Tab'), + leading: const Icon(MdiIcons.tab), + onTap: () async { + await openTab(false); + if (context.mounted) { + context.pop(); + } + }, + ), + ListTile( + title: const Text('Open Private Tab'), + leading: const Icon(MdiIcons.tabUnselected), + onTap: () async { + await openTab(true); + if (context.mounted) { + context.pop(); + } + }, + ), + ], + ), + ); + } +} diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart index f3edaac3..88dee46c 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart @@ -17,6 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; @@ -242,6 +243,7 @@ class BrowserBottomAppBar extends HookConsumerWidget { .addTab( url: ref.read(docsUriProvider), private: isPrivate, + container: const Value(null), ); }, leadingIcon: const Icon(Icons.help), @@ -301,6 +303,7 @@ class BrowserBottomAppBar extends HookConsumerWidget { .addTab( url: Uri.parse('https://addons.mozilla.org'), private: isPrivate, + container: const Value(null), ); }, leadingIcon: const Icon(MdiIcons.puzzlePlus), 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 1605b863..0029a228 100644 --- a/app/lib/features/geckoview/features/search/presentation/screens/search.dart +++ b/app/lib/features/geckoview/features/search/presentation/screens/search.dart @@ -44,8 +44,13 @@ import 'package:weblibre/utils/uri_parser.dart' as uri_parser; class SearchScreen extends HookConsumerWidget { final String? initialSearchText; final TabType tabType; + final bool launchedFromIntent; - const SearchScreen({required this.initialSearchText, required this.tabType}); + const SearchScreen({ + required this.initialSearchText, + required this.tabType, + this.launchedFromIntent = false, + }); @override Widget build(BuildContext context, WidgetRef ref) { @@ -109,6 +114,7 @@ class SearchScreen extends HookConsumerWidget { parentId: (selectedTabType.value == TabType.child) ? ref.read(selectedTabProvider) : null, + launchedFromIntent: launchedFromIntent, ); if (context.mounted) { @@ -212,6 +218,7 @@ class SearchScreen extends HookConsumerWidget { (selectedTabType.value == TabType.child) ? ref.read(selectedTabProvider) : null, + launchedFromIntent: launchedFromIntent, ); if (context.mounted) { diff --git a/app/lib/features/onboarding/presentation/onboarding.dart b/app/lib/features/onboarding/presentation/onboarding.dart index ac32dd5a..0023e181 100644 --- a/app/lib/features/onboarding/presentation/onboarding.dart +++ b/app/lib/features/onboarding/presentation/onboarding.dart @@ -17,6 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -147,6 +148,7 @@ class OnboardingScreen extends HookConsumerWidget { .addTab( url: ref.read(docsUriProvider), private: false, + container: const Value(null), ); ref.invalidate(routerProvider); diff --git a/app/lib/features/settings/presentation/screens/general_settings.dart b/app/lib/features/settings/presentation/screens/general_settings.dart index c9aac65a..831248d0 100644 --- a/app/lib/features/settings/presentation/screens/general_settings.dart +++ b/app/lib/features/settings/presentation/screens/general_settings.dart @@ -175,17 +175,22 @@ class GeneralSettingsScreen extends HookConsumerWidget { showSelectedIcon: false, segments: const [ ButtonSegment( - value: TabType.regular, + value: TabIntentOpenSetting.ask, + label: Text('Prompt'), + icon: Icon(MdiIcons.messageQuestion), + ), + ButtonSegment( + value: TabIntentOpenSetting.regular, label: Text('Regular'), icon: Icon(MdiIcons.tab), ), ButtonSegment( - value: TabType.private, + value: TabIntentOpenSetting.private, label: Text('Private'), icon: Icon(MdiIcons.tabUnselected), ), ], - selected: {generalSettings.defaultIntentTabType}, + selected: {generalSettings.tabIntentOpenSetting}, onSelectionChanged: (value) async { await ref .read( @@ -193,15 +198,16 @@ class GeneralSettingsScreen extends HookConsumerWidget { ) .save( (currentSettings) => currentSettings.copyWith - .defaultIntentTabType(value.first), + .tabIntentOpenSetting(value.first), ); }, - style: switch (generalSettings.defaultIntentTabType) { - TabType.regular => null, - TabType.private => SegmentedButton.styleFrom( - selectedBackgroundColor: const Color(0x648000D7), - ), - TabType.child => null, + style: switch (generalSettings.tabIntentOpenSetting) { + TabIntentOpenSetting.regular => null, + TabIntentOpenSetting.private => + SegmentedButton.styleFrom( + selectedBackgroundColor: const Color(0x648000D7), + ), + TabIntentOpenSetting.ask => null, }, ), ), diff --git a/app/lib/features/user/data/models/general_settings.dart b/app/lib/features/user/data/models/general_settings.dart index ac2a2018..cdf3d0cd 100644 --- a/app/lib/features/user/data/models/general_settings.dart +++ b/app/lib/features/user/data/models/general_settings.dart @@ -31,6 +31,8 @@ const _fallbackAutocompleteProvider = SearchSuggestionProviders.none; enum TabBarSwipeAction { switchLastOpened, navigateOrderedTabs } +enum TabIntentOpenSetting { regular, private, ask } + enum DeleteBrowsingDataType { tabs('Open tabs'), history('Browsing history'), @@ -58,7 +60,7 @@ class GeneralSettings with FastEquatable { final bool showExtensionShortcut; final bool enableLocalAiFeatures; final TabType defaultCreateTabType; - final TabType defaultIntentTabType; + final TabIntentOpenSetting tabIntentOpenSetting; final bool proxyPrivateTabsTor; final bool autoHideTabBar; final TabBarSwipeAction tabBarSwipeAction; @@ -75,7 +77,7 @@ class GeneralSettings with FastEquatable { required this.showExtensionShortcut, required this.enableLocalAiFeatures, required this.defaultCreateTabType, - required this.defaultIntentTabType, + required this.tabIntentOpenSetting, required this.autoHideTabBar, required this.tabBarSwipeAction, }); @@ -92,7 +94,7 @@ class GeneralSettings with FastEquatable { bool? showExtensionShortcut, bool? enableLocalAiFeatures, TabType? defaultCreateTabType, - TabType? defaultIntentTabType, + TabIntentOpenSetting? tabIntentOpenSetting, bool? autoHideTabBar, TabBarSwipeAction? tabBarSwipeAction, }) : themeMode = themeMode ?? ThemeMode.dark, @@ -106,7 +108,7 @@ class GeneralSettings with FastEquatable { showExtensionShortcut = showExtensionShortcut ?? false, enableLocalAiFeatures = enableLocalAiFeatures ?? true, defaultCreateTabType = defaultCreateTabType ?? TabType.regular, - defaultIntentTabType = defaultIntentTabType ?? TabType.regular, + tabIntentOpenSetting = tabIntentOpenSetting ?? TabIntentOpenSetting.ask, autoHideTabBar = autoHideTabBar ?? true, tabBarSwipeAction = tabBarSwipeAction ?? TabBarSwipeAction.switchLastOpened; @@ -128,7 +130,7 @@ class GeneralSettings with FastEquatable { showExtensionShortcut, enableLocalAiFeatures, defaultCreateTabType, - defaultIntentTabType, + tabIntentOpenSetting, proxyPrivateTabsTor, autoHideTabBar, tabBarSwipeAction, diff --git a/app/lib/features/user/data/models/general_settings.g.dart b/app/lib/features/user/data/models/general_settings.g.dart index ddb78cbe..b52bfc7f 100644 --- a/app/lib/features/user/data/models/general_settings.g.dart +++ b/app/lib/features/user/data/models/general_settings.g.dart @@ -33,7 +33,9 @@ abstract class _$GeneralSettingsCWProxy { GeneralSettings defaultCreateTabType(TabType defaultCreateTabType); - GeneralSettings defaultIntentTabType(TabType defaultIntentTabType); + GeneralSettings tabIntentOpenSetting( + TabIntentOpenSetting tabIntentOpenSetting, + ); GeneralSettings autoHideTabBar(bool autoHideTabBar); @@ -57,7 +59,7 @@ abstract class _$GeneralSettingsCWProxy { bool showExtensionShortcut, bool enableLocalAiFeatures, TabType defaultCreateTabType, - TabType defaultIntentTabType, + TabIntentOpenSetting tabIntentOpenSetting, bool autoHideTabBar, TabBarSwipeAction tabBarSwipeAction, }); @@ -115,8 +117,9 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { this(defaultCreateTabType: defaultCreateTabType); @override - GeneralSettings defaultIntentTabType(TabType defaultIntentTabType) => - this(defaultIntentTabType: defaultIntentTabType); + GeneralSettings tabIntentOpenSetting( + TabIntentOpenSetting tabIntentOpenSetting, + ) => this(tabIntentOpenSetting: tabIntentOpenSetting); @override GeneralSettings autoHideTabBar(bool autoHideTabBar) => @@ -145,7 +148,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { Object? showExtensionShortcut = const $CopyWithPlaceholder(), Object? enableLocalAiFeatures = const $CopyWithPlaceholder(), Object? defaultCreateTabType = const $CopyWithPlaceholder(), - Object? defaultIntentTabType = const $CopyWithPlaceholder(), + Object? tabIntentOpenSetting = const $CopyWithPlaceholder(), Object? autoHideTabBar = const $CopyWithPlaceholder(), Object? tabBarSwipeAction = const $CopyWithPlaceholder(), }) { @@ -200,10 +203,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { ? _value.defaultCreateTabType // ignore: cast_nullable_to_non_nullable : defaultCreateTabType as TabType, - defaultIntentTabType: defaultIntentTabType == const $CopyWithPlaceholder() - ? _value.defaultIntentTabType + tabIntentOpenSetting: tabIntentOpenSetting == const $CopyWithPlaceholder() + ? _value.tabIntentOpenSetting // ignore: cast_nullable_to_non_nullable - : defaultIntentTabType as TabType, + : tabIntentOpenSetting as TabIntentOpenSetting, autoHideTabBar: autoHideTabBar == const $CopyWithPlaceholder() ? _value.autoHideTabBar // ignore: cast_nullable_to_non_nullable @@ -248,9 +251,9 @@ GeneralSettings _$GeneralSettingsFromJson(Map json) => _$TabTypeEnumMap, json['defaultCreateTabType'], ), - defaultIntentTabType: $enumDecodeNullable( - _$TabTypeEnumMap, - json['defaultIntentTabType'], + tabIntentOpenSetting: $enumDecodeNullable( + _$TabIntentOpenSettingEnumMap, + json['tabIntentOpenSetting'], ), autoHideTabBar: json['autoHideTabBar'] as bool?, tabBarSwipeAction: $enumDecodeNullable( @@ -276,7 +279,8 @@ Map _$GeneralSettingsToJson( 'showExtensionShortcut': instance.showExtensionShortcut, 'enableLocalAiFeatures': instance.enableLocalAiFeatures, 'defaultCreateTabType': _$TabTypeEnumMap[instance.defaultCreateTabType]!, - 'defaultIntentTabType': _$TabTypeEnumMap[instance.defaultIntentTabType]!, + 'tabIntentOpenSetting': + _$TabIntentOpenSettingEnumMap[instance.tabIntentOpenSetting]!, 'proxyPrivateTabsTor': instance.proxyPrivateTabsTor, 'autoHideTabBar': instance.autoHideTabBar, 'tabBarSwipeAction': _$TabBarSwipeActionEnumMap[instance.tabBarSwipeAction]!, @@ -311,6 +315,12 @@ const _$TabTypeEnumMap = { TabType.child: 'child', }; +const _$TabIntentOpenSettingEnumMap = { + TabIntentOpenSetting.regular: 'regular', + TabIntentOpenSetting.private: 'private', + TabIntentOpenSetting.ask: 'ask', +}; + const _$TabBarSwipeActionEnumMap = { TabBarSwipeAction.switchLastOpened: 'switchLastOpened', TabBarSwipeAction.navigateOrderedTabs: 'navigateOrderedTabs', diff --git a/app/lib/features/user/domain/repositories/general_settings.dart b/app/lib/features/user/domain/repositories/general_settings.dart index c01eb5e7..fae8f966 100644 --- a/app/lib/features/user/domain/repositories/general_settings.dart +++ b/app/lib/features/user/domain/repositories/general_settings.dart @@ -88,7 +88,7 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository { DriftSqlType.string, db.typeMapping, ), - 'defaultIntentTabType': settings['defaultIntentTabType']?.readAs( + 'tabIntentOpenSetting': settings['tabIntentOpenSetting']?.readAs( DriftSqlType.string, db.typeMapping, ), diff --git a/app/lib/features/user/domain/repositories/general_settings.g.dart b/app/lib/features/user/domain/repositories/general_settings.g.dart index 5a1c8da2..ae0c446f 100644 --- a/app/lib/features/user/domain/repositories/general_settings.g.dart +++ b/app/lib/features/user/domain/repositories/general_settings.g.dart @@ -27,7 +27,7 @@ final generalSettingsWithDefaultsProvider = typedef GeneralSettingsWithDefaultsRef = AutoDisposeProviderRef; String _$generalSettingsRepositoryHash() => - r'e311760a7773109a90c28275262883fcca348bbb'; + r'7dd57d726d4bf86e04cb33da9b7a1399d49f7915'; /// See also [GeneralSettingsRepository]. @ProviderFor(GeneralSettingsRepository) diff --git a/app/lib/features/web_feed/presentation/screens/feed_article.dart b/app/lib/features/web_feed/presentation/screens/feed_article.dart index 58293812..7b24715e 100644 --- a/app/lib/features/web_feed/presentation/screens/feed_article.dart +++ b/app/lib/features/web_feed/presentation/screens/feed_article.dart @@ -18,6 +18,7 @@ * along with this program. If not, see . */ import 'package:collection/collection.dart'; +import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; @@ -205,6 +206,7 @@ class FeedArticleScreen extends HookConsumerWidget { .addTab( url: articleLink.uri, private: isPrivate, + container: const Value(null), ); if (context.mounted) { diff --git a/app/lib/presentation/main_app.dart b/app/lib/presentation/main_app.dart index 5737e6ee..70312aad 100644 --- a/app/lib/presentation/main_app.dart +++ b/app/lib/presentation/main_app.dart @@ -19,8 +19,18 @@ */ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/core/logger.dart'; import 'package:weblibre/core/providers/router.dart'; +import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/domain/services/app_initialization.dart'; +import 'package:weblibre/features/bangs/domain/providers/bangs.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/browser/domain/providers/intent.dart'; +import 'package:weblibre/features/share_intent/domain/entities/shared_content.dart'; +import 'package:weblibre/features/user/data/models/general_settings.dart'; +import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/presentation/widgets/failure_widget.dart'; class MainApp extends HookConsumerWidget { @@ -40,6 +50,71 @@ class MainApp extends HookConsumerWidget { final initializationResult = ref.watch(appInitializationServiceProvider); final router = ref.watch(routerProvider); + ref.listen( + engineBoundIntentStreamProvider, + (previous, next) { + next.whenData((sharedContent) async { + final router = await ref.read(routerProvider.future); + final settings = ref.read(generalSettingsWithDefaultsProvider); + + switch (settings.tabIntentOpenSetting) { + case TabIntentOpenSetting.regular: + case TabIntentOpenSetting.private: + switch (sharedContent) { + case SharedUrl(): + await ref + .read(tabRepositoryProvider.notifier) + .addTab( + url: sharedContent.url, + private: + settings.tabIntentOpenSetting == + TabIntentOpenSetting.private, + launchedFromIntent: true, + ); + case SharedText(): + final defaultSearchBang = + ref.read(selectedBangDataProvider()) ?? + await ref.read(defaultSearchBangDataProvider.future); + + await ref + .read(tabRepositoryProvider.notifier) + .addTab( + url: defaultSearchBang?.getTemplateUrl( + sharedContent.text, + ), + private: + settings.tabIntentOpenSetting == + TabIntentOpenSetting.private, + launchedFromIntent: true, + ); + } + case TabIntentOpenSetting.ask: + switch (sharedContent) { + case SharedUrl(): + final route = OpenSharedContentRoute(sharedContent.url); + await router.push(route.location, extra: route.$extra); + case SharedText(): + final route = SearchRoute( + tabType: + ref.read(selectedTabTypeProvider) ?? + settings.defaultCreateTabType, + searchText: sharedContent.text, + $extra: true, //launched from intent + ); + await router.push(route.location); + } + } + }); + }, + onError: (error, stackTrace) { + logger.e( + 'Error listening to engineBoundIntentStreamProvider', + error: error, + stackTrace: stackTrace, + ); + }, + ); + return initializationResult.fold( (initializationState) { if (!initializationState.initialized) {