first commit
This commit is contained in:
@@ -0,0 +1,400 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../../theme/app_theme.dart';
|
||||
|
||||
enum FABVariant {
|
||||
primary,
|
||||
secondary,
|
||||
gradient,
|
||||
glass,
|
||||
morphing,
|
||||
}
|
||||
|
||||
enum FABSize {
|
||||
small,
|
||||
regular,
|
||||
large,
|
||||
extended,
|
||||
}
|
||||
|
||||
class SophisticatedFAB extends StatefulWidget {
|
||||
final IconData? icon;
|
||||
final String? label;
|
||||
final VoidCallback? onPressed;
|
||||
final FABVariant variant;
|
||||
final FABSize size;
|
||||
final Color? backgroundColor;
|
||||
final Color? foregroundColor;
|
||||
final Gradient? gradient;
|
||||
final bool animated;
|
||||
final bool showPulse;
|
||||
final List<IconData>? morphingIcons;
|
||||
final Duration morphingDuration;
|
||||
final String? tooltip;
|
||||
|
||||
const SophisticatedFAB({
|
||||
super.key,
|
||||
this.icon,
|
||||
this.label,
|
||||
this.onPressed,
|
||||
this.variant = FABVariant.primary,
|
||||
this.size = FABSize.regular,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.gradient,
|
||||
this.animated = true,
|
||||
this.showPulse = false,
|
||||
this.morphingIcons,
|
||||
this.morphingDuration = const Duration(seconds: 2),
|
||||
this.tooltip,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SophisticatedFAB> createState() => _SophisticatedFABState();
|
||||
}
|
||||
|
||||
class _SophisticatedFABState extends State<SophisticatedFAB>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _scaleController;
|
||||
late AnimationController _rotationController;
|
||||
late AnimationController _pulseController;
|
||||
late AnimationController _morphingController;
|
||||
|
||||
late Animation<double> _scaleAnimation;
|
||||
late Animation<double> _rotationAnimation;
|
||||
late Animation<double> _pulseAnimation;
|
||||
|
||||
int _currentMorphingIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_scaleController = AnimationController(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_rotationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_pulseController = AnimationController(
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_morphingController = AnimationController(
|
||||
duration: widget.morphingDuration,
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_scaleAnimation = Tween<double>(
|
||||
begin: 1.0,
|
||||
end: 0.9,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _scaleController,
|
||||
curve: Curves.easeInOut,
|
||||
));
|
||||
|
||||
_rotationAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _rotationController,
|
||||
curve: Curves.elasticOut,
|
||||
));
|
||||
|
||||
_pulseAnimation = Tween<double>(
|
||||
begin: 1.0,
|
||||
end: 1.2,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _pulseController,
|
||||
curve: Curves.easeInOut,
|
||||
));
|
||||
|
||||
if (widget.showPulse) {
|
||||
_pulseController.repeat(reverse: true);
|
||||
}
|
||||
|
||||
if (widget.morphingIcons != null && widget.morphingIcons!.isNotEmpty) {
|
||||
_startMorphing();
|
||||
}
|
||||
}
|
||||
|
||||
void _startMorphing() {
|
||||
_morphingController.addListener(() {
|
||||
if (_morphingController.isCompleted) {
|
||||
setState(() {
|
||||
_currentMorphingIndex =
|
||||
(_currentMorphingIndex + 1) % widget.morphingIcons!.length;
|
||||
});
|
||||
_morphingController.reset();
|
||||
_morphingController.forward();
|
||||
}
|
||||
});
|
||||
_morphingController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scaleController.dispose();
|
||||
_rotationController.dispose();
|
||||
_pulseController.dispose();
|
||||
_morphingController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final config = _getFABConfig();
|
||||
|
||||
Widget fab = AnimatedBuilder(
|
||||
animation: Listenable.merge([
|
||||
_scaleController,
|
||||
_rotationController,
|
||||
_pulseController,
|
||||
]),
|
||||
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.1 : 0.0,
|
||||
child: Container(
|
||||
width: _getSize(),
|
||||
height: _getSize(),
|
||||
decoration: _getDecoration(config),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: _handleTap,
|
||||
onTapDown: widget.animated ? (_) => _scaleController.forward() : null,
|
||||
onTapUp: widget.animated ? (_) => _scaleController.reverse() : null,
|
||||
onTapCancel: widget.animated ? () => _scaleController.reverse() : null,
|
||||
customBorder: const CircleBorder(),
|
||||
child: _buildContent(config),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (widget.tooltip != null) {
|
||||
fab = Tooltip(
|
||||
message: widget.tooltip!,
|
||||
child: fab,
|
||||
);
|
||||
}
|
||||
|
||||
return fab;
|
||||
}
|
||||
|
||||
Widget _buildContent(_FABConfig config) {
|
||||
if (widget.size == FABSize.extended && widget.label != null) {
|
||||
return _buildExtendedContent(config);
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: _buildIcon(config),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExtendedContent(_FABConfig config) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildIcon(config),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
widget.label!,
|
||||
style: TextStyle(
|
||||
color: config.foregroundColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIcon(_FABConfig config) {
|
||||
IconData iconToShow = widget.icon ?? Icons.add;
|
||||
|
||||
if (widget.morphingIcons != null && widget.morphingIcons!.isNotEmpty) {
|
||||
iconToShow = widget.morphingIcons![_currentMorphingIndex];
|
||||
}
|
||||
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
transitionBuilder: (child, animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation,
|
||||
child: RotationTransition(
|
||||
turns: animation,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
iconToShow,
|
||||
key: ValueKey(iconToShow),
|
||||
color: config.foregroundColor,
|
||||
size: _getIconSize(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_FABConfig _getFABConfig() {
|
||||
switch (widget.variant) {
|
||||
case FABVariant.primary:
|
||||
return _FABConfig(
|
||||
backgroundColor: widget.backgroundColor ?? AppTheme.primaryColor,
|
||||
foregroundColor: widget.foregroundColor ?? Colors.white,
|
||||
hasElevation: true,
|
||||
);
|
||||
|
||||
case FABVariant.secondary:
|
||||
return _FABConfig(
|
||||
backgroundColor: widget.backgroundColor ?? AppTheme.secondaryColor,
|
||||
foregroundColor: widget.foregroundColor ?? Colors.white,
|
||||
hasElevation: true,
|
||||
);
|
||||
|
||||
case FABVariant.gradient:
|
||||
return _FABConfig(
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: widget.foregroundColor ?? Colors.white,
|
||||
hasElevation: true,
|
||||
useGradient: true,
|
||||
);
|
||||
|
||||
case FABVariant.glass:
|
||||
return _FABConfig(
|
||||
backgroundColor: Colors.white.withOpacity(0.2),
|
||||
foregroundColor: widget.foregroundColor ?? AppTheme.textPrimary,
|
||||
borderColor: Colors.white.withOpacity(0.3),
|
||||
hasElevation: true,
|
||||
isGlass: true,
|
||||
);
|
||||
|
||||
case FABVariant.morphing:
|
||||
return _FABConfig(
|
||||
backgroundColor: widget.backgroundColor ?? AppTheme.accentColor,
|
||||
foregroundColor: widget.foregroundColor ?? Colors.white,
|
||||
hasElevation: true,
|
||||
isMorphing: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Decoration _getDecoration(_FABConfig config) {
|
||||
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,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: config.hasElevation ? _getShadow(config) : null,
|
||||
);
|
||||
}
|
||||
|
||||
return BoxDecoration(
|
||||
color: config.backgroundColor,
|
||||
shape: BoxShape.circle,
|
||||
border: config.borderColor != null
|
||||
? Border.all(color: config.borderColor!, width: 1)
|
||||
: null,
|
||||
boxShadow: config.hasElevation ? _getShadow(config) : null,
|
||||
);
|
||||
}
|
||||
|
||||
List<BoxShadow> _getShadow(_FABConfig config) {
|
||||
final shadowColor = config.useGradient
|
||||
? (widget.backgroundColor ?? AppTheme.primaryColor)
|
||||
: config.backgroundColor;
|
||||
|
||||
return [
|
||||
BoxShadow(
|
||||
color: shadowColor.withOpacity(0.4),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
BoxShadow(
|
||||
color: shadowColor.withOpacity(0.2),
|
||||
blurRadius: 40,
|
||||
offset: const Offset(0, 16),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
double _getSize() {
|
||||
switch (widget.size) {
|
||||
case FABSize.small:
|
||||
return 40;
|
||||
case FABSize.regular:
|
||||
return 56;
|
||||
case FABSize.large:
|
||||
return 72;
|
||||
case FABSize.extended:
|
||||
return 56; // Height for extended FAB
|
||||
}
|
||||
}
|
||||
|
||||
double _getIconSize() {
|
||||
switch (widget.size) {
|
||||
case FABSize.small:
|
||||
return 20;
|
||||
case FABSize.regular:
|
||||
return 24;
|
||||
case FABSize.large:
|
||||
return 32;
|
||||
case FABSize.extended:
|
||||
return 24;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleTap() {
|
||||
HapticFeedback.lightImpact();
|
||||
|
||||
if (widget.animated) {
|
||||
_rotationController.forward().then((_) {
|
||||
_rotationController.reverse();
|
||||
});
|
||||
}
|
||||
|
||||
widget.onPressed?.call();
|
||||
}
|
||||
}
|
||||
|
||||
class _FABConfig {
|
||||
final Color backgroundColor;
|
||||
final Color foregroundColor;
|
||||
final Color? borderColor;
|
||||
final bool hasElevation;
|
||||
final bool useGradient;
|
||||
final bool isGlass;
|
||||
final bool isMorphing;
|
||||
|
||||
_FABConfig({
|
||||
required this.backgroundColor,
|
||||
required this.foregroundColor,
|
||||
this.borderColor,
|
||||
this.hasElevation = false,
|
||||
this.useGradient = false,
|
||||
this.isGlass = false,
|
||||
this.isMorphing = false,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user