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),
);
// 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)
@@ -38,10 +38,12 @@ TabViewReorderResult? buildTabViewReorderResult({
required List<TabViewItem> visibleItems,
required List<TabsWithRootAndDepthResult> treeRows,
required Set<String> collapsedGroups,
required Set<String> 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<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({
required List<String> movingTabIds,
required List<String> orderedTabIds,
@@ -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 <String>{},
),
);
final collapsedGroups = ref.watch(collapsedGroupsProvider);
final List<TabsWithRootAndDepthResult> 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;
@@ -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 <String>{},
),
);
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;