dont allow focus on buttons

This commit is contained in:
Fabian Freund
2026-01-13 18:41:42 +01:00
parent fa6878d2b2
commit dbe43897b6
5 changed files with 425 additions and 320 deletions
@@ -57,6 +57,7 @@ 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';
@@ -325,78 +326,90 @@ class BrowserTabBar extends HookConsumerWidget {
return Visibility( return Visibility(
visible: tabBarReaderView || readerabilityStateActive, visible: tabBarReaderView || readerabilityStateActive,
child: ReaderButton( child: NonFocusable(
buttonBuilder: (isLoading, readerActive, icon) => child: ReaderButton(
InkWell( buttonBuilder: (isLoading, readerActive, icon) =>
onTap: isLoading InkWell(
? null onTap: isLoading
: () async { ? null
await ref : () async {
.read( await ref
readerableScreenControllerProvider .read(
.notifier, readerableScreenControllerProvider
) .notifier,
.toggleReaderView(!readerActive); )
}, .toggleReaderView(
child: Padding( !readerActive,
padding: const EdgeInsets.symmetric( );
vertical: 15.0, },
horizontal: 8.0, child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 15.0,
horizontal: 8.0,
),
child: icon,
), ),
child: icon,
), ),
), ),
), ),
); );
}, },
), ),
if (showExtensionShortcut) if (showExtensionShortcut)
ExtensionShortcutMenu( NonFocusable(
controller: extensionMenuController, child: ExtensionShortcutMenu(
child: IconButton( controller: extensionMenuController,
onPressed: () { child: IconButton(
if (extensionMenuController.isOpen) { onPressed: () {
extensionMenuController.close(); if (extensionMenuController.isOpen) {
} else { extensionMenuController.close();
extensionMenuController.open(); } else {
} extensionMenuController.open();
}, }
icon: const Icon(MdiIcons.puzzle), },
icon: const Icon(MdiIcons.puzzle),
),
), ),
), ),
if (selectedTabId != null) if (selectedTabId != null)
TabMenu( NonFocusable(
controller: trippleDotMenuController, child: TabMenu(
selectedTabId: selectedTabId, controller: trippleDotMenuController,
builder: (context, controller, child) { selectedTabId: selectedTabId,
return InkWell( builder: (context, controller, child) {
onTap: () { return InkWell(
if (controller.isOpen) { onTap: () {
controller.close(); if (controller.isOpen) {
} else { controller.close();
controller.open(); } else {
} controller.open();
}, }
child: const Padding( },
padding: EdgeInsets.symmetric( child: const Padding(
horizontal: 8.0, padding: EdgeInsets.symmetric(
vertical: 15.0, horizontal: 8.0,
vertical: 15.0,
),
child: Icon(MdiIcons.dotsVertical),
), ),
child: Icon(MdiIcons.dotsVertical), );
), },
); ),
},
), ),
if (showMainToolbarTabsCount) if (showMainToolbarTabsCount)
TabsCountButton( NonFocusable(
selectedTabId: selectedTabId, child: TabsCountButton(
displayedSheet: displayedSheet, selectedTabId: selectedTabId,
showLongPressMenu: true, displayedSheet: displayedSheet,
showLongPressMenu: true,
),
), ),
if (showMainToolbarNavigationButton) if (showMainToolbarNavigationButton)
NavigationMenuButton( NonFocusable(
selectedTabId: selectedTabId, child: NavigationMenuButton(
showNavigationButtons: true, selectedTabId: selectedTabId,
showNavigationButtons: true,
),
), ),
], ],
), ),
@@ -440,32 +453,42 @@ class ContextualToolbar extends HookConsumerWidget {
children: [ children: [
if (tabState?.historyState.canGoBack == true || if (tabState?.historyState.canGoBack == true ||
tabState?.isLoading == true) tabState?.isLoading == true)
NavigateBackButton( NonFocusable(
selectedTabId: selectedTabId, child: NavigateBackButton(
isLoading: tabState?.isLoading ?? false, selectedTabId: selectedTabId,
isLoading: tabState?.isLoading ?? false,
),
) )
else else
IconButton( NonFocusable(
onPressed: () async { child: IconButton(
await BookmarkListRoute( onPressed: () async {
entryGuid: BookmarkRoot.root.id, await BookmarkListRoute(
).push(context); entryGuid: BookmarkRoot.root.id,
}, ).push(context);
icon: const Icon(MdiIcons.bookmarkMultiple), },
icon: const Icon(MdiIcons.bookmarkMultiple),
),
), ),
if (tabState?.historyState.canGoForward == true) if (tabState?.historyState.canGoForward == true)
NavigateForwardButton(selectedTabId: selectedTabId) NonFocusable(
child: NavigateForwardButton(selectedTabId: selectedTabId),
)
else else
ShareMenuButton(selectedTabId: selectedTabId), NonFocusable(child: ShareMenuButton(selectedTabId: selectedTabId)),
const AddTabButton(), const NonFocusable(child: AddTabButton()),
TabsCountButton( NonFocusable(
selectedTabId: selectedTabId, child: TabsCountButton(
displayedSheet: displayedSheet, selectedTabId: selectedTabId,
showLongPressMenu: false, displayedSheet: displayedSheet,
showLongPressMenu: false,
),
), ),
NavigationMenuButton( NonFocusable(
selectedTabId: selectedTabId, child: NavigationMenuButton(
showNavigationButtons: false, selectedTabId: selectedTabId,
showNavigationButtons: false,
),
), ),
], ],
); );
@@ -628,15 +651,17 @@ class ShareMenuButton extends HookConsumerWidget {
ShowQrCodeMenuItemButton(selectedTabId: selectedTabId), ShowQrCodeMenuItemButton(selectedTabId: selectedTabId),
], ],
builder: (context, controller, child) { builder: (context, controller, child) {
return IconButton( return NonFocusable(
onPressed: () { child: IconButton(
if (controller.isOpen) { onPressed: () {
controller.close(); if (controller.isOpen) {
} else { controller.close();
controller.open(); } else {
} controller.open();
}, }
icon: const Icon(Icons.share), },
icon: const Icon(Icons.share),
),
); );
}, },
); );
@@ -705,17 +730,19 @@ class NavigationMenuButton extends HookConsumerWidget {
alignment: WrapAlignment.center, alignment: WrapAlignment.center,
children: [ children: [
...pageExtensions.map( ...pageExtensions.map(
(extension) => IconButton( (extension) => NonFocusable(
onPressed: () async { child: IconButton(
//Use parents .ref because after onPressed this consumer gets disposed already onPressed: () async {
await addonService.invokeAddonAction( //Use parents .ref because after onPressed this consumer gets disposed already
extension.extensionId, await addonService.invokeAddonAction(
WebExtensionActionType.page, extension.extensionId,
); WebExtensionActionType.page,
}, );
icon: Padding( },
padding: const EdgeInsets.only(top: 4.0), icon: Padding(
child: ExtensionBadgeIcon(extension), padding: const EdgeInsets.only(top: 4.0),
child: ExtensionBadgeIcon(extension),
),
), ),
), ),
), ),
@@ -947,18 +974,20 @@ class NavigateForwardButton extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return IconButton( return NonFocusable(
onPressed: canGoForward child: IconButton(
? () async { onPressed: canGoForward
final controller = ref.read( ? () async {
tabSessionProvider(tabId: selectedTabId).notifier, final controller = ref.read(
); tabSessionProvider(tabId: selectedTabId).notifier,
);
await controller.goForward(); await controller.goForward();
menuControllerToClose?.close(); menuControllerToClose?.close();
} }
: null, : null,
icon: const Icon(Icons.arrow_forward), icon: const Icon(Icons.arrow_forward),
),
); );
} }
} }
@@ -979,33 +1008,37 @@ class NavigateBackButton extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return IconButton( return NonFocusable(
onPressed: (canGoBack || isLoading) child: IconButton(
? () async { onPressed: (canGoBack || isLoading)
final controller = ref.read( ? () async {
tabSessionProvider(tabId: selectedTabId).notifier, final controller = ref.read(
); tabSessionProvider(tabId: selectedTabId).notifier,
);
final isReaderActive = ref.read( final isReaderActive = ref.read(
selectedTabStateProvider.select( selectedTabStateProvider.select(
(state) => state?.readerableState.active ?? false, (state) => state?.readerableState.active ?? false,
), ),
); );
if (isLoading) { if (isLoading) {
await controller.stopLoading(); await controller.stopLoading();
} else if (isReaderActive) { } else if (isReaderActive) {
await ref await ref
.read(readerableScreenControllerProvider.notifier) .read(readerableScreenControllerProvider.notifier)
.toggleReaderView(false); .toggleReaderView(false);
} else { } else {
await controller.goBack(); await controller.goBack();
}
menuControllerToClose?.close();
} }
: null,
menuControllerToClose?.close(); icon: isLoading
} ? const Icon(Icons.close)
: null, : const Icon(Icons.arrow_back),
icon: isLoading ? const Icon(Icons.close) : const Icon(Icons.arrow_back), ),
); );
} }
} }
@@ -1019,28 +1052,30 @@ class AddTabButton extends HookConsumerWidget {
return TabCreationMenu( return TabCreationMenu(
controller: tabMenuController, controller: tabMenuController,
child: IconButton( child: NonFocusable(
onPressed: () async { child: IconButton(
final settings = ref.read(generalSettingsWithDefaultsProvider); onPressed: () async {
final settings = ref.read(generalSettingsWithDefaultsProvider);
await SearchRoute( await SearchRoute(
tabType: tabType:
ref.read(selectedTabTypeProvider) ?? ref.read(selectedTabTypeProvider) ??
settings.defaultCreateTabType, settings.defaultCreateTabType,
).push(context); ).push(context);
if (context.mounted) { if (context.mounted) {
const BrowserRoute().go(context); const BrowserRoute().go(context);
} }
}, },
icon: const Icon(MdiIcons.tabPlus), icon: const Icon(MdiIcons.tabPlus),
onLongPress: () { onLongPress: () {
if (tabMenuController.isOpen) { if (tabMenuController.isOpen) {
tabMenuController.close(); tabMenuController.close();
} else { } else {
tabMenuController.open(); tabMenuController.open();
} }
}, },
),
), ),
); );
} }
@@ -26,6 +26,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart'; import 'package:nullability/nullability.dart';
import 'package:weblibre/core/design/app_colors.dart'; import 'package:weblibre/core/design/app_colors.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart'; import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
import 'package:weblibre/presentation/widgets/non_focusable.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_icon.dart';
@@ -152,22 +153,24 @@ class GridTabPreview extends HookConsumerWidget {
child: Text('Close all from ${tabState.url.host}'), child: Text('Close all from ${tabState.url.host}'),
), ),
], ],
child: IconButton( child: NonFocusable(
visualDensity: const VisualDensity( child: IconButton(
horizontal: -4.0, visualDensity: const VisualDensity(
vertical: -4.0, horizontal: -4.0,
), vertical: -4.0,
onPressed: onDelete, ),
onLongPress: onDeleteAll != null onPressed: onDelete,
? () { onLongPress: onDeleteAll != null
if (extendedDeleteMenuController.isOpen) { ? () {
extendedDeleteMenuController.close(); if (extendedDeleteMenuController.isOpen) {
} else { extendedDeleteMenuController.close();
extendedDeleteMenuController.open(); } else {
extendedDeleteMenuController.open();
}
} }
} : null,
: null, icon: const Icon(Icons.close),
icon: const Icon(Icons.close), ),
), ),
), ),
?trailingChild, ?trailingChild,
@@ -348,18 +351,20 @@ class ListTabPreview extends HookConsumerWidget {
child: Text('Close all from ${tabState.url.host}'), child: Text('Close all from ${tabState.url.host}'),
), ),
], ],
child: IconButton( child: NonFocusable(
onPressed: onDelete, child: IconButton(
onLongPress: onDeleteAll != null onPressed: onDelete,
? () { onLongPress: onDeleteAll != null
if (extendedDeleteMenuController.isOpen) { ? () {
extendedDeleteMenuController.close(); if (extendedDeleteMenuController.isOpen) {
} else { extendedDeleteMenuController.close();
extendedDeleteMenuController.open(); } else {
extendedDeleteMenuController.open();
}
} }
} : null,
: null, icon: const Icon(Icons.close),
icon: const Icon(Icons.close), ),
), ),
), ),
?trailingChild, ?trailingChild,
@@ -625,10 +630,12 @@ class SuggestedSingleGridTabPreview extends StatelessWidget {
tabId: tabId, tabId: tabId,
isActive: tabId == activeTabId, isActive: tabId == activeTabId,
onTap: onTap, onTap: onTap,
trailingChild: const IconButton( trailingChild: const NonFocusable(
visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0), child: IconButton(
icon: Icon(MdiIcons.creation), visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0),
onPressed: null, icon: Icon(MdiIcons.creation),
onPressed: null,
),
), ),
), ),
); );
@@ -657,10 +664,12 @@ class SuggestedSingleListTabPreview extends StatelessWidget {
tabId: tabId, tabId: tabId,
isActive: tabId == activeTabId, isActive: tabId == activeTabId,
onTap: onTap, onTap: onTap,
trailingChild: const IconButton( trailingChild: const NonFocusable(
visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0), child: IconButton(
icon: Icon(MdiIcons.creation), visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0),
onPressed: null, icon: Icon(MdiIcons.creation),
onPressed: null,
),
), ),
), ),
); );
@@ -26,6 +26,7 @@ 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;
@@ -82,27 +83,31 @@ class TabViewHeader extends HookConsumerWidget {
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
IconButton( NonFocusable(
icon: const Icon(MdiIcons.tabSearch), child: IconButton(
iconSize: 18, icon: const Icon(MdiIcons.tabSearch),
padding: EdgeInsets.zero, iconSize: 18,
tooltip: 'Search inside tabs', padding: EdgeInsets.zero,
onPressed: () { tooltip: 'Search inside tabs',
switch (ref.read(tabsViewModeControllerProvider)) { onPressed: () {
case TabsViewMode.tree: switch (ref.read(
case TabsViewMode.list: tabsViewModeControllerProvider,
break; )) {
case TabsViewMode.grid: case TabsViewMode.tree:
ref case TabsViewMode.list:
.read( break;
tabsViewModeControllerProvider.notifier, case TabsViewMode.grid:
) ref
.set(TabsViewMode.list); .read(
} tabsViewModeControllerProvider.notifier,
)
.set(TabsViewMode.list);
}
searchMode.value = true; searchMode.value = true;
searchTextFocus.requestFocus(); searchTextFocus.requestFocus();
}, },
),
), ),
const SizedBox( const SizedBox(
height: 32, height: 32,
@@ -126,21 +131,23 @@ class TabViewHeader extends HookConsumerWidget {
), ),
) )
.toList(), .toList(),
child: IconButton( child: NonFocusable(
tooltip: 'Change view mode', child: IconButton(
onPressed: () { tooltip: 'Change view mode',
if (viewModeMenuController.isOpen) { onPressed: () {
viewModeMenuController.close(); if (viewModeMenuController.isOpen) {
} else { viewModeMenuController.close();
viewModeMenuController.open(); } else {
} viewModeMenuController.open();
}, }
icon: Row( },
mainAxisSize: MainAxisSize.min, icon: Row(
children: [ mainAxisSize: MainAxisSize.min,
Icon(tabsViewMode.icon, size: 18), children: [
const Icon(Icons.arrow_drop_down, size: 18), Icon(tabsViewMode.icon, size: 18),
], const Icon(Icons.arrow_drop_down, size: 18),
],
),
), ),
), ),
), ),
@@ -167,40 +174,42 @@ class TabViewHeader extends HookConsumerWidget {
style: const TextStyle(fontSize: 10), style: const TextStyle(fontSize: 10),
) )
: null, : null,
child: IconButton.filledTonal( child: NonFocusable(
icon: const Icon(MdiIcons.imageAutoAdjust), child: IconButton.filledTonal(
isSelected: tabSuggestionsEnabled, icon: const Icon(MdiIcons.imageAutoAdjust),
iconSize: 18, isSelected: tabSuggestionsEnabled,
padding: EdgeInsets.zero, iconSize: 18,
tooltip: downloadProgress != null padding: EdgeInsets.zero,
? 'Downloading AI models (${downloadProgress.progress.toInt()}%)' tooltip: downloadProgress != null
: tabSuggestionsEnabled ? 'Downloading AI models (${downloadProgress.progress.toInt()}%)'
? 'Disable AI tab suggestions' : tabSuggestionsEnabled
: 'Enable AI tab suggestions', ? 'Disable AI tab suggestions'
onPressed: () async { : 'Enable AI tab suggestions',
if (!tabSuggestionsEnabled) { onPressed: () async {
final result = if (!tabSuggestionsEnabled) {
await showEnableAiTabSuggestionsDialog( final result =
context, await showEnableAiTabSuggestionsDialog(
); context,
);
if (result == true) { if (result == true) {
ref
.read(
tabSuggestionsControllerProvider
.notifier,
)
.enable();
}
} else {
ref ref
.read( .read(
tabSuggestionsControllerProvider tabSuggestionsControllerProvider
.notifier, .notifier,
) )
.enable(); .disable();
} }
} else { },
ref ),
.read(
tabSuggestionsControllerProvider
.notifier,
)
.disable();
}
},
), ),
); );
}, },
@@ -504,16 +513,18 @@ class TabViewHeader extends HookConsumerWidget {
}, },
), ),
], ],
child: IconButton( child: NonFocusable(
tooltip: 'Tab actions', child: IconButton(
onPressed: () { tooltip: 'Tab actions',
if (tabsActionMenuController.isOpen) { onPressed: () {
tabsActionMenuController.close(); if (tabsActionMenuController.isOpen) {
} else { tabsActionMenuController.close();
tabsActionMenuController.open(); } else {
} tabsActionMenuController.open();
}, }
icon: const Icon(MdiIcons.dotsVertical), },
icon: const Icon(MdiIcons.dotsVertical),
),
), ),
), ),
], ],
@@ -537,13 +548,15 @@ class TabViewHeader extends HookConsumerWidget {
searchTextController.text = data; searchTextController.text = data;
}, },
), ),
IconButton( NonFocusable(
onPressed: () { child: IconButton(
searchTextController.clear(); onPressed: () {
searchTextFocus.requestFocus(); searchTextController.clear();
searchMode.value = false; searchTextFocus.requestFocus();
}, searchMode.value = false;
icon: const Icon(Icons.clear), },
icon: const Icon(Icons.clear),
),
), ),
], ],
), ),
@@ -26,6 +26,7 @@ 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;
@@ -56,63 +57,65 @@ class TabsActionButton extends HookConsumerWidget {
selectedContainerDataProvider.select((value) => value.value?.color), selectedContainerDataProvider.select((value) => value.value?.color),
); );
return InkWell( return NonFocusable(
onTap: onTap, child: InkWell(
onDoubleTap: onDoubleTap, onTap: onTap,
onLongPress: onLongPress, onDoubleTap: onDoubleTap,
child: Padding( onLongPress: onLongPress,
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 15.0), child: Padding(
child: Container( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 15.0),
decoration: BoxDecoration( child: Container(
border: Border.all( decoration: BoxDecoration(
width: 2.0, border: Border.all(
color: isActive width: 2.0,
? theme.colorScheme.primary color: isActive
: DefaultTextStyle.of(context).style.color!, ? theme.colorScheme.primary
: DefaultTextStyle.of(context).style.color!,
),
borderRadius: BorderRadius.circular(5.0),
color: containerColor.mapNotNull(ContainerColors.forAppBar),
), ),
borderRadius: BorderRadius.circular(5.0), constraints: const BoxConstraints(minWidth: 25.0),
color: containerColor.mapNotNull(ContainerColors.forAppBar), child: Center(
), child: tabCount.when(
constraints: const BoxConstraints(minWidth: 25.0), skipLoadingOnReload: true,
child: Center( data: (count) {
child: tabCount.when( return Text(
skipLoadingOnReload: true, count.toString(),
data: (count) { style: TextStyle(
return Text( fontWeight: FontWeight.bold,
count.toString(), fontSize: 14.0,
style: TextStyle( color: isActive ? theme.colorScheme.primary : null,
fontWeight: FontWeight.bold, ),
fontSize: 14.0, );
color: isActive ? theme.colorScheme.primary : null, },
), loading: () => (tabCount.hasValue)
); ? Text(
}, tabCount.value.toString(),
loading: () => (tabCount.hasValue) style: TextStyle(
? Text( fontWeight: FontWeight.bold,
tabCount.value.toString(), fontSize: 14.0,
style: TextStyle( color: isActive ? theme.colorScheme.primary : null,
fontWeight: FontWeight.bold, ),
fontSize: 14.0, )
color: isActive ? theme.colorScheme.primary : null, : const Skeletonizer(child: Text('00')),
), error: (error, stackTrace) {
) logger.e(
: const Skeletonizer(child: Text('00')), 'Could not determine tab count',
error: (error, stackTrace) { error: error,
logger.e( stackTrace: stackTrace,
'Could not determine tab count', );
error: error,
stackTrace: stackTrace,
);
return Text( return Text(
'-1', '-1',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 14.0, fontSize: 14.0,
color: isActive ? theme.colorScheme.primary : null, color: isActive ? theme.colorScheme.primary : null,
), ),
); );
}, },
),
), ),
), ),
), ),
@@ -0,0 +1,45 @@
/*
* 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);
}
}