diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart index 3b4d42c9..69a774ad 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart @@ -541,23 +541,27 @@ class _BrowserViewState extends ConsumerState if (_suggestionCountTime != null && DateTime.now().difference(_suggestionCountTime!) > widget.suggestionTimeout) { + final topRoute = ref.read(currentTopRouteProvider); + //Don't do anything if a child route is active - if (GoRouterState.of(context).topRoute?.name == BrowserRoute.name) { + if (topRoute is GoRoute && topRoute.name == BrowserRoute.name) { final settings = ref.read(generalSettingsWithDefaultsProvider); - unawaited( - showSuggestNewTabMessage( - context, - onAdd: (searchText) async { - await SearchRoute( - tabType: - ref.read(selectedTabTypeProvider) ?? - settings.defaultCreateTabType, - searchText: searchText ?? SearchRoute.emptySearchText, - ).push(context); - }, - ), - ); + if (settings.allowClipboardAccess) { + unawaited( + showSuggestNewTabMessage( + context, + onAdd: (searchText) async { + await SearchRoute( + tabType: + ref.read(selectedTabTypeProvider) ?? + settings.defaultCreateTabType, + searchText: searchText ?? SearchRoute.emptySearchText, + ).push(context); + }, + ), + ); + } } } diff --git a/app/lib/features/geckoview/features/search/domain/providers/engine_suggestions.dart b/app/lib/features/geckoview/features/search/domain/providers/engine_suggestions.dart index a71411f7..714db3d6 100644 --- a/app/lib/features/geckoview/features/search/domain/providers/engine_suggestions.dart +++ b/app/lib/features/geckoview/features/search/domain/providers/engine_suggestions.dart @@ -23,6 +23,7 @@ import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:rxdart/rxdart.dart'; import 'package:weblibre/features/geckoview/domain/providers.dart'; +import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; part 'engine_suggestions.g.dart'; @@ -39,9 +40,17 @@ class EngineSuggestions extends _$EngineSuggestions { String query, { List providers = const [GeckoSuggestionType.history], }) { + final allowClipboard = ref.read( + generalSettingsWithDefaultsProvider.select((s) => s.allowClipboardAccess), + ); + return ref .read(engineSuggestionsServiceProvider) - .querySuggestions(query, providers: providers); + .querySuggestions( + query, + providers: providers, + allowClipboard: allowClipboard, + ); } @override diff --git a/app/lib/features/geckoview/features/search/domain/providers/engine_suggestions.g.dart b/app/lib/features/geckoview/features/search/domain/providers/engine_suggestions.g.dart index 1137df4e..f0513b2c 100644 --- a/app/lib/features/geckoview/features/search/domain/providers/engine_suggestions.g.dart +++ b/app/lib/features/geckoview/features/search/domain/providers/engine_suggestions.g.dart @@ -33,7 +33,7 @@ final class EngineSuggestionsProvider EngineSuggestions create() => EngineSuggestions(); } -String _$engineSuggestionsHash() => r'ba2c2d7f0a5e99fd9e639acd020b8cf3016c6126'; +String _$engineSuggestionsHash() => r'10d4a8a53d184b6c1d107d8634a2f662dfd297f1'; abstract class _$EngineSuggestions extends $StreamNotifier> { diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart b/app/lib/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart index 49191f29..f982b80f 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/clipboard_fill.dart @@ -20,16 +20,26 @@ 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:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/presentation/hooks/cached_future.dart'; import 'package:weblibre/utils/clipboard.dart'; -class ClipboardFillLink extends HookWidget { +class ClipboardFillLink extends HookConsumerWidget { final TextEditingController controller; const ClipboardFillLink({super.key, required this.controller}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final allowClipboardAccess = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.allowClipboardAccess), + ); + + if (!allowClipboardAccess) { + return const SizedBox.shrink(); + } + final clipboardUrl = useCachedFuture(() => tryGetUriFromClipboard()); final currentText = useValueListenable(controller); diff --git a/app/lib/features/settings/presentation/screens/search_content_settings.dart b/app/lib/features/settings/presentation/screens/search_content_settings.dart index dde21c50..3f1c4ec5 100644 --- a/app/lib/features/settings/presentation/screens/search_content_settings.dart +++ b/app/lib/features/settings/presentation/screens/search_content_settings.dart @@ -74,6 +74,7 @@ class _SearchSection extends StatelessWidget { _BangsTile(), _AutocompleteProviderSection(), _MaxSearchHistoryEntriesSection(), + _AllowClipboardAccessTile(), ], ); } @@ -470,3 +471,29 @@ class _AddonCollectionTile extends StatelessWidget { ); } } + +class _AllowClipboardAccessTile extends HookConsumerWidget { + const _AllowClipboardAccessTile(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final allowClipboardAccess = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.allowClipboardAccess), + ); + + return SwitchListTile.adaptive( + title: const Text('Allow clipboard access for suggestions'), + subtitle: const Text('Browser can read clipboard to suggest URLs'), + secondary: const Icon(MdiIcons.clipboardTextOutline), + value: allowClipboardAccess, + onChanged: (value) async { + await ref + .read(saveGeneralSettingsControllerProvider.notifier) + .save( + (currentSettings) => + currentSettings.copyWith.allowClipboardAccess(value), + ); + }, + ); + } +} diff --git a/app/lib/features/user/data/models/general_settings.dart b/app/lib/features/user/data/models/general_settings.dart index 616a1467..c220a90e 100644 --- a/app/lib/features/user/data/models/general_settings.dart +++ b/app/lib/features/user/data/models/general_settings.dart @@ -85,6 +85,7 @@ class GeneralSettings with FastEquatable { final bool doubleBackCloseTab; final Duration unassignedTabsAutoCleanInterval; final int maxSearchHistoryEntries; + final bool allowClipboardAccess; GeneralSettings({ required this.themeMode, @@ -112,6 +113,7 @@ class GeneralSettings with FastEquatable { required this.doubleBackCloseTab, required this.unassignedTabsAutoCleanInterval, required this.maxSearchHistoryEntries, + required this.allowClipboardAccess, }); GeneralSettings.withDefaults({ @@ -140,6 +142,7 @@ class GeneralSettings with FastEquatable { bool? doubleBackCloseTab, Duration? unassignedTabsAutoCleanInterval, int? maxSearchHistoryEntries, + bool? allowClipboardAccess, }) : themeMode = themeMode ?? ThemeMode.dark, enableReadability = enableReadability ?? true, enforceReadability = enforceReadability ?? false, @@ -168,7 +171,8 @@ class GeneralSettings with FastEquatable { doubleBackCloseTab = doubleBackCloseTab ?? true, unassignedTabsAutoCleanInterval = unassignedTabsAutoCleanInterval ?? Duration.zero, - maxSearchHistoryEntries = maxSearchHistoryEntries ?? 5; + maxSearchHistoryEntries = maxSearchHistoryEntries ?? 5, + allowClipboardAccess = allowClipboardAccess ?? true; factory GeneralSettings.fromJson(Map json) => _$GeneralSettingsFromJson(json); @@ -202,5 +206,6 @@ class GeneralSettings with FastEquatable { doubleBackCloseTab, unassignedTabsAutoCleanInterval, maxSearchHistoryEntries, + allowClipboardAccess, ]; } 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 54da87be..1c3ea028 100644 --- a/app/lib/features/user/data/models/general_settings.g.dart +++ b/app/lib/features/user/data/models/general_settings.g.dart @@ -69,6 +69,8 @@ abstract class _$GeneralSettingsCWProxy { GeneralSettings maxSearchHistoryEntries(int maxSearchHistoryEntries); + GeneralSettings allowClipboardAccess(bool allowClipboardAccess); + /// Creates a new instance with the provided field values. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`. /// @@ -102,6 +104,7 @@ abstract class _$GeneralSettingsCWProxy { bool doubleBackCloseTab, Duration unassignedTabsAutoCleanInterval, int maxSearchHistoryEntries, + bool allowClipboardAccess, }); } @@ -217,6 +220,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { GeneralSettings maxSearchHistoryEntries(int maxSearchHistoryEntries) => call(maxSearchHistoryEntries: maxSearchHistoryEntries); + @override + GeneralSettings allowClipboardAccess(bool allowClipboardAccess) => + call(allowClipboardAccess: allowClipboardAccess); + @override /// Creates a new instance with the provided field values. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`. @@ -251,6 +258,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { Object? doubleBackCloseTab = const $CopyWithPlaceholder(), Object? unassignedTabsAutoCleanInterval = const $CopyWithPlaceholder(), Object? maxSearchHistoryEntries = const $CopyWithPlaceholder(), + Object? allowClipboardAccess = const $CopyWithPlaceholder(), }) { return GeneralSettings( themeMode: themeMode == const $CopyWithPlaceholder() || themeMode == null @@ -399,6 +407,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { ? _value.maxSearchHistoryEntries // ignore: cast_nullable_to_non_nullable : maxSearchHistoryEntries as int, + allowClipboardAccess: + allowClipboardAccess == const $CopyWithPlaceholder() || + allowClipboardAccess == null + ? _value.allowClipboardAccess + // ignore: cast_nullable_to_non_nullable + : allowClipboardAccess as bool, ); } } @@ -474,6 +488,7 @@ GeneralSettings _$GeneralSettingsFromJson( .toInt(), ), maxSearchHistoryEntries: (json['maxSearchHistoryEntries'] as num?)?.toInt(), + allowClipboardAccess: json['allowClipboardAccess'] as bool?, ); Map _$GeneralSettingsToJson( @@ -513,6 +528,7 @@ Map _$GeneralSettingsToJson( 'unassignedTabsAutoCleanInterval': instance.unassignedTabsAutoCleanInterval.inMicroseconds, 'maxSearchHistoryEntries': instance.maxSearchHistoryEntries, + 'allowClipboardAccess': instance.allowClipboardAccess, }; const _$ThemeModeEnumMap = { diff --git a/app/lib/features/user/domain/repositories/general_settings.dart b/app/lib/features/user/domain/repositories/general_settings.dart index 50c56b50..37345380 100644 --- a/app/lib/features/user/domain/repositories/general_settings.dart +++ b/app/lib/features/user/domain/repositories/general_settings.dart @@ -141,6 +141,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository { DriftSqlType.int, db.typeMapping, ), + 'allowClipboardAccess': settings['allowClipboardAccess']?.readAs( + DriftSqlType.bool, + 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 54c9a506..c6504a59 100644 --- a/app/lib/features/user/domain/repositories/general_settings.g.dart +++ b/app/lib/features/user/domain/repositories/general_settings.g.dart @@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider } String _$generalSettingsRepositoryHash() => - r'7cea68308e2e7847e9bb030e9c9c04860c760edd'; + r'302d33b57212a8f3affcf13a8ef5227d68e56ecb'; abstract class _$GeneralSettingsRepository extends $StreamNotifier { diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_suggestions.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_suggestions.dart index 7aaa26f2..ca96720e 100644 --- a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_suggestions.dart +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_suggestions.dart @@ -39,8 +39,13 @@ class GeckoSuggestionsService extends GeckoSuggestionEvents { GeckoSuggestionType.clipboard, GeckoSuggestionType.history, ], + bool allowClipboard = true, }) { - return _api.querySuggestions(text, providers); + final effectiveProviders = allowClipboard + ? providers + : providers.where((p) => p != GeckoSuggestionType.clipboard).toList(); + + return _api.querySuggestions(text, effectiveProviders); } Future getAutocompleteSuggestion(String query) {