import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../providers/settings_provider.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, { VoidCallback? onAnimationStart, VoidCallback? onAnimationMiddle, VoidCallback? onAnimationEnd, }) async { // onAnimationStart?.call(); // Start Phase 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; // onAnimationMiddle?.call(); // Middle Phase onImpact(); await _translateController.reverse(); } else { onAnimationStart?.call(); // Start Phase // Risky: Scale + Heavy Dash final attackScale = context.read().attackAnimScale; _scaleAnimation = Tween(begin: 1.0, end: attackScale).animate( CurvedAnimation(parent: _scaleController, curve: Curves.easeOut), ); _scaleController.duration = const Duration(milliseconds: 600); _translateController.duration = const Duration(milliseconds: 500); // 1. Scale Up (Preparation) await _scaleController.forward(); if (!mounted) return; onAnimationMiddle?.call(); // Middle Phase // 2. Dash to Target (Impact) // Adjust offset to prevent complete overlap (stop slightly short) since both share the same layer stack final adjustedOffset = targetOffset * 0.5; _translateAnimation = Tween(begin: Offset.zero, end: adjustedOffset).animate( CurvedAnimation( parent: _translateController, curve: Curves.easeInExpo, // Heavy impact curve ), ); await _translateController.forward(); if (!mounted) return; // onAnimationEnd?.call(); // End Phase (Moved before Impact) // 3. Impact Callback (Shake) onImpact(); // 4. Return (Reset) _scaleController.reverse(); await _translateController.reverse(); } // onAnimationEnd removed from here } Future animateDefense(VoidCallback onImpact) async { // Defense: Wobble/Shake horizontally _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(); } @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, ), ); }, ); } }