multiple runtime bugfixes for 1.5.5+65

This commit is contained in:
2026-08-28 20:42:30 +02:00
parent 65300614b1
commit 71839d0848
18 changed files with 344 additions and 159 deletions
+36 -5
View File
@@ -14,6 +14,15 @@ import 'app_progress_indicator.dart';
/// subtree for [placeholder] for the duration of any transition; the status
/// listener fires before that frame's layout, so the fragile subtree is gone
/// before the new transform lays out.
///
/// On top of that the gate always yields one idle post-frame (`_settled`)
/// before mounting [builder], whether or not a route animation ran. A
/// `RenderTransform` is a `RenderProxyBox` and only takes its size *after*
/// laying out its child, so during that first pass `size` throws — and route
/// transitions are not the only source of a fresh transform: `PersistentTabView`
/// wraps every tab screen in an animated `Transform.translate` and builds a tab
/// lazily on first activation, which is how the ticker's in-place PDF pages hit
/// the same window with no route animation in sight.
class RouteTransitionGate extends StatefulWidget {
const RouteTransitionGate({super.key, required this.builder, this.placeholder});
@@ -31,6 +40,11 @@ class _RouteTransitionGateState extends State<RouteTransitionGate> {
Animation<double>? _animation;
Animation<double>? _secondaryAnimation;
/// Becomes true one idle post-frame after the last transition ended; only
/// then is [RouteTransitionGate.builder] safe to mount (see class doc).
bool _settled = false;
bool _settleScheduled = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
@@ -68,15 +82,32 @@ class _RouteTransitionGateState extends State<RouteTransitionGate> {
status == AnimationStatus.forward || status == AnimationStatus.reverse;
void _onAnimationStatus(AnimationStatus status) {
if (mounted) setState(() {});
if (!mounted) return;
// A (re)starting transition invalidates the settled state; it has to be
// re-earned once the transition finishes.
setState(() {
if (_transitioning) _settled = false;
});
}
void _scheduleSettle() {
if (_settleScheduled) return;
_settleScheduled = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_settleScheduled = false;
if (mounted && !_transitioning && !_settled) {
setState(() => _settled = true);
}
});
}
@override
Widget build(BuildContext context) {
if (_transitioning) {
return widget.placeholder ??
const Center(child: AppProgressIndicator.large());
if (!_transitioning) {
if (_settled) return widget.builder(context);
_scheduleSettle();
}
return widget.builder(context);
return widget.placeholder ??
const Center(child: AppProgressIndicator.large());
}
}