dont allow focus on buttons
This commit is contained in:
+187
-152
@@ -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();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-34
@@ -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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+92
-79
@@ -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),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
+58
-55
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user