add permissions badge
This commit is contained in:
@@ -11,6 +11,7 @@ class AppColors extends ThemeExtension<AppColors> {
|
||||
this.torPurple = const Color(0xFF7D4698),
|
||||
this.torActiveGreen = const Color(0xFF68B030),
|
||||
this.torBackgroundGrey = const Color(0xFF333A41),
|
||||
this.warningAmber = const Color(0xFFFFA000),
|
||||
});
|
||||
|
||||
final Color seedColor;
|
||||
@@ -21,6 +22,7 @@ class AppColors extends ThemeExtension<AppColors> {
|
||||
final Color torPurple;
|
||||
final Color torActiveGreen;
|
||||
final Color torBackgroundGrey;
|
||||
final Color warningAmber;
|
||||
|
||||
static const light = AppColors._();
|
||||
static const dark = AppColors._();
|
||||
@@ -35,6 +37,7 @@ class AppColors extends ThemeExtension<AppColors> {
|
||||
Color? torPurple,
|
||||
Color? torActiveGreen,
|
||||
Color? torBackgroundGrey,
|
||||
Color? warningAmber,
|
||||
}) {
|
||||
return AppColors._(
|
||||
seedColor: seedColor ?? this.seedColor,
|
||||
@@ -46,6 +49,7 @@ class AppColors extends ThemeExtension<AppColors> {
|
||||
torPurple: torPurple ?? this.torPurple,
|
||||
torActiveGreen: torActiveGreen ?? this.torActiveGreen,
|
||||
torBackgroundGrey: torBackgroundGrey ?? this.torBackgroundGrey,
|
||||
warningAmber: warningAmber ?? this.warningAmber,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,6 +88,7 @@ class AppColors extends ThemeExtension<AppColors> {
|
||||
other.torBackgroundGrey,
|
||||
t,
|
||||
)!,
|
||||
warningAmber: Color.lerp(warningAmber, other.warningAmber, t)!,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -4,6 +4,8 @@ import 'package:weblibre/features/geckoview/features/browser/domain/entities/sit
|
||||
|
||||
part 'site_permissions.g.dart';
|
||||
|
||||
typedef PermissionUpdater = SitePermissions Function(SitePermissionsWrapper);
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class SitePermissionsRepository extends _$SitePermissionsRepository {
|
||||
final _api = GeckoSitePermissionsApi();
|
||||
@@ -15,9 +17,7 @@ class SitePermissionsRepository extends _$SitePermissionsRepository {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
Future<void> updatePermission(
|
||||
SitePermissions Function(SitePermissionsWrapper) updater,
|
||||
) async {
|
||||
Future<void> updatePermission(PermissionUpdater updater) async {
|
||||
final currentPermissions =
|
||||
await getPermissions() ??
|
||||
SitePermissions(
|
||||
@@ -48,7 +48,8 @@ class SitePermissionsRepository extends _$SitePermissionsRepository {
|
||||
Future<SitePermissions?> build({
|
||||
required String origin,
|
||||
required bool isPrivate,
|
||||
}) {
|
||||
return _api.getSitePermissions(origin, isPrivate);
|
||||
}) async {
|
||||
final permissions = await _api.getSitePermissions(origin, isPrivate);
|
||||
return permissions;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ final class SitePermissionsRepositoryProvider
|
||||
}
|
||||
|
||||
String _$sitePermissionsRepositoryHash() =>
|
||||
r'83033a26cd7c9473a7ac9a23f91787af8ed4ce86';
|
||||
r'b821fbd3a457f5b9cfc6c9d398c93f09c6b30b0b';
|
||||
|
||||
final class SitePermissionsRepositoryFamily extends $Family
|
||||
with
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/domain/repositories/site_permissions.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/tracking_protection_provider.dart';
|
||||
|
||||
part 'site_settings_badge_provider.g.dart';
|
||||
|
||||
/// Provider that determines whether to show the site settings badge on the tab icon.
|
||||
/// Returns true if any site-specific setting has been altered from defaults.
|
||||
@Riverpod()
|
||||
Future<bool> showSiteSettingsBadge(Ref ref) async {
|
||||
final tabState = ref.watch(selectedTabStateProvider);
|
||||
if (tabState == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check tracking protection exception
|
||||
final hasTrackingException = await ref.watch(
|
||||
hasTrackingProtectionExceptionProvider(tabState.id).future,
|
||||
);
|
||||
|
||||
// Watch site permissions
|
||||
final permissions = await ref.watch(
|
||||
sitePermissionsRepositoryProvider(
|
||||
origin: tabState.url.origin,
|
||||
isPrivate: tabState.isPrivate,
|
||||
).future,
|
||||
);
|
||||
|
||||
if (hasTrackingException) {
|
||||
return true;
|
||||
}
|
||||
// Check for altered permissions
|
||||
if (_hasAlteredPermissions(permissions)) {
|
||||
return true;
|
||||
}
|
||||
// Check for altered autoplay settings
|
||||
if (_hasAlteredAutoplay(permissions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Checks if any permission has been explicitly set to allowed or blocked
|
||||
bool _hasAlteredPermissions(SitePermissions? permissions) {
|
||||
if (permissions == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check all permission fields
|
||||
final permissionStatuses = [
|
||||
permissions.camera,
|
||||
permissions.microphone,
|
||||
permissions.location,
|
||||
permissions.notification,
|
||||
permissions.persistentStorage,
|
||||
permissions.crossOriginStorageAccess,
|
||||
permissions.mediaKeySystemAccess,
|
||||
permissions.localDeviceAccess,
|
||||
permissions.localNetworkAccess,
|
||||
];
|
||||
|
||||
return permissionStatuses.any(
|
||||
(status) =>
|
||||
status != null &&
|
||||
(status == SitePermissionStatus.allowed ||
|
||||
status == SitePermissionStatus.blocked),
|
||||
);
|
||||
}
|
||||
|
||||
/// Checks if autoplay settings differ from defaults
|
||||
/// Default: autoplayAudible = blocked (null), autoplayInaudible = allowed (null)
|
||||
bool _hasAlteredAutoplay(SitePermissions? permissions) {
|
||||
if (permissions == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final audible = permissions.autoplayAudible;
|
||||
final inaudible = permissions.autoplayInaudible;
|
||||
|
||||
// Check if audible differs from default (null or blocked)
|
||||
final audibleAltered = audible != null && audible != AutoplayStatus.blocked;
|
||||
|
||||
// Check if inaudible differs from default (null or allowed)
|
||||
final inaudibleAltered =
|
||||
inaudible != null && inaudible != AutoplayStatus.allowed;
|
||||
|
||||
return audibleAltered || inaudibleAltered;
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'site_settings_badge_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Provider that determines whether to show the site settings badge on the tab icon.
|
||||
/// Returns true if any site-specific setting has been altered from defaults.
|
||||
|
||||
@ProviderFor(showSiteSettingsBadge)
|
||||
final showSiteSettingsBadgeProvider = ShowSiteSettingsBadgeProvider._();
|
||||
|
||||
/// Provider that determines whether to show the site settings badge on the tab icon.
|
||||
/// Returns true if any site-specific setting has been altered from defaults.
|
||||
|
||||
final class ShowSiteSettingsBadgeProvider
|
||||
extends $FunctionalProvider<AsyncValue<bool>, bool, FutureOr<bool>>
|
||||
with $FutureModifier<bool>, $FutureProvider<bool> {
|
||||
/// Provider that determines whether to show the site settings badge on the tab icon.
|
||||
/// Returns true if any site-specific setting has been altered from defaults.
|
||||
ShowSiteSettingsBadgeProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'showSiteSettingsBadgeProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$showSiteSettingsBadgeHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$FutureProviderElement<bool> $createElement($ProviderPointer pointer) =>
|
||||
$FutureProviderElement(pointer);
|
||||
|
||||
@override
|
||||
FutureOr<bool> create(Ref ref) {
|
||||
return showSiteSettingsBadge(ref);
|
||||
}
|
||||
}
|
||||
|
||||
String _$showSiteSettingsBadgeHash() =>
|
||||
r'8b759a67e5c8b0f49a5d38b793a3357576a2eb87';
|
||||
+23
-1
@@ -28,6 +28,7 @@ import 'package:weblibre/core/routing/routes.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/toolbar_button.dart';
|
||||
import 'package:weblibre/presentation/widgets/uri_breadcrumb.dart';
|
||||
@@ -42,6 +43,9 @@ class AppBarTitle extends HookConsumerWidget {
|
||||
|
||||
final tabState = ref.watch(selectedTabStateProvider);
|
||||
final isTabTuneledAsync = ref.watch(isTabTunneledProvider(tabState?.id));
|
||||
final showSiteSettingsBadge = ref.watch(
|
||||
showSiteSettingsBadgeProvider.select((value) => value.value == true),
|
||||
);
|
||||
|
||||
if (tabState == null) {
|
||||
return const SizedBox.shrink();
|
||||
@@ -77,7 +81,25 @@ class AppBarTitle extends HookConsumerWidget {
|
||||
.read(bottomSheetControllerProvider.notifier)
|
||||
.show(SiteSettingsSheet(tabState: tabState));
|
||||
},
|
||||
child: TabIcon(tabState: tabState, iconSize: 24),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
TabIcon(tabState: tabState, iconSize: 24),
|
||||
if (showSiteSettingsBadge)
|
||||
Positioned(
|
||||
top: -4,
|
||||
right: -4,
|
||||
child: Icon(
|
||||
MdiIcons.shieldHalfFull,
|
||||
size: 10,
|
||||
color: appColors.warningAmber,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Title/URL tap → opens search screen
|
||||
Expanded(
|
||||
|
||||
+3
-5
@@ -77,9 +77,7 @@ class _PermissionsList extends HookConsumerWidget {
|
||||
final showAll = useState(false);
|
||||
|
||||
// Memoize the update callback to avoid recreating closures
|
||||
final updatePermission = useCallback((
|
||||
SitePermissions Function(SitePermissionsWrapper) updater,
|
||||
) async {
|
||||
Future<void> updatePermission(PermissionUpdater updater) async {
|
||||
await ref
|
||||
.read(
|
||||
sitePermissionsRepositoryProvider(
|
||||
@@ -89,7 +87,7 @@ class _PermissionsList extends HookConsumerWidget {
|
||||
)
|
||||
.updatePermission(updater);
|
||||
await ref.read(selectedTabSessionProvider).reload();
|
||||
}, [origin, isPrivate]);
|
||||
}
|
||||
|
||||
// Build permission entries - only recalculates when permissions change
|
||||
final allPermissions = useMemoized(
|
||||
@@ -104,7 +102,7 @@ class _PermissionsList extends HookConsumerWidget {
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
[permissions, updatePermission],
|
||||
[permissions],
|
||||
);
|
||||
|
||||
// Filter to only show permissions that have been explicitly set (not noDecision)
|
||||
|
||||
Reference in New Issue
Block a user