import 'package:animations/animations.dart'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/controller.dart'; import 'package:flutter/material.dart'; class BaseNavigator { static Future push(BuildContext context, Widget child) async { if (!appController.isMobile) { return await Navigator.of( context, ).push(CommonDesktopRoute(builder: (context) => child)); } return await Navigator.of( context, ).push(CommonRoute(builder: (context) => child)); } // static Future modal(BuildContext context, Widget child) async { // if (globalState.appState.viewMode != ViewMode.mobile) { // return await globalState.showCommonDialog( // child: CommonModal( // child: child, // ), // ); // } // return await Navigator.of(context).push( // CommonRoute( // builder: (context) => child, // ), // ); // } } const commonSharedXPageTransitions = SharedAxisPageTransitionsBuilder( transitionType: SharedAxisTransitionType.horizontal, fillColor: Colors.transparent, ); class CommonDesktopRoute extends PageRoute { final Widget Function(BuildContext context) builder; CommonDesktopRoute({required this.builder}); @override Color? get barrierColor => null; @override String? get barrierLabel => null; @override Widget buildPage( BuildContext context, Animation animation, Animation secondaryAnimation, ) { final Widget result = builder(context); return Semantics( scopesRoute: true, explicitChildNodes: true, child: FadeTransition(opacity: animation, child: result), ); } @override bool get maintainState => true; @override Duration get transitionDuration => Duration(milliseconds: 200); @override Duration get reverseTransitionDuration => Duration(milliseconds: 200); } class CommonRoute extends PageRoute { final Widget Function(BuildContext context) builder; CommonRoute({required this.builder}); @override Color? get barrierColor => null; @override String? get barrierLabel => null; @override bool get maintainState => true; @override Widget buildPage( BuildContext context, Animation animation, Animation secondaryAnimation, ) { final Widget result = builder(context); return Semantics( scopesRoute: true, explicitChildNodes: true, child: SharedAxisTransition( animation: animation, secondaryAnimation: secondaryAnimation, transitionType: SharedAxisTransitionType.horizontal, fillColor: context.colorScheme.surface, child: result, ), ); } @override Duration get transitionDuration => Duration(milliseconds: 300); @override Duration get reverseTransitionDuration => Duration(milliseconds: 300); } final Animatable _kRightMiddleTween = Tween( begin: const Offset(1.0, 0.0), end: Offset.zero, ); final Animatable _kMiddleLeftTween = Tween( begin: Offset.zero, end: const Offset(-1.0 / 3.0, 0.0), ); class CommonPageTransitionsBuilder extends PageTransitionsBuilder { const CommonPageTransitionsBuilder(); @override Widget buildTransitions( PageRoute route, BuildContext context, Animation animation, Animation secondaryAnimation, Widget child, ) { return CommonPageTransition( context: context, primaryRouteAnimation: animation, secondaryRouteAnimation: secondaryAnimation, linearTransition: false, child: child, ); } } class CommonPageTransition extends StatefulWidget { const CommonPageTransition({ super.key, required this.context, required this.primaryRouteAnimation, required this.secondaryRouteAnimation, required this.child, required this.linearTransition, }); final Widget child; final Animation primaryRouteAnimation; final Animation secondaryRouteAnimation; final BuildContext context; final bool linearTransition; static Widget? delegatedTransition( BuildContext context, Animation animation, Animation secondaryAnimation, bool allowSnapshotting, Widget? child, ) { final CurvedAnimation animation = CurvedAnimation( parent: secondaryAnimation, curve: Curves.linearToEaseOut, reverseCurve: Curves.easeInToLinear, ); final Animation delegatedPositionAnimation = animation.drive( _kMiddleLeftTween, ); animation.dispose(); assert(debugCheckHasDirectionality(context)); final TextDirection textDirection = Directionality.of(context); return SlideTransition( position: delegatedPositionAnimation, textDirection: textDirection, transformHitTests: false, child: child, ); } @override State createState() => _CommonPageTransitionState(); } class _CommonPageTransitionState extends State { late Animation _primaryPositionAnimation; late Animation _secondaryPositionAnimation; late Animation _primaryShadowAnimation; CurvedAnimation? _primaryPositionCurve; CurvedAnimation? _secondaryPositionCurve; CurvedAnimation? _primaryShadowCurve; @override void initState() { super.initState(); _setupAnimation(); } @override void didUpdateWidget(covariant CommonPageTransition oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.primaryRouteAnimation != widget.primaryRouteAnimation || oldWidget.secondaryRouteAnimation != widget.secondaryRouteAnimation || oldWidget.linearTransition != widget.linearTransition) { _disposeCurve(); _setupAnimation(); } } @override void dispose() { _disposeCurve(); super.dispose(); } void _disposeCurve() { _primaryPositionCurve?.dispose(); _secondaryPositionCurve?.dispose(); _primaryShadowCurve?.dispose(); _primaryPositionCurve = null; _secondaryPositionCurve = null; _primaryShadowCurve = null; } void _setupAnimation() { if (!widget.linearTransition) { _primaryPositionCurve = CurvedAnimation( parent: widget.primaryRouteAnimation, curve: Curves.fastEaseInToSlowEaseOut, reverseCurve: Curves.fastEaseInToSlowEaseOut.flipped, ); _secondaryPositionCurve = CurvedAnimation( parent: widget.secondaryRouteAnimation, curve: Curves.linearToEaseOut, reverseCurve: Curves.easeInToLinear, ); _primaryShadowCurve = CurvedAnimation( parent: widget.primaryRouteAnimation, curve: Curves.linearToEaseOut, ); } _primaryPositionAnimation = (_primaryPositionCurve ?? widget.primaryRouteAnimation).drive( _kRightMiddleTween, ); _secondaryPositionAnimation = (_secondaryPositionCurve ?? widget.secondaryRouteAnimation).drive( _kMiddleLeftTween, ); _primaryShadowAnimation = (_primaryShadowCurve ?? widget.primaryRouteAnimation).drive( DecorationTween( begin: const _CommonEdgeShadowDecoration(), end: _CommonEdgeShadowDecoration([ Color(0x04000000), Colors.transparent, ]), ), ); } @override Widget build(BuildContext context) { assert(debugCheckHasDirectionality(context)); final TextDirection textDirection = Directionality.of(context); return SlideTransition( position: _secondaryPositionAnimation, textDirection: textDirection, transformHitTests: false, child: SlideTransition( position: _primaryPositionAnimation, textDirection: textDirection, child: DecoratedBoxTransition( decoration: _primaryShadowAnimation, child: widget.child, ), ), ); } } class _CommonEdgeShadowDecoration extends Decoration { final List? _colors; const _CommonEdgeShadowDecoration([this._colors]); @override BoxPainter createBoxPainter([VoidCallback? onChanged]) { return _CommonEdgeShadowPainter(this, onChanged); } } class _CommonEdgeShadowPainter extends BoxPainter { _CommonEdgeShadowPainter(this._decoration, super.onChanged) : assert(_decoration._colors == null || _decoration._colors.length > 1); final _CommonEdgeShadowDecoration _decoration; @override void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { final List? colors = _decoration._colors; if (colors == null) { return; } final double shadowWidth = 0.05 * configuration.size!.width; final double shadowHeight = configuration.size!.height; final double bandWidth = shadowWidth / (colors.length - 1); final TextDirection? textDirection = configuration.textDirection; assert(textDirection != null); final (double shadowDirection, double start) = switch (textDirection!) { TextDirection.rtl => (1, offset.dx + configuration.size!.width), TextDirection.ltr => (-1, offset.dx), }; int bandColorIndex = 0; for (int dx = 0; dx < shadowWidth; dx += 1) { if (dx ~/ bandWidth != bandColorIndex) { bandColorIndex += 1; } final Paint paint = Paint() ..color = Color.lerp( colors[bandColorIndex], colors[bandColorIndex + 1], (dx % bandWidth) / bandWidth, )!; final double x = start + shadowDirection * dx; canvas.drawRect( Rect.fromLTWH(x - 1.0, offset.dy, 1.0, shadowHeight), paint, ); } } }