import 'package:flutter/material.dart'; import '../constants/design_system.dart'; /// Transitions de page personnalisées pour une navigation fluide /// /// Utilisez ces transitions au lieu de MaterialPageRoute standard /// pour une meilleure expérience utilisateur. /// /// **Types de transitions:** /// - Fade: Fondu simple /// - Slide: Glissement depuis une direction /// - Scale: Zoom in/out /// - Rotation: Rotation 3D /// - SlideFromBottom: Bottom sheet style /// Énumération des types de transitions enum PageTransitionType { fade, slideRight, slideLeft, slideUp, slideDown, scale, rotation, fadeScale, } /// Route personnalisée avec transitions fluides class CustomPageRoute extends PageRoute { CustomPageRoute({ required this.builder, this.transitionType = PageTransitionType.slideRight, this.duration, this.reverseDuration, this.curve, this.reverseCurve, super.settings, }); final WidgetBuilder builder; final PageTransitionType transitionType; final Duration? duration; final Duration? reverseDuration; final Curve? curve; final Curve? reverseCurve; @override Color? get barrierColor => null; @override String? get barrierLabel => null; @override bool get maintainState => true; @override Duration get transitionDuration => duration ?? DesignSystem.durationMedium; @override Duration get reverseTransitionDuration => reverseDuration ?? DesignSystem.durationMedium; @override Widget buildPage( BuildContext context, Animation animation, Animation secondaryAnimation, ) { return builder(context); } @override Widget buildTransitions( BuildContext context, Animation animation, Animation secondaryAnimation, Widget child, ) { final effectiveCurve = curve ?? DesignSystem.curveDecelerate; final effectiveReverseCurve = reverseCurve ?? DesignSystem.curveDecelerate; final curvedAnimation = CurvedAnimation( parent: animation, curve: effectiveCurve, reverseCurve: effectiveReverseCurve, ); switch (transitionType) { case PageTransitionType.fade: return FadeTransition( opacity: curvedAnimation, child: child, ); case PageTransitionType.slideRight: return SlideTransition( position: Tween( begin: const Offset(1, 0), end: Offset.zero, ).animate(curvedAnimation), child: child, ); case PageTransitionType.slideLeft: return SlideTransition( position: Tween( begin: const Offset(-1, 0), end: Offset.zero, ).animate(curvedAnimation), child: child, ); case PageTransitionType.slideUp: return SlideTransition( position: Tween( begin: const Offset(0, 1), end: Offset.zero, ).animate(curvedAnimation), child: child, ); case PageTransitionType.slideDown: return SlideTransition( position: Tween( begin: const Offset(0, -1), end: Offset.zero, ).animate(curvedAnimation), child: child, ); case PageTransitionType.scale: return ScaleTransition( scale: Tween( begin: 0.0, end: 1.0, ).animate(curvedAnimation), child: FadeTransition( opacity: curvedAnimation, child: child, ), ); case PageTransitionType.rotation: return RotationTransition( turns: Tween( begin: 0.0, end: 1.0, ).animate(curvedAnimation), child: FadeTransition( opacity: curvedAnimation, child: child, ), ); case PageTransitionType.fadeScale: return FadeTransition( opacity: curvedAnimation, child: ScaleTransition( scale: Tween( begin: 0.95, end: 1.0, ).animate(curvedAnimation), child: child, ), ); } } } /// Extension pour faciliter la navigation avec transitions extension NavigatorExtensions on BuildContext { /// Navigate avec fade transition Future pushFade(Widget page) { return Navigator.of(this).push( CustomPageRoute( builder: (_) => page, transitionType: PageTransitionType.fade, ), ); } /// Navigate avec slide from right Future pushSlideRight(Widget page) { return Navigator.of(this).push( CustomPageRoute( builder: (_) => page, transitionType: PageTransitionType.slideRight, ), ); } /// Navigate avec slide from left Future pushSlideLeft(Widget page) { return Navigator.of(this).push( CustomPageRoute( builder: (_) => page, transitionType: PageTransitionType.slideLeft, ), ); } /// Navigate avec slide from bottom Future pushSlideUp(Widget page) { return Navigator.of(this).push( CustomPageRoute( builder: (_) => page, transitionType: PageTransitionType.slideUp, curve: DesignSystem.curveSharp, ), ); } /// Navigate avec scale transition Future pushScale(Widget page) { return Navigator.of(this).push( CustomPageRoute( builder: (_) => page, transitionType: PageTransitionType.scale, ), ); } /// Navigate avec fade + scale (élégant) Future pushFadeScale(Widget page) { return Navigator.of(this).push( CustomPageRoute( builder: (_) => page, transitionType: PageTransitionType.fadeScale, ), ); } /// Navigate et remplace avec transition Future pushReplacementFade(Widget page) { return Navigator.of(this).pushReplacement( CustomPageRoute( builder: (_) => page, transitionType: PageTransitionType.fade, ), ); } } /// Transition de Hero personnalisée /// /// Pour des animations de shared element plus fluides. class CustomHeroTransition extends RectTween { CustomHeroTransition({ required Rect? begin, required Rect? end, }) : super(begin: begin, end: end); @override Rect? lerp(double t) { final elasticCurveValue = Curves.easeInOutCubic.transform(t); return Rect.fromLTRB( lerpDouble(begin!.left, end!.left, elasticCurveValue), lerpDouble(begin!.top, end!.top, elasticCurveValue), lerpDouble(begin!.right, end!.right, elasticCurveValue), lerpDouble(begin!.bottom, end!.bottom, elasticCurveValue), ); } double lerpDouble(double a, double b, double t) { return a + (b - a) * t; } } /// Bottom sheet avec animation personnalisée Future showCustomBottomSheet({ required BuildContext context, required Widget Function(BuildContext) builder, bool isScrollControlled = true, bool useRootNavigator = false, bool isDismissible = true, Color? backgroundColor, double? elevation, }) { return showModalBottomSheet( context: context, builder: builder, isScrollControlled: isScrollControlled, useRootNavigator: useRootNavigator, isDismissible: isDismissible, backgroundColor: backgroundColor ?? Colors.transparent, elevation: elevation ?? 0, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(DesignSystem.radiusXl), ), ), transitionAnimationController: _createBottomSheetController(context), ); } AnimationController _createBottomSheetController(BuildContext context) { return BottomSheet.createAnimationController(Navigator.of(context)) ..duration = DesignSystem.durationMedium ..reverseDuration = DesignSystem.durationFast; } /// Dialog avec animation personnalisée Future showCustomDialog({ required BuildContext context, required Widget child, bool barrierDismissible = true, Color? barrierColor, String? barrierLabel, }) { return showGeneralDialog( context: context, barrierDismissible: barrierDismissible, barrierColor: barrierColor ?? Colors.black54, barrierLabel: barrierLabel, transitionDuration: DesignSystem.durationMedium, transitionBuilder: (context, animation, secondaryAnimation, child) { return ScaleTransition( scale: Tween( begin: 0.8, end: 1.0, ).animate( CurvedAnimation( parent: animation, curve: DesignSystem.curveDecelerate, ), ), child: FadeTransition( opacity: animation, child: child, ), ); }, pageBuilder: (context, animation, secondaryAnimation) { return child; }, ); } /// Shared Element Transition (Hero avec contrôle fin) class SharedElementTransition extends StatelessWidget { const SharedElementTransition({ required this.tag, required this.child, this.transitionOnUserGestures = false, super.key, }); final Object tag; final Widget child; final bool transitionOnUserGestures; @override Widget build(BuildContext context) { return Hero( tag: tag, transitionOnUserGestures: transitionOnUserGestures, flightShuttleBuilder: ( flightContext, animation, flightDirection, fromHeroContext, toHeroContext, ) { final Hero toHero = toHeroContext.widget as Hero; return ScaleTransition( scale: Tween( begin: 0.95, end: 1.0, ).animate( CurvedAnimation( parent: animation, curve: DesignSystem.curveDecelerate, ), ), child: toHero.child, ); }, child: child, ); } }