A whole lot of stuff

This commit is contained in:
Courtney Arnold 2026-08-18 11:04:58 -05:00
parent faf319322a
commit ad850d2be6
50 changed files with 3013 additions and 213 deletions

View file

@ -1,6 +1,41 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
/// Waits for a route's push/pop transition, if one is still in progress, to
/// finish. Needed before capturing a highlighted target's on-screen
/// position via `RenderBox.localToGlobal` (see `MainShell._waitForTargets`
/// / `_SpotlightedDataSettingsScreen`'s `_showSpotlight` in
/// backup_reminder.dart, both of which push a [MaterialPageRoute] and then
/// spotlight something on it): a [RenderBox] reports `hasSize` — and a
/// real, but not yet final, global position — as soon as its first layout
/// pass completes, which happens well before a [MaterialPageRoute]'s
/// ~300ms slide-in transition actually settles. Capturing the position too
/// early bakes in wherever the page was mid-slide, which is what produced
/// a spotlight rectangle visibly shifted from its target.
///
/// Takes the [Animation] directly — rather than a [BuildContext] to look
/// one up via `ModalRoute.of` — because the caller doesn't always have a
/// [BuildContext] that's actually inside the route in question: `MainShell`
/// pushes the route from its own (different, already-settled) route, so
/// `ModalRoute.of(mainShellContext)` would resolve to the wrong one.
/// Passing null (nothing to wait for) or an already-[AnimationStatus.completed]
/// animation both resolve immediately, so it's safe to call unconditionally.
Future<void> waitForRouteTransition(Animation<double>? animation) async {
if (animation == null || animation.status == AnimationStatus.completed) return;
final completer = Completer<void>();
void listener(AnimationStatus status) {
if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) {
completer.complete();
}
}
animation.addStatusListener(listener);
await completer.future;
animation.removeStatusListener(listener);
}
/// The full-screen "spotlight" shown by one step of the first-launch
/// guided tour: a dimmed barrier with a cut-out hole around each of
/// [targetKeys]' current on-screen positions (or, for any key that isn't