fix: frames
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:random_avatar/random_avatar.dart';
|
||||
|
||||
import '../../../../core/settings/app_settings.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../core/theme/ranks.dart';
|
||||
import '../../../../core/widgets/game_ui.dart';
|
||||
import '../../../../core/widgets/rank_badge.dart';
|
||||
import '../../../wallet/presentation/bloc/wallet_bloc.dart';
|
||||
@@ -88,8 +89,9 @@ class ProfileScreen extends StatelessWidget {
|
||||
|
||||
Widget _content(BuildContext context, ProfileEntity d) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 28),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
@@ -100,117 +102,23 @@ class ProfileScreen extends StatelessWidget {
|
||||
const SizedBox(width: 48),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
GamePanel(
|
||||
child: Column(
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: AppColors.gold, width: 2.5),
|
||||
),
|
||||
child: RandomAvatar(d.avatar, height: 92, width: 92),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () => _editProfile(context, d),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
colors: [Color(0xFFFFD54F), AppColors.goldDark],
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.edit,
|
||||
color: AppColors.bg,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(child: GlowText(d.name, size: 22)),
|
||||
if (d.vip) ...[const SizedBox(width: 8), const _VipBadge()],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
RankBadge(tier: d.rankTier, points: d.rankPoints),
|
||||
if (d.mobile.isNotEmpty)
|
||||
Text(
|
||||
d.mobile,
|
||||
style: const TextStyle(color: Colors.white38, fontSize: 12),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _MiniStat(
|
||||
icon: Icons.star,
|
||||
label: 'سطح',
|
||||
value: '${d.level}',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _MiniStat(
|
||||
icon: Icons.emoji_events,
|
||||
label: 'جام',
|
||||
value: '${d.trophies}',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: LinearProgressIndicator(
|
||||
value: d.xpNext == 0 ? 0 : d.xpInto / d.xpNext,
|
||||
minHeight: 8,
|
||||
backgroundColor: Colors.white10,
|
||||
valueColor: const AlwaysStoppedAnimation(AppColors.gold),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
'${d.xpInto} / ${d.xpNext} XP',
|
||||
style: const TextStyle(color: Colors.white38, fontSize: 11),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: GlowText('آمار بازی', size: 18),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 12),
|
||||
_HeroCard(profile: d, onEdit: () => _editProfile(context, d)),
|
||||
const SizedBox(height: 14),
|
||||
_HighlightStrip(profile: d),
|
||||
const SizedBox(height: 20),
|
||||
_SectionHeader(icon: Icons.query_stats, title: 'آمار بازی'),
|
||||
const SizedBox(height: 10),
|
||||
_statsSection(context, d),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 20),
|
||||
_SectionHeader(icon: Icons.tune, title: 'تنظیمات'),
|
||||
const SizedBox(height: 10),
|
||||
const _GraphicsToggle(),
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: TextButton.icon(
|
||||
onPressed: () => context.push('/terms'),
|
||||
icon: const Icon(Icons.gavel, color: AppColors.gold, size: 18),
|
||||
label: const Text(
|
||||
'شرایط و قوانین',
|
||||
style: TextStyle(color: AppColors.gold, fontSize: 14),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_SettingsTile(
|
||||
icon: Icons.gavel,
|
||||
label: 'شرایط و قوانین',
|
||||
onTap: () => context.push('/terms'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -219,32 +127,45 @@ class ProfileScreen extends StatelessWidget {
|
||||
|
||||
Widget _statsSection(BuildContext context, ProfileEntity d) {
|
||||
final s = d.stats;
|
||||
final rows = <Widget>[
|
||||
_StatRow('بازی کل', s?.games, Icons.casino),
|
||||
_StatRow('برد کل', s?.wins, Icons.thumb_up),
|
||||
_StatRow('باخت کل', s?.losses, Icons.thumb_down),
|
||||
_StatRow('کُت کردن', s?.kotMade, Icons.flash_on),
|
||||
_StatRow('کُت شدن', s?.kotReceived, Icons.flash_off),
|
||||
_StatRow('بریدن', s?.cuts, Icons.bolt),
|
||||
_StatRow('دست حاکم', s?.hakemCount, Icons.workspace_premium),
|
||||
final rate =
|
||||
(s != null && s.games > 0)
|
||||
? '${(s.wins * 100 / s.games).round()}٪'
|
||||
: '—';
|
||||
final tiles = <Widget>[
|
||||
_StatTile('نرخ برد', rate, Icons.percent, accent: true),
|
||||
_StatTile('بازی کل', '${s?.games ?? '—'}', Icons.casino),
|
||||
_StatTile('برد', '${s?.wins ?? '—'}', Icons.thumb_up),
|
||||
_StatTile('باخت', '${s?.losses ?? '—'}', Icons.thumb_down),
|
||||
_StatTile('کُت کردن', '${s?.kotMade ?? '—'}', Icons.flash_on),
|
||||
_StatTile('کُت شدن', '${s?.kotReceived ?? '—'}', Icons.flash_off),
|
||||
_StatTile('بریدن', '${s?.cuts ?? '—'}', Icons.bolt),
|
||||
_StatTile('دست حاکم', '${s?.hakemCount ?? '—'}', Icons.workspace_premium),
|
||||
];
|
||||
final panel = GamePanel(child: Column(children: rows));
|
||||
if (d.vip) return panel;
|
||||
final grid = GridView.count(
|
||||
crossAxisCount: 2,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
mainAxisSpacing: 10,
|
||||
crossAxisSpacing: 10,
|
||||
childAspectRatio: 2.2,
|
||||
children: tiles,
|
||||
);
|
||||
if (d.vip) return grid;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Opacity(opacity: 0.35, child: IgnorePointer(child: panel)),
|
||||
Opacity(opacity: 0.3, child: IgnorePointer(child: grid)),
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.45),
|
||||
color: Colors.black.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.goldDark),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.lock, color: AppColors.gold, size: 36),
|
||||
const Icon(Icons.lock, color: AppColors.gold, size: 34),
|
||||
const SizedBox(height: 8),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
@@ -275,30 +196,115 @@ class ProfileScreen extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _StatRow extends StatelessWidget {
|
||||
final String label;
|
||||
final Object? value;
|
||||
final IconData icon;
|
||||
const _StatRow(this.label, this.value, this.icon);
|
||||
/// کارتِ سرآمدِ پروفایل: آواتار با حلقهی رتبه، نام، رتبه و نوارِ سطح/XP.
|
||||
class _HeroCard extends StatelessWidget {
|
||||
final ProfileEntity profile;
|
||||
final VoidCallback onEdit;
|
||||
const _HeroCard({required this.profile, required this.onEdit});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 7),
|
||||
child: Row(
|
||||
final d = profile;
|
||||
final grad = rankGradient(d.rankTier);
|
||||
final pct = d.xpNext == 0 ? 0.0 : (d.xpInto / d.xpNext).clamp(0.0, 1.0);
|
||||
return GamePanel(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: AppColors.gold, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 15),
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
colors: grad,
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: grad.first.withValues(alpha: 0.5),
|
||||
blurRadius: 14,
|
||||
spreadRadius: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(3),
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.bgDark,
|
||||
),
|
||||
child: RandomAvatar(d.avatar, height: 90, width: 90),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: GestureDetector(
|
||||
onTap: onEdit,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(7),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFFFFD54F), AppColors.goldDark],
|
||||
),
|
||||
border: Border.all(color: AppColors.bgDark, width: 2),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.edit,
|
||||
color: AppColors.bg,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'${value ?? '—'}',
|
||||
style: const TextStyle(
|
||||
color: AppColors.gold,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(child: GlowText(d.name, size: 22)),
|
||||
if (d.vip) ...[const SizedBox(width: 8), const _VipBadge()],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
RankBadge(tier: d.rankTier, points: d.rankPoints),
|
||||
if (d.mobile.isNotEmpty) ...[
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
d.mobile,
|
||||
style: const TextStyle(color: Colors.white38, fontSize: 12),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'سطح ${d.level}',
|
||||
style: const TextStyle(
|
||||
color: AppColors.gold,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'${d.xpInto} / ${d.xpNext} XP',
|
||||
style: const TextStyle(color: Colors.white38, fontSize: 11),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: LinearProgressIndicator(
|
||||
value: pct.toDouble(),
|
||||
minHeight: 9,
|
||||
backgroundColor: Colors.white10,
|
||||
valueColor: const AlwaysStoppedAnimation(AppColors.gold),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -307,34 +313,198 @@ class _StatRow extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _MiniStat extends StatelessWidget {
|
||||
/// نوارِ سهگانهی برجسته: سطح، جام، امتیازِ رتبه.
|
||||
class _HighlightStrip extends StatelessWidget {
|
||||
final ProfileEntity profile;
|
||||
const _HighlightStrip({required this.profile});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _Highlight(
|
||||
icon: Icons.star,
|
||||
value: '${profile.level}',
|
||||
label: 'سطح',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: _Highlight(
|
||||
icon: Icons.emoji_events,
|
||||
value: '${profile.trophies}',
|
||||
label: 'جام',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: _Highlight(
|
||||
icon: Icons.military_tech,
|
||||
value: '${profile.rankPoints}',
|
||||
label: 'امتیاز',
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Highlight extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String value;
|
||||
const _MiniStat({
|
||||
final String label;
|
||||
const _Highlight({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.label,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.panel.withValues(alpha: 0.55),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: AppColors.goldFaint),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: AppColors.gold, size: 22),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(color: Colors.white54, fontSize: 11),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// عنوانِ بخش با آیکون (راستچین).
|
||||
class _SectionHeader extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
const _SectionHeader({required this.icon, required this.title});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(icon, color: AppColors.gold, size: 22),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
Icon(icon, color: AppColors.gold, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
GlowText(title, size: 18),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// کاشیِ آماری (آیکون + مقدار + برچسب). accent ⇒ برجسته با قابِ طلایی.
|
||||
class _StatTile extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final IconData icon;
|
||||
final bool accent;
|
||||
const _StatTile(this.label, this.value, this.icon, {this.accent = false});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
accent
|
||||
? AppColors.gold.withValues(alpha: 0.14)
|
||||
: AppColors.panel.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: accent ? AppColors.gold : AppColors.goldFaint,
|
||||
width: accent ? 1.4 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: AppColors.gold, size: 22),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: Colors.white54,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ردیفِ تنظیمات (آیکون + برچسب + فلش) با ظاهرِ هماهنگ با سوئیچِ گرافیک.
|
||||
class _SettingsTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final VoidCallback onTap;
|
||||
const _SettingsTile({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.onTap,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.panel.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.goldFaint),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: AppColors.gold, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||
),
|
||||
),
|
||||
const Icon(
|
||||
Icons.chevron_left,
|
||||
color: Colors.white38,
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(color: Colors.white54, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user