// ویجت‌های خصوصیِ فروشگاه (تب‌ها و کارت‌های آیتم). part of 'shop_screen.dart'; class _ShopTabs extends StatelessWidget { const _ShopTabs(); static const _tabs = <(IconData, String)>[ (Icons.monetization_on, 'سکه'), (Icons.confirmation_number, 'بلیط'), (Icons.style, 'کارت'), (Icons.face, 'شخصیت'), (Icons.casino, 'فرش'), (Icons.filter_center_focus, 'قاب'), (Icons.bolt, 'تجهیزات'), (Icons.workspace_premium, 'VIP'), ]; @override Widget build(BuildContext context) { return TabBar( isScrollable: true, tabAlignment: TabAlignment.center, indicatorSize: TabBarIndicatorSize.tab, indicator: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [AppColors.lapis, AppColors.suitDark], ), borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.goldDark), boxShadow: [ BoxShadow( color: AppColors.gold.withValues(alpha: 0.25), blurRadius: 8, ), ], ), labelColor: AppColors.gold, unselectedLabelColor: Colors.white60, labelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13), dividerColor: Colors.transparent, padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6), tabs: [ for (final (icon, label) in _tabs) Tab( height: 40, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 16), const SizedBox(width: 5), Text(label), ], ), ), ], ); } } // ===== تب‌ها ===== class _CoinsTab extends StatelessWidget { final List packages; const _CoinsTab({required this.packages}); @override Widget build(BuildContext context) { return _grid( note: 'با داشتن اشتراک VIP در هر خرید ۱۰٪ سکه اضافه هدیه می‌گیرید.', children: [ // «سکه رایگان» (تبلیغِ ویدیویی-جایزه‌ای) فقط وقتی App ID تپسل تنظیم شده باشد. if (AppConfig.adsEnabled) _ItemCard( title: 'سکه رایگان', glowColor: AppColors.successDark, icon: Icons.card_giftcard, subtitle: 'با دیدن تبلیغ', action: _PriceButton( label: 'رایگان', green: true, onTap: () => context.read().add(AdRewardEvent()), ), ), for (final p in packages) _ItemCard( title: p.title, glowColor: AppColors.successDark, icon: Icons.savings, ribbon: p.bonusPct > 0 ? '+${p.bonusPct}٪' : null, subtitle: '${p.coins} سکه${p.vipDays > 0 ? ' + ${p.vipDays} روز VIP' : ''}', action: _PriceButton( label: '${p.priceToman} تومان', onTap: () => context.read().add( PurchaseEvent('coin', p.id, sku: p.sku), ), ), ), ], ); } } class _TicketsTab extends StatelessWidget { final List packages; const _TicketsTab({required this.packages}); @override Widget build(BuildContext context) { return _grid( note: 'با خرید بلیط می‌توانید درخواست بر زدن مجدد در بازی‌ها انجام دهید.', children: [ for (final p in packages) _ItemCard( title: p.title, glowColor: const Color(0xFF8E1B7A), icon: Icons.confirmation_number, subtitle: '${p.tickets} بلیط', action: _PriceButton( label: '${p.priceToman} تومان', onTap: () => context.read().add( PurchaseEvent('ticket', p.id, sku: p.sku), ), ), ), ], ); } } class _BoostersTab extends StatelessWidget { final List boosters; const _BoostersTab({required this.boosters}); @override Widget build(BuildContext context) { return _grid( note: 'با بسته‌های تجربه، چند برابر XP بگیرید و سریع‌تر بالا بروید.', children: [ for (final b in boosters) _ItemCard( title: b.title, glowColor: const Color(0xFF1E3A8A), icon: Icons.bolt, subtitle: 'تجربه ×${b.multiplier} — ${b.hours} ساعت', action: _PriceButton( label: '${b.priceToman} تومان', onTap: () => context.read().add( PurchaseEvent('booster', b.id, sku: b.sku), ), ), ), ], ); } } class _VipTab extends StatelessWidget { final List packages; const _VipTab({required this.packages}); @override Widget build(BuildContext context) { return _grid( note: 'با VIP: میز خصوصی نامحدود، آمار کامل و ۱۰٪ سکه‌ی هدیه.', children: [ for (final p in packages) _ItemCard( title: p.title, glowColor: const Color(0xFF8A6D00), icon: Icons.workspace_premium, ribbon: p.months >= 6 ? 'بهترین' : null, subtitle: '${p.months} ماه اشتراک', action: _PriceButton( label: '${p.priceToman} تومان', onTap: () => context.read().add( PurchaseEvent('vip', p.id, sku: p.sku), ), ), ), ], ); } } class _CardsTab extends StatelessWidget { final ShopData data; const _CardsTab({required this.data}); @override Widget build(BuildContext context) { return _grid( note: 'با اسکین کارت‌های متنوع حال‌وهوای بازی را عوض کن.', children: [ for (final c in data.cardSkins) _ItemCard( title: c.title, glowColor: const Color(0xFF0E3C73), icon: Icons.style, art: _CardSkinArt(skin: c.id, selected: data.selectedCard == c.id), subtitle: c.priceCoins > 0 ? '${c.priceCoins} سکه' : 'پیش‌فرض', action: _cardAction(context, c, data), ), ], ); } Widget _cardAction(BuildContext context, CardSkin c, ShopData d) { if (d.selectedCard == c.id) { return const _PriceButton(label: 'انتخاب شده', disabled: true); } if (d.owns(c.id)) { return _PriceButton( label: 'انتخاب', green: true, onTap: () => context.read().add(SelectCardEvent(c.id)), ); } return _PriceButton( label: '${c.priceCoins} سکه', green: true, coin: true, onTap: () => context.read().add(BuyCardEvent(c.id)), ); } } /// تبِ فرش‌ها: پیش‌نمایشِ تصویرِ هر فرش از backend با خرید (سکه/VIP) و انتخاب. /// داده‌ها مستقل از ShopBloc از /api/carpets گرفته می‌شوند. class _CarpetsTab extends StatefulWidget { const _CarpetsTab(); @override State<_CarpetsTab> createState() => _CarpetsTabState(); } class _CarpetsTabState extends State<_CarpetsTab> { final _api = locator(); late Future _future; String? _busy; @override void initState() { super.initState(); _future = _api.getCarpets(); } void _reload() => setState(() { _future = _api.getCarpets(); }); Future _act(Carpet c, {required bool buy}) async { setState(() => _busy = c.id); final err = buy ? await _api.buyCarpet(c.id) : await _api.selectCarpet(c.id); if (!mounted) return; setState(() => _busy = null); if (err == null) { context.read().add(LoadWalletEvent()); _reload(); } else { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(err))); } } @override Widget build(BuildContext context) { return FutureBuilder( future: _future, builder: (context, snap) { if (snap.connectionState != ConnectionState.done) { return const Center( child: CircularProgressIndicator(color: AppColors.gold), ); } if (!snap.hasData) { return Center( child: TextButton( onPressed: _reload, child: const Text('تلاش مجدد'), ), ); } final cat = snap.data!; return _grid( note: 'فرشِ زیرِ میز را عوض کن؛ بعضی فرش‌ها ویژه‌ی VIP هستند.', children: [ for (final c in cat.carpets) _CarpetCard( carpet: c, selected: cat.selected == c.id, busy: _busy == c.id, onSelect: () => _act(c, buy: false), onBuy: () => _act(c, buy: true), ), ], ); }, ); } } class _CarpetCard extends StatelessWidget { final Carpet carpet; final bool selected; final bool busy; final VoidCallback onSelect; final VoidCallback onBuy; const _CarpetCard({ required this.carpet, required this.selected, required this.busy, required this.onSelect, required this.onBuy, }); @override Widget build(BuildContext context) { final c = carpet; return Container( padding: const EdgeInsets.all(10), decoration: _shopCardDeco(highlight: selected), child: Column( children: [ Text( c.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.gold, fontWeight: FontWeight.bold, fontSize: 14, ), ), const SizedBox(height: 8), Expanded(child: _preview(c)), const SizedBox(height: 6), SizedBox(width: double.infinity, child: _action(c)), ], ), ); } Widget _preview(Carpet c) { final frame = ClipRRect( borderRadius: BorderRadius.circular(8), child: c.isClassic ? Container( color: const Color(0xFF0E3614), child: const Center( child: Icon(Icons.casino, color: AppColors.gold, size: 36), ), ) : Image.network( c.img, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Container( color: AppColors.lapisLo, child: const Icon( Icons.image_not_supported, color: AppColors.goldDark, ), ), ), ); return Stack( fit: StackFit.expand, children: [ frame, if (c.vip && !c.owned) const Positioned( top: 4, left: 4, child: Chip( label: Text( 'VIP', style: TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold, ), ), backgroundColor: AppColors.goldDark, padding: EdgeInsets.zero, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), ), ], ); } Widget _action(Carpet c) { if (busy) { return const _PriceButton(label: '...', disabled: true); } if (selected) { return const _PriceButton(label: 'انتخاب شده', disabled: true); } if (c.owned) { return _PriceButton(label: 'انتخاب', green: true, onTap: onSelect); } // قفل: VIP رایگان برای VIP (که owned می‌شود)، وگرنه خرید با سکه. return _PriceButton( label: '${c.priceCoins} سکه', green: true, coin: true, onTap: onBuy, ); } } // ===== تبِ قاب‌های آواتار ===== class _FramesTab extends StatefulWidget { const _FramesTab(); @override State<_FramesTab> createState() => _FramesTabState(); } class _FramesTabState extends State<_FramesTab> { final _api = locator(); late Future _future; String? _busy; @override void initState() { super.initState(); _future = _api.getFrames(); } void _reload() => setState(() { _future = _api.getFrames(); }); Future _act(AvatarFrame f, {required bool buy}) async { setState(() => _busy = f.id); final err = buy ? await _api.buyFrame(f.id) : await _api.selectFrame(f.id); if (!mounted) return; setState(() => _busy = null); if (err == null) { context.read().add(LoadWalletEvent()); _reload(); } else { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(err))); } } @override Widget build(BuildContext context) { return FutureBuilder( future: _future, builder: (context, snap) { if (snap.connectionState != ConnectionState.done) { return const Center( child: CircularProgressIndicator(color: AppColors.gold), ); } if (!snap.hasData) { return Center( child: TextButton( onPressed: _reload, child: const Text('تلاش مجدد'), ), ); } final cat = snap.data!; final ws = context.watch().state.walletStatus; final seed = ws is WalletLoaded ? ws.wallet.avatar : 'hakem-1'; return _grid( note: 'قابِ آواتارت را عوض کن؛ بازیکن‌های دیگر هم آن را می‌بینند.', children: [ for (final f in cat.frames) _FrameCard( frame: f, avatarSeed: seed.isEmpty ? 'hakem-1' : seed, selected: cat.selected == f.id || (cat.selected.isEmpty && f.id == 'none'), busy: _busy == f.id, onSelect: () => _act(f, buy: false), onBuy: () => _act(f, buy: true), ), ], ); }, ); } } class _FrameCard extends StatelessWidget { final AvatarFrame frame; final String avatarSeed; final bool selected; final bool busy; final VoidCallback onSelect; final VoidCallback onBuy; const _FrameCard({ required this.frame, required this.avatarSeed, required this.selected, required this.busy, required this.onSelect, required this.onBuy, }); @override Widget build(BuildContext context) { final f = frame; final grad = frameGradient(f.id) ?? const [Color(0xFFF3D27A), AppColors.goldDark]; return Container( padding: const EdgeInsets.all(10), decoration: _shopCardDeco(highlight: selected), child: Column( children: [ Text( f.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.gold, fontWeight: FontWeight.bold, fontSize: 14, ), ), const SizedBox(height: 8), // پیش‌نمایش: آواتار درونِ قاب با گرادیانِ همان قاب. Expanded( child: Center( child: LayoutBuilder( builder: (_, box) { final d = box.maxHeight.clamp(40.0, 92.0); return Container( width: d, height: d, padding: EdgeInsets.all(d * 0.08), decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: grad, ), ), child: ClipOval( child: SizedBox.expand(child: RandomAvatar(avatarSeed)), ), ); }, ), ), ), const SizedBox(height: 6), if (f.vip && !f.owned) const Padding( padding: EdgeInsets.only(bottom: 4), child: Text( 'ویژه‌ی VIP', style: TextStyle(color: AppColors.gold, fontSize: 11), ), ), SizedBox(width: double.infinity, child: _action(f)), ], ), ); } Widget _action(AvatarFrame f) { if (busy) return const _PriceButton(label: '...', disabled: true); if (selected) { return const _PriceButton(label: 'انتخاب شده', disabled: true); } if (f.owned) { return _PriceButton(label: 'انتخاب', green: true, onTap: onSelect); } return _PriceButton( label: '${f.priceCoins} سکه', green: true, coin: true, onTap: onBuy, ); } } // ===== تبِ شخصیت‌های آواتار ===== class _AvatarsTab extends StatefulWidget { const _AvatarsTab(); @override State<_AvatarsTab> createState() => _AvatarsTabState(); } class _AvatarsTabState extends State<_AvatarsTab> { final _api = locator(); late Future _future; String? _busy; @override void initState() { super.initState(); _future = _api.getAvatars(); } void _reload() => setState(() { _future = _api.getAvatars(); }); Future _act(AvatarCharacter a, {required bool buy}) async { setState(() => _busy = a.id); final err = buy ? await _api.buyAvatar(a.id) : await _api.selectAvatar(a.id); if (!mounted) return; setState(() => _busy = null); if (err == null) { context.read().add(LoadWalletEvent()); _reload(); } else { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(err))); } } Future _clear() async { setState(() => _busy = '__none__'); await _api.selectAvatar(''); // بازگشت به آواتارِ تصادفی if (!mounted) return; setState(() => _busy = null); context.read().add(LoadWalletEvent()); _reload(); } @override Widget build(BuildContext context) { return FutureBuilder( future: _future, builder: (context, snap) { if (snap.connectionState != ConnectionState.done) { return const Center( child: CircularProgressIndicator(color: AppColors.gold), ); } if (!snap.hasData) { return Center( child: TextButton( onPressed: _reload, child: const Text('تلاش مجدد'), ), ); } final cat = snap.data!; final ws = context.watch().state.walletStatus; final seed = ws is WalletLoaded ? ws.wallet.avatar : 'hakem-1'; if (cat.avatars.isEmpty) { return const Center( child: Padding( padding: EdgeInsets.all(24), child: Text( 'به‌زودی شخصیت‌های تازه اضافه می‌شوند.', style: TextStyle(color: AppColors.gold), ), ), ); } return _grid( note: 'شخصیتِ آواتارت را انتخاب کن؛ در کلِ بازی (سرِ میز، پروفایل و لابی) دیده می‌شود.', children: [ // «پیش‌فرض»: بازگشت به آواتارِ تصادفی. _AvatarCard.defaultRandom( avatarSeed: seed.isEmpty ? 'hakem-1' : seed, selected: cat.selected.isEmpty, busy: _busy == '__none__', onSelect: _clear, ), for (final a in cat.avatars) _AvatarCard( avatar: a, avatarSeed: seed.isEmpty ? 'hakem-1' : seed, selected: cat.selected == a.id, busy: _busy == a.id, onSelect: () => _act(a, buy: false), onBuy: () => _act(a, buy: true), ), ], ); }, ); } } class _AvatarCard extends StatelessWidget { final AvatarCharacter? avatar; // null ⇒ کارتِ «پیش‌فرض» (آواتارِ تصادفی) final String avatarSeed; final bool selected; final bool busy; final VoidCallback onSelect; final VoidCallback? onBuy; const _AvatarCard({ required this.avatar, required this.avatarSeed, required this.selected, required this.busy, required this.onSelect, required this.onBuy, }); const _AvatarCard.defaultRandom({ required this.avatarSeed, required this.selected, required this.busy, required this.onSelect, }) : avatar = null, onBuy = null; @override Widget build(BuildContext context) { final a = avatar; final title = a?.title ?? 'پیش‌فرض'; return Container( padding: const EdgeInsets.all(10), decoration: _shopCardDeco(highlight: selected), child: Column( children: [ Text( title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.gold, fontWeight: FontWeight.bold, fontSize: 14, ), ), const SizedBox(height: 8), Expanded( child: Center( child: LayoutBuilder( builder: (_, box) { final d = box.maxHeight.clamp(40.0, 96.0); return Container( width: d, height: d, padding: EdgeInsets.all(d * 0.06), decoration: BoxDecoration( shape: BoxShape.circle, color: AppColors.bgDark, border: Border.all( color: selected ? AppColors.gold : AppColors.goldDark, width: selected ? 2 : 1.4, ), ), child: PlayerAvatar( seed: avatarSeed, imageUrl: a?.img ?? '', ), ); }, ), ), ), const SizedBox(height: 6), if (a != null && a.vip && !a.owned) const Padding( padding: EdgeInsets.only(bottom: 4), child: Text( 'ویژه‌ی VIP', style: TextStyle(color: AppColors.gold, fontSize: 11), ), ), SizedBox(width: double.infinity, child: _action(a)), ], ), ); } Widget _action(AvatarCharacter? a) { if (busy) return const _PriceButton(label: '...', disabled: true); if (selected) { return const _PriceButton(label: 'انتخاب شده', disabled: true); } if (a == null || a.owned) { return _PriceButton(label: 'انتخاب', green: true, onTap: onSelect); } return _PriceButton( label: '${a.priceCoins} سکه', green: true, coin: true, onTap: onBuy, ); } } Widget _grid({required String note, required List children}) { return Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(12, 10, 12, 2), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: AppColors.gold.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.goldFaint), ), child: Row( children: [ const Icon(Icons.info_outline, color: AppColors.gold, size: 16), const SizedBox(width: 8), Expanded( child: Text( note, style: const TextStyle( color: AppColors.gold, fontSize: 12.5, height: 1.3, ), ), ), ], ), ), ), Expanded( child: GridView.count( crossAxisCount: 2, padding: const EdgeInsets.all(12), childAspectRatio: 0.72, mainAxisSpacing: 12, crossAxisSpacing: 12, children: children, ), ), ], ); } /// قابِ مشترکِ کارت‌های فروشگاه (سکه/کارت/فرش/…): گرادیانِ لاجوردی، حاشیه‌ی طلایی /// و عمقِ سایه. [highlight] برای آیتمِ انتخاب‌شده هاله‌ی طلایی اضافه می‌کند. BoxDecoration _shopCardDeco({bool highlight = false}) => BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF1C4A80), Color(0xFF0B2646)], ), borderRadius: BorderRadius.circular(16), border: Border.all( color: highlight ? AppColors.gold : AppColors.goldDark, width: highlight ? 2 : 1.3, ), boxShadow: [ const BoxShadow( color: Colors.black54, blurRadius: 10, offset: Offset(0, 5), ), if (highlight) BoxShadow(color: AppColors.gold.withValues(alpha: 0.3), blurRadius: 14), ], ); /// عددِ موجودی با جداکننده‌ی هزارگان (مثلِ ۱۲٬۵۰۰) برای خواناییِ بهتر. String _fmt(int n) { final s = n.abs().toString(); final b = StringBuffer(n < 0 ? '-' : ''); for (var i = 0; i < s.length; i++) { if (i > 0 && (s.length - i) % 3 == 0) b.write('٬'); b.write(s[i]); } return b.toString(); } /// حبابِ موجودی (سکه/بلیط) در سربرگِ فروشگاه — براق و برجسته. class _BalancePill extends StatelessWidget { final IconData icon; final Color iconColor; final String value; const _BalancePill({ required this.icon, required this.iconColor, required this.value, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0xFF17345C), Color(0xFF0C2848)], ), borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.goldDark), boxShadow: const [ BoxShadow(color: Colors.black45, blurRadius: 6, offset: Offset(0, 2)), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: iconColor, size: 18), const SizedBox(width: 6), Text( value, style: const TextStyle( color: AppColors.gold, fontWeight: FontWeight.bold, fontSize: 14, ), ), ], ), ); } } // ===== ویجت‌های کارت آیتم ===== class _ItemCard extends StatelessWidget { final String title; final IconData icon; final Color glowColor; final String subtitle; final String? ribbon; final Widget action; final Widget? art; // هنرِ سفارشی (مثل پیش‌نمایشِ اسکینِ کارت) به‌جای آیکن const _ItemCard({ required this.title, required this.icon, required this.glowColor, required this.subtitle, required this.action, this.ribbon, this.art, }); @override Widget build(BuildContext context) { final card = Container( padding: const EdgeInsets.all(10), decoration: _shopCardDeco(highlight: ribbon != null), child: Column( children: [ Text( title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.gold, fontWeight: FontWeight.bold, fontSize: 14, ), ), const SizedBox(height: 8), Expanded(child: art ?? _GlowArt(color: glowColor, icon: icon)), const SizedBox(height: 6), Text( subtitle, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Colors.white70, fontSize: 11.5), ), const SizedBox(height: 8), SizedBox(width: double.infinity, child: action), ], ), ); if (ribbon == null) return card; // ریبونِ گوشه: درصدِ هدیه سبز، «بهترین/ویژه» طلایی — جلبِ‌توجه. final isBonus = ribbon!.contains('٪') || ribbon!.contains('%'); return Stack( clipBehavior: Clip.none, children: [ card, Positioned( top: 10, left: -24, child: Transform.rotate( angle: -0.7, child: Container( padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 4), decoration: BoxDecoration( gradient: LinearGradient( colors: isBonus ? const [AppColors.success, AppColors.successDark] : const [Color(0xFFFFD54F), AppColors.goldDark], ), boxShadow: const [ BoxShadow(color: Colors.black54, blurRadius: 4), ], ), child: Text( ribbon!, style: TextStyle( color: isBonus ? Colors.white : AppColors.bg, fontSize: 11, fontWeight: FontWeight.bold, ), ), ), ), ), ], ); } } class _GlowArt extends StatelessWidget { final Color color; final IconData icon; const _GlowArt({required this.color, required this.icon}); @override Widget build(BuildContext context) { return Container( width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), gradient: RadialGradient( center: const Alignment(-0.15, -0.35), radius: 1.05, colors: [ Color.lerp(color, Colors.white, 0.15)!, color, Colors.black.withValues(alpha: 0.9), ], stops: const [0.0, 0.45, 1.0], ), border: Border.all(color: AppColors.goldFaint), ), child: Center( child: Icon( icon, size: 46, color: AppColors.gold, shadows: [ Shadow(color: color.withValues(alpha: 0.9), blurRadius: 18), const Shadow(color: Colors.black87, blurRadius: 6), ], ), ), ); } } /// پیش‌نمایشِ اسکینِ کارت در فروشگاه: دو کارت فن‌شده (پشت + تک‌خال پیک) /// که مستقیماً از backend بارگذاری می‌شوند تا کاربر هنرِ واقعی را ببیند. class _CardSkinArt extends StatelessWidget { final String skin; final bool selected; const _CardSkinArt({required this.skin, required this.selected}); String _url(String file) => '${AppConfig.baseUrl}/cards/$skin/$file'; Widget _card(String file, {double angle = 0, double dx = 0}) { return Transform.translate( offset: Offset(dx, 0), child: Transform.rotate( angle: angle, child: AspectRatio( aspectRatio: 0.7, child: ClipRRect( borderRadius: BorderRadius.circular(6), child: Image.network( _url(file), fit: BoxFit.cover, errorBuilder: (_, __, ___) => Container( color: const Color(0xFF0E3C73), child: const Icon(Icons.style, color: AppColors.gold), ), loadingBuilder: (ctx, child, progress) => progress == null ? child : Container(color: const Color(0xFF0A2547)), ), ), ), ), ); } @override Widget build(BuildContext context) { return Container( width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), gradient: const RadialGradient( colors: [AppColors.lapis, Colors.black87], radius: 0.95, ), border: Border.all( color: selected ? AppColors.gold : Colors.black26, width: selected ? 2 : 1, ), ), padding: const EdgeInsets.symmetric(vertical: 8), child: Center( child: Stack( alignment: Alignment.center, clipBehavior: Clip.none, children: [ _card('back.jpg', angle: -0.22, dx: -16), _card('AS.png', angle: 0.22, dx: 16), ], ), ), ); } } class _PriceButton extends StatefulWidget { final String label; final VoidCallback? onTap; final bool green; final bool coin; final bool disabled; const _PriceButton({ required this.label, this.onTap, this.green = false, this.coin = false, this.disabled = false, }); @override State<_PriceButton> createState() => _PriceButtonState(); } class _PriceButtonState extends State<_PriceButton> { bool _down = false; @override Widget build(BuildContext context) { final busy = context.select((ShopBloc c) => c.state.busy); final enabled = !(widget.disabled || busy) && widget.onTap != null; final colors = widget.disabled ? const [Color(0xFF555555), Color(0xFF333333)] : widget.green ? const [AppColors.success, AppColors.successDark] : const [ AppColors.accent, Color(0xFF7A1019), ]; // شنگرفِ ایرانی (هم‌رنگِ تم) return GestureDetector( onTapDown: enabled ? (_) => setState(() => _down = true) : null, onTapUp: enabled ? (_) => setState(() => _down = false) : null, onTapCancel: enabled ? () => setState(() => _down = false) : null, onTap: enabled ? widget.onTap : null, child: AnimatedScale( scale: _down ? 0.94 : 1, duration: const Duration(milliseconds: 90), child: Opacity( opacity: widget.disabled ? 0.85 : 1, child: Container( height: 38, alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: 6), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: colors, ), borderRadius: BorderRadius.circular(11), border: Border.all(color: AppColors.gold, width: 1.2), boxShadow: enabled ? [ BoxShadow( color: colors.last.withValues(alpha: 0.55), blurRadius: 8, offset: const Offset(0, 3), ), ] : null, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ if (widget.coin) ...[ const Icon( Icons.monetization_on, color: AppColors.gold, size: 16, ), const SizedBox(width: 4), ], Flexible( child: Text( widget.label, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold, ), ), ), ], ), ), ), ), ); } }