respect pinned tabs and direction when sorting
This commit is contained in:
@@ -401,7 +401,7 @@ class TabRepository extends _$TabRepository {
|
||||
// anchored to the bar's mental model.
|
||||
final newestFirst =
|
||||
ref.read(generalSettingsWithDefaultsProvider).tabBarDirection ==
|
||||
TabBarDirection.newestFirst;
|
||||
TabDirection.newestFirst;
|
||||
final TabDatabase tabDatabase = ref.read(tabDatabaseProvider);
|
||||
final definitions = tabDatabase.definitionsDrift;
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ final class TabRepositoryProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$tabRepositoryHash() => r'84ecfed72f17c367fc7bf0100f6367d4e7609596';
|
||||
String _$tabRepositoryHash() => r'a68941d373f348201e3d8b1cab26343acca3852f';
|
||||
|
||||
abstract class _$TabRepository extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
@@ -227,11 +227,74 @@ selectedContainerTabStatesWithContainer(Ref ref) {
|
||||
.watch(groupedTabListItemsProvider(containerId: filter.containerId))
|
||||
.value;
|
||||
|
||||
final tabListDirection = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select((s) => s.tabListDirection),
|
||||
);
|
||||
final tabBarDirection = ref.watch(
|
||||
generalSettingsWithDefaultsProvider.select((s) => s.tabBarDirection),
|
||||
);
|
||||
final pinnedTabIds = ref.watch(
|
||||
watchPinnedTabIdsProvider.select(
|
||||
(value) => value.value ?? const <String>{},
|
||||
),
|
||||
);
|
||||
final sortPinnedFirst = ref.watch(
|
||||
tabViewFilterControllerProvider.select((v) => v.sortPinnedFirst),
|
||||
);
|
||||
|
||||
// Build an order map from groupedItems. When the tab bar direction differs
|
||||
// from the tab list direction, reverse root group order within each
|
||||
// pinned/unpinned partition so the bar respects the user's direction
|
||||
// preference while keeping parent–child groups intact.
|
||||
List<TabListItemEntity> orderedItems = groupedItems;
|
||||
if (tabBarDirection != tabListDirection) {
|
||||
// Split groupedItems into root groups (each: root + its children).
|
||||
final rootGroups = <(bool isPinned, List<TabListItemEntity>)>[];
|
||||
var currentGroup = <TabListItemEntity>[];
|
||||
for (final item in groupedItems) {
|
||||
final isRoot =
|
||||
item is TabListStandaloneItem || item is TabListParentGroup;
|
||||
if (isRoot && currentGroup.isNotEmpty) {
|
||||
final isPinned =
|
||||
sortPinnedFirst && pinnedTabIds.contains(currentGroup.first.tabId);
|
||||
rootGroups.add((isPinned, currentGroup));
|
||||
currentGroup = [];
|
||||
}
|
||||
currentGroup.add(item);
|
||||
}
|
||||
if (currentGroup.isNotEmpty) {
|
||||
final isPinned =
|
||||
sortPinnedFirst && pinnedTabIds.contains(currentGroup.first.tabId);
|
||||
rootGroups.add((isPinned, currentGroup));
|
||||
}
|
||||
|
||||
// Reverse root groups within each partition (pinned / unpinned).
|
||||
final result = <TabListItemEntity>[];
|
||||
if (sortPinnedFirst) {
|
||||
final pinned = rootGroups
|
||||
.where((g) => g.$1)
|
||||
.toList()
|
||||
.reversed
|
||||
.expand((g) => g.$2);
|
||||
final unpinned = rootGroups
|
||||
.where((g) => !g.$1)
|
||||
.toList()
|
||||
.reversed
|
||||
.expand((g) => g.$2);
|
||||
result
|
||||
..addAll(pinned)
|
||||
..addAll(unpinned);
|
||||
} else {
|
||||
result.addAll(rootGroups.reversed.expand((g) => g.$2));
|
||||
}
|
||||
orderedItems = result;
|
||||
}
|
||||
|
||||
final groupedOrder = {
|
||||
for (var i = 0; i < groupedItems.length; i++) groupedItems[i].tabId: i,
|
||||
for (var i = 0; i < orderedItems.length; i++) orderedItems[i].tabId: i,
|
||||
};
|
||||
|
||||
final items = [
|
||||
var items = [
|
||||
for (final tabEntity in sortedTabs)
|
||||
if (tabStates.containsKey(tabEntity.tabId))
|
||||
(
|
||||
@@ -243,11 +306,21 @@ selectedContainerTabStatesWithContainer(Ref ref) {
|
||||
];
|
||||
|
||||
items.sort((a, b) {
|
||||
final aIndex = groupedOrder[a.$1.id] ?? groupedItems.length;
|
||||
final bIndex = groupedOrder[b.$1.id] ?? groupedItems.length;
|
||||
final aIndex = groupedOrder[a.$1.id] ?? orderedItems.length;
|
||||
final bIndex = groupedOrder[b.$1.id] ?? orderedItems.length;
|
||||
return aIndex.compareTo(bIndex);
|
||||
});
|
||||
|
||||
// Flat pinned-first: move all pinned tabs before unpinned regardless of
|
||||
// hierarchy, preserving relative order within each partition.
|
||||
if (sortPinnedFirst && pinnedTabIds.isNotEmpty) {
|
||||
final pinned = items.where((i) => pinnedTabIds.contains(i.$1.id)).toList();
|
||||
final unpinned = items
|
||||
.where((i) => !pinnedTabIds.contains(i.$1.id))
|
||||
.toList();
|
||||
items = [...pinned, ...unpinned];
|
||||
}
|
||||
|
||||
return EquatableValue(items);
|
||||
}
|
||||
|
||||
@@ -277,12 +350,35 @@ EquatableValue<List<TabStateWithContainer>> quickTabSwitcherTabStates(
|
||||
generalSettingsWithDefaultsProvider.select((s) => s.tabBarDirection),
|
||||
);
|
||||
|
||||
final pinnedTabIds = ref.watch(
|
||||
watchPinnedTabIdsProvider.select(
|
||||
(value) => value.value ?? const <String>{},
|
||||
),
|
||||
);
|
||||
final sortPinnedFirst = ref.watch(
|
||||
tabViewFilterControllerProvider.select((v) => v.sortPinnedFirst),
|
||||
);
|
||||
|
||||
return EquatableValue(switch (effectiveMode) {
|
||||
QuickTabSwitcherMode.lastUsedTabs => () {
|
||||
final filtered = tabStates
|
||||
.where((state) => state.$1.id != selectedTabId)
|
||||
.toList();
|
||||
return tabBarDirection == TabBarDirection.oldestFirst
|
||||
if (sortPinnedFirst && pinnedTabIds.isNotEmpty) {
|
||||
final pinned = filtered
|
||||
.where((s) => pinnedTabIds.contains(s.$1.id))
|
||||
.toList();
|
||||
final unpinned = filtered
|
||||
.where((s) => !pinnedTabIds.contains(s.$1.id))
|
||||
.toList();
|
||||
// Each partition is MRU-first from fifoTabStates. For oldestFirst,
|
||||
// reverse each partition independently so pinned stays on top.
|
||||
if (tabBarDirection == TabDirection.oldestFirst) {
|
||||
return [...pinned.reversed, ...unpinned.reversed];
|
||||
}
|
||||
return [...pinned, ...unpinned];
|
||||
}
|
||||
return tabBarDirection == TabDirection.oldestFirst
|
||||
? filtered.reversed.toList()
|
||||
: filtered;
|
||||
}(),
|
||||
@@ -605,7 +701,7 @@ EquatableValue<List<TabEntity>> seamlessFilteredTabEntities(
|
||||
// database order (ascending order_key) is oldest-first. Flip to
|
||||
// newest-first when the user selects that direction.
|
||||
List<TabEntity> applyDirection(List<TabEntity> entities) {
|
||||
return tabListDirection == TabListDirection.newestFirst
|
||||
return tabListDirection == TabDirection.newestFirst
|
||||
? entities.reversed.toList()
|
||||
: entities;
|
||||
}
|
||||
@@ -697,7 +793,7 @@ EquatableValue<List<TabPreview>> filteredTabPreviews(
|
||||
|
||||
/// Grouped flat-list rendering for the list and grid views.
|
||||
///
|
||||
/// Parent rows always render before their descendants. [TabListDirection]
|
||||
/// Parent rows always render before their descendants. [TabDirection]
|
||||
/// applies both to root group ordering and to sibling ordering below each
|
||||
/// parent, so parent-child pairs stay together while child order still follows
|
||||
/// the configured direction.
|
||||
@@ -867,7 +963,7 @@ EquatableValue<List<TabListItemEntity>> groupedTabListItems(
|
||||
// order_key ASC; newest-first reverses the group list. Pinned and
|
||||
// unpinned partitions are reversed independently so pinned-first stays
|
||||
// intact while still flipping the relative order within each partition.
|
||||
if (sortField == null && tabListDirection == TabListDirection.newestFirst) {
|
||||
if (sortField == null && tabListDirection == TabDirection.newestFirst) {
|
||||
if (filterOptions.sortPinnedFirst) {
|
||||
final pinned = groupRecords.where((g) => g.isPinned).toList()
|
||||
..sort((a, b) => b.rootOrderKey.compareTo(a.rootOrderKey));
|
||||
@@ -931,17 +1027,36 @@ EquatableValue<List<TabListItemEntity>> groupedTabListItems(
|
||||
.add(member);
|
||||
}
|
||||
|
||||
// Children are always sorted by storage `order_key` (optionally reversed
|
||||
// for newest-first) — even when an explicit `sortField` (title/url/date)
|
||||
// is active. The explicit sort applies only to root groups; descendants
|
||||
// remain in insertion order so a parent's children stay grouped in the
|
||||
// order they were opened rather than alphabetically interleaving across
|
||||
// siblings.
|
||||
// Children are sorted by storage `order_key` (optionally reversed for
|
||||
// newest-first) — even when an explicit `sortField` is active. The
|
||||
// explicit sort applies only to root groups; descendants remain in
|
||||
// insertion order. When `sortPinnedFirst` is true, pinned children are
|
||||
// sorted before unpinned siblings within each parent group.
|
||||
for (final children in childrenByVisibleParent.values) {
|
||||
children.sort((a, b) {
|
||||
final cmp = a.row.orderKey.compareTo(b.row.orderKey);
|
||||
return tabListDirection == TabListDirection.newestFirst ? -cmp : cmp;
|
||||
});
|
||||
if (filterOptions.sortPinnedFirst) {
|
||||
final pinned = children
|
||||
.where((c) => pinnedTabIds.contains(c.row.id))
|
||||
.toList();
|
||||
final unpinned = children
|
||||
.where((c) => !pinnedTabIds.contains(c.row.id))
|
||||
.toList();
|
||||
final cmp = (_GroupedRow a, _GroupedRow b) =>
|
||||
a.row.orderKey.compareTo(b.row.orderKey);
|
||||
final directionCmp = tabListDirection == TabDirection.newestFirst
|
||||
? (_GroupedRow a, _GroupedRow b) => -cmp(a, b)
|
||||
: cmp;
|
||||
pinned.sort(directionCmp);
|
||||
unpinned.sort(directionCmp);
|
||||
children
|
||||
..clear()
|
||||
..addAll(pinned)
|
||||
..addAll(unpinned);
|
||||
} else {
|
||||
children.sort((a, b) {
|
||||
final cmp = a.row.orderKey.compareTo(b.row.orderKey);
|
||||
return tabListDirection == TabDirection.newestFirst ? -cmp : cmp;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void addChildren(String parentId) {
|
||||
|
||||
@@ -49,7 +49,7 @@ final class CanManualTabReorderProvider
|
||||
}
|
||||
|
||||
String _$canManualTabReorderHash() =>
|
||||
r'598bc67a750f45893ba555b9b7866499b1808896';
|
||||
r'ba5d961933464b6e005d7945802908a9a4ae034b';
|
||||
|
||||
@ProviderFor(SelectedBangTrigger)
|
||||
final selectedBangTriggerProvider = SelectedBangTriggerFamily._();
|
||||
@@ -529,7 +529,7 @@ final class SelectedContainerTabStatesWithContainerProvider
|
||||
}
|
||||
|
||||
String _$selectedContainerTabStatesWithContainerHash() =>
|
||||
r'6b630cdfc589af6961d4c96c90d09bd107560f15';
|
||||
r'e6fd0e1e0ca4cfb0a0a46e8ff4e63d41032bfad9';
|
||||
|
||||
@ProviderFor(quickTabSwitcherTabStates)
|
||||
final quickTabSwitcherTabStatesProvider = QuickTabSwitcherTabStatesFamily._();
|
||||
@@ -601,7 +601,7 @@ final class QuickTabSwitcherTabStatesProvider
|
||||
}
|
||||
|
||||
String _$quickTabSwitcherTabStatesHash() =>
|
||||
r'32e928b4596ef2388f8c0e4cfdcf8f3563a1e24a';
|
||||
r'3a1a195f6b2fc4cc54e689fab7bacaf4e6422196';
|
||||
|
||||
final class QuickTabSwitcherTabStatesFamily extends $Family
|
||||
with
|
||||
@@ -962,7 +962,7 @@ final class SeamlessFilteredTabEntitiesProvider
|
||||
}
|
||||
|
||||
String _$seamlessFilteredTabEntitiesHash() =>
|
||||
r'79abca3ad2753af4482c0e3bcfa317a6f336ca17';
|
||||
r'83113c04be7932c60c7c97b70595fde0145f8040';
|
||||
|
||||
final class SeamlessFilteredTabEntitiesFamily extends $Family
|
||||
with
|
||||
@@ -1097,7 +1097,7 @@ final class FilteredTabPreviewsFamily extends $Family
|
||||
|
||||
/// Grouped flat-list rendering for the list and grid views.
|
||||
///
|
||||
/// Parent rows always render before their descendants. [TabListDirection]
|
||||
/// Parent rows always render before their descendants. [TabDirection]
|
||||
/// applies both to root group ordering and to sibling ordering below each
|
||||
/// parent, so parent-child pairs stay together while child order still follows
|
||||
/// the configured direction.
|
||||
@@ -1109,7 +1109,7 @@ final groupedTabListItemsProvider = GroupedTabListItemsFamily._();
|
||||
|
||||
/// Grouped flat-list rendering for the list and grid views.
|
||||
///
|
||||
/// Parent rows always render before their descendants. [TabListDirection]
|
||||
/// Parent rows always render before their descendants. [TabDirection]
|
||||
/// applies both to root group ordering and to sibling ordering below each
|
||||
/// parent, so parent-child pairs stay together while child order still follows
|
||||
/// the configured direction.
|
||||
@@ -1126,7 +1126,7 @@ final class GroupedTabListItemsProvider
|
||||
with $Provider<EquatableValue<List<TabListItemEntity>>> {
|
||||
/// Grouped flat-list rendering for the list and grid views.
|
||||
///
|
||||
/// Parent rows always render before their descendants. [TabListDirection]
|
||||
/// Parent rows always render before their descendants. [TabDirection]
|
||||
/// applies both to root group ordering and to sibling ordering below each
|
||||
/// parent, so parent-child pairs stay together while child order still follows
|
||||
/// the configured direction.
|
||||
@@ -1186,11 +1186,11 @@ final class GroupedTabListItemsProvider
|
||||
}
|
||||
|
||||
String _$groupedTabListItemsHash() =>
|
||||
r'48c7c971d95934afb48aa19b7400e208ee2f10e4';
|
||||
r'15106bfa42146270245c7178928a834237042c1c';
|
||||
|
||||
/// Grouped flat-list rendering for the list and grid views.
|
||||
///
|
||||
/// Parent rows always render before their descendants. [TabListDirection]
|
||||
/// Parent rows always render before their descendants. [TabDirection]
|
||||
/// applies both to root group ordering and to sibling ordering below each
|
||||
/// parent, so parent-child pairs stay together while child order still follows
|
||||
/// the configured direction.
|
||||
@@ -1214,7 +1214,7 @@ final class GroupedTabListItemsFamily extends $Family
|
||||
|
||||
/// Grouped flat-list rendering for the list and grid views.
|
||||
///
|
||||
/// Parent rows always render before their descendants. [TabListDirection]
|
||||
/// Parent rows always render before their descendants. [TabDirection]
|
||||
/// applies both to root group ordering and to sibling ordering below each
|
||||
/// parent, so parent-child pairs stay together while child order still follows
|
||||
/// the configured direction.
|
||||
|
||||
+5
-5
@@ -41,7 +41,7 @@ TabViewReorderResult? buildTabViewReorderResult({
|
||||
required Set<String> pinnedTabIds,
|
||||
required int oldIndex,
|
||||
required int newIndex,
|
||||
required TabListDirection tabListDirection,
|
||||
required TabDirection tabListDirection,
|
||||
required bool hierarchical,
|
||||
required bool sortPinnedFirst,
|
||||
}) {
|
||||
@@ -178,7 +178,7 @@ TabViewReorderResult? buildTabViewReorderResult({
|
||||
// order before picking anchors so genBetween receives prev.orderKey <
|
||||
// next.orderKey. Block contents (e.g. the moving subtree from _subtreeIds)
|
||||
// are already storage-ordered internally, so we reverse blocks as units.
|
||||
if (tabListDirection == TabListDirection.newestFirst) {
|
||||
if (tabListDirection == TabDirection.newestFirst) {
|
||||
for (final blocks in blocksByRoot.values) {
|
||||
if (blocks.length > 1) {
|
||||
final reversedTail = blocks.sublist(1).reversed.toList();
|
||||
@@ -189,7 +189,7 @@ TabViewReorderResult? buildTabViewReorderResult({
|
||||
}
|
||||
}
|
||||
|
||||
final orderedRootIds = tabListDirection == TabListDirection.newestFirst
|
||||
final orderedRootIds = tabListDirection == TabDirection.newestFirst
|
||||
? rootOrder.reversed
|
||||
: rootOrder;
|
||||
|
||||
@@ -213,13 +213,13 @@ TabViewReorderResult? buildTabViewReorderResult({
|
||||
|
||||
List<String> _orderedIdsForStorageAnchors(
|
||||
List<String> orderedTabIds, {
|
||||
required TabListDirection tabListDirection,
|
||||
required TabDirection tabListDirection,
|
||||
required Set<String> pinnedTabIds,
|
||||
required Map<String, String?> parentById,
|
||||
required String movingPartitionRootId,
|
||||
required bool sortPinnedFirst,
|
||||
}) {
|
||||
var storageOrderedIds = tabListDirection == TabListDirection.newestFirst
|
||||
var storageOrderedIds = tabListDirection == TabDirection.newestFirst
|
||||
// Rendering flips root group order for newest-first; convert the
|
||||
// display order back to storage order before choosing anchors.
|
||||
? orderedTabIds.reversed.toList()
|
||||
|
||||
+10
-1
@@ -200,7 +200,7 @@ class _TabGridView extends HookConsumerWidget {
|
||||
).select((value) => (value.value?.query ?? '').isNotEmpty),
|
||||
);
|
||||
|
||||
final List<TabViewItem> primaryRows;
|
||||
List<TabViewItem> primaryRows;
|
||||
if (hasActiveSearch) {
|
||||
final flat = ref.watch(
|
||||
seamlessFilteredTabEntitiesProvider(
|
||||
@@ -242,6 +242,15 @@ class _TabGridView extends HookConsumerWidget {
|
||||
: TabViewItem.standalone(tabId: c.tabId),
|
||||
},
|
||||
];
|
||||
if (!showHierarchicalTabs && filterOptions.sortPinnedFirst) {
|
||||
final pinned = primaryRows
|
||||
.where((r) => pinnedTabIds.contains(r.tabId))
|
||||
.toList();
|
||||
final unpinned = primaryRows
|
||||
.where((r) => !pinnedTabIds.contains(r.tabId))
|
||||
.toList();
|
||||
primaryRows = [...pinned, ...unpinned];
|
||||
}
|
||||
}
|
||||
|
||||
final tabSuggestionsEnabled = ref.watch(
|
||||
|
||||
+10
-1
@@ -261,7 +261,7 @@ class _TabListView extends HookConsumerWidget {
|
||||
).select((value) => (value.value?.query ?? '').isNotEmpty),
|
||||
);
|
||||
|
||||
final List<TabViewItem> primaryRows;
|
||||
List<TabViewItem> primaryRows;
|
||||
if (hasActiveSearch) {
|
||||
final flat = ref.watch(
|
||||
seamlessFilteredTabEntitiesProvider(
|
||||
@@ -303,6 +303,15 @@ class _TabListView extends HookConsumerWidget {
|
||||
: TabViewItem.standalone(tabId: c.tabId),
|
||||
},
|
||||
];
|
||||
if (!showHierarchicalTabs && filterOptions.sortPinnedFirst) {
|
||||
final pinned = primaryRows
|
||||
.where((r) => pinnedTabIds.contains(r.tabId))
|
||||
.toList();
|
||||
final unpinned = primaryRows
|
||||
.where((r) => !pinnedTabIds.contains(r.tabId))
|
||||
.toList();
|
||||
primaryRows = [...pinned, ...unpinned];
|
||||
}
|
||||
}
|
||||
|
||||
final tabSuggestionsEnabled = ref.watch(
|
||||
|
||||
Reference in New Issue
Block a user