Versione OK Pour l'onglet événements.

This commit is contained in:
DahoudG
2025-09-15 20:15:34 +00:00
parent 8a619ee1bf
commit 12d514d866
73 changed files with 11508 additions and 674 deletions

View File

@@ -0,0 +1,320 @@
import 'package:flutter/material.dart';
import '../../shared/theme/app_theme.dart';
/// Bouton animé avec effets visuels sophistiqués
class AnimatedButton extends StatefulWidget {
final String text;
final IconData? icon;
final VoidCallback? onPressed;
final Color? backgroundColor;
final Color? foregroundColor;
final double? width;
final double? height;
final bool isLoading;
final AnimatedButtonStyle style;
const AnimatedButton({
super.key,
required this.text,
this.icon,
this.onPressed,
this.backgroundColor,
this.foregroundColor,
this.width,
this.height,
this.isLoading = false,
this.style = AnimatedButtonStyle.primary,
});
@override
State<AnimatedButton> createState() => _AnimatedButtonState();
}
class _AnimatedButtonState extends State<AnimatedButton>
with TickerProviderStateMixin {
late AnimationController _scaleController;
late AnimationController _shimmerController;
late AnimationController _loadingController;
late Animation<double> _scaleAnimation;
late Animation<double> _shimmerAnimation;
late Animation<double> _loadingAnimation;
bool _isPressed = false;
@override
void initState() {
super.initState();
_scaleController = AnimationController(
duration: const Duration(milliseconds: 150),
vsync: this,
);
_shimmerController = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
_loadingController = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _scaleController,
curve: Curves.easeInOut,
));
_shimmerAnimation = Tween<double>(
begin: -1.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _shimmerController,
curve: Curves.easeInOut,
));
_loadingAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _loadingController,
curve: Curves.easeInOut,
));
if (widget.isLoading) {
_loadingController.repeat();
}
}
@override
void didUpdateWidget(AnimatedButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isLoading != oldWidget.isLoading) {
if (widget.isLoading) {
_loadingController.repeat();
} else {
_loadingController.stop();
_loadingController.reset();
}
}
}
@override
void dispose() {
_scaleController.dispose();
_shimmerController.dispose();
_loadingController.dispose();
super.dispose();
}
void _onTapDown(TapDownDetails details) {
if (widget.onPressed != null && !widget.isLoading) {
setState(() => _isPressed = true);
_scaleController.forward();
}
}
void _onTapUp(TapUpDetails details) {
if (widget.onPressed != null && !widget.isLoading) {
setState(() => _isPressed = false);
_scaleController.reverse();
_shimmerController.forward().then((_) {
_shimmerController.reset();
});
}
}
void _onTapCancel() {
if (widget.onPressed != null && !widget.isLoading) {
setState(() => _isPressed = false);
_scaleController.reverse();
}
}
@override
Widget build(BuildContext context) {
final colors = _getColors();
return AnimatedBuilder(
animation: Listenable.merge([_scaleAnimation, _shimmerAnimation, _loadingAnimation]),
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
onTap: widget.onPressed != null && !widget.isLoading ? widget.onPressed : null,
child: Container(
width: widget.width,
height: widget.height ?? 56,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
colors.backgroundColor,
colors.backgroundColor.withOpacity(0.8),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: colors.backgroundColor.withOpacity(0.3),
blurRadius: _isPressed ? 4 : 8,
offset: Offset(0, _isPressed ? 2 : 4),
),
],
),
child: Stack(
children: [
// Effet shimmer
if (!widget.isLoading)
Positioned.fill(
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: AnimatedBuilder(
animation: _shimmerAnimation,
builder: (context, child) {
return Transform.translate(
offset: Offset(_shimmerAnimation.value * 200, 0),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.transparent,
Colors.white.withOpacity(0.2),
Colors.transparent,
],
stops: const [0.0, 0.5, 1.0],
),
),
),
);
},
),
),
),
// Contenu du bouton
Center(
child: widget.isLoading
? _buildLoadingContent(colors)
: _buildNormalContent(colors),
),
],
),
),
),
);
},
);
}
Widget _buildLoadingContent(_ButtonColors colors) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(colors.foregroundColor),
),
),
const SizedBox(width: 12),
Text(
'Chargement...',
style: TextStyle(
color: colors.foregroundColor,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
);
}
Widget _buildNormalContent(_ButtonColors colors) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.icon != null) ...[
Icon(
widget.icon,
color: colors.foregroundColor,
size: 20,
),
const SizedBox(width: 8),
],
Text(
widget.text,
style: TextStyle(
color: colors.foregroundColor,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
);
}
_ButtonColors _getColors() {
switch (widget.style) {
case AnimatedButtonStyle.primary:
return _ButtonColors(
backgroundColor: widget.backgroundColor ?? AppTheme.primaryColor,
foregroundColor: widget.foregroundColor ?? Colors.white,
);
case AnimatedButtonStyle.secondary:
return _ButtonColors(
backgroundColor: widget.backgroundColor ?? AppTheme.secondaryColor,
foregroundColor: widget.foregroundColor ?? Colors.white,
);
case AnimatedButtonStyle.success:
return _ButtonColors(
backgroundColor: widget.backgroundColor ?? AppTheme.successColor,
foregroundColor: widget.foregroundColor ?? Colors.white,
);
case AnimatedButtonStyle.warning:
return _ButtonColors(
backgroundColor: widget.backgroundColor ?? AppTheme.warningColor,
foregroundColor: widget.foregroundColor ?? Colors.white,
);
case AnimatedButtonStyle.error:
return _ButtonColors(
backgroundColor: widget.backgroundColor ?? AppTheme.errorColor,
foregroundColor: widget.foregroundColor ?? Colors.white,
);
case AnimatedButtonStyle.outline:
return _ButtonColors(
backgroundColor: widget.backgroundColor ?? Colors.transparent,
foregroundColor: widget.foregroundColor ?? AppTheme.primaryColor,
);
}
}
}
class _ButtonColors {
final Color backgroundColor;
final Color foregroundColor;
_ButtonColors({
required this.backgroundColor,
required this.foregroundColor,
});
}
enum AnimatedButtonStyle {
primary,
secondary,
success,
warning,
error,
outline,
}

View File

@@ -0,0 +1,352 @@
import 'package:flutter/material.dart';
import '../../shared/theme/app_theme.dart';
/// Service de notifications animées
class AnimatedNotifications {
static OverlayEntry? _currentOverlay;
/// Affiche une notification de succès
static void showSuccess(
BuildContext context,
String message, {
Duration duration = const Duration(seconds: 3),
}) {
_showNotification(
context,
message,
NotificationType.success,
duration,
);
}
/// Affiche une notification d'erreur
static void showError(
BuildContext context,
String message, {
Duration duration = const Duration(seconds: 4),
}) {
_showNotification(
context,
message,
NotificationType.error,
duration,
);
}
/// Affiche une notification d'information
static void showInfo(
BuildContext context,
String message, {
Duration duration = const Duration(seconds: 3),
}) {
_showNotification(
context,
message,
NotificationType.info,
duration,
);
}
/// Affiche une notification d'avertissement
static void showWarning(
BuildContext context,
String message, {
Duration duration = const Duration(seconds: 3),
}) {
_showNotification(
context,
message,
NotificationType.warning,
duration,
);
}
static void _showNotification(
BuildContext context,
String message,
NotificationType type,
Duration duration,
) {
// Supprimer la notification précédente si elle existe
_currentOverlay?.remove();
final overlay = Overlay.of(context);
late OverlayEntry overlayEntry;
overlayEntry = OverlayEntry(
builder: (context) => AnimatedNotificationWidget(
message: message,
type: type,
onDismiss: () {
overlayEntry.remove();
_currentOverlay = null;
},
),
);
_currentOverlay = overlayEntry;
overlay.insert(overlayEntry);
// Auto-dismiss après la durée spécifiée
Future.delayed(duration, () {
if (_currentOverlay == overlayEntry) {
overlayEntry.remove();
_currentOverlay = null;
}
});
}
/// Masque la notification actuelle
static void dismiss() {
_currentOverlay?.remove();
_currentOverlay = null;
}
}
/// Widget de notification animée
class AnimatedNotificationWidget extends StatefulWidget {
final String message;
final NotificationType type;
final VoidCallback onDismiss;
const AnimatedNotificationWidget({
super.key,
required this.message,
required this.type,
required this.onDismiss,
});
@override
State<AnimatedNotificationWidget> createState() => _AnimatedNotificationWidgetState();
}
class _AnimatedNotificationWidgetState extends State<AnimatedNotificationWidget>
with TickerProviderStateMixin {
late AnimationController _slideController;
late AnimationController _fadeController;
late AnimationController _scaleController;
late Animation<Offset> _slideAnimation;
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_slideController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_fadeController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_scaleController = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_slideAnimation = Tween<Offset>(
begin: const Offset(0, -1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _slideController,
curve: Curves.elasticOut,
));
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _fadeController,
curve: Curves.easeOut,
));
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 1.05,
).animate(CurvedAnimation(
parent: _scaleController,
curve: Curves.easeInOut,
));
// Démarrer les animations d'entrée
_fadeController.forward();
_slideController.forward();
}
@override
void dispose() {
_slideController.dispose();
_fadeController.dispose();
_scaleController.dispose();
super.dispose();
}
void _dismiss() async {
await _fadeController.reverse();
widget.onDismiss();
}
@override
Widget build(BuildContext context) {
final colors = _getColors();
return Positioned(
top: MediaQuery.of(context).padding.top + 16,
left: 16,
right: 16,
child: AnimatedBuilder(
animation: Listenable.merge([
_slideAnimation,
_fadeAnimation,
_scaleAnimation,
]),
builder: (context, child) {
return SlideTransition(
position: _slideAnimation,
child: FadeTransition(
opacity: _fadeAnimation,
child: Transform.scale(
scale: _scaleAnimation.value,
child: GestureDetector(
onTap: () => _scaleController.forward().then((_) {
_scaleController.reverse();
}),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
colors.backgroundColor,
colors.backgroundColor.withOpacity(0.9),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: colors.backgroundColor.withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
// Icône
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: colors.iconBackgroundColor,
borderRadius: BorderRadius.circular(12),
),
child: Icon(
colors.icon,
color: colors.iconColor,
size: 24,
),
),
const SizedBox(width: 12),
// Message
Expanded(
child: Text(
widget.message,
style: TextStyle(
color: colors.textColor,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
// Bouton de fermeture
GestureDetector(
onTap: _dismiss,
child: Container(
padding: const EdgeInsets.all(4),
child: Icon(
Icons.close,
color: colors.textColor.withOpacity(0.7),
size: 20,
),
),
),
],
),
),
),
),
),
);
},
),
);
}
_NotificationColors _getColors() {
switch (widget.type) {
case NotificationType.success:
return _NotificationColors(
backgroundColor: AppTheme.successColor,
textColor: Colors.white,
icon: Icons.check_circle,
iconColor: Colors.white,
iconBackgroundColor: Colors.white.withOpacity(0.2),
);
case NotificationType.error:
return _NotificationColors(
backgroundColor: AppTheme.errorColor,
textColor: Colors.white,
icon: Icons.error,
iconColor: Colors.white,
iconBackgroundColor: Colors.white.withOpacity(0.2),
);
case NotificationType.warning:
return _NotificationColors(
backgroundColor: AppTheme.warningColor,
textColor: Colors.white,
icon: Icons.warning,
iconColor: Colors.white,
iconBackgroundColor: Colors.white.withOpacity(0.2),
);
case NotificationType.info:
return _NotificationColors(
backgroundColor: AppTheme.primaryColor,
textColor: Colors.white,
icon: Icons.info,
iconColor: Colors.white,
iconBackgroundColor: Colors.white.withOpacity(0.2),
);
}
}
}
class _NotificationColors {
final Color backgroundColor;
final Color textColor;
final IconData icon;
final Color iconColor;
final Color iconBackgroundColor;
_NotificationColors({
required this.backgroundColor,
required this.textColor,
required this.icon,
required this.iconColor,
required this.iconBackgroundColor,
});
}
enum NotificationType {
success,
error,
warning,
info,
}

View File

@@ -0,0 +1,368 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// Widget avec micro-interactions pour les boutons
class InteractiveButton extends StatefulWidget {
final Widget child;
final VoidCallback? onPressed;
final Color? backgroundColor;
final Color? foregroundColor;
final EdgeInsetsGeometry? padding;
final BorderRadius? borderRadius;
final bool enableHapticFeedback;
final bool enableSoundFeedback;
final Duration animationDuration;
const InteractiveButton({
super.key,
required this.child,
this.onPressed,
this.backgroundColor,
this.foregroundColor,
this.padding,
this.borderRadius,
this.enableHapticFeedback = true,
this.enableSoundFeedback = false,
this.animationDuration = const Duration(milliseconds: 150),
});
@override
State<InteractiveButton> createState() => _InteractiveButtonState();
}
class _InteractiveButtonState extends State<InteractiveButton>
with TickerProviderStateMixin {
late AnimationController _scaleController;
late AnimationController _rippleController;
late Animation<double> _scaleAnimation;
late Animation<double> _rippleAnimation;
bool _isPressed = false;
@override
void initState() {
super.initState();
_scaleController = AnimationController(
duration: widget.animationDuration,
vsync: this,
);
_rippleController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _scaleController,
curve: Curves.easeInOut,
));
_rippleAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _rippleController,
curve: Curves.easeOut,
));
}
@override
void dispose() {
_scaleController.dispose();
_rippleController.dispose();
super.dispose();
}
void _handleTapDown(TapDownDetails details) {
if (widget.onPressed != null) {
setState(() => _isPressed = true);
_scaleController.forward();
_rippleController.forward();
if (widget.enableHapticFeedback) {
HapticFeedback.lightImpact();
}
}
}
void _handleTapUp(TapUpDetails details) {
_handleTapEnd();
}
void _handleTapCancel() {
_handleTapEnd();
}
void _handleTapEnd() {
if (_isPressed) {
setState(() => _isPressed = false);
_scaleController.reverse();
Future.delayed(const Duration(milliseconds: 100), () {
_rippleController.reverse();
});
}
}
void _handleTap() {
if (widget.onPressed != null) {
if (widget.enableSoundFeedback) {
SystemSound.play(SystemSoundType.click);
}
widget.onPressed!();
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: _handleTapDown,
onTapUp: _handleTapUp,
onTapCancel: _handleTapCancel,
onTap: _handleTap,
child: AnimatedBuilder(
animation: Listenable.merge([_scaleAnimation, _rippleAnimation]),
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Container(
padding: widget.padding ?? const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
decoration: BoxDecoration(
color: widget.backgroundColor ?? Theme.of(context).primaryColor,
borderRadius: widget.borderRadius ?? BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: (widget.backgroundColor ?? Theme.of(context).primaryColor)
.withOpacity(0.3),
blurRadius: _isPressed ? 8 : 12,
offset: Offset(0, _isPressed ? 2 : 4),
spreadRadius: _isPressed ? 0 : 2,
),
],
),
child: Stack(
alignment: Alignment.center,
children: [
// Effet de ripple
if (_rippleAnimation.value > 0)
Positioned.fill(
child: Container(
decoration: BoxDecoration(
borderRadius: widget.borderRadius ?? BorderRadius.circular(8),
color: Colors.white.withOpacity(
0.2 * _rippleAnimation.value,
),
),
),
),
// Contenu du bouton
DefaultTextStyle(
style: TextStyle(
color: widget.foregroundColor ?? Colors.white,
fontWeight: FontWeight.w600,
),
child: widget.child,
),
],
),
),
);
},
),
);
}
}
/// Widget avec effet de parallax pour les cartes
class ParallaxCard extends StatefulWidget {
final Widget child;
final double parallaxOffset;
final Duration animationDuration;
const ParallaxCard({
super.key,
required this.child,
this.parallaxOffset = 20.0,
this.animationDuration = const Duration(milliseconds: 200),
});
@override
State<ParallaxCard> createState() => _ParallaxCardState();
}
class _ParallaxCardState extends State<ParallaxCard>
with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _offsetAnimation;
late Animation<double> _elevationAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.animationDuration,
vsync: this,
);
_offsetAnimation = Tween<Offset>(
begin: Offset.zero,
end: Offset(0, -widget.parallaxOffset),
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeOut,
));
_elevationAnimation = Tween<double>(
begin: 4.0,
end: 12.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeOut,
));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => _controller.forward(),
onExit: (_) => _controller.reverse(),
child: GestureDetector(
onTapDown: (_) => _controller.forward(),
onTapUp: (_) => _controller.reverse(),
onTapCancel: () => _controller.reverse(),
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.translate(
offset: _offsetAnimation.value,
child: Card(
elevation: _elevationAnimation.value,
child: widget.child,
),
);
},
),
),
);
}
}
/// Widget avec effet de morphing pour les icônes
class MorphingIcon extends StatefulWidget {
final IconData icon;
final IconData? alternateIcon;
final double size;
final Color? color;
final Duration animationDuration;
final VoidCallback? onPressed;
const MorphingIcon({
super.key,
required this.icon,
this.alternateIcon,
this.size = 24.0,
this.color,
this.animationDuration = const Duration(milliseconds: 300),
this.onPressed,
});
@override
State<MorphingIcon> createState() => _MorphingIconState();
}
class _MorphingIconState extends State<MorphingIcon>
with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _rotationAnimation;
bool _isAlternate = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.animationDuration,
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.5, curve: Curves.easeIn),
));
_rotationAnimation = Tween<double>(
begin: 0.0,
end: 0.5,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() {
_isAlternate = !_isAlternate;
});
_controller.reverse();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleTap() {
if (widget.alternateIcon != null) {
_controller.forward();
}
widget.onPressed?.call();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleTap,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value == 0.0 ? 1.0 : _scaleAnimation.value,
child: Transform.rotate(
angle: _rotationAnimation.value * 3.14159,
child: Icon(
_isAlternate && widget.alternateIcon != null
? widget.alternateIcon!
: widget.icon,
size: widget.size,
color: widget.color,
),
),
);
},
),
);
}
}

View File

@@ -176,6 +176,72 @@ class PageTransitions {
},
);
}
/// 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
@@ -209,6 +275,16 @@ extension NavigatorTransitions on NavigatorState {
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