import 'dart:math'; import 'package:flame/components.dart'; import 'package:flame/effects.dart'; import 'package:flame/events.dart'; import 'package:flutter/material.dart'; import '../../utils/colors.dart'; class Bubble extends CircleComponent with HasGameReference, TapCallbacks { static const double defaultRadius = 30.0; static const double maxRadius = 50.0; static const double minRadius = 20.0; late Color bubbleColor; bool isPopping = false; Function(Bubble)? onPop; Bubble({ required Vector2 position, double? radius, this.onPop, }) : super( radius: radius ?? _randomRadius(), position: position, anchor: Anchor.center, ); static double _randomRadius() { final random = Random(); return minRadius + random.nextDouble() * (maxRadius - minRadius); } @override Future onLoad() async { await super.onLoad(); // Set random bubble color with transparency bubbleColor = _getRandomBubbleColor(); paint = Paint() ..color = bubbleColor.withValues(alpha: 0.8) ..style = PaintingStyle.fill; // Add a subtle floating animation add( MoveEffect.by( Vector2(0, -10), EffectController( duration: 2.0, alternate: true, infinite: true, ), ), ); // Add a gentle scaling animation add( ScaleEffect.by( Vector2.all(0.1), EffectController( duration: 1.5, alternate: true, infinite: true, ), ), ); } Color _getRandomBubbleColor() { final random = Random(); final colors = [ ZenColors.bubbleDefault, ZenColors.defaultLink, ZenColors.hoverLink, ZenColors.lightModeHover, ZenColors.buttonBackground, ]; return colors[random.nextInt(colors.length)]; } @override bool onTapDown(TapDownEvent event) { if (!isPopping) { pop(); } return true; } void pop() { if (isPopping) return; isPopping = true; // Create pop animation final popEffect = ScaleEffect.to( Vector2.all(1.5), EffectController(duration: 0.2), ); final fadeEffect = OpacityEffect.to( 0.0, EffectController(duration: 0.2), ); add(popEffect); add(fadeEffect); // Create particle effects _createPopParticles(); // Notify parent and remove bubble onPop?.call(this); Future.delayed(const Duration(milliseconds: 200), () { if (isMounted) { removeFromParent(); } }); } void _createPopParticles() { final random = Random(); const particleCount = 8; for (int i = 0; i < particleCount; i++) { final angle = (i / particleCount) * 2 * pi; final particleVelocity = Vector2( cos(angle) * (50 + random.nextDouble() * 30), sin(angle) * (50 + random.nextDouble() * 30), ); final particle = CircleComponent( radius: 3 + random.nextDouble() * 3, position: position.clone(), paint: Paint() ..color = bubbleColor.withValues(alpha: 0.8) ..style = PaintingStyle.fill, ); parent?.add(particle); // Add movement effect to particle particle.add( MoveEffect.by( particleVelocity, EffectController(duration: 0.5), ), ); // Add fade effect to particle particle.add( OpacityEffect.to( 0.0, EffectController(duration: 0.5), ), ); // Remove particle after animation Future.delayed(const Duration(milliseconds: 500), () { if (particle.isMounted) { particle.removeFromParent(); } }); } } }