handleSample method

void handleSample(
  1. GlassTabBarScrollSample sample
)

Feeds one scroll sample to the state machine.

Pure with respect to Flutter, which is what makes it safe to call from anywhere: a host that observes scrolling some other way than owning a ScrollController can build its own samples and drive the controller directly. handleNotification is the ready-made path for the common case of a NotificationListener.

The order of the rules matters, and each early return also decides whether the accumulator survives.

Implementation

void handleSample(GlassTabBarScrollSample sample) {
  if (!_minimizes) return;

  final previous = _baseline;
  _baseline = sample.pixels;

  // 1. Content that cannot scroll never minimizes — and content that shrinks
  //    below the threshold while minimized must expand, or the bar is
  //    stranded as a circle with nothing to scroll back.
  if (sample.maxScrollExtent - sample.minScrollExtent <
      kMinScrollableExtent) {
    _accumulated = 0.0;
    _setMinimized(false);
    return;
  }

  // 2. At the resting edge, always expand — no threshold, no accumulation.
  //    `<=` rather than `==` so top rubber-band counts as "at the top":
  //    outOfRange is strict, so pixels == 0.0 is not out of range and would
  //    otherwise fall through to the accumulator below.
  if (_atRestingEdge(sample)) {
    _accumulated = 0.0;
    _setMinimized(false);
    return;
  }

  // 3. First sample after attaching — establish the baseline, decide
  //    nothing. This is what stops a restored scroll offset from minimizing
  //    the bar before the user has touched anything.
  if (previous == null) return;

  final delta = sample.pixels - previous;

  // 4. Not a user scroll at all: jumpTo, a PageStorage restore, the keyboard
  //    changing the viewport inset, or a layout correction. None of these
  //    begin a scrolling activity, so the direction is still idle — whereas
  //    a real drag, and the entire momentum phase after it, is not.
  if (sample.direction == ScrollDirection.idle) {
    _accumulated = 0.0;
    return;
  }

  // 5. A teleport rather than a scroll. Covers animateTo, which keeps a
  //    stale non-idle direction, and single-frame content-dimension jumps.
  if (delta.abs() > sample.viewportDimension * 0.5) {
    _accumulated = 0.0;
    return;
  }

  // 6. Rubber-band at the far edge: the offset keeps moving with no intent
  //    behind it. This must sit after the idle check and before accumulation
  //    — during a bounce the delta reverses sign while the direction is
  //    still the direction of the fling, which would otherwise read as a
  //    scroll back the other way and expand the bar.
  if (sample.outOfRange) {
    _accumulated = 0.0;
    return;
  }

  // 7. Accumulate since the last reversal.
  final effective = _effectiveDelta(delta);
  if (effective == 0.0) return;
  if (effective.sign != _accumulated.sign) _accumulated = 0.0;
  _accumulated += effective;

  if (_accumulated >= kMinimizeThreshold) {
    _accumulated = 0.0;
    _setMinimized(true);
  } else if (_accumulated <= -kExpandThreshold) {
    _accumulated = 0.0;
    _setMinimized(false);
  }
}