diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.dart index d5302f9a..63ba3592 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.dart @@ -27,13 +27,26 @@ import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode 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. +/// Indicates whether the site settings badge should be shown on the tab icon +/// and, if so, which direction settings have been changed. +enum SiteSettingsBadgeState { + /// No per-site settings differ from defaults. + hidden, + + /// At least one per-site setting is stricter than the default. + improved, + + /// At least one per-site setting is looser than the default. + /// Tracking-protection exceptions also count as weakened. + weakened, +} + +/// Provider that determines the site settings badge state on the tab icon. @Riverpod() -Future showSiteSettingsBadge(Ref ref) async { +Future showSiteSettingsBadge(Ref ref) async { final tabState = ref.watch(selectedTabStateProvider); if (tabState == null) { - return false; + return SiteSettingsBadgeState.hidden; } // Check tracking protection exception @@ -42,7 +55,7 @@ Future showSiteSettingsBadge(Ref ref) async { ); if (!tabState.url.hasScheme || !tabState.url.isHttpOrHttps) { - return false; + return SiteSettingsBadgeState.hidden; } // Watch site permissions @@ -54,27 +67,24 @@ Future showSiteSettingsBadge(Ref ref) async { ); if (hasTrackingException) { - return true; - } - // Check for altered permissions - if (_hasAlteredPermissions(permissions)) { - return true; - } - // Check for altered autoplay settings - if (_hasAlteredAutoplay(permissions)) { - return true; + return SiteSettingsBadgeState.weakened; } - return false; + final badgeState = _evaluateBadgeState(permissions); + return badgeState; } -/// Checks if any permission has been explicitly set to allowed or blocked -bool _hasAlteredPermissions(SitePermissions? permissions) { +/// Evaluates whether site permissions are default, improved, or weakened. +/// Weakened takes precedence over improved. +SiteSettingsBadgeState _evaluateBadgeState(SitePermissions? permissions) { if (permissions == null) { - return false; + return SiteSettingsBadgeState.hidden; } - // Check all permission fields + var hasImproved = false; + var hasWeakened = false; + + // Evaluate permission-type fields final permissionStatuses = [ permissions.camera, permissions.microphone, @@ -87,30 +97,40 @@ bool _hasAlteredPermissions(SitePermissions? permissions) { 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; + for (final status in permissionStatuses) { + if (status == null) { + continue; + } + if (status == SitePermissionStatus.blocked) { + hasImproved = true; + } else if (status == SitePermissionStatus.allowed) { + hasWeakened = true; + } + // noDecision is the default -> no effect } + // Evaluate autoplay audible final audible = permissions.autoplayAudible; + if (audible != null && audible != AutoplayStatus.blocked) { + hasWeakened = true; + } + + // Evaluate autoplay inaudible final inaudible = permissions.autoplayInaudible; + if (inaudible != null) { + if (inaudible == AutoplayStatus.blocked) { + hasImproved = true; + } else if (inaudible != AutoplayStatus.allowed) { + hasWeakened = true; + } + } - // Check if audible differs from default (null or blocked) - final audibleAltered = audible != null && audible != AutoplayStatus.blocked; + if (hasWeakened) { + return SiteSettingsBadgeState.weakened; + } + if (hasImproved) { + return SiteSettingsBadgeState.improved; + } - // Check if inaudible differs from default (null or allowed) - final inaudibleAltered = - inaudible != null && inaudible != AutoplayStatus.allowed; - - return audibleAltered || inaudibleAltered; + return SiteSettingsBadgeState.hidden; } diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.g.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.g.dart index a88b8661..6887cd5e 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.g.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.g.dart @@ -8,20 +8,24 @@ part of 'site_settings_badge_provider.dart'; // 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. +/// Provider that determines the site settings badge state on the tab icon. @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. +/// Provider that determines the site settings badge state on the tab icon. final class ShowSiteSettingsBadgeProvider - extends $FunctionalProvider, bool, FutureOr> - with $FutureModifier, $FutureProvider { - /// 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. + extends + $FunctionalProvider< + AsyncValue, + SiteSettingsBadgeState, + FutureOr + > + with + $FutureModifier, + $FutureProvider { + /// Provider that determines the site settings badge state on the tab icon. ShowSiteSettingsBadgeProvider._() : super( from: null, @@ -38,14 +42,15 @@ final class ShowSiteSettingsBadgeProvider @$internal @override - $FutureProviderElement $createElement($ProviderPointer pointer) => - $FutureProviderElement(pointer); + $FutureProviderElement $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); @override - FutureOr create(Ref ref) { + FutureOr create(Ref ref) { return showSiteSettingsBadge(ref); } } String _$showSiteSettingsBadgeHash() => - r'c68e3b37b6f25e02779123b3f642a58dc98ba3a7'; + r'fbfe409cdd6656d5cd26e377a68acde6ff4294fa'; diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart index 4384f2fa..8844be18 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart @@ -46,8 +46,10 @@ class CompactAppBarTitle extends ConsumerWidget { final selectedTabType = ref.watch(selectedTabTypeProvider); final settings = ref.watch(generalSettingsWithDefaultsProvider); final isTabTuneledAsync = ref.watch(isTabTunneledProvider(tabState?.id)); - final showSiteSettingsBadge = ref.watch( - showSiteSettingsBadgeProvider.select((value) => value.value == true), + final siteSettingsBadgeState = ref.watch( + showSiteSettingsBadgeProvider.select( + (value) => value.value ?? SiteSettingsBadgeState.hidden, + ), ); if (tabState == null) { @@ -64,7 +66,7 @@ class CompactAppBarTitle extends ConsumerWidget { tabState: tabState, isTabTunneled: isTabTuneledAsync.hasValue && isTabTuneledAsync.value == true, - showSiteSettingsBadge: showSiteSettingsBadge, + siteSettingsBadgeState: siteSettingsBadgeState, longPressUrlCopy: settings.tabBarLongPressUrlCopy, onSiteSettingsTap: () { ref @@ -87,7 +89,7 @@ class CompactAppBarTitleView extends StatelessWidget { super.key, required this.tabState, required this.isTabTunneled, - required this.showSiteSettingsBadge, + required this.siteSettingsBadgeState, required this.onSiteSettingsTap, required this.onTitleTap, this.tabIcon, @@ -96,7 +98,7 @@ class CompactAppBarTitleView extends StatelessWidget { final TabState tabState; final bool isTabTunneled; - final bool showSiteSettingsBadge; + final SiteSettingsBadgeState siteSettingsBadgeState; final VoidCallback onSiteSettingsTap; final VoidCallback onTitleTap; final Widget? tabIcon; @@ -117,17 +119,22 @@ class CompactAppBarTitleView extends StatelessWidget { clipBehavior: Clip.none, children: [ tabIcon ?? TabIcon(tabState: tabState, iconSize: 24), - Positioned( - top: -4, - right: -4, - child: Icon( - MdiIcons.shieldHalfFull, - size: 10, - color: showSiteSettingsBadge - ? appColors.warningAmber - : Colors.green, + if (siteSettingsBadgeState != SiteSettingsBadgeState.hidden) + Positioned( + top: -4, + right: -4, + child: Icon( + siteSettingsBadgeState == SiteSettingsBadgeState.improved + ? MdiIcons.shield + : MdiIcons.shieldAlert, + size: 10, + color: + siteSettingsBadgeState == + SiteSettingsBadgeState.improved + ? Colors.green + : appColors.warningAmber, + ), ), - ), ], ), ), @@ -202,8 +209,10 @@ class AppBarTitle extends ConsumerWidget { final selectedTabType = ref.watch(selectedTabTypeProvider); final settings = ref.watch(generalSettingsWithDefaultsProvider); final isTabTuneledAsync = ref.watch(isTabTunneledProvider(tabState?.id)); - final showSiteSettingsBadge = ref.watch( - showSiteSettingsBadgeProvider.select((value) => value.value == true), + final siteSettingsBadgeState = ref.watch( + showSiteSettingsBadgeProvider.select( + (value) => value.value ?? SiteSettingsBadgeState.hidden, + ), ); if (tabState == null) { @@ -220,7 +229,7 @@ class AppBarTitle extends ConsumerWidget { tabState: tabState, isTabTunneled: isTabTuneledAsync.hasValue && isTabTuneledAsync.value == true, - showSiteSettingsBadge: showSiteSettingsBadge, + siteSettingsBadgeState: siteSettingsBadgeState, longPressUrlCopy: settings.tabBarLongPressUrlCopy, onSiteSettingsTap: () { ref @@ -243,7 +252,7 @@ class AppBarTitleView extends StatelessWidget { super.key, required this.tabState, required this.isTabTunneled, - required this.showSiteSettingsBadge, + required this.siteSettingsBadgeState, required this.onSiteSettingsTap, required this.onTitleTap, required this.longPressUrlCopy, @@ -252,7 +261,7 @@ class AppBarTitleView extends StatelessWidget { final TabState tabState; final bool isTabTunneled; - final bool showSiteSettingsBadge; + final SiteSettingsBadgeState siteSettingsBadgeState; final VoidCallback onSiteSettingsTap; final VoidCallback onTitleTap; final Widget? tabIcon; @@ -273,17 +282,22 @@ class AppBarTitleView extends StatelessWidget { clipBehavior: Clip.none, children: [ tabIcon ?? TabIcon(tabState: tabState, iconSize: 24), - Positioned( - top: -4, - right: -4, - child: Icon( - MdiIcons.shieldHalfFull, - size: 10, - color: showSiteSettingsBadge - ? appColors.warningAmber - : Colors.green, + if (siteSettingsBadgeState != SiteSettingsBadgeState.hidden) + Positioned( + top: -4, + right: -4, + child: Icon( + siteSettingsBadgeState == SiteSettingsBadgeState.improved + ? MdiIcons.shield + : MdiIcons.shieldAlert, + size: 10, + color: + siteSettingsBadgeState == + SiteSettingsBadgeState.improved + ? Colors.green + : appColors.warningAmber, + ), ), - ), ], ), ), diff --git a/apps/weblibre/lib/features/settings/presentation/widgets/toolbar_preview.dart b/apps/weblibre/lib/features/settings/presentation/widgets/toolbar_preview.dart index 846ecf14..f8068b33 100644 --- a/apps/weblibre/lib/features/settings/presentation/widgets/toolbar_preview.dart +++ b/apps/weblibre/lib/features/settings/presentation/widgets/toolbar_preview.dart @@ -24,6 +24,7 @@ import 'package:weblibre/features/geckoview/domain/entities/states/security.dart import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_bar_buttons.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_toolbar.dart'; +import 'package:weblibre/features/geckoview/features/browser/presentation/providers/site_settings_badge_provider.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/navigation_buttons.dart'; @@ -351,7 +352,7 @@ class _RegularPreviewTitle extends StatelessWidget { return AppBarTitleView( tabState: tabState, isTabTunneled: false, - showSiteSettingsBadge: false, + siteSettingsBadgeState: SiteSettingsBadgeState.hidden, onSiteSettingsTap: _noop, onTitleTap: _noop, tabIcon: const Icon(MdiIcons.web, size: 24), @@ -370,7 +371,7 @@ class _CompactPreviewTitle extends StatelessWidget { return CompactAppBarTitleView( tabState: tabState, isTabTunneled: false, - showSiteSettingsBadge: false, + siteSettingsBadgeState: SiteSettingsBadgeState.hidden, onSiteSettingsTap: _noop, onTitleTap: _noop, tabIcon: const Icon(MdiIcons.web, size: 24),