import 'dart:math' as math; import 'package:flutter/material.dart'; class CozyWave extends StatefulWidget { final double percentage; final Color color; const CozyWave({super.key, required this.percentage, required this.color}); @override State createState() => _CozyWaveState(); } class _CozyWaveState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 3), )..repeat(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return ClipPath( clipper: WaveClipper(_controller.value, widget.percentage), child: Container( height: double.infinity, width: double.infinity, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [widget.color, widget.color.withOpacity(0.8)], ), ), ), ); }, ); } } class WaveClipper extends CustomClipper { final double animationValue; final double percentage; WaveClipper(this.animationValue, this.percentage); @override Path getClip(Size size) { final path = Path(); final double baseHeight = size.height * (1 - percentage); path.moveTo(0, baseHeight); for (double i = 0; i <= size.width; i++) { path.lineTo( i, baseHeight + math.sin( (i / size.width * 2 * math.pi) + (animationValue * 2 * math.pi), ) * 8, ); } path.lineTo(size.width, size.height); path.lineTo(0, size.height); path.close(); return path; } @override bool shouldReclip(WaveClipper oldClipper) => true; }