feat: profile and subs

This commit is contained in:
2026-06-17 11:32:18 +03:30
parent 943ba13872
commit e9f294fa19
19 changed files with 1762 additions and 34 deletions
@@ -0,0 +1,162 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import '../../core/network/api_client.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/game_ui.dart';
/// صفحه‌ی دورهمی: پیوستن با شماره میز یا ساختِ میز جدید.
class PrivateEntryScreen extends StatefulWidget {
final ApiClient api;
const PrivateEntryScreen({super.key, required this.api});
@override
State<PrivateEntryScreen> createState() => _PrivateEntryScreenState();
}
class _PrivateEntryScreenState extends State<PrivateEntryScreen> {
final _code = TextEditingController();
int _remaining = 0;
bool _unlimited = false;
bool _loading = true;
@override
void initState() {
super.initState();
_loadInfo();
}
@override
void dispose() {
_code.dispose();
super.dispose();
}
Future<void> _loadInfo() async {
try {
final res = await widget.api.dio.get('/tables/info');
final d = res.data as Map;
setState(() {
_remaining = (d['remaining'] ?? 0) as int;
_unlimited = (d['unlimited'] ?? false) as bool;
_loading = false;
});
} catch (_) {
setState(() => _loading = false);
}
}
bool get _canCreate => _unlimited || _remaining > 0;
void _join() {
final code = _code.text.trim();
if (code.length < 4) return;
context.push('/private/room?join=$code');
}
void _create() {
if (!_canCreate) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('سهمیه‌ی میزهای رایگان تمام شده؛ با VIP نامحدود بسازید')));
return;
}
context.push('/private/room?create=1').then((_) => _loadInfo());
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GameBackground(
child: SafeArea(
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.all(10),
child: GestureDetector(
onTap: () => context.pop(),
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: const Icon(Icons.arrow_back, color: AppColors.gold),
),
),
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
children: [
const SizedBox(height: 8),
const Icon(Icons.person, color: AppColors.gold, size: 56),
const SizedBox(height: 12),
TextField(
controller: _code,
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
style: const TextStyle(fontSize: 22, letterSpacing: 6),
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(5),
],
decoration: const InputDecoration(hintText: 'شماره میز'),
onChanged: (_) => setState(() {}),
),
const SizedBox(height: 16),
GameButton(
label: 'پیوستن',
width: double.infinity,
colors: const [Color(0xFFC2185B), Color(0xFF6A0D38)],
onTap: _code.text.trim().length >= 4 ? _join : null,
),
const SizedBox(height: 8),
const Text('برای ورود، شماره میز را وارد کنید.',
style: TextStyle(color: Colors.white60, fontSize: 13)),
const SizedBox(height: 24),
Divider(color: AppColors.goldDark.withValues(alpha: 0.5)),
const SizedBox(height: 16),
Text(
_loading
? '...'
: _unlimited
? 'میزهای نامحدود (VIP)'
: 'میزهای رایگان باقیمانده: $_remaining',
style: const TextStyle(
color: AppColors.gold,
fontSize: 14,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
const Icon(Icons.groups, color: AppColors.gold, size: 56),
const SizedBox(height: 12),
GameButton(
label: 'ساخت میز',
width: double.infinity,
colors: _canCreate
? const [Color(0xFFC2185B), Color(0xFF6A0D38)]
: const [Color(0xFF555555), Color(0xFF333333)],
onTap: _loading ? null : _create,
),
const SizedBox(height: 8),
const Text('میز جدید بساز و دوستانت را دعوت کن',
style: TextStyle(color: Colors.white60, fontSize: 13)),
const SizedBox(height: 24),
],
),
),
),
],
),
),
),
);
}
}