diff --git a/packages/flutter/lib/src/cupertino/route.dart b/packages/flutter/lib/src/cupertino/route.dart index 5c622a69fbae..eb96c18ca9ad 100644 --- a/packages/flutter/lib/src/cupertino/route.dart +++ b/packages/flutter/lib/src/cupertino/route.dart @@ -1245,23 +1245,7 @@ final Animatable _dialogScaleTween = Tween(begin: 1.3, end: 1.0) .chain(CurveTween(curve: Curves.linearToEaseOut)); Widget _buildCupertinoDialogTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { - final CurvedAnimation fadeAnimation = CurvedAnimation( - parent: animation, - curve: Curves.easeInOut, - ); - if (animation.status == AnimationStatus.reverse) { - return FadeTransition( - opacity: fadeAnimation, - child: child, - ); - } - return FadeTransition( - opacity: fadeAnimation, - child: ScaleTransition( - scale: animation.drive(_dialogScaleTween), - child: child, - ), - ); + return child; } /// Displays an iOS-style dialog above the current contents of the app, with @@ -1388,14 +1372,58 @@ class CupertinoDialogRoute extends RawDialogRoute { String? barrierLabel, // This transition duration was eyeballed comparing with iOS super.transitionDuration = const Duration(milliseconds: 250), - super.transitionBuilder = _buildCupertinoDialogTransitions, + this.transitionBuilder, super.settings, super.anchorPoint, }) : super( pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { return builder(context); }, + transitionBuilder: transitionBuilder ?? _buildCupertinoDialogTransitions, barrierLabel: barrierLabel ?? CupertinoLocalizations.of(context).modalBarrierDismissLabel, barrierColor: barrierColor ?? CupertinoDynamicColor.resolve(kCupertinoModalBarrierColor, context), ); + + /// Custom transition builder + RouteTransitionsBuilder? transitionBuilder; + + CurvedAnimation? _fadeAnimation; + + @override + Widget buildTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { + + if (transitionBuilder != null) { + return super.buildTransitions(context, animation, secondaryAnimation, child); + } + + if (_fadeAnimation?.parent != animation) { + _fadeAnimation?.dispose(); + _fadeAnimation = CurvedAnimation( + parent: animation, + curve: Curves.easeInOut, + ); + } + + final CurvedAnimation fadeAnimation = _fadeAnimation!; + + if (animation.status == AnimationStatus.reverse) { + return FadeTransition( + opacity: fadeAnimation, + child: super.buildTransitions(context, animation, secondaryAnimation, child), + ); + } + return FadeTransition( + opacity: fadeAnimation, + child: ScaleTransition( + scale: animation.drive(_dialogScaleTween), + child: super.buildTransitions(context, animation, secondaryAnimation, child), + ), + ); + } + + @override + void dispose() { + _fadeAnimation?.dispose(); + super.dispose(); + } } diff --git a/packages/flutter/test/cupertino/route_test.dart b/packages/flutter/test/cupertino/route_test.dart index 6c0810920d2b..abe679679139 100644 --- a/packages/flutter/test/cupertino/route_test.dart +++ b/packages/flutter/test/cupertino/route_test.dart @@ -2696,6 +2696,36 @@ void main() { await tester.tap(find.text('tap')); await tester.pumpAndSettle(); }); + + testWidgets('CupertinoDialogRoute does not leak CurveAnimation', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: ['CurvedAnimation']), + (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Navigator( + onGenerateRoute: (RouteSettings settings) { + return PageRouteBuilder( + pageBuilder: (BuildContext context, Animation _, Animation __) { + return GestureDetector( + onTap: () async { + await showCupertinoDialog( + context: context, + useRootNavigator: false, + builder: (BuildContext context) => const SizedBox(), + ); + }, + child: const Text('tap'), + ); + }, + ); + }, + ), + )); + + // Open the dialog. + await tester.tap(find.text('tap')); + await tester.pumpAndSettle(); + }); } class MockNavigatorObserver extends NavigatorObserver {