import 'package:flutter/material.dart'; import 'package:random_avatar/random_avatar.dart'; import '../../../../core/network/ws_client.dart'; import '../../../../core/theme/app_theme.dart'; import '../../../../core/theme/frames.dart'; import '../../../../core/theme/ranks.dart'; import '../../domain/entities/game_entities.dart'; import '../bloc/game_state.dart'; part 'table_hud.parts.dart'; /// اوورلیِ روی صحنه‌ی Flame: آواتارِ بازیکنان با قابِ طلایی، نام، نشانِ رتبه، /// شمارنده‌ی نوبت، تاجِ حاکم و تعدادِ کارت — به‌علاوه‌ی پنلِ امتیاز/حکم (بالا-چپ)، /// نشانگرِ اتصال و دکمه‌ی خروج (بالا-راست) و دکمه‌ی گفتگو (پایین-راست). /// همه‌ی چیدمان نسبت به اندازه‌ی صفحه است تا با کارت‌های Flame هم‌تراز بماند. class TableHud extends StatelessWidget { final GameState s; final WsStatus connection; final Map chatBubbles; final VoidCallback onExit; final VoidCallback onChat; const TableHud({ super.key, required this.s, required this.connection, required this.chatBubbles, required this.onExit, required this.onChat, }); // مکانِ آواتارِ هر جایگاهِ نسبی (۰=پایین/شما، ۱=چپ، ۲=بالا، ۳=راست). static const _spots = { 0: Alignment(0, 1.0), 1: Alignment(-1.0, -0.05), // چسبیده به لبه‌ی چپ 2: Alignment(0, -0.92), 3: Alignment(1.0, -0.05), // چسبیده به لبه‌ی راست }; int _rel(int seat) => (seat - s.yourSeat + 4) % 4; @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, c) { final w = c.maxWidth; final avatarD = (w * 0.17).clamp(48.0, 84.0); return Stack( children: [ for (final p in s.players) Align( alignment: _spots[_rel(p.seat)]!, child: Padding( padding: EdgeInsets.only( bottom: _rel(p.seat) == 0 ? 2 : 0, top: _rel(p.seat) == 2 ? 6 : 0, ), child: _PlayerSeat( player: p, diameter: avatarD, isTurn: s.turn == p.seat, isHakem: s.hakem == p.seat, nameAbove: _rel(p.seat) == 2, cardCount: p.seat < s.handCounts.length ? s.handCounts[p.seat] : 0, teamTricks: (p.seat % 2) < s.tricksWon.length ? s.tricksWon[p.seat % 2] : 0, turnToken: '${s.turn}|${s.trick.length}|${s.phase}', turnSeconds: s.turnTimeoutMs > 0 ? s.turnTimeoutMs / 1000 : 30, showRing: s.phase == 'playing' || s.phase == 'choose_trump', bubble: chatBubbles[p.seat], ), ), ), Positioned(top: 8, left: 8, child: SafeArea(child: _scorePanel())), Positioned( top: 8, right: 8, child: SafeArea(child: _topRight(context)), ), Positioned( bottom: 16, right: 12, child: _RoundBtn( icon: Icons.chat_bubble, color: const Color(0xFF1E5FA8), onTap: onChat, ), ), ], ); }, ); } // پنلِ امتیاز (ما/حریف) + چیپِ حکم. امتیازِ تیمِ شما = scores[yourSeat%2]. Widget _scorePanel() { final myTeam = s.yourSeat % 2; final mine = myTeam < s.scores.length ? s.scores[myTeam] : 0; final opp = (1 - myTeam) < s.scores.length ? s.scores[1 - myTeam] : 0; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), decoration: _panelDeco(), child: Row( children: [ _scoreCol('ما', mine), Container( width: 1, height: 30, margin: const EdgeInsets.symmetric(horizontal: 12), color: AppColors.goldFaint, ), _scoreCol('حریف', opp), ], ), ), const SizedBox(height: 6), _hokmChip(), ], ); } Widget _scoreCol(String label, int v) => Column( children: [ Text( label, style: const TextStyle(color: AppColors.gold, fontSize: 12), ), Text( '$v', style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), ], ); Widget _hokmChip() { final (glyph, color) = suitGlyph(s.trump); return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), decoration: _panelDeco(), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Text( 'حکم', style: TextStyle( color: AppColors.gold, fontSize: 13, fontWeight: FontWeight.bold, ), ), const SizedBox(width: 6), Text(glyph, style: TextStyle(color: color, fontSize: 18)), ], ), ); } Widget _topRight(BuildContext context) { final ok = connection == WsStatus.connected; return Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: _panelDeco(), child: Row( children: [ Icon( ok ? Icons.wifi : Icons.wifi_off, color: ok ? AppColors.online : Colors.redAccent, size: 16, ), const SizedBox(width: 5), Text( ok ? 'آنلاین' : 'قطع', style: TextStyle( color: ok ? AppColors.online : Colors.redAccent, fontSize: 12, fontWeight: FontWeight.bold, ), ), ], ), ), const SizedBox(width: 8), _RoundBtn( icon: Icons.logout, color: AppColors.accent, onTap: onExit, size: 40, ), ], ); } static BoxDecoration _panelDeco() => BoxDecoration( gradient: const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0xFF2A2018), Color(0xFF14100B)], ), borderRadius: BorderRadius.circular(14), border: Border.all(color: AppColors.goldDark, width: 1.4), boxShadow: const [BoxShadow(color: Colors.black54, blurRadius: 6)], ); } /// قابِ نمایشِ آواتارِ یک بازیکن: قابِ اختصاصیِ خریداری‌شده (اگر باشد)، وگرنه قابِ /// رتبه (رنگ از سرور). بات‌ها رتبه ندارند ⇒ رنگِ پیش‌فرض. List _seatFrame(GamePlayer p) => frameGradient(p.frame) ?? rankGradient(p.bot ? '' : p.rankTier);