From edbc157c468c70d8b3a190ab51400287c12d229e Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Sun, 11 Jan 2026 18:07:37 +0100 Subject: [PATCH] added setting to disable double back to close tab --- .../browser/presentation/screens/browser.dart | 61 +++++++++++-------- .../screens/general_settings.dart | 29 +++++++++ .../user/data/models/general_settings.dart | 7 ++- .../user/data/models/general_settings.g.dart | 16 +++++ .../domain/repositories/general_settings.dart | 4 ++ .../repositories/general_settings.g.dart | 2 +- 6 files changed, 91 insertions(+), 28 deletions(-) diff --git a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart index f964fb96..35994304 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart @@ -569,6 +569,10 @@ class _Browser extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final doubleBackCloseTab = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.doubleBackCloseTab), + ); + final lastBackButtonPress = useRef(null); final overlayBuilder = ref.watch(overlayControllerProvider); @@ -682,39 +686,44 @@ class _Browser extends HookConsumerWidget { return false; } - if (lastBackButtonPress.value != null && - DateTime.now().difference(lastBackButtonPress.value!) < - _backButtonPressTimeout) { - lastBackButtonPress.value = null; + // Handle double back to close (if enabled) + if (doubleBackCloseTab) { + if (lastBackButtonPress.value != null && + DateTime.now().difference(lastBackButtonPress.value!) < + _backButtonPressTimeout) { + lastBackButtonPress.value = null; - if (tabState != null && tabCount > 1) { - await ref - .read(tabRepositoryProvider.notifier) - .closeTab(tabState.id); + if (tabState != null && tabCount > 1) { + await ref + .read(tabRepositoryProvider.notifier) + .closeTab(tabState.id); - if (context.mounted) { - ui_helper.showTabUndoClose( - context, - ref.read(tabRepositoryProvider.notifier).undoClose, - ); + if (context.mounted) { + ui_helper.showTabUndoClose( + context, + ref.read(tabRepositoryProvider.notifier).undoClose, + ); + } + + return true; + } else { + //Mark back as unhandled and navigator will pop + await SystemNavigator.pop(); + return false; } + } else { + lastBackButtonPress.value = DateTime.now(); + ui_helper.showTabBackButtonMessage( + context, + tabCount, + _backButtonPressTimeout, + ); return true; - } else { - //Mark back as unhandled and navigator will pop - await SystemNavigator.pop(); - return false; } - } else { - lastBackButtonPress.value = DateTime.now(); - ui_helper.showTabBackButtonMessage( - context, - tabCount, - _backButtonPressTimeout, - ); - - return true; } + + return true; }, child: _BrowserView( isFullscreen: tabInFullScreen, diff --git a/app/lib/features/settings/presentation/screens/general_settings.dart b/app/lib/features/settings/presentation/screens/general_settings.dart index c2f70b41..f37fbbec 100644 --- a/app/lib/features/settings/presentation/screens/general_settings.dart +++ b/app/lib/features/settings/presentation/screens/general_settings.dart @@ -67,6 +67,7 @@ class GeneralSettingsScreen extends StatelessWidget { _BottomSheetTabViewTile(), _TabBarSwipeBehaviorSection(), _PullToRefreshTile(), + _DoubleBackCloseTabTile(), _IconCacheTile(), ], ); @@ -867,6 +868,34 @@ class _PullToRefreshTile extends HookConsumerWidget { } } +class _DoubleBackCloseTabTile extends HookConsumerWidget { + const _DoubleBackCloseTabTile(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final doubleBackCloseTab = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.doubleBackCloseTab), + ); + + return SwitchListTile.adaptive( + title: const Text('Double Back to Close Tab'), + subtitle: const Text( + 'When enabled, press back twice to close the tab. When disabled, back button only navigates page history.', + ), + secondary: const Icon(MdiIcons.gestureDoubleTap), + value: doubleBackCloseTab, + onChanged: (value) async { + await ref + .read(saveGeneralSettingsControllerProvider.notifier) + .save( + (currentSettings) => + currentSettings.copyWith.doubleBackCloseTab(value), + ); + }, + ); + } +} + class _IconCacheTile extends HookConsumerWidget { const _IconCacheTile(); diff --git a/app/lib/features/user/data/models/general_settings.dart b/app/lib/features/user/data/models/general_settings.dart index f677c1e6..2eb9b89b 100644 --- a/app/lib/features/user/data/models/general_settings.dart +++ b/app/lib/features/user/data/models/general_settings.dart @@ -81,6 +81,7 @@ class GeneralSettings with FastEquatable { final TabBarPosition tabBarPosition; final QuickTabSwitcherMode quickTabSwitcherMode; final bool pullToRefreshEnabled; + final bool doubleBackCloseTab; GeneralSettings({ required this.themeMode, @@ -104,6 +105,7 @@ class GeneralSettings with FastEquatable { required this.tabBarPosition, required this.quickTabSwitcherMode, required this.pullToRefreshEnabled, + required this.doubleBackCloseTab, }); GeneralSettings.withDefaults({ @@ -128,6 +130,7 @@ class GeneralSettings with FastEquatable { TabBarPosition? tabBarPosition, QuickTabSwitcherMode? quickTabSwitcherMode, bool? pullToRefreshEnabled, + bool? doubleBackCloseTab, }) : themeMode = themeMode ?? ThemeMode.dark, enableReadability = enableReadability ?? true, enforceReadability = enforceReadability ?? false, @@ -151,7 +154,8 @@ class GeneralSettings with FastEquatable { tabBarPosition = tabBarPosition ?? TabBarPosition.bottom, quickTabSwitcherMode = quickTabSwitcherMode ?? QuickTabSwitcherMode.lastUsedTabs, - pullToRefreshEnabled = pullToRefreshEnabled ?? true; + pullToRefreshEnabled = pullToRefreshEnabled ?? true, + doubleBackCloseTab = doubleBackCloseTab ?? true; factory GeneralSettings.fromJson(Map json) => _$GeneralSettingsFromJson(json); @@ -181,5 +185,6 @@ class GeneralSettings with FastEquatable { tabBarPosition, quickTabSwitcherMode, pullToRefreshEnabled, + doubleBackCloseTab, ]; } diff --git a/app/lib/features/user/data/models/general_settings.g.dart b/app/lib/features/user/data/models/general_settings.g.dart index a5df805a..5dce90dd 100644 --- a/app/lib/features/user/data/models/general_settings.g.dart +++ b/app/lib/features/user/data/models/general_settings.g.dart @@ -59,6 +59,8 @@ abstract class _$GeneralSettingsCWProxy { GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled); + GeneralSettings doubleBackCloseTab(bool doubleBackCloseTab); + /// Creates a new instance with the provided field values. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`. /// @@ -88,6 +90,7 @@ abstract class _$GeneralSettingsCWProxy { TabBarPosition tabBarPosition, QuickTabSwitcherMode quickTabSwitcherMode, bool pullToRefreshEnabled, + bool doubleBackCloseTab, }); } @@ -186,6 +189,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled) => call(pullToRefreshEnabled: pullToRefreshEnabled); + @override + GeneralSettings doubleBackCloseTab(bool doubleBackCloseTab) => + call(doubleBackCloseTab: doubleBackCloseTab); + @override /// Creates a new instance with the provided field values. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `GeneralSettings(...).copyWith.fieldName(value)`. @@ -216,6 +223,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { Object? tabBarPosition = const $CopyWithPlaceholder(), Object? quickTabSwitcherMode = const $CopyWithPlaceholder(), Object? pullToRefreshEnabled = const $CopyWithPlaceholder(), + Object? doubleBackCloseTab = const $CopyWithPlaceholder(), }) { return GeneralSettings( themeMode: themeMode == const $CopyWithPlaceholder() || themeMode == null @@ -340,6 +348,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { ? _value.pullToRefreshEnabled // ignore: cast_nullable_to_non_nullable : pullToRefreshEnabled as bool, + doubleBackCloseTab: + doubleBackCloseTab == const $CopyWithPlaceholder() || + doubleBackCloseTab == null + ? _value.doubleBackCloseTab + // ignore: cast_nullable_to_non_nullable + : doubleBackCloseTab as bool, ); } } @@ -405,6 +419,7 @@ GeneralSettings _$GeneralSettingsFromJson( json['quickTabSwitcherMode'], ), pullToRefreshEnabled: json['pullToRefreshEnabled'] as bool?, + doubleBackCloseTab: json['doubleBackCloseTab'] as bool?, ); Map _$GeneralSettingsToJson( @@ -439,6 +454,7 @@ Map _$GeneralSettingsToJson( 'quickTabSwitcherMode': _$QuickTabSwitcherModeEnumMap[instance.quickTabSwitcherMode]!, 'pullToRefreshEnabled': instance.pullToRefreshEnabled, + 'doubleBackCloseTab': instance.doubleBackCloseTab, }; const _$ThemeModeEnumMap = { diff --git a/app/lib/features/user/domain/repositories/general_settings.dart b/app/lib/features/user/domain/repositories/general_settings.dart index a6fc8a85..fab79928 100644 --- a/app/lib/features/user/domain/repositories/general_settings.dart +++ b/app/lib/features/user/domain/repositories/general_settings.dart @@ -126,6 +126,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository { DriftSqlType.bool, db.typeMapping, ), + 'doubleBackCloseTab': settings['doubleBackCloseTab']?.readAs( + DriftSqlType.bool, + db.typeMapping, + ), }); } diff --git a/app/lib/features/user/domain/repositories/general_settings.g.dart b/app/lib/features/user/domain/repositories/general_settings.g.dart index 42787471..3af10422 100644 --- a/app/lib/features/user/domain/repositories/general_settings.g.dart +++ b/app/lib/features/user/domain/repositories/general_settings.g.dart @@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider } String _$generalSettingsRepositoryHash() => - r'dceccac3a93e96907c7bd2b4c58f5021912553ac'; + r'ada58479ceea436f08e22780be200c99e3abedb7'; abstract class _$GeneralSettingsRepository extends $StreamNotifier {