advance_cart_stepper 2.0.1 copy "advance_cart_stepper: ^2.0.1" to clipboard
advance_cart_stepper: ^2.0.1 copied to clipboard

A customizable expandable cart quantity stepper widget for Flutter with async support, loading indicators, and theming.

example/lib/main.dart

import 'package:advance_cart_stepper/advance_cart_stepper.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const CartStepperExampleApp());
}

class CartStepperExampleApp extends StatelessWidget {
  const CartStepperExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Cart Stepper Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFD84315)),
        useMaterial3: true,
      ),
      darkTheme: ThemeData.dark(useMaterial3: true).copyWith(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFFD84315),
          brightness: Brightness.dark,
        ),
      ),
      home: const CartStepperDemo(),
    );
  }
}

class CartStepperDemo extends StatefulWidget {
  const CartStepperDemo({super.key});

  @override
  State<CartStepperDemo> createState() => _CartStepperDemoState();
}

class _CartStepperDemoState extends State<CartStepperDemo>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 6, vsync: this);
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Cart Stepper Demo'),
        bottom: TabBar(
          controller: _tabController,
          isScrollable: true,
          tabAlignment: TabAlignment.start,
          tabs: const [
            Tab(text: 'Basics'),
            Tab(text: 'Styles'),
            Tab(text: 'Async'),
            Tab(text: 'Advanced'),
            Tab(text: 'Components'),
            Tab(text: 'Real World'),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: const [
          _BasicsTab(),
          _StylesTab(),
          _AsyncTab(),
          _AdvancedTab(),
          _ComponentsTab(),
          _RealWorldTab(),
        ],
      ),
    );
  }
}

// =============================================================================
// TAB 1: BASICS
// =============================================================================
class _BasicsTab extends StatefulWidget {
  const _BasicsTab();

  @override
  State<_BasicsTab> createState() => _BasicsTabState();
}

class _BasicsTabState extends State<_BasicsTab>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  // Basic quantities
  int _basicQuantity = 0;
  int _compactQuantity = 0;
  int _normalQuantity = 2;
  int _largeQuantity = 1;

  // Add to cart button styles
  int _circleIconQty = 0;
  int _addButtonQty = 0;
  int _addToCartQty = 0;
  int _iconOnlyQty = 0;
  int _customButtonQty = 0;
  int _collapsedBuilderQty = 0;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        _buildSection(
          context,
          'Size Variants',
          'Different sizes for various use cases',
          [
            _buildRow(
              'Compact (32px)',
              CartStepper(
                quantity: _compactQuantity,
                size: CartStepperSize.compact,
                onQuantityChanged: (qty) =>
                    setState(() => _compactQuantity = qty),
                onRemove: () => setState(() => _compactQuantity = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Normal (40px) - Default',
              CartStepper(
                quantity: _normalQuantity,
                size: CartStepperSize.normal,
                onQuantityChanged: (qty) =>
                    setState(() => _normalQuantity = qty),
                onRemove: () => setState(() => _normalQuantity = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Large (48px)',
              CartStepper(
                quantity: _largeQuantity,
                size: CartStepperSize.large,
                onQuantityChanged: (qty) =>
                    setState(() => _largeQuantity = qty),
                onRemove: () => setState(() => _largeQuantity = 0),
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Add to Cart Button Styles',
          'Choose between circle icon or button styles',
          [
            _buildRow(
              'Circle Icon (default)',
              CartStepper(
                quantity: _circleIconQty,
                addToCartConfig: AddToCartButtonConfig.circleIcon,
                onQuantityChanged: (qty) =>
                    setState(() => _circleIconQty = qty),
                onRemove: () => setState(() => _circleIconQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              '"Add" Button',
              CartStepper(
                quantity: _addButtonQty,
                addToCartConfig: AddToCartButtonConfig.addButton,
                onQuantityChanged: (qty) => setState(() => _addButtonQty = qty),
                onRemove: () => setState(() => _addButtonQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              '"Add to Cart" Button',
              CartStepper(
                quantity: _addToCartQty,
                addToCartConfig: AddToCartButtonConfig.addToCartButton,
                onQuantityChanged: (qty) => setState(() => _addToCartQty = qty),
                onRemove: () => setState(() => _addToCartQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Icon Only Button',
              CartStepper(
                quantity: _iconOnlyQty,
                addToCartConfig: AddToCartButtonConfig.iconOnlyButton,
                onQuantityChanged: (qty) => setState(() => _iconOnlyQty = qty),
                onRemove: () => setState(() => _iconOnlyQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Custom Button',
              CartStepper(
                quantity: _customButtonQty,
                addToCartConfig: const AddToCartButtonConfig(
                  style: AddToCartButtonStyle.button,
                  buttonText: 'Buy Now',
                  icon: Icons.shopping_bag,
                  iconLeading: false,
                  buttonWidth: 110,
                  borderRadius: BorderRadius.all(Radius.circular(8)),
                ),
                style: const CartStepperStyle(
                  backgroundColor: Color(0xFF2E7D32),
                  foregroundColor: Colors.white,
                  borderColor: Color(0xFF2E7D32),
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _customButtonQty = qty),
                onRemove: () => setState(() => _customButtonQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Collapsed Builder',
              AsyncCartStepper(
                quantity: _collapsedBuilderQty,
                collapseConfig: CartStepperCollapseConfig(
                  collapsedWidth: 100,
                  collapsedHeight: 36,
                  collapsedBuilder: (context, qty, isLoading, onTap) {
                    return GestureDetector(
                      onTap: onTap,
                      child: Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 12,
                          vertical: 6,
                        ),
                        decoration: BoxDecoration(
                          gradient: const LinearGradient(
                            colors: [Color(0xFFFF6B35), Color(0xFFD84315)],
                          ),
                          borderRadius: BorderRadius.circular(20),
                        ),
                        child: isLoading
                            ? const SizedBox(
                                width: 18,
                                height: 18,
                                child: CircularProgressIndicator(
                                  strokeWidth: 2,
                                  color: Colors.white,
                                ),
                              )
                            : Row(
                                mainAxisSize: MainAxisSize.min,
                                children: [
                                  const Icon(
                                    Icons.add_shopping_cart,
                                    color: Colors.white,
                                    size: 16,
                                  ),
                                  const SizedBox(width: 4),
                                  Text(
                                    qty > 0 ? 'Add ($qty)' : 'Add',
                                    style: const TextStyle(
                                      color: Colors.white,
                                      fontSize: 13,
                                      fontWeight: FontWeight.w600,
                                    ),
                                  ),
                                ],
                              ),
                      ),
                    );
                  },
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _collapsedBuilderQty = qty),
                onRemove: () => setState(() => _collapsedBuilderQty = 0),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Fully custom widget via collapsedBuilder',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Basic Functionality',
          'Core features: add, increment, decrement, delete',
          [
            _buildRow(
              'Start from 0',
              CartStepper(
                quantity: _basicQuantity,
                onQuantityChanged: (qty) =>
                    setState(() => _basicQuantity = qty),
                onRemove: () => setState(() => _basicQuantity = 0),
              ),
            ),
            const SizedBox(height: 12),
            Text(
              'Tap + to add, use -/+ to adjust, trash to remove',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 60),
      ],
    );
  }
}

// =============================================================================
// TAB 2: STYLES
// =============================================================================
class _StylesTab extends StatefulWidget {
  const _StylesTab();

  @override
  State<_StylesTab> createState() => _StylesTabState();
}

class _StylesTabState extends State<_StylesTab>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  int _orangeQty = 2;
  int _darkQty = 3;
  int _lightQty = 1;
  int _blueQty = 2;
  int _greenQty = 1;
  int _tealQty = 2;
  int _indigoQty = 3;
  int _animFastQty = 1;
  int _animSmoothQty = 2;
  int _animCustomQty = 1;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        _buildSection(
          context,
          'Pre-built Themes',
          'Ready-to-use style variants',
          [
            _buildRow(
              'Orange (Default)',
              CartStepper(
                quantity: _orangeQty,
                style: CartStepperStyle.defaultOrange,
                onQuantityChanged: (qty) => setState(() => _orangeQty = qty),
                onRemove: () => setState(() => _orangeQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Dark',
              CartStepper(
                quantity: _darkQty,
                style: CartStepperStyle.dark,
                onQuantityChanged: (qty) => setState(() => _darkQty = qty),
                onRemove: () => setState(() => _darkQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Light',
              CartStepper(
                quantity: _lightQty,
                style: CartStepperStyle.light,
                onQuantityChanged: (qty) => setState(() => _lightQty = qty),
                onRemove: () => setState(() => _lightQty = 0),
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Custom Styles',
          'Create your own brand colors',
          [
            _buildRow(
              'Blue with elevation',
              CartStepper(
                quantity: _blueQty,
                style: const CartStepperStyle(
                  backgroundColor: Color(0xFF1976D2),
                  foregroundColor: Colors.white,
                  borderColor: Color(0xFF1976D2),
                  elevation: 4.0,
                  iconScale: 1.1,
                ),
                onQuantityChanged: (qty) => setState(() => _blueQty = qty),
                onRemove: () => setState(() => _blueQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Green bold font',
              CartStepper(
                quantity: _greenQty,
                style: const CartStepperStyle(
                  backgroundColor: Color(0xFF2E7D32),
                  foregroundColor: Colors.white,
                  borderColor: Color(0xFF2E7D32),
                  fontWeight: FontWeight.w800,
                ),
                onQuantityChanged: (qty) => setState(() => _greenQty = qty),
                onRemove: () => setState(() => _greenQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Rounded square',
              CartStepper(
                quantity: _tealQty,
                style: CartStepperStyle(
                  borderRadius: BorderRadius.circular(8),
                  backgroundColor: Colors.teal,
                  foregroundColor: Colors.white,
                  borderColor: Colors.teal,
                ),
                onQuantityChanged: (qty) => setState(() => _tealQty = qty),
                onRemove: () => setState(() => _tealQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Custom font style',
              CartStepper(
                quantity: _indigoQty,
                style: const CartStepperStyle(
                  backgroundColor: Colors.indigo,
                  foregroundColor: Colors.white,
                  borderColor: Colors.indigo,
                  textStyle: TextStyle(
                    fontFamily: 'Courier',
                    fontWeight: FontWeight.bold,
                    letterSpacing: 2,
                  ),
                ),
                onQuantityChanged: (qty) => setState(() => _indigoQty = qty),
                onRemove: () => setState(() => _indigoQty = 0),
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Animation Presets',
          'Different animation speeds and curves',
          [
            _buildRow(
              'Fast (150ms)',
              CartStepper(
                quantity: _animFastQty,
                animation: CartStepperAnimation.fast,
                onQuantityChanged: (qty) => setState(() => _animFastQty = qty),
                onRemove: () => setState(() => _animFastQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Smooth (bounce)',
              CartStepper(
                quantity: _animSmoothQty,
                animation: CartStepperAnimation.smooth,
                onQuantityChanged: (qty) =>
                    setState(() => _animSmoothQty = qty),
                onRemove: () => setState(() => _animSmoothQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Custom (elastic)',
              CartStepper(
                quantity: _animCustomQty,
                animation: const CartStepperAnimation(
                  expandDuration: Duration(milliseconds: 500),
                  expandCurve: Curves.elasticOut,
                  enableHaptics: true,
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _animCustomQty = qty),
                onRemove: () => setState(() => _animCustomQty = 0),
              ),
            ),
          ],
        ),
        const SizedBox(height: 60),
      ],
    );
  }
}

// =============================================================================
// TAB 3: ASYNC
// =============================================================================
class _AsyncTab extends StatefulWidget {
  const _AsyncTab();

  @override
  State<_AsyncTab> createState() => _AsyncTabState();
}

class _AsyncTabState extends State<_AsyncTab>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  int _asyncDefaultQty = 0;
  int _asyncFadingQty = 2;
  int _builtInCircularQty = 1;
  int _builtInLinearQty = 2;
  int _optimisticQty = 3;

  // Debounce mode
  int _debounceQty = 2;
  bool _debounceLoading = false;

  // Error handling
  int _errorQty = 2;
  String? _lastError;
  int _errorBuilderQty = 2;

  // Loading types showcase
  final Map<CartStepperLoadingType, int> _loadingTypeQty = {
    for (var type in CartStepperLoadingType.values) type: 1,
  };

  Future<void> _simulateApiCall({int delayMs = 800}) async {
    await Future.delayed(Duration(milliseconds: delayMs));
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        _buildSection(
          context,
          'Async Loading',
          'Shows loading spinner during API calls',
          [
            _buildRow(
              'ThreeBounce (default)',
              AsyncCartStepper(
                quantity: _asyncDefaultQty,
                onQuantityChangedAsync: (qty) async {
                  await _simulateApiCall();
                  setState(() => _asyncDefaultQty = qty);
                },
                onRemoveAsync: () async {
                  await _simulateApiCall();
                  setState(() => _asyncDefaultQty = 0);
                },
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'FadingCircle (slow)',
              AsyncCartStepper(
                quantity: _asyncFadingQty,
                loadingConfig: const CartStepperLoadingConfig(
                  type: CartStepperLoadingType.fadingCircle,
                  minimumDuration: Duration(milliseconds: 500),
                ),
                onQuantityChangedAsync: (qty) async {
                  await _simulateApiCall(delayMs: 1200);
                  setState(() => _asyncFadingQty = qty);
                },
                onRemoveAsync: () async {
                  await _simulateApiCall(delayMs: 1200);
                  setState(() => _asyncFadingQty = 0);
                },
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Built-in Loading (No SpinKit)',
          "Use Flutter's native indicators",
          [
            _buildRow(
              'CircularProgressIndicator',
              AsyncCartStepper(
                quantity: _builtInCircularQty,
                loadingConfig: CartStepperLoadingConfig.builtIn,
                onQuantityChangedAsync: (qty) async {
                  await _simulateApiCall();
                  setState(() => _builtInCircularQty = qty);
                },
                onRemoveAsync: () async {
                  await _simulateApiCall();
                  setState(() => _builtInCircularQty = 0);
                },
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'LinearProgressIndicator',
              AsyncCartStepper(
                quantity: _builtInLinearQty,
                loadingConfig: const CartStepperLoadingConfig(
                  type: CartStepperLoadingType.linear,
                  sizeMultiplier: 1.2,
                ),
                onQuantityChangedAsync: (qty) async {
                  await _simulateApiCall();
                  setState(() => _builtInLinearQty = qty);
                },
                onRemoveAsync: () async {
                  await _simulateApiCall();
                  setState(() => _builtInLinearQty = 0);
                },
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Optimistic Updates',
          'Update UI immediately, revert on error',
          [
            _buildRow(
              'Optimistic (instant UI)',
              AsyncCartStepper(
                quantity: _optimisticQty,
                asyncBehavior: const CartStepperAsyncBehavior(
                  optimisticUpdate: true,
                  revertOnError: true,
                ),
                loadingConfig: const CartStepperLoadingConfig(
                  type: CartStepperLoadingType.pulse,
                  sizeMultiplier: 0.6,
                ),
                onQuantityChangedAsync: (qty) async {
                  await _simulateApiCall(delayMs: 600);
                  setState(() => _optimisticQty = qty);
                },
                onRemoveAsync: () async {
                  await _simulateApiCall(delayMs: 600);
                  setState(() => _optimisticQty = 0);
                },
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Notice: quantity updates instantly while loading',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Debounce Mode (Best UX)',
          'Batch rapid changes into one API call',
          [
            _buildRow(
              'Debounce (500ms)',
              Column(
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  AsyncCartStepper(
                    quantity: _debounceQty,
                    asyncBehavior: const CartStepperAsyncBehavior(
                      debounceDelay: Duration(milliseconds: 500),
                    ),
                    maxQuantity: 99,
                    loadingConfig: const CartStepperLoadingConfig(
                      type: CartStepperLoadingType.threeBounce,
                    ),
                    onQuantityChangedAsync: (qty) async {
                      setState(() => _debounceLoading = true);
                      await _simulateApiCall(delayMs: 800);
                      setState(() {
                        _debounceQty = qty;
                        _debounceLoading = false;
                      });
                    },
                    onRemoveAsync: () async {
                      setState(() => _debounceLoading = true);
                      await _simulateApiCall(delayMs: 800);
                      setState(() {
                        _debounceQty = 0;
                        _debounceLoading = false;
                      });
                    },
                  ),
                  if (_debounceLoading)
                    const Padding(
                      padding: EdgeInsets.only(top: 4),
                      child: Text(
                        'Syncing...',
                        style: TextStyle(
                          fontSize: 10,
                          color: Colors.orange,
                          fontStyle: FontStyle.italic,
                        ),
                      ),
                    ),
                ],
              ),
            ),
            const SizedBox(height: 12),
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.green.shade50,
                borderRadius: BorderRadius.circular(8),
                border: Border.all(color: Colors.green.shade200),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Icon(Icons.lightbulb_outline,
                          color: Colors.green.shade700, size: 18),
                      const SizedBox(width: 8),
                      Text(
                        'Try this:',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          color: Colors.green.shade700,
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 8),
                  Text(
                    '• Tap +/- rapidly multiple times\n'
                    '• Long-press and hold to increment fast\n'
                    '• Notice: UI updates instantly, but only ONE API call is made after you stop!',
                    style: TextStyle(
                      fontSize: 12,
                      color: Colors.green.shade700,
                      height: 1.5,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Error Handling',
          'Handle async operation errors',
          [
            if (_lastError != null)
              Container(
                padding: const EdgeInsets.all(8),
                margin: const EdgeInsets.only(bottom: 12),
                decoration: BoxDecoration(
                  color: Colors.red.shade50,
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.red.shade200),
                ),
                child: Row(
                  children: [
                    Icon(Icons.error_outline,
                        color: Colors.red.shade700, size: 20),
                    const SizedBox(width: 8),
                    Expanded(
                      child: Text(
                        _lastError!,
                        style:
                            TextStyle(color: Colors.red.shade700, fontSize: 12),
                      ),
                    ),
                    IconButton(
                      icon: const Icon(Icons.close, size: 16),
                      onPressed: () => setState(() => _lastError = null),
                      padding: EdgeInsets.zero,
                      constraints: const BoxConstraints(),
                    ),
                  ],
                ),
              ),
            _buildRow(
              'With onError callback',
              AsyncCartStepper(
                quantity: _errorQty,
                onQuantityChangedAsync: (qty) async {
                  await _simulateApiCall(delayMs: 500);
                  if (qty > 5) {
                    throw Exception('Max 5 items allowed!');
                  }
                  setState(() => _errorQty = qty);
                },
                onError: (error, _) {
                  setState(() => _lastError = error.toString());
                },
                onRemoveAsync: () async {
                  await _simulateApiCall(delayMs: 300);
                  setState(() => _errorQty = 0);
                },
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Try incrementing past 5 to trigger an error',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
            const SizedBox(height: 20),
            _buildRow(
              'With errorBuilder (inline)',
              AsyncCartStepper(
                quantity: _errorBuilderQty,
                onQuantityChangedAsync: (qty) async {
                  await _simulateApiCall(delayMs: 500);
                  if (qty > 4) {
                    throw Exception('Stock limit reached');
                  }
                  setState(() => _errorBuilderQty = qty);
                },
                errorBuilder: (context, error, retry) => Padding(
                  padding: const EdgeInsets.only(top: 8),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(
                        error.message,
                        style: const TextStyle(color: Colors.red, fontSize: 11),
                      ),
                      const SizedBox(width: 4),
                      GestureDetector(
                        onTap: retry,
                        child: const Text(
                          'Retry',
                          style: TextStyle(
                            color: Colors.blue,
                            fontSize: 11,
                            decoration: TextDecoration.underline,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
                onRemoveAsync: () async {
                  await _simulateApiCall(delayMs: 300);
                  setState(() => _errorBuilderQty = 0);
                },
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'All Loading Types',
          'Tap to see each SpinKit animation',
          [
            Wrap(
              spacing: 8,
              runSpacing: 16,
              children: CartStepperLoadingType.values.map((type) {
                final name = type.name;
                return SizedBox(
                  width: 130,
                  child: Column(
                    children: [
                      Text(
                        name,
                        style: const TextStyle(fontSize: 10),
                        overflow: TextOverflow.ellipsis,
                      ),
                      const SizedBox(height: 4),
                      AsyncCartStepper(
                        quantity: _loadingTypeQty[type]!,
                        size: CartStepperSize.compact,
                        loadingConfig: CartStepperLoadingConfig(
                          type: type,
                          sizeMultiplier: 0.7,
                        ),
                        onQuantityChangedAsync: (qty) async {
                          await _simulateApiCall(delayMs: 1500);
                          setState(() => _loadingTypeQty[type] = qty);
                        },
                        onRemoveAsync: () async {
                          await _simulateApiCall(delayMs: 1500);
                          setState(() => _loadingTypeQty[type] = 0);
                        },
                      ),
                    ],
                  ),
                );
              }).toList(),
            ),
          ],
        ),
        const SizedBox(height: 60),
      ],
    );
  }
}

// =============================================================================
// TAB 4: ADVANCED
// =============================================================================
class _AdvancedTab extends StatefulWidget {
  const _AdvancedTab();

  @override
  State<_AdvancedTab> createState() => _AdvancedTabState();
}

class _AdvancedTabState extends State<_AdvancedTab>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  // Min/Max
  int _minMaxQty = 5;
  int _stepQty = 10;
  final int _disabledQty = 3;
  int _noDeleteQty = 1;
  int _deleteViaChangeQty = 1;

  // Long press
  int _fastLongPressQty = 50;
  int _slowLongPressQty = 50;

  // Quantity formatters
  int _abbrev1Qty = 1500;
  int _abbrev2Qty = 12500;
  int _abbrev3Qty = 1000000;
  int _maxIndicatorQty = 99;

  // Callbacks
  int _callbacksQty = 5;
  String? _callbackMessage;

  // Auto-collapse
  int _autoCollapseQty = 2;

  // Initially expanded
  int _expandedTrueQty = 3;
  int _expandedFalseQty = 5;

  // Custom icons
  int _customIconsQty = 2;

  // Validation
  int _validationQty = 2;

  // Manual Input
  int _manualInputQty = 5;
  int _manualInputLargeQty = 25;
  int _customInputBuilderQty = 10;

  // v2.0 new features
  double _decimalQty = 1.5;
  int _verticalQty = 3;
  int _rtlQty = 2;
  int _undoQty = 3;
  int _detailedQty = 2;
  String? _detailedMessage;
  int _expandedBuilderQty = 3;
  int _transitionQty = 0;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        _buildSection(
          context,
          'Min/Max & Step',
          'Control quantity limits and step values',
          [
            _buildRow(
              'Min: 5, Max: 15',
              CartStepper(
                quantity: _minMaxQty,
                minQuantity: 5,
                maxQuantity: 15,
                onQuantityChanged: (qty) => setState(() => _minMaxQty = qty),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Step: 5',
              AsyncCartStepper(
                quantity: _stepQty,
                minQuantity: 5,
                maxQuantity: 100,
                step: 5,
                quantityFormatter: (q) => '$q items',
                onQuantityChanged: (qty) => setState(() => _stepQty = qty),
                onRemove: () => setState(() => _stepQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Disabled',
              CartStepper(
                quantity: _disabledQty,
                enabled: false,
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'No delete icon',
              CartStepper(
                quantity: _noDeleteQty,
                showDeleteAtMin: false,
                onQuantityChanged: (qty) => setState(() => _noDeleteQty = qty),
                onRemove: () => setState(() => _noDeleteQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Delete via onQuantityChanged',
              AsyncCartStepper(
                quantity: _deleteViaChangeQty,
                minQuantity: 1,
                deleteViaQuantityChange: true,
                onQuantityChanged: (qty) {
                  if (qty < 1) {
                    setState(() => _deleteViaChangeQty = 0);
                  } else {
                    setState(() => _deleteViaChangeQty = qty);
                  }
                },
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Long Press Configuration',
          'Control delay before rapid increment starts',
          [
            _buildRow(
              'Fast (100ms delay)',
              AsyncCartStepper(
                quantity: _fastLongPressQty,
                maxQuantity: 999,
                longPressConfig: const CartStepperLongPressConfig(
                  initialDelay: Duration(milliseconds: 100),
                  interval: Duration(milliseconds: 50),
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _fastLongPressQty = qty),
                onRemove: () => setState(() => _fastLongPressQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Slow (800ms delay)',
              AsyncCartStepper(
                quantity: _slowLongPressQty,
                maxQuantity: 999,
                longPressConfig: const CartStepperLongPressConfig(
                  initialDelay: Duration(milliseconds: 800),
                  interval: Duration(milliseconds: 150),
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _slowLongPressQty = qty),
                onRemove: () => setState(() => _slowLongPressQty = 0),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Long-press and hold +/- buttons to see the difference',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Quantity Formatters',
          'Built-in abbreviation for large numbers',
          [
            _buildRow(
              '1,500 → "1.5k"',
              AsyncCartStepper(
                quantity: _abbrev1Qty,
                maxQuantity: 9999999,
                step: 100,
                quantityFormatter: QuantityFormatters.abbreviated,
                onQuantityChanged: (qty) => setState(() => _abbrev1Qty = qty),
                onRemove: () => setState(() => _abbrev1Qty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              '12,500 → "12.5k"',
              AsyncCartStepper(
                quantity: _abbrev2Qty,
                maxQuantity: 9999999,
                step: 500,
                quantityFormatter: QuantityFormatters.abbreviated,
                onQuantityChanged: (qty) => setState(() => _abbrev2Qty = qty),
                onRemove: () => setState(() => _abbrev2Qty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              '1,000,000 → "1M"',
              AsyncCartStepper(
                quantity: _abbrev3Qty,
                maxQuantity: 9999999,
                step: 100000,
                quantityFormatter: QuantityFormatters.abbreviated,
                onQuantityChanged: (qty) => setState(() => _abbrev3Qty = qty),
                onRemove: () => setState(() => _abbrev3Qty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Max indicator (99+)',
              AsyncCartStepper(
                quantity: _maxIndicatorQty,
                maxQuantity: 150,
                quantityFormatter: QuantityFormatters.abbreviatedWithMax(99),
                onQuantityChanged: (qty) =>
                    setState(() => _maxIndicatorQty = qty),
                onRemove: () => setState(() => _maxIndicatorQty = 0),
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Event Callbacks',
          'onMaxReached, onMinReached, onValidationRejected',
          [
            if (_callbackMessage != null)
              Container(
                padding: const EdgeInsets.all(8),
                margin: const EdgeInsets.only(bottom: 12),
                decoration: BoxDecoration(
                  color: Colors.blue.shade50,
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.blue.shade200),
                ),
                child: Row(
                  children: [
                    Icon(Icons.info_outline,
                        color: Colors.blue.shade700, size: 20),
                    const SizedBox(width: 8),
                    Expanded(
                      child: Text(
                        _callbackMessage!,
                        style: TextStyle(
                            color: Colors.blue.shade700, fontSize: 12),
                      ),
                    ),
                    IconButton(
                      icon: const Icon(Icons.close, size: 16),
                      onPressed: () => setState(() => _callbackMessage = null),
                      padding: EdgeInsets.zero,
                      constraints: const BoxConstraints(),
                    ),
                  ],
                ),
              ),
            _buildRow(
              'With callbacks (min:1, max:10)',
              AsyncCartStepper(
                quantity: _callbacksQty,
                minQuantity: 1,
                maxQuantity: 10,
                onQuantityChanged: (qty) => setState(() => _callbacksQty = qty),
                onMaxReached: () {
                  setState(() => _callbackMessage = 'Max quantity reached!');
                },
                onMinReached: () {
                  setState(() => _callbackMessage = 'Min quantity reached!');
                },
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Custom Validator',
          'Prevent changes that fail validation',
          [
            _buildRow(
              'Only even numbers',
              AsyncCartStepper(
                quantity: _validationQty,
                step: 1,
                maxQuantity: 20,
                validator: (current, next) => next % 2 == 0,
                onQuantityChanged: (qty) =>
                    setState(() => _validationQty = qty),
                onValidationRejected: (current, attempted) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text('$attempted is not an even number!'),
                      duration: const Duration(seconds: 1),
                    ),
                  );
                },
                onRemove: () => setState(() => _validationQty = 0),
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Auto-Collapse',
          'Collapses to a badge after inactivity',
          [
            _buildRow(
              'Auto-Collapse (3s)',
              AsyncCartStepper(
                quantity: _autoCollapseQty,
                collapseConfig: const CartStepperCollapseConfig(
                  autoCollapseDelay: Duration(seconds: 3),
                ),
                iconConfig: const CartStepperIconConfig(
                  collapsedBadgeIcon: Icons.shopping_cart,
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _autoCollapseQty = qty),
                onRemove: () => setState(() => _autoCollapseQty = 0),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Wait 3 seconds to see it collapse to badge view',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Initially Expanded',
          'Control initial state explicitly',
          [
            _buildRow(
              'initiallyExpanded: true',
              AsyncCartStepper(
                quantity: _expandedTrueQty,
                collapseConfig: const CartStepperCollapseConfig(
                  initiallyExpanded: true,
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _expandedTrueQty = qty),
                onRemove: () => setState(() => _expandedTrueQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'initiallyExpanded: false',
              AsyncCartStepper(
                quantity: _expandedFalseQty,
                collapseConfig: const CartStepperCollapseConfig(
                  initiallyExpanded: false,
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _expandedFalseQty = qty),
                onRemove: () => setState(() => _expandedFalseQty = 0),
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Custom Icons',
          'Replace default icons',
          [
            _buildRow(
              'Custom increment/decrement',
              AsyncCartStepper(
                quantity: _customIconsQty,
                iconConfig: const CartStepperIconConfig(
                  addIcon: Icons.add_circle,
                  incrementIcon: Icons.arrow_upward,
                  decrementIcon: Icons.arrow_downward,
                  deleteIcon: Icons.cancel,
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _customIconsQty = qty),
                onRemove: () => setState(() => _customIconsQty = 0),
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Decimal / Double Quantities',
          'Generic T extends num support for non-integer quantities',
          [
            _buildRow(
              'Step: 0.5 (double)',
              AsyncCartStepper<double>(
                quantity: _decimalQty,
                minQuantity: 0.5,
                maxQuantity: 10.0,
                step: 0.5,
                quantityFormatter: (q) => q.toStringAsFixed(1),
                onQuantityChanged: (qty) => setState(() => _decimalQty = qty),
                onRemove: () => setState(() => _decimalQty = 0.0),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'CartStepper<double> with step: 0.5',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Vertical Layout',
          'CartStepperDirection.vertical for vertical stepper controls',
          [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Column(
                  children: [
                    const Text('Vertical', style: TextStyle(fontSize: 12)),
                    const SizedBox(height: 8),
                    AsyncCartStepper(
                      quantity: _verticalQty,
                      direction: CartStepperDirection.vertical,
                      maxQuantity: 20,
                      onQuantityChanged: (qty) =>
                          setState(() => _verticalQty = qty),
                      onRemove: () => setState(() => _verticalQty = 0),
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Manual Input',
          'Tap on the quantity to type a value directly',
          [
            _buildRow(
              'Tap to edit (max: 99)',
              AsyncCartStepper(
                quantity: _manualInputQty,
                manualInputConfig: CartStepperManualInputConfig(
                  enabled: true,
                  onSubmitted: (qty) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('Manually set to $qty'),
                        duration: const Duration(seconds: 1),
                      ),
                    );
                  },
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _manualInputQty = qty),
                onRemove: () => setState(() => _manualInputQty = 0),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Tap the number in the stepper to open keyboard input',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Large size with manual input',
              AsyncCartStepper(
                quantity: _manualInputLargeQty,
                size: CartStepperSize.large,
                maxQuantity: 999,
                manualInputConfig: const CartStepperManualInputConfig(
                  enabled: true,
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _manualInputLargeQty = qty),
                onRemove: () => setState(() => _manualInputLargeQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'Custom input builder',
              AsyncCartStepper(
                quantity: _customInputBuilderQty,
                maxQuantity: 50,
                manualInputConfig: CartStepperManualInputConfig(
                  enabled: true,
                  builder: (context, currentValue, onSubmit, onCancel) {
                    final controller =
                        TextEditingController(text: currentValue.toString());
                    return Container(
                      padding: const EdgeInsets.symmetric(
                          horizontal: 8, vertical: 4),
                      decoration: BoxDecoration(
                        color: Colors.white.withValues(alpha: 0.2),
                        borderRadius: BorderRadius.circular(8),
                        border: Border.all(color: Colors.white, width: 2),
                      ),
                      child: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          SizedBox(
                            width: 40,
                            child: TextField(
                              controller: controller,
                              keyboardType: TextInputType.number,
                              textAlign: TextAlign.center,
                              style: const TextStyle(
                                color: Colors.white,
                                fontSize: 14,
                                fontWeight: FontWeight.bold,
                              ),
                              decoration: const InputDecoration(
                                isDense: true,
                                contentPadding: EdgeInsets.zero,
                                border: InputBorder.none,
                              ),
                              autofocus: true,
                              onSubmitted: onSubmit,
                            ),
                          ),
                          GestureDetector(
                            onTap: () => onSubmit(controller.text),
                            child: const Icon(Icons.check,
                                color: Colors.white, size: 16),
                          ),
                        ],
                      ),
                    );
                  },
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _customInputBuilderQty = qty),
                onRemove: () => setState(() => _customInputBuilderQty = 0),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Custom builder with a confirm button',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'RTL / Directionality',
          'Right-to-left layout support',
          [
            _buildRow(
              'LTR (default)',
              CartStepper(
                quantity: _rtlQty,
                textDirection: TextDirection.ltr,
                onQuantityChanged: (qty) => setState(() => _rtlQty = qty),
                onRemove: () => setState(() => _rtlQty = 0),
              ),
            ),
            const SizedBox(height: 16),
            _buildRow(
              'RTL (mirrored)',
              CartStepper(
                quantity: _rtlQty,
                textDirection: TextDirection.rtl,
                onQuantityChanged: (qty) => setState(() => _rtlQty = qty),
                onRemove: () => setState(() => _rtlQty = 0),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Notice the increment/decrement buttons are swapped in RTL',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Undo After Delete',
          'Shows an undo prompt before finalizing removal',
          [
            _buildRow(
              'Undo (3 seconds)',
              AsyncCartStepper(
                quantity: _undoQty,
                undoConfig: const CartStepperUndoConfig(
                  enabled: true,
                  duration: Duration(seconds: 3),
                ),
                onQuantityChanged: (qty) => setState(() => _undoQty = qty),
                onRemove: () => setState(() => _undoQty = 0),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Delete an item and watch the undo indicator appear',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Detailed Quantity Changed',
          'Track how the quantity was changed',
          [
            if (_detailedMessage != null)
              Container(
                padding: const EdgeInsets.all(8),
                margin: const EdgeInsets.only(bottom: 12),
                decoration: BoxDecoration(
                  color: Colors.purple.shade50,
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.purple.shade200),
                ),
                child: Row(
                  children: [
                    Icon(Icons.analytics_outlined,
                        color: Colors.purple.shade700, size: 20),
                    const SizedBox(width: 8),
                    Expanded(
                      child: Text(
                        _detailedMessage!,
                        style: TextStyle(
                            color: Colors.purple.shade700, fontSize: 12),
                      ),
                    ),
                    IconButton(
                      icon: const Icon(Icons.close, size: 16),
                      onPressed: () => setState(() => _detailedMessage = null),
                      padding: EdgeInsets.zero,
                      constraints: const BoxConstraints(),
                    ),
                  ],
                ),
              ),
            _buildRow(
              'With onDetailedQuantityChanged',
              CartStepper(
                quantity: _detailedQty,
                onQuantityChanged: (qty) => setState(() => _detailedQty = qty),
                onDetailedQuantityChanged: (newQty, oldQty, changeType) {
                  setState(() {
                    _detailedMessage =
                        '$oldQty → $newQty via ${changeType.name}';
                  });
                },
                onRemove: () => setState(() => _detailedQty = 0),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Tap +/- to see the change type reported above',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Custom Expanded Builder',
          'Replace the default stepper controls entirely',
          [
            _buildRow(
              'Custom layout',
              AsyncCartStepper(
                quantity: _expandedBuilderQty,
                maxQuantity: 20,
                expandedBuilder:
                    (context, qty, increment, decrement, isLoading) {
                  return Container(
                    padding:
                        const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                    decoration: BoxDecoration(
                      color: const Color(0xFFD84315),
                      borderRadius: BorderRadius.circular(20),
                    ),
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        GestureDetector(
                          onTap: decrement,
                          child: const Padding(
                            padding: EdgeInsets.all(4),
                            child: Icon(Icons.remove_circle_outline,
                                color: Colors.white, size: 22),
                          ),
                        ),
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 12),
                          child: Text(
                            '$qty',
                            style: const TextStyle(
                              color: Colors.white,
                              fontWeight: FontWeight.bold,
                              fontSize: 16,
                            ),
                          ),
                        ),
                        GestureDetector(
                          onTap: increment,
                          child: const Padding(
                            padding: EdgeInsets.all(4),
                            child: Icon(Icons.add_circle_outline,
                                color: Colors.white, size: 22),
                          ),
                        ),
                      ],
                    ),
                  );
                },
                onQuantityChanged: (qty) =>
                    setState(() => _expandedBuilderQty = qty),
                onRemove: () => setState(() => _expandedBuilderQty = 0),
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Custom Transition Builder',
          'Change the expand/collapse animation',
          [
            _buildRow(
              'Scale transition',
              CartStepper(
                quantity: _transitionQty,
                animation: CartStepperAnimation(
                  transitionBuilder: (context, animation, child) {
                    return ScaleTransition(scale: animation, child: child);
                  },
                ),
                onQuantityChanged: (qty) =>
                    setState(() => _transitionQty = qty),
                onRemove: () => setState(() => _transitionQty = 0),
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Tap + to see scale transition instead of width animation',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 60),
      ],
    );
  }
}

// =============================================================================
// TAB 5: COMPONENTS
// =============================================================================
class _ComponentsTab extends StatefulWidget {
  const _ComponentsTab();

  @override
  State<_ComponentsTab> createState() => _ComponentsTabState();
}

class _ComponentsTabState extends State<_ComponentsTab>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  // Controller
  late final CartStepperController _controller;

  // Product tiles
  int _productTileQty1 = 1;
  int _productTileQty2 = 0;
  int _productTileQty3 = 2;
  int _productTileQty4 = 0;
  double _productTileWeightQty = 1.5;
  int _productTilePizzaQty = 1;

  // Badge
  int _badgeCount = 5;

  // Group
  List<CartStepperGroupItem> _sizeVariants = [
    const CartStepperGroupItem(id: 's', quantity: 0, label: 'S'),
    const CartStepperGroupItem(id: 'm', quantity: 1, label: 'M'),
    const CartStepperGroupItem(id: 'l', quantity: 0, label: 'L'),
    const CartStepperGroupItem(id: 'xl', quantity: 2, label: 'XL'),
  ];

  // Selection mode group
  List<CartStepperGroupItem> _singleSelectVariants = [
    const CartStepperGroupItem(id: 'red', quantity: 0, label: 'Red'),
    const CartStepperGroupItem(id: 'blue', quantity: 1, label: 'Blue'),
    const CartStepperGroupItem(id: 'green', quantity: 0, label: 'Green'),
  ];

  // Themed
  int _themed1Qty = 2;
  int _themed2Qty = 1;

  @override
  void initState() {
    super.initState();
    _controller = CartStepperController(
      initialQuantity: 3,
      maxQuantity: 10,
      onMaxReached: () => _showMessage('Controller: Max reached!'),
      onMinReached: () => _showMessage('Controller: Min reached!'),
    );
    _controller.addListener(_onControllerChanged);
  }

  void _onControllerChanged() {
    if (mounted) setState(() {});
  }

  void _showMessage(String message) {
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message), duration: const Duration(seconds: 1)),
    );
  }

  @override
  void dispose() {
    _controller.removeListener(_onControllerChanged);
    _controller.dispose();
    super.dispose();
  }

  int get _totalVariants =>
      _sizeVariants.fold(0, (sum, item) => sum + item.quantity);

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        _buildSection(
          context,
          'CartStepperController',
          'External state management with ChangeNotifier',
          [
            _buildRow(
              'Controlled (qty: ${_controller.quantity})',
              CartStepper(
                quantity: _controller.quantity,
                maxQuantity: _controller.maxQuantity,
                onQuantityChanged: _controller.setQuantity,
                onRemove: _controller.reset,
              ),
            ),
            const SizedBox(height: 16),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              alignment: WrapAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: _controller.reset,
                  child: const Text('Reset'),
                ),
                ElevatedButton(
                  onPressed: _controller.canIncrement
                      ? () => _controller.setQuantity(5)
                      : null,
                  child: const Text('Set to 5'),
                ),
                ElevatedButton(
                  onPressed: _controller.canIncrement
                      ? () => _controller.setQuantity(10)
                      : null,
                  child: const Text('Set to Max'),
                ),
              ],
            ),
            const SizedBox(height: 12),
            Container(
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: Colors.grey.shade100,
                borderRadius: BorderRadius.circular(8),
              ),
              child: Text(
                'canIncrement: ${_controller.canIncrement}\n'
                'canDecrement: ${_controller.canDecrement}\n'
                'isAtMin: ${_controller.isAtMin}\n'
                'isAtMax: ${_controller.isAtMax}',
                style: const TextStyle(fontSize: 12, fontFamily: 'monospace'),
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'CartProductTile',
          'Ready-to-use product cards with integrated stepper',
          [
            CartProductTile(
              leading: Container(
                width: 56,
                height: 56,
                decoration: BoxDecoration(
                  color: Colors.orange.shade50,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Icon(Icons.headphones,
                    color: Colors.orange.shade700, size: 28),
              ),
              title: 'Wireless Headphones',
              subtitle: 'Noise cancelling, 30h battery',
              price: '\$79.99',
              quantity: _productTileQty1,
              onQuantityChanged: (qty) =>
                  setState(() => _productTileQty1 = qty),
              onRemove: () => setState(() => _productTileQty1 = 0),
            ),
            const SizedBox(height: 12),
            CartProductTile(
              leading: Container(
                width: 56,
                height: 56,
                decoration: BoxDecoration(
                  color: Colors.blue.shade50,
                  borderRadius: BorderRadius.circular(8),
                ),
                child:
                    Icon(Icons.keyboard, color: Colors.blue.shade700, size: 28),
              ),
              title: 'Mechanical Keyboard',
              subtitle: 'Cherry MX Blue, RGB Backlit',
              price: '\$129.99',
              quantity: _productTileQty2,
              stepperStyle: CartStepperStyle.dark,
              onQuantityChanged: (qty) =>
                  setState(() => _productTileQty2 = qty),
              onRemove: () => setState(() => _productTileQty2 = 0),
            ),
            const SizedBox(height: 12),
            CartProductTile(
              leading: Container(
                width: 56,
                height: 56,
                decoration: BoxDecoration(
                  color: Colors.green.shade50,
                  borderRadius: BorderRadius.circular(8),
                ),
                child:
                    Icon(Icons.mouse, color: Colors.green.shade700, size: 28),
              ),
              title: 'Ergonomic Mouse',
              subtitle: '4000 DPI, Wireless',
              price: '\$49.99',
              quantity: _productTileQty3,
              stepperStyle: const CartStepperStyle(
                backgroundColor: Color(0xFF2E7D32),
                foregroundColor: Colors.white,
                borderColor: Color(0xFF2E7D32),
              ),
              backgroundColor: Colors.green.shade50,
              borderRadius: 20,
              onQuantityChanged: (qty) =>
                  setState(() => _productTileQty3 = qty),
              onRemove: () => setState(() => _productTileQty3 = 0),
            ),
            const SizedBox(height: 12),
            CartProductTile(
              leading: Container(
                width: 56,
                height: 56,
                decoration: BoxDecoration(
                  color: Colors.purple.shade50,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Icon(Icons.monitor,
                    color: Colors.purple.shade700, size: 28),
              ),
              title: 'Ultra-wide Monitor',
              subtitle: '34", 144Hz, HDR',
              price: '\$499.99',
              quantity: _productTileQty4,
              stepperSize: CartStepperSize.normal,
              maxQuantity: 3,
              stepperStyle: const CartStepperStyle(
                backgroundColor: Colors.deepPurple,
                foregroundColor: Colors.white,
                borderColor: Colors.deepPurple,
              ),
              onQuantityChanged: (qty) =>
                  setState(() => _productTileQty4 = qty),
              onRemove: () => setState(() => _productTileQty4 = 0),
              onTap: () {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(
                    content: Text('Navigate to product details'),
                    duration: Duration(seconds: 1),
                  ),
                );
              },
            ),
            const SizedBox(height: 8),
            Text(
              'Tap the monitor tile to see onTap callback',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'CartProductTile - Customized',
          'Double quantities, custom padding, and larger stepper',
          [
            CartProductTile<double>(
              leading: Container(
                width: 64,
                height: 64,
                decoration: BoxDecoration(
                  color: Colors.brown.shade50,
                  borderRadius: BorderRadius.circular(12),
                ),
                child:
                    Icon(Icons.coffee, color: Colors.brown.shade700, size: 32),
              ),
              title: 'Premium Coffee Beans',
              subtitle: 'Single origin, medium roast',
              price: '\$${(18.50 * _productTileWeightQty).toStringAsFixed(2)}',
              quantity: _productTileWeightQty,
              minQuantity: 0.25,
              maxQuantity: 5.0,
              step: 0.25,
              stepperSize: CartStepperSize.normal,
              padding: const EdgeInsets.all(16),
              borderRadius: 16,
              onQuantityChanged: (qty) =>
                  setState(() => _productTileWeightQty = qty),
              onRemove: () => setState(() => _productTileWeightQty = 0.0),
            ),
            const SizedBox(height: 8),
            Text(
              'CartProductTile<double> with step: 0.25 for weight-based pricing',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
            const SizedBox(height: 12),
            CartProductTile(
              leading: Container(
                width: 64,
                height: 64,
                decoration: BoxDecoration(
                  color: Colors.red.shade50,
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Icon(Icons.local_pizza,
                    color: Colors.red.shade700, size: 32),
              ),
              title: 'Margherita Pizza',
              subtitle: 'Fresh mozzarella, basil',
              price: '\$14.99',
              quantity: _productTilePizzaQty,
              maxQuantity: 5,
              stepperSize: CartStepperSize.large,
              stepperStyle: const CartStepperStyle(
                backgroundColor: Color(0xFFE53935),
                foregroundColor: Colors.white,
                borderColor: Color(0xFFE53935),
                elevation: 4.0,
              ),
              padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
              backgroundColor: Colors.red.shade50,
              borderRadius: 24,
              onQuantityChanged: (qty) =>
                  setState(() => _productTilePizzaQty = qty),
              onRemove: () => setState(() => _productTilePizzaQty = 0),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'CartBadge',
          'Display cart count on icons',
          [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                CartBadge(
                  count: _badgeCount,
                  child: const Icon(Icons.shopping_cart, size: 32),
                ),
                CartBadge(
                  count: _badgeCount,
                  badgeColor: Colors.blue,
                  alignment: Alignment.topLeft,
                  child: const Icon(Icons.shopping_bag, size: 32),
                ),
                CartBadge(
                  count: 150,
                  maxCount: 99,
                  child: const Icon(Icons.favorite, size: 32),
                ),
              ],
            ),
            const SizedBox(height: 16),
            Slider(
              value: _badgeCount.toDouble(),
              min: 0,
              max: 150,
              divisions: 150,
              label: '$_badgeCount',
              onChanged: (v) => setState(() => _badgeCount = v.toInt()),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'CartStepperGroup',
          'Horizontal row for variant selection',
          [
            CartStepperGroup(
              items: _sizeVariants,
              size: CartStepperSize.compact,
              maxTotalQuantity: 10,
              onQuantityChanged: (index, qty) {
                setState(() {
                  _sizeVariants = List.from(_sizeVariants);
                  _sizeVariants[index] =
                      _sizeVariants[index].copyWith(quantity: qty);
                });
              },
              onRemove: (index) {
                setState(() {
                  _sizeVariants = List.from(_sizeVariants);
                  _sizeVariants[index] =
                      _sizeVariants[index].copyWith(quantity: 0);
                });
              },
            ),
            const SizedBox(height: 12),
            Text(
              'Total: $_totalVariants / 10',
              style: Theme.of(context).textTheme.bodyMedium,
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'CartStepperGroup - Single Selection',
          'Radio-style: only one item can have quantity > 0',
          [
            CartStepperGroup(
              items: _singleSelectVariants,
              size: CartStepperSize.compact,
              selectionMode: CartStepperSelectionMode.single,
              onQuantityChanged: (index, qty) {
                setState(() {
                  _singleSelectVariants = List.from(_singleSelectVariants);
                  // In single selection mode, reset all others
                  for (var i = 0; i < _singleSelectVariants.length; i++) {
                    if (i == index) {
                      _singleSelectVariants[i] =
                          _singleSelectVariants[i].copyWith(quantity: qty);
                    } else {
                      _singleSelectVariants[i] =
                          _singleSelectVariants[i].copyWith(quantity: 0);
                    }
                  }
                });
              },
              onRemove: (index) {
                setState(() {
                  _singleSelectVariants = List.from(_singleSelectVariants);
                  _singleSelectVariants[index] =
                      _singleSelectVariants[index].copyWith(quantity: 0);
                });
              },
              onTotalChanged: (total) {
                // Total quantity callback
              },
            ),
            const SizedBox(height: 8),
            Text(
              'CartStepperSelectionMode.single',
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[600],
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'CartStepperTheme',
          'Theme multiple steppers consistently',
          [
            CartStepperTheme(
              data: const CartStepperThemeData(
                style: CartStepperStyle(
                  backgroundColor: Color(0xFF6B4EE6),
                  foregroundColor: Colors.white,
                  borderColor: Color(0xFF6B4EE6),
                ),
                size: CartStepperSize.normal,
              ),
              child: Column(
                children: [
                  _buildRow(
                    'Themed 1',
                    ThemedCartStepper(
                      quantity: _themed1Qty,
                      onQuantityChanged: (qty) =>
                          setState(() => _themed1Qty = qty),
                      onRemove: () => setState(() => _themed1Qty = 0),
                    ),
                  ),
                  const SizedBox(height: 16),
                  _buildRow(
                    'Themed 2',
                    ThemedCartStepper(
                      quantity: _themed2Qty,
                      onQuantityChanged: (qty) =>
                          setState(() => _themed2Qty = qty),
                      onRemove: () => setState(() => _themed2Qty = 0),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),
        _buildSection(
          context,
          'Standalone Components',
          'Use individual widgets separately',
          [
            const Text(
              'AnimatedCounter:',
              style: TextStyle(fontWeight: FontWeight.w600),
            ),
            const SizedBox(height: 8),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                AnimatedCounter(
                  value: _badgeCount,
                  style: const TextStyle(
                    fontSize: 32,
                    fontWeight: FontWeight.bold,
                    color: Color(0xFFD84315),
                  ),
                ),
                AnimatedCounter(
                  value: _badgeCount,
                  style: const TextStyle(fontSize: 24),
                  formatter: QuantityFormatters.abbreviated,
                ),
              ],
            ),
            const SizedBox(height: 16),
            const Text(
              'StepperButton:',
              style: TextStyle(fontWeight: FontWeight.w600),
            ),
            const SizedBox(height: 8),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Container(
                  decoration: BoxDecoration(
                    color: const Color(0xFFD84315),
                    borderRadius: BorderRadius.circular(22),
                  ),
                  child: StepperButton(
                    icon: Icons.remove,
                    iconSize: 24,
                    iconColor: Colors.white,
                    enabled: _badgeCount > 0,
                    onTap: () => setState(
                        () => _badgeCount = (_badgeCount - 1).clamp(0, 150)),
                    onLongPressStart: () {},
                    onLongPressEnd: () {},
                    size: 44,
                    splashColor: Colors.white24,
                    highlightColor: Colors.white12,
                  ),
                ),
                Container(
                  width: 44,
                  height: 44,
                  decoration: BoxDecoration(
                    color: const Color(0xFFD84315),
                    borderRadius: BorderRadius.circular(22),
                  ),
                  child: Center(
                    child: Text(
                      '$_badgeCount',
                      style: const TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                ),
                Container(
                  decoration: BoxDecoration(
                    color: const Color(0xFFD84315),
                    borderRadius: BorderRadius.circular(22),
                  ),
                  child: StepperButton(
                    icon: Icons.add,
                    iconSize: 24,
                    iconColor: Colors.white,
                    enabled: _badgeCount < 150,
                    onTap: () => setState(
                        () => _badgeCount = (_badgeCount + 1).clamp(0, 150)),
                    onLongPressStart: () {},
                    onLongPressEnd: () {},
                    size: 44,
                    splashColor: Colors.white24,
                    highlightColor: Colors.white12,
                  ),
                ),
              ],
            ),
          ],
        ),
        const SizedBox(height: 60),
      ],
    );
  }
}

// =============================================================================
// TAB 6: REAL WORLD
// =============================================================================
class _RealWorldTab extends StatefulWidget {
  const _RealWorldTab();

  @override
  State<_RealWorldTab> createState() => _RealWorldTabState();
}

class _RealWorldTabState extends State<_RealWorldTab>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  // View toggle
  bool _showCatalog = false;

  final List<_Product> _products = [
    _Product(
      id: '1',
      name: 'iPhone 15 Pro',
      description: '256GB, Natural Titanium',
      price: 999.00,
      quantity: 1,
      image: Icons.phone_iphone,
    ),
    _Product(
      id: '2',
      name: 'AirPods Pro',
      description: '2nd Generation',
      price: 249.00,
      quantity: 2,
      image: Icons.headphones,
    ),
    _Product(
      id: '3',
      name: 'MacBook Air M3',
      description: '13", 8GB RAM, 256GB SSD',
      price: 1299.00,
      quantity: 0,
      image: Icons.laptop_mac,
    ),
    _Product(
      id: '4',
      name: 'Apple Watch Ultra 2',
      description: '49mm, Titanium Case',
      price: 799.00,
      quantity: 0,
      image: Icons.watch,
    ),
    _Product(
      id: '5',
      name: 'iPad Pro 12.9"',
      description: 'M4 chip, 256GB, Wi-Fi',
      price: 1099.00,
      quantity: 1,
      image: Icons.tablet_mac,
    ),
  ];

  // Grocery catalog products
  final List<_Product> _groceries = [
    _Product(
      id: 'g1',
      name: 'Organic Bananas',
      description: 'Bunch of 6',
      price: 2.49,
      quantity: 0,
      image: Icons.breakfast_dining,
    ),
    _Product(
      id: 'g2',
      name: 'Whole Milk',
      description: '1 Gallon',
      price: 4.99,
      quantity: 0,
      image: Icons.water_drop,
    ),
    _Product(
      id: 'g3',
      name: 'Sourdough Bread',
      description: 'Artisan baked, sliced',
      price: 5.99,
      quantity: 0,
      image: Icons.bakery_dining,
    ),
    _Product(
      id: 'g4',
      name: 'Free Range Eggs',
      description: 'Dozen, large',
      price: 6.49,
      quantity: 0,
      image: Icons.egg,
    ),
    _Product(
      id: 'g5',
      name: 'Greek Yogurt',
      description: 'Plain, 32oz',
      price: 5.49,
      quantity: 0,
      image: Icons.icecream,
    ),
    _Product(
      id: 'g6',
      name: 'Avocados',
      description: 'Hass, pack of 4',
      price: 4.99,
      quantity: 0,
      image: Icons.eco,
    ),
  ];

  int get _totalItems =>
      _products.fold(0, (sum, product) => sum + product.quantity);

  double get _totalPrice => _products.fold(
      0.0, (sum, product) => sum + (product.price * product.quantity));

  int get _groceryTotalItems =>
      _groceries.fold(0, (sum, product) => sum + product.quantity);

  double get _groceryTotalPrice => _groceries.fold(
      0.0, (sum, product) => sum + (product.price * product.quantity));

  void _updateQuantity(int index, int qty) {
    setState(() {
      _products[index] = _products[index].copyWith(quantity: qty);
    });
  }

  void _updateGroceryQuantity(int index, int qty) {
    setState(() {
      _groceries[index] = _groceries[index].copyWith(quantity: qty);
    });
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    final theme = Theme.of(context);

    return Column(
      children: [
        // Toggle between views
        Container(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: Row(
            children: [
              Expanded(
                child: _ViewToggleButton(
                  label: 'Shopping Cart',
                  icon: Icons.shopping_cart,
                  isSelected: !_showCatalog,
                  badgeCount: _totalItems,
                  onTap: () => setState(() => _showCatalog = false),
                ),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: _ViewToggleButton(
                  label: 'Grocery Store',
                  icon: Icons.storefront,
                  isSelected: _showCatalog,
                  badgeCount: _groceryTotalItems,
                  onTap: () => setState(() => _showCatalog = true),
                ),
              ),
            ],
          ),
        ),
        Expanded(
          child: _showCatalog
              ? _buildGroceryCatalog(theme)
              : _buildShoppingCart(theme),
        ),
      ],
    );
  }

  Widget _buildShoppingCart(ThemeData theme) {
    return Column(
      children: [
        // Cart summary header
        Container(
          padding: const EdgeInsets.all(16),
          decoration: BoxDecoration(
            color: theme.colorScheme.primaryContainer.withValues(alpha: 0.3),
            border: Border(
              bottom: BorderSide(color: theme.dividerColor),
            ),
          ),
          child: Row(
            children: [
              CartBadge(
                count: _totalItems,
                child: Icon(
                  Icons.shopping_cart,
                  size: 32,
                  color: theme.colorScheme.primary,
                ),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Shopping Cart',
                      style: theme.textTheme.titleMedium?.copyWith(
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Text(
                      '$_totalItems items',
                      style: theme.textTheme.bodySmall?.copyWith(
                        color: Colors.grey[600],
                      ),
                    ),
                  ],
                ),
              ),
              Column(
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  Text(
                    'Total',
                    style: theme.textTheme.bodySmall?.copyWith(
                      color: Colors.grey[600],
                    ),
                  ),
                  Text(
                    '\$${_totalPrice.toStringAsFixed(2)}',
                    style: theme.textTheme.titleLarge?.copyWith(
                      fontWeight: FontWeight.bold,
                      color: theme.colorScheme.primary,
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),

        // Product list
        Expanded(
          child: ListView.separated(
            padding: const EdgeInsets.all(16),
            itemCount: _products.length,
            separatorBuilder: (_, __) => const SizedBox(height: 12),
            itemBuilder: (context, index) {
              final product = _products[index];
              return CartProductTile(
                leading: Container(
                  width: 64,
                  height: 64,
                  decoration: BoxDecoration(
                    color: Colors.grey[200],
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(
                    product.image,
                    color: Colors.grey[600],
                    size: 32,
                  ),
                ),
                title: product.name,
                subtitle: product.description,
                price: '\$${product.price.toStringAsFixed(2)}',
                quantity: product.quantity,
                maxQuantity: 10,
                stepperSize: CartStepperSize.compact,
                onQuantityChanged: (qty) => _updateQuantity(index, qty),
                onRemove: () => _updateQuantity(index, 0),
              );
            },
          ),
        ),

        // Checkout button
        _buildCheckoutBar(theme, _totalItems, _totalPrice),
      ],
    );
  }

  Widget _buildGroceryCatalog(ThemeData theme) {
    return Column(
      children: [
        // Store header
        Container(
          padding: const EdgeInsets.all(16),
          decoration: BoxDecoration(
            color: Colors.green.shade50,
            border: Border(
              bottom: BorderSide(color: theme.dividerColor),
            ),
          ),
          child: Row(
            children: [
              Icon(Icons.storefront, size: 32, color: Colors.green.shade700),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Fresh Groceries',
                      style: theme.textTheme.titleMedium?.copyWith(
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Text(
                      'Tap + to add items to your basket',
                      style: theme.textTheme.bodySmall?.copyWith(
                        color: Colors.grey[600],
                      ),
                    ),
                  ],
                ),
              ),
              if (_groceryTotalItems > 0)
                CartBadge(
                  count: _groceryTotalItems,
                  badgeColor: Colors.green.shade700,
                  child: Icon(
                    Icons.shopping_basket,
                    size: 28,
                    color: Colors.green.shade700,
                  ),
                ),
            ],
          ),
        ),

        // Grocery list with CartProductTile
        Expanded(
          child: ListView.separated(
            padding: const EdgeInsets.all(16),
            itemCount: _groceries.length,
            separatorBuilder: (_, __) => const SizedBox(height: 10),
            itemBuilder: (context, index) {
              final product = _groceries[index];
              final colors = [
                Colors.yellow.shade50,
                Colors.blue.shade50,
                Colors.amber.shade50,
                Colors.orange.shade50,
                Colors.pink.shade50,
                Colors.green.shade50,
              ];
              final iconColors = [
                Colors.yellow.shade800,
                Colors.blue.shade700,
                Colors.amber.shade800,
                Colors.orange.shade700,
                Colors.pink.shade700,
                Colors.green.shade700,
              ];
              return CartProductTile(
                leading: Container(
                  width: 56,
                  height: 56,
                  decoration: BoxDecoration(
                    color: colors[index % colors.length],
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Icon(
                    product.image,
                    color: iconColors[index % iconColors.length],
                    size: 28,
                  ),
                ),
                title: product.name,
                subtitle: product.description,
                price: '\$${product.price.toStringAsFixed(2)}',
                quantity: product.quantity,
                maxQuantity: 20,
                stepperSize: CartStepperSize.compact,
                stepperStyle: const CartStepperStyle(
                  backgroundColor: Color(0xFF2E7D32),
                  foregroundColor: Colors.white,
                  borderColor: Color(0xFF2E7D32),
                ),
                borderRadius: 16,
                onQuantityChanged: (qty) => _updateGroceryQuantity(index, qty),
                onRemove: () => _updateGroceryQuantity(index, 0),
              );
            },
          ),
        ),

        // Grocery checkout bar
        if (_groceryTotalItems > 0)
          _buildCheckoutBar(
            theme,
            _groceryTotalItems,
            _groceryTotalPrice,
            color: Colors.green.shade700,
          ),
      ],
    );
  }

  Widget _buildCheckoutBar(
    ThemeData theme,
    int totalItems,
    double totalPrice, {
    Color? color,
  }) {
    final btnColor = color ?? theme.colorScheme.primary;
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: theme.scaffoldBackgroundColor,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withValues(alpha: 0.1),
            blurRadius: 8,
            offset: const Offset(0, -2),
          ),
        ],
      ),
      child: SafeArea(
        child: SizedBox(
          width: double.infinity,
          child: ElevatedButton(
            onPressed: totalItems > 0
                ? () {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text(
                          'Checkout: $totalItems items for \$${totalPrice.toStringAsFixed(2)}',
                        ),
                      ),
                    );
                  }
                : null,
            style: ElevatedButton.styleFrom(
              backgroundColor: btnColor,
              foregroundColor: Colors.white,
              padding: const EdgeInsets.symmetric(vertical: 16),
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
            ),
            child: Text(
              totalItems > 0
                  ? 'Checkout (\$${totalPrice.toStringAsFixed(2)})'
                  : 'Cart is Empty',
              style: const TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _ViewToggleButton extends StatelessWidget {
  final String label;
  final IconData icon;
  final bool isSelected;
  final int badgeCount;
  final VoidCallback onTap;

  const _ViewToggleButton({
    required this.label,
    required this.icon,
    required this.isSelected,
    required this.badgeCount,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Material(
      color: isSelected
          ? theme.colorScheme.primaryContainer
          : Colors.grey.shade100,
      borderRadius: BorderRadius.circular(12),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Padding(
          padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              if (badgeCount > 0)
                CartBadge(
                  count: badgeCount,
                  size: 16,
                  child: Icon(
                    icon,
                    size: 20,
                    color: isSelected
                        ? theme.colorScheme.primary
                        : Colors.grey[600],
                  ),
                )
              else
                Icon(
                  icon,
                  size: 20,
                  color:
                      isSelected ? theme.colorScheme.primary : Colors.grey[600],
                ),
              const SizedBox(width: 8),
              Flexible(
                child: Text(
                  label,
                  style: TextStyle(
                    fontSize: 13,
                    fontWeight:
                        isSelected ? FontWeight.w600 : FontWeight.normal,
                    color: isSelected
                        ? theme.colorScheme.primary
                        : Colors.grey[600],
                  ),
                  overflow: TextOverflow.ellipsis,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// =============================================================================
// HELPERS
// =============================================================================
Widget _buildSection(
  BuildContext context,
  String title,
  String description,
  List<Widget> children,
) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(
        title,
        style: Theme.of(context).textTheme.titleLarge?.copyWith(
              fontWeight: FontWeight.bold,
            ),
      ),
      const SizedBox(height: 4),
      Text(
        description,
        style: Theme.of(context).textTheme.bodyMedium?.copyWith(
              color: Colors.grey[600],
            ),
      ),
      const SizedBox(height: 16),
      Container(
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: Colors.grey[50],
          borderRadius: BorderRadius.circular(12),
          border: Border.all(color: Colors.grey[200]!),
        ),
        child: Column(children: children),
      ),
    ],
  );
}

Widget _buildRow(String label, Widget stepper) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    children: [
      Flexible(
        child: Text(label, style: const TextStyle(fontSize: 14)),
      ),
      stepper,
    ],
  );
}

// =============================================================================
// MODELS
// =============================================================================
class _Product {
  final String id;
  final String name;
  final String description;
  final double price;
  final int quantity;
  final IconData image;

  const _Product({
    required this.id,
    required this.name,
    required this.description,
    required this.price,
    required this.quantity,
    required this.image,
  });

  _Product copyWith({
    String? id,
    String? name,
    String? description,
    double? price,
    int? quantity,
    IconData? image,
  }) {
    return _Product(
      id: id ?? this.id,
      name: name ?? this.name,
      description: description ?? this.description,
      price: price ?? this.price,
      quantity: quantity ?? this.quantity,
      image: image ?? this.image,
    );
  }
}
1
likes
150
points
533
downloads

Publisher

unverified uploader

Weekly Downloads

A customizable expandable cart quantity stepper widget for Flutter with async support, loading indicators, and theming.

Repository (GitHub)
View/report issues

Topics

#cart #stepper #quantity #e-commerce #widget

Documentation

API reference

License

MIT (license)

Dependencies

flutter, flutter_spinkit

More

Packages that depend on advance_cart_stepper