matchGlassNavActions function

  1. @visibleForTesting
List<GlassNavActionSlot> matchGlassNavActions(
  1. List<GlassBarActionItem> from,
  2. List<GlassBarActionItem> to, {
  3. bool anchoredAtStart = false,
})

Pairs two routes' action items so matched ones morph in place.

Mirrors UIKit, which matches bar button items by identifier when one is set and otherwise falls back to position and content heuristics.

Returned slots are ordered leading-to-trailing in the incoming cluster, with items that only exist on the outgoing route appended.

anchoredAtStart follows the cluster it is matching: a trailing cluster counts positions from its trailing edge, a leading one from its leading edge. Either way an item keeps its place when items are added on the far side of the cluster from the bar edge it is pinned to.

Public for testing; held back from the barrel's show clause.

Implementation

@visibleForTesting
List<GlassNavActionSlot> matchGlassNavActions(
  List<GlassBarActionItem> from,
  List<GlassBarActionItem> to, {
  bool anchoredAtStart = false,
}) {
  // Slot index counts from the anchored edge, because that is the edge the
  // cluster grows away from.
  int slotOf(int index, int length) =>
      anchoredAtStart ? index : length - 1 - index;

  final slots = <GlassNavActionSlot>[];
  final usedFrom = <int>{};

  int? takeMatch(GlassBarActionItem toItem, int toIndex) {
    // 1. Explicit identifier match, mirroring UIBarButtonItem.identifier.
    if (toItem.id != null) {
      for (var i = 0; i < from.length; i++) {
        if (usedFrom.contains(i)) continue;
        if (from[i].id == toItem.id) return i;
      }
    }
    // 2. Positional fallback, counting from the anchored edge. An item
    //    carrying a different explicit id is never matched positionally.
    final wanted = slotOf(toIndex, to.length);
    for (var i = 0; i < from.length; i++) {
      if (usedFrom.contains(i)) continue;
      if (from[i].id != null && from[i].id != toItem.id) continue;
      if (slotOf(i, from.length) == wanted) return i;
    }
    return null;
  }

  for (var i = 0; i < to.length; i++) {
    final match = takeMatch(to[i], i);
    if (match != null) usedFrom.add(match);
    slots.add(GlassNavActionSlot(
      fromItem: match != null ? from[match] : null,
      toItem: to[i],
    ));
  }

  // Anything left on the outgoing route exits in place.
  for (var i = 0; i < from.length; i++) {
    if (usedFrom.contains(i)) continue;
    slots.add(GlassNavActionSlot(fromItem: from[i]));
  }

  return slots;
}