refactor: all screens refactor

This commit is contained in:
2026-07-08 20:12:04 +03:30
parent 2496580e86
commit adc2d1fc47
16 changed files with 3546 additions and 3351 deletions
@@ -0,0 +1,433 @@
// نمای لابیِ میزِ خصوصی و اوورلیِ شمارشِ معکوس.
part of 'private_table_screen.dart';
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: [
if (lobby != null)
btn(Icons.share, () {
Clipboard.setData(ClipboardData(text: lobby.code));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('کد میز کپی شد؛ برای دوستانت بفرست'),
),
);
}),
btn(Icons.arrow_forward, _leave),
],
),
),
);
}
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: [AppColors.lapisHi, AppColors.lapisLo],
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: 14),
const Text('ورودی (سکه) — برنده کل را می‌برد',
style: TextStyle(color: Colors.white70)),
const SizedBox(height: 10),
Wrap(
alignment: WrapAlignment.center,
spacing: 8,
runSpacing: 8,
children: [
for (final v in const [0, 500, 1000, 5000, 20000])
_stakeChip(v, lobby.isHost, lobby.stake),
],
),
const SizedBox(height: 20),
if (lobby.isHost)
GameButton(
label: lobby.stake > 0
? 'شروع بازی (${lobby.stake} سکه)'
: 'شروع بازی',
icon: Icons.play_arrow,
width: double.infinity,
colors: const [AppColors.success, AppColors.successDark],
onTap: () =>
context.read<GameBloc>().startTable(_hands, stake: lobby.stake),
)
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.avatarSeed),
),
),
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: [AppColors.lapisMid, AppColors.lapisLo],
),
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: [AppColors.gold, AppColors.goldDark],
)
: 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 ? AppColors.bg : Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
// چیپِ شرطِ سکه؛ میزبان با انتخاب، setStake را زنده برای همه پخش می‌کند.
Widget _stakeChip(int value, bool editable, int current) {
final selected = current == value;
final label = value == 0 ? 'رایگان' : '$value';
return GestureDetector(
onTap: editable ? () => context.read<GameBloc>().setStake(value) : null,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: selected
? const LinearGradient(colors: [AppColors.gold, AppColors.goldDark])
: null,
color: selected ? null : AppColors.panel,
border: Border.all(
color: selected ? AppColors.gold : AppColors.goldDark,
width: 1.6,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (value > 0)
Icon(Icons.monetization_on,
size: 16, color: selected ? AppColors.bg : AppColors.gold),
if (value > 0) const SizedBox(width: 4),
Text(
label,
style: TextStyle(
color: selected ? AppColors.bg : Colors.white,
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),
),
);
}
}