Revert "dont allow focus on buttons"

This reverts commit dbe43897b6.
This commit is contained in:
Fabian Freund
2026-01-14 05:22:10 +01:00
parent 057d14b6d9
commit 53df72d906
5 changed files with 320 additions and 424 deletions
@@ -57,7 +57,6 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart
import 'package:weblibre/presentation/hooks/cached_future.dart'; import 'package:weblibre/presentation/hooks/cached_future.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/presentation/widgets/non_focusable.dart';
import 'package:weblibre/presentation/widgets/selectable_chips.dart'; import 'package:weblibre/presentation/widgets/selectable_chips.dart';
import 'package:weblibre/presentation/widgets/url_icon.dart'; import 'package:weblibre/presentation/widgets/url_icon.dart';
@@ -326,7 +325,6 @@ class BrowserTabBar extends HookConsumerWidget {
return Visibility( return Visibility(
visible: tabBarReaderView || readerabilityStateActive, visible: tabBarReaderView || readerabilityStateActive,
child: NonFocusable(
child: ReaderButton( child: ReaderButton(
buttonBuilder: (isLoading, readerActive, icon) => buttonBuilder: (isLoading, readerActive, icon) =>
InkWell( InkWell(
@@ -338,9 +336,7 @@ class BrowserTabBar extends HookConsumerWidget {
readerableScreenControllerProvider readerableScreenControllerProvider
.notifier, .notifier,
) )
.toggleReaderView( .toggleReaderView(!readerActive);
!readerActive,
);
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -351,13 +347,11 @@ class BrowserTabBar extends HookConsumerWidget {
), ),
), ),
), ),
),
); );
}, },
), ),
if (showExtensionShortcut) if (showExtensionShortcut)
NonFocusable( ExtensionShortcutMenu(
child: ExtensionShortcutMenu(
controller: extensionMenuController, controller: extensionMenuController,
child: IconButton( child: IconButton(
onPressed: () { onPressed: () {
@@ -370,10 +364,8 @@ class BrowserTabBar extends HookConsumerWidget {
icon: const Icon(MdiIcons.puzzle), icon: const Icon(MdiIcons.puzzle),
), ),
), ),
),
if (selectedTabId != null) if (selectedTabId != null)
NonFocusable( TabMenu(
child: TabMenu(
controller: trippleDotMenuController, controller: trippleDotMenuController,
selectedTabId: selectedTabId, selectedTabId: selectedTabId,
builder: (context, controller, child) { builder: (context, controller, child) {
@@ -395,22 +387,17 @@ class BrowserTabBar extends HookConsumerWidget {
); );
}, },
), ),
),
if (showMainToolbarTabsCount) if (showMainToolbarTabsCount)
NonFocusable( TabsCountButton(
child: TabsCountButton(
selectedTabId: selectedTabId, selectedTabId: selectedTabId,
displayedSheet: displayedSheet, displayedSheet: displayedSheet,
showLongPressMenu: true, showLongPressMenu: true,
), ),
),
if (showMainToolbarNavigationButton) if (showMainToolbarNavigationButton)
NonFocusable( NavigationMenuButton(
child: NavigationMenuButton(
selectedTabId: selectedTabId, selectedTabId: selectedTabId,
showNavigationButtons: true, showNavigationButtons: true,
), ),
),
], ],
), ),
), ),
@@ -453,15 +440,12 @@ class ContextualToolbar extends HookConsumerWidget {
children: [ children: [
if (tabState?.historyState.canGoBack == true || if (tabState?.historyState.canGoBack == true ||
tabState?.isLoading == true) tabState?.isLoading == true)
NonFocusable( NavigateBackButton(
child: NavigateBackButton(
selectedTabId: selectedTabId, selectedTabId: selectedTabId,
isLoading: tabState?.isLoading ?? false, isLoading: tabState?.isLoading ?? false,
),
) )
else else
NonFocusable( IconButton(
child: IconButton(
onPressed: () async { onPressed: () async {
await BookmarkListRoute( await BookmarkListRoute(
entryGuid: BookmarkRoot.root.id, entryGuid: BookmarkRoot.root.id,
@@ -469,27 +453,20 @@ class ContextualToolbar extends HookConsumerWidget {
}, },
icon: const Icon(MdiIcons.bookmarkMultiple), icon: const Icon(MdiIcons.bookmarkMultiple),
), ),
),
if (tabState?.historyState.canGoForward == true) if (tabState?.historyState.canGoForward == true)
NonFocusable( NavigateForwardButton(selectedTabId: selectedTabId)
child: NavigateForwardButton(selectedTabId: selectedTabId),
)
else else
NonFocusable(child: ShareMenuButton(selectedTabId: selectedTabId)), ShareMenuButton(selectedTabId: selectedTabId),
const NonFocusable(child: AddTabButton()), const AddTabButton(),
NonFocusable( TabsCountButton(
child: TabsCountButton(
selectedTabId: selectedTabId, selectedTabId: selectedTabId,
displayedSheet: displayedSheet, displayedSheet: displayedSheet,
showLongPressMenu: false, showLongPressMenu: false,
), ),
), NavigationMenuButton(
NonFocusable(
child: NavigationMenuButton(
selectedTabId: selectedTabId, selectedTabId: selectedTabId,
showNavigationButtons: false, showNavigationButtons: false,
), ),
),
], ],
); );
} }
@@ -651,8 +628,7 @@ class ShareMenuButton extends HookConsumerWidget {
ShowQrCodeMenuItemButton(selectedTabId: selectedTabId), ShowQrCodeMenuItemButton(selectedTabId: selectedTabId),
], ],
builder: (context, controller, child) { builder: (context, controller, child) {
return NonFocusable( return IconButton(
child: IconButton(
onPressed: () { onPressed: () {
if (controller.isOpen) { if (controller.isOpen) {
controller.close(); controller.close();
@@ -661,7 +637,6 @@ class ShareMenuButton extends HookConsumerWidget {
} }
}, },
icon: const Icon(Icons.share), icon: const Icon(Icons.share),
),
); );
}, },
); );
@@ -730,8 +705,7 @@ class NavigationMenuButton extends HookConsumerWidget {
alignment: WrapAlignment.center, alignment: WrapAlignment.center,
children: [ children: [
...pageExtensions.map( ...pageExtensions.map(
(extension) => NonFocusable( (extension) => IconButton(
child: IconButton(
onPressed: () async { onPressed: () async {
//Use parents .ref because after onPressed this consumer gets disposed already //Use parents .ref because after onPressed this consumer gets disposed already
await addonService.invokeAddonAction( await addonService.invokeAddonAction(
@@ -745,7 +719,6 @@ class NavigationMenuButton extends HookConsumerWidget {
), ),
), ),
), ),
),
], ],
); );
}, },
@@ -974,8 +947,7 @@ class NavigateForwardButton extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return NonFocusable( return IconButton(
child: IconButton(
onPressed: canGoForward onPressed: canGoForward
? () async { ? () async {
final controller = ref.read( final controller = ref.read(
@@ -987,7 +959,6 @@ class NavigateForwardButton extends HookConsumerWidget {
} }
: null, : null,
icon: const Icon(Icons.arrow_forward), icon: const Icon(Icons.arrow_forward),
),
); );
} }
} }
@@ -1008,8 +979,7 @@ class NavigateBackButton extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return NonFocusable( return IconButton(
child: IconButton(
onPressed: (canGoBack || isLoading) onPressed: (canGoBack || isLoading)
? () async { ? () async {
final controller = ref.read( final controller = ref.read(
@@ -1035,10 +1005,7 @@ class NavigateBackButton extends HookConsumerWidget {
menuControllerToClose?.close(); menuControllerToClose?.close();
} }
: null, : null,
icon: isLoading icon: isLoading ? const Icon(Icons.close) : const Icon(Icons.arrow_back),
? const Icon(Icons.close)
: const Icon(Icons.arrow_back),
),
); );
} }
} }
@@ -1052,7 +1019,6 @@ class AddTabButton extends HookConsumerWidget {
return TabCreationMenu( return TabCreationMenu(
controller: tabMenuController, controller: tabMenuController,
child: NonFocusable(
child: IconButton( child: IconButton(
onPressed: () async { onPressed: () async {
final settings = ref.read(generalSettingsWithDefaultsProvider); final settings = ref.read(generalSettingsWithDefaultsProvider);
@@ -1076,7 +1042,6 @@ class AddTabButton extends HookConsumerWidget {
} }
}, },
), ),
),
); );
} }
} }
@@ -152,7 +152,6 @@ class GridTabPreview extends HookConsumerWidget {
child: Text('Close all from ${tabState.url.host}'), child: Text('Close all from ${tabState.url.host}'),
), ),
], ],
child: NonFocusable(
child: IconButton( child: IconButton(
visualDensity: const VisualDensity( visualDensity: const VisualDensity(
horizontal: -4.0, horizontal: -4.0,
@@ -171,7 +170,6 @@ class GridTabPreview extends HookConsumerWidget {
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
), ),
), ),
),
?trailingChild, ?trailingChild,
], ],
), ),
@@ -352,7 +350,6 @@ class ListTabPreview extends HookConsumerWidget {
child: Text('Close all from ${tabState.url.host}'), child: Text('Close all from ${tabState.url.host}'),
), ),
], ],
child: NonFocusable(
child: IconButton( child: IconButton(
onPressed: onDelete, onPressed: onDelete,
onLongPress: onDeleteAll != null onLongPress: onDeleteAll != null
@@ -367,7 +364,6 @@ class ListTabPreview extends HookConsumerWidget {
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
), ),
), ),
),
?trailingChild, ?trailingChild,
], ],
), ),
@@ -631,14 +627,12 @@ class SuggestedSingleGridTabPreview extends StatelessWidget {
tabId: tabId, tabId: tabId,
isActive: tabId == activeTabId, isActive: tabId == activeTabId,
onTap: onTap, onTap: onTap,
trailingChild: const NonFocusable( trailingChild: const IconButton(
child: IconButton(
visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0), visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0),
icon: Icon(MdiIcons.creation), icon: Icon(MdiIcons.creation),
onPressed: null, onPressed: null,
), ),
), ),
),
); );
} }
} }
@@ -665,14 +659,12 @@ class SuggestedSingleListTabPreview extends StatelessWidget {
tabId: tabId, tabId: tabId,
isActive: tabId == activeTabId, isActive: tabId == activeTabId,
onTap: onTap, onTap: onTap,
trailingChild: const NonFocusable( trailingChild: const IconButton(
child: IconButton(
visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0), visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0),
icon: Icon(MdiIcons.creation), icon: Icon(MdiIcons.creation),
onPressed: null, onPressed: null,
), ),
), ),
),
); );
} }
} }
@@ -26,7 +26,6 @@ import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/c
import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart'; import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.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/widgets/non_focusable.dart';
import 'package:weblibre/presentation/widgets/speech_to_text_button.dart'; import 'package:weblibre/presentation/widgets/speech_to_text_button.dart';
import 'package:weblibre/utils/ui_helper.dart' as ui_helper; import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
@@ -83,16 +82,13 @@ class TabViewHeader extends HookConsumerWidget {
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
NonFocusable( IconButton(
child: IconButton(
icon: const Icon(MdiIcons.tabSearch), icon: const Icon(MdiIcons.tabSearch),
iconSize: 18, iconSize: 18,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
tooltip: 'Search inside tabs', tooltip: 'Search inside tabs',
onPressed: () { onPressed: () {
switch (ref.read( switch (ref.read(tabsViewModeControllerProvider)) {
tabsViewModeControllerProvider,
)) {
case TabsViewMode.tree: case TabsViewMode.tree:
case TabsViewMode.list: case TabsViewMode.list:
break; break;
@@ -108,7 +104,6 @@ class TabViewHeader extends HookConsumerWidget {
searchTextFocus.requestFocus(); searchTextFocus.requestFocus();
}, },
), ),
),
const SizedBox( const SizedBox(
height: 32, height: 32,
child: VerticalDivider(indent: 4, endIndent: 4), child: VerticalDivider(indent: 4, endIndent: 4),
@@ -131,7 +126,6 @@ class TabViewHeader extends HookConsumerWidget {
), ),
) )
.toList(), .toList(),
child: NonFocusable(
child: IconButton( child: IconButton(
tooltip: 'Change view mode', tooltip: 'Change view mode',
onPressed: () { onPressed: () {
@@ -150,7 +144,6 @@ class TabViewHeader extends HookConsumerWidget {
), ),
), ),
), ),
),
if (enableAiFeatures && if (enableAiFeatures &&
switch (tabsViewMode) { switch (tabsViewMode) {
TabsViewMode.list || TabsViewMode.grid => true, TabsViewMode.list || TabsViewMode.grid => true,
@@ -174,7 +167,6 @@ class TabViewHeader extends HookConsumerWidget {
style: const TextStyle(fontSize: 10), style: const TextStyle(fontSize: 10),
) )
: null, : null,
child: NonFocusable(
child: IconButton.filledTonal( child: IconButton.filledTonal(
icon: const Icon(MdiIcons.imageAutoAdjust), icon: const Icon(MdiIcons.imageAutoAdjust),
isSelected: tabSuggestionsEnabled, isSelected: tabSuggestionsEnabled,
@@ -210,7 +202,6 @@ class TabViewHeader extends HookConsumerWidget {
} }
}, },
), ),
),
); );
}, },
), ),
@@ -513,7 +504,6 @@ class TabViewHeader extends HookConsumerWidget {
}, },
), ),
], ],
child: NonFocusable(
child: IconButton( child: IconButton(
tooltip: 'Tab actions', tooltip: 'Tab actions',
onPressed: () { onPressed: () {
@@ -526,7 +516,6 @@ class TabViewHeader extends HookConsumerWidget {
icon: const Icon(MdiIcons.dotsVertical), icon: const Icon(MdiIcons.dotsVertical),
), ),
), ),
),
], ],
) )
else else
@@ -548,8 +537,7 @@ class TabViewHeader extends HookConsumerWidget {
searchTextController.text = data; searchTextController.text = data;
}, },
), ),
NonFocusable( IconButton(
child: IconButton(
onPressed: () { onPressed: () {
searchTextController.clear(); searchTextController.clear();
searchTextFocus.requestFocus(); searchTextFocus.requestFocus();
@@ -557,7 +545,6 @@ class TabViewHeader extends HookConsumerWidget {
}, },
icon: const Icon(Icons.clear), icon: const Icon(Icons.clear),
), ),
),
], ],
), ),
), ),
@@ -26,7 +26,6 @@ import 'package:weblibre/features/geckoview/features/tabs/data/entities/containe
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart'; import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
import 'package:weblibre/presentation/widgets/non_focusable.dart';
class TabsActionButton extends HookConsumerWidget { class TabsActionButton extends HookConsumerWidget {
final bool isActive; final bool isActive;
@@ -57,8 +56,7 @@ class TabsActionButton extends HookConsumerWidget {
selectedContainerDataProvider.select((value) => value.value?.color), selectedContainerDataProvider.select((value) => value.value?.color),
); );
return NonFocusable( return InkWell(
child: InkWell(
onTap: onTap, onTap: onTap,
onDoubleTap: onDoubleTap, onDoubleTap: onDoubleTap,
onLongPress: onLongPress, onLongPress: onLongPress,
@@ -119,7 +117,6 @@ class TabsActionButton extends HookConsumerWidget {
), ),
), ),
), ),
),
); );
} }
} }
@@ -1,45 +0,0 @@
/*
* 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/material.dart';
/// A wrapper that prevents a widget from receiving keyboard focus.
///
/// This fixes the issue where arrow keys on a bluetooth keyboard navigate
/// to toolbar buttons instead of moving the text cursor in input fields.
///
/// Example:
/// ```dart
/// NonFocusable(
/// child: IconButton(
/// onPressed: () {},
/// icon: Icon(Icons.menu),
/// ),
/// )
/// ```
class NonFocusable extends StatelessWidget {
final Widget child;
const NonFocusable({required this.child, super.key});
@override
Widget build(BuildContext context) {
return Focus(canRequestFocus: false, skipTraversal: true, child: child);
}
}