diff --git a/app/lib/core/routing/routes.browser.dart b/app/lib/core/routing/routes.browser.dart index 7d0cdb1f..4adb94f7 100644 --- a/app/lib/core/routing/routes.browser.dart +++ b/app/lib/core/routing/routes.browser.dart @@ -116,9 +116,25 @@ class SearchRoute extends GoRouteData with $SearchRoute { } } +bool _isContainerUiEnabled(BuildContext context) { + final settings = ProviderScope.containerOf( + context, + listen: false, + ).read(generalSettingsWithDefaultsProvider); + + return settings.showContainerUi; +} + class ContainerDraftRoute extends GoRouteData with $ContainerDraftRoute { const ContainerDraftRoute(); + @override + String? redirect(BuildContext context, GoRouterState state) { + return _isContainerUiEnabled(context) + ? null + : const BrowserRoute().location; + } + @override Widget build(BuildContext context, GoRouterState state) { return const ContainerDraftSuggestionsScreen(); @@ -128,6 +144,13 @@ class ContainerDraftRoute extends GoRouteData with $ContainerDraftRoute { class ContainerListRoute extends GoRouteData with $ContainerListRoute { const ContainerListRoute(); + @override + String? redirect(BuildContext context, GoRouterState state) { + return _isContainerUiEnabled(context) + ? null + : const BrowserRoute().location; + } + @override Widget build(BuildContext context, GoRouterState state) { return const ContainerListScreen(); @@ -138,6 +161,13 @@ class ContainerSelectionRoute extends GoRouteData with $ContainerSelectionRoute { const ContainerSelectionRoute(); + @override + String? redirect(BuildContext context, GoRouterState state) { + return _isContainerUiEnabled(context) + ? null + : const BrowserRoute().location; + } + @override Widget build(BuildContext context, GoRouterState state) { return const ContainerSelectionScreen(); @@ -149,6 +179,13 @@ class ContainerEditRoute extends GoRouteData with $ContainerEditRoute { const ContainerEditRoute({required this.containerData}); + @override + String? redirect(BuildContext context, GoRouterState state) { + return _isContainerUiEnabled(context) + ? null + : const BrowserRoute().location; + } + @override Widget build(BuildContext context, GoRouterState state) { return ContainerEditScreen.edit( @@ -165,6 +202,13 @@ class ContainerCreateRoute extends GoRouteData with $ContainerCreateRoute { ContainerCreateRoute({required this.containerData, this.tabIds = '[]'}); + @override + String? redirect(BuildContext context, GoRouterState state) { + return _isContainerUiEnabled(context) + ? null + : const BrowserRoute().location; + } + @override Widget build(BuildContext context, GoRouterState state) { final tabIdsList = jsonDecode(tabIds) as List; diff --git a/app/lib/core/routing/routes.dart b/app/lib/core/routing/routes.dart index 42a6bfd0..f2a350f9 100644 --- a/app/lib/core/routing/routes.dart +++ b/app/lib/core/routing/routes.dart @@ -23,6 +23,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nullability/nullability.dart'; import 'package:weblibre/core/routing/widgets/bottom_sheet_page.dart'; import 'package:weblibre/core/routing/widgets/dialog_page.dart'; @@ -82,6 +83,7 @@ import 'package:weblibre/features/user/domain/presentation/screens/profile_edit. import 'package:weblibre/features/user/domain/presentation/screens/profile_list.dart'; import 'package:weblibre/features/user/domain/presentation/screens/profile_restore.dart'; import 'package:weblibre/features/user/domain/presentation/widgets/auth_gate.dart'; +import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/features/web_feed/presentation/add_feed_dialog.dart'; import 'package:weblibre/features/web_feed/presentation/screens/feed_article.dart'; import 'package:weblibre/features/web_feed/presentation/screens/feed_article_list.dart'; diff --git a/app/lib/features/bangs/presentation/screens/category.dart b/app/lib/features/bangs/presentation/screens/category.dart index 456d4ab4..8ad7f9c2 100644 --- a/app/lib/features/bangs/presentation/screens/category.dart +++ b/app/lib/features/bangs/presentation/screens/category.dart @@ -69,7 +69,7 @@ class BangCategoryScreen extends HookConsumerWidget { SearchRoute( tabType: ref.read(selectedTabTypeProvider) ?? - settings.defaultCreateTabType, + settings.effectiveDefaultCreateTabType, ).go(context); }, ); diff --git a/app/lib/features/bangs/presentation/widgets/bang_details.dart b/app/lib/features/bangs/presentation/widgets/bang_details.dart index 441bf07a..10e4d561 100644 --- a/app/lib/features/bangs/presentation/widgets/bang_details.dart +++ b/app/lib/features/bangs/presentation/widgets/bang_details.dart @@ -92,7 +92,7 @@ class BangDetails extends HookConsumerWidget { final tabMode = TabMode.fromTabType( ref .read(generalSettingsWithDefaultsProvider) - .defaultCreateTabType, + .effectiveDefaultCreateTabType, ); await ref diff --git a/app/lib/features/geckoview/domain/providers.dart b/app/lib/features/geckoview/domain/providers.dart index 9b377529..835d8628 100644 --- a/app/lib/features/geckoview/domain/providers.dart +++ b/app/lib/features/geckoview/domain/providers.dart @@ -54,7 +54,8 @@ GeckoSelectionActionService selectionActionService(Ref ref) { final selectedTabType = selectedTabState?.tabMode.toTabType(); final route = SearchRoute( - tabType: selectedTabType ?? settings.defaultCreateTabType, + tabType: + selectedTabType ?? settings.effectiveDefaultCreateTabType, searchText: text, ); @@ -78,7 +79,7 @@ GeckoSelectionActionService selectionActionService(Ref ref) { TabMode.fromTabType( ref .read(generalSettingsWithDefaultsProvider) - .defaultCreateTabType, + .effectiveDefaultCreateTabType, ); await ref diff --git a/app/lib/features/geckoview/domain/providers.g.dart b/app/lib/features/geckoview/domain/providers.g.dart index 142176d3..451377e1 100644 --- a/app/lib/features/geckoview/domain/providers.g.dart +++ b/app/lib/features/geckoview/domain/providers.g.dart @@ -55,7 +55,7 @@ final class SelectionActionServiceProvider } String _$selectionActionServiceHash() => - r'4ba617f9edb4c2ba1cb5d9e4bbc4e5eec6feecba'; + r'5faf8c13c414406dfc2eb55fdd372677dbccdf8c'; @ProviderFor(eventService) final eventServiceProvider = EventServiceProvider._(); diff --git a/app/lib/features/geckoview/features/browser/domain/providers.dart b/app/lib/features/geckoview/features/browser/domain/providers.dart index 4f2894c9..e42ef3fb 100644 --- a/app/lib/features/geckoview/features/browser/domain/providers.dart +++ b/app/lib/features/geckoview/features/browser/domain/providers.dart @@ -216,9 +216,14 @@ EquatableValue> quickTabSwitcherTabStates( Ref ref, QuickTabSwitcherMode mode, ) { + final effectiveMode = ref.watch( + generalSettingsWithDefaultsProvider.select( + (settings) => settings.effectiveUiQuickTabSwitcherMode(), + ), + ); final selectedTabId = ref.watch(selectedTabProvider); - final tabStates = switch (mode) { + final tabStates = switch (effectiveMode) { QuickTabSwitcherMode.lastUsedTabs => ref.watch(fifoTabStatesProvider).value, QuickTabSwitcherMode.containerTabs => ref.watch(selectedContainerTabStatesWithContainerProvider).value, @@ -234,6 +239,11 @@ Future> quickTabSwitcherHistorySuggestions( Ref ref, QuickTabSwitcherMode mode, ) async { + final effectiveMode = ref.watch( + generalSettingsWithDefaultsProvider.select( + (settings) => settings.effectiveUiQuickTabSwitcherMode(), + ), + ); final showHistorySuggestions = ref.watch( generalSettingsWithDefaultsProvider.select( (settings) => settings.quickTabSwitcherShowHistorySuggestions, @@ -246,7 +256,7 @@ Future> quickTabSwitcherHistorySuggestions( final hasTabStates = ref.watch( quickTabSwitcherTabStatesProvider( - mode, + effectiveMode, ).select((value) => value.value.isNotEmpty), ); if (hasTabStates) { @@ -263,6 +273,11 @@ AsyncValue quickTabSwitcherHasResults( Ref ref, QuickTabSwitcherMode mode, ) { + final effectiveMode = ref.watch( + generalSettingsWithDefaultsProvider.select( + (settings) => settings.effectiveUiQuickTabSwitcherMode(), + ), + ); final showQuickTabSwitcherBar = ref.watch( generalSettingsWithDefaultsProvider.select( (settings) => settings.tabBarShowQuickTabSwitcherBar, @@ -274,7 +289,7 @@ AsyncValue quickTabSwitcherHasResults( final hasResults = ref.watch( quickTabSwitcherTabStatesProvider( - mode, + effectiveMode, ).select((value) => value.value.isNotEmpty), ); @@ -283,7 +298,7 @@ AsyncValue quickTabSwitcherHasResults( } return ref - .watch(quickTabSwitcherHistorySuggestionsProvider(mode)) + .watch(quickTabSwitcherHistorySuggestionsProvider(effectiveMode)) .whenData((visits) => visits.isNotEmpty); } diff --git a/app/lib/features/geckoview/features/browser/domain/providers.g.dart b/app/lib/features/geckoview/features/browser/domain/providers.g.dart index d39140ed..01af2c7d 100644 --- a/app/lib/features/geckoview/features/browser/domain/providers.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/providers.g.dart @@ -559,7 +559,7 @@ final class QuickTabSwitcherTabStatesProvider } String _$quickTabSwitcherTabStatesHash() => - r'db82bf9e338c94768b0531aeaffb152ff726f81f'; + r'df990fb96cca62a272572c925028725f81a0e96b'; final class QuickTabSwitcherTabStatesFamily extends $Family with @@ -642,7 +642,7 @@ final class QuickTabSwitcherHistorySuggestionsProvider } String _$quickTabSwitcherHistorySuggestionsHash() => - r'3446741dfedf6fda9ab61421c1b74370c7f43f5b'; + r'2e689ce72aac6b5b5b41e0390b168b3fdf5d4251'; final class QuickTabSwitcherHistorySuggestionsFamily extends $Family with @@ -730,7 +730,7 @@ final class QuickTabSwitcherHasResultsProvider } String _$quickTabSwitcherHasResultsHash() => - r'bcbddfefe7f5eb2c5d8c7c1925fe593a857b477b'; + r'5c488c9933372350c9e87ca1b83145176823dc88'; final class QuickTabSwitcherHasResultsFamily extends $Family with $FunctionalFamilyOverride, QuickTabSwitcherMode> { 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 bef8cd77..be831648 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart @@ -277,7 +277,7 @@ class BrowserScreen extends HookConsumerWidget { ); final quickTabSwitcherMode = ref.watch( generalSettingsWithDefaultsProvider.select( - (value) => value.quickTabSwitcherMode, + (value) => value.effectiveUiQuickTabSwitcherMode(), ), ); final quickTabSwitcherHasResults = ref.watch( diff --git a/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart b/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart index 847a44aa..8518da45 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart @@ -103,7 +103,7 @@ class TabViewScreen extends HookConsumerWidget { await SearchRoute( tabType: ref.read(selectedTabTypeProvider) ?? - settings.defaultCreateTabType, + settings.effectiveDefaultCreateTabType, ).push(context); if (context.mounted) { diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart index 005d4931..e40d2920 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart @@ -194,6 +194,7 @@ class BrowserTabBar extends HookConsumerWidget { final trippleDotMenuController = useMenuController(); final selectedTabId = ref.watch(selectedTabProvider); + final settings = ref.watch(generalSettingsWithDefaultsProvider); final containerColor = ref.watch( watchTabContainerDataProvider( @@ -201,23 +202,10 @@ class BrowserTabBar extends HookConsumerWidget { ).select((data) => data.value?.color), ); - final showExtensionShortcut = ref.watch( - generalSettingsWithDefaultsProvider.select( - (value) => value.showExtensionShortcut, - ), - ); + final showExtensionShortcut = settings.showExtensionShortcut; + final quickTabSwitcherMode = settings.effectiveUiQuickTabSwitcherMode(); - final quickTabSwitcherMode = ref.watch( - generalSettingsWithDefaultsProvider.select( - (value) => value.quickTabSwitcherMode, - ), - ); - - final tabBarPosition = ref.watch( - generalSettingsWithDefaultsProvider.select( - (value) => value.tabBarPosition, - ), - ); + final tabBarPosition = settings.tabBarPosition; final dragStartPosition = useRef(Offset.zero); @@ -300,7 +288,9 @@ class BrowserTabBar extends HookConsumerWidget { leadingWidth: 40.0, toolbarHeight: kToolbarHeight, backgroundColor: - (containerColor != null && displayedSheet is! ViewTabsSheet) + (settings.showContainerUi && + containerColor != null && + displayedSheet is! ViewTabsSheet) ? ContainerColors.forAppBar(containerColor) : null, leading: showMainToolbarNavigationButton @@ -485,6 +475,9 @@ class QuickTabSwitcher extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final appColors = AppColors.of(context); + final showIsolatedTabUi = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.showIsolatedTabUi), + ); final showTitles = ref.watch( generalSettingsWithDefaultsProvider.select( (s) => s.quickTabSwitcherShowTitles, @@ -556,7 +549,7 @@ class QuickTabSwitcher extends HookConsumerWidget { constraints: const BoxConstraints(maxWidth: 64), child: Text(item.title), ), - if (item.tabMode is IsolatedTabMode) + if (showIsolatedTabUi && item.tabMode is IsolatedTabMode) Padding( padding: const EdgeInsets.only(left: 8.0), child: Icon( @@ -710,7 +703,7 @@ class AddTabButton extends HookConsumerWidget { await SearchRoute( tabType: ref.read(selectedTabTypeProvider) ?? - settings.defaultCreateTabType, + settings.effectiveDefaultCreateTabType, ).push(context); if (context.mounted) { diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart index f4d8c812..566b9b04 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart @@ -240,7 +240,7 @@ class _BrowserViewState extends ConsumerState final route = SearchRoute( tabType: ref.read(selectedTabTypeProvider) ?? - settings.defaultCreateTabType, + settings.effectiveDefaultCreateTabType, searchText: sharedContent.text, launchedFromIntent: true, //launched from intent ); @@ -334,6 +334,13 @@ class _BrowserViewState extends ConsumerState await router.push(route.location); } else if (type == 'new_isolated_tab') { + final settings = ref.read( + generalSettingsWithDefaultsProvider, + ); + if (!settings.showIsolatedTabUi) { + return; + } + lastAction = DateTime.now(); final router = await ref.read(routerProvider.future); @@ -348,6 +355,9 @@ class _BrowserViewState extends ConsumerState } }); + final settings = ref.read( + generalSettingsWithDefaultsProvider, + ); await quickActions.setShortcutItems([ //TODO: add icons const ShortcutItem( @@ -358,10 +368,11 @@ class _BrowserViewState extends ConsumerState type: 'new_private_tab', localizedTitle: 'New Private Tab', ), - const ShortcutItem( - type: 'new_isolated_tab', - localizedTitle: 'New Isolated Tab', - ), + if (settings.showIsolatedTabUi) + const ShortcutItem( + type: 'new_isolated_tab', + localizedTitle: 'New Isolated Tab', + ), ]); initializationCompleter.complete(); @@ -592,7 +603,7 @@ class _BrowserViewState extends ConsumerState await SearchRoute( tabType: ref.read(selectedTabTypeProvider) ?? - settings.defaultCreateTabType, + settings.effectiveDefaultCreateTabType, searchText: searchText ?? SearchRoute.emptySearchText, ).push(context); }, diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/navigation_drawer.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/navigation_drawer.dart index d7731516..58ecf498 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/navigation_drawer.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/navigation_drawer.dart @@ -52,6 +52,9 @@ class BrowserNavigationDrawer extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final colorScheme = Theme.of(context).colorScheme; + final showContainerUi = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.showContainerUi), + ); return NavigationDrawer( backgroundColor: colorScheme.surface, @@ -137,14 +140,15 @@ class BrowserNavigationDrawer extends HookConsumerWidget { }, ), - ListTile( - leading: const Icon(MdiIcons.folder), - title: const Text('Containers'), - onTap: () async { - Navigator.of(context).pop(); - await const ContainerListRoute().push(context); - }, - ), + if (showContainerUi) + ListTile( + leading: const Icon(MdiIcons.folder), + title: const Text('Containers'), + onTap: () async { + Navigator.of(context).pop(); + await const ContainerListRoute().push(context); + }, + ), ListTile( leading: const Icon(Icons.rss_feed), @@ -322,7 +326,7 @@ class _ExtensionsSection extends HookConsumerWidget { final tabMode = TabMode.fromTabType( ref .read(generalSettingsWithDefaultsProvider) - .defaultCreateTabType, + .effectiveDefaultCreateTabType, ); await ref diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_creation_menu.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_creation_menu.dart index 4f94fc83..0c8d7d9a 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_creation_menu.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_creation_menu.dart @@ -42,6 +42,11 @@ class TabCreationMenu extends HookConsumerWidget { (value) => value.createChildTabsOption, ), ); + final showIsolatedTabUi = ref.watch( + generalSettingsWithDefaultsProvider.select( + (value) => value.showIsolatedTabUi, + ), + ); return MenuAnchor( controller: controller, @@ -63,16 +68,17 @@ class TabCreationMenu extends HookConsumerWidget { leadingIcon: const Icon(WebLibreIcons.privateTab), child: const Text('Add Private Tab'), ), - MenuItemButton( - onPressed: () async { - await const SearchRoute(tabType: TabType.isolated).push(context); - }, - leadingIcon: Icon( - MdiIcons.shieldLock, - color: AppColors.of(context).isolatedTabTeal, + if (showIsolatedTabUi) + MenuItemButton( + onPressed: () async { + await const SearchRoute(tabType: TabType.isolated).push(context); + }, + leadingIcon: Icon( + MdiIcons.shieldLock, + color: AppColors.of(context).isolatedTabTeal, + ), + child: const Text('Add Isolated Tab'), ), - child: const Text('Add Isolated Tab'), - ), if (createChildTabsOption) MenuItemButton( onPressed: () async { diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_menu.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_menu.dart index 135c618a..fa5e8cff 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_menu.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_menu.dart @@ -48,6 +48,7 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/entities/contai import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart'; +import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/presentation/hooks/menu_controller.dart'; import 'package:weblibre/presentation/icons/weblibre_icons.dart'; import 'package:weblibre/presentation/widgets/website_feed_menu_button.dart'; @@ -94,6 +95,7 @@ class TabMenu extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final showFeeds = useState(false); + final settings = ref.watch(generalSettingsWithDefaultsProvider); final controller = this.controller ?? useMenuController(); @@ -313,46 +315,47 @@ class TabMenu extends HookConsumerWidget { } }, ), - MenuItemButton( - leadingIcon: Icon( - MdiIcons.shieldLock, - color: AppColors.of(context).isolatedTabTeal, - ), - child: const Text('Isolated'), - onPressed: () async { - final tabState = ref.read(tabStateProvider(selectedTabId))!; - final containerData = await ref - .read(tabDataRepositoryProvider.notifier) - .getTabContainerData(selectedTabId); + if (settings.showIsolatedTabUi) + MenuItemButton( + leadingIcon: Icon( + MdiIcons.shieldLock, + color: AppColors.of(context).isolatedTabTeal, + ), + child: const Text('Isolated'), + onPressed: () async { + final tabState = ref.read(tabStateProvider(selectedTabId))!; + final containerData = await ref + .read(tabDataRepositoryProvider.notifier) + .getTabContainerData(selectedTabId); - final tabId = await ref - .read(tabRepositoryProvider.notifier) - .addTab( - url: tabState.url, - tabMode: TabMode.newIsolated(), - containerSelection: containerData == null - ? const TabContainerSelection.unassigned() - : TabContainerSelection.specific(containerData), - selectTab: false, + final tabId = await ref + .read(tabRepositoryProvider.notifier) + .addTab( + url: tabState.url, + tabMode: TabMode.newIsolated(), + containerSelection: containerData == null + ? const TabContainerSelection.unassigned() + : TabContainerSelection.specific(containerData), + selectTab: false, + ); + + if (context.mounted) { + final repo = ref.read(tabRepositoryProvider.notifier); + + ui_helper.showTabSwitchMessage( + context, + onSwitch: () async { + await repo.selectTab(tabId); + }, ); - - if (context.mounted) { - final repo = ref.read(tabRepositoryProvider.notifier); - - ui_helper.showTabSwitchMessage( - context, - onSwitch: () async { - await repo.selectTab(tabId); - }, - ); - } - }, - ), + } + }, + ), ], leadingIcon: const Icon(MdiIcons.tabPlus), child: const Text('Clone Tab'), ), - if (enableContainer) + if (enableContainer && settings.showContainerUi) SubmenuButton( menuChildren: [ MenuItemButton( diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart index 198badd9..185b76e9 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart @@ -577,7 +577,7 @@ class ViewTabGridWidget extends HookConsumerWidget { await SearchRoute( tabType: ref.read(selectedTabTypeProvider) ?? - settings.defaultCreateTabType, + settings.effectiveDefaultCreateTabType, ).push(context); onClose(); diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart index 2071316a..ff5226a3 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart @@ -606,7 +606,7 @@ class ViewTabListWidget extends HookConsumerWidget { await SearchRoute( tabType: ref.read(selectedTabTypeProvider) ?? - settings.defaultCreateTabType, + settings.effectiveDefaultCreateTabType, ).push(context); onClose(); diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart index ec47a943..a0557cac 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart @@ -345,7 +345,7 @@ class ViewTabTreesWidget extends HookConsumerWidget { await SearchRoute( tabType: ref.read(selectedTabTypeProvider) ?? - settings.defaultCreateTabType, + settings.effectiveDefaultCreateTabType, ).push(context); onClose(); diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart index 74300921..35e9710f 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_view_header.dart @@ -230,6 +230,16 @@ class TabViewHeader extends HookConsumerWidget { (settings) => settings.enableLocalAiFeatures, ), ); + final showContainerUi = ref.watch( + generalSettingsWithDefaultsProvider.select( + (settings) => settings.showContainerUi, + ), + ); + final showIsolatedTabUi = ref.watch( + generalSettingsWithDefaultsProvider.select( + (settings) => settings.showIsolatedTabUi, + ), + ); final selectedContainerId = ref.watch(selectedContainerProvider); final isSyncedScope = ref.watch( @@ -503,55 +513,63 @@ class TabViewHeader extends HookConsumerWidget { }, child: const Text('Close Private Tabs'), ), - MenuItemButton( - leadingIcon: Icon( - MdiIcons.shieldLock, - color: AppColors.of(context).isolatedTabTeal, - ), - onPressed: isSyncedScope - ? null - : () async { - // Count distinct isolation groups that will be destroyed - final allStates = ref.read(tabStatesProvider); - final isolatedContextIds = allStates.values - .where( - (s) => - s.tabMode is IsolatedTabMode && - s.isolationContextId != null, - ) - .map((s) => s.isolationContextId!) - .toSet(); - - if (isolatedContextIds.isNotEmpty && - context.mounted) { - final confirmed = await ui_helper - .confirmIsolatedTabClose( - context, - groupCount: isolatedContextIds.length, - ); - if (!confirmed) return; - } - - final count = await ref - .read(tabDataRepositoryProvider.notifier) - .closeContainerTabs( - selectedContainerId, - includeRegular: false, - includePrivate: false, - ); - - if (context.mounted) { - ui_helper.showTabUndoClose( - context, - ref - .read(tabRepositoryProvider.notifier) - .undoClose, - count: count.length, + if (showIsolatedTabUi) + MenuItemButton( + leadingIcon: Icon( + MdiIcons.shieldLock, + color: AppColors.of(context).isolatedTabTeal, + ), + onPressed: isSyncedScope + ? null + : () async { + // Count distinct isolation groups that will be destroyed + final allStates = ref.read( + tabStatesProvider, ); - } - }, - child: const Text('Close Isolated Tabs'), - ), + final isolatedContextIds = allStates.values + .where( + (s) => + s.tabMode is IsolatedTabMode && + s.isolationContextId != null, + ) + .map((s) => s.isolationContextId!) + .toSet(); + + if (isolatedContextIds.isNotEmpty && + context.mounted) { + final confirmed = await ui_helper + .confirmIsolatedTabClose( + context, + groupCount: + isolatedContextIds.length, + ); + if (!confirmed) return; + } + + final count = await ref + .read( + tabDataRepositoryProvider.notifier, + ) + .closeContainerTabs( + selectedContainerId, + includeRegular: false, + includePrivate: false, + ); + + if (context.mounted) { + ui_helper.showTabUndoClose( + context, + ref + .read( + tabRepositoryProvider.notifier, + ) + .undoClose, + count: count.length, + ); + } + }, + child: const Text('Close Isolated Tabs'), + ), const Divider(), MenuItemButton( leadingIcon: const Icon(MdiIcons.bookmarkPlusOutline), @@ -809,11 +827,12 @@ class TabViewHeader extends HookConsumerWidget { ), ), ), - Consumer( - builder: (context, ref, child) { - return _TabFilters(tabsViewMode: tabsViewMode); - }, - ), + if (showContainerUi) + Consumer( + builder: (context, ref, child) { + return _TabFilters(tabsViewMode: tabsViewMode); + }, + ), const SizedBox(height: 8), ], ), diff --git a/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_image_new_tab.dart b/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_image_new_tab.dart index 11785863..096bfad5 100644 --- a/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_image_new_tab.dart +++ b/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_image_new_tab.dart @@ -50,7 +50,7 @@ class OpenImageInNewTab extends HookConsumerWidget { TabMode.fromTabType( ref .read(generalSettingsWithDefaultsProvider) - .defaultCreateTabType, + .effectiveDefaultCreateTabType, ); final tabId = await ref diff --git a/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_in_container.dart b/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_in_container.dart index a8567191..e4819d6e 100644 --- a/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_in_container.dart +++ b/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_in_container.dart @@ -61,6 +61,11 @@ class OpenInContainer extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final settings = ref.watch(generalSettingsWithDefaultsProvider); + if (!settings.showContainerUi) { + return const SizedBox.shrink(); + } + return ListTile( leading: const Icon(MdiIcons.selectGroup), title: const Text('Open in container'), @@ -92,11 +97,7 @@ class OpenInContainer extends HookConsumerWidget { final currentTab = ref.read(selectedTabStateProvider); final tabMode = currentTab?.tabMode ?? - TabMode.fromTabType( - ref - .read(generalSettingsWithDefaultsProvider) - .defaultCreateTabType, - ); + TabMode.fromTabType(settings.effectiveDefaultCreateTabType); final tabId = await ref .read(tabRepositoryProvider.notifier) diff --git a/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_new_tab.dart b/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_new_tab.dart index f1655dfa..84c4f126 100644 --- a/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_new_tab.dart +++ b/app/lib/features/geckoview/features/contextmenu/presentation/candidates/open_new_tab.dart @@ -50,7 +50,7 @@ class OpenInNewTab extends HookConsumerWidget { TabMode.fromTabType( ref .read(generalSettingsWithDefaultsProvider) - .defaultCreateTabType, + .effectiveDefaultCreateTabType, ); final tabId = await ref diff --git a/app/lib/features/geckoview/features/contextmenu/presentation/context_menu_dialog.dart b/app/lib/features/geckoview/features/contextmenu/presentation/context_menu_dialog.dart index 8feb3c98..3dac626a 100644 --- a/app/lib/features/geckoview/features/contextmenu/presentation/context_menu_dialog.dart +++ b/app/lib/features/geckoview/features/contextmenu/presentation/context_menu_dialog.dart @@ -37,6 +37,7 @@ import 'package:weblibre/features/geckoview/features/contextmenu/presentation/ca import 'package:weblibre/features/geckoview/features/contextmenu/presentation/candidates/share_email.dart'; import 'package:weblibre/features/geckoview/features/contextmenu/presentation/candidates/share_image.dart'; import 'package:weblibre/features/geckoview/features/contextmenu/presentation/candidates/share_link.dart'; +import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/presentation/hooks/cached_future.dart'; class ContextMenuDialog extends HookConsumerWidget { @@ -46,6 +47,10 @@ class ContextMenuDialog extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final showContainerUi = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.showContainerUi), + ); + return SimpleDialog( title: AutoSizeText( hitResult.getTitle(), @@ -58,7 +63,7 @@ class ContextMenuDialog extends HookConsumerWidget { children: [ if (OpenInNewTab.isSupported(hitResult)) OpenInNewTab(hitResult: hitResult), - if (OpenInContainer.isSupported(hitResult)) + if (showContainerUi && OpenInContainer.isSupported(hitResult)) OpenInContainer(hitResult: hitResult), if (CopyLink.isSupported(hitResult)) CopyLink(hitResult: hitResult), if (SaveFile.isSupported(hitResult)) SaveFile(hitResult: hitResult), diff --git a/app/lib/features/geckoview/features/open_link_tools/presentation/dialogs/open_shared_content.dart b/app/lib/features/geckoview/features/open_link_tools/presentation/dialogs/open_shared_content.dart index 29fc76a2..bcc872e7 100644 --- a/app/lib/features/geckoview/features/open_link_tools/presentation/dialogs/open_shared_content.dart +++ b/app/lib/features/geckoview/features/open_link_tools/presentation/dialogs/open_shared_content.dart @@ -240,16 +240,17 @@ class OpenSharedContent extends HookConsumerWidget { children: [ Text('Open link', style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 16), - ContainerChips( - displayMenu: false, - selectedContainer: selectedContainer.value, - onSelected: (container) { - selectedContainer.value = container; - }, - onDeleted: (container) { - selectedContainer.value = null; - }, - ), + if (settings.showContainerUi) + ContainerChips( + displayMenu: false, + selectedContainer: selectedContainer.value, + onSelected: (container) { + selectedContainer.value = container; + }, + onDeleted: (container) { + selectedContainer.value = null; + }, + ), TextFormField( controller: textController, keyboardType: TextInputType.url, @@ -380,8 +381,10 @@ class OpenSharedContent extends HookConsumerWidget { subtitle: 'Add to your browser tabs', icon: MdiIcons.tab, trailing: PopupMenuButton( - icon: Icon(MdiIcons.tabUnselected, size: 24), - tooltip: 'Private / Isolated', + icon: const Icon(MdiIcons.tabUnselected, size: 24), + tooltip: settings.showIsolatedTabUi + ? 'Private / Isolated' + : 'Private', onSelected: openTab, itemBuilder: (context) => [ PopupMenuItem( @@ -398,20 +401,21 @@ class OpenSharedContent extends HookConsumerWidget { ], ), ), - PopupMenuItem( - value: TabMode.newIsolated(), - child: Row( - children: [ - Icon( - MdiIcons.shieldLock, - color: appColors.isolatedTabTeal, - size: 20, - ), - const SizedBox(width: 12), - const Text('Isolated'), - ], + if (settings.showIsolatedTabUi) + PopupMenuItem( + value: TabMode.newIsolated(), + child: Row( + children: [ + Icon( + MdiIcons.shieldLock, + color: appColors.isolatedTabTeal, + size: 20, + ), + const SizedBox(width: 12), + const Text('Isolated'), + ], + ), ), - ), ], ), onTap: () => openTab(TabMode.regular), diff --git a/app/lib/features/geckoview/features/search/presentation/screens/search.dart b/app/lib/features/geckoview/features/search/presentation/screens/search.dart index 5ded2738..31b6ec4c 100644 --- a/app/lib/features/geckoview/features/search/presentation/screens/search.dart +++ b/app/lib/features/geckoview/features/search/presentation/screens/search.dart @@ -78,10 +78,24 @@ class SearchScreen extends HookConsumerWidget { (value) => value.createChildTabsOption, ), ); + final settings = ref.watch(generalSettingsWithDefaultsProvider); - final selectedTabType = useState(tabType); + final initialTabType = + !settings.showIsolatedTabUi && tabType == TabType.isolated + ? TabType.regular + : tabType; + + final selectedTabType = useState(initialTabType); final currentTabTabType = ref.watch(selectedTabTypeProvider); + useEffect(() { + if (!settings.showIsolatedTabUi && + selectedTabType.value == TabType.isolated) { + selectedTabType.value = TabType.regular; + } + return null; + }, [settings.showIsolatedTabUi]); + final selectedContainer = ref.watch( selectedContainerDataProvider.select((value) => value.value), ); @@ -288,58 +302,72 @@ class SearchScreen extends HookConsumerWidget { ? null : Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( - children: [ - Expanded( - flex: 4, - child: Align( - alignment: Alignment.centerLeft, - child: Focus( - canRequestFocus: false, - child: AnimatedTabTypeSwitcher( - selected: selectedTabType.value, - onChanged: (value) { - selectedTabType.value = value; - // Restore focus to search field after segment change - WidgetsBinding.instance - .addPostFrameCallback((_) { - searchFocusNode.requestFocus(); - }); - }, - showChildOption: createChildTabsOption, - selectedBackgroundColor: - switch (selectedTabType.value) { - TabType.regular => null, - TabType.private => - appColors.privateSelectionOverlay, - TabType.isolated => - appColors.isolatedSelectionOverlay, - TabType.child => - switch (currentTabTabType) { - TabType.private => - appColors - .privateSelectionOverlay, - TabType.isolated => - appColors - .isolatedSelectionOverlay, - _ => null, - }, - }, + child: Builder( + builder: (context) { + final tabTypeSwitcher = Focus( + canRequestFocus: false, + child: AnimatedTabTypeSwitcher( + selected: selectedTabType.value, + onChanged: (value) { + selectedTabType.value = value; + // Restore focus to search field after segment change + WidgetsBinding.instance.addPostFrameCallback(( + _, + ) { + searchFocusNode.requestFocus(); + }); + }, + showChildOption: createChildTabsOption, + showIsolatedOption: settings.showIsolatedTabUi, + selectedBackgroundColor: switch (selectedTabType + .value) { + TabType.regular => null, + TabType.private => + appColors.privateSelectionOverlay, + TabType.isolated => + appColors.isolatedSelectionOverlay, + TabType.child => switch (currentTabTabType) { + TabType.private => + appColors.privateSelectionOverlay, + TabType.isolated => + appColors.isolatedSelectionOverlay, + _ => null, + }, + }, + ), + ); + + if (!settings.showContainerUi) { + return Center( + child: Transform.scale( + scale: 1.08, + child: tabTypeSwitcher, + ), + ); + } + + return Row( + children: [ + Expanded( + flex: 4, + child: Align( + alignment: Alignment.centerLeft, + child: tabTypeSwitcher, ), ), - ), - ), - const SizedBox(width: 8), - Flexible( - flex: 2, - child: Align( - alignment: Alignment.centerRight, - child: CompactContainerSelector( - selectedContainer: selectedContainer, + const SizedBox(width: 8), + Flexible( + flex: 2, + child: Align( + alignment: Alignment.centerRight, + child: CompactContainerSelector( + selectedContainer: selectedContainer, + ), + ), ), - ), - ), - ], + ], + ); + }, ), ), bottom: PreferredSize( diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart b/app/lib/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart index 05f47e2e..655c7f5e 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart @@ -28,6 +28,7 @@ class AnimatedTabTypeSwitcher extends StatelessWidget { final TabType selected; final ValueChanged onChanged; final bool showChildOption; + final bool showIsolatedOption; final Color? selectedBackgroundColor; const AnimatedTabTypeSwitcher({ @@ -35,6 +36,7 @@ class AnimatedTabTypeSwitcher extends StatelessWidget { required this.selected, required this.onChanged, this.showChildOption = false, + this.showIsolatedOption = true, this.selectedBackgroundColor, }); @@ -72,15 +74,17 @@ class AnimatedTabTypeSwitcher extends StatelessWidget { selectedBackgroundColor: selectedBackgroundColor, onTap: () => onChanged(TabType.private), ), - _divider(borderColor), - _Segment( - tabType: TabType.isolated, - icon: MdiIcons.shieldLock, - label: 'Isolated', - isSelected: selected == TabType.isolated, - selectedBackgroundColor: selectedBackgroundColor, - onTap: () => onChanged(TabType.isolated), - ), + if (showIsolatedOption) ...[ + _divider(borderColor), + _Segment( + tabType: TabType.isolated, + icon: MdiIcons.shieldLock, + label: 'Isolated', + isSelected: selected == TabType.isolated, + selectedBackgroundColor: selectedBackgroundColor, + onTap: () => onChanged(TabType.isolated), + ), + ], if (showChildOption) ...[ _divider(borderColor), _Segment( diff --git a/app/lib/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart b/app/lib/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart index d7046cea..bef739cc 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/widgets/compact_container_selector.dart @@ -28,6 +28,7 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/entities/contai import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_title.dart'; import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart'; +import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; /// A compact container selector that displays only the currently selected /// container (or "unassigned" if none selected) without counts. @@ -40,6 +41,15 @@ class CompactContainerSelector extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final containerUiEnabled = ref.watch( + generalSettingsWithDefaultsProvider.select( + (settings) => settings.showContainerUi, + ), + ); + if (!containerUiEnabled) { + return const SizedBox.shrink(); + } + final theme = Theme.of(context); final isSelected = selectedContainer != null; diff --git a/app/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart b/app/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart index 19d23427..4cb34fa6 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/widgets/container_chips.dart @@ -200,6 +200,15 @@ class ContainerChips extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final containerUiEnabled = ref.watch( + generalSettingsWithDefaultsProvider.select( + (settings) => settings.showContainerUi, + ), + ); + if (!containerUiEnabled) { + return const SizedBox.shrink(); + } + final searchText = useListenableSelector( searchTextListenable, () => searchTextListenable?.value.text, diff --git a/app/lib/features/settings/presentation/screens/appearance_display_settings.dart b/app/lib/features/settings/presentation/screens/appearance_display_settings.dart index 89c1e9b2..437d50e9 100644 --- a/app/lib/features/settings/presentation/screens/appearance_display_settings.dart +++ b/app/lib/features/settings/presentation/screens/appearance_display_settings.dart @@ -353,9 +353,8 @@ class _QuickTabSwitcherModeSection extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final quickTabSwitcherMode = ref.watch( - generalSettingsWithDefaultsProvider.select((s) => s.quickTabSwitcherMode), - ); + final settings = ref.watch(generalSettingsWithDefaultsProvider); + final quickTabSwitcherMode = settings.effectiveUiQuickTabSwitcherMode(); return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), @@ -380,18 +379,19 @@ class _QuickTabSwitcherModeSection extends HookConsumerWidget { ); } }, - child: const Column( + child: Column( children: [ - RadioListTile.adaptive( + const RadioListTile.adaptive( value: QuickTabSwitcherMode.lastUsedTabs, title: Text('Recently Used Tabs'), subtitle: Text('Recently used tabs across all containers'), ), - RadioListTile.adaptive( - value: QuickTabSwitcherMode.containerTabs, - title: Text('Container Tabs'), - subtitle: Text('Ordered tabs of the selected container'), - ), + if (settings.showContainerUi) + const RadioListTile.adaptive( + value: QuickTabSwitcherMode.containerTabs, + title: Text('Container Tabs'), + subtitle: Text('Ordered tabs of the selected container'), + ), ], ), ), diff --git a/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart b/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart index efcb4509..115a6360 100644 --- a/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart +++ b/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart @@ -25,6 +25,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:weblibre/core/design/app_colors.dart'; import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart'; +import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart'; import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart'; import 'package:weblibre/features/settings/presentation/widgets/sections.dart'; import 'package:weblibre/features/user/data/models/general_settings.dart'; @@ -83,6 +84,8 @@ class _TabOrganizationSection extends StatelessWidget { return const Column( children: [ SettingSection(name: 'Tab Organization'), + _ShowContainerUiTile(), + _ShowIsolatedTabUiTile(), _CreateChildTabsTile(), ], ); @@ -110,9 +113,8 @@ class _NewTabDefaultSection extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final appColors = AppColors.of(context); - final defaultCreateTabType = ref.watch( - generalSettingsWithDefaultsProvider.select((s) => s.defaultCreateTabType), - ); + final settings = ref.watch(generalSettingsWithDefaultsProvider); + final defaultCreateTabType = settings.effectiveDefaultCreateTabType; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), @@ -140,16 +142,17 @@ class _NewTabDefaultSection extends HookConsumerWidget { label: Text('Private'), icon: Icon(WebLibreIcons.privateTab), ), - ButtonSegment( - value: TabType.isolated, - label: const Text('Isolated'), - icon: Icon( - MdiIcons.shieldLock, - color: defaultCreateTabType == TabType.isolated - ? null - : appColors.isolatedTabTeal, + if (settings.showIsolatedTabUi) + ButtonSegment( + value: TabType.isolated, + label: const Text('Isolated'), + icon: Icon( + MdiIcons.shieldLock, + color: defaultCreateTabType == TabType.isolated + ? null + : appColors.isolatedTabTeal, + ), ), - ), ], selected: {defaultCreateTabType}, onSelectionChanged: (value) async { @@ -157,7 +160,7 @@ class _NewTabDefaultSection extends HookConsumerWidget { .read(saveGeneralSettingsControllerProvider.notifier) .save( (currentSettings) => currentSettings.copyWith - .defaultCreateTabType(value.first), + .storedDefaultCreateTabType(value.first), ); }, style: switch (defaultCreateTabType) { @@ -274,6 +277,75 @@ class _CreateChildTabsTile extends HookConsumerWidget { } } +class _ShowContainerUiTile extends HookConsumerWidget { + const _ShowContainerUiTile(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final showContainerUi = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.showContainerUi), + ); + + return SwitchListTile.adaptive( + title: const Text('Show Container UI'), + subtitle: const Text('Show container selectors, menus, and management'), + secondary: const Icon(MdiIcons.folder), + value: showContainerUi, + onChanged: (value) async { + await ref.read(saveGeneralSettingsControllerProvider.notifier).save(( + currentSettings, + ) { + var updated = currentSettings.copyWith.showContainerUi(value); + if (!value && + updated.quickTabSwitcherMode == + QuickTabSwitcherMode.containerTabs) { + updated = updated.copyWith.quickTabSwitcherMode( + QuickTabSwitcherMode.lastUsedTabs, + ); + } + return updated; + }); + + if (!value) { + ref.read(selectedContainerProvider.notifier).clearContainer(); + } + }, + ); + } +} + +class _ShowIsolatedTabUiTile extends HookConsumerWidget { + const _ShowIsolatedTabUiTile(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final showIsolatedTabUi = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.showIsolatedTabUi), + ); + + return SwitchListTile.adaptive( + title: const Text('Show Isolated Tab UI'), + subtitle: const Text('Show isolated-tab creation options in the UI'), + secondary: const Icon(MdiIcons.shieldLock), + value: showIsolatedTabUi, + onChanged: (value) async { + await ref.read(saveGeneralSettingsControllerProvider.notifier).save(( + currentSettings, + ) { + var updated = currentSettings.copyWith.showIsolatedTabUi(value); + if (!value && + updated.storedDefaultCreateTabType == TabType.isolated) { + updated = updated.copyWith.storedDefaultCreateTabType( + TabType.regular, + ); + } + return updated; + }); + }, + ); + } +} + class _ShowExtensionShortcutTile extends HookConsumerWidget { const _ShowExtensionShortcutTile(); diff --git a/app/lib/features/tor/presentation/screens/tor_proxy.dart b/app/lib/features/tor/presentation/screens/tor_proxy.dart index f2ceac28..3d5df6ba 100644 --- a/app/lib/features/tor/presentation/screens/tor_proxy.dart +++ b/app/lib/features/tor/presentation/screens/tor_proxy.dart @@ -29,6 +29,7 @@ import 'package:weblibre/features/settings/presentation/controllers/save_setting import 'package:weblibre/features/tor/domain/services/tor_proxy.dart'; import 'package:weblibre/features/tor/presentation/screens/country_picker.dart'; import 'package:weblibre/features/user/data/models/tor_settings.dart'; +import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/features/user/domain/repositories/tor_settings.dart'; import 'package:weblibre/presentation/hooks/on_initialization.dart'; import 'package:weblibre/utils/ui_helper.dart'; @@ -79,6 +80,9 @@ class TorProxyScreen extends HookConsumerWidget { bootstrapProgress > 0 && bootstrapProgress < 100; final torSettings = ref.watch(torSettingsWithDefaultsProvider); + final showContainerUi = ref.watch( + generalSettingsWithDefaultsProvider.select((s) => s.showContainerUi), + ); useOnInitialization(() async { await ref.read(torProxyServiceProvider.notifier).requestSync(); @@ -246,22 +250,36 @@ class TorProxyScreen extends HookConsumerWidget { ); } }, - child: const Column( + child: Column( children: [ - RadioListTile.adaptive( - value: TorRegularTabProxyMode.container, - title: Text('Container-Based Routing'), - subtitle: Text( - 'Route only tabs in Tor containers through the Tor network. Private tabs remain unaffected.', + if (showContainerUi) + const RadioListTile.adaptive( + value: TorRegularTabProxyMode.container, + title: Text('Container-Based Routing'), + subtitle: Text( + 'Route only tabs in Tor containers through the Tor network. Private tabs remain unaffected.', + ), ), - ), - RadioListTile.adaptive( + const RadioListTile.adaptive( value: TorRegularTabProxyMode.all, title: Text('Global Routing'), subtitle: Text( 'Route all regular tabs through the Tor network. Private tabs remain unaffected.', ), ), + if (!showContainerUi && + torSettings.proxyRegularTabsMode == + TorRegularTabProxyMode.container) + const Padding( + padding: EdgeInsets.only( + left: 56, + right: 24, + top: 4, + ), + child: Text( + 'Container-based routing is currently active but hidden because Container UI is disabled.', + ), + ), ], ), ), diff --git a/app/lib/features/user/data/models/general_settings.dart b/app/lib/features/user/data/models/general_settings.dart index 165b122c..4a8cdee7 100644 --- a/app/lib/features/user/data/models/general_settings.dart +++ b/app/lib/features/user/data/models/general_settings.dart @@ -69,7 +69,9 @@ class GeneralSettings with FastEquatable { final bool createChildTabsOption; final bool showExtensionShortcut; final bool enableLocalAiFeatures; - final TabType defaultCreateTabType; + final bool showContainerUi; + final bool showIsolatedTabUi; + final TabType storedDefaultCreateTabType; final TabIntentOpenSetting tabIntentOpenSetting; final bool autoHideTabBar; final TabBarSwipeAction tabBarSwipeAction; @@ -113,7 +115,9 @@ class GeneralSettings with FastEquatable { required this.createChildTabsOption, required this.showExtensionShortcut, required this.enableLocalAiFeatures, - required this.defaultCreateTabType, + required this.showContainerUi, + required this.showIsolatedTabUi, + required this.storedDefaultCreateTabType, required this.tabIntentOpenSetting, required this.autoHideTabBar, required this.tabBarSwipeAction, @@ -158,7 +162,9 @@ class GeneralSettings with FastEquatable { bool? createChildTabsOption, bool? showExtensionShortcut, bool? enableLocalAiFeatures, - TabType? defaultCreateTabType, + bool? showContainerUi, + bool? showIsolatedTabUi, + TabType? storedDefaultCreateTabType, TabIntentOpenSetting? tabIntentOpenSetting, bool? autoHideTabBar, TabBarSwipeAction? tabBarSwipeAction, @@ -200,7 +206,10 @@ class GeneralSettings with FastEquatable { createChildTabsOption = createChildTabsOption ?? false, showExtensionShortcut = showExtensionShortcut ?? false, enableLocalAiFeatures = enableLocalAiFeatures ?? true, - defaultCreateTabType = defaultCreateTabType ?? TabType.regular, + showContainerUi = showContainerUi ?? true, + showIsolatedTabUi = showIsolatedTabUi ?? true, + storedDefaultCreateTabType = + storedDefaultCreateTabType ?? TabType.regular, tabIntentOpenSetting = tabIntentOpenSetting ?? TabIntentOpenSetting.ask, autoHideTabBar = autoHideTabBar ?? true, tabBarSwipeAction = @@ -248,6 +257,21 @@ class GeneralSettings with FastEquatable { Map toJson() => _$GeneralSettingsToJson(this); + TabType get effectiveDefaultCreateTabType { + if (!showIsolatedTabUi && storedDefaultCreateTabType == TabType.isolated) { + return TabType.regular; + } + return storedDefaultCreateTabType; + } + + QuickTabSwitcherMode effectiveUiQuickTabSwitcherMode() { + if (!showContainerUi && + quickTabSwitcherMode == QuickTabSwitcherMode.containerTabs) { + return QuickTabSwitcherMode.lastUsedTabs; + } + return quickTabSwitcherMode; + } + @override List get hashParameters => [ themeMode, @@ -259,7 +283,9 @@ class GeneralSettings with FastEquatable { createChildTabsOption, showExtensionShortcut, enableLocalAiFeatures, - defaultCreateTabType, + showContainerUi, + showIsolatedTabUi, + storedDefaultCreateTabType, tabIntentOpenSetting, autoHideTabBar, tabBarSwipeAction, 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 9aa712b7..73b52137 100644 --- a/app/lib/features/user/data/models/general_settings.g.dart +++ b/app/lib/features/user/data/models/general_settings.g.dart @@ -29,7 +29,13 @@ abstract class _$GeneralSettingsCWProxy { GeneralSettings enableLocalAiFeatures(bool enableLocalAiFeatures); - GeneralSettings defaultCreateTabType(TabType defaultCreateTabType); + GeneralSettings showContainerUi(bool showContainerUi); + + GeneralSettings showIsolatedTabUi(bool showIsolatedTabUi); + + GeneralSettings storedDefaultCreateTabType( + TabType storedDefaultCreateTabType, + ); GeneralSettings tabIntentOpenSetting( TabIntentOpenSetting tabIntentOpenSetting, @@ -124,7 +130,9 @@ abstract class _$GeneralSettingsCWProxy { bool createChildTabsOption, bool showExtensionShortcut, bool enableLocalAiFeatures, - TabType defaultCreateTabType, + bool showContainerUi, + bool showIsolatedTabUi, + TabType storedDefaultCreateTabType, TabIntentOpenSetting tabIntentOpenSetting, bool autoHideTabBar, TabBarSwipeAction tabBarSwipeAction, @@ -205,8 +213,17 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { call(enableLocalAiFeatures: enableLocalAiFeatures); @override - GeneralSettings defaultCreateTabType(TabType defaultCreateTabType) => - call(defaultCreateTabType: defaultCreateTabType); + GeneralSettings showContainerUi(bool showContainerUi) => + call(showContainerUi: showContainerUi); + + @override + GeneralSettings showIsolatedTabUi(bool showIsolatedTabUi) => + call(showIsolatedTabUi: showIsolatedTabUi); + + @override + GeneralSettings storedDefaultCreateTabType( + TabType storedDefaultCreateTabType, + ) => call(storedDefaultCreateTabType: storedDefaultCreateTabType); @override GeneralSettings tabIntentOpenSetting( @@ -364,7 +381,9 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { Object? createChildTabsOption = const $CopyWithPlaceholder(), Object? showExtensionShortcut = const $CopyWithPlaceholder(), Object? enableLocalAiFeatures = const $CopyWithPlaceholder(), - Object? defaultCreateTabType = const $CopyWithPlaceholder(), + Object? showContainerUi = const $CopyWithPlaceholder(), + Object? showIsolatedTabUi = const $CopyWithPlaceholder(), + Object? storedDefaultCreateTabType = const $CopyWithPlaceholder(), Object? tabIntentOpenSetting = const $CopyWithPlaceholder(), Object? autoHideTabBar = const $CopyWithPlaceholder(), Object? tabBarSwipeAction = const $CopyWithPlaceholder(), @@ -450,12 +469,24 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy { ? _value.enableLocalAiFeatures // ignore: cast_nullable_to_non_nullable : enableLocalAiFeatures as bool, - defaultCreateTabType: - defaultCreateTabType == const $CopyWithPlaceholder() || - defaultCreateTabType == null - ? _value.defaultCreateTabType + showContainerUi: + showContainerUi == const $CopyWithPlaceholder() || + showContainerUi == null + ? _value.showContainerUi // ignore: cast_nullable_to_non_nullable - : defaultCreateTabType as TabType, + : showContainerUi as bool, + showIsolatedTabUi: + showIsolatedTabUi == const $CopyWithPlaceholder() || + showIsolatedTabUi == null + ? _value.showIsolatedTabUi + // ignore: cast_nullable_to_non_nullable + : showIsolatedTabUi as bool, + storedDefaultCreateTabType: + storedDefaultCreateTabType == const $CopyWithPlaceholder() || + storedDefaultCreateTabType == null + ? _value.storedDefaultCreateTabType + // ignore: cast_nullable_to_non_nullable + : storedDefaultCreateTabType as TabType, tabIntentOpenSetting: tabIntentOpenSetting == const $CopyWithPlaceholder() || tabIntentOpenSetting == null @@ -682,9 +713,11 @@ GeneralSettings _$GeneralSettingsFromJson( createChildTabsOption: json['createChildTabsOption'] as bool?, showExtensionShortcut: json['showExtensionShortcut'] as bool?, enableLocalAiFeatures: json['enableLocalAiFeatures'] as bool?, - defaultCreateTabType: $enumDecodeNullable( + showContainerUi: json['showContainerUi'] as bool?, + showIsolatedTabUi: json['showIsolatedTabUi'] as bool?, + storedDefaultCreateTabType: $enumDecodeNullable( _$TabTypeEnumMap, - json['defaultCreateTabType'], + json['storedDefaultCreateTabType'], ), tabIntentOpenSetting: $enumDecodeNullable( _$TabIntentOpenSettingEnumMap, @@ -763,7 +796,10 @@ Map _$GeneralSettingsToJson( 'createChildTabsOption': instance.createChildTabsOption, 'showExtensionShortcut': instance.showExtensionShortcut, 'enableLocalAiFeatures': instance.enableLocalAiFeatures, - 'defaultCreateTabType': _$TabTypeEnumMap[instance.defaultCreateTabType]!, + 'showContainerUi': instance.showContainerUi, + 'showIsolatedTabUi': instance.showIsolatedTabUi, + 'storedDefaultCreateTabType': + _$TabTypeEnumMap[instance.storedDefaultCreateTabType]!, 'tabIntentOpenSetting': _$TabIntentOpenSettingEnumMap[instance.tabIntentOpenSetting]!, 'autoHideTabBar': instance.autoHideTabBar, diff --git a/app/lib/features/user/domain/repositories/general_settings.dart b/app/lib/features/user/domain/repositories/general_settings.dart index 99bbd7bf..e26540b1 100644 --- a/app/lib/features/user/domain/repositories/general_settings.dart +++ b/app/lib/features/user/domain/repositories/general_settings.dart @@ -80,7 +80,15 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository { DriftSqlType.bool, db.typeMapping, ), - 'defaultCreateTabType': settings['defaultCreateTabType']?.readAs( + 'showContainerUi': settings['showContainerUi']?.readAs( + DriftSqlType.bool, + db.typeMapping, + ), + 'showIsolatedTabUi': settings['showIsolatedTabUi']?.readAs( + DriftSqlType.bool, + db.typeMapping, + ), + 'storedDefaultCreateTabType': settings['defaultCreateTabType']?.readAs( DriftSqlType.string, 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 87525066..217543b2 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'5fcb1b28918a6c7bb23f72681fdec8bee77e49a5'; + r'51cfcf20b24705f5c6f51d4e204fff5a8e4eee89'; abstract class _$GeneralSettingsRepository extends $StreamNotifier { diff --git a/app/lib/features/web_feed/presentation/screens/feed_article.dart b/app/lib/features/web_feed/presentation/screens/feed_article.dart index 37e89a85..f08a0ca2 100644 --- a/app/lib/features/web_feed/presentation/screens/feed_article.dart +++ b/app/lib/features/web_feed/presentation/screens/feed_article.dart @@ -199,7 +199,7 @@ class FeedArticleScreen extends HookConsumerWidget { final tabMode = TabMode.fromTabType( ref .read(generalSettingsWithDefaultsProvider) - .defaultCreateTabType, + .effectiveDefaultCreateTabType, ); await ref @@ -249,7 +249,7 @@ class FeedArticleScreen extends HookConsumerWidget { final tabMode = TabMode.fromTabType( ref .read(generalSettingsWithDefaultsProvider) - .defaultCreateTabType, + .effectiveDefaultCreateTabType, ); await ref