Files
front-hokm/lib/feature/tournament/presentation/screen/tournaments_screen.dart
T

393 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/locator/locator.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/widgets/game_ui.dart';
import '../../../wallet/presentation/bloc/wallet_bloc.dart';
import '../../../wallet/presentation/bloc/wallet_event.dart';
import '../../../wallet/presentation/bloc/wallet_state.dart';
import '../../../wallet/presentation/bloc/wallet_status.dart';
import '../../data/data_source/remote/tournament_api_provider.dart';
import '../../domain/entities/tournament.dart';
import '../widgets/tournament_guide.dart';
import 'tournament_standings_screen.dart';
/// فهرستِ تورنومنت‌ها: جایزه، ورودی، وضعیت و دکمه‌ی ثبت‌نام / جدول.
class TournamentsScreen extends StatefulWidget {
const TournamentsScreen({super.key});
@override
State<TournamentsScreen> createState() => _TournamentsScreenState();
}
class _TournamentsScreenState extends State<TournamentsScreen> {
final _api = locator<TournamentApiProvider>();
late Future<List<Tournament>> _future;
int? _busyId;
@override
void initState() {
super.initState();
_future = _api.getTournaments();
}
void _reload() => setState(() {
_future = _api.getTournaments();
});
Future<void> _join(Tournament t) async {
setState(() => _busyId = t.id);
final err = await _api.join(t.id);
if (!mounted) return;
setState(() => _busyId = null);
if (err == null) {
context.read<WalletBloc>().add(LoadWalletEvent());
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('ثبت‌نام انجام شد ✓')),
);
_reload();
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(err)));
}
}
void _openStandings(Tournament t) {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => TournamentStandingsScreen(tournament: t),
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GameBackground(
child: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Row(children: [
const AppBackButton(),
const Spacer(),
const GlowText('تورنومنت‌ها', size: 24),
const Spacer(),
IconButton(
tooltip: 'راهنما',
onPressed: () => TournamentGuide.show(context),
icon: const Icon(Icons.help_outline, color: AppColors.gold),
),
BlocBuilder<WalletBloc, WalletBlocState>(
builder: (context, s) {
final w = s.walletStatus is WalletLoaded
? (s.walletStatus as WalletLoaded).wallet
: null;
return StatChip(
icon: Icons.monetization_on,
value: '${w?.coins ?? 0}',
);
},
),
]),
),
Expanded(
child: RefreshIndicator(
color: AppColors.gold,
onRefresh: () async => _reload(),
child: FutureBuilder<List<Tournament>>(
future: _future,
builder: (context, snap) {
if (snap.connectionState != ConnectionState.done) {
return const Center(
child:
CircularProgressIndicator(color: AppColors.gold));
}
if (snap.hasError) {
return _retry();
}
final items = snap.data ?? const <Tournament>[];
if (items.isEmpty) {
return _empty();
}
return ListView.separated(
padding: const EdgeInsets.fromLTRB(16, 4, 16, 24),
itemCount: items.length,
separatorBuilder: (_, __) => const SizedBox(height: 14),
itemBuilder: (_, i) => _card(items[i]),
);
},
),
),
),
],
),
),
),
);
}
Widget _retry() => ListView(children: [
const SizedBox(height: 120),
const Center(
child: Text('خطا در دریافت تورنومنت‌ها',
style: TextStyle(color: Colors.white70))),
const SizedBox(height: 8),
Center(
child: TextButton(
onPressed: _reload,
child: const Text('تلاش مجدد',
style: TextStyle(color: AppColors.gold)),
),
),
]);
Widget _empty() => ListView(children: const [
SizedBox(height: 140),
Icon(Icons.emoji_events_outlined, color: Colors.white24, size: 60),
SizedBox(height: 12),
Center(
child: Text('در حال حاضر تورنومنتی برگزار نمی‌شود',
style: TextStyle(color: Colors.white54))),
]);
Widget _card(Tournament t) {
final busy = _busyId == t.id;
return GestureDetector(
onTap: () => _openStandings(t),
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF3A2A6E), Color(0xFF15193F), AppColors.lapisInk],
),
borderRadius: BorderRadius.circular(18),
border: Border.all(color: AppColors.gold, width: 1.6),
boxShadow: [
BoxShadow(
color: AppColors.gold.withValues(alpha: 0.18), blurRadius: 12),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Container(
padding: const EdgeInsets.all(9),
decoration: const BoxDecoration(
gradient:
LinearGradient(colors: [AppColors.gold, AppColors.goldDark]),
shape: BoxShape.circle,
),
child: const Icon(Icons.emoji_events,
color: AppColors.lapisInk, size: 22),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(t.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.gold,
fontSize: 18,
fontWeight: FontWeight.bold)),
if (t.description.isNotEmpty)
Text(t.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white60, fontSize: 12)),
],
),
),
_statusBadge(t.status),
]),
const SizedBox(height: 12),
Row(children: [
_info(Icons.emoji_events, 'جایزه کل', '${t.prizePool}',
gold: true),
const SizedBox(width: 8),
_info(Icons.login, 'ورودی',
t.entryFee == 0 ? 'رایگان' : '${t.entryFee}'),
const SizedBox(width: 8),
_info(Icons.people, 'شرکت‌کننده', '${t.players}'),
]),
if (t.startsLocal.isNotEmpty) ...[
const SizedBox(height: 8),
Row(children: [
const Icon(Icons.schedule, color: Colors.white38, size: 13),
const SizedBox(width: 5),
Text(
t.isUpcoming
? 'شروع: ${t.startsLocal}'
: 'پایان: ${t.endsLocal}',
style: const TextStyle(color: Colors.white54, fontSize: 11),
),
]),
],
const SizedBox(height: 12),
_action(t, busy),
],
),
),
);
}
Widget _statusBadge(String status) {
late Color c;
late String label;
switch (status) {
case 'active':
c = AppColors.success;
label = 'در حال برگزاری';
break;
case 'upcoming':
c = const Color(0xFFE9952F);
label = 'به‌زودی';
break;
default:
c = Colors.white38;
label = 'پایان‌یافته';
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 4),
decoration: BoxDecoration(
color: c.withValues(alpha: 0.18),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: c),
),
child: Text(label,
style:
TextStyle(color: c, fontSize: 11, fontWeight: FontWeight.bold)),
);
}
Widget _info(IconData icon, String label, String value, {bool gold = false}) {
return Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 6),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.white.withValues(alpha: 0.1)),
),
child: Column(children: [
Icon(icon, size: 16, color: gold ? AppColors.gold : Colors.white70),
const SizedBox(height: 3),
Text(value,
style: TextStyle(
color: gold ? AppColors.gold : Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold)),
Text(label,
style: const TextStyle(color: Colors.white54, fontSize: 10)),
]),
),
);
}
Widget _action(Tournament t, bool busy) {
if (t.joined) {
return Column(children: [
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
alignment: Alignment.center,
decoration: BoxDecoration(
color: AppColors.success.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.success),
),
child: Text(
t.myRank > 0
? 'رتبه شما: ${t.myRank} • امتیاز: ${t.myPoints}'
: 'ثبت‌نام شده • امتیاز: ${t.myPoints}',
style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13),
),
),
const SizedBox(height: 8),
Row(children: [
if (t.isActive)
Expanded(
child: GameButton(
label: 'بازی کن و امتیاز بگیر',
icon: Icons.sports_esports,
colors: const [AppColors.gold, AppColors.goldDark],
onTap: () async {
await context.push('/game/tiers');
if (context.mounted) _reload();
},
),
)
else
Expanded(
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
side: const BorderSide(color: AppColors.gold),
padding: const EdgeInsets.symmetric(vertical: 10),
),
onPressed: () => _openStandings(t),
icon: const Icon(Icons.leaderboard,
color: AppColors.gold, size: 18),
label: const Text('جدول نتایج',
style: TextStyle(color: AppColors.gold)),
),
),
const SizedBox(width: 8),
_tableButton(t),
]),
]);
}
if (t.canJoin) {
return Row(children: [
Expanded(
child: GameButton(
label: busy
? '...'
: (t.entryFee == 0
? 'ثبت‌نام رایگان'
: 'ثبت‌نام (${t.entryFee} سکه)'),
icon: Icons.how_to_reg,
colors: const [AppColors.success, AppColors.successDark],
onTap: busy ? null : () => _join(t),
),
),
const SizedBox(width: 8),
_tableButton(t),
]);
}
// پایان‌یافته و ثبت‌نام‌نشده ⇒ فقط جدول.
return SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
side: const BorderSide(color: AppColors.gold),
padding: const EdgeInsets.symmetric(vertical: 10),
),
onPressed: () => _openStandings(t),
icon: const Icon(Icons.leaderboard, color: AppColors.gold, size: 18),
label: const Text('جدول نتایج',
style: TextStyle(color: AppColors.gold)),
),
);
}
Widget _tableButton(Tournament t) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.gold),
),
child: IconButton(
tooltip: 'جدول',
onPressed: () => _openStandings(t),
icon: const Icon(Icons.leaderboard, color: AppColors.gold),
),
);
}