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 options; final String? selectedValue; final List? selectedValues; // For multi-select final Function(String)? onSelectionChanged; final Function(List)? 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 createState() => _SophisticatedButtonGroupState(); } class _SophisticatedButtonGroupState extends State with TickerProviderStateMixin { late AnimationController _slideController; late Animation _slideAnimation; String? _internalSelectedValue; List _internalSelectedValues = []; @override void initState() { super.initState(); _slideController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _slideAnimation = Tween( 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); } } }