Versione OK Pour l'onglet événements.
This commit is contained in:
320
unionflow-mobile-apps/lib/core/animations/animated_button.dart
Normal file
320
unionflow-mobile-apps/lib/core/animations/animated_button.dart
Normal 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,
|
||||
}
|
||||
Reference in New Issue
Block a user