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/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/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/presentation/models/contextual_toolbar_scope.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 = [
ToolbarButtonDefinition(
spec: backToolbarButtonSpec,
label: 'Back',
icon: Icons.arrow_back,
isPrimaryAvailable: (scope, ref) =>
scope.tabState?.historyState.canGoBack == true ||
scope.tabState?.isLoading == true,
isPrimaryAvailable: (scope, ref) {
final canGoBack = scope.tabState?.historyState.canGoBack == 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)'],
builder: (scope, context, ref) {
if (scope.isPreview) {
@@ -104,6 +123,7 @@ final List<ToolbarButtonDefinition> toolbarButtonRegistry = [
return NavigateBackButton(
selectedTabId: scope.selectedTabId,
isLoading: scope.tabState?.isLoading ?? false,
stopLoadingFallback: !_isReloadButtonVisible(ref),
);
},
),
@@ -616,6 +636,7 @@ class _ReloadToolbarButton extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final menuController = useMemoized(MenuController.new);
final isLoading = scope.tabState?.isLoading ?? false;
return MenuAnchor(
controller: menuController,
@@ -640,12 +661,21 @@ class _ReloadToolbarButton extends HookConsumerWidget {
: () async {
final tabId = scope.selectedTabId;
if (tabId != null) {
await ref
.read(tabSessionProvider(tabId: tabId).notifier)
.reload();
final controller = ref.read(
tabSessionProvider(tabId: tabId).notifier,
);
// 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
: () {
if (menuController.isOpen) {
@@ -654,7 +684,7 @@ class _ReloadToolbarButton extends HookConsumerWidget {
menuController.open();
}
},
icon: const Icon(Icons.refresh),
icon: Icon(isLoading ? Icons.close : Icons.refresh),
),
);
}
@@ -52,21 +52,30 @@ class NavigateBackButtonView extends StatelessWidget {
super.key,
required this.canGoBack,
required this.isLoading,
this.stopLoadingFallback = true,
this.onPressed,
this.onLongPress,
});
final bool canGoBack;
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? onLongPress;
@override
Widget build(BuildContext context) {
final showStop = isLoading && stopLoadingFallback;
return IconButton(
onPressed: (canGoBack || isLoading) ? onPressed : null,
onLongPress: (canGoBack && !isLoading) ? onLongPress : null,
icon: isLoading ? const Icon(Icons.close) : const Icon(Icons.arrow_back),
onPressed: (canGoBack || showStop) ? onPressed : null,
onLongPress: (canGoBack && !showStop) ? onLongPress : null,
icon: showStop ? const Icon(Icons.close) : const Icon(Icons.arrow_back),
);
}
}
@@ -118,18 +127,23 @@ class NavigateBackButton extends HookConsumerWidget {
super.key,
required this.selectedTabId,
required this.isLoading,
this.stopLoadingFallback = true,
this.menuControllerToClose,
this.canGoBack = true,
});
final String? selectedTabId;
final bool isLoading;
/// See [NavigateBackButtonView.stopLoadingFallback].
final bool stopLoadingFallback;
final MenuController? menuControllerToClose;
final bool canGoBack;
@override
Widget build(BuildContext context, WidgetRef ref) {
final historyMenuController = useMenuController();
final showStop = isLoading && stopLoadingFallback;
return HistoryMenu(
selectedTabId: selectedTabId,
@@ -138,6 +152,7 @@ class NavigateBackButton extends HookConsumerWidget {
child: NavigateBackButtonView(
canGoBack: canGoBack,
isLoading: isLoading,
stopLoadingFallback: stopLoadingFallback,
onPressed: () async {
final controller = ref.read(
tabSessionProvider(tabId: selectedTabId).notifier,
@@ -149,7 +164,7 @@ class NavigateBackButton extends HookConsumerWidget {
),
);
if (isLoading) {
if (showStop) {
await controller.stopLoading();
} else if (isReaderActive) {
await ref