import 'package:flutter/material.dart'; /// Transitions de pages personnalisées pour une meilleure UX class PageTransitions { /// Transition de glissement depuis la droite (par défaut iOS) static PageRouteBuilder slideFromRight(Widget page) { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionDuration: const Duration(milliseconds: 300), reverseTransitionDuration: const Duration(milliseconds: 250), transitionsBuilder: (context, animation, secondaryAnimation, child) { const begin = Offset(1.0, 0.0); const end = Offset.zero; const curve = Curves.easeInOut; var tween = Tween(begin: begin, end: end).chain( CurveTween(curve: curve), ); return SlideTransition( position: animation.drive(tween), child: child, ); }, ); } /// Transition de glissement depuis le bas static PageRouteBuilder slideFromBottom(Widget page) { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionDuration: const Duration(milliseconds: 350), reverseTransitionDuration: const Duration(milliseconds: 300), transitionsBuilder: (context, animation, secondaryAnimation, child) { const begin = Offset(0.0, 1.0); const end = Offset.zero; const curve = Curves.easeOutCubic; var tween = Tween(begin: begin, end: end).chain( CurveTween(curve: curve), ); return SlideTransition( position: animation.drive(tween), child: child, ); }, ); } /// Transition de fondu static PageRouteBuilder fadeIn(Widget page) { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionDuration: const Duration(milliseconds: 400), reverseTransitionDuration: const Duration(milliseconds: 300), transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition( opacity: animation, child: child, ); }, ); } /// Transition d'échelle avec fondu static PageRouteBuilder scaleWithFade(Widget page) { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionDuration: const Duration(milliseconds: 400), reverseTransitionDuration: const Duration(milliseconds: 300), transitionsBuilder: (context, animation, secondaryAnimation, child) { const curve = Curves.easeInOutCubic; var scaleTween = Tween(begin: 0.8, end: 1.0).chain( CurveTween(curve: curve), ); var fadeTween = Tween(begin: 0.0, end: 1.0).chain( CurveTween(curve: curve), ); return ScaleTransition( scale: animation.drive(scaleTween), child: FadeTransition( opacity: animation.drive(fadeTween), child: child, ), ); }, ); } /// Transition de rotation avec échelle static PageRouteBuilder rotateScale(Widget page) { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionDuration: const Duration(milliseconds: 500), reverseTransitionDuration: const Duration(milliseconds: 400), transitionsBuilder: (context, animation, secondaryAnimation, child) { const curve = Curves.elasticOut; var scaleTween = Tween(begin: 0.0, end: 1.0).chain( CurveTween(curve: curve), ); var rotationTween = Tween(begin: 0.5, end: 1.0).chain( CurveTween(curve: Curves.easeInOut), ); return ScaleTransition( scale: animation.drive(scaleTween), child: RotationTransition( turns: animation.drive(rotationTween), child: child, ), ); }, ); } /// Transition personnalisée avec effet de rebond static PageRouteBuilder bounceIn(Widget page) { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionDuration: const Duration(milliseconds: 600), reverseTransitionDuration: const Duration(milliseconds: 400), transitionsBuilder: (context, animation, secondaryAnimation, child) { const curve = Curves.bounceOut; var scaleTween = Tween(begin: 0.3, end: 1.0).chain( CurveTween(curve: curve), ); return ScaleTransition( scale: animation.drive(scaleTween), child: child, ); }, ); } /// Transition de glissement avec parallaxe static PageRouteBuilder slideWithParallax(Widget page) { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionDuration: const Duration(milliseconds: 350), reverseTransitionDuration: const Duration(milliseconds: 300), transitionsBuilder: (context, animation, secondaryAnimation, child) { const primaryBegin = Offset(1.0, 0.0); const primaryEnd = Offset.zero; const secondaryBegin = Offset.zero; const secondaryEnd = Offset(-0.3, 0.0); const curve = Curves.easeInOut; var primaryTween = Tween(begin: primaryBegin, end: primaryEnd).chain( CurveTween(curve: curve), ); var secondaryTween = Tween(begin: secondaryBegin, end: secondaryEnd).chain( CurveTween(curve: curve), ); return Stack( children: [ SlideTransition( position: secondaryAnimation.drive(secondaryTween), child: Container(), // Page précédente ), SlideTransition( position: animation.drive(primaryTween), child: child, ), ], ); }, ); } /// Transition avec effet de morphing et blur static PageRouteBuilder morphWithBlur(Widget page) { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionDuration: const Duration(milliseconds: 500), reverseTransitionDuration: const Duration(milliseconds: 400), transitionsBuilder: (context, animation, secondaryAnimation, child) { final curvedAnimation = CurvedAnimation( parent: animation, curve: Curves.easeInOutCubic, ); final scaleAnimation = Tween( begin: 0.8, end: 1.0, ).animate(curvedAnimation); final fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: animation, curve: const Interval(0.3, 1.0, curve: Curves.easeOut), )); return FadeTransition( opacity: fadeAnimation, child: Transform.scale( scale: scaleAnimation.value, child: child, ), ); }, ); } /// Transition avec effet de rotation 3D static PageRouteBuilder rotate3D(Widget page) { return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionDuration: const Duration(milliseconds: 600), reverseTransitionDuration: const Duration(milliseconds: 500), transitionsBuilder: (context, animation, secondaryAnimation, child) { final curvedAnimation = CurvedAnimation( parent: animation, curve: Curves.easeInOutCubic, ); return AnimatedBuilder( animation: curvedAnimation, builder: (context, child) { final rotationY = (1.0 - curvedAnimation.value) * 0.5; return Transform( alignment: Alignment.center, transform: Matrix4.identity() ..setEntry(3, 2, 0.001) ..rotateY(rotationY), child: child, ); }, child: child, ); }, ); } } /// Extensions pour faciliter l'utilisation des transitions extension NavigatorTransitions on NavigatorState { /// Navigation avec transition de glissement depuis la droite Future pushSlideFromRight(Widget page) { return push(PageTransitions.slideFromRight(page)); } /// Navigation avec transition de glissement depuis le bas Future pushSlideFromBottom(Widget page) { return push(PageTransitions.slideFromBottom(page)); } /// Navigation avec transition de fondu Future pushFadeIn(Widget page) { return push(PageTransitions.fadeIn(page)); } /// Navigation avec transition d'échelle et fondu Future pushScaleWithFade(Widget page) { return push(PageTransitions.scaleWithFade(page)); } /// Navigation avec transition de rebond Future pushBounceIn(Widget page) { return push(PageTransitions.bounceIn(page)); } /// Navigation avec transition de parallaxe Future pushSlideWithParallax(Widget page) { return push(PageTransitions.slideWithParallax(page)); } /// Navigation avec transition de morphing Future pushMorphWithBlur(Widget page) { return push(PageTransitions.morphWithBlur(page)); } /// Navigation avec transition de rotation 3D Future pushRotate3D(Widget page) { return push(PageTransitions.rotate3D(page)); } } /// Widget d'animation pour les éléments de liste class AnimatedListItem extends StatefulWidget { final Widget child; final int index; final Duration delay; final Duration duration; final Curve curve; final Offset slideOffset; const AnimatedListItem({ super.key, required this.child, required this.index, this.delay = const Duration(milliseconds: 100), this.duration = const Duration(milliseconds: 500), this.curve = Curves.easeOutCubic, this.slideOffset = const Offset(0, 50), }); @override State createState() => _AnimatedListItemState(); } class _AnimatedListItemState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _fadeAnimation; late Animation _slideAnimation; @override void initState() { super.initState(); _controller = AnimationController( duration: widget.duration, vsync: this, ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _controller, curve: widget.curve, )); _slideAnimation = Tween( begin: widget.slideOffset, end: Offset.zero, ).animate(CurvedAnimation( parent: _controller, curve: widget.curve, )); // Démarrer l'animation avec un délai basé sur l'index Future.delayed( Duration(milliseconds: widget.delay.inMilliseconds * widget.index), () { if (mounted) { _controller.forward(); } }, ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.translate( offset: _slideAnimation.value, child: Opacity( opacity: _fadeAnimation.value, child: widget.child, ), ); }, ); } }