setting to hide container and isolated tab ui
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -69,7 +69,7 @@ class BangCategoryScreen extends HookConsumerWidget {
|
||||
SearchRoute(
|
||||
tabType:
|
||||
ref.read(selectedTabTypeProvider) ??
|
||||
settings.defaultCreateTabType,
|
||||
settings.effectiveDefaultCreateTabType,
|
||||
).go(context);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -92,7 +92,7 @@ class BangDetails extends HookConsumerWidget {
|
||||
final tabMode = TabMode.fromTabType(
|
||||
ref
|
||||
.read(generalSettingsWithDefaultsProvider)
|
||||
.defaultCreateTabType,
|
||||
.effectiveDefaultCreateTabType,
|
||||
);
|
||||
|
||||
await ref
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -55,7 +55,7 @@ final class SelectionActionServiceProvider
|
||||
}
|
||||
|
||||
String _$selectionActionServiceHash() =>
|
||||
r'4ba617f9edb4c2ba1cb5d9e4bbc4e5eec6feecba';
|
||||
r'5faf8c13c414406dfc2eb55fdd372677dbccdf8c';
|
||||
|
||||
@ProviderFor(eventService)
|
||||
final eventServiceProvider = EventServiceProvider._();
|
||||
|
||||
@@ -216,9 +216,14 @@ EquatableValue<List<TabStateWirthContainer>> 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<List<VisitInfo>> 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<List<VisitInfo>> quickTabSwitcherHistorySuggestions(
|
||||
|
||||
final hasTabStates = ref.watch(
|
||||
quickTabSwitcherTabStatesProvider(
|
||||
mode,
|
||||
effectiveMode,
|
||||
).select((value) => value.value.isNotEmpty),
|
||||
);
|
||||
if (hasTabStates) {
|
||||
@@ -263,6 +273,11 @@ AsyncValue<bool> 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<bool> quickTabSwitcherHasResults(
|
||||
|
||||
final hasResults = ref.watch(
|
||||
quickTabSwitcherTabStatesProvider(
|
||||
mode,
|
||||
effectiveMode,
|
||||
).select((value) => value.value.isNotEmpty),
|
||||
);
|
||||
|
||||
@@ -283,7 +298,7 @@ AsyncValue<bool> quickTabSwitcherHasResults(
|
||||
}
|
||||
|
||||
return ref
|
||||
.watch(quickTabSwitcherHistorySuggestionsProvider(mode))
|
||||
.watch(quickTabSwitcherHistorySuggestionsProvider(effectiveMode))
|
||||
.whenData((visits) => visits.isNotEmpty);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<AsyncValue<bool>, QuickTabSwitcherMode> {
|
||||
|
||||
@@ -277,7 +277,7 @@ class BrowserScreen extends HookConsumerWidget {
|
||||
);
|
||||
final quickTabSwitcherMode = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(value) => value.quickTabSwitcherMode,
|
||||
(value) => value.effectiveUiQuickTabSwitcherMode(),
|
||||
),
|
||||
);
|
||||
final quickTabSwitcherHasResults = ref.watch(
|
||||
|
||||
@@ -103,7 +103,7 @@ class TabViewScreen extends HookConsumerWidget {
|
||||
await SearchRoute(
|
||||
tabType:
|
||||
ref.read(selectedTabTypeProvider) ??
|
||||
settings.defaultCreateTabType,
|
||||
settings.effectiveDefaultCreateTabType,
|
||||
).push(context);
|
||||
|
||||
if (context.mounted) {
|
||||
|
||||
+12
-19
@@ -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) {
|
||||
|
||||
+17
-6
@@ -240,7 +240,7 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
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<BrowserView>
|
||||
|
||||
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<BrowserView>
|
||||
}
|
||||
});
|
||||
|
||||
final settings = ref.read(
|
||||
generalSettingsWithDefaultsProvider,
|
||||
);
|
||||
await quickActions.setShortcutItems([
|
||||
//TODO: add icons
|
||||
const ShortcutItem(
|
||||
@@ -358,10 +368,11 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
||||
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<BrowserView>
|
||||
await SearchRoute(
|
||||
tabType:
|
||||
ref.read(selectedTabTypeProvider) ??
|
||||
settings.defaultCreateTabType,
|
||||
settings.effectiveDefaultCreateTabType,
|
||||
searchText: searchText ?? SearchRoute.emptySearchText,
|
||||
).push(context);
|
||||
},
|
||||
|
||||
+13
-9
@@ -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
|
||||
|
||||
+15
-9
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
+1
-1
@@ -577,7 +577,7 @@ class ViewTabGridWidget extends HookConsumerWidget {
|
||||
await SearchRoute(
|
||||
tabType:
|
||||
ref.read(selectedTabTypeProvider) ??
|
||||
settings.defaultCreateTabType,
|
||||
settings.effectiveDefaultCreateTabType,
|
||||
).push(context);
|
||||
|
||||
onClose();
|
||||
|
||||
+1
-1
@@ -606,7 +606,7 @@ class ViewTabListWidget extends HookConsumerWidget {
|
||||
await SearchRoute(
|
||||
tabType:
|
||||
ref.read(selectedTabTypeProvider) ??
|
||||
settings.defaultCreateTabType,
|
||||
settings.effectiveDefaultCreateTabType,
|
||||
).push(context);
|
||||
|
||||
onClose();
|
||||
|
||||
+1
-1
@@ -345,7 +345,7 @@ class ViewTabTreesWidget extends HookConsumerWidget {
|
||||
await SearchRoute(
|
||||
tabType:
|
||||
ref.read(selectedTabTypeProvider) ??
|
||||
settings.defaultCreateTabType,
|
||||
settings.effectiveDefaultCreateTabType,
|
||||
).push(context);
|
||||
|
||||
onClose();
|
||||
|
||||
+72
-53
@@ -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),
|
||||
],
|
||||
),
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ class OpenImageInNewTab extends HookConsumerWidget {
|
||||
TabMode.fromTabType(
|
||||
ref
|
||||
.read(generalSettingsWithDefaultsProvider)
|
||||
.defaultCreateTabType,
|
||||
.effectiveDefaultCreateTabType,
|
||||
);
|
||||
|
||||
final tabId = await ref
|
||||
|
||||
+6
-5
@@ -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)
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ class OpenInNewTab extends HookConsumerWidget {
|
||||
TabMode.fromTabType(
|
||||
ref
|
||||
.read(generalSettingsWithDefaultsProvider)
|
||||
.defaultCreateTabType,
|
||||
.effectiveDefaultCreateTabType,
|
||||
);
|
||||
|
||||
final tabId = await ref
|
||||
|
||||
+6
-1
@@ -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),
|
||||
|
||||
+29
-25
@@ -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<TabMode>(
|
||||
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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
+13
-9
@@ -28,6 +28,7 @@ class AnimatedTabTypeSwitcher extends StatelessWidget {
|
||||
final TabType selected;
|
||||
final ValueChanged<TabType> 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(
|
||||
|
||||
+10
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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.',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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<String, dynamic> 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<Object?> get hashParameters => [
|
||||
themeMode,
|
||||
@@ -259,7 +283,9 @@ class GeneralSettings with FastEquatable {
|
||||
createChildTabsOption,
|
||||
showExtensionShortcut,
|
||||
enableLocalAiFeatures,
|
||||
defaultCreateTabType,
|
||||
showContainerUi,
|
||||
showIsolatedTabUi,
|
||||
storedDefaultCreateTabType,
|
||||
tabIntentOpenSetting,
|
||||
autoHideTabBar,
|
||||
tabBarSwipeAction,
|
||||
|
||||
@@ -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<String, dynamic> _$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,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider
|
||||
}
|
||||
|
||||
String _$generalSettingsRepositoryHash() =>
|
||||
r'5fcb1b28918a6c7bb23f72681fdec8bee77e49a5';
|
||||
r'51cfcf20b24705f5c6f51d4e204fff5a8e4eee89';
|
||||
|
||||
abstract class _$GeneralSettingsRepository
|
||||
extends $StreamNotifier<GeneralSettings> {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user