resolve static method

GlassVerticalBarData? resolve({
  1. required EdgeInsets viewPadding,
  2. required Size size,
  3. required TargetPlatform platform,
  4. required TextDirection textDirection,
  5. List<DisplayFeature> displayFeatures = const <DisplayFeature>[],
  6. GlassVerticalBarEdge? systemEdge,
  7. GlassVerticalBarCompression compression = GlassVerticalBarCompression.automatic,
})

The strip the system has reserved, read from the view's insets.

iPhone Duo reports its strip only as a lateral inset, which carries the strip's full width on its side. Nothing else insets exactly one side: a regular iPhone in landscape insets both equally ((62, 0, 62, 20) on iPhone 17), and an iPad neither, so a single lateral inset is required rather than a particular width.

The side is the system's to choose, so it is never assumed. A full-screen app has the strip on the right, and the status bar moves into it, leaving a zero top inset. In Split View each app has it on its outer edge, the left for the app on the left, and the status bar stays along the top of the window.

viewPadding rather than padding, so the strip stays put while the keyboard is up, and it has to be read above any SafeArea, which removes viewPadding along with padding for its subtree. The insets are physical, so the strip keeps its side under RTL, where it becomes the GlassVerticalBarEdge.leading edge.

GlassVerticalBarData.top and GlassVerticalBarData.bottom are what the insets do not carry: where the status cluster and the camera end. UIKit reports both as occlusion regions, which move — the cluster grows with live activities and goes with the status bar — and they are read from displayFeatures: the controls start below the cutouts that overlap the top half of the strip and end above those in the bottom half, and keep GlassVerticalBarMetrics.edgeMargin clear of an end with none. Flutter does not report them on iOS yet (flutter/flutter#193025), so GlassNavigationShell adds the regions the package reads from UIKit itself.

Until a region is reported they come from a table keyed on the screen size, measured on the 27.1 simulator: 170pt on the outer display, 120pt on the inner display in landscape, and in outer landscape, which hides the status bar, 82pt clear of the camera — at the top when the strip is on the left, at the bottom when it is on the right.

Where the status bar keeps a top inset, as in Split View, the strip and the title row start GlassVerticalBarMetrics.edgeMargin below it.

compression resolves GlassVerticalBarData.collapsesTabBar; under GlassVerticalBarCompression.automatic the tab bar collapses in outer landscape, the one posture too short for it and the chrome together.

Implementation

static GlassVerticalBarData? resolve({
  required EdgeInsets viewPadding,
  required Size size,
  required TargetPlatform platform,
  required TextDirection textDirection,
  List<DisplayFeature> displayFeatures = const <DisplayFeature>[],
  GlassVerticalBarEdge? systemEdge,
  GlassVerticalBarCompression compression =
      GlassVerticalBarCompression.automatic,
}) {
  if (platform != TargetPlatform.iOS) return null;
  var left = viewPadding.left > 0;
  final right = viewPadding.right > 0;
  // Without an inset on one side only, the strip is the app's to keep, on
  // the edge UIKit names, if it names one.
  final systemInset = left != right;
  if (!systemInset) {
    if (systemEdge == null) return null;
    left = (systemEdge == GlassVerticalBarEdge.leading) ==
        (textDirection == TextDirection.ltr);
  }
  final onStart = left == (textDirection == TextDirection.ltr);

  final stripWidth = !systemInset
      ? GlassVerticalBarMetrics.stripWidth
      : left
          ? viewPadding.left
          : viewPadding.right;
  final strip = left
      ? Rect.fromLTWH(0, 0, stripWidth, size.height)
      : Rect.fromLTWH(size.width - stripWidth, 0, stripWidth, size.height);

  final width = size.width.round();
  final height = size.height.round();
  // A window that keeps its status bar along the top, as in Split View.
  final statusBarOnTop = viewPadding.top > 0;
  // Inner display, landscape: the status cluster sits in the strip above
  // the controls, shorter than on the outer display.
  final innerLandscape = width == 951 && height == 669;
  // Landscape on the outer display hides the status bar entirely.
  final outerLandscape = !statusBarOnTop && !innerLandscape && width > height;

  // The camera's end of the strip in outer landscape.
  const cameraClearance = 82.0;
  final belowStatusBar = viewPadding.top + GlassVerticalBarMetrics.edgeMargin;

  double? top;
  double? bottom;
  for (final feature in displayFeatures) {
    final bounds = feature.bounds;
    if (feature.type != DisplayFeatureType.cutout ||
        !bounds.overlaps(strip)) {
      continue;
    }
    if (bounds.center.dy < size.height / 2) {
      top =
          math.max(top ?? GlassVerticalBarMetrics.edgeMargin, bounds.bottom);
    } else {
      bottom = math.max(bottom ?? GlassVerticalBarMetrics.edgeMargin,
          size.height - bounds.top);
    }
  }
  if (statusBarOnTop) {
    top = math.max(top ?? 0, belowStatusBar);
  } else if (top == null && bottom == null && systemInset) {
    // Where the system keeps the strip, its status cluster and camera sit
    // at its ends; a strip the app keeps has neither.
    top = innerLandscape
        ? 120.0
        : outerLandscape
            ? (left ? cameraClearance : null)
            : 170.0;
    bottom = outerLandscape && right ? cameraClearance : null;
  }

  return GlassVerticalBarData(
    edge: onStart
        ? GlassVerticalBarEdge.leading
        : GlassVerticalBarEdge.trailing,
    width: stripWidth,
    top: top ?? GlassVerticalBarMetrics.edgeMargin,
    bottom: bottom ?? GlassVerticalBarMetrics.edgeMargin,
    rowTop: belowStatusBar,
    systemInset: systemInset,
    collapsesTabBar: switch (compression) {
      GlassVerticalBarCompression.automatic => outerLandscape,
      GlassVerticalBarCompression.prefersBarItems => true,
      GlassVerticalBarCompression.prefersTabBar => false,
    },
  );
}