improved quick tab switcher & general tab design/logic;

This commit is contained in:
Fabian Freund
2026-06-12 03:47:20 +02:00
parent 83c78b53d3
commit 3c5f472bdf
35 changed files with 2715 additions and 768 deletions
@@ -47,6 +47,13 @@ const defaultQuickTabSwitcherHierarchyGlyphs = 2;
const minQuickTabSwitcherHierarchyGlyphs = 0;
const maxQuickTabSwitcherHierarchyGlyphs = 4;
/// Max width (logical px) of the title text on a quick tab switcher chip.
/// The default of 64 sits at the 1/3 position of the slider scale.
const defaultQuickTabSwitcherTitleWidth = 64.0;
const minQuickTabSwitcherTitleWidth = 32.0;
const maxQuickTabSwitcherTitleWidth = 128.0;
const quickTabSwitcherTitleWidthStep = 8.0;
/// Controls the Android display refresh rate the app requests at startup.
///
/// Flutter does not request a high refresh rate by default, so on many devices
@@ -58,8 +65,24 @@ enum RefreshRateMode { system, high, low }
enum TabBarSwipeAction { switchLastOpened, navigateOrderedTabs }
/// Row kind rendered inside the quick tab switcher bar. [TabBarStackingMode]
/// decides which row(s) are shown; this enum identifies a single row.
enum QuickTabSwitcherMode { lastUsedTabs, containerTabs }
/// Layout of the quick tab switcher bar. Merges the former
/// "show tab switcher bar" toggle and [QuickTabSwitcherMode] selection.
///
/// [accordion] renders all containers as header chips with the selected
/// container's tabs expanded inline. [twoLevel] stacks a container-tabs row
/// on top of a recently-used row. [disabled] hides the bar entirely.
enum TabBarStackingMode {
lastUsedTabs,
containerTabs,
accordion,
twoLevel,
disabled,
}
enum TabIntentOpenSetting { regular, private, isolated, ask }
enum TabDirection { newestFirst, oldestFirst }
@@ -118,10 +141,9 @@ class GeneralSettings with FastEquatable {
final Duration historyAutoCleanInterval;
final bool tabViewBottomSheet;
final bool tabBarShowContextualBar;
final bool tabBarShowQuickTabSwitcherBar;
final TabBarPosition tabBarPosition;
final TabBarLayout tabBarLayout;
final QuickTabSwitcherMode quickTabSwitcherMode;
final TabBarStackingMode tabBarStackingMode;
final bool pullToRefreshEnabled;
final bool useExternalDownloadManager;
final bool doubleBackCloseTab;
@@ -132,6 +154,13 @@ class GeneralSettings with FastEquatable {
final bool quickTabSwitcherShowTitles;
final int quickTabSwitcherHierarchyGlyphs;
final bool quickTabSwitcherShowHistorySuggestions;
/// Max width (logical px) for chip titles in the quick tab switcher.
final double quickTabSwitcherTitleWidth;
/// Whether every tab chip in the quick tab switcher shows a close button.
/// The selected tab's chip always shows one regardless of this setting.
final bool quickTabSwitcherShowCloseButtonOnAllTabs;
final String syncServerOverride;
final String syncTokenServerOverride;
final bool urlCleanerEnabled;
@@ -202,10 +231,9 @@ class GeneralSettings with FastEquatable {
required this.historyAutoCleanInterval,
required this.tabViewBottomSheet,
required this.tabBarShowContextualBar,
required this.tabBarShowQuickTabSwitcherBar,
required this.tabBarPosition,
required this.tabBarLayout,
required this.quickTabSwitcherMode,
required this.tabBarStackingMode,
required this.pullToRefreshEnabled,
required this.useExternalDownloadManager,
required this.doubleBackCloseTab,
@@ -216,6 +244,8 @@ class GeneralSettings with FastEquatable {
required this.quickTabSwitcherShowTitles,
required this.quickTabSwitcherHierarchyGlyphs,
required this.quickTabSwitcherShowHistorySuggestions,
required this.quickTabSwitcherTitleWidth,
required this.quickTabSwitcherShowCloseButtonOnAllTabs,
required this.syncServerOverride,
required this.syncTokenServerOverride,
required this.urlCleanerEnabled,
@@ -267,10 +297,9 @@ class GeneralSettings with FastEquatable {
Duration? historyAutoCleanInterval,
bool? tabViewBottomSheet,
bool? tabBarShowContextualBar,
bool? tabBarShowQuickTabSwitcherBar,
TabBarPosition? tabBarPosition,
TabBarLayout? tabBarLayout,
QuickTabSwitcherMode? quickTabSwitcherMode,
TabBarStackingMode? tabBarStackingMode,
bool? pullToRefreshEnabled,
bool? useExternalDownloadManager,
bool? doubleBackCloseTab,
@@ -281,6 +310,8 @@ class GeneralSettings with FastEquatable {
bool? quickTabSwitcherShowTitles,
int? quickTabSwitcherHierarchyGlyphs,
bool? quickTabSwitcherShowHistorySuggestions,
double? quickTabSwitcherTitleWidth,
bool? quickTabSwitcherShowCloseButtonOnAllTabs,
String? syncServerOverride,
String? syncTokenServerOverride,
bool? urlCleanerEnabled,
@@ -332,11 +363,9 @@ class GeneralSettings with FastEquatable {
historyAutoCleanInterval ?? const Duration(days: 90),
tabViewBottomSheet = tabViewBottomSheet ?? false,
tabBarShowContextualBar = tabBarShowContextualBar ?? true,
tabBarShowQuickTabSwitcherBar = tabBarShowQuickTabSwitcherBar ?? true,
tabBarPosition = tabBarPosition ?? TabBarPosition.bottom,
tabBarLayout = tabBarLayout ?? TabBarLayout.compact,
quickTabSwitcherMode =
quickTabSwitcherMode ?? QuickTabSwitcherMode.lastUsedTabs,
tabBarStackingMode = tabBarStackingMode ?? TabBarStackingMode.accordion,
pullToRefreshEnabled = pullToRefreshEnabled ?? true,
useExternalDownloadManager = useExternalDownloadManager ?? false,
doubleBackCloseTab = doubleBackCloseTab ?? true,
@@ -351,6 +380,10 @@ class GeneralSettings with FastEquatable {
defaultQuickTabSwitcherHierarchyGlyphs,
quickTabSwitcherShowHistorySuggestions =
quickTabSwitcherShowHistorySuggestions ?? true,
quickTabSwitcherTitleWidth =
quickTabSwitcherTitleWidth ?? defaultQuickTabSwitcherTitleWidth,
quickTabSwitcherShowCloseButtonOnAllTabs =
quickTabSwitcherShowCloseButtonOnAllTabs ?? false,
syncServerOverride = syncServerOverride ?? '',
syncTokenServerOverride = syncTokenServerOverride ?? '',
urlCleanerEnabled = urlCleanerEnabled ?? true,
@@ -394,6 +427,24 @@ class GeneralSettings with FastEquatable {
json.putIfAbsent('tabListDirection', () => mapped);
json.putIfAbsent('tabBarDirection', () => mapped);
}
// Migrate the legacy `tabBarShowQuickTabSwitcherBar` toggle and
// `quickTabSwitcherMode` selection to the merged `tabBarStackingMode`.
// The legacy mode names are a subset of the new enum's, so values map
// verbatim.
// TODO: Drop this fallback (and the legacy rows in the user settings DB)
// once enough releases have shipped that rolling back to a version
// without `tabBarStackingMode` is no longer a concern.
final legacyShowSwitcherBar = json['tabBarShowQuickTabSwitcherBar'];
final legacySwitcherMode = json['quickTabSwitcherMode'];
if (json['tabBarStackingMode'] == null) {
if (legacyShowSwitcherBar == false) {
json['tabBarStackingMode'] = 'disabled';
} else if (legacySwitcherMode != null) {
json['tabBarStackingMode'] = legacySwitcherMode;
}
}
return _$GeneralSettingsFromJson(json);
}
@@ -421,12 +472,18 @@ class GeneralSettings with FastEquatable {
return tabIntentOpenSetting;
}
QuickTabSwitcherMode effectiveUiQuickTabSwitcherMode() {
/// Container-dependent stacking modes degrade to a single recently-used
/// row when the container UI is disabled.
TabBarStackingMode effectiveTabBarStackingMode() {
if (!showContainerUi &&
quickTabSwitcherMode == QuickTabSwitcherMode.containerTabs) {
return QuickTabSwitcherMode.lastUsedTabs;
const {
TabBarStackingMode.containerTabs,
TabBarStackingMode.accordion,
TabBarStackingMode.twoLevel,
}.contains(tabBarStackingMode)) {
return TabBarStackingMode.lastUsedTabs;
}
return quickTabSwitcherMode;
return tabBarStackingMode;
}
@override
@@ -456,10 +513,9 @@ class GeneralSettings with FastEquatable {
historyAutoCleanInterval,
tabViewBottomSheet,
tabBarShowContextualBar,
tabBarShowQuickTabSwitcherBar,
tabBarPosition,
tabBarLayout,
quickTabSwitcherMode,
tabBarStackingMode,
pullToRefreshEnabled,
useExternalDownloadManager,
doubleBackCloseTab,
@@ -470,6 +526,8 @@ class GeneralSettings with FastEquatable {
quickTabSwitcherShowTitles,
quickTabSwitcherHierarchyGlyphs,
quickTabSwitcherShowHistorySuggestions,
quickTabSwitcherTitleWidth,
quickTabSwitcherShowCloseButtonOnAllTabs,
syncServerOverride,
syncTokenServerOverride,
urlCleanerEnabled,
@@ -65,17 +65,11 @@ abstract class _$GeneralSettingsCWProxy {
GeneralSettings tabBarShowContextualBar(bool tabBarShowContextualBar);
GeneralSettings tabBarShowQuickTabSwitcherBar(
bool tabBarShowQuickTabSwitcherBar,
);
GeneralSettings tabBarPosition(TabBarPosition tabBarPosition);
GeneralSettings tabBarLayout(TabBarLayout tabBarLayout);
GeneralSettings quickTabSwitcherMode(
QuickTabSwitcherMode quickTabSwitcherMode,
);
GeneralSettings tabBarStackingMode(TabBarStackingMode tabBarStackingMode);
GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled);
@@ -103,6 +97,12 @@ abstract class _$GeneralSettingsCWProxy {
bool quickTabSwitcherShowHistorySuggestions,
);
GeneralSettings quickTabSwitcherTitleWidth(double quickTabSwitcherTitleWidth);
GeneralSettings quickTabSwitcherShowCloseButtonOnAllTabs(
bool quickTabSwitcherShowCloseButtonOnAllTabs,
);
GeneralSettings syncServerOverride(String syncServerOverride);
GeneralSettings syncTokenServerOverride(String syncTokenServerOverride);
@@ -186,10 +186,9 @@ abstract class _$GeneralSettingsCWProxy {
Duration historyAutoCleanInterval,
bool tabViewBottomSheet,
bool tabBarShowContextualBar,
bool tabBarShowQuickTabSwitcherBar,
TabBarPosition tabBarPosition,
TabBarLayout tabBarLayout,
QuickTabSwitcherMode quickTabSwitcherMode,
TabBarStackingMode tabBarStackingMode,
bool pullToRefreshEnabled,
bool useExternalDownloadManager,
bool doubleBackCloseTab,
@@ -200,6 +199,8 @@ abstract class _$GeneralSettingsCWProxy {
bool quickTabSwitcherShowTitles,
int quickTabSwitcherHierarchyGlyphs,
bool quickTabSwitcherShowHistorySuggestions,
double quickTabSwitcherTitleWidth,
bool quickTabSwitcherShowCloseButtonOnAllTabs,
String syncServerOverride,
String syncTokenServerOverride,
bool urlCleanerEnabled,
@@ -337,11 +338,6 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
GeneralSettings tabBarShowContextualBar(bool tabBarShowContextualBar) =>
call(tabBarShowContextualBar: tabBarShowContextualBar);
@override
GeneralSettings tabBarShowQuickTabSwitcherBar(
bool tabBarShowQuickTabSwitcherBar,
) => call(tabBarShowQuickTabSwitcherBar: tabBarShowQuickTabSwitcherBar);
@override
GeneralSettings tabBarPosition(TabBarPosition tabBarPosition) =>
call(tabBarPosition: tabBarPosition);
@@ -351,9 +347,8 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
call(tabBarLayout: tabBarLayout);
@override
GeneralSettings quickTabSwitcherMode(
QuickTabSwitcherMode quickTabSwitcherMode,
) => call(quickTabSwitcherMode: quickTabSwitcherMode);
GeneralSettings tabBarStackingMode(TabBarStackingMode tabBarStackingMode) =>
call(tabBarStackingMode: tabBarStackingMode);
@override
GeneralSettings pullToRefreshEnabled(bool pullToRefreshEnabled) =>
@@ -401,6 +396,19 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
quickTabSwitcherShowHistorySuggestions,
);
@override
GeneralSettings quickTabSwitcherTitleWidth(
double quickTabSwitcherTitleWidth,
) => call(quickTabSwitcherTitleWidth: quickTabSwitcherTitleWidth);
@override
GeneralSettings quickTabSwitcherShowCloseButtonOnAllTabs(
bool quickTabSwitcherShowCloseButtonOnAllTabs,
) => call(
quickTabSwitcherShowCloseButtonOnAllTabs:
quickTabSwitcherShowCloseButtonOnAllTabs,
);
@override
GeneralSettings syncServerOverride(String syncServerOverride) =>
call(syncServerOverride: syncServerOverride);
@@ -529,10 +537,9 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
Object? historyAutoCleanInterval = const $CopyWithPlaceholder(),
Object? tabViewBottomSheet = const $CopyWithPlaceholder(),
Object? tabBarShowContextualBar = const $CopyWithPlaceholder(),
Object? tabBarShowQuickTabSwitcherBar = const $CopyWithPlaceholder(),
Object? tabBarPosition = const $CopyWithPlaceholder(),
Object? tabBarLayout = const $CopyWithPlaceholder(),
Object? quickTabSwitcherMode = const $CopyWithPlaceholder(),
Object? tabBarStackingMode = const $CopyWithPlaceholder(),
Object? pullToRefreshEnabled = const $CopyWithPlaceholder(),
Object? useExternalDownloadManager = const $CopyWithPlaceholder(),
Object? doubleBackCloseTab = const $CopyWithPlaceholder(),
@@ -544,6 +551,9 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
Object? quickTabSwitcherHierarchyGlyphs = const $CopyWithPlaceholder(),
Object? quickTabSwitcherShowHistorySuggestions =
const $CopyWithPlaceholder(),
Object? quickTabSwitcherTitleWidth = const $CopyWithPlaceholder(),
Object? quickTabSwitcherShowCloseButtonOnAllTabs =
const $CopyWithPlaceholder(),
Object? syncServerOverride = const $CopyWithPlaceholder(),
Object? syncTokenServerOverride = const $CopyWithPlaceholder(),
Object? urlCleanerEnabled = const $CopyWithPlaceholder(),
@@ -714,12 +724,6 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
? _value.tabBarShowContextualBar
// ignore: cast_nullable_to_non_nullable
: tabBarShowContextualBar as bool,
tabBarShowQuickTabSwitcherBar:
tabBarShowQuickTabSwitcherBar == const $CopyWithPlaceholder() ||
tabBarShowQuickTabSwitcherBar == null
? _value.tabBarShowQuickTabSwitcherBar
// ignore: cast_nullable_to_non_nullable
: tabBarShowQuickTabSwitcherBar as bool,
tabBarPosition:
tabBarPosition == const $CopyWithPlaceholder() ||
tabBarPosition == null
@@ -731,12 +735,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
? _value.tabBarLayout
// ignore: cast_nullable_to_non_nullable
: tabBarLayout as TabBarLayout,
quickTabSwitcherMode:
quickTabSwitcherMode == const $CopyWithPlaceholder() ||
quickTabSwitcherMode == null
? _value.quickTabSwitcherMode
tabBarStackingMode:
tabBarStackingMode == const $CopyWithPlaceholder() ||
tabBarStackingMode == null
? _value.tabBarStackingMode
// ignore: cast_nullable_to_non_nullable
: quickTabSwitcherMode as QuickTabSwitcherMode,
: tabBarStackingMode as TabBarStackingMode,
pullToRefreshEnabled:
pullToRefreshEnabled == const $CopyWithPlaceholder() ||
pullToRefreshEnabled == null
@@ -798,6 +802,19 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
? _value.quickTabSwitcherShowHistorySuggestions
// ignore: cast_nullable_to_non_nullable
: quickTabSwitcherShowHistorySuggestions as bool,
quickTabSwitcherTitleWidth:
quickTabSwitcherTitleWidth == const $CopyWithPlaceholder() ||
quickTabSwitcherTitleWidth == null
? _value.quickTabSwitcherTitleWidth
// ignore: cast_nullable_to_non_nullable
: quickTabSwitcherTitleWidth as double,
quickTabSwitcherShowCloseButtonOnAllTabs:
quickTabSwitcherShowCloseButtonOnAllTabs ==
const $CopyWithPlaceholder() ||
quickTabSwitcherShowCloseButtonOnAllTabs == null
? _value.quickTabSwitcherShowCloseButtonOnAllTabs
// ignore: cast_nullable_to_non_nullable
: quickTabSwitcherShowCloseButtonOnAllTabs as bool,
syncServerOverride:
syncServerOverride == const $CopyWithPlaceholder() ||
syncServerOverride == null
@@ -1005,7 +1022,6 @@ GeneralSettings _$GeneralSettingsFromJson(
),
tabViewBottomSheet: json['tabViewBottomSheet'] as bool?,
tabBarShowContextualBar: json['tabBarShowContextualBar'] as bool?,
tabBarShowQuickTabSwitcherBar: json['tabBarShowQuickTabSwitcherBar'] as bool?,
tabBarPosition: $enumDecodeNullable(
_$TabBarPositionEnumMap,
json['tabBarPosition'],
@@ -1014,9 +1030,9 @@ GeneralSettings _$GeneralSettingsFromJson(
_$TabBarLayoutEnumMap,
json['tabBarLayout'],
),
quickTabSwitcherMode: $enumDecodeNullable(
_$QuickTabSwitcherModeEnumMap,
json['quickTabSwitcherMode'],
tabBarStackingMode: $enumDecodeNullable(
_$TabBarStackingModeEnumMap,
json['tabBarStackingMode'],
),
pullToRefreshEnabled: json['pullToRefreshEnabled'] as bool?,
useExternalDownloadManager: json['useExternalDownloadManager'] as bool?,
@@ -1036,6 +1052,10 @@ GeneralSettings _$GeneralSettingsFromJson(
(json['quickTabSwitcherHierarchyGlyphs'] as num?)?.toInt(),
quickTabSwitcherShowHistorySuggestions:
json['quickTabSwitcherShowHistorySuggestions'] as bool?,
quickTabSwitcherTitleWidth: (json['quickTabSwitcherTitleWidth'] as num?)
?.toDouble(),
quickTabSwitcherShowCloseButtonOnAllTabs:
json['quickTabSwitcherShowCloseButtonOnAllTabs'] as bool?,
syncServerOverride: json['syncServerOverride'] as String?,
syncTokenServerOverride: json['syncTokenServerOverride'] as String?,
urlCleanerEnabled: json['urlCleanerEnabled'] as bool?,
@@ -1107,11 +1127,10 @@ Map<String, dynamic> _$GeneralSettingsToJson(
'historyAutoCleanInterval': instance.historyAutoCleanInterval.inMicroseconds,
'tabViewBottomSheet': instance.tabViewBottomSheet,
'tabBarShowContextualBar': instance.tabBarShowContextualBar,
'tabBarShowQuickTabSwitcherBar': instance.tabBarShowQuickTabSwitcherBar,
'tabBarPosition': _$TabBarPositionEnumMap[instance.tabBarPosition]!,
'tabBarLayout': _$TabBarLayoutEnumMap[instance.tabBarLayout]!,
'quickTabSwitcherMode':
_$QuickTabSwitcherModeEnumMap[instance.quickTabSwitcherMode]!,
'tabBarStackingMode':
_$TabBarStackingModeEnumMap[instance.tabBarStackingMode]!,
'pullToRefreshEnabled': instance.pullToRefreshEnabled,
'useExternalDownloadManager': instance.useExternalDownloadManager,
'doubleBackCloseTab': instance.doubleBackCloseTab,
@@ -1124,6 +1143,9 @@ Map<String, dynamic> _$GeneralSettingsToJson(
'quickTabSwitcherHierarchyGlyphs': instance.quickTabSwitcherHierarchyGlyphs,
'quickTabSwitcherShowHistorySuggestions':
instance.quickTabSwitcherShowHistorySuggestions,
'quickTabSwitcherTitleWidth': instance.quickTabSwitcherTitleWidth,
'quickTabSwitcherShowCloseButtonOnAllTabs':
instance.quickTabSwitcherShowCloseButtonOnAllTabs,
'syncServerOverride': instance.syncServerOverride,
'syncTokenServerOverride': instance.syncTokenServerOverride,
'urlCleanerEnabled': instance.urlCleanerEnabled,
@@ -1214,9 +1236,12 @@ const _$TabBarLayoutEnumMap = {
TabBarLayout.compact: 'compact',
};
const _$QuickTabSwitcherModeEnumMap = {
QuickTabSwitcherMode.lastUsedTabs: 'lastUsedTabs',
QuickTabSwitcherMode.containerTabs: 'containerTabs',
const _$TabBarStackingModeEnumMap = {
TabBarStackingMode.lastUsedTabs: 'lastUsedTabs',
TabBarStackingMode.containerTabs: 'containerTabs',
TabBarStackingMode.accordion: 'accordion',
TabBarStackingMode.twoLevel: 'twoLevel',
TabBarStackingMode.disabled: 'disabled',
};
const _$IntentSourcePolicyEnumMap = {