import 'package:flutter/material.dart'; import 'dart:math' as math; import '../../utils/colors.dart'; import '../../utils/theme_manager.dart'; import '../../utils/theme_notifier.dart'; class AnimatedBackground extends StatefulWidget { final Widget child; final bool isZenMode; final Function()? onShake; const AnimatedBackground({ super.key, required this.child, this.isZenMode = false, this.onShake, }); @override State createState() => _AnimatedBackgroundState(); } class _AnimatedBackgroundState extends State with TickerProviderStateMixin { late AnimationController _controller1; late AnimationController _controller2; late AnimationController _controller3; late AnimationController _shakeController; late AnimationController _seasonalController; bool _isShaking = false; // Static variables to preserve animation state across navigation static double _preservedLayer1Value = 0.0; static double _preservedLayer2Value = 0.0; static double _preservedLayer3Value = 0.0; static double _preservedSeasonalValue = 0.0; static DateTime _lastPreservedTime = DateTime.now(); @override void initState() { super.initState(); // Calculate time elapsed since last preservation final now = DateTime.now(); final elapsedSeconds = now.difference(_lastPreservedTime).inMilliseconds / 1000.0; // Update preserved values based on elapsed time _preservedLayer1Value = (_preservedLayer1Value + elapsedSeconds / 60.0) % 1.0; _preservedLayer2Value = (_preservedLayer2Value + elapsedSeconds / 80.0) % 1.0; _preservedLayer3Value = (_preservedLayer3Value + elapsedSeconds / 100.0) % 1.0; _preservedSeasonalValue = (_preservedSeasonalValue + elapsedSeconds / 60.0) % 1.0; // Create multiple animation controllers for layered effects _controller1 = AnimationController( duration: const Duration(seconds: 60), vsync: this, ); _controller1.value = _preservedLayer1Value; _controller1.repeat(); _controller2 = AnimationController( duration: const Duration(seconds: 80), vsync: this, ); _controller2.value = _preservedLayer2Value; _controller2.repeat(); _controller3 = AnimationController( duration: const Duration(seconds: 100), vsync: this, ); _controller3.value = _preservedLayer3Value; _controller3.repeat(); // Faster controller for seasonal patterns _seasonalController = AnimationController( duration: const Duration(seconds: 60), // Faster for seasonal effects vsync: this, ); _seasonalController.value = _preservedSeasonalValue; _seasonalController.repeat(); // Shake effect controller _shakeController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); } @override void dispose() { // Preserve current animation values for next instance _preservedLayer1Value = _controller1.value; _preservedLayer2Value = _controller2.value; _preservedLayer3Value = _controller3.value; _preservedSeasonalValue = _seasonalController.value; _lastPreservedTime = DateTime.now(); _controller1.dispose(); _controller2.dispose(); _controller3.dispose(); _shakeController.dispose(); _seasonalController.dispose(); super.dispose(); } void triggerShake() { if (!_isShaking) { _isShaking = true; _shakeController.forward().then((_) { _shakeController.reset(); _isShaking = false; }); widget.onShake?.call(); } } @override Widget build(BuildContext context) { return ThemeAwareBuilder( builder: (context, theme) { final seasonalPattern = ThemeManager.effectiveTheme; return Stack( children: [ // Base background Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ ZenColors.currentAppBackground, ZenColors.currentSecondaryBackground, ], ), ), ), // Seasonal pattern overlay if (seasonalPattern != SeasonalTheme.default_) _buildSeasonalPattern(seasonalPattern), // Animated gradient layers ...List.generate(2, (index) => _buildAnimatedLayer(index)), // Content overlay widget.child, ], ); }, ); } Widget _buildSeasonalPattern(SeasonalTheme theme) { switch (theme) { case SeasonalTheme.winter: return _buildSnowfall(); case SeasonalTheme.summer: return _buildSunshine(); case SeasonalTheme.autumn: return _buildFallingLeaves(); case SeasonalTheme.spring: return _buildFlowersBlooming(); default: return const SizedBox.shrink(); } } Widget _buildSnowfall() { return AnimatedBuilder( animation: _seasonalController, builder: (context, child) { return Container( decoration: BoxDecoration( gradient: RadialGradient( center: Alignment.topCenter, radius: 1.5, colors: [Colors.white.withValues(alpha: 0.1), Colors.transparent], ), ), child: CustomPaint( painter: SnowfallPainter(_seasonalController.value), size: Size.infinite, ), ); }, ); } Widget _buildSunshine() { return AnimatedBuilder( animation: _seasonalController, builder: (context, child) { return Container( decoration: BoxDecoration( gradient: RadialGradient( center: Alignment.topCenter, radius: 1.5, colors: [ Colors.yellow.withValues(alpha: 0.15), Colors.transparent, ], ), ), child: CustomPaint( painter: SunshinePainter(_seasonalController.value), size: Size.infinite, ), ); }, ); } Widget _buildFallingLeaves() { return AnimatedBuilder( animation: _seasonalController, builder: (context, child) { return Container( decoration: BoxDecoration( gradient: RadialGradient( center: Alignment.topCenter, radius: 1.5, colors: [ Colors.orange.withValues(alpha: 0.1), Colors.transparent, ], ), ), child: CustomPaint( painter: FallingLeavesPainter(_seasonalController.value), size: Size.infinite, ), ); }, ); } Widget _buildFlowersBlooming() { return AnimatedBuilder( animation: _seasonalController, builder: (context, child) { return Container( decoration: BoxDecoration( gradient: RadialGradient( center: Alignment.topCenter, radius: 1.5, colors: [Colors.pink.withValues(alpha: 0.1), Colors.transparent], ), ), child: CustomPaint( painter: FlowersPainter(_seasonalController.value), size: Size.infinite, ), ); }, ); } Widget _buildAnimatedLayer(int layerIndex) { AnimationController controller; List colors; switch (layerIndex) { case 0: controller = _controller1; colors = widget.isZenMode ? ZenColors.zenModeColors : ZenColors.animationLayer1; break; case 1: controller = _controller2; colors = ZenColors.animationLayer2; break; default: controller = _controller3; colors = ZenColors.animationLayer3; } return AnimatedBuilder( animation: Listenable.merge([controller, _shakeController]), builder: (context, child) { // Add shake effect double shakeIntensity = 0.0; if (_isShaking) { shakeIntensity = math.sin(_shakeController.value * math.pi * 8) * (1 - _shakeController.value) * 10; } return Transform.translate( offset: Offset( shakeIntensity * (math.Random().nextDouble() - 0.5) * 2, shakeIntensity * (math.Random().nextDouble() - 0.5) * 2, ), child: Transform.rotate( angle: controller.value * 2 * math.pi, child: Container( decoration: BoxDecoration( gradient: RadialGradient( center: Alignment( math.sin(controller.value * 2 * math.pi) * 0.5, math.cos(controller.value * 2 * math.pi) * 0.5, ), radius: 1.5 + math.sin(controller.value * math.pi) * 0.5 + (shakeIntensity * 0.1), colors: colors, ), ), ), ), ); }, ); } } class SnowfallPainter extends CustomPainter { final double animationValue; SnowfallPainter(this.animationValue); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.white.withValues(alpha: 0.3) ..style = PaintingStyle.fill; // Fixed seed for consistent positions (removed unused variable) // Draw 80 snowflakes with falling animation covering entire screen for (int i = 0; i < 80; i++) { // Better distribution using sine and cosine functions with different frequencies final angle1 = i * 2.39996; // Use irrational number for better distribution final baseX = (math.sin(angle1) * 0.5 + 0.5) * size.width; // Consistent falling animation starting from top const fallSpeed = 0.2; // Same speed for all snowflakes final fallOffset = animationValue * fallSpeed * size.height * 1.2; final animatedY = (fallOffset + (i * 20) % size.height) % (size.height + 100) - 100; final snowflakeSize = (2 + math.sin(i * 0.3) * 2) * 3; // 3x larger _drawDetailedSnowflake( canvas, Offset(baseX, animatedY), snowflakeSize, paint, ); } } void _drawDetailedSnowflake( Canvas canvas, Offset center, double size, Paint paint, ) { // Draw a 6-pointed snowflake final strokePaint = Paint() ..color = paint.color ..strokeWidth = size * 0.1 ..style = PaintingStyle.stroke; // Draw 6 main spokes for (int i = 0; i < 6; i++) { final angle = i * math.pi / 3; final startX = center.dx + math.cos(angle) * size * 0.2; final startY = center.dy + math.sin(angle) * size * 0.2; final endX = center.dx + math.cos(angle) * size; final endY = center.dy + math.sin(angle) * size; canvas.drawLine(Offset(startX, startY), Offset(endX, endY), strokePaint); // Draw small branches on each spoke final branchSize = size * 0.3; final branchX = center.dx + math.cos(angle) * size * 0.6; final branchY = center.dy + math.sin(angle) * size * 0.6; // Left branch final leftBranchAngle = angle - math.pi / 4; canvas.drawLine( Offset(branchX, branchY), Offset( branchX + math.cos(leftBranchAngle) * branchSize, branchY + math.sin(leftBranchAngle) * branchSize, ), strokePaint, ); // Right branch final rightBranchAngle = angle + math.pi / 4; canvas.drawLine( Offset(branchX, branchY), Offset( branchX + math.cos(rightBranchAngle) * branchSize, branchY + math.sin(rightBranchAngle) * branchSize, ), strokePaint, ); } // Draw center circle canvas.drawCircle(center, size * 0.15, paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => true; } class SunshinePainter extends CustomPainter { final double animationValue; SunshinePainter(this.animationValue); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width * 0.8, size.height * 0.2); // Draw sun rays final rayPaint = Paint() ..color = Colors.yellow.withValues(alpha: 0.2) ..strokeWidth = 2 ..style = PaintingStyle.stroke; for (int i = 0; i < 12; i++) { final angle = (i * math.pi * 2 / 12) + (animationValue * math.pi * 2); final startRadius = 30; final endRadius = 60 + math.sin(animationValue * math.pi * 4) * 10; final start = Offset( center.dx + math.cos(angle) * startRadius, center.dy + math.sin(angle) * startRadius, ); final end = Offset( center.dx + math.cos(angle) * endRadius, center.dy + math.sin(angle) * endRadius, ); canvas.drawLine(start, end, rayPaint); } // Draw sun final sunPaint = Paint() ..color = Colors.yellow.withValues(alpha: 0.3) ..style = PaintingStyle.fill; canvas.drawCircle(center, 25, sunPaint); } @override bool shouldRepaint(CustomPainter oldDelegate) => true; } class FallingLeavesPainter extends CustomPainter { final double animationValue; FallingLeavesPainter(this.animationValue); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.orange.withValues(alpha: 0.4) ..style = PaintingStyle.fill; // Fixed seed for consistent positions (removed unused variable) // Draw 40 leaves with slow falling animation for (int i = 0; i < 40; i++) { // Better distribution using trigonometric functions final angle1 = i * 1.618; // Golden ratio for better distribution final baseX = (math.sin(angle1) * 0.5 + 0.5) * size.width; // Consistent falling animation starting from top with slight swaying const fallSpeed = 0.2; // Same speed as snowflakes final swayAmount = math.sin(animationValue * math.pi * 2 + i) * 25; final fallOffset = animationValue * fallSpeed * size.height * 1.2; final animatedY = (fallOffset + (i * 30) % size.height) % (size.height + 100) - 100; final animatedX = baseX + swayAmount; final leafSize = (6 + math.sin(i * 0.4) * 4) * 3; // 3x larger _drawDetailedLeaf( canvas, Offset(animatedX, animatedY), leafSize, paint, i, ); } } void _drawDetailedLeaf( Canvas canvas, Offset center, double size, Paint paint, int index, ) { // Create a more detailed leaf shape final leafPaint = Paint() ..color = paint.color ..style = PaintingStyle.fill; final strokePaint = Paint() ..color = paint.color.withValues(alpha: 0.8) ..strokeWidth = size * 0.05 ..style = PaintingStyle.stroke; // Different leaf shapes based on index final leafType = index % 3; if (leafType == 0) { // Oak leaf shape _drawOakLeaf(canvas, center, size, leafPaint, strokePaint); } else if (leafType == 1) { // Maple leaf shape _drawMapleLeaf(canvas, center, size, leafPaint, strokePaint); } else { // Simple oval leaf with stem _drawSimpleLeaf(canvas, center, size, leafPaint, strokePaint); } } void _drawOakLeaf( Canvas canvas, Offset center, double size, Paint fillPaint, Paint strokePaint, ) { final path = Path(); path.moveTo(center.dx, center.dy - size * 0.4); // Create wavy edges for (int i = 0; i < 6; i++) { final angle = i * math.pi / 3; final radius = size * (0.3 + math.sin(i * 2) * 0.1); path.lineTo( center.dx + math.cos(angle) * radius, center.dy + math.sin(angle) * radius, ); } path.close(); canvas.drawPath(path, fillPaint); canvas.drawPath(path, strokePaint); // Draw stem canvas.drawLine( center, Offset(center.dx, center.dy + size * 0.3), strokePaint, ); } void _drawMapleLeaf( Canvas canvas, Offset center, double size, Paint fillPaint, Paint strokePaint, ) { final path = Path(); // Create 5-pointed maple leaf shape for (int i = 0; i < 5; i++) { final angle = i * math.pi * 2 / 5 - math.pi / 2; final radius = size * (i % 2 == 0 ? 0.4 : 0.2); if (i == 0) { path.moveTo( center.dx + math.cos(angle) * radius, center.dy + math.sin(angle) * radius, ); } else { path.lineTo( center.dx + math.cos(angle) * radius, center.dy + math.sin(angle) * radius, ); } } path.close(); canvas.drawPath(path, fillPaint); canvas.drawPath(path, strokePaint); // Draw stem canvas.drawLine( center, Offset(center.dx, center.dy + size * 0.3), strokePaint, ); } void _drawSimpleLeaf( Canvas canvas, Offset center, double size, Paint fillPaint, Paint strokePaint, ) { // Draw oval leaf canvas.drawOval( Rect.fromCenter(center: center, width: size, height: size * 0.6), fillPaint, ); // Draw center vein canvas.drawLine( Offset(center.dx, center.dy - size * 0.3), Offset(center.dx, center.dy + size * 0.3), strokePaint, ); // Draw side veins for (int i = 0; i < 3; i++) { final y = center.dy + (i - 1) * size * 0.15; canvas.drawLine( Offset(center.dx, y), Offset(center.dx + size * 0.3, y), strokePaint, ); canvas.drawLine( Offset(center.dx, y), Offset(center.dx - size * 0.3, y), strokePaint, ); } // Draw stem canvas.drawLine( center, Offset(center.dx, center.dy + size * 0.4), strokePaint, ); } @override bool shouldRepaint(CustomPainter oldDelegate) => true; } class FlowersPainter extends CustomPainter { final double animationValue; FlowersPainter(this.animationValue); @override void paint(Canvas canvas, Size size) { final petalPaint = Paint() ..color = Colors.pink.withValues(alpha: 0.3) ..style = PaintingStyle.fill; final centerPaint = Paint() ..color = Colors.yellow.withValues(alpha: 0.4) ..style = PaintingStyle.fill; // Fixed seed for consistent positions (removed unused variable) // Draw 25 flowers with gentle falling animation for (int i = 0; i < 25; i++) { // Better distribution using sine for X position final angle = i * 2.399963; // Golden angle in radians final baseX = (math.sin(angle) * 0.5 + 0.5) * size.width; // Consistent falling animation starting from top const fallSpeed = 0.2; // Same speed as other elements final fallOffset = animationValue * fallSpeed * size.height * 1.2; final animatedY = (fallOffset + (i * 25) % size.height) % (size.height + 100) - 100; final center = Offset(baseX, animatedY); // Blooming animation - flowers grow and shrink gently final bloomPhase = (animationValue + i * 0.1) % 1.0; final scale = (0.5 + math.sin(bloomPhase * math.pi * 2) * 0.3) * 2; // 2x larger // Draw petals for (int petal = 0; petal < 5; petal++) { final petalAngle = petal * math.pi * 2 / 5; final petalCenter = Offset( center.dx + math.cos(petalAngle) * 8 * scale, center.dy + math.sin(petalAngle) * 8 * scale, ); canvas.drawCircle(petalCenter, 6 * scale, petalPaint); } // Draw flower center canvas.drawCircle(center, 4 * scale, centerPaint); } } @override bool shouldRepaint(CustomPainter oldDelegate) => true; }