diff --git a/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart b/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart index 073b9811..6725f43d 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart @@ -60,13 +60,7 @@ bool canManualTabReorder(Ref ref) { ).select((value) => (value.value?.query ?? '').isNotEmpty), ); - // sortPinnedFirst partitions the rendered list into pinned/unpinned - // sections that don't reflect storage order_key ordering. A drag would - // compute anchors across the partition boundary and silently snap the - // moved tab into the wrong section once the stream re-renders. - return !filterOptions.hasActiveFilter && - !hasActiveSearch && - !filterOptions.sortPinnedFirst; + return !filterOptions.hasActiveFilter && !hasActiveSearch; } @Riverpod(keepAlive: true) diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/utils/tab_view_reorder.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/utils/tab_view_reorder.dart index 54f68383..8958c753 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/utils/tab_view_reorder.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/utils/tab_view_reorder.dart @@ -38,10 +38,12 @@ TabViewReorderResult? buildTabViewReorderResult({ required List visibleItems, required List treeRows, required Set collapsedGroups, + required Set pinnedTabIds, required int oldIndex, required int newIndex, required TabListDirection tabListDirection, required bool hierarchical, + required bool sortPinnedFirst, }) { if (oldIndex < 0 || oldIndex >= visibleItems.length) { logger.t( @@ -65,11 +67,14 @@ TabViewReorderResult? buildTabViewReorderResult({ final ordered = reordered.map((item) => item.tabId).toList(); return _resultFromOrderedIds( movingTabIds: [movingItem.tabId], - orderedTabIds: tabListDirection == TabListDirection.newestFirst - // Rendering flips root group order for newest-first; convert the - // display order back to storage order before choosing anchors. - ? ordered.reversed.toList() - : ordered, + orderedTabIds: _orderedIdsForStorageAnchors( + ordered, + tabListDirection: tabListDirection, + pinnedTabIds: pinnedTabIds, + parentById: const {}, + movingPartitionRootId: movingItem.tabId, + sortPinnedFirst: sortPinnedFirst, + ), ); } @@ -195,10 +200,44 @@ TabViewReorderResult? buildTabViewReorderResult({ return _resultFromOrderedIds( movingTabIds: moveBlock, - orderedTabIds: orderedTabIds, + orderedTabIds: _orderedIdsForStorageAnchors( + orderedTabIds, + tabListDirection: tabListDirection, + pinnedTabIds: pinnedTabIds, + parentById: parentById, + movingPartitionRootId: _rootIdFor(movingItem.tabId, parentById), + sortPinnedFirst: sortPinnedFirst, + ), ); } +List _orderedIdsForStorageAnchors( + List orderedTabIds, { + required TabListDirection tabListDirection, + required Set pinnedTabIds, + required Map parentById, + required String movingPartitionRootId, + required bool sortPinnedFirst, +}) { + var storageOrderedIds = tabListDirection == TabListDirection.newestFirst + // Rendering flips root group order for newest-first; convert the + // display order back to storage order before choosing anchors. + ? orderedTabIds.reversed.toList() + : orderedTabIds; + + if (!sortPinnedFirst || pinnedTabIds.isEmpty) { + return storageOrderedIds; + } + + // Pinned-first is a render-only partition, so choose DB anchors only from + // the moving tab's own partition to avoid snapping across the boundary. + final movingPinned = pinnedTabIds.contains(movingPartitionRootId); + return storageOrderedIds.where((tabId) { + final rootId = _rootIdFor(tabId, parentById); + return pinnedTabIds.contains(rootId) == movingPinned; + }).toList(); +} + TabViewReorderResult? _resultFromOrderedIds({ required List movingTabIds, required List orderedTabIds, diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart index 701d324a..42d591be 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart @@ -184,6 +184,11 @@ class _TabGridView extends HookConsumerWidget { final tabListDirection = ref.watch( generalSettingsWithDefaultsProvider.select((s) => s.tabListDirection), ); + final pinnedTabIds = ref.watch( + watchPinnedTabIdsProvider.select( + (value) => value.value ?? const {}, + ), + ); final collapsedGroups = ref.watch(collapsedGroupsProvider); final List treeRows = showHierarchicalTabs ? ref.watch( @@ -384,10 +389,12 @@ class _TabGridView extends HookConsumerWidget { visibleItems: primaryRows, treeRows: treeRows, collapsedGroups: collapsedGroups, + pinnedTabIds: pinnedTabIds, oldIndex: oldIndex, newIndex: newIndex, tabListDirection: tabListDirection, hierarchical: showHierarchicalTabs && !hasActiveSearch, + sortPinnedFirst: filterOptions.sortPinnedFirst, ); if (result == null) return; diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart index fdf32ef5..3158923a 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart @@ -241,6 +241,11 @@ class _TabListView extends HookConsumerWidget { final canManualReorder = ref.watch(canManualTabReorderProvider); final reorderEnabled = tabsReorderable && canManualReorder; final filterOptions = ref.watch(tabViewFilterControllerProvider); + final pinnedTabIds = ref.watch( + watchPinnedTabIdsProvider.select( + (value) => value.value ?? const {}, + ), + ); final showHierarchicalTabs = filterOptions.showHierarchicalTabs; final tabListDirection = ref.watch( generalSettingsWithDefaultsProvider.select((s) => s.tabListDirection), @@ -447,10 +452,12 @@ class _TabListView extends HookConsumerWidget { visibleItems: primaryRows, treeRows: treeRows, collapsedGroups: collapsedGroups, + pinnedTabIds: pinnedTabIds, oldIndex: oldIndex, newIndex: newIndex, tabListDirection: tabListDirection, hierarchical: showHierarchicalTabs && !hasActiveSearch, + sortPinnedFirst: filterOptions.sortPinnedFirst, ); if (result == null) return;