first commit

This commit is contained in:
DahoudG
2025-08-20 21:00:35 +00:00
commit b2a23bdf89
583 changed files with 243074 additions and 0 deletions

View File

@@ -0,0 +1,383 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../theme/app_theme.dart';
enum ButtonGroupVariant {
segmented,
toggle,
tabs,
chips,
}
class ButtonGroupOption {
final String text;
final IconData? icon;
final String value;
final bool disabled;
final Widget? badge;
const ButtonGroupOption({
required this.text,
required this.value,
this.icon,
this.disabled = false,
this.badge,
});
}
class SophisticatedButtonGroup extends StatefulWidget {
final List<ButtonGroupOption> options;
final String? selectedValue;
final List<String>? selectedValues; // For multi-select
final Function(String)? onSelectionChanged;
final Function(List<String>)? onMultiSelectionChanged;
final ButtonGroupVariant variant;
final bool multiSelect;
final Color? backgroundColor;
final Color? selectedColor;
final Color? unselectedColor;
final double? height;
final EdgeInsets? padding;
final bool animated;
final bool fullWidth;
const SophisticatedButtonGroup({
super.key,
required this.options,
this.selectedValue,
this.selectedValues,
this.onSelectionChanged,
this.onMultiSelectionChanged,
this.variant = ButtonGroupVariant.segmented,
this.multiSelect = false,
this.backgroundColor,
this.selectedColor,
this.unselectedColor,
this.height,
this.padding,
this.animated = true,
this.fullWidth = false,
});
@override
State<SophisticatedButtonGroup> createState() => _SophisticatedButtonGroupState();
}
class _SophisticatedButtonGroupState extends State<SophisticatedButtonGroup>
with TickerProviderStateMixin {
late AnimationController _slideController;
late Animation<double> _slideAnimation;
String? _internalSelectedValue;
List<String> _internalSelectedValues = [];
@override
void initState() {
super.initState();
_slideController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_slideAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _slideController,
curve: Curves.easeInOut,
));
_internalSelectedValue = widget.selectedValue;
_internalSelectedValues = widget.selectedValues ?? [];
if (widget.animated) {
_slideController.forward();
}
}
@override
void didUpdateWidget(SophisticatedButtonGroup oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedValue != oldWidget.selectedValue) {
_internalSelectedValue = widget.selectedValue;
}
if (widget.selectedValues != oldWidget.selectedValues) {
_internalSelectedValues = widget.selectedValues ?? [];
}
}
@override
void dispose() {
_slideController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
switch (widget.variant) {
case ButtonGroupVariant.segmented:
return _buildSegmentedGroup();
case ButtonGroupVariant.toggle:
return _buildToggleGroup();
case ButtonGroupVariant.tabs:
return _buildTabsGroup();
case ButtonGroupVariant.chips:
return _buildChipsGroup();
}
}
Widget _buildSegmentedGroup() {
return AnimatedBuilder(
animation: _slideAnimation,
builder: (context, child) {
return Container(
height: widget.height ?? 48,
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: widget.backgroundColor ?? AppTheme.backgroundLight,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: AppTheme.textHint.withOpacity(0.2),
width: 1,
),
),
child: Row(
children: widget.options.asMap().entries.map((entry) {
final index = entry.key;
final option = entry.value;
final isSelected = _isSelected(option.value);
return Expanded(
child: _buildSegmentedButton(option, isSelected, index),
);
}).toList(),
),
);
},
);
}
Widget _buildSegmentedButton(ButtonGroupOption option, bool isSelected, int index) {
return AnimatedContainer(
duration: widget.animated ? const Duration(milliseconds: 200) : Duration.zero,
margin: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: isSelected
? (widget.selectedColor ?? AppTheme.primaryColor)
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
boxShadow: isSelected ? [
BoxShadow(
color: (widget.selectedColor ?? AppTheme.primaryColor).withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
] : null,
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: option.disabled ? null : () => _handleSelection(option.value),
borderRadius: BorderRadius.circular(8),
child: Container(
padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: _buildButtonContent(option, isSelected),
),
),
),
);
}
Widget _buildToggleGroup() {
return Wrap(
spacing: 8,
runSpacing: 8,
children: widget.options.map((option) {
final isSelected = _isSelected(option.value);
return _buildToggleButton(option, isSelected);
}).toList(),
);
}
Widget _buildToggleButton(ButtonGroupOption option, bool isSelected) {
return AnimatedContainer(
duration: widget.animated ? const Duration(milliseconds: 200) : Duration.zero,
decoration: BoxDecoration(
color: isSelected
? (widget.selectedColor ?? AppTheme.primaryColor)
: (widget.backgroundColor ?? Colors.transparent),
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: isSelected
? (widget.selectedColor ?? AppTheme.primaryColor)
: AppTheme.textHint.withOpacity(0.3),
width: 1.5,
),
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: option.disabled ? null : () => _handleSelection(option.value),
borderRadius: BorderRadius.circular(24),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: _buildButtonContent(option, isSelected),
),
),
),
);
}
Widget _buildTabsGroup() {
return Container(
height: widget.height ?? 44,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: AppTheme.textHint.withOpacity(0.2),
width: 1,
),
),
),
child: Row(
children: widget.options.asMap().entries.map((entry) {
final index = entry.key;
final option = entry.value;
final isSelected = _isSelected(option.value);
return widget.fullWidth
? Expanded(child: _buildTabButton(option, isSelected))
: _buildTabButton(option, isSelected);
}).toList(),
),
);
}
Widget _buildTabButton(ButtonGroupOption option, bool isSelected) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: option.disabled ? null : () => _handleSelection(option.value),
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: isSelected
? (widget.selectedColor ?? AppTheme.primaryColor)
: Colors.transparent,
width: 2,
),
),
),
child: _buildButtonContent(option, isSelected),
),
),
),
);
}
Widget _buildChipsGroup() {
return Wrap(
spacing: 8,
runSpacing: 8,
children: widget.options.map((option) {
final isSelected = _isSelected(option.value);
return _buildChip(option, isSelected);
}).toList(),
);
}
Widget _buildChip(ButtonGroupOption option, bool isSelected) {
return FilterChip(
label: _buildButtonContent(option, isSelected),
selected: isSelected,
onSelected: option.disabled ? null : (selected) => _handleSelection(option.value),
backgroundColor: widget.backgroundColor,
selectedColor: widget.selectedColor ?? AppTheme.primaryColor,
checkmarkColor: Colors.white,
labelStyle: TextStyle(
color: isSelected ? Colors.white : (widget.unselectedColor ?? AppTheme.textPrimary),
fontWeight: FontWeight.w600,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
);
}
Widget _buildButtonContent(ButtonGroupOption option, bool isSelected) {
final color = isSelected
? Colors.white
: (widget.unselectedColor ?? AppTheme.textSecondary);
if (option.icon != null) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
option.icon,
size: 16,
color: color,
),
const SizedBox(width: 6),
Text(
option.text,
style: TextStyle(
color: color,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
if (option.badge != null) ...[
const SizedBox(width: 6),
option.badge!,
],
],
);
}
return Text(
option.text,
style: TextStyle(
color: color,
fontWeight: FontWeight.w600,
fontSize: 14,
),
textAlign: TextAlign.center,
);
}
bool _isSelected(String value) {
if (widget.multiSelect) {
return _internalSelectedValues.contains(value);
}
return _internalSelectedValue == value;
}
void _handleSelection(String value) {
HapticFeedback.selectionClick();
if (widget.multiSelect) {
setState(() {
if (_internalSelectedValues.contains(value)) {
_internalSelectedValues.remove(value);
} else {
_internalSelectedValues.add(value);
}
});
widget.onMultiSelectionChanged?.call(_internalSelectedValues);
} else {
setState(() {
_internalSelectedValue = value;
});
widget.onSelectionChanged?.call(value);
}
}
}

View File

@@ -0,0 +1,303 @@
// Export all sophisticated button components
export 'sophisticated_button.dart';
export 'floating_action_button.dart';
export 'icon_button.dart';
export 'button_group.dart';
// Predefined button styles for quick usage
import 'package:flutter/material.dart';
import 'sophisticated_button.dart';
import 'floating_action_button.dart';
import 'icon_button.dart';
import '../../theme/app_theme.dart';
// Quick button factory methods
class QuickButtons {
// Primary buttons
static Widget primary({
required String text,
required VoidCallback onPressed,
IconData? icon,
ButtonSize size = ButtonSize.medium,
bool loading = false,
}) {
return SophisticatedButton(
text: text,
icon: icon,
onPressed: onPressed,
variant: ButtonVariant.primary,
size: size,
loading: loading,
);
}
static Widget secondary({
required String text,
required VoidCallback onPressed,
IconData? icon,
ButtonSize size = ButtonSize.medium,
bool loading = false,
}) {
return SophisticatedButton(
text: text,
icon: icon,
onPressed: onPressed,
variant: ButtonVariant.secondary,
size: size,
loading: loading,
);
}
static Widget outline({
required String text,
required VoidCallback onPressed,
IconData? icon,
ButtonSize size = ButtonSize.medium,
Color? color,
}) {
return SophisticatedButton(
text: text,
icon: icon,
onPressed: onPressed,
variant: ButtonVariant.outline,
size: size,
backgroundColor: color,
);
}
static Widget ghost({
required String text,
required VoidCallback onPressed,
IconData? icon,
ButtonSize size = ButtonSize.medium,
Color? color,
}) {
return SophisticatedButton(
text: text,
icon: icon,
onPressed: onPressed,
variant: ButtonVariant.ghost,
size: size,
backgroundColor: color,
);
}
static Widget gradient({
required String text,
required VoidCallback onPressed,
IconData? icon,
ButtonSize size = ButtonSize.medium,
Gradient? gradient,
}) {
return SophisticatedButton(
text: text,
icon: icon,
onPressed: onPressed,
variant: ButtonVariant.gradient,
size: size,
gradient: gradient,
);
}
static Widget glass({
required String text,
required VoidCallback onPressed,
IconData? icon,
ButtonSize size = ButtonSize.medium,
}) {
return SophisticatedButton(
text: text,
icon: icon,
onPressed: onPressed,
variant: ButtonVariant.glass,
size: size,
);
}
static Widget danger({
required String text,
required VoidCallback onPressed,
IconData? icon,
ButtonSize size = ButtonSize.medium,
}) {
return SophisticatedButton(
text: text,
icon: icon,
onPressed: onPressed,
variant: ButtonVariant.danger,
size: size,
);
}
static Widget success({
required String text,
required VoidCallback onPressed,
IconData? icon,
ButtonSize size = ButtonSize.medium,
}) {
return SophisticatedButton(
text: text,
icon: icon,
onPressed: onPressed,
variant: ButtonVariant.success,
size: size,
);
}
// Icon buttons
static Widget iconPrimary({
required IconData icon,
required VoidCallback onPressed,
double? size,
String? tooltip,
int? notificationCount,
}) {
return SophisticatedIconButton(
icon: icon,
onPressed: onPressed,
variant: IconButtonVariant.filled,
backgroundColor: AppTheme.primaryColor,
size: size,
tooltip: tooltip,
notificationCount: notificationCount,
);
}
static Widget iconSecondary({
required IconData icon,
required VoidCallback onPressed,
double? size,
String? tooltip,
int? notificationCount,
}) {
return SophisticatedIconButton(
icon: icon,
onPressed: onPressed,
variant: IconButtonVariant.filled,
backgroundColor: AppTheme.secondaryColor,
size: size,
tooltip: tooltip,
notificationCount: notificationCount,
);
}
static Widget iconOutline({
required IconData icon,
required VoidCallback onPressed,
double? size,
String? tooltip,
Color? color,
}) {
return SophisticatedIconButton(
icon: icon,
onPressed: onPressed,
variant: IconButtonVariant.outlined,
foregroundColor: color ?? AppTheme.primaryColor,
borderColor: color ?? AppTheme.primaryColor,
size: size,
tooltip: tooltip,
);
}
static Widget iconGhost({
required IconData icon,
required VoidCallback onPressed,
double? size,
String? tooltip,
Color? color,
}) {
return SophisticatedIconButton(
icon: icon,
onPressed: onPressed,
variant: IconButtonVariant.ghost,
backgroundColor: color ?? AppTheme.primaryColor,
size: size,
tooltip: tooltip,
);
}
static Widget iconGradient({
required IconData icon,
required VoidCallback onPressed,
double? size,
String? tooltip,
Gradient? gradient,
}) {
return SophisticatedIconButton(
icon: icon,
onPressed: onPressed,
variant: IconButtonVariant.gradient,
gradient: gradient,
size: size,
tooltip: tooltip,
);
}
// FAB buttons
static Widget fab({
required VoidCallback onPressed,
IconData icon = Icons.add,
FABVariant variant = FABVariant.primary,
FABSize size = FABSize.regular,
String? tooltip,
}) {
return SophisticatedFAB(
icon: icon,
onPressed: onPressed,
variant: variant,
size: size,
tooltip: tooltip,
);
}
static Widget fabExtended({
required String label,
required VoidCallback onPressed,
IconData icon = Icons.add,
FABVariant variant = FABVariant.primary,
String? tooltip,
}) {
return SophisticatedFAB(
icon: icon,
label: label,
onPressed: onPressed,
variant: variant,
size: FABSize.extended,
tooltip: tooltip,
);
}
static Widget fabGradient({
required VoidCallback onPressed,
IconData icon = Icons.add,
FABSize size = FABSize.regular,
Gradient? gradient,
String? tooltip,
}) {
return SophisticatedFAB(
icon: icon,
onPressed: onPressed,
variant: FABVariant.gradient,
size: size,
gradient: gradient,
tooltip: tooltip,
);
}
static Widget fabMorphing({
required VoidCallback onPressed,
required List<IconData> icons,
FABSize size = FABSize.regular,
Duration morphingDuration = const Duration(seconds: 2),
String? tooltip,
}) {
return SophisticatedFAB(
onPressed: onPressed,
variant: FABVariant.morphing,
size: size,
morphingIcons: icons,
morphingDuration: morphingDuration,
tooltip: tooltip,
);
}
}

View File

@@ -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,
});
}

View File

@@ -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,
});
}

View File

@@ -0,0 +1,554 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../theme/app_theme.dart';
enum ButtonVariant {
primary,
secondary,
outline,
ghost,
gradient,
glass,
danger,
success,
}
enum ButtonSize {
small,
medium,
large,
extraLarge,
}
enum ButtonShape {
rounded,
circular,
square,
}
class SophisticatedButton extends StatefulWidget {
final String? text;
final Widget? child;
final IconData? icon;
final IconData? suffixIcon;
final VoidCallback? onPressed;
final VoidCallback? onLongPress;
final ButtonVariant variant;
final ButtonSize size;
final ButtonShape shape;
final Color? backgroundColor;
final Color? foregroundColor;
final Gradient? gradient;
final bool loading;
final bool disabled;
final bool animated;
final bool showRipple;
final double? width;
final double? height;
final EdgeInsets? padding;
final List<BoxShadow>? customShadow;
final String? tooltip;
final bool hapticFeedback;
const SophisticatedButton({
super.key,
this.text,
this.child,
this.icon,
this.suffixIcon,
this.onPressed,
this.onLongPress,
this.variant = ButtonVariant.primary,
this.size = ButtonSize.medium,
this.shape = ButtonShape.rounded,
this.backgroundColor,
this.foregroundColor,
this.gradient,
this.loading = false,
this.disabled = false,
this.animated = true,
this.showRipple = true,
this.width,
this.height,
this.padding,
this.customShadow,
this.tooltip,
this.hapticFeedback = true,
});
@override
State<SophisticatedButton> createState() => _SophisticatedButtonState();
}
class _SophisticatedButtonState extends State<SophisticatedButton>
with TickerProviderStateMixin {
late AnimationController _pressController;
late AnimationController _loadingController;
late AnimationController _shimmerController;
late Animation<double> _scaleAnimation;
late Animation<double> _shadowAnimation;
late Animation<double> _loadingAnimation;
late Animation<double> _shimmerAnimation;
bool _isPressed = false;
@override
void initState() {
super.initState();
_pressController = AnimationController(
duration: const Duration(milliseconds: 150),
vsync: this,
);
_loadingController = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_shimmerController = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.95,
).animate(CurvedAnimation(
parent: _pressController,
curve: Curves.easeInOut,
));
_shadowAnimation = Tween<double>(
begin: 1.0,
end: 0.7,
).animate(CurvedAnimation(
parent: _pressController,
curve: Curves.easeInOut,
));
_loadingAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _loadingController,
curve: Curves.easeInOut,
));
_shimmerAnimation = Tween<double>(
begin: -1.0,
end: 2.0,
).animate(CurvedAnimation(
parent: _shimmerController,
curve: Curves.easeInOut,
));
if (widget.loading) {
_loadingController.repeat();
}
// Shimmer effect for premium buttons
if (widget.variant == ButtonVariant.gradient) {
_shimmerController.repeat();
}
}
@override
void didUpdateWidget(SophisticatedButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.loading != oldWidget.loading) {
if (widget.loading) {
_loadingController.repeat();
} else {
_loadingController.reset();
}
}
}
@override
void dispose() {
_pressController.dispose();
_loadingController.dispose();
_shimmerController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final config = _getButtonConfig();
final isDisabled = widget.disabled || widget.loading;
Widget button = AnimatedBuilder(
animation: Listenable.merge([_pressController, _loadingController, _shimmerController]),
builder: (context, child) {
return Transform.scale(
scale: widget.animated ? _scaleAnimation.value : 1.0,
child: Container(
width: widget.width,
height: widget.height ?? _getHeight(),
padding: widget.padding ?? _getPadding(),
decoration: _getDecoration(config),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: isDisabled ? null : _handleTap,
onLongPress: isDisabled ? null : widget.onLongPress,
onTapDown: widget.animated && !isDisabled ? (_) => _pressController.forward() : null,
onTapUp: widget.animated && !isDisabled ? (_) => _pressController.reverse() : null,
onTapCancel: widget.animated && !isDisabled ? () => _pressController.reverse() : null,
borderRadius: _getBorderRadius(),
splashColor: widget.showRipple ? config.foregroundColor.withOpacity(0.2) : Colors.transparent,
highlightColor: widget.showRipple ? config.foregroundColor.withOpacity(0.1) : Colors.transparent,
child: _buildContent(config),
),
),
),
);
},
);
if (widget.tooltip != null) {
button = Tooltip(
message: widget.tooltip!,
child: button,
);
}
return button;
}
Widget _buildContent(_ButtonConfig config) {
final hasIcon = widget.icon != null;
final hasSuffixIcon = widget.suffixIcon != null;
final hasText = widget.text != null || widget.child != null;
if (widget.loading) {
return _buildLoadingContent(config);
}
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (hasIcon) ...[
_buildIcon(widget.icon!, config),
if (hasText) SizedBox(width: _getIconSpacing()),
],
if (hasText) ...[
Flexible(child: _buildText(config)),
],
if (hasSuffixIcon) ...[
if (hasText || hasIcon) SizedBox(width: _getIconSpacing()),
_buildIcon(widget.suffixIcon!, config),
],
],
);
}
Widget _buildLoadingContent(_ButtonConfig config) {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: _getIconSize(),
height: _getIconSize(),
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(config.foregroundColor),
),
),
if (widget.text != null) ...[
SizedBox(width: _getIconSpacing()),
Text(
'Chargement...',
style: _getTextStyle(config),
),
],
],
);
}
Widget _buildIcon(IconData icon, _ButtonConfig config) {
return Icon(
icon,
size: _getIconSize(),
color: config.foregroundColor,
);
}
Widget _buildText(_ButtonConfig config) {
if (widget.child != null) {
return DefaultTextStyle(
style: _getTextStyle(config),
child: widget.child!,
);
}
return Text(
widget.text!,
style: _getTextStyle(config),
textAlign: TextAlign.center,
);
}
_ButtonConfig _getButtonConfig() {
final isDisabled = widget.disabled || widget.loading;
switch (widget.variant) {
case ButtonVariant.primary:
return _ButtonConfig(
backgroundColor: isDisabled
? AppTheme.textHint
: (widget.backgroundColor ?? AppTheme.primaryColor),
foregroundColor: isDisabled
? AppTheme.textSecondary
: (widget.foregroundColor ?? Colors.white),
hasElevation: true,
);
case ButtonVariant.secondary:
return _ButtonConfig(
backgroundColor: isDisabled
? AppTheme.backgroundLight
: (widget.backgroundColor ?? AppTheme.secondaryColor),
foregroundColor: isDisabled
? AppTheme.textHint
: (widget.foregroundColor ?? Colors.white),
hasElevation: true,
);
case ButtonVariant.outline:
return _ButtonConfig(
backgroundColor: Colors.transparent,
foregroundColor: isDisabled
? AppTheme.textHint
: (widget.foregroundColor ?? AppTheme.primaryColor),
borderColor: isDisabled
? AppTheme.textHint
: (widget.backgroundColor ?? AppTheme.primaryColor),
hasElevation: false,
);
case ButtonVariant.ghost:
return _ButtonConfig(
backgroundColor: isDisabled
? Colors.transparent
: (widget.backgroundColor ?? AppTheme.primaryColor).withOpacity(0.1),
foregroundColor: isDisabled
? AppTheme.textHint
: (widget.foregroundColor ?? AppTheme.primaryColor),
hasElevation: false,
);
case ButtonVariant.gradient:
return _ButtonConfig(
backgroundColor: Colors.transparent,
foregroundColor: isDisabled
? AppTheme.textHint
: (widget.foregroundColor ?? Colors.white),
hasElevation: true,
useGradient: true,
);
case ButtonVariant.glass:
return _ButtonConfig(
backgroundColor: isDisabled
? Colors.grey.withOpacity(0.1)
: Colors.white.withOpacity(0.2),
foregroundColor: isDisabled
? AppTheme.textHint
: (widget.foregroundColor ?? AppTheme.textPrimary),
borderColor: Colors.white.withOpacity(0.3),
hasElevation: true,
isGlass: true,
);
case ButtonVariant.danger:
return _ButtonConfig(
backgroundColor: isDisabled
? AppTheme.textHint
: AppTheme.errorColor,
foregroundColor: isDisabled
? AppTheme.textSecondary
: Colors.white,
hasElevation: true,
);
case ButtonVariant.success:
return _ButtonConfig(
backgroundColor: isDisabled
? AppTheme.textHint
: AppTheme.successColor,
foregroundColor: isDisabled
? AppTheme.textSecondary
: Colors.white,
hasElevation: true,
);
}
}
Decoration _getDecoration(_ButtonConfig config) {
final borderRadius = _getBorderRadius();
final isDisabled = widget.disabled || widget.loading;
if (config.useGradient && !isDisabled) {
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) : null,
);
}
return BoxDecoration(
color: config.backgroundColor,
borderRadius: borderRadius,
border: config.borderColor != null
? Border.all(color: config.borderColor!, width: 1.5)
: null,
boxShadow: config.hasElevation && !isDisabled ? _getShadow(config) : null,
);
}
List<BoxShadow> _getShadow(_ButtonConfig config) {
if (widget.customShadow != null) {
return widget.customShadow!.map((shadow) => BoxShadow(
color: shadow.color.withOpacity(shadow.color.opacity * _shadowAnimation.value),
blurRadius: shadow.blurRadius * _shadowAnimation.value,
offset: shadow.offset * _shadowAnimation.value,
spreadRadius: shadow.spreadRadius,
)).toList();
}
final shadowColor = config.useGradient
? (widget.backgroundColor ?? AppTheme.primaryColor)
: config.backgroundColor;
return [
BoxShadow(
color: shadowColor.withOpacity(0.3 * _shadowAnimation.value),
blurRadius: 15 * _shadowAnimation.value,
offset: Offset(0, 8 * _shadowAnimation.value),
),
];
}
BorderRadius _getBorderRadius() {
switch (widget.shape) {
case ButtonShape.rounded:
return BorderRadius.circular(_getHeight() / 2);
case ButtonShape.circular:
return BorderRadius.circular(_getHeight());
case ButtonShape.square:
return BorderRadius.circular(8);
}
}
double _getHeight() {
switch (widget.size) {
case ButtonSize.small:
return 32;
case ButtonSize.medium:
return 44;
case ButtonSize.large:
return 56;
case ButtonSize.extraLarge:
return 72;
}
}
EdgeInsets _getPadding() {
switch (widget.size) {
case ButtonSize.small:
return const EdgeInsets.symmetric(horizontal: 16, vertical: 6);
case ButtonSize.medium:
return const EdgeInsets.symmetric(horizontal: 24, vertical: 12);
case ButtonSize.large:
return const EdgeInsets.symmetric(horizontal: 32, vertical: 16);
case ButtonSize.extraLarge:
return const EdgeInsets.symmetric(horizontal: 40, vertical: 20);
}
}
double _getFontSize() {
switch (widget.size) {
case ButtonSize.small:
return 14;
case ButtonSize.medium:
return 16;
case ButtonSize.large:
return 18;
case ButtonSize.extraLarge:
return 20;
}
}
double _getIconSize() {
switch (widget.size) {
case ButtonSize.small:
return 16;
case ButtonSize.medium:
return 20;
case ButtonSize.large:
return 24;
case ButtonSize.extraLarge:
return 28;
}
}
double _getIconSpacing() {
switch (widget.size) {
case ButtonSize.small:
return 6;
case ButtonSize.medium:
return 8;
case ButtonSize.large:
return 10;
case ButtonSize.extraLarge:
return 12;
}
}
TextStyle _getTextStyle(_ButtonConfig config) {
return TextStyle(
fontSize: _getFontSize(),
fontWeight: FontWeight.w600,
color: config.foregroundColor,
letterSpacing: 0.5,
);
}
void _handleTap() {
if (widget.hapticFeedback) {
HapticFeedback.lightImpact();
}
widget.onPressed?.call();
}
}
class _ButtonConfig {
final Color backgroundColor;
final Color foregroundColor;
final Color? borderColor;
final bool hasElevation;
final bool useGradient;
final bool isGlass;
_ButtonConfig({
required this.backgroundColor,
required this.foregroundColor,
this.borderColor,
this.hasElevation = false,
this.useGradient = false,
this.isGlass = false,
});
}