use reload button to cancel load if available

This commit is contained in:
Fabian Freund
2026-06-02 12:56:29 +02:00
parent 9e6cc84f87
commit f174bf532e
2 changed files with 57 additions and 12 deletions
@@ -34,6 +34,8 @@ import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
import 'package:weblibre/features/geckoview/features/bookmarks/domain/repositories/bookmarks.dart'; import 'package:weblibre/features/geckoview/features/bookmarks/domain/repositories/bookmarks.dart';
import 'package:weblibre/features/geckoview/features/bookmarks/domain/utils/bookmark_tree_utils.dart'; import 'package:weblibre/features/geckoview/features/bookmarks/domain/utils/bookmark_tree_utils.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/entities/font_size_constants.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/entities/font_size_constants.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_id.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_spec.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_button_spec.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_bar_buttons.dart'; import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/widgets/contextual_bar_buttons.dart';
@@ -83,14 +85,31 @@ class ToolbarButtonDefinition {
}); });
} }
/// Whether a reload button is currently configured visible in the contextual
/// toolbar. Used to decide whether the back button should fall back to acting
/// as a stop-loading control (see issue #351).
bool _isReloadButtonVisible(WidgetRef ref) {
return ref
.read(effectiveToolbarButtonConfigsProvider)
.value
.any(
(config) =>
config.buttonId == ToolbarButtonId.reload.name && config.isVisible,
);
}
final List<ToolbarButtonDefinition> toolbarButtonRegistry = [ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
ToolbarButtonDefinition( ToolbarButtonDefinition(
spec: backToolbarButtonSpec, spec: backToolbarButtonSpec,
label: 'Back', label: 'Back',
icon: Icons.arrow_back, icon: Icons.arrow_back,
isPrimaryAvailable: (scope, ref) => isPrimaryAvailable: (scope, ref) {
scope.tabState?.historyState.canGoBack == true || final canGoBack = scope.tabState?.historyState.canGoBack == true;
scope.tabState?.isLoading == true, final isLoading = scope.tabState?.isLoading == true;
// The back button only doubles as a stop-loading control when no
// dedicated reload button is present to take over that role.
return canGoBack || (isLoading && !_isReloadButtonVisible(ref));
},
longPressActions: ['History Menu (Previous pages)'], longPressActions: ['History Menu (Previous pages)'],
builder: (scope, context, ref) { builder: (scope, context, ref) {
if (scope.isPreview) { if (scope.isPreview) {
@@ -104,6 +123,7 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
return NavigateBackButton( return NavigateBackButton(
selectedTabId: scope.selectedTabId, selectedTabId: scope.selectedTabId,
isLoading: scope.tabState?.isLoading ?? false, isLoading: scope.tabState?.isLoading ?? false,
stopLoadingFallback: !_isReloadButtonVisible(ref),
); );
}, },
), ),
@@ -616,6 +636,7 @@ class _ReloadToolbarButton extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final menuController = useMemoized(MenuController.new); final menuController = useMemoized(MenuController.new);
final isLoading = scope.tabState?.isLoading ?? false;
return MenuAnchor( return MenuAnchor(
controller: menuController, controller: menuController,
@@ -640,12 +661,21 @@ class _ReloadToolbarButton extends HookConsumerWidget {
: () async { : () async {
final tabId = scope.selectedTabId; final tabId = scope.selectedTabId;
if (tabId != null) { if (tabId != null) {
await ref final controller = ref.read(
.read(tabSessionProvider(tabId: tabId).notifier) tabSessionProvider(tabId: tabId).notifier,
.reload(); );
// While loading the button acts as a stop control; otherwise
// it reloads the page.
if (isLoading) {
await controller.stopLoading();
} else {
await controller.reload();
}
} }
}, },
onLongPress: scope.isPreview // Hard Refresh is meaningless mid-load, so disable the long-press menu
// while the stop action is active.
onLongPress: (scope.isPreview || isLoading)
? null ? null
: () { : () {
if (menuController.isOpen) { if (menuController.isOpen) {
@@ -654,7 +684,7 @@ class _ReloadToolbarButton extends HookConsumerWidget {
menuController.open(); menuController.open();
} }
}, },
icon: const Icon(Icons.refresh), icon: Icon(isLoading ? Icons.close : Icons.refresh),
), ),
); );
} }
@@ -52,21 +52,30 @@ class NavigateBackButtonView extends StatelessWidget {
super.key, super.key,
required this.canGoBack, required this.canGoBack,
required this.isLoading, required this.isLoading,
this.stopLoadingFallback = true,
this.onPressed, this.onPressed,
this.onLongPress, this.onLongPress,
}); });
final bool canGoBack; final bool canGoBack;
final bool isLoading; final bool isLoading;
/// When `true` (default), the button doubles as a stop-loading control while
/// [isLoading] (shows a close icon, taps cancel the load). When `false` it
/// stays a plain back button even during loading — used when a dedicated
/// reload/stop button is present elsewhere in the toolbar.
final bool stopLoadingFallback;
final VoidCallback? onPressed; final VoidCallback? onPressed;
final VoidCallback? onLongPress; final VoidCallback? onLongPress;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final showStop = isLoading && stopLoadingFallback;
return IconButton( return IconButton(
onPressed: (canGoBack || isLoading) ? onPressed : null, onPressed: (canGoBack || showStop) ? onPressed : null,
onLongPress: (canGoBack && !isLoading) ? onLongPress : null, onLongPress: (canGoBack && !showStop) ? onLongPress : null,
icon: isLoading ? const Icon(Icons.close) : const Icon(Icons.arrow_back), icon: showStop ? const Icon(Icons.close) : const Icon(Icons.arrow_back),
); );
} }
} }
@@ -118,18 +127,23 @@ class NavigateBackButton extends HookConsumerWidget {
super.key, super.key,
required this.selectedTabId, required this.selectedTabId,
required this.isLoading, required this.isLoading,
this.stopLoadingFallback = true,
this.menuControllerToClose, this.menuControllerToClose,
this.canGoBack = true, this.canGoBack = true,
}); });
final String? selectedTabId; final String? selectedTabId;
final bool isLoading; final bool isLoading;
/// See [NavigateBackButtonView.stopLoadingFallback].
final bool stopLoadingFallback;
final MenuController? menuControllerToClose; final MenuController? menuControllerToClose;
final bool canGoBack; final bool canGoBack;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final historyMenuController = useMenuController(); final historyMenuController = useMenuController();
final showStop = isLoading && stopLoadingFallback;
return HistoryMenu( return HistoryMenu(
selectedTabId: selectedTabId, selectedTabId: selectedTabId,
@@ -138,6 +152,7 @@ class NavigateBackButton extends HookConsumerWidget {
child: NavigateBackButtonView( child: NavigateBackButtonView(
canGoBack: canGoBack, canGoBack: canGoBack,
isLoading: isLoading, isLoading: isLoading,
stopLoadingFallback: stopLoadingFallback,
onPressed: () async { onPressed: () async {
final controller = ref.read( final controller = ref.read(
tabSessionProvider(tabId: selectedTabId).notifier, tabSessionProvider(tabId: selectedTabId).notifier,
@@ -149,7 +164,7 @@ class NavigateBackButton extends HookConsumerWidget {
), ),
); );
if (isLoading) { if (showStop) {
await controller.stopLoading(); await controller.stopLoading();
} else if (isReaderActive) { } else if (isReaderActive) {
await ref await ref