long press history navigation
This commit is contained in:
@@ -56,6 +56,10 @@ class TabSession extends _$TabSession {
|
||||
return _sessionService.goForward();
|
||||
}
|
||||
|
||||
Future<void> goToHistoryIndex({required int index}) {
|
||||
return _sessionService.goToHistoryIndex(index: index);
|
||||
}
|
||||
|
||||
Future<void> exitFullscreen() {
|
||||
return _sessionService.exitFullscreen();
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ final class TabSessionProvider extends $NotifierProvider<TabSession, void> {
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabSessionHash() => r'4b7206863e205dfeee6201ba6072e742c43ab6f3';
|
||||
String _$tabSessionHash() => r'5be471574bb868889cc1eee37ae7e4570a9e21a4';
|
||||
|
||||
final class TabSessionFamily extends $Family
|
||||
with $ClassFamilyOverride<TabSession, void, void, void, String?> {
|
||||
|
||||
+64
-35
@@ -41,6 +41,7 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/contro
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/app_bar_title.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_badge_icon.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/extension_shortcut_menu.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/history_menu.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/menu_item_buttons.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_creation_menu.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_menu.dart';
|
||||
@@ -924,18 +925,32 @@ class NavigateForwardButton extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return IconButton(
|
||||
onPressed: canGoForward
|
||||
? () async {
|
||||
final controller = ref.read(
|
||||
tabSessionProvider(tabId: selectedTabId).notifier,
|
||||
);
|
||||
final historyMenuController = useMenuController();
|
||||
|
||||
await controller.goForward();
|
||||
menuControllerToClose?.close();
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
return HistoryMenu(
|
||||
selectedTabId: selectedTabId,
|
||||
controller: historyMenuController,
|
||||
direction: HistoryMenuDirection.forward,
|
||||
child: IconButton(
|
||||
onPressed: canGoForward
|
||||
? () async {
|
||||
final controller = ref.read(
|
||||
tabSessionProvider(tabId: selectedTabId).notifier,
|
||||
);
|
||||
|
||||
await controller.goForward();
|
||||
menuControllerToClose?.close();
|
||||
}
|
||||
: null,
|
||||
onLongPress: canGoForward
|
||||
? () {
|
||||
if (!historyMenuController.isOpen) {
|
||||
historyMenuController.open();
|
||||
}
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -956,33 +971,47 @@ class NavigateBackButton extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return IconButton(
|
||||
onPressed: (canGoBack || isLoading)
|
||||
? () async {
|
||||
final controller = ref.read(
|
||||
tabSessionProvider(tabId: selectedTabId).notifier,
|
||||
);
|
||||
final historyMenuController = useMenuController();
|
||||
|
||||
final isReaderActive = ref.read(
|
||||
selectedTabStateProvider.select(
|
||||
(state) => state?.readerableState.active ?? false,
|
||||
),
|
||||
);
|
||||
return HistoryMenu(
|
||||
selectedTabId: selectedTabId,
|
||||
controller: historyMenuController,
|
||||
direction: HistoryMenuDirection.back,
|
||||
child: IconButton(
|
||||
onPressed: (canGoBack || isLoading)
|
||||
? () async {
|
||||
final controller = ref.read(
|
||||
tabSessionProvider(tabId: selectedTabId).notifier,
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
await controller.stopLoading();
|
||||
} else if (isReaderActive) {
|
||||
await ref
|
||||
.read(readerableScreenControllerProvider.notifier)
|
||||
.toggleReaderView(false);
|
||||
} else {
|
||||
await controller.goBack();
|
||||
final isReaderActive = ref.read(
|
||||
selectedTabStateProvider.select(
|
||||
(state) => state?.readerableState.active ?? false,
|
||||
),
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
await controller.stopLoading();
|
||||
} else if (isReaderActive) {
|
||||
await ref
|
||||
.read(readerableScreenControllerProvider.notifier)
|
||||
.toggleReaderView(false);
|
||||
} else {
|
||||
await controller.goBack();
|
||||
}
|
||||
|
||||
menuControllerToClose?.close();
|
||||
}
|
||||
|
||||
menuControllerToClose?.close();
|
||||
}
|
||||
: null,
|
||||
icon: isLoading ? const Icon(Icons.close) : const Icon(Icons.arrow_back),
|
||||
: null,
|
||||
onLongPress: (canGoBack && !isLoading)
|
||||
? () {
|
||||
if (!historyMenuController.isOpen) {
|
||||
historyMenuController.open();
|
||||
}
|
||||
}
|
||||
: null,
|
||||
icon: isLoading ? const Icon(Icons.close) : const Icon(Icons.arrow_back),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/entities/states/history.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/history_menu_item.dart';
|
||||
|
||||
enum HistoryMenuDirection { back, forward }
|
||||
|
||||
class HistoryMenu extends HookConsumerWidget {
|
||||
const HistoryMenu({
|
||||
super.key,
|
||||
required this.selectedTabId,
|
||||
required this.controller,
|
||||
required this.direction,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final String? selectedTabId;
|
||||
final MenuController controller;
|
||||
final HistoryMenuDirection direction;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final historyState = ref.watch(
|
||||
tabStateProvider(selectedTabId).select((state) => state?.historyState),
|
||||
);
|
||||
|
||||
final menuItems = useMemoized(() => _buildMenuItems(historyState), [
|
||||
historyState,
|
||||
]);
|
||||
|
||||
return MenuAnchor(
|
||||
controller: controller,
|
||||
consumeOutsideTap: true,
|
||||
menuChildren: menuItems,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildMenuItems(HistoryState? historyState) {
|
||||
if (historyState == null || historyState.items.isEmpty) {
|
||||
return [
|
||||
MenuItemButton(
|
||||
child: Text(
|
||||
direction == HistoryMenuDirection.back
|
||||
? 'No previous pages'
|
||||
: 'No forward pages',
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
final items = historyState.items.indexed;
|
||||
final currentIndex = historyState.currentIndex;
|
||||
|
||||
final historyItems = switch (direction) {
|
||||
// Back: oldest at top, newest at bottom (close to button)
|
||||
HistoryMenuDirection.back => items.where((e) => e.$1 < currentIndex),
|
||||
// Forward: furthest at top, closest at bottom (close to button)
|
||||
HistoryMenuDirection.forward =>
|
||||
items.where((e) => e.$1 > currentIndex).toList().reversed,
|
||||
};
|
||||
|
||||
if (historyItems.isEmpty) {
|
||||
final message = direction == HistoryMenuDirection.back
|
||||
? 'No previous pages'
|
||||
: 'No forward pages';
|
||||
return [MenuItemButton(child: Text(message))];
|
||||
}
|
||||
|
||||
return historyItems
|
||||
.map(
|
||||
(e) => HistoryMenuItem(
|
||||
selectedTabId: selectedTabId,
|
||||
item: e.$2,
|
||||
historyIndex: e.$1,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/entities/states/history.dart';
|
||||
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
||||
import 'package:weblibre/presentation/widgets/uri_breadcrumb.dart';
|
||||
import 'package:weblibre/presentation/widgets/url_icon.dart';
|
||||
|
||||
class HistoryMenuItem extends HookConsumerWidget {
|
||||
const HistoryMenuItem({
|
||||
super.key,
|
||||
required this.selectedTabId,
|
||||
required this.item,
|
||||
required this.historyIndex,
|
||||
});
|
||||
|
||||
final String? selectedTabId;
|
||||
final HistoryItem item;
|
||||
final int historyIndex;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return MenuItemButton(
|
||||
leadingIcon: UrlIcon([item.url], iconSize: 24),
|
||||
closeOnActivate: false,
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(tabSessionProvider(tabId: selectedTabId).notifier)
|
||||
.goToHistoryIndex(index: historyIndex);
|
||||
|
||||
if (context.mounted) {
|
||||
MenuController.maybeOf(context)?.close();
|
||||
}
|
||||
},
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 280),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
item.title.isNotEmpty ? item.title : item.url.authority,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
UriBreadcrumb(
|
||||
uri: item.url,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user