customize quick tab switcher buttons

This commit is contained in:
Fabian Freund
2026-07-06 17:48:58 +02:00
parent 10052ed3a3
commit 1b1c33dee5
30 changed files with 4763 additions and 133 deletions
@@ -23,7 +23,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/providers/toolbar_button_configs.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/contextual_toolbar_config_repository.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/data/repositories/toolbar_button_config_repository.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_config_location.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/domain/entities/toolbar_fallback_choice.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/models/contextual_toolbar_scope.dart';
import 'package:weblibre/features/geckoview/features/browser/features/contextual_toolbar/presentation/toolbar_button_registry.dart';
@@ -33,12 +34,22 @@ import 'package:weblibre/features/settings/presentation/widgets/settings_detail.
import 'package:weblibre/features/user/data/database/definitions.drift.dart';
class ContextualToolbarSettingsScreen extends HookConsumerWidget {
const ContextualToolbarSettingsScreen({super.key});
const ContextualToolbarSettingsScreen({
super.key,
this.location = ToolbarConfigLocation.contextual,
this.title = 'Customize Toolbar',
});
/// Which independently-configured toolbar this screen edits.
final ToolbarConfigLocation location;
/// App bar title, so the quick switcher variant reads distinctly.
final String title;
@override
Widget build(BuildContext context, WidgetRef ref) {
final configs = ref.watch(effectiveToolbarButtonConfigsProvider);
final repository = ref.watch(contextualToolbarConfigRepositoryProvider);
final configs = ref.watch(effectiveToolbarButtonConfigsProvider(location));
final repository = ref.watch(toolbarConfigRepositoryProvider(location));
final search = useSettingsSearch();
final query = search.normalizedQuery;
@@ -69,7 +80,7 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget {
);
return SettingsCustomScrollScaffold(
title: 'Customize Toolbar',
title: title,
searchController: search.controller,
searchHintText: 'Search toolbar buttons',
actions: [
@@ -87,7 +98,7 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget {
menuChildren: [
MenuItemButton(
leadingIcon: const Icon(Icons.restore),
onPressed: () => _resetToDefaults(ref),
onPressed: () => _resetToDefaults(ref, location),
child: const Text('Reset to Defaults'),
),
],
@@ -97,7 +108,10 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget {
const SliverToBoxAdapter(child: SizedBox(height: 16)),
SliverPersistentHeader(
pinned: true,
delegate: _ToolbarPreviewDelegate(configs: configs.value),
delegate: _ToolbarPreviewDelegate(
configs: configs.value,
location: location,
),
),
const SliverPadding(
padding: EdgeInsets.fromLTRB(16, 20, 16, 0),
@@ -193,7 +207,7 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget {
List<ToolbarButtonConfig> configs,
int oldIndex,
int newIndex,
ContextualToolbarConfigRepository repository, {
ToolbarButtonConfigRepository repository, {
required bool isVisible,
}) {
if (oldIndex == newIndex) return;
@@ -212,7 +226,7 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget {
}
Future<void> _reorderViaDb(
ContextualToolbarConfigRepository repository,
ToolbarButtonConfigRepository repository,
List<ToolbarButtonConfig> configs,
int oldIndex,
int targetIndex,
@@ -248,9 +262,12 @@ class ContextualToolbarSettingsScreen extends HookConsumerWidget {
await repository.assignOrderKey(movedId, orderKey: orderKey);
}
Future<void> _resetToDefaults(WidgetRef ref) async {
final repository = ref.read(contextualToolbarConfigRepositoryProvider);
await repository.replaceAll(defaultToolbarButtonConfigs.value);
Future<void> _resetToDefaults(
WidgetRef ref,
ToolbarConfigLocation location,
) async {
final repository = ref.read(toolbarConfigRepositoryProvider(location));
await repository.replaceAll(defaultToolbarButtonConfigsFor(location).value);
}
}
@@ -284,7 +301,7 @@ class _ToolbarButtonConfigTile extends HookConsumerWidget {
final int index;
final ToolbarButtonConfig config;
final ContextualToolbarConfigRepository repository;
final ToolbarButtonConfigRepository repository;
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -511,9 +528,13 @@ class _FallbackPicker extends StatelessWidget {
}
class _ToolbarPreviewDelegate extends SliverPersistentHeaderDelegate {
const _ToolbarPreviewDelegate({required this.configs});
const _ToolbarPreviewDelegate({
required this.configs,
required this.location,
});
final List<ToolbarButtonConfig> configs;
final ToolbarConfigLocation location;
static const _previewHeight = BrowserTabBar.contextualToolabarHeight;
@@ -532,19 +553,21 @@ class _ToolbarPreviewDelegate extends SliverPersistentHeaderDelegate {
height: maxExtent,
child: ColoredBox(
color: Theme.of(context).colorScheme.surfaceContainer,
child: _ToolbarPreview(configs: configs),
child: _ToolbarPreview(configs: configs, location: location),
),
);
}
@override
bool shouldRebuild(_ToolbarPreviewDelegate old) => old.configs != configs;
bool shouldRebuild(_ToolbarPreviewDelegate old) =>
old.configs != configs || old.location != location;
}
class _ToolbarPreview extends ConsumerWidget {
const _ToolbarPreview({required this.configs});
const _ToolbarPreview({required this.configs, required this.location});
final List<ToolbarButtonConfig> configs;
final ToolbarConfigLocation location;
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -555,6 +578,7 @@ class _ToolbarPreview extends ConsumerWidget {
displayedSheet: null,
tabState: null,
isPreview: true,
location: location,
);
final buttons = visibleConfigs.map((config) {
@@ -92,6 +92,12 @@ const List<SettingsSectionDefinition> toolbarLayoutSettingsSections = [
],
child: _TabBarStackingModeSection(),
),
SettingsEntryDefinition(
title: 'Customize Switcher Buttons',
subtitle: 'Choose which action buttons appear at the end of the bar',
keywords: ['buttons', 'new tab', 'actions', 'trailing'],
child: _CustomizeQuickSwitcherButtonsTile(),
),
SettingsEntryDefinition(
title: 'Close Buttons on All Tabs',
subtitle: 'Show a close button on every switcher chip',
@@ -328,6 +334,33 @@ class _CustomizeToolbarButtonsTile extends HookConsumerWidget {
}
}
class _CustomizeQuickSwitcherButtonsTile extends HookConsumerWidget {
const _CustomizeQuickSwitcherButtonsTile();
@override
Widget build(BuildContext context, WidgetRef ref) {
final switcherEnabled = ref.watch(
generalSettingsWithDefaultsProvider.select(
(s) => s.effectiveTabBarStackingMode() != TabBarStackingMode.disabled,
),
);
return ListTile(
leading: const Icon(Icons.tune),
title: const Text('Customize Switcher Buttons'),
subtitle: const Text(
'Action buttons pinned at the end of the switcher bar (independent of '
'the contextual toolbar)',
),
trailing: const Icon(Icons.chevron_right),
enabled: switcherEnabled,
onTap: () async {
await const QuickSwitcherToolbarSettingsRoute().push(context);
},
);
}
}
class _TabBarStackingModeSection extends HookConsumerWidget {
const _TabBarStackingModeSection();