import 'package:flutter/material.dart'; import '../../game/enums.dart'; class BattleAnimationWidget extends StatefulWidget { final Widget child; const BattleAnimationWidget({super.key, required this.child}); @override BattleAnimationWidgetState createState() => BattleAnimationWidgetState(); } class BattleAnimationWidgetState extends State with TickerProviderStateMixin { late AnimationController _scaleController; late AnimationController _translateController; late Animation _scaleAnimation; late Animation _translateAnimation; @override void initState() { super.initState(); _scaleController = AnimationController( vsync: this, duration: const Duration(milliseconds: 800), ); _translateController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1000), ); _scaleAnimation = Tween( begin: 1.0, end: 2.0, ).animate(CurvedAnimation(parent: _scaleController, curve: Curves.easeOut)); // Default translation, will be updated on animateAttack _translateAnimation = Tween( begin: Offset.zero, end: Offset.zero, ).animate(_translateController); } @override void dispose() { _scaleController.dispose(); _translateController.dispose(); super.dispose(); } Future animateAttack( Offset targetOffset, VoidCallback onImpact, RiskLevel risk, ) async { if (risk == RiskLevel.safe || risk == RiskLevel.normal) { // Safe & Normal: Dash/Wobble without scale final isSafe = risk == RiskLevel.safe; final duration = isSafe ? 500 : 400; final offsetFactor = isSafe ? 0.2 : 0.5; _translateController.duration = Duration(milliseconds: duration); _translateAnimation = Tween( begin: Offset.zero, end: targetOffset * offsetFactor, ).animate( CurvedAnimation( parent: _translateController, curve: Curves.easeOutQuad, ), ); await _translateController.forward(); if (!mounted) return; onImpact(); await _translateController.reverse(); } else { // Risky: Scale + Heavy Dash _scaleController.duration = const Duration(milliseconds: 600); _translateController.duration = const Duration(milliseconds: 500); // 1. Scale Up (Preparation) await _scaleController.forward(); if (!mounted) return; // 2. Dash to Target (Impact) _translateAnimation = Tween(begin: Offset.zero, end: targetOffset) .animate( CurvedAnimation( parent: _translateController, curve: Curves.easeInExpo, // Heavy impact curve ), ); await _translateController.forward(); if (!mounted) return; // 3. Impact Callback (Shake) onImpact(); // 4. Return (Reset) _scaleController.reverse(); _translateController.reverse(); } } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: Listenable.merge([_scaleController, _translateController]), builder: (context, child) { return Transform.translate( offset: _translateAnimation.value, child: Transform.scale( scale: _scaleAnimation.value, child: widget.child, ), ); }, ); } }