import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../providers/settings_provider.dart'; import '../../game/enums.dart'; import '../../game/config.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: AnimationConfig.attackRiskyScale, ); _translateController = AnimationController( vsync: this, duration: AnimationConfig.attackRiskyDash, ); _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, { VoidCallback? onAnimationStart, VoidCallback? onAnimationMiddle, VoidCallback? onAnimationEnd, }) async { _resetControllers(); onAnimationStart?.call(); if (risk == RiskLevel.safe || risk == RiskLevel.normal) { final isSafe = risk == RiskLevel.safe; final duration = AnimationConfig.getAttackDuration(risk); final offsetFactor = isSafe ? 0.2 : 0.5; final curve = isSafe ? AnimationConfig.attackSafeCurve : AnimationConfig.attackNormalCurve; _translateController.duration = duration; _translateAnimation = Tween( begin: Offset.zero, end: targetOffset * offsetFactor, ).animate(CurvedAnimation(parent: _translateController, curve: curve)); await _translateController.forward(); if (!mounted) return; onAnimationEnd?.call(); onImpact(); await _translateController.reverse(); } else { final attackScale = context.read().attackAnimScale; _scaleAnimation = Tween(begin: 1.0, end: attackScale).animate( CurvedAnimation(parent: _scaleController, curve: Curves.easeOut), ); _scaleController.duration = AnimationConfig.attackRiskyScale; _translateController.duration = AnimationConfig.attackRiskyDash; await _scaleController.forward(); if (!mounted) return; onAnimationMiddle?.call(); final adjustedOffset = targetOffset * 0.5; _translateAnimation = Tween(begin: Offset.zero, end: adjustedOffset).animate( CurvedAnimation( parent: _translateController, curve: AnimationConfig.attackRiskyDashCurve, ), ); await _translateController.forward(); if (!mounted) return; onAnimationEnd?.call(); onImpact(); await Future.wait([ _scaleController.reverse(), _translateController.reverse(), ]); } } Future animateDefense(VoidCallback onImpact) async { _resetControllers(); _translateController.duration = const Duration(milliseconds: 800); // Sequence: Left -> Right -> Center _translateAnimation = TweenSequence([ TweenSequenceItem( tween: Tween(begin: Offset.zero, end: const Offset(-10, 0)), weight: 25, ), TweenSequenceItem( tween: Tween( begin: const Offset(-10, 0), end: const Offset(10, 0), ), weight: 50, ), TweenSequenceItem( tween: Tween(begin: const Offset(10, 0), end: Offset.zero), weight: 25, ), ]).animate( CurvedAnimation( parent: _translateController, curve: Curves.easeInOut, ), ); await _translateController.forward(); if (!mounted) return; onImpact(); _translateController.reset(); } void _resetControllers() { if (_scaleController.isAnimating) { _scaleController.stop(); } if (_translateController.isAnimating) { _translateController.stop(); } _scaleController.reset(); _translateController.reset(); } @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, ), ); }, ); } }