import 'dart:math' as math; import 'package:flutter/material.dart'; import '../../../../core/theme/app_theme.dart'; /// انیمیشنِ «پروازِ سکه‌ها به کیف‌پول»: چند سکه از مبدأ (مثلاً کارتِ هدیه) روی یک /// قوس به سمتِ مقصد (چیپِ سکه) پرواز می‌کنند. با Overlay نمایش داده می‌شود. class CoinFly { static void play( BuildContext context, { required Offset from, required Offset to, int count = 9, }) { final overlay = Overlay.maybeOf(context, rootOverlay: true); if (overlay == null) return; late OverlayEntry entry; entry = OverlayEntry( builder: (_) => _CoinFlyOverlay( from: from, to: to, count: count, onDone: () => entry.remove(), ), ); overlay.insert(entry); } } class _CoinFlyOverlay extends StatefulWidget { final Offset from, to; final int count; final VoidCallback onDone; const _CoinFlyOverlay({ required this.from, required this.to, required this.count, required this.onDone, }); @override State<_CoinFlyOverlay> createState() => _CoinFlyOverlayState(); } class _CoinFlyOverlayState extends State<_CoinFlyOverlay> with SingleTickerProviderStateMixin { late final AnimationController _c; late final List<_Coin> _coins; @override void initState() { super.initState(); final r = math.Random(); _coins = List.generate(widget.count, (_) { return _Coin( jitter: Offset((r.nextDouble() - 0.5) * 46, (r.nextDouble() - 0.5) * 46), delay: r.nextDouble() * 0.28, arc: -60 - r.nextDouble() * 90, // ارتفاعِ قوس (به بالا) size: 16 + r.nextDouble() * 8, ); }); _c = AnimationController( vsync: this, duration: const Duration(milliseconds: 950), ) ..addStatusListener((s) { if (s == AnimationStatus.completed) widget.onDone(); }) ..forward(); } @override void dispose() { _c.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return IgnorePointer( child: AnimatedBuilder( animation: _c, builder: (_, __) { return Stack( children: [ for (final coin in _coins) _buildCoin(coin), ], ); }, ), ); } Widget _buildCoin(_Coin coin) { final local = ((_c.value - coin.delay) / (1 - coin.delay)).clamp(0.0, 1.0); if (local <= 0) return const SizedBox.shrink(); final start = widget.from + coin.jitter; final end = widget.to; // قوسِ درجه‌دو: نقطه‌ی کنترل بالای میانه. final mid = Offset((start.dx + end.dx) / 2, (start.dy + end.dy) / 2 + coin.arc); final t = Curves.easeInCubic.transform(local); final p = _quad(start, mid, end, t); final opacity = local < 0.85 ? 1.0 : (1 - (local - 0.85) / 0.15); final scale = 1.0 - 0.35 * local; return Positioned( left: p.dx - coin.size / 2, top: p.dy - coin.size / 2, child: Opacity( opacity: opacity.clamp(0.0, 1.0), child: Transform.scale( scale: scale, child: Icon(Icons.monetization_on, color: AppColors.gold, size: coin.size), ), ), ); } Offset _quad(Offset a, Offset b, Offset c, double t) { final u = 1 - t; return a * (u * u) + b * (2 * u * t) + c * (t * t); } } class _Coin { final Offset jitter; final double delay, arc, size; _Coin({ required this.jitter, required this.delay, required this.arc, required this.size, }); }