diff --git a/.vscode/launch.json b/.vscode/launch.json index 9ae82f1..19ac997 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "type": "dart", "program": "lib/main.dart", "args": [ - "--dart-define=BASE_URL=http://192.168.1.105:8080", + "--dart-define=BASE_URL=http://192.168.100.6:8080", "--target-platform=android-arm64" ] }, diff --git a/lib/core/config.dart b/lib/core/config.dart index 80d2d3e..de54b3d 100644 --- a/lib/core/config.dart +++ b/lib/core/config.dart @@ -6,8 +6,10 @@ class AppConfig { /// - وب / شبیه‌ساز iOS: `--dart-define=BASE_URL=http://localhost:8080` /// - پروداکشن: `--dart-define=BASE_URL=https://api.hakemsho.ir` /// با تغییر شبکه/IP مک، مقدار dart-define یا همین پیش‌فرض را عوض کنید. - static const String baseUrl = - String.fromEnvironment('BASE_URL', defaultValue: 'http://192.168.1.105:8080'); + static const String baseUrl = String.fromEnvironment( + 'BASE_URL', + defaultValue: 'http://192.168.100.6:8080', + ); static String get apiUrl => '$baseUrl/api'; diff --git a/lib/core/widgets/game_ui.dart b/lib/core/widgets/game_ui.dart new file mode 100644 index 0000000..cbde4f2 --- /dev/null +++ b/lib/core/widgets/game_ui.dart @@ -0,0 +1,233 @@ +import 'package:flutter/material.dart'; + +import '../theme/app_theme.dart'; + +/// مجموعه‌ی ویجت‌های گرافیکیِ مشترک برای ظاهرِ بازیِ (Unity-style): +/// پس‌زمینه‌ی گرادیانی، پنل براق، دکمه‌ی جواهرگون، و عنوانِ درخشان. + +/// پس‌زمینه‌ی گرادیانیِ قرمز/طلایی با وینیت — زیربنای همه‌ی صفحات. +class GameBackground extends StatelessWidget { + final Widget child; + final bool safeArea; + const GameBackground({super.key, required this.child, this.safeArea = true}); + + @override + Widget build(BuildContext context) { + final content = safeArea ? SafeArea(child: child) : child; + return DecoratedBox( + decoration: const BoxDecoration( + gradient: RadialGradient( + center: Alignment(0, -0.4), + radius: 1.3, + colors: [Color(0xFF5A0F1E), Color(0xFF240108)], + stops: [0.0, 1.0], + ), + ), + child: Stack( + children: [ + // درخششِ ملایمِ بالا برای حسِ عمق. + const Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: RadialGradient( + center: Alignment(0, -1.1), + radius: 1.0, + colors: [Color(0x33E9B949), Color(0x00000000)], + ), + ), + ), + ), + content, + ], + ), + ); + } +} + +/// پنلِ براقِ طلایی‌حاشیه با گرادیان و سایه. +class GamePanel extends StatelessWidget { + final Widget child; + final EdgeInsetsGeometry padding; + final double radius; + const GamePanel({ + super.key, + required this.child, + this.padding = const EdgeInsets.all(16), + this.radius = 18, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: padding, + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFF6E1322), Color(0xFF3A0A12)], + ), + borderRadius: BorderRadius.circular(radius), + border: Border.all(color: AppColors.gold, width: 2), + boxShadow: const [ + BoxShadow(color: Colors.black54, blurRadius: 14, offset: Offset(0, 6)), + BoxShadow(color: Color(0x22E9B949), blurRadius: 2, spreadRadius: 1), + ], + ), + child: child, + ); + } +} + +/// دکمه‌ی جواهرگون با گرادیان، بِوِل، سایه و انیمیشنِ فشار. +class GameButton extends StatefulWidget { + final String label; + final IconData? icon; + final VoidCallback? onTap; + final List colors; // گرادیان (روشن بالا → تیره پایین) + final double height; + final double? width; + + const GameButton({ + super.key, + required this.label, + this.icon, + this.onTap, + this.colors = const [Color(0xFFD83A4A), Color(0xFF8E0E1B)], + this.height = 56, + this.width, + }); + + @override + State createState() => _GameButtonState(); +} + +class _GameButtonState extends State { + bool _down = false; + + @override + Widget build(BuildContext context) { + final enabled = widget.onTap != null; + return GestureDetector( + onTapDown: enabled ? (_) => setState(() => _down = true) : null, + onTapUp: enabled ? (_) => setState(() => _down = false) : null, + onTapCancel: enabled ? () => setState(() => _down = false) : null, + onTap: widget.onTap, + child: AnimatedScale( + scale: _down ? 0.96 : 1.0, + duration: const Duration(milliseconds: 90), + child: Opacity( + opacity: enabled ? 1 : 0.5, + child: Container( + width: widget.width, + height: widget.height, + alignment: Alignment.center, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: widget.colors, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColors.gold, width: 1.6), + boxShadow: _down + ? const [] + : const [ + BoxShadow( + color: Colors.black54, + blurRadius: 8, + offset: Offset(0, 5)), + ], + ), + foregroundDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + // هایلایتِ بالا برای حسِ شیشه‌ای/بِوِل. + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.center, + colors: [Color(0x40FFFFFF), Color(0x00FFFFFF)], + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.icon != null) ...[ + Icon(widget.icon, color: AppColors.gold, size: 22), + const SizedBox(width: 10), + ], + Text( + widget.label, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + shadows: [Shadow(color: Colors.black54, blurRadius: 3)], + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +/// عنوانِ درخشان (هاله‌ی طلایی). +class GlowText extends StatelessWidget { + final String text; + final double size; + const GlowText(this.text, {super.key, this.size = 34}); + + @override + Widget build(BuildContext context) { + return Text( + text, + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'Dana', + fontSize: size, + fontWeight: FontWeight.bold, + color: AppColors.gold, + shadows: const [ + Shadow(color: Color(0xCCE9B949), blurRadius: 18), + Shadow(color: Color(0x88E9B949), blurRadius: 36), + ], + ), + ); + } +} + +/// چیپِ آماری براق (سکه/بلیط) با آیکن و مقدار. +class StatChip extends StatelessWidget { + final IconData icon; + final String value; + final Color iconColor; + const StatChip({ + super.key, + required this.icon, + required this.value, + this.iconColor = AppColors.gold, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFF2A0610), Color(0xFF14040A)], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppColors.goldDark), + boxShadow: const [BoxShadow(color: Colors.black54, blurRadius: 4)], + ), + child: Row(mainAxisSize: MainAxisSize.min, children: [ + Icon(icon, color: iconColor, size: 18), + const SizedBox(width: 6), + Text(value, style: const TextStyle(color: AppColors.text, fontWeight: FontWeight.bold)), + ]), + ); + } +} diff --git a/lib/features/auth/mobile_screen.dart b/lib/features/auth/mobile_screen.dart index 59b65c3..6edc34c 100644 --- a/lib/features/auth/mobile_screen.dart +++ b/lib/features/auth/mobile_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; -import '../../core/theme/app_theme.dart'; +import '../../core/widgets/game_ui.dart'; import 'auth_cubit.dart'; /// صفحه‌ی ورود شماره موبایل. @@ -28,7 +28,7 @@ class _MobileScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: SafeArea( + body: GameBackground( child: BlocConsumer( listener: (context, state) { if (state.status == AuthStatus.otpSent) { @@ -41,47 +41,50 @@ class _MobileScreenState extends State { }, builder: (context, state) { final loading = state.status == AuthStatus.loading; - return Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('سلطان حکم', - style: TextStyle( - fontSize: 36, - fontWeight: FontWeight.bold, - color: AppColors.gold)), - const SizedBox(height: 8), - const Text('برای ورود شماره موبایلت رو وارد کن', - style: TextStyle(color: Colors.white70)), - const SizedBox(height: 32), - TextField( - controller: _controller, - keyboardType: TextInputType.phone, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 20, letterSpacing: 2), - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(11), - ], - decoration: const InputDecoration(hintText: '09xxxxxxxxx'), - onChanged: (_) => setState(() {}), - ), - const SizedBox(height: 24), - ElevatedButton( - onPressed: (!_valid || loading) - ? null - : () => context - .read() - .requestOtp(_controller.text.trim()), - child: loading - ? const SizedBox( - height: 22, - width: 22, - child: CircularProgressIndicator(strokeWidth: 2)) - : const Text('دریافت کد'), - ), - ], + return Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const GlowText('سلطان حکم', size: 40), + const SizedBox(height: 28), + GamePanel( + padding: const EdgeInsets.all(22), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('برای ورود شماره موبایلت رو وارد کن', + style: TextStyle(color: Colors.white70)), + const SizedBox(height: 20), + TextField( + controller: _controller, + keyboardType: TextInputType.phone, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 20, letterSpacing: 2), + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(11), + ], + decoration: const InputDecoration(hintText: '09xxxxxxxxx'), + onChanged: (_) => setState(() {}), + ), + const SizedBox(height: 20), + GameButton( + label: 'دریافت کد', + width: double.infinity, + colors: const [Color(0xFF3FA34D), Color(0xFF1B5E20)], + onTap: (!_valid || loading) + ? null + : () => context + .read() + .requestOtp(_controller.text.trim()), + ), + ], + ), + ), + ], + ), ), ); }, diff --git a/lib/features/auth/otp_screen.dart b/lib/features/auth/otp_screen.dart index 32c3f24..1d59e85 100644 --- a/lib/features/auth/otp_screen.dart +++ b/lib/features/auth/otp_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import '../../core/theme/app_theme.dart'; +import '../../core/widgets/game_ui.dart'; import 'auth_cubit.dart'; /// صفحه‌ی ورود کد یک‌بارمصرف (۵ رقمی). @@ -28,8 +29,7 @@ class _OtpScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('تأیید کد')), - body: SafeArea( + body: GameBackground( child: BlocConsumer( listener: (context, state) { if (state.status == AuthStatus.authenticated) { @@ -43,47 +43,56 @@ class _OtpScreenState extends State { }, builder: (context, state) { final loading = state.status == AuthStatus.loading; - return Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('کد پیامک‌شده به ${state.mobile} را وارد کنید', - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.white70)), - const SizedBox(height: 32), - TextField( - controller: _controller, - keyboardType: TextInputType.number, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 28, letterSpacing: 12), - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(5), - ], - decoration: const InputDecoration(hintText: '- - - - -'), - onChanged: (_) => setState(() {}), - ), - const SizedBox(height: 24), - ElevatedButton( - onPressed: (!_valid || loading) - ? null - : () => context - .read() - .verifyOtp(_controller.text.trim()), - child: loading - ? const SizedBox( - height: 22, - width: 22, - child: CircularProgressIndicator(strokeWidth: 2)) - : const Text('ورود'), - ), - TextButton( - onPressed: loading ? null : () => context.pop(), - child: const Text('تغییر شماره', - style: TextStyle(color: AppColors.gold)), - ), - ], + return Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const GlowText('تأیید کد', size: 32), + const SizedBox(height: 24), + GamePanel( + padding: const EdgeInsets.all(22), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('کد پیامک‌شده به ${state.mobile} را وارد کنید', + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.white70)), + const SizedBox(height: 20), + TextField( + controller: _controller, + keyboardType: TextInputType.number, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 28, letterSpacing: 12), + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(5), + ], + decoration: const InputDecoration(hintText: '- - - - -'), + onChanged: (_) => setState(() {}), + ), + const SizedBox(height: 20), + GameButton( + label: 'ورود', + width: double.infinity, + colors: const [Color(0xFF3FA34D), Color(0xFF1B5E20)], + onTap: (!_valid || loading) + ? null + : () => context + .read() + .verifyOtp(_controller.text.trim()), + ), + TextButton( + onPressed: loading ? null : () => context.pop(), + child: const Text('تغییر شماره', + style: TextStyle(color: AppColors.gold)), + ), + ], + ), + ), + ], + ), ), ); }, diff --git a/lib/features/game/tier_list_screen.dart b/lib/features/game/tier_list_screen.dart index 651c7fc..c0a0686 100644 --- a/lib/features/game/tier_list_screen.dart +++ b/lib/features/game/tier_list_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../core/theme/app_theme.dart'; +import '../../core/widgets/game_ui.dart'; import 'game_repository.dart'; import 'tier.dart'; @@ -26,92 +27,172 @@ class _TierListScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('انتخاب میز')), - body: FutureBuilder>( - future: _future, - builder: (context, snap) { - if (snap.connectionState != ConnectionState.done) { - return const Center(child: CircularProgressIndicator()); - } - if (snap.hasError || snap.data == null) { - return Center( - child: Column(mainAxisSize: MainAxisSize.min, children: [ - const Text('خطا در بارگذاری میزها'), - TextButton( - onPressed: () => - setState(() => _future = widget.repo.getTiers()), - child: const Text('تلاش مجدد'), - ), - ]), - ); - } - final tiers = snap.data!; - return ListView.separated( - padding: const EdgeInsets.all(16), - itemCount: tiers.length, - separatorBuilder: (_, __) => const SizedBox(height: 12), - itemBuilder: (_, i) => _TierCard(tier: tiers[i]), - ); - }, - ), - ); - } -} - -class _TierCard extends StatelessWidget { - final TableTier tier; - const _TierCard({required this.tier}); - - @override - Widget build(BuildContext context) { - return InkWell( - borderRadius: BorderRadius.circular(14), - onTap: () => context.push('/game/${tier.id}?prize=${tier.prize}'), - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColors.panel, - borderRadius: BorderRadius.circular(14), - border: Border.all(color: AppColors.goldDark), - ), - child: Row( + body: GameBackground( + child: Column( children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(tier.title, - style: const TextStyle( - color: AppColors.gold, - fontSize: 18, - fontWeight: FontWeight.bold)), - const SizedBox(height: 4), - Text('${tier.hands} دست', - style: const TextStyle(color: Colors.white60)), - ], + Padding( + padding: const EdgeInsets.all(12), + child: Row(children: [ + _BackButton(onTap: () => context.pop()), + const Spacer(), + const GlowText('انتخاب میز', size: 26), + const Spacer(), + const SizedBox(width: 48), + ]), ), - const Spacer(), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - _row(Icons.login, 'ورودی', tier.entry), - _row(Icons.emoji_events, 'جایزه', tier.prize), - ], + Expanded( + child: FutureBuilder>( + future: _future, + builder: (context, snap) { + if (snap.connectionState != ConnectionState.done) { + return const Center( + child: CircularProgressIndicator(color: AppColors.gold)); + } + if (snap.hasError || snap.data == null) { + return Center( + child: Column(mainAxisSize: MainAxisSize.min, children: [ + const Text('خطا در بارگذاری میزها'), + TextButton( + onPressed: () => + setState(() => _future = widget.repo.getTiers()), + child: const Text('تلاش مجدد'), + ), + ]), + ); + } + final tiers = snap.data!; + return ListView.separated( + padding: const EdgeInsets.fromLTRB(16, 4, 16, 20), + itemCount: tiers.length, + separatorBuilder: (_, __) => const SizedBox(height: 14), + itemBuilder: (_, i) => _TierCard(tier: tiers[i], index: i), + ); + }, + ), ), ], ), ), ); } +} - Widget _row(IconData icon, String label, int value) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: Row(mainAxisSize: MainAxisSize.min, children: [ - Text('$label: $value', - style: const TextStyle(color: AppColors.text, fontSize: 13)), - const SizedBox(width: 4), - Icon(icon, size: 16, color: AppColors.gold), - ]), +class _BackButton extends StatelessWidget { + final VoidCallback onTap; + const _BackButton({required this.onTap}); + @override + Widget build(BuildContext context) => GestureDetector( + onTap: onTap, + child: Container( + width: 48, + height: 48, + 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), + ), + ); +} + +class _TierCard extends StatelessWidget { + final TableTier tier; + final int index; + const _TierCard({required this.tier, required this.index}); + + // پالتِ رنگیِ هر میز (مطابق اپ مرجع). + static const _palettes = [ + [Color(0xFF43A047), Color(0xFF1B5E20)], // سبز + [Color(0xFFE53935), Color(0xFF8E0E1B)], // قرمز + [Color(0xFF1E88E5), Color(0xFF0D3C73)], // آبی + [Color(0xFF8E24AA), Color(0xFF4A0D5E)], // بنفش + ]; + + @override + Widget build(BuildContext context) { + final colors = _palettes[index % _palettes.length]; + return GestureDetector( + onTap: () => context.push('/game/${tier.id}?prize=${tier.prize}'), + child: Container( + padding: const EdgeInsets.fromLTRB(14, 12, 14, 12), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: colors, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColors.gold, width: 2), + boxShadow: const [ + BoxShadow(color: Colors.black54, blurRadius: 10, offset: Offset(0, 5)), + ], + ), + child: Row( + children: [ + // ریبونِ تعداد دست + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFFE9952F), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppColors.gold), + ), + child: Column(children: [ + Text('${tier.hands}', + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold)), + const Text('دست', + style: TextStyle(color: Colors.white, fontSize: 11)), + ]), + ), + const SizedBox(width: 14), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(tier.title, + style: const TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + shadows: [Shadow(color: Colors.black54, blurRadius: 3)], + )), + const SizedBox(height: 6), + _stat(Icons.login, 'ورودی', tier.entry), + _stat(Icons.monetization_on, 'جایزه', tier.prize), + ], + ), + const Spacer(), + Column(children: [ + _badge(Icons.star, 'XP ${tier.xp}'), + const SizedBox(height: 6), + _badge(Icons.emoji_events, '${tier.trophy}'), + ]), + ], + ), + ), ); } + + Widget _stat(IconData icon, String label, int value) => Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row(mainAxisSize: MainAxisSize.min, children: [ + Icon(icon, size: 15, color: AppColors.gold), + const SizedBox(width: 5), + Text('$label: $value', + style: const TextStyle(color: Colors.white, fontSize: 13)), + ]), + ); + + Widget _badge(IconData icon, String text) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 16, color: AppColors.gold), + const SizedBox(width: 4), + Text(text, + style: const TextStyle(color: Colors.white, fontSize: 12)), + ], + ); } diff --git a/lib/features/lobby/lobby_screen.dart b/lib/features/lobby/lobby_screen.dart index 144de65..3d07b54 100644 --- a/lib/features/lobby/lobby_screen.dart +++ b/lib/features/lobby/lobby_screen.dart @@ -3,11 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import '../../core/theme/app_theme.dart'; +import '../../core/widgets/game_ui.dart'; import '../auth/auth_cubit.dart'; import 'wallet.dart'; import 'wallet_cubit.dart'; -/// لابی اصلی: کیف‌پول، دکمه بازی، سکه روزانه. +/// لابی اصلی: کیف‌پول، دکمه بازی، فروشگاه، سکه روزانه (ظاهرِ بازی‌گونه). class LobbyScreen extends StatefulWidget { const LobbyScreen({super.key}); @@ -25,54 +26,51 @@ class _LobbyScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: SafeArea( + body: GameBackground( child: BlocBuilder( builder: (context, state) { return Column( children: [ - _TopBar(wallet: state.wallet), + _TopBar(wallet: state.wallet, onCoinTap: () => _openShop(context)), Expanded( child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('سلطان حکم', - style: TextStyle( - fontSize: 40, - fontWeight: FontWeight.bold, - color: AppColors.gold)), - const SizedBox(height: 48), - _MenuButton( - label: 'بازی', - icon: Icons.style, - color: const Color(0xFF8E1B5B), - onTap: () async { - await context.push('/game/tiers'); - if (context.mounted) { - context.read().load(); - } - }, - ), - const SizedBox(height: 16), - _MenuButton( - label: 'فروشگاه', - icon: Icons.storefront, - color: const Color(0xFF4A148C), - onTap: () async { - await context.push('/shop'); - if (context.mounted) { - context.read().load(); - } - }, - ), - const SizedBox(height: 16), - _MenuButton( - label: 'سکه روزانه', - icon: Icons.monetization_on, - color: AppColors.green, - onTap: () => _claimDaily(context), - ), - ], + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 28), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const GlowText('سلطان حکم', size: 44), + const SizedBox(height: 44), + GameButton( + label: 'بازی', + icon: Icons.style, + width: double.infinity, + colors: const [Color(0xFFC2185B), Color(0xFF6A0D38)], + onTap: () async { + await context.push('/game/tiers'); + if (context.mounted) { + context.read().load(); + } + }, + ), + const SizedBox(height: 16), + GameButton( + label: 'فروشگاه', + icon: Icons.storefront, + width: double.infinity, + colors: const [Color(0xFF7B1FA2), Color(0xFF3E0A57)], + onTap: () => _openShop(context), + ), + const SizedBox(height: 16), + GameButton( + label: 'سکه روزانه', + icon: Icons.monetization_on, + width: double.infinity, + colors: const [Color(0xFF3FA34D), Color(0xFF1B5E20)], + onTap: () => _claimDaily(context), + ), + ], + ), ), ), ), @@ -94,6 +92,11 @@ class _LobbyScreenState extends State { ); } + Future _openShop(BuildContext context) async { + await context.push('/shop'); + if (context.mounted) context.read().load(); + } + Future _claimDaily(BuildContext context) async { final amount = await context.read().claimDaily(); if (!context.mounted) return; @@ -109,7 +112,8 @@ class _LobbyScreenState extends State { class _TopBar extends StatelessWidget { final Wallet? wallet; - const _TopBar({this.wallet}); + final VoidCallback onCoinTap; + const _TopBar({this.wallet, required this.onCoinTap}); @override Widget build(BuildContext context) { @@ -118,136 +122,70 @@ class _TopBar extends StatelessWidget { margin: const EdgeInsets.all(8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( - color: AppColors.bgDark, - borderRadius: BorderRadius.circular(14), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFF3A0A12), Color(0xFF1A0106)], + ), + borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.goldDark), + boxShadow: const [BoxShadow(color: Colors.black54, blurRadius: 8)], ), child: Row( children: [ - const CircleAvatar( - backgroundColor: AppColors.panel, - child: Icon(Icons.person, color: AppColors.gold), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: AppColors.gold, width: 2), + ), + child: const CircleAvatar( + radius: 22, + backgroundColor: AppColors.panel, + child: Icon(Icons.person, color: AppColors.gold), + ), ), const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - Text('سطح ${w?.level ?? '-'}', - style: const TextStyle(color: AppColors.gold)), - if (w != null) - SizedBox( - width: 90, + Row(children: [ + const Icon(Icons.star, color: AppColors.gold, size: 16), + const SizedBox(width: 4), + Text('سطح ${w?.level ?? '-'}', + style: const TextStyle( + color: AppColors.gold, fontWeight: FontWeight.bold)), + ]), + const SizedBox(height: 4), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: SizedBox( + width: 96, + height: 7, child: LinearProgressIndicator( - value: w.xpForNext == 0 ? 0 : w.xpIntoLevel / w.xpForNext, - backgroundColor: Colors.white12, - color: AppColors.gold, - minHeight: 5, + value: (w == null || w.xpForNext == 0) + ? 0 + : w.xpIntoLevel / w.xpForNext, + backgroundColor: Colors.white10, + valueColor: const AlwaysStoppedAnimation(AppColors.gold), ), ), + ), ], ), const Spacer(), - _Chip(icon: Icons.confirmation_number, value: w?.tickets ?? 0), - const SizedBox(width: 8), - _Chip(icon: Icons.monetization_on, value: w?.coins ?? 0), - ], - ), - ); - } -} - -class _Chip extends StatelessWidget { - final IconData icon; - final int value; - const _Chip({required this.icon, required this.value}); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - decoration: BoxDecoration( - color: AppColors.panel, - borderRadius: BorderRadius.circular(20), - border: Border.all(color: AppColors.goldDark), - ), - child: Row( - children: [ - Icon(icon, color: AppColors.gold, size: 18), - const SizedBox(width: 6), - Text('$value', style: const TextStyle(color: AppColors.text)), - ], - ), - ); - } -} - -class _MenuButton extends StatelessWidget { - final String label; - final IconData icon; - final Color color; - final VoidCallback onTap; - const _MenuButton({ - required this.label, - required this.icon, - required this.color, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - // رنگِ تیره‌ترِ پایه برای بِوِل (لبه‌ی پایینیِ برجسته). - final dark = Color.lerp(color, Colors.black, 0.45)!; - final light = Color.lerp(color, Colors.white, 0.12)!; - return SizedBox( - width: 280, - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(16), - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: onTap, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 15), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [light, color, dark], - stops: const [0, 0.5, 1], - ), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppColors.gold, width: 1.6), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.55), - blurRadius: 10, - offset: const Offset(0, 5), - ), - // هایلایتِ بالا برای حسِ شیشه‌ای/برجسته. - BoxShadow( - color: Colors.white.withValues(alpha: 0.12), - blurRadius: 1, - offset: const Offset(0, 1), - spreadRadius: -1, - ), - ], - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, color: AppColors.gold), - const SizedBox(width: 10), - Text(label, - style: const TextStyle( - color: AppColors.text, - fontSize: 18, - fontWeight: FontWeight.bold, - shadows: [Shadow(color: Colors.black54, blurRadius: 3)], - )), - ], - ), + GestureDetector( + onTap: onCoinTap, + child: StatChip( + icon: Icons.confirmation_number, value: '${w?.tickets ?? 0}'), ), - ), + const SizedBox(width: 8), + GestureDetector( + onTap: onCoinTap, + child: StatChip( + icon: Icons.monetization_on, value: '${w?.coins ?? 0}'), + ), + ], ), ); } diff --git a/lib/features/shop/shop_screen.dart b/lib/features/shop/shop_screen.dart index e7f1691..7897969 100644 --- a/lib/features/shop/shop_screen.dart +++ b/lib/features/shop/shop_screen.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; import '../../core/theme/app_theme.dart'; +import '../../core/widgets/game_ui.dart'; import '../lobby/wallet_cubit.dart'; import 'shop_cubit.dart'; import 'shop_models.dart'; -/// صفحه‌ی فروشگاه با تب‌های سکه/بلیط/کارت/تجهیزات. +/// فروشگاه با تب‌های سکه/بلیط/کارت/تجهیزات و ظاهرِ بازی‌گونه. class ShopScreen extends StatelessWidget { const ShopScreen({super.key}); @@ -15,71 +17,135 @@ class ShopScreen extends StatelessWidget { return DefaultTabController( length: 4, child: Scaffold( - appBar: AppBar( - title: const Text('فروشگاه'), - actions: [ - BlocBuilder( - builder: (context, s) => Center( - child: Padding( - padding: const EdgeInsets.only(left: 12), - child: Row(children: [ - const Icon(Icons.monetization_on, - color: AppColors.gold, size: 18), - const SizedBox(width: 4), - Text('${s.wallet?.coins ?? 0}', - style: const TextStyle(color: AppColors.text)), - ]), + backgroundColor: Colors.transparent, + body: GameBackground( + child: Column( + children: [ + _header(context), + Expanded( + child: Container( + margin: const EdgeInsets.fromLTRB(8, 0, 8, 8), + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFF4A0C16), Color(0xFF2A0710)], + ), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColors.goldDark, width: 1.5), + ), + child: Column( + children: [ + const _ShopTabs(), + Expanded( + child: BlocBuilder( + builder: (context, state) { + if (state.status == ShopStatus.loading || + state.status == ShopStatus.initial) { + return const Center( + child: CircularProgressIndicator( + color: AppColors.gold)); + } + if (state.status == ShopStatus.error || + state.data == null) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('خطا در بارگذاری فروشگاه'), + TextButton( + onPressed: () => + context.read().load(), + child: const Text('تلاش مجدد'), + ), + ]), + ); + } + final d = state.data!; + return TabBarView( + children: [ + _CoinsTab(packages: d.coinPackages), + _TicketsTab(packages: d.ticketPackages), + _CardsTab(data: d), + _BoostersTab(boosters: d.boosters), + ], + ); + }, + ), + ), + ], + ), ), ), - ), - ], - bottom: const TabBar( - isScrollable: true, - labelColor: AppColors.gold, - indicatorColor: AppColors.gold, - tabs: [ - Tab(text: 'سکه'), - Tab(text: 'بلیط'), - Tab(text: 'کارت'), - Tab(text: 'تجهیزات'), ], ), ), - body: BlocConsumer( - listener: (context, state) {}, - builder: (context, state) { - if (state.status == ShopStatus.loading || - state.status == ShopStatus.initial) { - return const Center(child: CircularProgressIndicator()); - } - if (state.status == ShopStatus.error || state.data == null) { - return Center( - child: Column(mainAxisSize: MainAxisSize.min, children: [ - const Text('خطا در بارگذاری فروشگاه'), - TextButton( - onPressed: () => context.read().load(), - child: const Text('تلاش مجدد'), - ), - ]), - ); - } - final d = state.data!; - return TabBarView( - children: [ - _CoinsTab(packages: d.coinPackages), - _TicketsTab(packages: d.ticketPackages), - _CardsTab(data: d), - _BoostersTab(boosters: d.boosters), - ], - ); - }, - ), ), ); } + + Widget _header(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8), + child: Row(children: [ + 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), + ), + ), + const Spacer(), + BlocBuilder( + builder: (context, s) => Row(children: [ + StatChip( + icon: Icons.confirmation_number, + value: '${s.wallet?.tickets ?? 0}'), + const SizedBox(width: 8), + StatChip( + icon: Icons.monetization_on, value: '${s.wallet?.coins ?? 0}'), + ]), + ), + ]), + ); + } } -/// اجرای یک عملیات فروشگاه و نمایش نتیجه + بازخوانی کیف‌پول. +class _ShopTabs extends StatelessWidget { + const _ShopTabs(); + @override + Widget build(BuildContext context) { + return const TabBar( + indicator: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFFC62828), Color(0xFF7B0E14)], + ), + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + ), + indicatorSize: TabBarIndicatorSize.tab, + labelColor: AppColors.gold, + unselectedLabelColor: Colors.white60, + labelStyle: TextStyle(fontWeight: FontWeight.bold), + dividerColor: Colors.transparent, + tabs: [ + Tab(text: 'سکه'), + Tab(text: 'بلیط'), + Tab(text: 'کارت'), + Tab(text: 'تجهیزات'), + ], + ); + } +} + +/// اجرای یک عملیات فروشگاه، نمایش نتیجه و بازخوانی کیف‌پول. Future _do(BuildContext context, Future Function() action) async { final msg = await action(); if (!context.mounted || msg.isEmpty) return; @@ -88,34 +154,39 @@ Future _do(BuildContext context, Future Function() action) async { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); } +// ===== تب‌ها ===== + class _CoinsTab extends StatelessWidget { final List packages; const _CoinsTab({required this.packages}); - @override Widget build(BuildContext context) { - return GridView.count( - crossAxisCount: 2, - padding: const EdgeInsets.all(12), - childAspectRatio: 0.82, - mainAxisSpacing: 12, - crossAxisSpacing: 12, + return _grid( + note: 'با داشتن اشتراک VIP در هر خرید ۱۰٪ سکه اضافه هدیه می‌گیرید.', children: [ - _FreeCoinCard( - onTap: () => _do(context, () => context.read().claimAd()), + _ItemCard( + title: 'سکه رایگان', + glowColor: const Color(0xFF1B5E20), + icon: Icons.card_giftcard, + subtitle: 'با دیدن تبلیغ', + action: _PriceButton( + label: 'رایگان', + green: true, + onTap: () => _do(context, () => context.read().claimAd()), + ), ), for (final p in packages) - _StoreCard( + _ItemCard( title: p.title, - badge: p.bonusPct > 0 ? '+${p.bonusPct}%' : null, - lines: [ - '${p.coins} سکه', - if (p.vipDays > 0) '${p.vipDays} روز VIP', - ], - priceLabel: '${p.priceToman} تومان', + glowColor: const Color(0xFF1B5E20), icon: Icons.savings, - onTap: () => - _do(context, () => context.read().purchase('coin', p.id)), + ribbon: p.bonusPct > 0 ? '+${p.bonusPct}٪' : null, + subtitle: '${p.coins} سکه${p.vipDays > 0 ? ' + ${p.vipDays} روز VIP' : ''}', + action: _PriceButton( + label: '${p.priceToman} تومان', + onTap: () => _do( + context, () => context.read().purchase('coin', p.id)), + ), ), ], ); @@ -125,24 +196,22 @@ class _CoinsTab extends StatelessWidget { class _TicketsTab extends StatelessWidget { final List packages; const _TicketsTab({required this.packages}); - @override Widget build(BuildContext context) { - return GridView.count( - crossAxisCount: 2, - padding: const EdgeInsets.all(12), - childAspectRatio: 0.82, - mainAxisSpacing: 12, - crossAxisSpacing: 12, + return _grid( + note: 'با خرید بلیط می‌توانید درخواست بر زدن مجدد در بازی‌ها انجام دهید.', children: [ for (final p in packages) - _StoreCard( + _ItemCard( title: p.title, - lines: ['${p.tickets} بلیط'], - priceLabel: '${p.priceToman} تومان', + glowColor: const Color(0xFF8E1B7A), icon: Icons.confirmation_number, - onTap: () => - _do(context, () => context.read().purchase('ticket', p.id)), + subtitle: '${p.tickets} بلیط', + action: _PriceButton( + label: '${p.priceToman} تومان', + onTap: () => _do(context, + () => context.read().purchase('ticket', p.id)), + ), ), ], ); @@ -152,24 +221,22 @@ class _TicketsTab extends StatelessWidget { class _BoostersTab extends StatelessWidget { final List boosters; const _BoostersTab({required this.boosters}); - @override Widget build(BuildContext context) { - return GridView.count( - crossAxisCount: 2, - padding: const EdgeInsets.all(12), - childAspectRatio: 0.82, - mainAxisSpacing: 12, - crossAxisSpacing: 12, + return _grid( + note: 'با بسته‌های تجربه، چند برابر XP بگیرید و سریع‌تر بالا بروید.', children: [ for (final b in boosters) - _StoreCard( + _ItemCard( title: b.title, - lines: ['تجربه ×${b.multiplier}', '${b.hours} ساعت'], - priceLabel: '${b.priceToman} تومان', + glowColor: const Color(0xFF1E3A8A), icon: Icons.bolt, - onTap: () => - _do(context, () => context.read().purchase('booster', b.id)), + subtitle: 'تجربه ×${b.multiplier} — ${b.hours} ساعت', + action: _PriceButton( + label: '${b.priceToman} تومان', + onTap: () => _do(context, + () => context.read().purchase('booster', b.id)), + ), ), ], ); @@ -179,206 +246,224 @@ class _BoostersTab extends StatelessWidget { class _CardsTab extends StatelessWidget { final ShopData data; const _CardsTab({required this.data}); - @override Widget build(BuildContext context) { - return GridView.count( - crossAxisCount: 2, - padding: const EdgeInsets.all(12), - childAspectRatio: 0.82, - mainAxisSpacing: 12, - crossAxisSpacing: 12, + return _grid( + note: 'با اسکین کارت‌های متنوع حال‌وهوای بازی را عوض کن.', children: [ for (final c in data.cardSkins) - _CardSkinCard( - skin: c, - owned: data.owns(c.id), - selected: data.selectedCard == c.id, + _ItemCard( + title: c.title, + glowColor: const Color(0xFF0E3C73), + icon: Icons.style, + subtitle: c.priceCoins > 0 ? '${c.priceCoins} سکه' : 'پیش‌فرض', + action: _cardAction(context, c, data), ), ], ); } -} -class _CardSkinCard extends StatelessWidget { - final CardSkin skin; - final bool owned; - final bool selected; - const _CardSkinCard( - {required this.skin, required this.owned, required this.selected}); - - @override - Widget build(BuildContext context) { - final Widget action; - if (selected) { - action = const _Pill(text: 'انتخاب شده', color: AppColors.goldDark); - } else if (owned) { - action = _ActionButton( + 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: 'انتخاب', - color: AppColors.green, + green: true, onTap: () => - _do(context, () => context.read().selectCard(skin.id)), - ); - } else { - action = _ActionButton( - label: '${skin.priceCoins} سکه', - color: AppColors.accent, - onTap: () => _do(context, () => context.read().buyCard(skin.id)), + _do(context, () => context.read().selectCard(c.id)), ); } - return _Panel( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(skin.title, - textAlign: TextAlign.center, - style: const TextStyle(color: AppColors.gold)), - const Icon(Icons.style, size: 48, color: AppColors.text), - SizedBox(width: double.infinity, child: action), - ], - ), + return _PriceButton( + label: '${c.priceCoins} سکه', + green: true, + coin: true, + onTap: () => _do(context, () => context.read().buyCard(c.id)), ); } } -class _FreeCoinCard extends StatelessWidget { - final VoidCallback onTap; - const _FreeCoinCard({required this.onTap}); - - @override - Widget build(BuildContext context) { - return _Panel( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('سکه رایگان', - textAlign: TextAlign.center, - style: TextStyle(color: AppColors.gold)), - const Icon(Icons.ondemand_video, size: 44, color: AppColors.green), - const Text('با دیدن تبلیغ', - style: TextStyle(color: Colors.white60, fontSize: 12)), - SizedBox( - width: double.infinity, - child: _ActionButton( - label: 'رایگان', color: AppColors.green, onTap: onTap), - ), - ], +Widget _grid({required String note, required List children}) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 6), + child: Text(note, + textAlign: TextAlign.center, + style: const TextStyle(color: AppColors.gold, fontSize: 13)), ), - ); - } + Expanded( + child: GridView.count( + crossAxisCount: 2, + padding: const EdgeInsets.all(12), + childAspectRatio: 0.74, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + children: children, + ), + ), + ], + ); } -class _StoreCard extends StatelessWidget { +// ===== ویجت‌های کارت آیتم ===== + +class _ItemCard extends StatelessWidget { final String title; - final List lines; - final String priceLabel; final IconData icon; - final String? badge; - final VoidCallback onTap; - const _StoreCard({ + final Color glowColor; + final String subtitle; + final String? ribbon; + final Widget action; + const _ItemCard({ required this.title, - required this.lines, - required this.priceLabel, required this.icon, - required this.onTap, - this.badge, + required this.glowColor, + required this.subtitle, + required this.action, + this.ribbon, }); @override Widget build(BuildContext context) { - return Stack( - children: [ - _Panel( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(title, - textAlign: TextAlign.center, - style: const TextStyle(color: AppColors.gold)), - Icon(icon, size: 40, color: AppColors.gold), - Column( - children: [ - for (final l in lines) - Text(l, style: const TextStyle(color: AppColors.text)), - ], - ), - SizedBox( - width: double.infinity, - child: _ActionButton( - label: priceLabel, color: AppColors.accent, onTap: onTap), - ), - ], + final card = Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFF6E1322), Color(0xFF3A0A12)], + ), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: AppColors.gold, width: 1.6), + boxShadow: const [ + BoxShadow(color: Colors.black54, blurRadius: 8, offset: Offset(0, 4)), + ], + ), + child: Column( + children: [ + Text(title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: AppColors.gold, fontWeight: FontWeight.bold)), + const SizedBox(height: 6), + Expanded(child: _GlowArt(color: glowColor, icon: icon)), + const SizedBox(height: 4), + Text(subtitle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle(color: Colors.white70, fontSize: 11)), + const SizedBox(height: 6), + SizedBox(width: double.infinity, child: action), + ], + ), + ); + if (ribbon == null) return card; + return Stack(clipBehavior: Clip.none, children: [ + card, + Positioned( + top: 8, + left: -22, + child: Transform.rotate( + angle: -0.7, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 26, vertical: 3), + color: const Color(0xFF2E7D32), + child: Text(ribbon!, + style: const TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.bold)), ), ), - if (badge != null) - Positioned( - top: 4, - left: 4, - child: _Pill(text: badge!, color: AppColors.green), - ), - ], - ); + ), + ]); } } -class _Panel extends StatelessWidget { - final Widget child; - const _Panel({required this.child}); - +/// قابِ هنریِ آیتم با درخششِ شعاعی و آیکن. +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( - padding: const EdgeInsets.all(10), + width: double.infinity, decoration: BoxDecoration( - color: AppColors.panel, - borderRadius: BorderRadius.circular(14), - border: Border.all(color: AppColors.goldDark), + borderRadius: BorderRadius.circular(10), + gradient: RadialGradient( + colors: [color, Colors.black.withValues(alpha: 0.85)], + radius: 0.9, + ), + border: Border.all(color: Colors.black26), + ), + child: Center( + child: Icon(icon, size: 44, color: AppColors.gold), ), - child: child, ); } } -class _ActionButton extends StatelessWidget { +class _PriceButton extends StatelessWidget { final String label; - final Color color; - final VoidCallback onTap; - const _ActionButton( - {required this.label, required this.color, required this.onTap}); + 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 Widget build(BuildContext context) { final busy = context.select((ShopCubit c) => c.state.busy); - return ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: color, - minimumSize: const Size.fromHeight(38), - padding: const EdgeInsets.symmetric(horizontal: 6), + final colors = disabled + ? const [Color(0xFF555555), Color(0xFF333333)] + : green + ? const [Color(0xFF3FA34D), Color(0xFF1B5E20)] + : const [Color(0xFFD83A4A), Color(0xFF8E0E1B)]; + return Opacity( + opacity: disabled ? 0.85 : 1, + child: GestureDetector( + onTap: (disabled || busy) ? null : onTap, + child: Container( + height: 36, + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 6), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: colors, + ), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: AppColors.gold, width: 1.2), + ), + child: Row(mainAxisSize: MainAxisSize.min, children: [ + if (coin) ...[ + const Icon(Icons.monetization_on, + color: AppColors.gold, size: 16), + const SizedBox(width: 4), + ], + Flexible( + child: Text(label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.bold)), + ), + ]), + ), ), - onPressed: busy ? null : onTap, - child: Text(label, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 13)), - ); - } -} - -class _Pill extends StatelessWidget { - final String text; - final Color color; - const _Pill({required this.text, required this.color}); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(8), - ), - child: Text(text, - style: const TextStyle(color: Colors.white, fontSize: 12)), ); } }