Files
front-hokm/lib/feature/game/presentation/widgets/table_hud.dart
T

222 lines
7.3 KiB
Dart

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<int, ChatBubble> 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<Color> _seatFrame(GamePlayer p) =>
frameGradient(p.frame) ?? rankGradient(p.bot ? '' : p.rankTier);