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

356 lines
9.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../theme/app_theme.dart';
import '../badges/count_badge.dart';
enum IconButtonVariant {
standard,
filled,
outlined,
ghost,
gradient,
glass,
}
enum IconButtonShape {
circle,
rounded,
square,
}
class SophisticatedIconButton extends StatefulWidget {
final IconData icon;
final VoidCallback? onPressed;
final VoidCallback? onLongPress;
final IconButtonVariant variant;
final IconButtonShape shape;
final double? size;
final Color? backgroundColor;
final Color? foregroundColor;
final Color? borderColor;
final Gradient? gradient;
final bool animated;
final bool disabled;
final String? tooltip;
final Widget? badge;
final int? notificationCount;
final bool showPulse;
const SophisticatedIconButton({
super.key,
required this.icon,
this.onPressed,
this.onLongPress,
this.variant = IconButtonVariant.standard,
this.shape = IconButtonShape.circle,
this.size,
this.backgroundColor,
this.foregroundColor,
this.borderColor,
this.gradient,
this.animated = true,
this.disabled = false,
this.tooltip,
this.badge,
this.notificationCount,
this.showPulse = false,
});
@override
State<SophisticatedIconButton> createState() => _SophisticatedIconButtonState();
}
class _SophisticatedIconButtonState extends State<SophisticatedIconButton>
with TickerProviderStateMixin {
late AnimationController _pressController;
late AnimationController _pulseController;
late AnimationController _rotationController;
late Animation<double> _scaleAnimation;
late Animation<double> _pulseAnimation;
late Animation<double> _rotationAnimation;
@override
void initState() {
super.initState();
_pressController = AnimationController(
duration: const Duration(milliseconds: 100),
vsync: this,
);
_pulseController = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_rotationController = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.9,
).animate(CurvedAnimation(
parent: _pressController,
curve: Curves.easeInOut,
));
_pulseAnimation = Tween<double>(
begin: 1.0,
end: 1.1,
).animate(CurvedAnimation(
parent: _pulseController,
curve: Curves.easeInOut,
));
_rotationAnimation = Tween<double>(
begin: 0.0,
end: 0.25,
).animate(CurvedAnimation(
parent: _rotationController,
curve: Curves.elasticOut,
));
if (widget.showPulse) {
_pulseController.repeat(reverse: true);
}
}
@override
void dispose() {
_pressController.dispose();
_pulseController.dispose();
_rotationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final config = _getButtonConfig();
final buttonSize = widget.size ?? 48.0;
final iconSize = buttonSize * 0.5;
Widget button = AnimatedBuilder(
animation: Listenable.merge([_pressController, _pulseController, _rotationController]),
builder: (context, child) {
return Transform.scale(
scale: widget.animated
? _scaleAnimation.value * (widget.showPulse ? _pulseAnimation.value : 1.0)
: 1.0,
child: Transform.rotate(
angle: widget.animated ? _rotationAnimation.value : 0.0,
child: Container(
width: buttonSize,
height: buttonSize,
decoration: _getDecoration(config, buttonSize),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: widget.disabled ? null : _handleTap,
onLongPress: widget.disabled ? null : widget.onLongPress,
onTapDown: widget.animated && !widget.disabled ? (_) => _pressController.forward() : null,
onTapUp: widget.animated && !widget.disabled ? (_) => _pressController.reverse() : null,
onTapCancel: widget.animated && !widget.disabled ? () => _pressController.reverse() : null,
customBorder: _getInkWellBorder(buttonSize),
child: Center(
child: Icon(
widget.icon,
size: iconSize,
color: widget.disabled
? AppTheme.textHint
: config.foregroundColor,
),
),
),
),
),
),
);
},
);
// Add badge if provided
if (widget.badge != null || widget.notificationCount != null) {
button = Stack(
clipBehavior: Clip.none,
children: [
button,
if (widget.notificationCount != null)
Positioned(
top: -8,
right: -8,
child: CountBadge(
count: widget.notificationCount!,
size: 18,
),
),
if (widget.badge != null)
Positioned(
top: -4,
right: -4,
child: widget.badge!,
),
],
);
}
if (widget.tooltip != null) {
button = Tooltip(
message: widget.tooltip!,
child: button,
);
}
return button;
}
_IconButtonConfig _getButtonConfig() {
switch (widget.variant) {
case IconButtonVariant.standard:
return _IconButtonConfig(
backgroundColor: Colors.transparent,
foregroundColor: widget.foregroundColor ?? AppTheme.textPrimary,
hasElevation: false,
);
case IconButtonVariant.filled:
return _IconButtonConfig(
backgroundColor: widget.backgroundColor ?? AppTheme.primaryColor,
foregroundColor: widget.foregroundColor ?? Colors.white,
hasElevation: true,
);
case IconButtonVariant.outlined:
return _IconButtonConfig(
backgroundColor: Colors.transparent,
foregroundColor: widget.foregroundColor ?? AppTheme.primaryColor,
borderColor: widget.borderColor ?? AppTheme.primaryColor,
hasElevation: false,
);
case IconButtonVariant.ghost:
return _IconButtonConfig(
backgroundColor: (widget.backgroundColor ?? AppTheme.primaryColor).withOpacity(0.1),
foregroundColor: widget.foregroundColor ?? AppTheme.primaryColor,
hasElevation: false,
);
case IconButtonVariant.gradient:
return _IconButtonConfig(
backgroundColor: Colors.transparent,
foregroundColor: widget.foregroundColor ?? Colors.white,
hasElevation: true,
useGradient: true,
);
case IconButtonVariant.glass:
return _IconButtonConfig(
backgroundColor: Colors.white.withOpacity(0.2),
foregroundColor: widget.foregroundColor ?? AppTheme.textPrimary,
borderColor: Colors.white.withOpacity(0.3),
hasElevation: true,
isGlass: true,
);
}
}
Decoration _getDecoration(_IconButtonConfig config, double size) {
final borderRadius = _getBorderRadius(size);
if (config.useGradient) {
return BoxDecoration(
gradient: widget.gradient ?? LinearGradient(
colors: [
widget.backgroundColor ?? AppTheme.primaryColor,
(widget.backgroundColor ?? AppTheme.primaryColor).withOpacity(0.7),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: borderRadius,
boxShadow: config.hasElevation ? _getShadow(config, size) : null,
);
}
return BoxDecoration(
color: config.backgroundColor,
borderRadius: borderRadius,
border: config.borderColor != null
? Border.all(color: config.borderColor!, width: 1.5)
: null,
boxShadow: config.hasElevation && !widget.disabled ? _getShadow(config, size) : null,
);
}
BorderRadius _getBorderRadius(double size) {
switch (widget.shape) {
case IconButtonShape.circle:
return BorderRadius.circular(size / 2);
case IconButtonShape.rounded:
return BorderRadius.circular(size * 0.25);
case IconButtonShape.square:
return BorderRadius.circular(8);
}
}
ShapeBorder _getInkWellBorder(double size) {
switch (widget.shape) {
case IconButtonShape.circle:
return const CircleBorder();
case IconButtonShape.rounded:
return RoundedRectangleBorder(
borderRadius: BorderRadius.circular(size * 0.25),
);
case IconButtonShape.square:
return RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
);
}
}
List<BoxShadow> _getShadow(_IconButtonConfig config, double size) {
final shadowColor = config.useGradient
? (widget.backgroundColor ?? AppTheme.primaryColor)
: config.backgroundColor;
return [
BoxShadow(
color: shadowColor.withOpacity(0.3),
blurRadius: size * 0.3,
offset: Offset(0, size * 0.1),
),
];
}
void _handleTap() {
HapticFeedback.selectionClick();
if (widget.animated) {
_rotationController.forward().then((_) {
_rotationController.reverse();
});
}
widget.onPressed?.call();
}
}
class _IconButtonConfig {
final Color backgroundColor;
final Color foregroundColor;
final Color? borderColor;
final bool hasElevation;
final bool useGradient;
final bool isGlass;
_IconButtonConfig({
required this.backgroundColor,
required this.foregroundColor,
this.borderColor,
this.hasElevation = false,
this.useGradient = false,
this.isGlass = false,
});
}