add setting to control tab insertion position

This commit is contained in:
Fabian Freund
2026-03-13 05:07:48 +01:00
parent 8dc7151c45
commit 8a07d7df1a
10 changed files with 132 additions and 10 deletions
@@ -57,7 +57,7 @@ final class TabSessionProvider extends $NotifierProvider<TabSession, void> {
}
}
String _$tabSessionHash() => r'be44598ad023c2fb23e48d3fa4bf805c4640c599';
String _$tabSessionHash() => r'636f0940425d689ff98fabfc61687de828a8eeb9';
final class TabSessionFamily extends $Family
with $ClassFamilyOverride<TabSession, void, void, void, String?> {
@@ -42,6 +42,8 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selec
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/tor/domain/repositories/tor_proxy.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/utils/debouncer.dart';
part 'tab.g.dart';
@@ -66,6 +68,14 @@ class TabRepository extends _$TabRepository {
_tabFromIntent.remove(tabId);
}
NewTabPosition _newTabPositionForParent(String? parentId) {
if (parentId != null) {
return NewTabPosition.first;
}
return ref.read(generalSettingsWithDefaultsProvider).newTabPosition;
}
Future<String?> _resolveParentIdForContext({
required String? parentId,
required String? targetContextId,
@@ -122,6 +132,7 @@ class TabRepository extends _$TabRepository {
final effectiveContextId = tabMode is IsolatedTabMode
? effectiveIsolationContextId
: assignedContainer?.metadata.contextualIdentity;
final newTabPosition = _newTabPositionForParent(validatedParentId);
final newTabId = await tabDao.upsertTabTransactional(
() {
@@ -139,6 +150,7 @@ class TabRepository extends _$TabRepository {
);
},
parentId: Value(validatedParentId),
newTabPosition: newTabPosition,
containerId: Value(assignedContainer?.id),
url: Value(url),
tabMode: Value(tabMode),
@@ -171,7 +183,6 @@ class TabRepository extends _$TabRepository {
tabs: tabs,
selectTabId: selectTabId,
);
// Build sets for validation
final creatingTabIds = createdTabIds.toSet();
final parentIdsToValidate = tabs
@@ -204,6 +215,7 @@ class TabRepository extends _$TabRepository {
tabId,
parentId: Value(validatedParentId),
source: TabSource.manual,
newTabPosition: _newTabPositionForParent(validatedParentId),
containerId: Value(assignedContainer?.id),
url: Value(Uri.tryParse(tab.url)),
tabMode: Value(
@@ -254,6 +266,7 @@ class TabRepository extends _$TabRepository {
);
},
parentId: const Value.absent(),
newTabPosition: _newTabPositionForParent(null),
containerId: Value(containerData?.id),
tabMode: Value(duplicateTabMode),
);
@@ -597,6 +610,7 @@ class TabRepository extends _$TabRepository {
tabId,
parentId: const Value.absent(),
source: TabSource.addedEvent,
newTabPosition: _newTabPositionForParent(null),
containerId: Value(containerId),
);
},
@@ -41,7 +41,7 @@ final class TabRepositoryProvider
}
}
String _$tabRepositoryHash() => r'1c866a1f5b1872ba07ebac82ff92455a7af316a3';
String _$tabRepositoryHash() => r'4dd31901b4d02ca075d3dcec3822f5a2832d164c';
abstract class _$TabRepository extends $Notifier<void> {
void build();
@@ -601,7 +601,7 @@ final class QuickTabSwitcherTabStatesProvider
}
String _$quickTabSwitcherTabStatesHash() =>
r'2627bcd25718472f0c8057e836d76bdaa7317c1c';
r'd83ea34d366238681baaad0b36699ee0742edc3d';
final class QuickTabSwitcherTabStatesFamily extends $Family
with
@@ -30,6 +30,7 @@ import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode
import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_source.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
class SyncTabsResult {
final Set<String> deletedIsolationContextIds;
@@ -139,6 +140,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
Future<String> _generateOrderKey({
required Value<String?> parentId,
required Value<String?> containerId,
required NewTabPosition newTabPosition,
}) async {
if (parentId.value.isNotEmpty) {
return await db.containerDao
@@ -148,15 +150,23 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
.generateLeadingOrderKey(containerId.value)
.getSingle();
} else {
return db.containerDao
.generateLeadingOrderKey(containerId.value)
.getSingle();
return switch (newTabPosition) {
NewTabPosition.first =>
db.containerDao
.generateLeadingOrderKey(containerId.value)
.getSingle(),
NewTabPosition.end =>
db.containerDao
.generateTrailingOrderKey(containerId.value)
.getSingle(),
};
}
}
Future<String> upsertTabTransactional(
Future<String> Function() createTab, {
required Value<String?> parentId,
NewTabPosition newTabPosition = NewTabPosition.first,
Value<String?> containerId = const Value.absent(),
Value<String?> orderKey = const Value.absent(),
Value<Uri?> url = const Value.absent(),
@@ -167,7 +177,11 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
final tabId = await createTab();
final currentOrderKey =
orderKey.value ??
await _generateOrderKey(parentId: parentId, containerId: containerId);
await _generateOrderKey(
parentId: parentId,
containerId: containerId,
newTabPosition: newTabPosition,
);
final Value<TabModeDbValue> persistedTabMode = tabMode.present
? Value(tabMode.value.toDbValue())
: const Value.absent();
@@ -211,6 +225,7 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
String tabId, {
required TabSource source,
required Value<String?> parentId,
NewTabPosition newTabPosition = NewTabPosition.first,
Value<String?> containerId = const Value.absent(),
Value<String?> orderKey = const Value.absent(),
Value<Uri?> url = const Value.absent(),
@@ -220,7 +235,11 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
return db.transaction(() async {
final currentOrderKey =
orderKey.value ??
await _generateOrderKey(parentId: parentId, containerId: containerId);
await _generateOrderKey(
parentId: parentId,
containerId: containerId,
newTabPosition: newTabPosition,
);
final Value<TabModeDbValue> persistedTabMode = tabMode.present
? Value(tabMode.value.toDbValue())
: const Value.absent();
@@ -68,6 +68,7 @@ class _TabCreationSection extends StatelessWidget {
children: [
SettingSection(name: 'Tab Creation'),
_NewTabDefaultSection(),
_NewTabPositionSection(),
_ExternalLinkHandlingSection(),
_AppLinksModeSection(),
],
@@ -255,6 +256,59 @@ class _ExternalLinkHandlingSection extends HookConsumerWidget {
}
}
class _NewTabPositionSection extends HookConsumerWidget {
const _NewTabPositionSection();
@override
Widget build(BuildContext context, WidgetRef ref) {
final newTabPosition = ref.watch(
generalSettingsWithDefaultsProvider.select((s) => s.newTabPosition),
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ListTile(
title: Text('New Tab Position'),
subtitle: Text('Choose where newly created tabs appear by default'),
leading: Icon(MdiIcons.reorderHorizontal),
contentPadding: EdgeInsets.zero,
),
Center(
child: SegmentedButton(
showSelectedIcon: false,
segments: const [
ButtonSegment(
value: NewTabPosition.first,
label: Text('First'),
icon: Icon(MdiIcons.arrowCollapseLeft),
),
ButtonSegment(
value: NewTabPosition.end,
label: Text('End'),
icon: Icon(MdiIcons.arrowCollapseRight),
),
],
selected: {newTabPosition},
onSelectionChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) =>
currentSettings.copyWith.newTabPosition(value.first),
);
},
),
),
],
),
);
}
}
class _CreateChildTabsTile extends HookConsumerWidget {
const _CreateChildTabsTile();
@@ -45,6 +45,8 @@ enum QuickTabSwitcherMode { lastUsedTabs, containerTabs }
enum TabIntentOpenSetting { regular, private, ask }
enum NewTabPosition { first, end }
enum TabBarPosition { top, bottom }
enum TabBarLayout { withTitle, compact }
@@ -82,6 +84,7 @@ class GeneralSettings with FastEquatable {
final bool showIsolatedTabUi;
@JsonKey(name: 'defaultCreateTabType')
final TabType storedDefaultCreateTabType;
final NewTabPosition newTabPosition;
final TabIntentOpenSetting tabIntentOpenSetting;
final bool autoHideTabBar;
final TabBarSwipeAction tabBarSwipeAction;
@@ -129,6 +132,7 @@ class GeneralSettings with FastEquatable {
required this.showContainerUi,
required this.showIsolatedTabUi,
required this.storedDefaultCreateTabType,
required this.newTabPosition,
required this.tabIntentOpenSetting,
required this.autoHideTabBar,
required this.tabBarSwipeAction,
@@ -177,6 +181,7 @@ class GeneralSettings with FastEquatable {
bool? showContainerUi,
bool? showIsolatedTabUi,
TabType? storedDefaultCreateTabType,
NewTabPosition? newTabPosition,
TabIntentOpenSetting? tabIntentOpenSetting,
bool? autoHideTabBar,
TabBarSwipeAction? tabBarSwipeAction,
@@ -223,6 +228,7 @@ class GeneralSettings with FastEquatable {
showIsolatedTabUi = showIsolatedTabUi ?? true,
storedDefaultCreateTabType =
storedDefaultCreateTabType ?? TabType.regular,
newTabPosition = newTabPosition ?? NewTabPosition.first,
tabIntentOpenSetting = tabIntentOpenSetting ?? TabIntentOpenSetting.ask,
autoHideTabBar = autoHideTabBar ?? true,
tabBarSwipeAction =
@@ -300,6 +306,7 @@ class GeneralSettings with FastEquatable {
showContainerUi,
showIsolatedTabUi,
storedDefaultCreateTabType,
newTabPosition,
tabIntentOpenSetting,
autoHideTabBar,
tabBarSwipeAction,
@@ -41,6 +41,8 @@ abstract class _$GeneralSettingsCWProxy {
TabType storedDefaultCreateTabType,
);
GeneralSettings newTabPosition(NewTabPosition newTabPosition);
GeneralSettings tabIntentOpenSetting(
TabIntentOpenSetting tabIntentOpenSetting,
);
@@ -137,6 +139,7 @@ abstract class _$GeneralSettingsCWProxy {
bool showContainerUi,
bool showIsolatedTabUi,
TabType storedDefaultCreateTabType,
NewTabPosition newTabPosition,
TabIntentOpenSetting tabIntentOpenSetting,
bool autoHideTabBar,
TabBarSwipeAction tabBarSwipeAction,
@@ -236,6 +239,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
TabType storedDefaultCreateTabType,
) => call(storedDefaultCreateTabType: storedDefaultCreateTabType);
@override
GeneralSettings newTabPosition(NewTabPosition newTabPosition) =>
call(newTabPosition: newTabPosition);
@override
GeneralSettings tabIntentOpenSetting(
TabIntentOpenSetting tabIntentOpenSetting,
@@ -393,6 +400,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
Object? showContainerUi = const $CopyWithPlaceholder(),
Object? showIsolatedTabUi = const $CopyWithPlaceholder(),
Object? storedDefaultCreateTabType = const $CopyWithPlaceholder(),
Object? newTabPosition = const $CopyWithPlaceholder(),
Object? tabIntentOpenSetting = const $CopyWithPlaceholder(),
Object? autoHideTabBar = const $CopyWithPlaceholder(),
Object? tabBarSwipeAction = const $CopyWithPlaceholder(),
@@ -506,6 +514,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
? _value.storedDefaultCreateTabType
// ignore: cast_nullable_to_non_nullable
: storedDefaultCreateTabType as TabType,
newTabPosition:
newTabPosition == const $CopyWithPlaceholder() ||
newTabPosition == null
? _value.newTabPosition
// ignore: cast_nullable_to_non_nullable
: newTabPosition as NewTabPosition,
tabIntentOpenSetting:
tabIntentOpenSetting == const $CopyWithPlaceholder() ||
tabIntentOpenSetting == null
@@ -733,6 +747,10 @@ GeneralSettings _$GeneralSettingsFromJson(
_$TabTypeEnumMap,
json['defaultCreateTabType'],
),
newTabPosition: $enumDecodeNullable(
_$NewTabPositionEnumMap,
json['newTabPosition'],
),
tabIntentOpenSetting: $enumDecodeNullable(
_$TabIntentOpenSettingEnumMap,
json['tabIntentOpenSetting'],
@@ -818,6 +836,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(
'showIsolatedTabUi': instance.showIsolatedTabUi,
'defaultCreateTabType':
_$TabTypeEnumMap[instance.storedDefaultCreateTabType]!,
'newTabPosition': _$NewTabPositionEnumMap[instance.newTabPosition]!,
'tabIntentOpenSetting':
_$TabIntentOpenSettingEnumMap[instance.tabIntentOpenSetting]!,
'autoHideTabBar': instance.autoHideTabBar,
@@ -885,6 +904,11 @@ const _$TabTypeEnumMap = {
TabType.isolated: 'isolated',
};
const _$NewTabPositionEnumMap = {
NewTabPosition.first: 'first',
NewTabPosition.end: 'end',
};
const _$TabIntentOpenSettingEnumMap = {
TabIntentOpenSetting.regular: 'regular',
TabIntentOpenSetting.private: 'private',
@@ -100,6 +100,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
DriftSqlType.string,
db.typeMapping,
),
'newTabPosition': settings['newTabPosition']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
'tabIntentOpenSetting': settings['tabIntentOpenSetting']?.readAs(
DriftSqlType.string,
db.typeMapping,
@@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider
}
String _$generalSettingsRepositoryHash() =>
r'44aaf9f3c8c6e1a8c6569ed59b76fa0360528578';
r'04764c3b9a71aec077ab2b7894d117f993678b2e';
abstract class _$GeneralSettingsRepository
extends $StreamNotifier<GeneralSettings> {