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