Files
unionflow-server-api/unionflow-mobile-apps/lib/core/animations/page_transitions.dart
2025-09-15 20:15:34 +00:00

376 lines
11 KiB
Dart

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<T> slideFromRight<T>(Widget page) {
return PageRouteBuilder<T>(
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<T> slideFromBottom<T>(Widget page) {
return PageRouteBuilder<T>(
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<T> fadeIn<T>(Widget page) {
return PageRouteBuilder<T>(
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<T> scaleWithFade<T>(Widget page) {
return PageRouteBuilder<T>(
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<T> rotateScale<T>(Widget page) {
return PageRouteBuilder<T>(
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<T> bounceIn<T>(Widget page) {
return PageRouteBuilder<T>(
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<T> slideWithParallax<T>(Widget page) {
return PageRouteBuilder<T>(
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<T> morphWithBlur<T>(Widget page) {
return PageRouteBuilder<T>(
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<double>(
begin: 0.8,
end: 1.0,
).animate(curvedAnimation);
final fadeAnimation = Tween<double>(
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<T> rotate3D<T>(Widget page) {
return PageRouteBuilder<T>(
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<T?> pushSlideFromRight<T>(Widget page) {
return push<T>(PageTransitions.slideFromRight<T>(page));
}
/// Navigation avec transition de glissement depuis le bas
Future<T?> pushSlideFromBottom<T>(Widget page) {
return push<T>(PageTransitions.slideFromBottom<T>(page));
}
/// Navigation avec transition de fondu
Future<T?> pushFadeIn<T>(Widget page) {
return push<T>(PageTransitions.fadeIn<T>(page));
}
/// Navigation avec transition d'échelle et fondu
Future<T?> pushScaleWithFade<T>(Widget page) {
return push<T>(PageTransitions.scaleWithFade<T>(page));
}
/// Navigation avec transition de rebond
Future<T?> pushBounceIn<T>(Widget page) {
return push<T>(PageTransitions.bounceIn<T>(page));
}
/// Navigation avec transition de parallaxe
Future<T?> pushSlideWithParallax<T>(Widget page) {
return push<T>(PageTransitions.slideWithParallax<T>(page));
}
/// Navigation avec transition de morphing
Future<T?> pushMorphWithBlur<T>(Widget page) {
return push<T>(PageTransitions.morphWithBlur<T>(page));
}
/// Navigation avec transition de rotation 3D
Future<T?> pushRotate3D<T>(Widget page) {
return push<T>(PageTransitions.rotate3D<T>(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<AnimatedListItem> createState() => _AnimatedListItemState();
}
class _AnimatedListItemState extends State<AnimatedListItem>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: widget.curve,
));
_slideAnimation = Tween<Offset>(
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,
),
);
},
);
}
}