expressive_wavy_progress 1.0.0 copy "expressive_wavy_progress: ^1.0.0" to clipboard
expressive_wavy_progress: ^1.0.0 copied to clipboard

Google Material 3 Expressive wavy progress indicators, Pixel squiggly sliders, liquid fluid tanks, story progress segments, audio visualizers, and shape morphing spinners for Flutter.

example/lib/main.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:expressive_wavy_progress/expressive_wavy_progress.dart';

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

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

  @override
  State<GoogleWavyApp> createState() => _GoogleWavyAppState();
}

class _GoogleWavyAppState extends State<GoogleWavyApp> {
  ThemeMode _themeMode = ThemeMode.system;
  Color _seedColor = const Color(0xFF4285F4); // Google Blue default

  void _toggleTheme() {
    setState(() {
      _themeMode = _themeMode == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark;
    });
  }

  void _changeSeedColor(Color color) {
    setState(() {
      _seedColor = color;
    });
  }

  @override
  Widget build(BuildContext context) {
    final textTheme = GoogleFonts.plusJakartaSansTextTheme();

    return MaterialApp(
      title: 'Google Material 3 Expressive Wavy Progress',
      debugShowCheckedModeBanner: false,
      themeMode: _themeMode,
      theme: ThemeData(
        useMaterial3: true,
        brightness: Brightness.light,
        colorScheme: ColorScheme.fromSeed(
          seedColor: _seedColor,
          brightness: Brightness.light,
        ),
        textTheme: textTheme,
      ),
      darkTheme: ThemeData(
        useMaterial3: true,
        brightness: Brightness.dark,
        colorScheme: ColorScheme.fromSeed(
          seedColor: _seedColor,
          brightness: Brightness.dark,
        ),
        scaffoldBackgroundColor: const Color(0xFF131314), // Google dark surface
        textTheme: textTheme.apply(
          bodyColor: const Color(0xFFE3E3E3),
          displayColor: Colors.white,
        ),
      ),
      home: ShowcaseScreen(
        onToggleTheme: _toggleTheme,
        onChangeSeedColor: _changeSeedColor,
        currentSeed: _seedColor,
        isDark: _themeMode == ThemeMode.dark,
      ),
    );
  }
}

class ShowcaseScreen extends StatefulWidget {
  final VoidCallback onToggleTheme;
  final ValueChanged<Color> onChangeSeedColor;
  final Color currentSeed;
  final bool isDark;

  const ShowcaseScreen({
    super.key,
    required this.onToggleTheme,
    required this.onChangeSeedColor,
    required this.currentSeed,
    required this.isDark,
  });

  @override
  State<ShowcaseScreen> createState() => _ShowcaseScreenState();
}

class _ShowcaseScreenState extends State<ShowcaseScreen>
    with TickerProviderStateMixin {
  late TabController _tabController;

  // Global Determinate Simulation Progress
  double _determinateProgress = 0.65;
  bool _isAutoSimulating = false;
  Timer? _simulationTimer;

  // Customizer Sandbox State
  double _customAmplitude = 4.0;
  double _customWavelength = 28.0;
  double _customStrokeWidth = 4.0;
  double _customSpeed = 1.0;
  double _customTrackGap = 4.0;
  bool _customWavyTrack = false;
  int _customColorPreset = 0; // 0=Google 4-Color, 1=Gemini Aura, 2=Single Theme, 3=Pixel Ocean
  double _customGlowRadius = 0.0;
  bool _customSparkles = false;

  // Music Player Demo State
  bool _isPlaying = true;
  double _songProgress = 0.42;

  // Vertical Slider Demo State
  double _verticalVolume = 0.72;
  double _verticalBrightness = 0.55;

  // Story Segment State
  int _storyIndex = 1;
  double _storyProgress = 0.45;
  Timer? _storyTimer;

  // Liquid Container State
  double _liquidLevel = 0.68;
  LiquidShape _currentLiquidShape = LiquidShape.battery;

  // Audio Reactive State
  double _manualMicVolume = 0.5;
  bool _autoSimulateVoice = true;

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

  void _startStoryTimer() {
    _storyTimer?.cancel();
    _storyTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
      if (!mounted) return;
      setState(() {
        _storyProgress += 0.02;
        if (_storyProgress >= 1.0) {
          _storyProgress = 0.0;
          _storyIndex = (_storyIndex + 1) % 4;
        }
      });
    });
  }

  @override
  void dispose() {
    _tabController.dispose();
    _simulationTimer?.cancel();
    _storyTimer?.cancel();
    super.dispose();
  }

  void _toggleAutoSimulation() {
    setState(() {
      _isAutoSimulating = !_isAutoSimulating;
      if (_isAutoSimulating) {
        _simulationTimer = Timer.periodic(const Duration(milliseconds: 50), (timer) {
          if (!mounted) return;
          setState(() {
            _determinateProgress += 0.006;
            if (_determinateProgress > 1.0) {
              _determinateProgress = 0.0;
            }
          });
        });
      } else {
        _simulationTimer?.cancel();
      }
    });
  }

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

    return Scaffold(
      body: NestedScrollView(
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          return <Widget>[
            // Sleek Expressive App Bar
            SliverAppBar.large(
              expandedHeight: 200,
              floating: false,
              pinned: true,
              backgroundColor: colorScheme.surface,
              surfaceTintColor: colorScheme.primary,
              title: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  _buildGoogleLogoBadge(),
                  const SizedBox(width: 12),
                  const Text(
                    'Expressive Wavy Progress',
                    style: TextStyle(fontWeight: FontWeight.w700, letterSpacing: -0.5),
                  ),
                ],
              ),
              actions: [
                // Palette Picker
                PopupMenuButton<Color>(
                  tooltip: 'Select Color Theme',
                  icon: const Icon(Icons.palette_outlined),
                  onSelected: widget.onChangeSeedColor,
                  itemBuilder: (context) => [
                    const PopupMenuItem(
                      value: Color(0xFF4285F4),
                      child: Row(
                        children: [
                          CircleAvatar(radius: 8, backgroundColor: Color(0xFF4285F4)),
                          SizedBox(width: 8),
                          Text('Google Blue'),
                        ],
                      ),
                    ),
                    const PopupMenuItem(
                      value: Color(0xFFEA4335),
                      child: Row(
                        children: [
                          CircleAvatar(radius: 8, backgroundColor: Color(0xFFEA4335)),
                          SizedBox(width: 8),
                          Text('Google Red'),
                        ],
                      ),
                    ),
                    const PopupMenuItem(
                      value: Color(0xFF34A853),
                      child: Row(
                        children: [
                          CircleAvatar(radius: 8, backgroundColor: Color(0xFF34A853)),
                          SizedBox(width: 8),
                          Text('Google Green'),
                        ],
                      ),
                    ),
                    const PopupMenuItem(
                      value: Color(0xFF9C27B0),
                      child: Row(
                        children: [
                          CircleAvatar(radius: 8, backgroundColor: Color(0xFF9C27B0)),
                          SizedBox(width: 8),
                          Text('Pixel Purple'),
                        ],
                      ),
                    ),
                  ],
                ),
                // Dark / Light Mode Toggle
                IconButton(
                  tooltip: 'Toggle Dark/Light Mode',
                  icon: Icon(
                    widget.isDark ? Icons.light_mode_outlined : Icons.dark_mode_outlined,
                  ),
                  onPressed: widget.onToggleTheme,
                ),
                const SizedBox(width: 12),
              ],
              flexibleSpace: FlexibleSpaceBar(
                background: Container(
                  decoration: BoxDecoration(
                    gradient: LinearGradient(
                      begin: Alignment.topLeft,
                      end: Alignment.bottomRight,
                      colors: [
                        colorScheme.primaryContainer.withValues(alpha: 0.35),
                        colorScheme.surface,
                      ],
                    ),
                  ),
                  child: Padding(
                    padding: const EdgeInsets.only(left: 24, right: 24, bottom: 64),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.end,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          children: [
                            Container(
                              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
                              decoration: BoxDecoration(
                                color: GoogleWavyColors.blue.withValues(alpha: 0.12),
                                borderRadius: BorderRadius.circular(20),
                                border: Border.all(color: GoogleWavyColors.blue.withValues(alpha: 0.3)),
                              ),
                              child: const Row(
                                mainAxisSize: MainAxisSize.min,
                                children: [
                                  Icon(Icons.auto_awesome, size: 14, color: GoogleWavyColors.blue),
                                  SizedBox(width: 4),
                                  Text(
                                    'Material 3 Expressive Suite',
                                    style: TextStyle(
                                      fontSize: 12,
                                      fontWeight: FontWeight.w600,
                                      color: GoogleWavyColors.blue,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                            const SizedBox(width: 8),
                            Container(
                              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
                              decoration: BoxDecoration(
                                color: GoogleWavyColors.green.withValues(alpha: 0.12),
                                borderRadius: BorderRadius.circular(20),
                              ),
                              child: const Text(
                                'v1.0.0 Ready',
                                style: TextStyle(
                                  fontSize: 12,
                                  fontWeight: FontWeight.w600,
                                  color: GoogleWavyColors.green,
                                ),
                              ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 8),
                        Text(
                          'Sinusoidal waves, liquid fluid tanks, story segments, audio waveforms, & shape-morphing spinners.',
                          style: TextStyle(
                            fontSize: 14,
                            color: colorScheme.onSurfaceVariant,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
              bottom: TabBar(
                controller: _tabController,
                isScrollable: true,
                tabAlignment: TabAlignment.start,
                indicatorColor: colorScheme.primary,
                dividerColor: Colors.transparent,
                tabs: const [
                  Tab(icon: Icon(Icons.linear_scale, size: 18), text: 'Linear Wavy'),
                  Tab(icon: Icon(Icons.motion_photos_on, size: 18), text: 'Circular Wavy'),
                  Tab(icon: Icon(Icons.water_drop_outlined, size: 18), text: 'Liquid Tanks'),
                  Tab(icon: Icon(Icons.view_carousel_outlined, size: 18), text: 'Story Segments'),
                  Tab(icon: Icon(Icons.tune_rounded, size: 18), text: 'Sliders (Pixel & Vert)'),
                  Tab(icon: Icon(Icons.graphic_eq_rounded, size: 18), text: 'Voice & Audio'),
                  Tab(icon: Icon(Icons.auto_fix_high_rounded, size: 18), text: 'Spinners & Skeletons'),
                  Tab(icon: Icon(Icons.code_rounded, size: 18), text: 'Sandbox & Code'),
                ],
              ),
            ),
          ];
        },
        body: TabBarView(
          controller: _tabController,
          children: [
            _buildLinearWavyTab(colorScheme),
            _buildCircularWavyTab(colorScheme),
            _buildLiquidWaveTab(colorScheme),
            _buildStorySegmentsTab(colorScheme),
            _buildSlidersTab(colorScheme),
            _buildAudioVisualizerTab(colorScheme),
            _buildMorphingAndSkeletonsTab(colorScheme),
            _buildSandboxTab(colorScheme),
          ],
        ),
      ),
    );
  }

  Widget _buildGoogleLogoBadge() {
    return Container(
      width: 28,
      height: 28,
      decoration: BoxDecoration(
        color: Colors.white,
        shape: BoxShape.circle,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withValues(alpha: 0.1),
            blurRadius: 4,
            offset: const Offset(0, 2),
          ),
        ],
      ),
      child: Center(
        child: Text(
          'G',
          style: GoogleFonts.poppins(
            fontSize: 18,
            fontWeight: FontWeight.w700,
            color: const Color(0xFF4285F4),
          ),
        ),
      ),
    );
  }

  // -------------------------------------------------------------
  // TAB 1: Linear Wavy Progress
  // -------------------------------------------------------------
  Widget _buildLinearWavyTab(ColorScheme colorScheme) {
    return ListView(
      padding: const EdgeInsets.all(24),
      children: [
        _buildGlobalProgressSlider(colorScheme),
        const SizedBox(height: 24),
        _buildSectionCard(
          title: 'Google Signature 4-Color Wavy Bar',
          subtitle: 'Determinate multi-color traveling wave with smooth start/stop envelope',
          child: Column(
            children: [
              GoogleLinearWavyProgressIndicator.googleColors(
                value: _determinateProgress,
                minHeight: 20,
                strokeWidth: 4.5,
                wavelength: 30,
                amplitude: 4.0,
              ),
              const SizedBox(height: 12),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    'Progress: ${(_determinateProgress * 100).toInt()}%',
                    style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13),
                  ),
                  Text(
                    'Wavelength: 30dp · Amp: 4.0dp',
                    style: TextStyle(fontSize: 12, color: colorScheme.outline),
                  ),
                ],
              ),
            ],
          ),
        ),
        const SizedBox(height: 20),
        _buildSectionCard(
          title: 'Google Gemini AI Iridescent Aura',
          subtitle: 'Multi-hue sweep shimmer with floating sparkle particles',
          child: GoogleWavyParticleOverlay(
            isEmitting: true,
            particleType: WavyParticleType.sparkleStar,
            child: GoogleLinearWavyProgressIndicator.gemini(
              value: _determinateProgress,
              minHeight: 24,
              strokeWidth: 5.0,
              wavelength: 32,
              amplitude: 4.5,
              glowRadius: 6.0,
              glowColor: const Color(0xFF9C27B0),
            ),
          ),
        ),
        const SizedBox(height: 20),
        _buildSectionCard(
          title: 'Material 3 Indeterminate Loading',
          subtitle: 'Continuous infinite sinusoidal traveling wave for background operations',
          child: Column(
            children: [
              GoogleLinearWavyProgressIndicator.googleColors(
                minHeight: 20,
                strokeWidth: 4.5,
                wavelength: 26,
                amplitude: 3.5,
              ),
              const SizedBox(height: 16),
              GoogleLinearWavyProgressIndicator(
                colors: [colorScheme.primary, colorScheme.tertiary],
                minHeight: 18,
                strokeWidth: 4.0,
                amplitude: 4.0,
                wavyTrack: true,
              ),
            ],
          ),
        ),
      ],
    );
  }

  // -------------------------------------------------------------
  // TAB 2: Circular Wavy Indicators
  // -------------------------------------------------------------
  Widget _buildCircularWavyTab(ColorScheme colorScheme) {
    return ListView(
      padding: const EdgeInsets.all(24),
      children: [
        _buildGlobalProgressSlider(colorScheme),
        const SizedBox(height: 24),
        _buildSectionCard(
          title: 'Radial Sinusoidal Undulations',
          subtitle: 'Determinate circular progress arcs with sinusoidal lobe oscillations',
          child: Wrap(
            spacing: 32,
            runSpacing: 24,
            alignment: WrapAlignment.spaceEvenly,
            crossAxisAlignment: WrapCrossAlignment.center,
            children: [
              _buildLabeledCircular(
                label: 'Google 4-Color Arc',
                indicator: GoogleCircularWavyProgressIndicator.googleColors(
                  value: _determinateProgress,
                  size: 64,
                  strokeWidth: 5.0,
                  waveCount: 8,
                  amplitude: 3.5,
                ),
              ),
              _buildLabeledCircular(
                label: 'Gemini AI Aura',
                indicator: GoogleCircularWavyProgressIndicator.gemini(
                  value: _determinateProgress,
                  size: 72,
                  strokeWidth: 5.5,
                  waveCount: 10,
                  amplitude: 4.0,
                  glowRadius: 6.0,
                  glowColor: const Color(0xFF9C27B0),
                ),
              ),
              _buildLabeledCircular(
                label: '12-Lobe Flower',
                indicator: GoogleCircularWavyProgressIndicator(
                  value: _determinateProgress,
                  size: 64,
                  strokeWidth: 4.5,
                  color: colorScheme.primary,
                  waveCount: 12,
                  amplitude: 3.0,
                ),
              ),
            ],
          ),
        ),
        const SizedBox(height: 20),
        _buildSectionCard(
          title: 'Indeterminate Rotating Sweep Spinners',
          subtitle: 'Dynamic arc expansion, rotation, and undulation according to M3 specs',
          child: Wrap(
            spacing: 32,
            runSpacing: 24,
            alignment: WrapAlignment.spaceEvenly,
            crossAxisAlignment: WrapCrossAlignment.center,
            children: [
              _buildLabeledCircular(
                label: 'Google Quadrant Sweep',
                indicator: GoogleCircularWavyProgressIndicator.googleColors(
                  size: 64,
                  strokeWidth: 4.5,
                  waveCount: 8,
                  amplitude: 3.5,
                ),
              ),
              _buildLabeledCircular(
                label: 'Iridescent Gemini Sweep',
                indicator: GoogleCircularWavyProgressIndicator.gemini(
                  size: 72,
                  strokeWidth: 5.0,
                  waveCount: 10,
                  amplitude: 4.0,
                ),
              ),
              _buildLabeledCircular(
                label: 'Wavy Track Spinner',
                indicator: GoogleCircularWavyProgressIndicator(
                  size: 64,
                  strokeWidth: 4.5,
                  color: colorScheme.secondary,
                  waveCount: 9,
                  amplitude: 3.5,
                  wavyTrack: true,
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

  // -------------------------------------------------------------
  // TAB 3: Liquid Wave Containers
  // -------------------------------------------------------------
  Widget _buildLiquidWaveTab(ColorScheme colorScheme) {
    return ListView(
      padding: const EdgeInsets.all(24),
      children: [
        Card(
          elevation: 0,
          color: colorScheme.surfaceContainerLow,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    const Text('Fluid Level Control', style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16)),
                    Text('${(_liquidLevel * 100).toInt()}%', style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16)),
                  ],
                ),
                Slider(
                  value: _liquidLevel,
                  min: 0.0,
                  max: 1.0,
                  onChanged: (val) => setState(() => _liquidLevel = val),
                ),
                const SizedBox(height: 8),
                SingleChildScrollView(
                  scrollDirection: Axis.horizontal,
                  child: Row(
                    children: LiquidShape.values.map((s) {
                      final isSelected = _currentLiquidShape == s;
                      return Padding(
                        padding: const EdgeInsets.only(right: 8.0),
                        child: ChoiceChip(
                          label: Text(s.name.toUpperCase()),
                          selected: isSelected,
                          onSelected: (selected) {
                            if (selected) setState(() => _currentLiquidShape = s);
                          },
                        ),
                      );
                    }).toList(),
                  ),
                ),
              ],
            ),
          ),
        ),
        const SizedBox(height: 24),
        _buildSectionCard(
          title: 'Geometric Fluid Tanks & Level Meters',
          subtitle: 'Dual sinusoidal fluid waves with surface refraction and floating metrics',
          child: Wrap(
            spacing: 28,
            runSpacing: 24,
            alignment: WrapAlignment.spaceEvenly,
            crossAxisAlignment: WrapCrossAlignment.center,
            children: [
              _buildLabeledLiquid(
                label: 'Google 4-Color Circle',
                child: GoogleLiquidWaveIndicator.googleColors(
                  value: _liquidLevel,
                  shape: LiquidShape.circle,
                  width: 110,
                  height: 110,
                  center: Text(
                    '${(_liquidLevel * 100).toInt()}%',
                    style: const TextStyle(fontWeight: FontWeight.w800, fontSize: 18, color: Colors.white),
                  ),
                ),
              ),
              _buildLabeledLiquid(
                label: 'Pixel Battery Cell',
                child: GoogleLiquidWaveIndicator.battery(
                  value: _liquidLevel,
                  width: 75,
                  height: 125,
                  center: Text(
                    '${(_liquidLevel * 100).toInt()}%',
                    style: const TextStyle(fontWeight: FontWeight.w800, fontSize: 13, color: Colors.white),
                  ),
                ),
              ),
              _buildLabeledLiquid(
                label: 'Gemini AI Droplet',
                child: GoogleLiquidWaveIndicator.gemini(
                  value: _liquidLevel,
                  shape: LiquidShape.droplet,
                  width: 105,
                  height: 125,
                  center: const Padding(
                    padding: EdgeInsets.only(top: 24.0),
                    child: Icon(Icons.auto_awesome, color: Colors.white, size: 24),
                  ),
                ),
              ),
              _buildLabeledLiquid(
                label: 'Active Shape: ${_currentLiquidShape.name}',
                child: GoogleLiquidWaveIndicator(
                  value: _liquidLevel,
                  shape: _currentLiquidShape,
                  width: 110,
                  height: 110,
                  color: colorScheme.primary,
                  secondaryColor: colorScheme.primary.withValues(alpha: 0.35),
                  borderColor: colorScheme.primary,
                  center: Text(
                    '${(_liquidLevel * 100).toInt()}%',
                    style: const TextStyle(fontWeight: FontWeight.w800, fontSize: 16, color: Colors.white),
                  ),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

  // -------------------------------------------------------------
  // TAB 4: Story Segments
  // -------------------------------------------------------------
  Widget _buildStorySegmentsTab(ColorScheme colorScheme) {
    return ListView(
      padding: const EdgeInsets.all(24),
      children: [
        _buildSectionCard(
          title: 'Instagram / WhatsApp Story Style Wavy Segments',
          subtitle: 'Active step undulates with sinusoidal wave while past steps remain solid',
          child: Column(
            children: [
              GoogleSegmentedWavyProgressIndicator.googleColors(
                segmentCount: 4,
                currentIndex: _storyIndex,
                currentSegmentProgress: _storyProgress,
                strokeWidth: 4.5,
                waveAmplitude: 3.5,
                segmentGap: 8.0,
              ),
              const SizedBox(height: 24),
              Container(
                height: 180,
                width: double.infinity,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(16),
                  gradient: LinearGradient(
                    begin: Alignment.topLeft,
                    end: Alignment.bottomRight,
                    colors: [
                      GoogleWavyColors.googleFourColors[_storyIndex % 4].withValues(alpha: 0.3),
                      colorScheme.surfaceContainerHighest,
                    ],
                  ),
                ),
                child: Center(
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(
                        [Icons.cloud_upload_outlined, Icons.sync_rounded, Icons.auto_awesome, Icons.check_circle_outline][_storyIndex % 4],
                        size: 44,
                        color: GoogleWavyColors.googleFourColors[_storyIndex % 4],
                      ),
                      const SizedBox(height: 8),
                      Text(
                        ['Step 1: Uploading Media', 'Step 2: Syncing Metadata', 'Step 3: AI Processing', 'Step 4: Publishing Live'][_storyIndex % 4],
                        style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16),
                      ),
                      Text(
                        'Progress: ${(_storyProgress * 100).toInt()}%',
                        style: TextStyle(color: colorScheme.outline, fontSize: 13),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 16),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  IconButton.filledTonal(
                    icon: const Icon(Icons.skip_previous_rounded),
                    onPressed: () {
                      setState(() {
                        _storyIndex = (_storyIndex - 1).clamp(0, 3);
                        _storyProgress = 0.0;
                      });
                    },
                  ),
                  const SizedBox(width: 12),
                  IconButton.filled(
                    icon: Icon(_storyTimer?.isActive ?? false ? Icons.pause_rounded : Icons.play_arrow_rounded),
                    onPressed: () {
                      setState(() {
                        if (_storyTimer?.isActive ?? false) {
                          _storyTimer?.cancel();
                        } else {
                          _startStoryTimer();
                        }
                      });
                    },
                  ),
                  const SizedBox(width: 12),
                  IconButton.filledTonal(
                    icon: const Icon(Icons.skip_next_rounded),
                    onPressed: () {
                      setState(() {
                        _storyIndex = (_storyIndex + 1) % 4;
                        _storyProgress = 0.0;
                      });
                    },
                  ),
                ],
              ),
            ],
          ),
        ),
      ],
    );
  }

  // -------------------------------------------------------------
  // TAB 5: Sliders (Pixel & Vertical)
  // -------------------------------------------------------------
  Widget _buildSlidersTab(ColorScheme colorScheme) {
    return ListView(
      padding: const EdgeInsets.all(24),
      children: [
        // Horizontal Pixel Scrubber
        _buildSectionCard(
          title: 'Pixel Squiggly Media Scrubber (Horizontal)',
          subtitle: 'Interactive waveform scrubber that flattens seamlessly when paused',
          child: Column(
            children: [
              GoogleWavySlider.googleColors(
                value: _songProgress,
                min: 0.0,
                max: 1.0,
                isPlaying: _isPlaying,
                amplitude: 4.5,
                wavelength: 26.0,
                strokeWidth: 4.5,
                thumbRadius: 9.0,
                onChanged: (val) => setState(() => _songProgress = val),
              ),
              const SizedBox(height: 12),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(_formatDuration(_songProgress * 214), style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
                  IconButton.filled(
                    icon: Icon(_isPlaying ? Icons.pause_rounded : Icons.play_arrow_rounded),
                    onPressed: () => setState(() => _isPlaying = !_isPlaying),
                  ),
                  Text(_formatDuration(214), style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
                ],
              ),
            ],
          ),
        ),
        const SizedBox(height: 24),
        // Vertical Wavy Sliders
        _buildSectionCard(
          title: 'Expressive Vertical Wavy Sliders & Equalizers',
          subtitle: 'Vertical volume and brightness controls with squiggly active tracks',
          child: SizedBox(
            height: 260,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Column(
                  children: [
                    Expanded(
                      child: GoogleVerticalWavySlider.googleColors(
                        value: _verticalVolume,
                        min: 0.0,
                        max: 1.0,
                        isPlaying: _isPlaying,
                        amplitude: 4.0,
                        topIcon: const Icon(Icons.volume_up_rounded, size: 20),
                        bottomIcon: const Icon(Icons.volume_mute_rounded, size: 20),
                        onChanged: (val) => setState(() => _verticalVolume = val),
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text('Volume: ${(_verticalVolume * 100).toInt()}%', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
                  ],
                ),
                Column(
                  children: [
                    Expanded(
                      child: GoogleVerticalWavySlider(
                        value: _verticalBrightness,
                        min: 0.0,
                        max: 1.0,
                        isPlaying: _isPlaying,
                        activeColor: const Color(0xFFFBBC05),
                        amplitude: 4.0,
                        topIcon: const Icon(Icons.brightness_high_rounded, size: 20, color: Color(0xFFFBBC05)),
                        bottomIcon: const Icon(Icons.brightness_low_rounded, size: 20),
                        onChanged: (val) => setState(() => _verticalBrightness = val),
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text('Brightness: ${(_verticalBrightness * 100).toInt()}%', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
                  ],
                ),
                Column(
                  children: [
                    Expanded(
                      child: GoogleVerticalWavySlider(
                        value: _songProgress,
                        min: 0.0,
                        max: 1.0,
                        isPlaying: _isPlaying,
                        activeColor: const Color(0xFF9C27B0),
                        amplitude: 4.5,
                        topIcon: const Icon(Icons.equalizer_rounded, size: 20, color: Color(0xFF9C27B0)),
                        bottomIcon: const Icon(Icons.graphic_eq_rounded, size: 20),
                        onChanged: (val) => setState(() => _songProgress = val),
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text('EQ Gain: ${(_songProgress * 100).toInt()}%', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
                  ],
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }

  // -------------------------------------------------------------
  // TAB 6: Voice & Audio Waveforms
  // -------------------------------------------------------------
  Widget _buildAudioVisualizerTab(ColorScheme colorScheme) {
    return ListView(
      padding: const EdgeInsets.all(24),
      children: [
        _buildSectionCard(
          title: 'Gemini Live / Google Assistant Voice Waveform',
          subtitle: 'Multi-layer harmonic sinusoidal synthesizer with glowing resonance aura',
          child: Column(
            children: [
              GoogleAudioReactiveWave.geminiVoice(
                height: 110,
                amplitude: _autoSimulateVoice ? null : _manualMicVolume,
                isSimulating: _autoSimulateVoice,
              ),
              const SizedBox(height: 16),
              SwitchListTile(
                title: const Text('Auto-Simulate Live Speech Pulse', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
                value: _autoSimulateVoice,
                onChanged: (val) => setState(() => _autoSimulateVoice = val),
              ),
              if (!_autoSimulateVoice) ...[
                const SizedBox(height: 8),
                Row(
                  children: [
                    const Icon(Icons.mic_rounded, size: 18),
                    const SizedBox(width: 8),
                    Expanded(
                      child: Slider(
                        value: _manualMicVolume,
                        onChanged: (val) => setState(() => _manualMicVolume = val),
                      ),
                    ),
                    Text('${(_manualMicVolume * 100).toInt()}%'),
                  ],
                ),
              ],
            ],
          ),
        ),
        const SizedBox(height: 20),
        _buildSectionCard(
          title: 'Google 4-Color Audio Visualizer',
          subtitle: '4-Color harmonic wave frequency layering for music streaming & equalizer displays',
          child: GoogleAudioReactiveWave.googleColors(
            height: 90,
            isSimulating: true,
          ),
        ),
      ],
    );
  }

  // -------------------------------------------------------------
  // TAB 7: Spinners & Skeletons
  // -------------------------------------------------------------
  Widget _buildMorphingAndSkeletonsTab(ColorScheme colorScheme) {
    return ListView(
      padding: const EdgeInsets.all(24),
      children: [
        _buildSectionCard(
          title: 'Material 3 Expressive Shape Morphing Spinners',
          subtitle: 'Smooth geometric morphing (Pill -> Star Clover -> Squircle -> 4-Petal Flower)',
          child: Wrap(
            spacing: 32,
            runSpacing: 24,
            alignment: WrapAlignment.spaceEvenly,
            crossAxisAlignment: WrapCrossAlignment.center,
            children: [
              _buildLabeledSpinner(
                label: 'Filled Google 4-Color',
                indicator: GoogleExpressiveMorphingIndicator.filled(
                  size: 56,
                ),
              ),
              _buildLabeledSpinner(
                label: 'Outlined Expressive',
                indicator: GoogleExpressiveMorphingIndicator.outlined(
                  size: 56,
                  strokeWidth: 4.5,
                ),
              ),
              _buildLabeledSpinner(
                label: 'Elevated Glow Aura',
                indicator: GoogleExpressiveMorphingIndicator(
                  size: 56,
                  variant: MorphVariant.elevatedGlow,
                ),
              ),
            ],
          ),
        ),
        const SizedBox(height: 24),
        _buildSectionCard(
          title: 'Wavy Shimmer Skeleton Placeholder Card',
          subtitle: 'Iridescent sinusoidal light sweeps across placeholder cards during content fetch',
          child: const GoogleWavySkeletonCard(
            hasAvatar: true,
            lineCount: 3,
            hasMediaThumbnail: true,
          ),
        ),
      ],
    );
  }

  // -------------------------------------------------------------
  // TAB 8: Sandbox & Code Generator
  // -------------------------------------------------------------
  Widget _buildSandboxTab(ColorScheme colorScheme) {
    List<Color> resolvedColors;
    switch (_customColorPreset) {
      case 0:
        resolvedColors = GoogleWavyColors.googleFourColors;
        break;
      case 1:
        resolvedColors = GoogleWavyColors.geminiAura;
        break;
      case 2:
        resolvedColors = [colorScheme.primary];
        break;
      case 3:
      default:
        resolvedColors = GoogleWavyColors.pixelOcean;
        break;
    }

    return ListView(
      padding: const EdgeInsets.all(24),
      children: [
        // Live Preview Window
        Card(
          elevation: 0,
          color: colorScheme.surfaceContainerLow,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
          child: Padding(
            padding: const EdgeInsets.all(24.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text('Live Sandbox Preview', style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16)),
                const SizedBox(height: 24),
                Center(
                  child: GoogleWavyParticleOverlay(
                    isEmitting: _customSparkles,
                    child: GoogleLinearWavyProgressIndicator(
                      value: _determinateProgress,
                      colors: resolvedColors,
                      amplitude: _customAmplitude,
                      wavelength: _customWavelength,
                      strokeWidth: _customStrokeWidth,
                      waveSpeed: _customSpeed,
                      trackGap: _customTrackGap,
                      wavyTrack: _customWavyTrack,
                      glowRadius: _customGlowRadius,
                    ),
                  ),
                ),
                const SizedBox(height: 24),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text('Progress: ${(_determinateProgress * 100).toInt()}%', style: const TextStyle(fontWeight: FontWeight.w600)),
                    ElevatedButton.icon(
                      icon: const Icon(Icons.copy_rounded, size: 16),
                      label: const Text('Copy Flutter Code'),
                      onPressed: () => _copySnippet(context),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
        const SizedBox(height: 24),
        // Controls
        _buildSectionCard(
          title: 'Fine-Tune Parameters',
          subtitle: 'Adjust amplitude, wavelength, stroke, speed, glow, and particle sparkle',
          child: Column(
            children: [
              _buildSliderRow('Wave Amplitude', '${_customAmplitude.toStringAsFixed(1)} dp', _customAmplitude, 0.5, 10.0, (v) => setState(() => _customAmplitude = v)),
              _buildSliderRow('Wavelength', '${_customWavelength.toStringAsFixed(1)} dp', _customWavelength, 12.0, 60.0, (v) => setState(() => _customWavelength = v)),
              _buildSliderRow('Stroke Thickness', '${_customStrokeWidth.toStringAsFixed(1)} dp', _customStrokeWidth, 2.0, 10.0, (v) => setState(() => _customStrokeWidth = v)),
              _buildSliderRow('Track Gap', '${_customTrackGap.toStringAsFixed(1)} dp', _customTrackGap, 0.0, 12.0, (v) => setState(() => _customTrackGap = v)),
              _buildSliderRow('Speed Multiplier', '${_customSpeed.toStringAsFixed(1)}x', _customSpeed, 0.2, 3.0, (v) => setState(() => _customSpeed = v)),
              _buildSliderRow('Ambient Glow', '${_customGlowRadius.toStringAsFixed(1)} dp', _customGlowRadius, 0.0, 12.0, (v) => setState(() => _customGlowRadius = v)),
              const SizedBox(height: 12),
              Wrap(
                spacing: 8,
                children: [
                  ChoiceChip(label: const Text('Google 4-Color'), selected: _customColorPreset == 0, onSelected: (_) => setState(() => _customColorPreset = 0)),
                  ChoiceChip(label: const Text('Gemini Aura'), selected: _customColorPreset == 1, onSelected: (_) => setState(() => _customColorPreset = 1)),
                  ChoiceChip(label: const Text('Theme Primary'), selected: _customColorPreset == 2, onSelected: (_) => setState(() => _customColorPreset = 2)),
                  ChoiceChip(label: const Text('Pixel Ocean'), selected: _customColorPreset == 3, onSelected: (_) => setState(() => _customColorPreset = 3)),
                ],
              ),
              const SizedBox(height: 8),
              SwitchListTile(
                title: const Text('AI Sparkle Particles', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
                value: _customSparkles,
                onChanged: (v) => setState(() => _customSparkles = v),
              ),
              SwitchListTile(
                title: const Text('Wavy Background Track', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
                value: _customWavyTrack,
                onChanged: (v) => setState(() => _customWavyTrack = v),
              ),
            ],
          ),
        ),
      ],
    );
  }

  // -------------------------------------------------------------
  // HELPER WIDGETS
  // -------------------------------------------------------------
  Widget _buildGlobalProgressSlider(ColorScheme colorScheme) {
    return Card(
      elevation: 0,
      color: colorScheme.surfaceContainerLow,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Row(
          children: [
            IconButton.filled(
              icon: Icon(_isAutoSimulating ? Icons.pause_rounded : Icons.play_arrow_rounded),
              onPressed: _toggleAutoSimulation,
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      const Text('Determinate Progress', style: TextStyle(fontWeight: FontWeight.w700, fontSize: 13)),
                      Text('${(_determinateProgress * 100).toInt()}%', style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13)),
                    ],
                  ),
                  Slider(
                    value: _determinateProgress,
                    min: 0.0,
                    max: 1.0,
                    onChanged: (val) {
                      if (_isAutoSimulating) _toggleAutoSimulation();
                      setState(() => _determinateProgress = val);
                    },
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSectionCard({required String title, required String subtitle, required Widget child}) {
    final theme = Theme.of(context);
    return Card(
      elevation: 0,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
        side: BorderSide(color: theme.colorScheme.outlineVariant.withValues(alpha: 0.4)),
      ),
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 15)),
            const SizedBox(height: 4),
            Text(subtitle, style: TextStyle(color: theme.colorScheme.outline, fontSize: 13)),
            const SizedBox(height: 18),
            child,
          ],
        ),
      ),
    );
  }

  Widget _buildSliderRow(String title, String valText, double value, double min, double max, ValueChanged<double> onChanged) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4.0),
      child: Row(
        children: [
          SizedBox(width: 130, child: Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600))),
          Expanded(child: Slider(value: value, min: min, max: max, onChanged: onChanged)),
          SizedBox(width: 60, child: Text(valText, textAlign: TextAlign.end, style: const TextStyle(fontSize: 12))),
        ],
      ),
    );
  }

  Widget _buildLabeledCircular({required String label, required Widget indicator}) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        indicator,
        const SizedBox(height: 12),
        Text(label, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
      ],
    );
  }

  Widget _buildLabeledLiquid({required String label, required Widget child}) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        child,
        const SizedBox(height: 12),
        Text(label, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
      ],
    );
  }

  Widget _buildLabeledSpinner({required String label, required Widget indicator}) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        indicator,
        const SizedBox(height: 12),
        Text(label, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
      ],
    );
  }

  String _formatDuration(double seconds) {
    final int mins = (seconds / 60).floor();
    final int secs = (seconds % 60).floor();
    return '$mins:${secs.toString().padLeft(2, '0')}';
  }

  void _copySnippet(BuildContext context) {
    final snippet = '''
GoogleLinearWavyProgressIndicator(
  value: ${_determinateProgress.toStringAsFixed(2)},
  amplitude: ${_customAmplitude.toStringAsFixed(1)},
  wavelength: ${_customWavelength.toStringAsFixed(1)},
  strokeWidth: ${_customStrokeWidth.toStringAsFixed(1)},
  waveSpeed: ${_customSpeed.toStringAsFixed(1)},
  glowRadius: ${_customGlowRadius.toStringAsFixed(1)},
)''';
    Clipboard.setData(ClipboardData(text: snippet));
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('Flutter code copied to clipboard!')),
    );
  }
}
2
likes
150
points
74
downloads

Documentation

API reference

Publisher

verified publisherndronux.in

Weekly Downloads

Google Material 3 Expressive wavy progress indicators, Pixel squiggly sliders, liquid fluid tanks, story progress segments, audio visualizers, and shape morphing spinners for Flutter.

Repository (GitHub)
View/report issues

Topics

#progress-indicator #material3 #animation #wavy-progress #custom-painter

License

MIT (license)

Dependencies

flutter

More

Packages that depend on expressive_wavy_progress