// اوورلی‌های خصوصیِ صفحه‌ی بازی (بنرها، پنلِ جستجو، انتخابِ حکم، نتیجه‌ی دست/بازی). part of 'game_screen.dart'; /// بنرِ «نوبت شماست» وقتی کاربر باید کارت بیندازد. class _TurnBanner extends StatelessWidget { const _TurnBanner(); @override Widget build(BuildContext context) => Align( alignment: const Alignment(0, 0.3), child: IgnorePointer( child: Container( padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 8), decoration: BoxDecoration( gradient: const LinearGradient( colors: [AppColors.gold, AppColors.goldDark], ), borderRadius: BorderRadius.circular(20), boxShadow: const [ BoxShadow(color: Colors.black54, blurRadius: 8) ], ), child: const Text( 'نوبت شماست', style: TextStyle( color: AppColors.bg, fontWeight: FontWeight.bold, fontSize: 16, ), ), ), ), ); } /// بنرِ قطعِ ارتباط با سرور (بالای صفحه). class _ConnBanner extends StatelessWidget { const _ConnBanner(); @override Widget build(BuildContext context) => Positioned( top: 0, left: 0, right: 0, child: Material( color: Colors.orange.shade900, child: const SafeArea( bottom: false, child: Padding( padding: EdgeInsets.all(8), child: Text( 'ارتباط با سرور قطع شد، در حال تلاش برای اتصال مجدد…', textAlign: TextAlign.center, style: TextStyle(color: Colors.white), ), ), ), ), ); } /// نشانِ درون‌بازیِ تورنومنت: می‌فهماند این بازیِ عمومی به تورنومنت امتیاز می‌دهد. class _TournamentBadge extends StatelessWidget { final String title; const _TournamentBadge({required this.title}); @override Widget build(BuildContext context) => Positioned( top: 6, left: 0, right: 0, child: IgnorePointer( child: Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF3A2A6E), AppColors.lapisInk], ), borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.gold, width: 1.2), boxShadow: [ BoxShadow( color: AppColors.gold.withValues(alpha: 0.3), blurRadius: 8), ], ), child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.emoji_events, color: AppColors.gold, size: 15), const SizedBox(width: 5), Text( 'تورنومنت: $title', style: const TextStyle( color: AppColors.gold, fontSize: 12, fontWeight: FontWeight.bold), ), ]), ), ), ), ); } /// پنلِ «جستجوی حریف»: چهار جایگاه + وضعیتِ جستجو یا شمارشِ معکوسِ شروع. class _SearchPanel extends StatelessWidget { final GameUiState state; final int countdown; const _SearchPanel({required this.state, required this.countdown}); @override Widget build(BuildContext context) { final players = state.state?.players ?? const []; final mySeat = state.state?.yourSeat ?? -1; final searching = state.state == null; GamePlayer? at(int seat) { for (final p in players) { if (p.seat == seat) return p; } return null; } return Container( color: Colors.black.withValues(alpha: 0.78), alignment: Alignment.center, child: Container( margin: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.fromLTRB(16, 14, 16, 18), decoration: BoxDecoration( color: AppColors.panel, borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.gold, width: 2.5), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'جستجوی حریف', style: TextStyle( color: AppColors.gold, fontSize: 22, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 18), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ for (var seat = 0; seat < 4; seat++) _SearchSlot( player: at(seat), isYou: seat == mySeat, seat: seat, ), ], ), if (searching) ...[ const SizedBox(height: 16), const Text( 'در حال یافتن حریفان…', style: TextStyle(color: Colors.white70, fontSize: 13), ), ] else if (countdown > 0) ...[ const SizedBox(height: 14), const Text( 'شروع بازی', style: TextStyle(color: Colors.white70, fontSize: 13), ), const SizedBox(height: 6), TweenAnimationBuilder( // هر عدد با یک «پاپ» ظاهر می‌شود تا واضح و جلب‌توجه باشد. key: ValueKey(countdown), tween: Tween(begin: 1.6, end: 1.0), duration: const Duration(milliseconds: 400), curve: Curves.easeOut, builder: (_, scale, child) => Transform.scale(scale: scale, child: child), child: Container( width: 54, height: 54, alignment: Alignment.center, decoration: BoxDecoration( shape: BoxShape.circle, gradient: const LinearGradient( colors: [AppColors.gold, AppColors.goldDark], ), border: Border.all(color: Colors.white, width: 2), ), child: Text( '$countdown', style: const TextStyle( color: AppColors.bg, fontSize: 26, fontWeight: FontWeight.bold, ), ), ), ), ], ], ), ), ); } } /// یک جایگاه در پنلِ جستجو (نام، آواتار/آیکن، برچسبِ نقش). class _SearchSlot extends StatelessWidget { final GamePlayer? player; final bool isYou; final int seat; const _SearchSlot({ required this.player, required this.isYou, required this.seat, }); @override Widget build(BuildContext context) { final p = player; final found = p != null; return Padding( padding: const EdgeInsets.symmetric(horizontal: 6), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( found ? p.name : '...', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: isYou ? AppColors.gold : Colors.white70, fontSize: 12, ), ), const SizedBox(height: 4), Container( width: 58, height: 58, padding: const EdgeInsets.all(3), decoration: BoxDecoration( color: AppColors.bgDark, borderRadius: BorderRadius.circular(10), border: Border.all( color: isYou ? AppColors.gold : AppColors.goldDark, width: 1.5, ), ), child: !found ? _SearchingAvatar(slot: seat) // آواتارهای متغیر و متفاوت : p.bot ? const Icon( Icons.smart_toy, color: AppColors.gold, size: 30, ) : ClipRRect( borderRadius: BorderRadius.circular(8), child: RandomAvatar(p.avatarSeed), ), ), const SizedBox(height: 2), Text( found ? (p.bot ? 'ربات' : (isYou ? 'شما' : 'حریف')) : '', style: const TextStyle(color: Colors.white38, fontSize: 10), ), ], ), ); } } /// دیالوگِ انتخابِ حکم توسطِ حاکم (+ بُرِ مجدد با مصرفِ یک بلیت). class _TrumpPicker extends StatelessWidget { const _TrumpPicker(); @override Widget build(BuildContext context) { const suits = [ ('spades', '♠', 'پیک', Colors.white), ('hearts', '♥', 'دل', AppColors.suitRed), ('diamonds', '♦', 'خشت', AppColors.suitRed), ('clubs', '♣', 'گشنیز', Colors.white), ]; return Container( color: Colors.black54, alignment: Alignment.center, child: Container( margin: const EdgeInsets.all(24), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppColors.panel, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.gold, width: 2), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'حکم را انتخاب کن', style: TextStyle(color: AppColors.gold, fontSize: 20), ), const SizedBox(height: 16), Wrap( spacing: 12, runSpacing: 12, alignment: WrapAlignment.center, children: [ for (final (id, sym, name, color) in suits) ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppColors.bgDark, minimumSize: const Size(120, 64), ), onPressed: () => context.read().chooseTrump(id), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(sym, style: TextStyle(fontSize: 26, color: color)), const SizedBox(width: 8), Text( name, style: const TextStyle(color: AppColors.text), ), ], ), ), ], ), const SizedBox(height: 16), const Divider(color: AppColors.goldFaint, height: 1), const SizedBox(height: 12), // بُرِ مجدد با مصرفِ یک بلیت (۵ کارتِ تازه، سپس باز هم انتخابِ حکم). BlocBuilder( builder: (context, ws) { final tickets = ws.walletStatus is WalletLoaded ? (ws.walletStatus as WalletLoaded).wallet.tickets : 0; final canReshuffle = tickets > 0; return Column( children: [ ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF8E1B7A), disabledBackgroundColor: const Color(0xFF3A2233), minimumSize: const Size(220, 48), ), onPressed: canReshuffle ? () { AppSounds.button(); context.read().reshuffle(); // پس از مصرفِ بلیت، موجودی را تازه کن. final wallet = context.read(); Future.delayed(const Duration(milliseconds: 500), () => wallet.add(LoadWalletEvent())); } : null, icon: const Icon(Icons.casino, color: Colors.white), label: const Text( 'بُر مجدد (۱ بلیت)', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold), ), ), const SizedBox(height: 4), Text( canReshuffle ? 'بلیت‌های شما: $tickets' : 'بلیت کافی ندارید', style: const TextStyle(color: Colors.white60, fontSize: 12), ), ], ); }, ), ], ), ), ); } } /// نتیجه‌ی یک دست (بُرد/باخت/کُت) با جشنِ کُتِ بُرده. class _HandResult extends StatelessWidget { final GameUiState state; const _HandResult({required this.state}); @override Widget build(BuildContext context) { final r = state.handResult!; final mySeat = state.state?.yourSeat ?? 0; final myTeam = mySeat % 2; final won = r.winnerTeam == myTeam; final kotWin = won && r.kot; // جشنِ ویژه فقط برای کُتِ بُرده return IgnorePointer( child: Stack( children: [ if (kotWin) const Positioned.fill(child: Confetti(count: 40)), Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 18), decoration: BoxDecoration( gradient: kotWin ? const LinearGradient( colors: [AppColors.lapisMid, AppColors.lapisInk]) : null, color: kotWin ? null : Colors.black87, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.gold, width: kotWin ? 2 : 1), boxShadow: kotWin ? [ BoxShadow( color: AppColors.gold.withValues(alpha: 0.4), blurRadius: 20), ] : null, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ if (kotWin) Text( r.hakemKot ? 'حاکم‌کُت! 🔥' : 'کُت! 🔥', style: const TextStyle( color: AppColors.gold, fontSize: 30, fontWeight: FontWeight.bold, shadows: [Shadow(color: Colors.black, blurRadius: 8)], ), ), Text( won ? 'این دست را بردید!' : 'این دست را باختید', style: TextStyle( color: won ? AppColors.green : Colors.redAccent, fontSize: kotWin ? 16 : 20, fontWeight: FontWeight.bold, ), ), if (r.kot && !kotWin) Padding( padding: const EdgeInsets.only(top: 6), child: Text( r.hakemKot ? 'حاکم‌کُت! (${r.points} امتیاز)' : 'کُت! (${r.points} امتیاز)', style: const TextStyle(color: AppColors.gold), ), ), if (kotWin) Text('+${r.points} امتیاز', style: const TextStyle(color: AppColors.gold)), const SizedBox(height: 6), Text( 'امتیاز: ${r.scores.isNotEmpty ? r.scores[0] : 0} - ${r.scores.length > 1 ? r.scores[1] : 0}', style: const TextStyle(color: Colors.white70), ), ], ), ), ), ], ), ); } } /// اوورلیِ پایانِ بازی (مدال، نتیجه، پاداش‌ها، بازگشت به لابی). class _GameOver extends StatelessWidget { final GameUiState state; final int prize; final String? tournamentTitle; final VoidCallback onExit; const _GameOver({ required this.state, required this.prize, required this.tournamentTitle, required this.onExit, }); @override Widget build(BuildContext context) { final g = state.gameOver!; final mySeat = state.state?.yourSeat ?? 0; final won = g.winnerTeam == mySeat % 2; return Stack( children: [ const Positioned.fill(child: ColoredBox(color: Colors.black87)), if (won) const Positioned.fill(child: Confetti()), Center( child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ // مدالِ متحرک (تاجِ طلایی برای برد، نشانِ ملایم برای باخت). TweenAnimationBuilder( tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 650), curve: Curves.easeOutBack, builder: (_, v, __) => Transform.scale( scale: v.clamp(0.0, 1.2), child: Container( width: 100, height: 100, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( colors: won ? const [AppColors.gold, AppColors.goldDark] : [Colors.grey.shade700, Colors.grey.shade900], ), border: Border.all( color: won ? Colors.white70 : Colors.white24, width: 2), boxShadow: won ? [ BoxShadow( color: AppColors.gold.withValues(alpha: 0.55), blurRadius: 28), ] : null, ), child: Icon( won ? Icons.emoji_events : Icons.sentiment_dissatisfied_outlined, color: won ? AppColors.lapisInk : Colors.white54, size: 54, ), ), ), ), const SizedBox(height: 16), Text( won ? 'بردید! 🎉' : 'باختید', style: TextStyle( color: won ? AppColors.gold : Colors.redAccent, fontSize: 34, fontWeight: FontWeight.bold, shadows: const [Shadow(color: Colors.black, blurRadius: 8)], ), ), const SizedBox(height: 10), Text( 'نتیجه نهایی: ${g.scores.isNotEmpty ? g.scores[0] : 0} - ${g.scores.length > 1 ? g.scores[1] : 0}', style: const TextStyle(color: Colors.white70, fontSize: 18), ), // پاداشِ سکه‌ی برد (جایزه‌ی میز). if (won && prize > 0) ...[ const SizedBox(height: 16), _RewardChip( icon: Icons.monetization_on, text: '+$prize سکه'), ], // امتیازِ کسب‌شده‌ی تورنومنت (برد ۱۰۰، شرکت ۲۵). if (tournamentTitle != null) ...[ const SizedBox(height: 12), _RewardChip( icon: Icons.emoji_events, text: '+${won ? 100 : 25} امتیاز تورنومنت «$tournamentTitle»'), ], const SizedBox(height: 30), SizedBox( width: 220, child: ElevatedButton( onPressed: onExit, child: const Text('بازگشت به لابی'), ), ), ], ), ), ), ], ); } } /// چیپِ پاداش (آیکن + متن) در اوورلیِ پایانِ بازی. class _RewardChip extends StatelessWidget { final IconData icon; final String text; const _RewardChip({required this.icon, required this.text}); @override Widget build(BuildContext context) => Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: AppColors.gold.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(14), border: Border.all(color: AppColors.gold), ), child: Row(mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: AppColors.gold, size: 20), const SizedBox(width: 8), Text(text, style: const TextStyle( color: AppColors.gold, fontSize: 14, fontWeight: FontWeight.bold)), ]), ); } /// آواتارِ در حالِ تغییر برای جایگاه‌های خالیِ پنل جستجو (حسِ «در حال یافتن»). class _SearchingAvatar extends StatefulWidget { final int slot; // هر جایگاه دامنه‌ی seedِ متفاوت دارد تا یکسان نباشند const _SearchingAvatar({required this.slot}); @override State<_SearchingAvatar> createState() => _SearchingAvatarState(); } class _SearchingAvatarState extends State<_SearchingAvatar> { Timer? _timer; int _i = 0; @override void initState() { super.initState(); _i = widget.slot * 13; // شروعِ متفاوت برای هر جایگاه _timer = Timer.periodic(const Duration(milliseconds: 240), (_) { if (mounted) setState(() => _i++); }); } @override void dispose() { _timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { // پیشوندِ مخصوصِ جایگاه ⇒ آواتارِ هر جایگاه با بقیه فرق دارد. return ClipRRect( borderRadius: BorderRadius.circular(8), child: RandomAvatar('s${widget.slot}-$_i'), ); } }