Files
unionflow-client-quarkus-pr…/unionflow-mobile-apps/lib/shared/widgets/loading_button.dart
2025-08-20 21:00:35 +00:00

203 lines
5.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../theme/app_theme.dart';
class LoadingButton extends StatefulWidget {
final VoidCallback? onPressed;
final String text;
final bool isLoading;
final double? width;
final double height;
final Color? backgroundColor;
final Color? textColor;
final IconData? icon;
final bool enabled;
const LoadingButton({
super.key,
required this.onPressed,
required this.text,
this.isLoading = false,
this.width,
this.height = 48,
this.backgroundColor,
this.textColor,
this.icon,
this.enabled = true,
});
@override
State<LoadingButton> createState() => _LoadingButtonState();
}
class _LoadingButtonState extends State<LoadingButton>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
late Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 150),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
));
_opacityAnimation = Tween<double>(
begin: 1.0,
end: 0.8,
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
));
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
bool get _isEnabled => widget.enabled && !widget.isLoading && widget.onPressed != null;
Color get _backgroundColor {
if (!_isEnabled) {
return AppTheme.textHint.withOpacity(0.3);
}
return widget.backgroundColor ?? AppTheme.primaryColor;
}
Color get _textColor {
if (!_isEnabled) {
return AppTheme.textHint;
}
return widget.textColor ?? Colors.white;
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Opacity(
opacity: _opacityAnimation.value,
child: Container(
width: widget.width,
height: widget.height,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: _isEnabled
? [
BoxShadow(
color: _backgroundColor.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 4),
),
]
: null,
),
child: ElevatedButton(
onPressed: _isEnabled ? _handlePressed : null,
style: ElevatedButton.styleFrom(
backgroundColor: _backgroundColor,
foregroundColor: _textColor,
elevation: 0,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 24),
),
child: _buildButtonContent(),
),
),
),
);
},
);
}
Widget _buildButtonContent() {
if (widget.isLoading) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(_textColor),
strokeWidth: 2,
),
),
const SizedBox(width: 12),
Text(
'Chargement...',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: _textColor,
),
),
],
);
}
if (widget.icon != null) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
widget.icon,
size: 20,
color: _textColor,
),
const SizedBox(width: 8),
Text(
widget.text,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: _textColor,
),
),
],
);
}
return Text(
widget.text,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: _textColor,
),
);
}
void _handlePressed() {
if (!_isEnabled) return;
// Animation de pression
_animationController.forward().then((_) {
_animationController.reverse();
});
// Vibration tactile
HapticFeedback.lightImpact();
// Callback
widget.onPressed?.call();
}
}