Files
front-hokm/lib/feature/profile/presentation/screen/profile_screen.parts.dart
T
2026-07-10 00:56:58 +03:30

512 lines
16 KiB
Dart

// ویجت‌های خصوصیِ صفحه‌ی پروفایل (کارتِ سرآمد، نوارِ برجسته، کاشی‌ها، شیتِ ویرایش).
part of 'profile_screen.dart';
/// کارتِ سرآمدِ پروفایل: آواتار با حلقه‌ی رتبه، نام، رتبه و نوارِ سطح/XP.
class _HeroCard extends StatelessWidget {
final ProfileEntity profile;
final VoidCallback onEdit;
const _HeroCard({required this.profile, required this.onEdit});
@override
Widget build(BuildContext context) {
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: [
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: PlayerAvatar(
seed: d.avatar,
imageUrl: d.avatarImg,
size: 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 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),
),
),
],
),
);
}
}
/// نوارِ سه‌گانه‌ی برجسته: سطح، جام، امتیازِ رتبه.
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 value;
final String label;
const _Highlight({
required this.icon,
required this.value,
required this.label,
});
@override
Widget build(BuildContext context) {
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: 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,
),
],
),
),
),
);
}
}
class _VipBadge extends StatelessWidget {
const _VipBadge();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFFFD54F), AppColors.goldDark],
),
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'VIP',
style: TextStyle(
color: AppColors.bg,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
);
}
}
/// شیتِ ویرایش نام و آواتار (با تأیید، مقدار جدید را برمی‌گرداند).
class _EditProfileSheet extends StatefulWidget {
final String name;
final String avatar;
const _EditProfileSheet({required this.name, required this.avatar});
@override
State<_EditProfileSheet> createState() => _EditProfileSheetState();
}
class _EditProfileSheetState extends State<_EditProfileSheet> {
late final TextEditingController _name;
late final List<String> _seeds;
late String _selected;
@override
void initState() {
super.initState();
_name = TextEditingController(text: widget.name);
_seeds = List.generate(12, (i) => 'hakem-${i + 1}');
if (!_seeds.contains(widget.avatar)) _seeds.insert(0, widget.avatar);
_selected = widget.avatar;
}
@override
void dispose() {
_name.dispose();
super.dispose();
}
bool get _valid => _name.text.trim().length >= 2;
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
child: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Container(
decoration: const BoxDecoration(
color: AppColors.bgDark,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
border: Border(top: BorderSide(color: AppColors.gold, width: 2)),
),
padding: const EdgeInsets.all(18),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const GlowText('ویرایش پروفایل', size: 20),
const SizedBox(height: 14),
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: AppColors.gold, width: 2),
),
child: RandomAvatar(_selected, height: 72, width: 72),
),
const SizedBox(height: 12),
TextField(
controller: _name,
textAlign: TextAlign.center,
maxLength: 20,
inputFormatters: [LengthLimitingTextInputFormatter(20)],
decoration: const InputDecoration(
hintText: 'نام نمایشی (۲ تا ۲۰ حرف)',
counterText: '',
),
onChanged: (_) => setState(() {}),
),
const SizedBox(height: 10),
const Align(
alignment: Alignment.centerRight,
child: Text(
'انتخاب آواتار',
style: TextStyle(color: AppColors.gold),
),
),
const SizedBox(height: 8),
GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: [
for (final s in _seeds)
GestureDetector(
onTap: () => setState(() => _selected = s),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.panel,
border: Border.all(
color:
_selected == s
? AppColors.gold
: Colors.transparent,
width: 2.5,
),
),
child: RandomAvatar(s),
),
),
],
),
const SizedBox(height: 16),
GameButton(
label: 'تأیید',
width: double.infinity,
colors: const [AppColors.success, AppColors.successDark],
onTap:
_valid
? () => Navigator.pop(context, {
'name': _name.text.trim(),
'avatar': _selected,
})
: null,
),
],
),
),
),
),
);
}
}
/// سوئیچِ «گرافیک کم‌مصرف»: افکت‌های اتمسفری را برای گوشی‌های ضعیف خاموش می‌کند.
class _GraphicsToggle extends StatelessWidget {
const _GraphicsToggle();
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: AppSettings.I.lowGraphics,
builder: (context, low, _) => Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
decoration: BoxDecoration(
color: AppColors.panel.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.goldFaint),
),
child: Row(
children: [
const Icon(Icons.bolt, color: AppColors.gold, size: 20),
const SizedBox(width: 10),
const Expanded(
child: Text(
'گرافیک کم‌مصرف',
style: TextStyle(color: Colors.white, fontSize: 14),
),
),
Switch(
value: low,
activeThumbColor: AppColors.gold,
onChanged: (v) => AppSettings.I.setLowGraphics(v),
),
],
),
),
);
}
}