Skip to main content

flow_stepper_widget 0.0.6 copy "flow_stepper_widget: ^0.0.6" to clipboard
flow_stepper_widget: ^0.0.6 copied to clipboard

A lightweight, highly-customizable Flutter stepper/indicator widget with modern design and full styling control.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flow_stepper_widget/flow_stepper_widget.dart';
import 'package:url_launcher/url_launcher.dart';
import 'mock_events.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flow Stepper Widget Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late List<FlowStep> _steps;

  final Color _activeColor = Colors.deepPurple;
  final Color _inactiveColor = Colors.grey;
  final Color _disabledColor = Colors.grey.shade400;

  bool _isVertical = true;
  bool _showLines = true;
  bool _reverse = false;
  double _spacing = 24.0;
  double _lineThickness = 1.0;
  List<double>? _dashPattern;

  ShapeBorder _shape = const CircleBorder();
  double _iconSize = 32.0;
  double _labelFontSize = 16.0;
  double _padding = 12.0;

  IconData _selectedIcon = Icons.star;
  bool _useCustomWidget = false;
  final double _customWidgetSize = 42;
  bool _useLabelBuilder = false;
  LabelType _selectedLabelEnum = LabelType.vertical;
  LabelOrderType _selectedLabelOrderEnum = LabelOrderType.start;

  @override
  void initState() {
    super.initState();
    _steps = List<FlowStep>.from(demoSteps);
  }

  void _toggleStep(String id, bool newState) {
    setState(() {
      final index = _steps.indexWhere((step) => step.id == id);
      if (index != -1) {
        _steps[index] = _steps[index].copyWith(active: newState);
      }
    });
  }

  void _showTooltip(String id) {
    final step = _steps.firstWhere((step) => step.id == id);
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('${step.label ?? 'Step'} is ${step.enabled ? 'enabled' : 'disabled'}'),
        duration: const Duration(seconds: 1),
      ),
    );
  }

  Future<void> _launchUrl(String url) async {
    if (!await launchUrl(
      Uri.parse(url),
      mode: LaunchMode.externalApplication,
    )) {
      throw Exception('Could not launch $url');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flow Stepper Widget Demo'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Package info
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Flow Stepper Widget',
                      style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                            fontWeight: FontWeight.bold,
                          ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      'A lightweight, highly-customizable Flutter stepper/indicator widget with modern design and full styling control.',
                      style: Theme.of(context).textTheme.bodyLarge,
                    ),
                    const SizedBox(height: 16),
                    Row(
                      children: [
                        FilledButton.icon(
                          onPressed: () => _launchUrl('https://pub.dev/packages/flow_stepper_widget'),
                          icon: const Icon(Icons.public),
                          label: const Text('pub.dev'),
                        ),
                        const SizedBox(width: 8),
                        FilledButton.icon(
                          onPressed: () => _launchUrl('https://github.com/danial2026/flow_stepper_widget'),
                          icon: const Icon(Icons.code),
                          label: const Text('GitHub'),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 24),
            // Controls
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      'Controls',
                      style: TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 16),
                    Wrap(
                      spacing: 16,
                      runSpacing: 16,
                      children: [
                        SwitchListTile(
                          title: const Text('Vertical Layout'),
                          value: _isVertical,
                          onChanged: (value) => setState(() => _isVertical = value),
                        ),
                        SwitchListTile(
                          title: const Text('Show Lines'),
                          value: _showLines,
                          onChanged: (value) => setState(() => _showLines = value),
                        ),
                        SwitchListTile(
                          title: const Text('Reverse Order'),
                          value: _reverse,
                          onChanged: (value) => setState(() => _reverse = value),
                        ),
                        SwitchListTile(
                          title: const Text('Dashed Lines'),
                          value: _dashPattern != null,
                          onChanged: (value) => setState(() => _dashPattern = value ? [4, 2] : null),
                        ),
                        SwitchListTile(
                          title: const Text('Use Label Builder'),
                          subtitle: const Text('Switch between traditional labels and custom labelBuilder'),
                          value: _useLabelBuilder,
                          onChanged: (value) => setState(() => _useLabelBuilder = value),
                        ),
                      ],
                    ),
                    const SizedBox(height: 16),
                    Wrap(
                      spacing: 16,
                      runSpacing: 16,
                      children: [
                        SizedBox(
                          width: 200,
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              const Text('Spacing'),
                              Slider(
                                value: _spacing,
                                min: 8,
                                max: 48,
                                divisions: 5,
                                label: _spacing.round().toString(),
                                onChanged: (value) => setState(() => _spacing = value),
                              ),
                            ],
                          ),
                        ),
                        SizedBox(
                          width: 200,
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              const Text('Line Thickness'),
                              Slider(
                                value: _lineThickness,
                                min: 1,
                                max: 4,
                                divisions: 3,
                                label: _lineThickness.round().toString(),
                                onChanged: (value) => setState(() => _lineThickness = value),
                              ),
                            ],
                          ),
                        ),
                        SizedBox(
                          width: 200,
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              const Text('Icon Size'),
                              Slider(
                                value: _iconSize,
                                min: 16,
                                max: 64,
                                divisions: 6,
                                label: _iconSize.round().toString(),
                                onChanged: (value) => setState(() => _iconSize = value),
                              ),
                            ],
                          ),
                        ),
                        SizedBox(
                          width: 200,
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              const Text('Label Font Size'),
                              Slider(
                                value: _labelFontSize,
                                min: 10,
                                max: 32,
                                divisions: 11,
                                label: _labelFontSize.round().toString(),
                                onChanged: (value) => setState(() => _labelFontSize = value),
                              ),
                            ],
                          ),
                        ),
                        SizedBox(
                          width: 200,
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              const Text('Padding'),
                              Slider(
                                value: _padding,
                                min: 0,
                                max: 32,
                                divisions: 8,
                                label: _padding.round().toString(),
                                onChanged: (value) => setState(() => _padding = value),
                              ),
                            ],
                          ),
                        ),
                        SizedBox(
                          width: 200,
                          child: DropdownButtonFormField<ShapeBorder>(
                            value: _shape,
                            decoration: const InputDecoration(labelText: 'Shape'),
                            items: const [
                              DropdownMenuItem(
                                value: CircleBorder(),
                                child: Text('Circle'),
                              ),
                              DropdownMenuItem(
                                value: StadiumBorder(),
                                child: Text('Stadium'),
                              ),
                              DropdownMenuItem(
                                value: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.all(Radius.circular(12)),
                                ),
                                child: Text('Rounded Rectangle'),
                              ),
                            ],
                            onChanged: (value) {
                              if (value != null) setState(() => _shape = value);
                            },
                          ),
                        ),
                        SizedBox(
                          width: 200,
                          child: DropdownButtonFormField<IconData>(
                            value: _selectedIcon,
                            decoration: const InputDecoration(labelText: 'Step Icon'),
                            items: const [
                              DropdownMenuItem(
                                value: Icons.star,
                                child: Text('Star'),
                              ),
                              DropdownMenuItem(
                                value: Icons.check,
                                child: Text('Check'),
                              ),
                              DropdownMenuItem(
                                value: Icons.more_horiz,
                                child: Text('More'),
                              ),
                              DropdownMenuItem(
                                value: Icons.favorite,
                                child: Text('Favorite'),
                              ),
                              DropdownMenuItem(
                                value: Icons.circle,
                                child: Text('Circle'),
                              ),
                            ],
                            onChanged: (value) {
                              if (value != null) setState(() => _selectedIcon = value);
                            },
                          ),
                        ),
                        SizedBox(
                          width: 200,
                          child: DropdownButtonFormField<LabelType>(
                            value: _selectedLabelEnum,
                            decoration: const InputDecoration(labelText: 'Label Type'),
                            items: const [
                              DropdownMenuItem(
                                value: LabelType.vertical,
                                child: Text('Vertical'),
                              ),
                              DropdownMenuItem(
                                value: LabelType.horizontal,
                                child: Text('Horizontal'),
                              ),
                            ],
                            onChanged: (value) {
                              if (value != null) setState(() => _selectedLabelEnum = value);
                            },
                          ),
                        ),
                        SizedBox(
                          width: 200,
                          child: DropdownButtonFormField<LabelOrderType>(
                            value: _selectedLabelOrderEnum,
                            decoration: const InputDecoration(labelText: 'Label Order Type'),
                            items: const [
                              DropdownMenuItem(
                                value: LabelOrderType.start,
                                child: Text('Start'),
                              ),
                              DropdownMenuItem(
                                value: LabelOrderType.end,
                                child: Text('End'),
                              ),
                            ],
                            onChanged: (value) {
                              if (value != null) setState(() => _selectedLabelOrderEnum = value);
                            },
                          ),
                        ),
                        SizedBox(
                          width: 200,
                          child: SwitchListTile(
                            title: const Text('Custom Widget Indicator'),
                            value: _useCustomWidget,
                            onChanged: (value) => setState(() => _useCustomWidget = value),
                          ),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 24),
            // Stepper
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      'Stepper',
                      style: TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 16),
                    Center(
                      child: _useLabelBuilder
                          ? FlowStepperWidget(
                              steps: _steps
                                  .map(
                                    (step) => step.copyWith(
                                      icon: _selectedIcon,
                                      iconSize: _iconSize,
                                      labelBuilder: (context, step) => _buildCustomLabel(context, step),
                                      shape: _shape,
                                      padding: EdgeInsets.all(_padding),
                                    ),
                                  )
                                  .toList(),
                              labelType: _selectedLabelEnum,
                              direction: _isVertical ? Axis.vertical : Axis.horizontal,
                              labelOrderType: _selectedLabelOrderEnum,
                              spacing: _spacing,
                              showLines: _showLines,
                              reverse: _reverse,
                              lineActiveColor: _activeColor,
                              lineInactiveColor: _inactiveColor,
                              lineThickness: _lineThickness,
                              dashPattern: _dashPattern,
                              activeStepColor: _activeColor,
                              inactiveStepColor: _inactiveColor,
                              disabledStepColor: _disabledColor,
                              stepIconSize: _useCustomWidget ? _customWidgetSize : _iconSize,
                              stepTextStyle: TextStyle(fontSize: _labelFontSize),
                              stepShape: _shape,
                              stepPadding: EdgeInsets.all(_padding),
                              stepAnimationDuration: const Duration(milliseconds: 300),
                              onStepTap: _toggleStep,
                              onStepLongPress: _showTooltip,
                              stepIconBuilder: _useCustomWidget ? _buildStepIcon : null,
                            )
                          : FlowStepperWidget(
                              steps: _steps
                                  .map((step) => step.copyWith(
                                        icon: _selectedIcon,
                                        iconSize: _iconSize,
                                        labelBuilder: null, // Clear custom labelBuilder
                                        textStyle: TextStyle(fontSize: _labelFontSize),
                                        shape: _shape,
                                        padding: EdgeInsets.all(_padding),
                                      ))
                                  .toList(),
                              labelType: _selectedLabelEnum,
                              direction: _isVertical ? Axis.vertical : Axis.horizontal,
                              labelOrderType: _selectedLabelOrderEnum,
                              spacing: _spacing,
                              showLines: _showLines,
                              reverse: _reverse,
                              lineActiveColor: _activeColor,
                              lineInactiveColor: _inactiveColor,
                              lineThickness: _lineThickness,
                              dashPattern: _dashPattern,
                              activeStepColor: _activeColor,
                              inactiveStepColor: _inactiveColor,
                              disabledStepColor: _disabledColor,
                              stepIconSize: _useCustomWidget ? _customWidgetSize : _iconSize,
                              stepTextStyle: TextStyle(fontSize: _labelFontSize),
                              stepShape: _shape,
                              stepPadding: EdgeInsets.all(_padding),
                              stepAnimationDuration: const Duration(milliseconds: 300),
                              onStepTap: _toggleStep,
                              onStepLongPress: _showTooltip,
                              stepIconBuilder: _useCustomWidget ? _buildStepIcon : null,
                            ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildStepIcon(BuildContext context, FlowStep step, Color color, double size) {
    int index = demoSteps.indexWhere((v) => v.id == step.id);
    return Icon(
      _getStepIcons[index],
      size: size,
      color: Colors.white,
    );
  }

  Widget _buildCustomLabel(BuildContext context, FlowStep step) {
    // This function matches the labelBuilder signature: Widget Function(dynamic, dynamic)
    // Get the original label from demoSteps for reference
    final originalStep = demoSteps.firstWhere((s) => s.id == step.id);
    final originalLabel = originalStep.label ?? 'Step';

    // Create a custom label with enhanced styling
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      decoration: BoxDecoration(
        color: step.active ? _activeColor.withOpacity(0.1) : _inactiveColor.withOpacity(0.1),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(
          color: step.active ? _activeColor : _inactiveColor,
          width: 1,
        ),
        boxShadow: step.active
            ? [
                BoxShadow(
                  color: _activeColor.withOpacity(0.3),
                  blurRadius: 4,
                  offset: const Offset(0, 2),
                ),
              ]
            : null,
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(
            step.active ? Icons.check_circle : Icons.radio_button_unchecked,
            size: 16,
            color: step.active ? _activeColor : _inactiveColor,
          ),
          const SizedBox(width: 6),
          Text(
            originalLabel,
            style: TextStyle(
              color: step.active ? _activeColor : _inactiveColor,
              fontWeight: step.active ? FontWeight.bold : FontWeight.normal,
              fontSize: _labelFontSize,
            ),
          ),
          if (step.active) ...[
            const SizedBox(width: 4),
            Icon(
              Icons.auto_awesome,
              size: 12,
              color: _activeColor,
            ),
          ],
        ],
      ),
    );
  }

  final List<IconData> _getStepIcons = [
    Icons.star,
    Icons.favorite,
    Icons.bolt,
    Icons.cloud,
    Icons.diamond,
  ];
}
3
likes
160
points
88
downloads

Documentation

API reference

Publisher

verified publisherdanials.space

Weekly Downloads

A lightweight, highly-customizable Flutter stepper/indicator widget with modern design and full styling control.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flow_stepper_widget