add long press menus to bottom sheet

This commit is contained in:
Fabian Freund
2026-03-12 08:37:21 +01:00
parent bf4b8a31f4
commit 02db40d74c
@@ -49,6 +49,7 @@ import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/content_selection_dialog.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/content_selection_dialog.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/qr_code.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/history_menu.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/translation_bottom_sheet.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/translation_bottom_sheet.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/open_link_tools/domain/services/url_cleaner_catalog_service.dart'; import 'package:weblibre/features/geckoview/features/open_link_tools/domain/services/url_cleaner_catalog_service.dart';
@@ -72,6 +73,7 @@ import 'package:weblibre/features/user/domain/providers.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/controllers/website_title.dart'; import 'package:weblibre/presentation/controllers/website_title.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/icons/tor_icons.dart'; import 'package:weblibre/presentation/icons/tor_icons.dart';
import 'package:weblibre/utils/exit_app.dart'; import 'package:weblibre/utils/exit_app.dart';
import 'package:weblibre/utils/ui_helper.dart' as ui_helper; import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
@@ -224,16 +226,33 @@ class _NavigationRow extends HookConsumerWidget {
final history = ref.watch( final history = ref.watch(
tabStateProvider(selectedTabId).select((value) => value?.historyState), tabStateProvider(selectedTabId).select((value) => value?.historyState),
); );
final host = ref.watch(
tabStateProvider(selectedTabId).select((value) => value?.url.host),
);
final isLoading = ref.watch( final isLoading = ref.watch(
tabStateProvider( tabStateProvider(
selectedTabId, selectedTabId,
).select((state) => state?.isLoading ?? false), ).select((state) => state?.isLoading ?? false),
); );
final isReaderActive = ref.watch(
tabStateProvider(
selectedTabId,
).select((state) => state?.readerableState.active ?? false),
);
final backMenuController = useMenuController();
final forwardMenuController = useMenuController();
final closeMenuController = useMenuController();
final reloadMenuController = useMenuController();
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
_buildNavIcon( HistoryMenu(
selectedTabId: selectedTabId,
controller: backMenuController,
direction: HistoryMenuDirection.back,
child: _buildNavIcon(
icon: isLoading ? Icons.close : Icons.arrow_back, icon: isLoading ? Icons.close : Icons.arrow_back,
label: isLoading ? 'Stop' : 'Back', label: isLoading ? 'Stop' : 'Back',
disabled: !isLoading && history?.canGoBack != true, disabled: !isLoading && history?.canGoBack != true,
@@ -243,13 +262,29 @@ class _NavigationRow extends HookConsumerWidget {
); );
if (isLoading) { if (isLoading) {
await controller.stopLoading(); await controller.stopLoading();
} else if (isReaderActive) {
await ref
.read(readerableScreenControllerProvider.notifier)
.toggleReaderView(false);
} else { } else {
await controller.goBack(); await controller.goBack();
} }
if (context.mounted) Navigator.pop(context); if (context.mounted) Navigator.pop(context);
}, },
onLongPress: isLoading || history?.canGoBack != true
? null
: () {
if (!backMenuController.isOpen) {
backMenuController.open();
}
},
), ),
_buildNavIcon( ),
HistoryMenu(
selectedTabId: selectedTabId,
controller: forwardMenuController,
direction: HistoryMenuDirection.forward,
child: _buildNavIcon(
icon: Icons.arrow_forward, icon: Icons.arrow_forward,
label: 'Forward', label: 'Forward',
disabled: history?.canGoForward != true, disabled: history?.canGoForward != true,
@@ -259,8 +294,59 @@ class _NavigationRow extends HookConsumerWidget {
.goForward(); .goForward();
if (context.mounted) Navigator.pop(context); if (context.mounted) Navigator.pop(context);
}, },
onLongPress: history?.canGoForward != true
? null
: () {
if (!forwardMenuController.isOpen) {
forwardMenuController.open();
}
},
), ),
_buildNavIcon( ),
MenuAnchor(
controller: closeMenuController,
builder: (context, controller, child) => child!,
menuChildren: [
MenuItemButton(
leadingIcon: const Icon(Icons.tab),
onPressed: () async {
final tabStates = ref.read(tabStatesProvider);
final otherIds = tabStates.keys
.where((id) => id != selectedTabId)
.toList();
if (otherIds.isNotEmpty) {
await ref
.read(tabRepositoryProvider.notifier)
.closeTabs(otherIds);
}
if (context.mounted) {
Navigator.pop(context);
}
},
child: const Text('Close Others'),
),
if (host != null && host.isNotEmpty)
MenuItemButton(
leadingIcon: const Icon(Icons.language),
onPressed: () async {
final tabStates = ref.read(tabStatesProvider);
final sameHostIds = tabStates.entries
.where((e) => e.value.url.host == host)
.map((e) => e.key)
.toList();
if (sameHostIds.isNotEmpty) {
await ref
.read(tabRepositoryProvider.notifier)
.closeTabs(sameHostIds);
}
if (context.mounted) {
Navigator.pop(context);
}
},
child: const Text('Close from Same Host'),
),
],
child: _buildNavIcon(
icon: MdiIcons.tabMinus, icon: MdiIcons.tabMinus,
label: 'Close Tab', label: 'Close Tab',
onTap: () async { onTap: () async {
@@ -269,7 +355,8 @@ class _NavigationRow extends HookConsumerWidget {
final allStates = ref.read(tabStatesProvider); final allStates = ref.read(tabStatesProvider);
final groupCount = allStates.values final groupCount = allStates.values
.where( .where(
(s) => s.isolationContextId == tabState.isolationContextId, (s) =>
s.isolationContextId == tabState.isolationContextId,
) )
.length; .length;
if (groupCount <= 1 && context.mounted) { if (groupCount <= 1 && context.mounted) {
@@ -292,8 +379,27 @@ class _NavigationRow extends HookConsumerWidget {
); );
} }
}, },
onLongPress: closeMenuController.open,
), ),
_buildNavIcon( ),
MenuAnchor(
controller: reloadMenuController,
builder: (context, controller, child) => child!,
menuChildren: [
MenuItemButton(
leadingIcon: const Icon(Icons.refresh),
onPressed: () async {
await ref
.read(tabSessionProvider(tabId: selectedTabId).notifier)
.reload(flags: LoadUrlFlags.BYPASS_CACHE);
if (context.mounted) {
Navigator.pop(context);
}
},
child: const Text('Hard Refresh'),
),
],
child: _buildNavIcon(
icon: Icons.refresh, icon: Icons.refresh,
label: 'Reload', label: 'Reload',
onTap: () async { onTap: () async {
@@ -302,6 +408,8 @@ class _NavigationRow extends HookConsumerWidget {
.reload(); .reload();
if (context.mounted) Navigator.pop(context); if (context.mounted) Navigator.pop(context);
}, },
onLongPress: reloadMenuController.open,
),
), ),
], ],
); );
@@ -312,12 +420,14 @@ class _NavigationRow extends HookConsumerWidget {
required String label, required String label,
bool disabled = false, bool disabled = false,
required VoidCallback onTap, required VoidCallback onTap,
VoidCallback? onLongPress,
}) { }) {
return Builder( return Builder(
builder: (context) { builder: (context) {
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
return InkWell( return InkWell(
onTap: disabled ? null : onTap, onTap: disabled ? null : onTap,
onLongPress: disabled ? null : onLongPress,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(