support reordering with tab pinning active

This commit is contained in:
Fabian Freund
2026-05-02 08:51:31 +02:00
parent de5f8138ee
commit 9ff9b83373
4 changed files with 60 additions and 13 deletions
@@ -60,13 +60,7 @@ bool canManualTabReorder(Ref ref) {
).select((value) => (value.value?.query ?? '').isNotEmpty), ).select((value) => (value.value?.query ?? '').isNotEmpty),
); );
// sortPinnedFirst partitions the rendered list into pinned/unpinned return !filterOptions.hasActiveFilter && !hasActiveSearch;
// 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;
} }
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
@@ -38,10 +38,12 @@ TabViewReorderResult? buildTabViewReorderResult({
required List<TabViewItem> visibleItems, required List<TabViewItem> visibleItems,
required List<TabsWithRootAndDepthResult> treeRows, required List<TabsWithRootAndDepthResult> treeRows,
required Set<String> collapsedGroups, required Set<String> collapsedGroups,
required Set<String> pinnedTabIds,
required int oldIndex, required int oldIndex,
required int newIndex, required int newIndex,
required TabListDirection tabListDirection, required TabListDirection tabListDirection,
required bool hierarchical, required bool hierarchical,
required bool sortPinnedFirst,
}) { }) {
if (oldIndex < 0 || oldIndex >= visibleItems.length) { if (oldIndex < 0 || oldIndex >= visibleItems.length) {
logger.t( logger.t(
@@ -65,11 +67,14 @@ TabViewReorderResult? buildTabViewReorderResult({
final ordered = reordered.map((item) => item.tabId).toList(); final ordered = reordered.map((item) => item.tabId).toList();
return _resultFromOrderedIds( return _resultFromOrderedIds(
movingTabIds: [movingItem.tabId], movingTabIds: [movingItem.tabId],
orderedTabIds: tabListDirection == TabListDirection.newestFirst orderedTabIds: _orderedIdsForStorageAnchors(
// Rendering flips root group order for newest-first; convert the ordered,
// display order back to storage order before choosing anchors. tabListDirection: tabListDirection,
? ordered.reversed.toList() pinnedTabIds: pinnedTabIds,
: ordered, parentById: const {},
movingPartitionRootId: movingItem.tabId,
sortPinnedFirst: sortPinnedFirst,
),
); );
} }
@@ -195,10 +200,44 @@ TabViewReorderResult? buildTabViewReorderResult({
return _resultFromOrderedIds( return _resultFromOrderedIds(
movingTabIds: moveBlock, movingTabIds: moveBlock,
orderedTabIds: orderedTabIds, orderedTabIds: _orderedIdsForStorageAnchors(
orderedTabIds,
tabListDirection: tabListDirection,
pinnedTabIds: pinnedTabIds,
parentById: parentById,
movingPartitionRootId: _rootIdFor(movingItem.tabId, parentById),
sortPinnedFirst: sortPinnedFirst,
),
); );
} }
List<String> _orderedIdsForStorageAnchors(
List<String> orderedTabIds, {
required TabListDirection tabListDirection,
required Set<String> pinnedTabIds,
required Map<String, String?> 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({ TabViewReorderResult? _resultFromOrderedIds({
required List<String> movingTabIds, required List<String> movingTabIds,
required List<String> orderedTabIds, required List<String> orderedTabIds,
@@ -184,6 +184,11 @@ class _TabGridView extends HookConsumerWidget {
final tabListDirection = ref.watch( final tabListDirection = ref.watch(
generalSettingsWithDefaultsProvider.select((s) => s.tabListDirection), generalSettingsWithDefaultsProvider.select((s) => s.tabListDirection),
); );
final pinnedTabIds = ref.watch(
watchPinnedTabIdsProvider.select(
(value) => value.value ?? const <String>{},
),
);
final collapsedGroups = ref.watch(collapsedGroupsProvider); final collapsedGroups = ref.watch(collapsedGroupsProvider);
final List<TabsWithRootAndDepthResult> treeRows = showHierarchicalTabs final List<TabsWithRootAndDepthResult> treeRows = showHierarchicalTabs
? ref.watch( ? ref.watch(
@@ -384,10 +389,12 @@ class _TabGridView extends HookConsumerWidget {
visibleItems: primaryRows, visibleItems: primaryRows,
treeRows: treeRows, treeRows: treeRows,
collapsedGroups: collapsedGroups, collapsedGroups: collapsedGroups,
pinnedTabIds: pinnedTabIds,
oldIndex: oldIndex, oldIndex: oldIndex,
newIndex: newIndex, newIndex: newIndex,
tabListDirection: tabListDirection, tabListDirection: tabListDirection,
hierarchical: showHierarchicalTabs && !hasActiveSearch, hierarchical: showHierarchicalTabs && !hasActiveSearch,
sortPinnedFirst: filterOptions.sortPinnedFirst,
); );
if (result == null) return; if (result == null) return;
@@ -241,6 +241,11 @@ class _TabListView extends HookConsumerWidget {
final canManualReorder = ref.watch(canManualTabReorderProvider); final canManualReorder = ref.watch(canManualTabReorderProvider);
final reorderEnabled = tabsReorderable && canManualReorder; final reorderEnabled = tabsReorderable && canManualReorder;
final filterOptions = ref.watch(tabViewFilterControllerProvider); final filterOptions = ref.watch(tabViewFilterControllerProvider);
final pinnedTabIds = ref.watch(
watchPinnedTabIdsProvider.select(
(value) => value.value ?? const <String>{},
),
);
final showHierarchicalTabs = filterOptions.showHierarchicalTabs; final showHierarchicalTabs = filterOptions.showHierarchicalTabs;
final tabListDirection = ref.watch( final tabListDirection = ref.watch(
generalSettingsWithDefaultsProvider.select((s) => s.tabListDirection), generalSettingsWithDefaultsProvider.select((s) => s.tabListDirection),
@@ -447,10 +452,12 @@ class _TabListView extends HookConsumerWidget {
visibleItems: primaryRows, visibleItems: primaryRows,
treeRows: treeRows, treeRows: treeRows,
collapsedGroups: collapsedGroups, collapsedGroups: collapsedGroups,
pinnedTabIds: pinnedTabIds,
oldIndex: oldIndex, oldIndex: oldIndex,
newIndex: newIndex, newIndex: newIndex,
tabListDirection: tabListDirection, tabListDirection: tabListDirection,
hierarchical: showHierarchicalTabs && !hasActiveSearch, hierarchical: showHierarchicalTabs && !hasActiveSearch,
sortPinnedFirst: filterOptions.sortPinnedFirst,
); );
if (result == null) return; if (result == null) return;