diff --git a/app/lib/features/geckoview/domain/providers/tab_session.g.dart b/app/lib/features/geckoview/domain/providers/tab_session.g.dart index 9d5ef887..fd19554f 100644 --- a/app/lib/features/geckoview/domain/providers/tab_session.g.dart +++ b/app/lib/features/geckoview/domain/providers/tab_session.g.dart @@ -57,7 +57,7 @@ final class TabSessionProvider extends $NotifierProvider { } } -String _$tabSessionHash() => r'be44598ad023c2fb23e48d3fa4bf805c4640c599'; +String _$tabSessionHash() => r'636f0940425d689ff98fabfc61687de828a8eeb9'; final class TabSessionFamily extends $Family with $ClassFamilyOverride { diff --git a/app/lib/features/geckoview/domain/repositories/tab.dart b/app/lib/features/geckoview/domain/repositories/tab.dart index b21ab2e7..5d278785 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.dart @@ -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 _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), ); }, diff --git a/app/lib/features/geckoview/domain/repositories/tab.g.dart b/app/lib/features/geckoview/domain/repositories/tab.g.dart index 63a6ee93..bad5cba8 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.g.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.g.dart @@ -41,7 +41,7 @@ final class TabRepositoryProvider } } -String _$tabRepositoryHash() => r'1c866a1f5b1872ba07ebac82ff92455a7af316a3'; +String _$tabRepositoryHash() => r'4dd31901b4d02ca075d3dcec3822f5a2832d164c'; abstract class _$TabRepository extends $Notifier { void build(); diff --git a/app/lib/features/geckoview/features/browser/domain/providers.g.dart b/app/lib/features/geckoview/features/browser/domain/providers.g.dart index 2788e92d..30bb3a20 100644 --- a/app/lib/features/geckoview/features/browser/domain/providers.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/providers.g.dart @@ -601,7 +601,7 @@ final class QuickTabSwitcherTabStatesProvider } String _$quickTabSwitcherTabStatesHash() => - r'2627bcd25718472f0c8057e836d76bdaa7317c1c'; + r'd83ea34d366238681baaad0b36699ee0742edc3d'; final class QuickTabSwitcherTabStatesFamily extends $Family with diff --git a/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart b/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart index 8070535a..b8be466d 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart +++ b/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart @@ -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 deletedIsolationContextIds; @@ -139,6 +140,7 @@ class TabDao extends DatabaseAccessor with $TabDaoMixin { Future _generateOrderKey({ required Value parentId, required Value containerId, + required NewTabPosition newTabPosition, }) async { if (parentId.value.isNotEmpty) { return await db.containerDao @@ -148,15 +150,23 @@ class TabDao extends DatabaseAccessor 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 upsertTabTransactional( Future Function() createTab, { required Value parentId, + NewTabPosition newTabPosition = NewTabPosition.first, Value containerId = const Value.absent(), Value orderKey = const Value.absent(), Value url = const Value.absent(), @@ -167,7 +177,11 @@ class TabDao extends DatabaseAccessor 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 persistedTabMode = tabMode.present ? Value(tabMode.value.toDbValue()) : const Value.absent(); @@ -211,6 +225,7 @@ class TabDao extends DatabaseAccessor with $TabDaoMixin { String tabId, { required TabSource source, required Value parentId, + NewTabPosition newTabPosition = NewTabPosition.first, Value containerId = const Value.absent(), Value orderKey = const Value.absent(), Value url = const Value.absent(), @@ -220,7 +235,11 @@ class TabDao extends DatabaseAccessor 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 persistedTabMode = tabMode.present ? Value(tabMode.value.toDbValue()) : const Value.absent(); diff --git a/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart b/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart index 442ba230..4456942a 100644 --- a/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart +++ b/app/lib/features/settings/presentation/screens/tabs_behavior_settings.dart @@ -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(); diff --git a/app/lib/features/user/data/models/general_settings.dart b/app/lib/features/user/data/models/general_settings.dart index 8c95b1ac..b33e89ea 100644 --- a/app/lib/features/user/data/models/general_settings.dart +++ b/app/lib/features/user/data/models/general_settings.dart @@ -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, diff --git a/app/lib/features/user/data/models/general_settings.g.dart b/app/lib/features/user/data/models/general_settings.g.dart index 19abce8a..0c11075e 100644 --- a/app/lib/features/user/data/models/general_settings.g.dart +++ b/app/lib/features/user/data/models/general_settings.g.dart @@ -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 _$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', diff --git a/app/lib/features/user/domain/repositories/general_settings.dart b/app/lib/features/user/domain/repositories/general_settings.dart index b34e4213..a321b501 100644 --- a/app/lib/features/user/domain/repositories/general_settings.dart +++ b/app/lib/features/user/domain/repositories/general_settings.dart @@ -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, diff --git a/app/lib/features/user/domain/repositories/general_settings.g.dart b/app/lib/features/user/domain/repositories/general_settings.g.dart index a761542e..55a9ebce 100644 --- a/app/lib/features/user/domain/repositories/general_settings.g.dart +++ b/app/lib/features/user/domain/repositories/general_settings.g.dart @@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider } String _$generalSettingsRepositoryHash() => - r'44aaf9f3c8c6e1a8c6569ed59b76fa0360528578'; + r'04764c3b9a71aec077ab2b7894d117f993678b2e'; abstract class _$GeneralSettingsRepository extends $StreamNotifier {