expressive_wavy_progress 1.0.0
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.
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!')),
);
}
}