import 'package:flutter/material.dart'; import '../../shared/theme/app_theme.dart'; /// Animations de chargement personnalisées class LoadingAnimations { /// Indicateur de chargement avec points animés static Widget dots({ Color color = AppTheme.primaryColor, double size = 8.0, Duration duration = const Duration(milliseconds: 1200), }) { return _DotsLoadingAnimation( color: color, size: size, duration: duration, ); } /// Indicateur de chargement avec vagues static Widget waves({ Color color = AppTheme.primaryColor, double size = 40.0, Duration duration = const Duration(milliseconds: 1000), }) { return _WavesLoadingAnimation( color: color, size: size, duration: duration, ); } /// Indicateur de chargement avec rotation static Widget spinner({ Color color = AppTheme.primaryColor, double size = 40.0, double strokeWidth = 4.0, Duration duration = const Duration(milliseconds: 1000), }) { return _SpinnerLoadingAnimation( color: color, size: size, strokeWidth: strokeWidth, duration: duration, ); } /// Indicateur de chargement avec pulsation static Widget pulse({ Color color = AppTheme.primaryColor, double size = 40.0, Duration duration = const Duration(milliseconds: 1000), }) { return _PulseLoadingAnimation( color: color, size: size, duration: duration, ); } /// Skeleton loader pour les cartes static Widget skeleton({ double height = 100.0, double width = double.infinity, BorderRadius? borderRadius, Duration duration = const Duration(milliseconds: 1500), }) { return _SkeletonLoadingAnimation( height: height, width: width, borderRadius: borderRadius ?? BorderRadius.circular(8), duration: duration, ); } } /// Animation de points qui rebondissent class _DotsLoadingAnimation extends StatefulWidget { final Color color; final double size; final Duration duration; const _DotsLoadingAnimation({ required this.color, required this.size, required this.duration, }); @override State<_DotsLoadingAnimation> createState() => _DotsLoadingAnimationState(); } class _DotsLoadingAnimationState extends State<_DotsLoadingAnimation> with TickerProviderStateMixin { late List _controllers; late List> _animations; @override void initState() { super.initState(); _controllers = List.generate(3, (index) { return AnimationController( duration: widget.duration, vsync: this, ); }); _animations = _controllers.map((controller) { return Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: controller, curve: Curves.easeInOut), ); }).toList(); _startAnimations(); } void _startAnimations() { for (int i = 0; i < _controllers.length; i++) { Future.delayed(Duration(milliseconds: i * 200), () { if (mounted) { _controllers[i].repeat(reverse: true); } }); } } @override void dispose() { for (final controller in _controllers) { controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: List.generate(3, (index) { return AnimatedBuilder( animation: _animations[index], builder: (context, child) { return Container( margin: EdgeInsets.symmetric(horizontal: widget.size * 0.2), child: Transform.translate( offset: Offset(0, -widget.size * _animations[index].value), child: Container( width: widget.size, height: widget.size, decoration: BoxDecoration( color: widget.color, shape: BoxShape.circle, ), ), ), ); }, ); }), ); } } /// Animation de vagues class _WavesLoadingAnimation extends StatefulWidget { final Color color; final double size; final Duration duration; const _WavesLoadingAnimation({ required this.color, required this.size, required this.duration, }); @override State<_WavesLoadingAnimation> createState() => _WavesLoadingAnimationState(); } class _WavesLoadingAnimationState extends State<_WavesLoadingAnimation> with TickerProviderStateMixin { late List _controllers; late List> _animations; @override void initState() { super.initState(); _controllers = List.generate(4, (index) { return AnimationController( duration: widget.duration, vsync: this, ); }); _animations = _controllers.map((controller) { return Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: controller, curve: Curves.easeInOut), ); }).toList(); _startAnimations(); } void _startAnimations() { for (int i = 0; i < _controllers.length; i++) { Future.delayed(Duration(milliseconds: i * 150), () { if (mounted) { _controllers[i].repeat(); } }); } } @override void dispose() { for (final controller in _controllers) { controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return SizedBox( width: widget.size, height: widget.size, child: Stack( alignment: Alignment.center, children: List.generate(4, (index) { return AnimatedBuilder( animation: _animations[index], builder: (context, child) { return Container( width: widget.size * _animations[index].value, height: widget.size * _animations[index].value, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: widget.color.withOpacity(1 - _animations[index].value), width: 2, ), ), ); }, ); }), ), ); } } /// Animation de spinner personnalisé class _SpinnerLoadingAnimation extends StatefulWidget { final Color color; final double size; final double strokeWidth; final Duration duration; const _SpinnerLoadingAnimation({ required this.color, required this.size, required this.strokeWidth, required this.duration, }); @override State<_SpinnerLoadingAnimation> createState() => _SpinnerLoadingAnimationState(); } class _SpinnerLoadingAnimationState extends State<_SpinnerLoadingAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( duration: widget.duration, vsync: this, )..repeat(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.rotate( angle: _controller.value * 2 * 3.14159, child: SizedBox( width: widget.size, height: widget.size, child: CircularProgressIndicator( strokeWidth: widget.strokeWidth, valueColor: AlwaysStoppedAnimation(widget.color), backgroundColor: widget.color.withOpacity(0.2), ), ), ); }, ); } } /// Animation de pulsation class _PulseLoadingAnimation extends StatefulWidget { final Color color; final double size; final Duration duration; const _PulseLoadingAnimation({ required this.color, required this.size, required this.duration, }); @override State<_PulseLoadingAnimation> createState() => _PulseLoadingAnimationState(); } class _PulseLoadingAnimationState extends State<_PulseLoadingAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: widget.duration, vsync: this, ); _animation = Tween(begin: 0.8, end: 1.2).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); _controller.repeat(reverse: true); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (context, child) { return Transform.scale( scale: _animation.value, child: Container( width: widget.size, height: widget.size, decoration: BoxDecoration( color: widget.color, shape: BoxShape.circle, ), ), ); }, ); } } /// Animation skeleton pour le chargement de contenu class _SkeletonLoadingAnimation extends StatefulWidget { final double height; final double width; final BorderRadius borderRadius; final Duration duration; const _SkeletonLoadingAnimation({ required this.height, required this.width, required this.borderRadius, required this.duration, }); @override State<_SkeletonLoadingAnimation> createState() => _SkeletonLoadingAnimationState(); } class _SkeletonLoadingAnimationState extends State<_SkeletonLoadingAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: widget.duration, vsync: this, ); _animation = Tween(begin: -1.0, end: 2.0).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); _controller.repeat(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animation, builder: (context, child) { return Container( width: widget.width, height: widget.height, decoration: BoxDecoration( borderRadius: widget.borderRadius, gradient: LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, stops: [ (_animation.value - 0.3).clamp(0.0, 1.0), _animation.value.clamp(0.0, 1.0), (_animation.value + 0.3).clamp(0.0, 1.0), ], colors: const [ Color(0xFFE0E0E0), Color(0xFFF5F5F5), Color(0xFFE0E0E0), ], ), ), ); }, ); } }