Files
unionflow-client-quarkus-pr…/unionflow-mobile-apps/lib/core/animations/animated_button.dart
2025-09-15 20:15:34 +00:00

321 lines
9.0 KiB
Dart

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,
}