add setting to disable automatic clipboard access (#159)

This commit is contained in:
Fabian Freund
2026-02-05 17:19:18 +01:00
parent d17c6e542f
commit be9dd7d2aa
10 changed files with 101 additions and 21 deletions
@@ -541,23 +541,27 @@ class _BrowserViewState extends ConsumerState<BrowserView>
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);
},
),
);
}
}
}
@@ -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<GeckoSuggestionType> 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
@@ -33,7 +33,7 @@ final class EngineSuggestionsProvider
EngineSuggestions create() => EngineSuggestions();
}
String _$engineSuggestionsHash() => r'ba2c2d7f0a5e99fd9e639acd020b8cf3016c6126';
String _$engineSuggestionsHash() => r'10d4a8a53d184b6c1d107d8634a2f662dfd297f1';
abstract class _$EngineSuggestions
extends $StreamNotifier<List<GeckoSuggestion>> {
@@ -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);
@@ -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),
);
},
);
}
}
@@ -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<String, dynamic> json) =>
_$GeneralSettingsFromJson(json);
@@ -202,5 +206,6 @@ class GeneralSettings with FastEquatable {
doubleBackCloseTab,
unassignedTabsAutoCleanInterval,
maxSearchHistoryEntries,
allowClipboardAccess,
];
}
@@ -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<String, dynamic> _$GeneralSettingsToJson(
@@ -513,6 +528,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(
'unassignedTabsAutoCleanInterval':
instance.unassignedTabsAutoCleanInterval.inMicroseconds,
'maxSearchHistoryEntries': instance.maxSearchHistoryEntries,
'allowClipboardAccess': instance.allowClipboardAccess,
};
const _$ThemeModeEnumMap = {
@@ -141,6 +141,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
DriftSqlType.int,
db.typeMapping,
),
'allowClipboardAccess': settings['allowClipboardAccess']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
});
}
@@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider
}
String _$generalSettingsRepositoryHash() =>
r'7cea68308e2e7847e9bb030e9c9c04860c760edd';
r'302d33b57212a8f3affcf13a8ef5227d68e56ecb';
abstract class _$GeneralSettingsRepository
extends $StreamNotifier<GeneralSettings> {