app links setting

This commit is contained in:
Fabian Freund
2026-01-24 07:38:57 +01:00
parent aeb8a8d5af
commit be0575664d
18 changed files with 1194 additions and 830 deletions
File diff suppressed because it is too large Load Diff
@@ -19,6 +19,7 @@
*/
import 'package:collection/collection.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -375,3 +376,18 @@ EquatableValue<List<TabPreview>> filteredTabPreviews(
.toList(),
);
}
@Riverpod()
class AppLinksModeNotifier extends _$AppLinksModeNotifier {
final _service = GeckoEngineSettingsService();
Future<void> setMode(AppLinksMode mode) async {
await _service.setAppLinksMode(mode);
ref.invalidateSelf();
}
@override
Future<AppLinksMode> build() {
return _service.getAppLinksMode();
}
}
@@ -791,3 +791,48 @@ final class FilteredTabPreviewsFamily extends $Family
@override
String toString() => r'filteredTabPreviewsProvider';
}
@ProviderFor(AppLinksModeNotifier)
final appLinksModeProvider = AppLinksModeNotifierProvider._();
final class AppLinksModeNotifierProvider
extends $AsyncNotifierProvider<AppLinksModeNotifier, AppLinksMode> {
AppLinksModeNotifierProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'appLinksModeProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$appLinksModeNotifierHash();
@$internal
@override
AppLinksModeNotifier create() => AppLinksModeNotifier();
}
String _$appLinksModeNotifierHash() =>
r'2643b7d2799870fd444f7db204ea452d975368a3';
abstract class _$AppLinksModeNotifier extends $AsyncNotifier<AppLinksMode> {
FutureOr<AppLinksMode> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<AsyncValue<AppLinksMode>, AppLinksMode>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<AppLinksMode>, AppLinksMode>,
AsyncValue<AppLinksMode>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@@ -30,7 +30,10 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart
part 'engine_settings_replication.g.dart';
/// Checks if any Custom ETP setting changed between two EngineSettings instances.
bool _customEtpSettingsChanged(GeckoEngineSettings? previous, GeckoEngineSettings current) {
bool _customEtpSettingsChanged(
GeckoEngineSettings? previous,
GeckoEngineSettings current,
) {
if (previous == null) return true;
return previous.blockCookies != current.blockCookies ||
previous.customCookiePolicy != current.customCookiePolicy ||
@@ -39,8 +42,10 @@ bool _customEtpSettingsChanged(GeckoEngineSettings? previous, GeckoEngineSetting
previous.blockCryptominers != current.blockCryptominers ||
previous.blockFingerprinters != current.blockFingerprinters ||
previous.blockRedirectTrackers != current.blockRedirectTrackers ||
previous.blockSuspectedFingerprinters != current.blockSuspectedFingerprinters ||
previous.suspectedFingerprintersScope != current.suspectedFingerprintersScope ||
previous.blockSuspectedFingerprinters !=
current.blockSuspectedFingerprinters ||
previous.suspectedFingerprintersScope !=
current.suspectedFingerprintersScope ||
previous.allowListBaseline != current.allowListBaseline ||
previous.allowListConvenience != current.allowListConvenience;
}
@@ -107,14 +112,18 @@ class EngineSettingsReplicationService
await _service.javascriptEnabled(settings.javascriptEnabled);
}
// Check if tracking protection policy mode changed OR any custom ETP setting changed
final policyModeChanged = previous.value?.trackingProtectionPolicy !=
final policyModeChanged =
previous.value?.trackingProtectionPolicy !=
settings.trackingProtectionPolicy;
final customSettingsChanged = settings.trackingProtectionPolicy == TrackingProtectionPolicy.custom &&
final customSettingsChanged =
settings.trackingProtectionPolicy ==
TrackingProtectionPolicy.custom &&
_customEtpSettingsChanged(previous.value, settings);
if (policyModeChanged || customSettingsChanged) {
// Always send full custom settings when in CUSTOM mode
if (settings.trackingProtectionPolicy == TrackingProtectionPolicy.custom) {
if (settings.trackingProtectionPolicy ==
TrackingProtectionPolicy.custom) {
await _service.customTrackingProtectionPolicy(
trackingProtectionPolicy: settings.trackingProtectionPolicy,
blockCookies: settings.blockCookies,
@@ -124,8 +133,10 @@ class EngineSettingsReplicationService
blockCryptominers: settings.blockCryptominers,
blockFingerprinters: settings.blockFingerprinters,
blockRedirectTrackers: settings.blockRedirectTrackers,
blockSuspectedFingerprinters: settings.blockSuspectedFingerprinters,
suspectedFingerprintersScope: settings.suspectedFingerprintersScope,
blockSuspectedFingerprinters:
settings.blockSuspectedFingerprinters,
suspectedFingerprintersScope:
settings.suspectedFingerprintersScope,
allowListBaseline: settings.allowListBaseline,
allowListConvenience: settings.allowListConvenience,
);
@@ -44,7 +44,7 @@ final class EngineSettingsReplicationServiceProvider
}
String _$engineSettingsReplicationServiceHash() =>
r'0a9539e19946028f525de64ed5f33e2483e7abc2';
r'3e06f0c273090f9f9dfc42f2140d60ef9557f176';
abstract class _$EngineSettingsReplicationService extends $Notifier<void> {
void build();
@@ -48,4 +48,4 @@ final class ShowSiteSettingsBadgeProvider
}
String _$showSiteSettingsBadgeHash() =>
r'8b759a67e5c8b0f49a5d38b793a3357576a2eb87';
r'e326617d2d794844c6bbe6d909042b91d95c2996';
@@ -20,9 +20,11 @@
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/design/app_colors.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
import 'package:weblibre/features/settings/presentation/widgets/sections.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
@@ -65,6 +67,7 @@ class _TabCreationSection extends StatelessWidget {
SettingSection(name: 'Tab Creation'),
_NewTabDefaultSection(),
_ExternalLinkHandlingSection(),
_AppLinksModeSection(),
],
);
}
@@ -340,3 +343,63 @@ class _TabBarSwipeBehaviorSection extends HookConsumerWidget {
);
}
}
class _AppLinksModeSection extends HookConsumerWidget {
const _AppLinksModeSection();
@override
Widget build(BuildContext context, WidgetRef ref) {
final appLinksMode = ref.watch(
appLinksModeProvider.select((value) => value.value),
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ListTile(
title: Text('Open Links in Apps'),
subtitle: Text(
'Choose how links that can be opened in other apps are handled',
),
leading: Icon(MdiIcons.openInApp),
contentPadding: EdgeInsets.zero,
),
RadioGroup(
groupValue: appLinksMode,
onChanged: (value) async {
if (value != null) {
await ref.read(appLinksModeProvider.notifier).setMode(value);
}
},
child: const Column(
children: [
RadioListTile.adaptive(
value: AppLinksMode.always,
title: Text('Always'),
subtitle: Text(
'Always open links in their native apps without asking',
),
),
RadioListTile.adaptive(
value: AppLinksMode.ask,
title: Text('Ask before opening'),
subtitle: Text('Show a prompt before opening links in apps'),
),
RadioListTile.adaptive(
value: AppLinksMode.never,
title: Text('Never'),
subtitle: Text(
'Always open links in the browser instead of apps',
),
),
],
),
),
],
),
);
}
}