This commit is contained in:
Fabian Freund
2026-01-16 04:32:57 +01:00
parent aa2123d636
commit d416b8963d
15 changed files with 2269 additions and 233 deletions
@@ -0,0 +1,236 @@
/*
* Copyright (c) 2024-2025 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/site_permissions_provider.dart';
/// Section widget for clearing site data
class ClearSiteDataSection extends HookConsumerWidget {
final Uri url;
const ClearSiteDataSection({
required this.url,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isExpanded = useState(false);
final isClearing = useState(false);
final selectedTypes = ref.watch(selectedClearDataTypesProvider);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.delete_sweep),
title: const Text('Clear Site Data'),
subtitle: Text(
isExpanded.value
? 'Select data types to clear'
: 'Cookies, cache, and site data',
),
trailing: Icon(
isExpanded.value ? Icons.expand_less : Icons.expand_more,
),
onTap: () => isExpanded.value = !isExpanded.value,
),
if (isExpanded.value) ...[
_DataTypeCheckbox(
label: 'Cookies',
subtitle: 'Login tokens, preferences, tracking data',
type: ClearDataType.cookies,
isSelected: selectedTypes.contains(ClearDataType.cookies),
onChanged: (selected) {
ref.read(selectedClearDataTypesProvider.notifier).toggle(ClearDataType.cookies);
},
),
_DataTypeCheckbox(
label: 'Cached Files',
subtitle: 'Images, scripts, stylesheets',
type: ClearDataType.allCaches,
isSelected: selectedTypes.contains(ClearDataType.allCaches),
onChanged: (selected) {
ref.read(selectedClearDataTypesProvider.notifier).toggle(ClearDataType.allCaches);
},
),
_DataTypeCheckbox(
label: 'Site Data',
subtitle: 'Offline storage, databases, local files',
type: ClearDataType.allSiteData,
isSelected: selectedTypes.contains(ClearDataType.allSiteData),
onChanged: (selected) {
ref.read(selectedClearDataTypesProvider.notifier).toggle(ClearDataType.allSiteData);
},
),
_DataTypeCheckbox(
label: 'Auth Sessions',
subtitle: 'Saved logins, active sessions',
type: ClearDataType.authSessions,
isSelected: selectedTypes.contains(ClearDataType.authSessions),
onChanged: (selected) {
ref.read(selectedClearDataTypesProvider.notifier).toggle(ClearDataType.authSessions);
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: SizedBox(
width: double.infinity,
child: FilledButton.icon(
onPressed: isClearing.value || selectedTypes.isEmpty
? null
: () => _showConfirmationAndClear(context, ref, isClearing),
icon: isClearing.value
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.delete),
label: Text(isClearing.value ? 'Clearing...' : 'Clear Now'),
),
),
),
],
],
);
}
Future<void> _showConfirmationAndClear(
BuildContext context,
WidgetRef ref,
ValueNotifier<bool> isClearing,
) async {
final selectedTypes = ref.read(selectedClearDataTypesProvider);
if (selectedTypes.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Select at least one data type')),
);
return;
}
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
icon: const Icon(Icons.warning),
title: const Text('Clear Site Data'),
content: Text(
'This will clear ${_formatTypes(selectedTypes)} for ${url.host}.\n\n'
'You may need to log in again.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('Clear'),
),
],
),
);
if (confirmed == true && context.mounted) {
isClearing.value = true;
try {
await _clearData(ref, selectedTypes);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Site data cleared')),
);
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to clear site data: $e')),
);
}
} finally {
isClearing.value = false;
}
}
}
String _formatTypes(Set<ClearDataType> types) {
final labels = types.map((t) {
switch (t) {
case ClearDataType.cookies:
return 'cookies';
case ClearDataType.allCaches:
return 'cached files';
case ClearDataType.allSiteData:
return 'site data';
case ClearDataType.authSessions:
return 'auth sessions';
}
}).toList();
if (labels.length == 1) return labels.first;
if (labels.length == 2) return '${labels[0]} and ${labels[1]}';
return '${labels.sublist(0, labels.length - 1).join(', ')}, and ${labels.last}';
}
Future<void> _clearData(WidgetRef ref, Set<ClearDataType> selectedTypes) async {
final host = url.host;
// Get base domain using PSL API (falls back to host on error)
final pslApi = GeckoPublicSuffixListApi();
final baseDomain = await pslApi.getPublicSuffixPlusOne(host);
// Clear data via API
final clearApi = GeckoDeleteBrowsingDataController();
await clearApi.clearDataForHost(baseDomain, selectedTypes.toList());
// Reload tab
await ref.read(selectedTabSessionProvider).reload();
}
}
class _DataTypeCheckbox extends StatelessWidget {
final String label;
final String subtitle;
final ClearDataType type;
final bool isSelected;
final ValueChanged<bool> onChanged;
const _DataTypeCheckbox({
required this.label,
required this.subtitle,
required this.type,
required this.isSelected,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(label),
subtitle: Text(subtitle),
value: isSelected,
onChanged: (value) => onChanged(value ?? false),
controlAffinity: ListTileControlAffinity.trailing,
);
}
}
@@ -0,0 +1,492 @@
/*
* Copyright (c) 2024-2025 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/site_permissions_provider.dart';
/// Section widget displaying site permissions with toggles
class PermissionsSection extends HookConsumerWidget {
final String origin;
final bool isPrivate;
const PermissionsSection({
required this.origin,
required this.isPrivate,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final permissionsAsync = ref.watch(
sitePermissionsProvider(origin, isPrivate),
);
return permissionsAsync.when(
data: (permissions) => _PermissionsList(
origin: origin,
isPrivate: isPrivate,
permissions: permissions,
),
loading: () => const Padding(
padding: EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator()),
),
error: (error, stack) => Padding(
padding: const EdgeInsets.all(16.0),
child: Text('Error loading permissions: $error'),
),
);
}
}
class _PermissionsList extends HookConsumerWidget {
final String origin;
final bool isPrivate;
final SitePermissions? permissions;
const _PermissionsList({
required this.origin,
required this.isPrivate,
required this.permissions,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final showAll = useState(false);
// Build list of permission entries with their current status
final allPermissions = [
_PermissionEntry(
icon: Icons.videocam,
label: 'Camera',
status: permissions?.camera,
onChanged: (status) => _updatePermission(ref, (p) => _copyWithCamera(p, status)),
),
_PermissionEntry(
icon: Icons.mic,
label: 'Microphone',
status: permissions?.microphone,
onChanged: (status) => _updatePermission(ref, (p) => _copyWithMicrophone(p, status)),
),
_PermissionEntry(
icon: Icons.location_on,
label: 'Location',
status: permissions?.location,
onChanged: (status) => _updatePermission(ref, (p) => _copyWithLocation(p, status)),
),
_PermissionEntry(
icon: Icons.notifications,
label: 'Notifications',
status: permissions?.notification,
onChanged: (status) => _updatePermission(ref, (p) => _copyWithNotification(p, status)),
),
_PermissionEntry(
icon: Icons.storage,
label: 'Persistent Storage',
status: permissions?.persistentStorage,
onChanged: (status) => _updatePermission(ref, (p) => _copyWithPersistentStorage(p, status)),
),
_PermissionEntry(
icon: Icons.cookie,
label: 'Cross-Origin Storage',
status: permissions?.crossOriginStorageAccess,
onChanged: (status) => _updatePermission(ref, (p) => _copyWithCrossOriginStorage(p, status)),
),
_PermissionEntry(
icon: Icons.key,
label: 'Media Key System (DRM)',
status: permissions?.mediaKeySystemAccess,
onChanged: (status) => _updatePermission(ref, (p) => _copyWithMediaKeySystem(p, status)),
),
];
// Filter to only show permissions that have been explicitly set (not noDecision)
final setPermissions = allPermissions.where(
(p) => p.status != null && p.status != SitePermissionStatus.noDecision,
).toList();
final permissionsToShow = showAll.value ? allPermissions : setPermissions;
final hiddenCount = allPermissions.length - setPermissions.length;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
children: [
Text(
'Permissions',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
),
const Spacer(),
if (hiddenCount > 0)
TextButton(
onPressed: () => showAll.value = !showAll.value,
child: Text(showAll.value ? 'Show less' : 'Show all'),
),
],
),
),
...permissionsToShow.map((entry) => _PermissionTile(
icon: entry.icon,
label: entry.label,
status: entry.status,
onChanged: entry.onChanged,
)),
if (permissionsToShow.isEmpty && !showAll.value)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Text(
'No permissions set for this site',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
const SizedBox(height: 8.0),
_AutoplayTile(
audibleStatus: permissions?.autoplayAudible,
inaudibleStatus: permissions?.autoplayInaudible,
onChanged: (audible, inaudible) => _updateAutoplay(ref, audible, inaudible),
),
],
);
}
Future<void> _updatePermission(
WidgetRef ref,
SitePermissions Function(SitePermissions) updater,
) async {
final currentPermissions = permissions ?? SitePermissions(
origin: origin,
savedAt: DateTime.now().millisecondsSinceEpoch,
);
final updatedPermissions = updater(currentPermissions);
final api = GeckoSitePermissionsApi();
await api.setSitePermissions(updatedPermissions, isPrivate);
// Invalidate the provider to refetch
ref.invalidate(sitePermissionsProvider(origin, isPrivate));
// Reload the tab to apply changes
await ref.read(selectedTabSessionProvider).reload();
}
Future<void> _updateAutoplay(
WidgetRef ref,
AutoplayStatus? audible,
AutoplayStatus? inaudible,
) async {
final currentPermissions = permissions ?? SitePermissions(
origin: origin,
savedAt: DateTime.now().millisecondsSinceEpoch,
);
final updatedPermissions = SitePermissions(
origin: currentPermissions.origin,
camera: currentPermissions.camera,
microphone: currentPermissions.microphone,
location: currentPermissions.location,
notification: currentPermissions.notification,
persistentStorage: currentPermissions.persistentStorage,
crossOriginStorageAccess: currentPermissions.crossOriginStorageAccess,
mediaKeySystemAccess: currentPermissions.mediaKeySystemAccess,
localDeviceAccess: currentPermissions.localDeviceAccess,
localNetworkAccess: currentPermissions.localNetworkAccess,
autoplayAudible: audible ?? currentPermissions.autoplayAudible,
autoplayInaudible: inaudible ?? currentPermissions.autoplayInaudible,
savedAt: currentPermissions.savedAt,
);
final api = GeckoSitePermissionsApi();
await api.setSitePermissions(updatedPermissions, isPrivate);
ref.invalidate(sitePermissionsProvider(origin, isPrivate));
await ref.read(selectedTabSessionProvider).reload();
}
// Copy helper methods since Pigeon doesn't generate copyWith
SitePermissions _copyWithCamera(SitePermissions p, SitePermissionStatus status) {
return SitePermissions(
origin: p.origin,
camera: status,
microphone: p.microphone,
location: p.location,
notification: p.notification,
persistentStorage: p.persistentStorage,
crossOriginStorageAccess: p.crossOriginStorageAccess,
mediaKeySystemAccess: p.mediaKeySystemAccess,
localDeviceAccess: p.localDeviceAccess,
localNetworkAccess: p.localNetworkAccess,
autoplayAudible: p.autoplayAudible,
autoplayInaudible: p.autoplayInaudible,
savedAt: p.savedAt,
);
}
SitePermissions _copyWithMicrophone(SitePermissions p, SitePermissionStatus status) {
return SitePermissions(
origin: p.origin,
camera: p.camera,
microphone: status,
location: p.location,
notification: p.notification,
persistentStorage: p.persistentStorage,
crossOriginStorageAccess: p.crossOriginStorageAccess,
mediaKeySystemAccess: p.mediaKeySystemAccess,
localDeviceAccess: p.localDeviceAccess,
localNetworkAccess: p.localNetworkAccess,
autoplayAudible: p.autoplayAudible,
autoplayInaudible: p.autoplayInaudible,
savedAt: p.savedAt,
);
}
SitePermissions _copyWithLocation(SitePermissions p, SitePermissionStatus status) {
return SitePermissions(
origin: p.origin,
camera: p.camera,
microphone: p.microphone,
location: status,
notification: p.notification,
persistentStorage: p.persistentStorage,
crossOriginStorageAccess: p.crossOriginStorageAccess,
mediaKeySystemAccess: p.mediaKeySystemAccess,
localDeviceAccess: p.localDeviceAccess,
localNetworkAccess: p.localNetworkAccess,
autoplayAudible: p.autoplayAudible,
autoplayInaudible: p.autoplayInaudible,
savedAt: p.savedAt,
);
}
SitePermissions _copyWithNotification(SitePermissions p, SitePermissionStatus status) {
return SitePermissions(
origin: p.origin,
camera: p.camera,
microphone: p.microphone,
location: p.location,
notification: status,
persistentStorage: p.persistentStorage,
crossOriginStorageAccess: p.crossOriginStorageAccess,
mediaKeySystemAccess: p.mediaKeySystemAccess,
localDeviceAccess: p.localDeviceAccess,
localNetworkAccess: p.localNetworkAccess,
autoplayAudible: p.autoplayAudible,
autoplayInaudible: p.autoplayInaudible,
savedAt: p.savedAt,
);
}
SitePermissions _copyWithPersistentStorage(SitePermissions p, SitePermissionStatus status) {
return SitePermissions(
origin: p.origin,
camera: p.camera,
microphone: p.microphone,
location: p.location,
notification: p.notification,
persistentStorage: status,
crossOriginStorageAccess: p.crossOriginStorageAccess,
mediaKeySystemAccess: p.mediaKeySystemAccess,
localDeviceAccess: p.localDeviceAccess,
localNetworkAccess: p.localNetworkAccess,
autoplayAudible: p.autoplayAudible,
autoplayInaudible: p.autoplayInaudible,
savedAt: p.savedAt,
);
}
SitePermissions _copyWithCrossOriginStorage(SitePermissions p, SitePermissionStatus status) {
return SitePermissions(
origin: p.origin,
camera: p.camera,
microphone: p.microphone,
location: p.location,
notification: p.notification,
persistentStorage: p.persistentStorage,
crossOriginStorageAccess: status,
mediaKeySystemAccess: p.mediaKeySystemAccess,
localDeviceAccess: p.localDeviceAccess,
localNetworkAccess: p.localNetworkAccess,
autoplayAudible: p.autoplayAudible,
autoplayInaudible: p.autoplayInaudible,
savedAt: p.savedAt,
);
}
SitePermissions _copyWithMediaKeySystem(SitePermissions p, SitePermissionStatus status) {
return SitePermissions(
origin: p.origin,
camera: p.camera,
microphone: p.microphone,
location: p.location,
notification: p.notification,
persistentStorage: p.persistentStorage,
crossOriginStorageAccess: p.crossOriginStorageAccess,
mediaKeySystemAccess: status,
localDeviceAccess: p.localDeviceAccess,
localNetworkAccess: p.localNetworkAccess,
autoplayAudible: p.autoplayAudible,
autoplayInaudible: p.autoplayInaudible,
savedAt: p.savedAt,
);
}
}
class _PermissionEntry {
final IconData icon;
final String label;
final SitePermissionStatus? status;
final ValueChanged<SitePermissionStatus> onChanged;
_PermissionEntry({
required this.icon,
required this.label,
required this.status,
required this.onChanged,
});
}
class _PermissionTile extends StatelessWidget {
final IconData icon;
final String label;
final SitePermissionStatus? status;
final ValueChanged<SitePermissionStatus> onChanged;
const _PermissionTile({
required this.icon,
required this.label,
required this.status,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
final currentStatus = status ?? SitePermissionStatus.noDecision;
return ListTile(
leading: Icon(icon),
title: Text(label),
trailing: DropdownButton<SitePermissionStatus>(
value: currentStatus,
underline: const SizedBox(),
items: const [
DropdownMenuItem(
value: SitePermissionStatus.noDecision,
child: Text('Ask'),
),
DropdownMenuItem(
value: SitePermissionStatus.allowed,
child: Text('Allow'),
),
DropdownMenuItem(
value: SitePermissionStatus.blocked,
child: Text('Block'),
),
],
onChanged: (value) {
if (value != null) {
onChanged(value);
}
},
),
);
}
}
class _AutoplayTile extends StatelessWidget {
final AutoplayStatus? audibleStatus;
final AutoplayStatus? inaudibleStatus;
final void Function(AutoplayStatus?, AutoplayStatus?) onChanged;
const _AutoplayTile({
required this.audibleStatus,
required this.inaudibleStatus,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
// Determine the combined autoplay setting
final combinedStatus = _getCombinedStatus();
return ListTile(
leading: const Icon(Icons.play_circle),
title: const Text('Autoplay'),
trailing: DropdownButton<_AutoplayCombined>(
value: combinedStatus,
underline: const SizedBox(),
items: const [
DropdownMenuItem(
value: _AutoplayCombined.allowAll,
child: Text('Allow All'),
),
DropdownMenuItem(
value: _AutoplayCombined.blockAudible,
child: Text('Block Audible'),
),
DropdownMenuItem(
value: _AutoplayCombined.blockAll,
child: Text('Block All'),
),
],
onChanged: (value) {
if (value == null) return;
switch (value) {
case _AutoplayCombined.allowAll:
onChanged(AutoplayStatus.allowed, AutoplayStatus.allowed);
case _AutoplayCombined.blockAudible:
onChanged(AutoplayStatus.blocked, AutoplayStatus.allowed);
case _AutoplayCombined.blockAll:
onChanged(AutoplayStatus.blocked, AutoplayStatus.blocked);
}
},
),
);
}
_AutoplayCombined _getCombinedStatus() {
final audible = audibleStatus ?? AutoplayStatus.blocked;
final inaudible = inaudibleStatus ?? AutoplayStatus.allowed;
if (audible == AutoplayStatus.allowed && inaudible == AutoplayStatus.allowed) {
return _AutoplayCombined.allowAll;
} else if (audible == AutoplayStatus.blocked && inaudible == AutoplayStatus.allowed) {
return _AutoplayCombined.blockAudible;
} else {
return _AutoplayCombined.blockAll;
}
}
}
enum _AutoplayCombined {
allowAll,
blockAudible,
blockAll,
}
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2024-2025 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'site_permissions_provider.g.dart';
/// Provider to fetch site permissions for a given origin
@Riverpod()
Future<SitePermissions?> sitePermissions(
Ref ref,
String origin,
bool isPrivate,
) async {
final api = GeckoSitePermissionsApi();
return api.getSitePermissions(origin, isPrivate);
}
/// Provider to get the public suffix plus one (eTLD+1) for a host
@Riverpod()
Future<String> publicSuffixPlusOne(
Ref ref,
String host,
) async {
final api = GeckoPublicSuffixListApi();
return api.getPublicSuffixPlusOne(host);
}
/// Notifier for managing selected clear data types
@Riverpod()
class SelectedClearDataTypes extends _$SelectedClearDataTypes {
@override
Set<ClearDataType> build() {
// Default to clearing all site data and auth sessions (most common use case)
return {
ClearDataType.allSiteData,
ClearDataType.authSessions,
};
}
void toggle(ClearDataType type) {
if (state.contains(type)) {
state = Set.from(state)..remove(type);
} else {
state = Set.from(state)..add(type);
}
}
void selectAll() {
state = Set.from(ClearDataType.values);
}
void clearAll() {
state = {};
}
}
@@ -0,0 +1,231 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'site_permissions_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Provider to fetch site permissions for a given origin
@ProviderFor(sitePermissions)
final sitePermissionsProvider = SitePermissionsFamily._();
/// Provider to fetch site permissions for a given origin
final class SitePermissionsProvider
extends
$FunctionalProvider<
AsyncValue<SitePermissions?>,
SitePermissions?,
FutureOr<SitePermissions?>
>
with $FutureModifier<SitePermissions?>, $FutureProvider<SitePermissions?> {
/// Provider to fetch site permissions for a given origin
SitePermissionsProvider._({
required SitePermissionsFamily super.from,
required (String, bool) super.argument,
}) : super(
retry: null,
name: r'sitePermissionsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$sitePermissionsHash();
@override
String toString() {
return r'sitePermissionsProvider'
''
'$argument';
}
@$internal
@override
$FutureProviderElement<SitePermissions?> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<SitePermissions?> create(Ref ref) {
final argument = this.argument as (String, bool);
return sitePermissions(ref, argument.$1, argument.$2);
}
@override
bool operator ==(Object other) {
return other is SitePermissionsProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$sitePermissionsHash() => r'c605e9a1acb261ec46659431b5bb1e105b8aa2a1';
/// Provider to fetch site permissions for a given origin
final class SitePermissionsFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<SitePermissions?>, (String, bool)> {
SitePermissionsFamily._()
: super(
retry: null,
name: r'sitePermissionsProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
/// Provider to fetch site permissions for a given origin
SitePermissionsProvider call(String origin, bool isPrivate) =>
SitePermissionsProvider._(argument: (origin, isPrivate), from: this);
@override
String toString() => r'sitePermissionsProvider';
}
/// Provider to get the public suffix plus one (eTLD+1) for a host
@ProviderFor(publicSuffixPlusOne)
final publicSuffixPlusOneProvider = PublicSuffixPlusOneFamily._();
/// Provider to get the public suffix plus one (eTLD+1) for a host
final class PublicSuffixPlusOneProvider
extends $FunctionalProvider<AsyncValue<String>, String, FutureOr<String>>
with $FutureModifier<String>, $FutureProvider<String> {
/// Provider to get the public suffix plus one (eTLD+1) for a host
PublicSuffixPlusOneProvider._({
required PublicSuffixPlusOneFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'publicSuffixPlusOneProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$publicSuffixPlusOneHash();
@override
String toString() {
return r'publicSuffixPlusOneProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<String> $createElement($ProviderPointer pointer) =>
$FutureProviderElement(pointer);
@override
FutureOr<String> create(Ref ref) {
final argument = this.argument as String;
return publicSuffixPlusOne(ref, argument);
}
@override
bool operator ==(Object other) {
return other is PublicSuffixPlusOneProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$publicSuffixPlusOneHash() =>
r'e259f66e3ebf278468223d8e25618eb9f67a3504';
/// Provider to get the public suffix plus one (eTLD+1) for a host
final class PublicSuffixPlusOneFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<String>, String> {
PublicSuffixPlusOneFamily._()
: super(
retry: null,
name: r'publicSuffixPlusOneProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
/// Provider to get the public suffix plus one (eTLD+1) for a host
PublicSuffixPlusOneProvider call(String host) =>
PublicSuffixPlusOneProvider._(argument: host, from: this);
@override
String toString() => r'publicSuffixPlusOneProvider';
}
/// Notifier for managing selected clear data types
@ProviderFor(SelectedClearDataTypes)
final selectedClearDataTypesProvider = SelectedClearDataTypesProvider._();
/// Notifier for managing selected clear data types
final class SelectedClearDataTypesProvider
extends $NotifierProvider<SelectedClearDataTypes, Set<ClearDataType>> {
/// Notifier for managing selected clear data types
SelectedClearDataTypesProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'selectedClearDataTypesProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$selectedClearDataTypesHash();
@$internal
@override
SelectedClearDataTypes create() => SelectedClearDataTypes();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Set<ClearDataType> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Set<ClearDataType>>(value),
);
}
}
String _$selectedClearDataTypesHash() =>
r'f010b5dc724bd214add09f7f85c56dd615003915';
/// Notifier for managing selected clear data types
abstract class _$SelectedClearDataTypes extends $Notifier<Set<ClearDataType>> {
Set<ClearDataType> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<Set<ClearDataType>, Set<ClearDataType>>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<Set<ClearDataType>, Set<ClearDataType>>,
Set<ClearDataType>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@@ -27,6 +27,8 @@ import 'package:weblibre/features/bangs/presentation/widgets/site_search.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/certificate_tile.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/draggable_scrollable_header.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/clear_site_data_section.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/permissions_section.dart';
import 'package:weblibre/presentation/widgets/website_title_tile.dart';
class ClampingScrollPhysicsWithoutImplicit extends ClampingScrollPhysics {
@@ -206,6 +208,17 @@ class ViewTabSheetWidget extends HookConsumerWidget {
),
),
const Divider(),
// Permissions Section
PermissionsSection(
origin: initialTabState.url.origin,
isPrivate: initialTabState.isPrivate,
),
const Divider(),
// Clear Site Data Section
ClearSiteDataSection(
url: initialTabState.url,
),
const SizedBox(height: 16.0),
],
);
},