first commit
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user