feature extension shortcut

This commit is contained in:
Fabian Freund
2025-07-09 23:46:08 +02:00
parent 8a23f37887
commit 324474f2fc
7 changed files with 116 additions and 2 deletions
@@ -18,10 +18,12 @@ import 'package:weblibre/features/geckoview/features/browser/domain/entities/she
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/app_bar_title.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/edit_url_dialog.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/edit_url_dialog.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_shortcut_menu.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_creation_menu.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_creation_menu.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tabs_action_button.dart';
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart'; import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.dart'; import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/presentation/hooks/menu_controller.dart'; import 'package:weblibre/presentation/hooks/menu_controller.dart';
import 'package:weblibre/presentation/icons/tor_icons.dart'; import 'package:weblibre/presentation/icons/tor_icons.dart';
import 'package:weblibre/utils/ui_helper.dart' as ui_helper; import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
@@ -36,6 +38,7 @@ class BrowserBottomAppBar extends HookConsumerWidget {
final addonService = ref.watch(addonServiceProvider); final addonService = ref.watch(addonServiceProvider);
final tabMenuController = useMenuController(); final tabMenuController = useMenuController();
final extensionMenuController = useMenuController();
final trippleDotMenuController = useMenuController(); final trippleDotMenuController = useMenuController();
final selectedTabId = ref.watch(selectedTabProvider); final selectedTabId = ref.watch(selectedTabProvider);
@@ -43,6 +46,12 @@ class BrowserBottomAppBar extends HookConsumerWidget {
selectedTabStateProvider.select((state) => state?.isPrivate ?? false), selectedTabStateProvider.select((state) => state?.isPrivate ?? false),
); );
final showExtensionShortcut = ref.watch(
generalSettingsRepositoryProvider.select(
(value) => value.showExtensionShortcut,
),
);
final dragStartPosition = useRef(Offset.zero); final dragStartPosition = useRef(Offset.zero);
return BottomAppBar( return BottomAppBar(
@@ -97,6 +106,20 @@ class BrowserBottomAppBar extends HookConsumerWidget {
actions: [ actions: [
if (selectedTabId != null && displayedSheet is! ViewTabsSheet) if (selectedTabId != null && displayedSheet is! ViewTabsSheet)
ReaderButton(), ReaderButton(),
if (showExtensionShortcut)
ExtensionShortcutMenu(
controller: extensionMenuController,
child: IconButton(
onPressed: () {
if (extensionMenuController.isOpen) {
extensionMenuController.close();
} else {
extensionMenuController.open();
}
},
icon: const Icon(MdiIcons.puzzle),
),
),
TabCreationMenu( TabCreationMenu(
controller: tabMenuController, controller: tabMenuController,
selectedTabId: selectedTabId, selectedTabId: selectedTabId,
@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/geckoview/domain/providers.dart';
import 'package:weblibre/features/geckoview/domain/providers/web_extensions_state.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
class ExtensionShortcutMenu extends HookConsumerWidget {
final Widget child;
final MenuController controller;
const ExtensionShortcutMenu({
super.key,
required this.child,
required this.controller,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final browserExtensions = ref.watch(
webExtensionsStateProvider(
WebExtensionActionType.browser,
).select((value) => value.values.toList()),
);
return MenuAnchor(
controller: controller,
builder: (context, controller, child) {
return child!;
},
menuChildren: [
...browserExtensions.map(
(extension) => MenuItemButton(
onPressed: () async {
//Use parents .ref because after onPressed this consumer gets disposed already
await ref
.read(addonServiceProvider)
.invokeAddonAction(
extension.extensionId,
WebExtensionActionType.browser,
);
},
leadingIcon: ExtensionBadgeIcon(extension),
child: Text(extension.title ?? ''),
),
),
],
child: Visibility(visible: browserExtensions.isNotEmpty, child: child),
);
}
}
@@ -210,6 +210,22 @@ class GeneralSettingsScreen extends HookConsumerWidget {
); );
}, },
), ),
SwitchListTile.adaptive(
title: const Text('Show Extension Shortcut'),
subtitle: const Text(
'Display an extension menu directly on the tab bar',
),
secondary: const Icon(MdiIcons.puzzleHeart),
value: generalSettings.showExtensionShortcut,
onChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) => currentSettings.copyWith
.showExtensionShortcut(value),
);
},
),
Consumer( Consumer(
builder: (context, ref, child) { builder: (context, ref, child) {
final size = ref.watch( final size = ref.watch(
@@ -33,6 +33,7 @@ class GeneralSettings with FastEquatable {
final String defaultSearchProvider; final String defaultSearchProvider;
final SearchSuggestionProviders defaultSearchSuggestionsProvider; final SearchSuggestionProviders defaultSearchSuggestionsProvider;
final bool createChildTabsOption; final bool createChildTabsOption;
final bool showExtensionShortcut;
final bool proxyPrivateTabsTor; final bool proxyPrivateTabsTor;
@@ -45,6 +46,7 @@ class GeneralSettings with FastEquatable {
required this.defaultSearchSuggestionsProvider, required this.defaultSearchSuggestionsProvider,
required this.createChildTabsOption, required this.createChildTabsOption,
required this.proxyPrivateTabsTor, required this.proxyPrivateTabsTor,
required this.showExtensionShortcut,
}); });
GeneralSettings.withDefaults({ GeneralSettings.withDefaults({
@@ -56,6 +58,7 @@ class GeneralSettings with FastEquatable {
SearchSuggestionProviders? defaultSearchSuggestionsProvider, SearchSuggestionProviders? defaultSearchSuggestionsProvider,
bool? createChildTabsOption, bool? createChildTabsOption,
bool? proxyPrivateTabsTor, bool? proxyPrivateTabsTor,
bool? showExtensionShortcut,
}) : themeMode = themeMode ?? ThemeMode.dark, }) : themeMode = themeMode ?? ThemeMode.dark,
enableReadability = enableReadability ?? true, enableReadability = enableReadability ?? true,
enforceReadability = enforceReadability ?? false, enforceReadability = enforceReadability ?? false,
@@ -63,7 +66,8 @@ class GeneralSettings with FastEquatable {
defaultSearchSuggestionsProvider = defaultSearchSuggestionsProvider =
defaultSearchSuggestionsProvider ?? _fallbackAutocompleteProvider, defaultSearchSuggestionsProvider ?? _fallbackAutocompleteProvider,
createChildTabsOption = createChildTabsOption ?? false, createChildTabsOption = createChildTabsOption ?? false,
proxyPrivateTabsTor = proxyPrivateTabsTor ?? false; proxyPrivateTabsTor = proxyPrivateTabsTor ?? false,
showExtensionShortcut = showExtensionShortcut ?? false;
factory GeneralSettings.fromJson(Map<String, dynamic> json) => factory GeneralSettings.fromJson(Map<String, dynamic> json) =>
_$GeneralSettingsFromJson(json); _$GeneralSettingsFromJson(json);
@@ -79,6 +83,7 @@ class GeneralSettings with FastEquatable {
defaultSearchProvider, defaultSearchProvider,
defaultSearchSuggestionsProvider, defaultSearchSuggestionsProvider,
createChildTabsOption, createChildTabsOption,
showExtensionShortcut,
proxyPrivateTabsTor, proxyPrivateTabsTor,
]; ];
} }
@@ -27,6 +27,8 @@ abstract class _$GeneralSettingsCWProxy {
GeneralSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor); GeneralSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor);
GeneralSettings showExtensionShortcut(bool showExtensionShortcut);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
/// ///
/// Usage /// Usage
@@ -42,6 +44,7 @@ abstract class _$GeneralSettingsCWProxy {
SearchSuggestionProviders defaultSearchSuggestionsProvider, SearchSuggestionProviders defaultSearchSuggestionsProvider,
bool createChildTabsOption, bool createChildTabsOption,
bool proxyPrivateTabsTor, bool proxyPrivateTabsTor,
bool showExtensionShortcut,
}); });
} }
@@ -84,6 +87,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
GeneralSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor) => GeneralSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor) =>
this(proxyPrivateTabsTor: proxyPrivateTabsTor); this(proxyPrivateTabsTor: proxyPrivateTabsTor);
@override
GeneralSettings showExtensionShortcut(bool showExtensionShortcut) =>
this(showExtensionShortcut: showExtensionShortcut);
@override @override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
/// ///
@@ -100,6 +107,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
Object? defaultSearchSuggestionsProvider = const $CopyWithPlaceholder(), Object? defaultSearchSuggestionsProvider = const $CopyWithPlaceholder(),
Object? createChildTabsOption = const $CopyWithPlaceholder(), Object? createChildTabsOption = const $CopyWithPlaceholder(),
Object? proxyPrivateTabsTor = const $CopyWithPlaceholder(), Object? proxyPrivateTabsTor = const $CopyWithPlaceholder(),
Object? showExtensionShortcut = const $CopyWithPlaceholder(),
}) { }) {
return GeneralSettings( return GeneralSettings(
themeMode: themeMode == const $CopyWithPlaceholder() themeMode: themeMode == const $CopyWithPlaceholder()
@@ -138,6 +146,11 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
? _value.proxyPrivateTabsTor ? _value.proxyPrivateTabsTor
// ignore: cast_nullable_to_non_nullable // ignore: cast_nullable_to_non_nullable
: proxyPrivateTabsTor as bool, : proxyPrivateTabsTor as bool,
showExtensionShortcut:
showExtensionShortcut == const $CopyWithPlaceholder()
? _value.showExtensionShortcut
// ignore: cast_nullable_to_non_nullable
: showExtensionShortcut as bool,
); );
} }
} }
@@ -168,6 +181,7 @@ GeneralSettings _$GeneralSettingsFromJson(Map<String, dynamic> json) =>
), ),
createChildTabsOption: json['createChildTabsOption'] as bool?, createChildTabsOption: json['createChildTabsOption'] as bool?,
proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?, proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?,
showExtensionShortcut: json['showExtensionShortcut'] as bool?,
); );
Map<String, dynamic> _$GeneralSettingsToJson(GeneralSettings instance) => Map<String, dynamic> _$GeneralSettingsToJson(GeneralSettings instance) =>
@@ -183,6 +197,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(GeneralSettings instance) =>
_$SearchSuggestionProvidersEnumMap[instance _$SearchSuggestionProvidersEnumMap[instance
.defaultSearchSuggestionsProvider]!, .defaultSearchSuggestionsProvider]!,
'createChildTabsOption': instance.createChildTabsOption, 'createChildTabsOption': instance.createChildTabsOption,
'showExtensionShortcut': instance.showExtensionShortcut,
'proxyPrivateTabsTor': instance.proxyPrivateTabsTor, 'proxyPrivateTabsTor': instance.proxyPrivateTabsTor,
}; };
@@ -56,6 +56,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
DriftSqlType.bool, DriftSqlType.bool,
db.typeMapping, db.typeMapping,
), ),
'showExtensionShortcut': settings['showExtensionShortcut']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
}); });
} }
@@ -7,7 +7,7 @@ part of 'general_settings.dart';
// ************************************************************************** // **************************************************************************
String _$generalSettingsRepositoryHash() => String _$generalSettingsRepositoryHash() =>
r'5335acea5fb175b665b80b2241ce463ae8ad9f89'; r'eb5f27e0bd8f5def833e1c703cb42181acc2440f';
/// See also [GeneralSettingsRepository]. /// See also [GeneralSettingsRepository].
@ProviderFor(GeneralSettingsRepository) @ProviderFor(GeneralSettingsRepository)