import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; class BaseNavigator { static Future push(BuildContext context, Widget child) async { if (globalState.appState.viewMode != ViewMode.mobile) { return await Navigator.of(context).push( CommonDesktopRoute( builder: (context) => child, ), ); } return await Navigator.of(context).push( CommonRoute( builder: (context) => child, ), ); } } 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 MaterialPageRoute { CommonRoute({ required super.builder, }); @override Duration get transitionDuration => const Duration(milliseconds: 500); @override Duration get reverseTransitionDuration => const Duration(milliseconds: 500); } 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 Animation delegatedPositionAnimation = CurvedAnimation( parent: secondaryAnimation, curve: Curves.linearToEaseOut, reverseCurve: Curves.easeInToLinear, ).drive(_kMiddleLeftTween); 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( [ widget.context.colorScheme.inverseSurface .withValues(alpha: 0.02), 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 = 1 * 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); } } }