matchGlassNavGroups function
Pairs two routes' groups so a shell that survives the transition morphs in place.
matchGlassNavActions one level up. A group pairs first with the group
holding any of its items — by identity, or by id where the item has one —
so a shell follows its items when a spacer adds or removes a group beside
it. Groups left over pair by position, counted from the anchored edge as
anchoredAtStart describes, and anything still unpaired enters or exits
on its own.
Returned with the incoming groups first, in order, and the groups only the outgoing route has after them.
Public for testing; held back from the barrel's show clause.
Implementation
@visibleForTesting
List<({GlassNavBarGroup? from, GlassNavBarGroup? to})> matchGlassNavGroups(
List<GlassNavBarGroup> from,
List<GlassNavBarGroup> to, {
bool anchoredAtStart = false,
}) {
int slotOf(int index, int length) =>
anchoredAtStart ? index : length - 1 - index;
final matches = List<int?>.filled(to.length, null);
final usedFrom = <int>{};
// Shared items first, for every group, so no group's positional fallback
// can take a partner another group's items have already claimed.
for (var t = 0; t < to.length; t++) {
for (var f = 0; f < from.length; f++) {
if (usedFrom.contains(f) || !to[t].items.any(from[f].contains)) continue;
matches[t] = f;
usedFrom.add(f);
break;
}
}
for (var t = 0; t < to.length; t++) {
if (matches[t] != null) continue;
final wanted = slotOf(t, to.length);
for (var f = 0; f < from.length; f++) {
if (usedFrom.contains(f) || slotOf(f, from.length) != wanted) continue;
matches[t] = f;
usedFrom.add(f);
break;
}
}
return [
for (var t = 0; t < to.length; t++)
(from: matches[t] == null ? null : from[matches[t]!], to: to[t]),
for (var f = 0; f < from.length; f++)
if (!usedFrom.contains(f)) (from: from[f], to: null),
];
}