From f7496ff3e3ccbb1d023df15427e1cabbe3105615 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Wed, 11 Feb 2026 11:51:46 +0100 Subject: [PATCH] add setting to show favicon instead of thumbnail in tab list (#174) --- .../geckoview/domain/repositories/tab.g.dart | 2 +- .../widgets/tab_view/tab_preview.dart | 21 +++++++++----- .../screens/appearance_display_settings.dart | 29 +++++++++++++++++++ .../user/data/models/general_settings.dart | 7 ++++- .../user/data/models/general_settings.g.dart | 16 ++++++++++ .../domain/repositories/general_settings.dart | 4 +++ .../repositories/general_settings.g.dart | 2 +- 7 files changed, 70 insertions(+), 11 deletions(-) diff --git a/app/lib/features/geckoview/domain/repositories/tab.g.dart b/app/lib/features/geckoview/domain/repositories/tab.g.dart index f03e2dd9..9d51e619 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.g.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.g.dart @@ -41,7 +41,7 @@ final class TabRepositoryProvider } } -String _$tabRepositoryHash() => r'0158f3aeb942dda3dab07c1129ced1695c7703c7'; +String _$tabRepositoryHash() => r'db35cef59703f15f1b099b6adb42f7623bec612f'; abstract class _$TabRepository extends $Notifier { void build(); diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_preview.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_preview.dart index 7f86ee51..a8fb159a 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_preview.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_preview.dart @@ -32,6 +32,7 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget import 'package:weblibre/features/geckoview/features/find_in_page/domain/entities/find_in_page_state.dart'; import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart'; +import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/presentation/hooks/menu_controller.dart'; import 'package:weblibre/presentation/widgets/safe_raw_image.dart'; import 'package:weblibre/presentation/widgets/uri_breadcrumb.dart'; @@ -275,8 +276,19 @@ class ListTabPreview extends HookConsumerWidget { ) ?? TabState.$default(tabId); + final tabListShowFavicons = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.tabListShowFavicons), + ); + final extendedDeleteMenuController = useMenuController(); + final leadingWidget = switch ((tabListShowFavicons, tabState.thumbnail)) { + (false, final thumbnail?) when !thumbnail.isDisposed => RepaintBoundary( + child: SafeRawImage(image: thumbnail, fit: BoxFit.fitHeight), + ), + _ => TabIcon(tabState: tabState, iconSize: 32), + }; + return Container( decoration: BoxDecoration( color: tabState.isPrivate ? appColors.privateTabBackground : null, @@ -291,14 +303,7 @@ class ListTabPreview extends HookConsumerWidget { onTap: onTap, onLongPress: onLongPress, contentPadding: const EdgeInsets.only(left: 4), - leading: (tabState.thumbnail != null && !tabState.thumbnail!.isDisposed) - ? RepaintBoundary( - child: SafeRawImage( - image: tabState.thumbnail, - fit: BoxFit.fitHeight, - ), - ) - : TabIcon(tabState: tabState, iconSize: 32), + leading: leadingWidget, title: Text( overflow: TextOverflow.ellipsis, tabState.titleOrAuthority, diff --git a/app/lib/features/settings/presentation/screens/appearance_display_settings.dart b/app/lib/features/settings/presentation/screens/appearance_display_settings.dart index a2ef47fc..da293fc1 100644 --- a/app/lib/features/settings/presentation/screens/appearance_display_settings.dart +++ b/app/lib/features/settings/presentation/screens/appearance_display_settings.dart @@ -79,6 +79,7 @@ class _TabBarLayoutSection extends StatelessWidget { _ShowContextualTabBarTile(), _AutoHideTabBarTile(), _BottomSheetTabViewTile(), + _TabListShowFaviconsTile(), _ShowQuickTabSwitcherBarTile(), _QuickTabSwitcherModeSection(), ], @@ -377,6 +378,34 @@ class _BottomSheetTabViewTile extends HookConsumerWidget { } } +class _TabListShowFaviconsTile extends HookConsumerWidget { + const _TabListShowFaviconsTile(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final tabListShowFavicons = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.tabListShowFavicons), + ); + + return SwitchListTile.adaptive( + title: const Text('Show Favicons in List View'), + subtitle: const Text( + 'Display website icons instead of page thumbnails in tab list view', + ), + secondary: const Icon(MdiIcons.web), + value: tabListShowFavicons, + onChanged: (value) async { + await ref + .read(saveGeneralSettingsControllerProvider.notifier) + .save( + (currentSettings) => + currentSettings.copyWith.tabListShowFavicons(value), + ); + }, + ); + } +} + class _PullToRefreshTile extends HookConsumerWidget { const _PullToRefreshTile(); diff --git a/app/lib/features/user/data/models/general_settings.dart b/app/lib/features/user/data/models/general_settings.dart index 5ef60e7a..c9f62be1 100644 --- a/app/lib/features/user/data/models/general_settings.dart +++ b/app/lib/features/user/data/models/general_settings.dart @@ -86,6 +86,7 @@ class GeneralSettings with FastEquatable { final Duration unassignedTabsAutoCleanInterval; final int maxSearchHistoryEntries; final bool allowClipboardAccess; + final bool tabListShowFavicons; GeneralSettings({ required this.themeMode, @@ -114,6 +115,7 @@ class GeneralSettings with FastEquatable { required this.unassignedTabsAutoCleanInterval, required this.maxSearchHistoryEntries, required this.allowClipboardAccess, + required this.tabListShowFavicons, }); GeneralSettings.withDefaults({ @@ -143,6 +145,7 @@ class GeneralSettings with FastEquatable { Duration? unassignedTabsAutoCleanInterval, int? maxSearchHistoryEntries, bool? allowClipboardAccess, + bool? tabListShowFavicons, }) : themeMode = themeMode ?? ThemeMode.dark, enableReadability = enableReadability ?? true, enforceReadability = enforceReadability ?? false, @@ -172,7 +175,8 @@ class GeneralSettings with FastEquatable { unassignedTabsAutoCleanInterval = unassignedTabsAutoCleanInterval ?? Duration.zero, maxSearchHistoryEntries = maxSearchHistoryEntries ?? 5, - allowClipboardAccess = allowClipboardAccess ?? true; + allowClipboardAccess = allowClipboardAccess ?? true, + tabListShowFavicons = tabListShowFavicons ?? false; factory GeneralSettings.fromJson(Map json) => _$GeneralSettingsFromJson(json); @@ -207,5 +211,6 @@ class GeneralSettings with FastEquatable { unassignedTabsAutoCleanInterval, maxSearchHistoryEntries, allowClipboardAccess, + tabListShowFavicons, ]; } diff --git a/app/lib/features/user/data/models/general_settings.g.dart b/app/lib/features/user/data/models/general_settings.g.dart index 1c3ea028..5431887e 100644 --- a/app/lib/features/user/data/models/general_settings.g.dart +++ b/app/lib/features/user/data/models/general_settings.g.dart @@ -71,6 +71,8 @@ abstract class _$GeneralSettingsCWProxy { GeneralSettings allowClipboardAccess(bool allowClipboardAccess); + GeneralSettings tabListShowFavicons(bool tabListShowFavicons); + /// 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)`. /// @@ -105,6 +107,7 @@ abstract class _$GeneralSettingsCWProxy { Duration unassignedTabsAutoCleanInterval, int maxSearchHistoryEntries, bool allowClipboardAccess, + bool tabListShowFavicons, }); } @@ -224,6 +227,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { GeneralSettings allowClipboardAccess(bool allowClipboardAccess) => call(allowClipboardAccess: allowClipboardAccess); + @override + GeneralSettings tabListShowFavicons(bool tabListShowFavicons) => + call(tabListShowFavicons: tabListShowFavicons); + @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)`. @@ -259,6 +266,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { Object? unassignedTabsAutoCleanInterval = const $CopyWithPlaceholder(), Object? maxSearchHistoryEntries = const $CopyWithPlaceholder(), Object? allowClipboardAccess = const $CopyWithPlaceholder(), + Object? tabListShowFavicons = const $CopyWithPlaceholder(), }) { return GeneralSettings( themeMode: themeMode == const $CopyWithPlaceholder() || themeMode == null @@ -413,6 +421,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { ? _value.allowClipboardAccess // ignore: cast_nullable_to_non_nullable : allowClipboardAccess as bool, + tabListShowFavicons: + tabListShowFavicons == const $CopyWithPlaceholder() || + tabListShowFavicons == null + ? _value.tabListShowFavicons + // ignore: cast_nullable_to_non_nullable + : tabListShowFavicons as bool, ); } } @@ -489,6 +503,7 @@ GeneralSettings _$GeneralSettingsFromJson( ), maxSearchHistoryEntries: (json['maxSearchHistoryEntries'] as num?)?.toInt(), allowClipboardAccess: json['allowClipboardAccess'] as bool?, + tabListShowFavicons: json['tabListShowFavicons'] as bool?, ); Map _$GeneralSettingsToJson( @@ -529,6 +544,7 @@ Map _$GeneralSettingsToJson( instance.unassignedTabsAutoCleanInterval.inMicroseconds, 'maxSearchHistoryEntries': instance.maxSearchHistoryEntries, 'allowClipboardAccess': instance.allowClipboardAccess, + 'tabListShowFavicons': instance.tabListShowFavicons, }; const _$ThemeModeEnumMap = { diff --git a/app/lib/features/user/domain/repositories/general_settings.dart b/app/lib/features/user/domain/repositories/general_settings.dart index 3f7284f1..5a99c907 100644 --- a/app/lib/features/user/domain/repositories/general_settings.dart +++ b/app/lib/features/user/domain/repositories/general_settings.dart @@ -145,6 +145,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository { DriftSqlType.bool, db.typeMapping, ), + 'tabListShowFavicons': settings['tabListShowFavicons']?.readAs( + DriftSqlType.bool, + db.typeMapping, + ), }); } diff --git a/app/lib/features/user/domain/repositories/general_settings.g.dart b/app/lib/features/user/domain/repositories/general_settings.g.dart index c6504a59..28d277f3 100644 --- a/app/lib/features/user/domain/repositories/general_settings.g.dart +++ b/app/lib/features/user/domain/repositories/general_settings.g.dart @@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider } String _$generalSettingsRepositoryHash() => - r'302d33b57212a8f3affcf13a8ef5227d68e56ecb'; + r'a4e4fb6f4e5b40d32b4cc676532af3bacdbe54d2'; abstract class _$GeneralSettingsRepository extends $StreamNotifier {