From 8dc7151c458c0f1cb18b3806c85e445c89edf2d3 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Fri, 13 Mar 2026 04:36:39 +0100 Subject: [PATCH] add long press information in button customization screen --- .../presentation/toolbar_button_registry.dart | 28 ++++ .../screens/contextual_toolbar_settings.dart | 138 +++++++++++++++++- 2 files changed, 162 insertions(+), 4 deletions(-) diff --git a/app/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart b/app/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart index 340785eb..b291c2c2 100644 --- a/app/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart +++ b/app/lib/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart @@ -64,6 +64,7 @@ class ToolbarButtonDefinition { WidgetRef ref, ) builder; + final List longPressActions; const ToolbarButtonDefinition({ required this.spec, @@ -71,6 +72,7 @@ class ToolbarButtonDefinition { required this.icon, this.isPrimaryAvailable, required this.builder, + this.longPressActions = const [], }); } @@ -82,6 +84,7 @@ final List toolbarButtonRegistry = [ isPrimaryAvailable: (scope, ref) => scope.tabState?.historyState.canGoBack == true || scope.tabState?.isLoading == true, + longPressActions: ['History Menu (Previous pages)'], builder: (scope, context, ref) { if (scope.isPreview) { return NavigateBackButtonView( @@ -103,6 +106,7 @@ final List toolbarButtonRegistry = [ icon: Icons.arrow_forward, isPrimaryAvailable: (scope, ref) => scope.tabState?.historyState.canGoForward == true, + longPressActions: ['History Menu (Forward pages)'], builder: (scope, context, ref) { if (scope.isPreview) { return NavigateForwardButtonView( @@ -118,6 +122,7 @@ final List toolbarButtonRegistry = [ spec: bookmarksToolbarButtonSpec, label: 'Bookmarks', icon: MdiIcons.bookmarkMultiple, + longPressActions: ['Add Bookmark', 'Remove Bookmark'], builder: (scope, context, ref) => _BookmarkToolbarButton(scope: scope), ), ToolbarButtonDefinition( @@ -132,6 +137,12 @@ final List toolbarButtonRegistry = [ spec: addTabToolbarButtonSpec, label: 'New Tab', icon: MdiIcons.tabPlus, + longPressActions: [ + 'Add Regular Tab', + 'Add Child Tab', + 'Add Private Tab', + 'Add Isolated Tab', + ], builder: (scope, context, ref) => scope.isPreview ? AddTabButtonView(onPressed: () {}, onLongPress: () {}) : const AddTabButton(), @@ -140,6 +151,12 @@ final List toolbarButtonRegistry = [ spec: tabsCountToolbarButtonSpec, label: 'Tabs', icon: MdiIcons.tab, + longPressActions: [ + 'Add Regular Tab', + 'Add Child Tab', + 'Add Private Tab', + 'Add Isolated Tab', + ], builder: (scope, context, ref) => scope.isPreview ? TabsCountButtonView( isActive: false, @@ -172,6 +189,7 @@ final List toolbarButtonRegistry = [ spec: reloadToolbarButtonSpec, label: 'Reload', icon: Icons.refresh, + longPressActions: ['Hard Refresh (bypass cache)'], builder: (scope, context, ref) => _ReloadToolbarButton(scope: scope), ), ToolbarButtonDefinition( @@ -223,6 +241,7 @@ final List toolbarButtonRegistry = [ spec: translationToolbarButtonSpec, label: 'Translate', icon: Icons.translate, + longPressActions: ['Show Translation Options'], isPrimaryAvailable: (scope, ref) { final engineState = ref.read(translationEngineStateProvider); final readerActive = scope.tabState?.readerableState.active ?? false; @@ -257,6 +276,7 @@ final List toolbarButtonRegistry = [ spec: closeTabToolbarButtonSpec, label: 'Close Tab', icon: MdiIcons.tabMinus, + longPressActions: ['Close Others', 'Close from Same Host'], builder: (scope, context, ref) => _CloseTabToolbarButton(scope: scope), ), ToolbarButtonDefinition( @@ -294,6 +314,11 @@ final List toolbarButtonRegistry = [ spec: duplicateTabToolbarButtonSpec, label: 'Duplicate Tab', icon: MdiIcons.contentDuplicate, + longPressActions: [ + 'Clone as Regular', + 'Clone as Private', + 'Clone as Isolated', + ], builder: (scope, context, ref) { return scope.isPreview ? CloneTabButtonView(onPressed: () {}, onLongPress: () {}) @@ -341,6 +366,7 @@ final List toolbarButtonRegistry = [ spec: pageUpToolbarButtonSpec, label: 'Page Up', icon: MdiIcons.chevronDoubleUp, + longPressActions: ['Scroll to Top'], builder: (scope, context, ref) { return IconButton( onPressed: scope.isPreview @@ -371,6 +397,7 @@ final List toolbarButtonRegistry = [ spec: pageDownToolbarButtonSpec, label: 'Page Down', icon: MdiIcons.chevronDoubleDown, + longPressActions: ['Scroll to Bottom'], builder: (scope, context, ref) { return IconButton( onPressed: scope.isPreview @@ -415,6 +442,7 @@ final List toolbarButtonRegistry = [ spec: extensionShortcutToolbarButtonSpec, label: 'Extensions', icon: MdiIcons.puzzle, + longPressActions: ['Extensions Menu'], isPrimaryAvailable: (scope, ref) => ref .read( webExtensionsStateProvider( diff --git a/app/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart b/app/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart index 1eba27ca..7c3e29a8 100644 --- a/app/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart +++ b/app/lib/features/settings/presentation/screens/contextual_toolbar_settings.dart @@ -191,21 +191,34 @@ class _ToolbarButtonConfigTile extends HookConsumerWidget { ) .toList(); + final longPressActions = def.longPressActions; + return Material( color: Colors.transparent, child: ListTile( leading: Icon(def.icon), title: Text(def.label), - subtitle: hasStatefulFallback - ? _FallbackPicker( + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (hasStatefulFallback) + _FallbackPicker( current: ToolbarFallbackChoice.fromStored(config.fallbackId), options: fallbackOptions, onChanged: (newFallback) => repository.assignFallback( config.buttonId, (newFallback ?? ToolbarFallbackNone()).toStoredFallbackId(), ), - ) - : null, + ), + if (longPressActions.isNotEmpty) + _LongPressHint( + buttonLabel: def.label, + icon: def.icon, + actions: longPressActions, + ), + ], + ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -228,6 +241,121 @@ class _ToolbarButtonConfigTile extends HookConsumerWidget { } } +class _LongPressHint extends StatelessWidget { + const _LongPressHint({ + required this.buttonLabel, + required this.icon, + required this.actions, + }); + + final String buttonLabel; + final IconData icon; + final List actions; + + @override + Widget build(BuildContext context) { + return InkWell( + borderRadius: BorderRadius.circular(4), + onTap: () => _showLongPressDetails(context), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.touch_app, + size: 14, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 8), + Flexible( + child: Text( + 'Long press available', + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontSize: 12, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 4), + Icon( + Icons.info_outline, + size: 14, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ], + ), + ), + ); + } + + Future _showLongPressDetails(BuildContext context) async { + await showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, color: Theme.of(context).colorScheme.primary), + const SizedBox(width: 12), + Text( + '$buttonLabel Long Press', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + 'Press and hold this button to access:', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 12), + ...actions.map( + (action) => Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + children: [ + Icon( + Icons.touch_app, + size: 18, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + action, + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + ], + ), + ), + ), + const SizedBox(height: 16), + ], + ), + ), + ); + }, + ); + } +} + class _FallbackPicker extends StatelessWidget { const _FallbackPicker({ required this.current, @@ -245,6 +373,8 @@ class _FallbackPicker extends StatelessWidget { value: current, hint: const Text('No fallback'), isExpanded: true, + isDense: true, + padding: const EdgeInsets.symmetric(vertical: 2.0), underline: const SizedBox.shrink(), items: [ DropdownMenuItem(