make tab selection cross container borders
This commit is contained in:
@@ -467,12 +467,14 @@ class TabRepository extends _$TabRepository {
|
|||||||
|
|
||||||
/// Moves the selection one step through the tab sequence.
|
/// Moves the selection one step through the tab sequence.
|
||||||
///
|
///
|
||||||
/// Unscoped calls — the tab bar swipe and the next/previous tab gestures —
|
/// Calls that cross containers ([skipContainerCheck] with no explicit
|
||||||
|
/// [containerId]) — the tab bar swipe and the next/previous tab gestures —
|
||||||
/// step through the *rendered* order
|
/// step through the *rendered* order
|
||||||
/// ([sequentialTabNavigationOrderProvider]) so navigation matches the tabs the
|
/// ([sequentialTabNavigationOrderProvider]) so navigation matches the tabs the
|
||||||
/// user sees, including the tray's sort type, grouping, filters and
|
/// user sees, including the tray's sort type, grouping, filters and
|
||||||
/// pinned-first handling. That order is authoritative once it exists, and
|
/// pinned-first handling. That order spans every populated container, so this
|
||||||
/// every outcome stays inside it:
|
/// keeps walking past a container boundary exactly like the storage-order walk
|
||||||
|
/// did. It is authoritative once it exists, and every outcome stays inside it:
|
||||||
///
|
///
|
||||||
/// - current tab in the order: step one row, stopping at either end;
|
/// - current tab in the order: step one row, stopping at either end;
|
||||||
/// - current tab outside it — hidden by the active filter, or folded into a
|
/// - current tab outside it — hidden by the active filter, or folded into a
|
||||||
@@ -480,9 +482,9 @@ class TabRepository extends _$TabRepository {
|
|||||||
/// from, rather than jumping to a tab the filter excludes;
|
/// from, rather than jumping to a tab the filter excludes;
|
||||||
/// - nothing visible at all: do nothing.
|
/// - nothing visible at all: do nothing.
|
||||||
///
|
///
|
||||||
/// The storage-order path is left for calls that scope navigation to a
|
/// The storage-order path is left for calls that scope navigation to a single
|
||||||
/// container (which the rendered order, tied to the selected container, cannot
|
/// container (which the cross-container order cannot answer) and for the brief
|
||||||
/// answer) and for the brief window before the tree data has loaded.
|
/// window before the tree data has loaded.
|
||||||
Future<bool> _selectAdjacentTab(
|
Future<bool> _selectAdjacentTab(
|
||||||
String tabId, {
|
String tabId, {
|
||||||
required String? containerId,
|
required String? containerId,
|
||||||
|
|||||||
@@ -1247,16 +1247,26 @@ EquatableValue<List<TabListItemEntity>> visibleTabListItems(
|
|||||||
/// Flat tab id order used by sequential tab navigation: the tab bar swipe
|
/// Flat tab id order used by sequential tab navigation: the tab bar swipe
|
||||||
/// action and the next/previous tab gestures.
|
/// action and the next/previous tab gestures.
|
||||||
///
|
///
|
||||||
/// Navigation follows the rendered tray order instead of the raw storage
|
/// Navigation follows the rendered order instead of the raw storage
|
||||||
/// `order_key`, so it carries the active sort type, tree grouping, collapsed
|
/// `order_key`, so it carries the active sort type, tree grouping, collapsed
|
||||||
/// groups, pinned-first handling and the tab-type/date filter — stepping to the
|
/// groups, pinned-first handling and the tab-type/date filter — stepping to the
|
||||||
/// tab the user sees next to the current one rather than to an unrelated
|
/// tab the user sees next to the current one rather than to an unrelated
|
||||||
/// `order_key` neighbour.
|
/// `order_key` neighbour.
|
||||||
///
|
///
|
||||||
|
/// It spans **all** containers, keeping the boundary-crossing reach the
|
||||||
|
/// storage-order walk had: each container contributes the rows its tray would
|
||||||
|
/// render, and the containers follow one another in the order the quick tab
|
||||||
|
/// switcher lays them out — the unassigned bucket first, then containers by
|
||||||
|
/// pinned/`order_key`. Stepping off the end of one container therefore
|
||||||
|
/// continues into the next, and selecting that tab moves the selected container
|
||||||
|
/// along with it. Named containers holding no tabs are skipped so their tree
|
||||||
|
/// query never runs.
|
||||||
|
///
|
||||||
/// "Previous" is a step towards the top of that order and "next" a step
|
/// "Previous" is a step towards the top of that order and "next" a step
|
||||||
/// towards its end, so direction follows `tabListDirection` (baked into the
|
/// towards its end, so direction follows `tabListDirection` (baked into the
|
||||||
/// order) rather than `tabBarDirection`. The two only disagree when the user
|
/// order) rather than `tabBarDirection`. The two only disagree when the user
|
||||||
/// sets them apart, and the tray order is the one the sequence is built from.
|
/// sets them apart, and the rendered order is the one the sequence is built
|
||||||
|
/// from.
|
||||||
///
|
///
|
||||||
/// The tray's own search results are deliberately not part of this: the swipe
|
/// The tray's own search results are deliberately not part of this: the swipe
|
||||||
/// and the gestures are only reachable with the tray closed.
|
/// and the gestures are only reachable with the tray closed.
|
||||||
@@ -1271,26 +1281,44 @@ EquatableValue<List<TabListItemEntity>> visibleTabListItems(
|
|||||||
/// widget tree. Without a listener Riverpod pauses the chain when nothing is on
|
/// widget tree. Without a listener Riverpod pauses the chain when nothing is on
|
||||||
/// screen watching it, so the order could go stale — or be created empty on the
|
/// screen watching it, so the order could go stale — or be created empty on the
|
||||||
/// read, with its tree stream still loading, and silently drop navigation back
|
/// read, with its tree stream still loading, and silently drop navigation back
|
||||||
/// to storage order. It is alive anyway whenever the quick tab switcher or the
|
/// to storage order. The selected container's chain is alive anyway whenever the
|
||||||
/// tray is on screen — both watch the same [groupedTabListItemsProvider] chain.
|
/// quick tab switcher or the tray is on screen; the price of crossing container
|
||||||
|
/// boundaries is that the other populated containers' tree queries are kept
|
||||||
|
/// alive too.
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
EquatableValue<List<String>?> sequentialTabNavigationOrder(Ref ref) {
|
EquatableValue<List<String>?> sequentialTabNavigationOrder(Ref ref) {
|
||||||
final containerId = ref.watch(selectedContainerProvider);
|
final containers = ref.watch(
|
||||||
|
watchContainersWithCountProvider.select((value) => value.value),
|
||||||
final hasTreeData = ref.watch(
|
|
||||||
watchTabsWithRootAndDepthProvider(
|
|
||||||
containerId,
|
|
||||||
).select((value) => value.hasValue),
|
|
||||||
);
|
);
|
||||||
if (!hasTreeData) {
|
if (containers == null) {
|
||||||
return EquatableValue(null);
|
return EquatableValue(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
final visibleItems = ref
|
final containerIds = <String?>[
|
||||||
.watch(visibleTabListItemsProvider(containerId: containerId))
|
null,
|
||||||
.value;
|
for (final container in containers)
|
||||||
|
if ((container.tabCount ?? 0) > 0) container.id,
|
||||||
|
];
|
||||||
|
|
||||||
return EquatableValue([for (final item in visibleItems) item.tabId]);
|
final order = <String>[];
|
||||||
|
for (final containerId in containerIds) {
|
||||||
|
final hasTreeData = ref.watch(
|
||||||
|
watchTabsWithRootAndDepthProvider(
|
||||||
|
containerId,
|
||||||
|
).select((value) => value.hasValue),
|
||||||
|
);
|
||||||
|
if (!hasTreeData) {
|
||||||
|
return EquatableValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
final visibleItems = ref
|
||||||
|
.watch(visibleTabListItemsProvider(containerId: containerId))
|
||||||
|
.value;
|
||||||
|
|
||||||
|
order.addAll(visibleItems.map((item) => item.tabId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return EquatableValue(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _nearestVisibleParentId(
|
String _nearestVisibleParentId(
|
||||||
|
|||||||
@@ -1385,16 +1385,26 @@ final class VisibleTabListItemsFamily extends $Family
|
|||||||
/// Flat tab id order used by sequential tab navigation: the tab bar swipe
|
/// Flat tab id order used by sequential tab navigation: the tab bar swipe
|
||||||
/// action and the next/previous tab gestures.
|
/// action and the next/previous tab gestures.
|
||||||
///
|
///
|
||||||
/// Navigation follows the rendered tray order instead of the raw storage
|
/// Navigation follows the rendered order instead of the raw storage
|
||||||
/// `order_key`, so it carries the active sort type, tree grouping, collapsed
|
/// `order_key`, so it carries the active sort type, tree grouping, collapsed
|
||||||
/// groups, pinned-first handling and the tab-type/date filter — stepping to the
|
/// groups, pinned-first handling and the tab-type/date filter — stepping to the
|
||||||
/// tab the user sees next to the current one rather than to an unrelated
|
/// tab the user sees next to the current one rather than to an unrelated
|
||||||
/// `order_key` neighbour.
|
/// `order_key` neighbour.
|
||||||
///
|
///
|
||||||
|
/// It spans **all** containers, keeping the boundary-crossing reach the
|
||||||
|
/// storage-order walk had: each container contributes the rows its tray would
|
||||||
|
/// render, and the containers follow one another in the order the quick tab
|
||||||
|
/// switcher lays them out — the unassigned bucket first, then containers by
|
||||||
|
/// pinned/`order_key`. Stepping off the end of one container therefore
|
||||||
|
/// continues into the next, and selecting that tab moves the selected container
|
||||||
|
/// along with it. Containers without tabs are skipped so their tree query never
|
||||||
|
/// runs.
|
||||||
|
///
|
||||||
/// "Previous" is a step towards the top of that order and "next" a step
|
/// "Previous" is a step towards the top of that order and "next" a step
|
||||||
/// towards its end, so direction follows `tabListDirection` (baked into the
|
/// towards its end, so direction follows `tabListDirection` (baked into the
|
||||||
/// order) rather than `tabBarDirection`. The two only disagree when the user
|
/// order) rather than `tabBarDirection`. The two only disagree when the user
|
||||||
/// sets them apart, and the tray order is the one the sequence is built from.
|
/// sets them apart, and the rendered order is the one the sequence is built
|
||||||
|
/// from.
|
||||||
///
|
///
|
||||||
/// The tray's own search results are deliberately not part of this: the swipe
|
/// The tray's own search results are deliberately not part of this: the swipe
|
||||||
/// and the gestures are only reachable with the tray closed.
|
/// and the gestures are only reachable with the tray closed.
|
||||||
@@ -1409,8 +1419,10 @@ final class VisibleTabListItemsFamily extends $Family
|
|||||||
/// widget tree. Without a listener Riverpod pauses the chain when nothing is on
|
/// widget tree. Without a listener Riverpod pauses the chain when nothing is on
|
||||||
/// screen watching it, so the order could go stale — or be created empty on the
|
/// screen watching it, so the order could go stale — or be created empty on the
|
||||||
/// read, with its tree stream still loading, and silently drop navigation back
|
/// read, with its tree stream still loading, and silently drop navigation back
|
||||||
/// to storage order. It is alive anyway whenever the quick tab switcher or the
|
/// to storage order. The selected container's chain is alive anyway whenever the
|
||||||
/// tray is on screen — both watch the same [groupedTabListItemsProvider] chain.
|
/// quick tab switcher or the tray is on screen; the price of crossing container
|
||||||
|
/// boundaries is that the other populated containers' tree queries are kept
|
||||||
|
/// alive too.
|
||||||
|
|
||||||
@ProviderFor(sequentialTabNavigationOrder)
|
@ProviderFor(sequentialTabNavigationOrder)
|
||||||
final sequentialTabNavigationOrderProvider =
|
final sequentialTabNavigationOrderProvider =
|
||||||
@@ -1419,16 +1431,26 @@ final sequentialTabNavigationOrderProvider =
|
|||||||
/// Flat tab id order used by sequential tab navigation: the tab bar swipe
|
/// Flat tab id order used by sequential tab navigation: the tab bar swipe
|
||||||
/// action and the next/previous tab gestures.
|
/// action and the next/previous tab gestures.
|
||||||
///
|
///
|
||||||
/// Navigation follows the rendered tray order instead of the raw storage
|
/// Navigation follows the rendered order instead of the raw storage
|
||||||
/// `order_key`, so it carries the active sort type, tree grouping, collapsed
|
/// `order_key`, so it carries the active sort type, tree grouping, collapsed
|
||||||
/// groups, pinned-first handling and the tab-type/date filter — stepping to the
|
/// groups, pinned-first handling and the tab-type/date filter — stepping to the
|
||||||
/// tab the user sees next to the current one rather than to an unrelated
|
/// tab the user sees next to the current one rather than to an unrelated
|
||||||
/// `order_key` neighbour.
|
/// `order_key` neighbour.
|
||||||
///
|
///
|
||||||
|
/// It spans **all** containers, keeping the boundary-crossing reach the
|
||||||
|
/// storage-order walk had: each container contributes the rows its tray would
|
||||||
|
/// render, and the containers follow one another in the order the quick tab
|
||||||
|
/// switcher lays them out — the unassigned bucket first, then containers by
|
||||||
|
/// pinned/`order_key`. Stepping off the end of one container therefore
|
||||||
|
/// continues into the next, and selecting that tab moves the selected container
|
||||||
|
/// along with it. Containers without tabs are skipped so their tree query never
|
||||||
|
/// runs.
|
||||||
|
///
|
||||||
/// "Previous" is a step towards the top of that order and "next" a step
|
/// "Previous" is a step towards the top of that order and "next" a step
|
||||||
/// towards its end, so direction follows `tabListDirection` (baked into the
|
/// towards its end, so direction follows `tabListDirection` (baked into the
|
||||||
/// order) rather than `tabBarDirection`. The two only disagree when the user
|
/// order) rather than `tabBarDirection`. The two only disagree when the user
|
||||||
/// sets them apart, and the tray order is the one the sequence is built from.
|
/// sets them apart, and the rendered order is the one the sequence is built
|
||||||
|
/// from.
|
||||||
///
|
///
|
||||||
/// The tray's own search results are deliberately not part of this: the swipe
|
/// The tray's own search results are deliberately not part of this: the swipe
|
||||||
/// and the gestures are only reachable with the tray closed.
|
/// and the gestures are only reachable with the tray closed.
|
||||||
@@ -1443,8 +1465,10 @@ final sequentialTabNavigationOrderProvider =
|
|||||||
/// widget tree. Without a listener Riverpod pauses the chain when nothing is on
|
/// widget tree. Without a listener Riverpod pauses the chain when nothing is on
|
||||||
/// screen watching it, so the order could go stale — or be created empty on the
|
/// screen watching it, so the order could go stale — or be created empty on the
|
||||||
/// read, with its tree stream still loading, and silently drop navigation back
|
/// read, with its tree stream still loading, and silently drop navigation back
|
||||||
/// to storage order. It is alive anyway whenever the quick tab switcher or the
|
/// to storage order. The selected container's chain is alive anyway whenever the
|
||||||
/// tray is on screen — both watch the same [groupedTabListItemsProvider] chain.
|
/// quick tab switcher or the tray is on screen; the price of crossing container
|
||||||
|
/// boundaries is that the other populated containers' tree queries are kept
|
||||||
|
/// alive too.
|
||||||
|
|
||||||
final class SequentialTabNavigationOrderProvider
|
final class SequentialTabNavigationOrderProvider
|
||||||
extends
|
extends
|
||||||
@@ -1457,16 +1481,26 @@ final class SequentialTabNavigationOrderProvider
|
|||||||
/// Flat tab id order used by sequential tab navigation: the tab bar swipe
|
/// Flat tab id order used by sequential tab navigation: the tab bar swipe
|
||||||
/// action and the next/previous tab gestures.
|
/// action and the next/previous tab gestures.
|
||||||
///
|
///
|
||||||
/// Navigation follows the rendered tray order instead of the raw storage
|
/// Navigation follows the rendered order instead of the raw storage
|
||||||
/// `order_key`, so it carries the active sort type, tree grouping, collapsed
|
/// `order_key`, so it carries the active sort type, tree grouping, collapsed
|
||||||
/// groups, pinned-first handling and the tab-type/date filter — stepping to the
|
/// groups, pinned-first handling and the tab-type/date filter — stepping to the
|
||||||
/// tab the user sees next to the current one rather than to an unrelated
|
/// tab the user sees next to the current one rather than to an unrelated
|
||||||
/// `order_key` neighbour.
|
/// `order_key` neighbour.
|
||||||
///
|
///
|
||||||
|
/// It spans **all** containers, keeping the boundary-crossing reach the
|
||||||
|
/// storage-order walk had: each container contributes the rows its tray would
|
||||||
|
/// render, and the containers follow one another in the order the quick tab
|
||||||
|
/// switcher lays them out — the unassigned bucket first, then containers by
|
||||||
|
/// pinned/`order_key`. Stepping off the end of one container therefore
|
||||||
|
/// continues into the next, and selecting that tab moves the selected container
|
||||||
|
/// along with it. Containers without tabs are skipped so their tree query never
|
||||||
|
/// runs.
|
||||||
|
///
|
||||||
/// "Previous" is a step towards the top of that order and "next" a step
|
/// "Previous" is a step towards the top of that order and "next" a step
|
||||||
/// towards its end, so direction follows `tabListDirection` (baked into the
|
/// towards its end, so direction follows `tabListDirection` (baked into the
|
||||||
/// order) rather than `tabBarDirection`. The two only disagree when the user
|
/// order) rather than `tabBarDirection`. The two only disagree when the user
|
||||||
/// sets them apart, and the tray order is the one the sequence is built from.
|
/// sets them apart, and the rendered order is the one the sequence is built
|
||||||
|
/// from.
|
||||||
///
|
///
|
||||||
/// The tray's own search results are deliberately not part of this: the swipe
|
/// The tray's own search results are deliberately not part of this: the swipe
|
||||||
/// and the gestures are only reachable with the tray closed.
|
/// and the gestures are only reachable with the tray closed.
|
||||||
@@ -1481,8 +1515,10 @@ final class SequentialTabNavigationOrderProvider
|
|||||||
/// widget tree. Without a listener Riverpod pauses the chain when nothing is on
|
/// widget tree. Without a listener Riverpod pauses the chain when nothing is on
|
||||||
/// screen watching it, so the order could go stale — or be created empty on the
|
/// screen watching it, so the order could go stale — or be created empty on the
|
||||||
/// read, with its tree stream still loading, and silently drop navigation back
|
/// read, with its tree stream still loading, and silently drop navigation back
|
||||||
/// to storage order. It is alive anyway whenever the quick tab switcher or the
|
/// to storage order. The selected container's chain is alive anyway whenever the
|
||||||
/// tray is on screen — both watch the same [groupedTabListItemsProvider] chain.
|
/// quick tab switcher or the tray is on screen; the price of crossing container
|
||||||
|
/// boundaries is that the other populated containers' tree queries are kept
|
||||||
|
/// alive too.
|
||||||
SequentialTabNavigationOrderProvider._()
|
SequentialTabNavigationOrderProvider._()
|
||||||
: super(
|
: super(
|
||||||
from: null,
|
from: null,
|
||||||
@@ -1520,4 +1556,4 @@ final class SequentialTabNavigationOrderProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$sequentialTabNavigationOrderHash() =>
|
String _$sequentialTabNavigationOrderHash() =>
|
||||||
r'2a6b1965657942524e8e514cd4deb0ce77667fe1';
|
r'cd8f2337012473028084f2efb1bff540e80abdf4';
|
||||||
|
|||||||
Reference in New Issue
Block a user