Files
front-hokm/lib/feature/game/presentation/screen/private_table_screen.dart
T
2026-06-19 23:18:51 +03:30

387 lines
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:random_avatar/random_avatar.dart';
import '../../../../core/network/ws_client.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/widgets/game_ui.dart';
import '../bloc/game_bloc.dart';
import '../bloc/game_state.dart';
import 'game_screen.dart';
/// میز خصوصی: اتاق انتظار (کد، بازیکنان، شروع) سپس صحنه‌ی بازی (روی همان اتصال).
class PrivateTableScreen extends StatelessWidget {
const PrivateTableScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocConsumer<GameBloc, GameUiState>(
listenWhen: (a, b) =>
(a.notice != b.notice && b.notice != null) ||
(!a.tableClosed && b.tableClosed),
listener: (context, state) {
if (state.tableClosed) {
if (context.canPop()) context.pop();
return;
}
if (state.notice != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(state.notice!),
duration: const Duration(seconds: 2)));
context.read<GameBloc>().clearNotice();
}
},
builder: (context, state) {
if (state.state != null) {
return const GameScreen(prize: 0);
}
return _LobbyView(state: state);
},
);
}
}
class _LobbyView extends StatefulWidget {
final GameUiState state;
const _LobbyView({required this.state});
@override
State<_LobbyView> createState() => _LobbyViewState();
}
class _LobbyViewState extends State<_LobbyView> {
int _hands = 3; // تعداد دستِ انتخابیِ میزبان
void _leave() {
context.read<GameBloc>().leaveTable();
if (context.canPop()) context.pop();
}
@override
Widget build(BuildContext context) {
final state = widget.state;
final lobby = state.lobby;
final connecting = state.connection != WsStatus.connected || lobby == null;
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (!didPop) _leave();
},
child: Scaffold(
body: GameBackground(
child: SafeArea(
child: Stack(
children: [
Column(
children: [
_topBar(context, lobby),
Expanded(
child: connecting
? const Center(
child: CircularProgressIndicator(
color: AppColors.gold))
: _content(context, lobby),
),
],
),
if (state.countdown != null)
_CountdownOverlay(seconds: state.countdown!),
],
),
),
),
),
);
}
// نوار بالا: بازگشت (چپ) و اشتراک‌گذاریِ کد (راست).
Widget _topBar(BuildContext context, TableLobby? lobby) {
Widget btn(IconData icon, VoidCallback onTap) => GestureDetector(
onTap: onTap,
child: Container(
width: 46,
height: 46,
decoration: BoxDecoration(
color: AppColors.panel,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.gold, width: 1.5),
),
child: Icon(icon, color: AppColors.gold),
),
);
return Padding(
padding: const EdgeInsets.all(10),
child: Directionality(
textDirection: TextDirection.ltr,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
btn(Icons.arrow_back, _leave),
if (lobby != null)
btn(Icons.share, () {
Clipboard.setData(ClipboardData(text: lobby.code));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('کد میز کپی شد؛ برای دوستانت بفرست')));
}),
],
),
),
);
}
Widget _content(BuildContext context, TableLobby lobby) {
final youSeat = lobby.youSeat;
// نگاشتِ جایگاهِ مطلق → بازیکن (خالی = بات هنگام شروع).
final bySeat = <int, LobbyPlayer>{for (final p in lobby.players) p.seat: p};
// چیدمان از نگاهِ خودِ کاربر: «شما» پایین، هم‌تیمی (روبه‌رو) بالا، بقیه کنار.
LobbyPlayer? seatAt(int rel) => bySeat[(youSeat + rel) % 4];
final me = seatAt(0);
final top = seatAt(2); // هم‌تیمی (روبه‌رو)
final left = seatAt(1);
final right = seatAt(3);
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
const Text('شماره میز', style: TextStyle(color: Colors.white70)),
const SizedBox(height: 4),
GlowText(lobby.code, size: 36),
const SizedBox(height: 8),
// میز با چهار جایگاه.
SizedBox(
height: 340,
child: Stack(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 28, vertical: 36),
child: Container(
decoration: BoxDecoration(
gradient: const RadialGradient(
colors: [Color(0xFF1E7E3A), Color(0xFF0C5022)],
radius: 0.9,
),
borderRadius: BorderRadius.circular(160),
border: Border.all(color: const Color(0xFF8A5A12), width: 6),
),
child: const Center(
child: GlowText('سلطان حکم', size: 18),
),
),
),
Align(alignment: Alignment.topCenter, child: _seat(top, false)),
Align(alignment: Alignment.centerLeft, child: _seat(left, false)),
Align(
alignment: Alignment.centerRight, child: _seat(right, false)),
Align(alignment: Alignment.bottomCenter, child: _seat(me, true)),
// چرخشِ جایگاه‌ها (فقط میزبان) — تغییرِ تیم‌بندی.
if (lobby.isHost) ...[
Align(
alignment: const Alignment(-1, -0.35),
child: _rotateBtn(Icons.rotate_left,
() => context.read<GameBloc>().rotateTable(-1)),
),
Align(
alignment: const Alignment(1, -0.35),
child: _rotateBtn(Icons.rotate_right,
() => context.read<GameBloc>().rotateTable(1)),
),
],
],
),
),
const SizedBox(height: 8),
const Text('تعداد دست', style: TextStyle(color: Colors.white70)),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (final h in [3, 5, 7]) _handChip(h, lobby.isHost),
],
),
const SizedBox(height: 20),
if (lobby.isHost)
GameButton(
label: 'شروع بازی',
icon: Icons.play_arrow,
width: double.infinity,
colors: const [Color(0xFF3FA34D), Color(0xFF1B5E20)],
onTap: () => context.read<GameBloc>().startTable(_hands),
)
else
const Text('در انتظار شروع توسط میزبان…',
style: TextStyle(color: AppColors.gold, fontSize: 15)),
const SizedBox(height: 6),
if (lobby.isHost)
const Text('جای‌های خالی با ربات پر می‌شوند',
style: TextStyle(color: Colors.white54, fontSize: 12)),
const SizedBox(height: 20),
],
),
);
}
// یک جایگاهِ بازیکن دورِ میز (آواتار + ستاره + نام).
Widget _seat(LobbyPlayer? p, bool isYou) {
final empty = p == null;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Stack(
clipBehavior: Clip.none,
children: [
Container(
width: 62,
height: 62,
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
color: AppColors.bgDark,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isYou ? AppColors.gold : AppColors.goldDark,
width: 2),
),
child: empty
? const Icon(Icons.person_add_alt_1, color: Colors.white24)
: ClipRRect(
borderRadius: BorderRadius.circular(9),
child: RandomAvatar(p.name),
),
),
if (!empty)
const Positioned(
bottom: -6,
left: -4,
child: Icon(Icons.star, color: AppColors.gold, size: 22),
),
],
),
const SizedBox(height: 6),
Text(
empty ? 'در انتظار' : (isYou ? 'شما' : p.name),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: empty ? Colors.white38 : Colors.white,
fontSize: 12,
fontWeight: empty ? FontWeight.normal : FontWeight.bold,
),
),
if (p?.host == true)
const Text('میزبان',
style: TextStyle(color: AppColors.gold, fontSize: 10)),
],
);
}
// دکمه‌ی چرخشِ جایگاه‌ها (کنارِ میز).
Widget _rotateBtn(IconData icon, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 44,
height: 44,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF8E1B2A), Color(0xFF5A0E18)],
),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColors.gold, width: 1.5),
),
child: Icon(icon, color: AppColors.gold, size: 26),
),
);
}
Widget _handChip(int h, bool editable) {
const fa = {3: '۳', 5: '۵', 7: '۷'};
final selected = _hands == h;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: GestureDetector(
onTap: editable ? () => setState(() => _hands = h) : null,
child: Container(
width: 52,
height: 52,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: selected
? const LinearGradient(
colors: [Color(0xFFE9B949), Color(0xFFB8860B)])
: null,
color: selected ? null : AppColors.panel,
border: Border.all(
color: selected ? AppColors.gold : AppColors.goldDark,
width: 2),
),
child: Text(
fa[h]!,
style: TextStyle(
color: selected ? const Color(0xFF3A0A12) : Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
}
/// اوورلی شمارش معکوس ۳، ۲، ۱ پیش از شروع بازی.
class _CountdownOverlay extends StatefulWidget {
final int seconds;
const _CountdownOverlay({required this.seconds});
@override
State<_CountdownOverlay> createState() => _CountdownOverlayState();
}
class _CountdownOverlayState extends State<_CountdownOverlay> {
late int _n;
Timer? _timer;
@override
void initState() {
super.initState();
_n = widget.seconds;
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
if (!mounted) return;
setState(() => _n--);
if (_n <= 0) _timer?.cancel();
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.black.withValues(alpha: 0.7),
alignment: Alignment.center,
child: TweenAnimationBuilder<double>(
key: ValueKey(_n),
tween: Tween(begin: 0.4, end: 1.2),
duration: const Duration(milliseconds: 700),
curve: Curves.easeOut,
builder: (context, scale, child) =>
Transform.scale(scale: scale, child: child),
child: GlowText(_n > 0 ? '$_n' : 'شروع!', size: 96),
),
);
}
}