diff --git a/lib/feature/wallet/data/model/wallet_model.dart b/lib/feature/wallet/data/model/wallet_model.dart index 1e93687..cf3141f 100644 --- a/lib/feature/wallet/data/model/wallet_model.dart +++ b/lib/feature/wallet/data/model/wallet_model.dart @@ -15,6 +15,10 @@ class WalletModel extends WalletEntity { required super.selectedCarpet, required super.name, required super.avatar, + super.dailyStreak, + super.bestStreak, + super.dailyReady, + super.nextDaily, }); factory WalletModel.fromJson( @@ -36,6 +40,10 @@ class WalletModel extends WalletEntity { selectedCarpet: (wallet['selected_carpet'] ?? 'classic') as String, name: (name == null || name.isEmpty) ? 'بازیکن' : name, avatar: (avatar == null || avatar.isEmpty) ? 'hakem-1' : avatar, + dailyStreak: (wallet['daily_streak'] ?? 0) as int, + bestStreak: (wallet['best_streak'] ?? 0) as int, + dailyReady: (wallet['daily_ready'] ?? true) as bool, + nextDaily: (wallet['next_daily'] ?? 200) as int, ); } } diff --git a/lib/feature/wallet/domain/entities/wallet_entity.dart b/lib/feature/wallet/domain/entities/wallet_entity.dart index 2f5a4e0..50e28a8 100644 --- a/lib/feature/wallet/domain/entities/wallet_entity.dart +++ b/lib/feature/wallet/domain/entities/wallet_entity.dart @@ -12,6 +12,10 @@ class WalletEntity { final String selectedCarpet; final String name; final String avatar; + final int dailyStreak; // روزهای پیاپیِ دریافتِ سکه‌ی روزانه + final int bestStreak; + final bool dailyReady; // آیا امروز سکه‌ی روزانه قابلِ دریافت است + final int nextDaily; // جایزه‌ی روزانه‌ی بعدی const WalletEntity({ required this.coins, @@ -26,5 +30,9 @@ class WalletEntity { required this.selectedCarpet, required this.name, required this.avatar, + this.dailyStreak = 0, + this.bestStreak = 0, + this.dailyReady = true, + this.nextDaily = 200, }); } diff --git a/lib/feature/wallet/presentation/screen/lobby_screen.dart b/lib/feature/wallet/presentation/screen/lobby_screen.dart index 00f4ce8..7d5bf4f 100644 --- a/lib/feature/wallet/presentation/screen/lobby_screen.dart +++ b/lib/feature/wallet/presentation/screen/lobby_screen.dart @@ -13,6 +13,7 @@ import '../bloc/wallet_bloc.dart'; import '../bloc/wallet_event.dart'; import '../bloc/wallet_state.dart'; import '../bloc/wallet_status.dart'; +import '../widgets/streak_dialog.dart'; /// لابی اصلی: کیف‌پول، دکمه بازی/دورهمی/فروشگاه/سکه روزانه. class LobbyScreen extends StatefulWidget { @@ -120,9 +121,17 @@ class _LobbyScreenState extends State { ), const SizedBox(height: 14), _DailyCard( - onTap: () => context - .read() - .add(ClaimDailyEvent()), + wallet: wallet, + onTap: () => StreakDialog.show( + context, + streak: wallet?.dailyStreak ?? 0, + best: wallet?.bestStreak ?? 0, + ready: wallet?.dailyReady ?? true, + nextReward: wallet?.nextDaily ?? 200, + onClaim: () => context + .read() + .add(ClaimDailyEvent()), + ), ), ], ), @@ -305,46 +314,101 @@ class _ActionTile extends StatelessWidget { } } -/// کارتِ سکه‌ی روزانه (اکشنِ برجسته‌ی سبز-طلایی). +/// کارتِ هدیه‌ی روزانه با استریک (مثلِ دولینگو): شعله + تعدادِ روزهای پیاپی و +/// وضعیتِ قابلِ‌دریافت/دریافت‌شده. class _DailyCard extends StatelessWidget { + final WalletEntity? wallet; final VoidCallback onTap; - const _DailyCard({required this.onTap}); + const _DailyCard({required this.wallet, required this.onTap}); + @override Widget build(BuildContext context) { - return Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(16), - child: InkWell( + final w = wallet; + final streak = w?.dailyStreak ?? 0; + final ready = w?.dailyReady ?? true; + final next = w?.nextDaily ?? 200; + + final colors = ready + ? const [AppColors.success, AppColors.successDark] + : const [AppColors.lapisHi, AppColors.lapisLo]; + + return Opacity( + opacity: ready ? 1 : 0.75, + child: Material( + color: Colors.transparent, borderRadius: BorderRadius.circular(16), - onTap: onTap, - child: Ink( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - decoration: BoxDecoration( - gradient: const LinearGradient( - colors: [AppColors.success, AppColors.successDark], + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: onTap, // همیشه دیالوگِ استریک را باز می‌کند + child: Ink( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13), + decoration: BoxDecoration( + gradient: LinearGradient(colors: colors), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColors.gold, width: 1.4), + ), + child: Row( + children: [ + // شعله‌ی استریک با عددِ روز. + Stack( + alignment: Alignment.center, + children: [ + Icon(Icons.local_fire_department, + color: streak > 0 ? const Color(0xFFFF7A1A) : Colors.white54, + size: 40), + if (streak > 0) + Padding( + padding: const EdgeInsets.only(top: 6), + child: Text('$streak', + style: const TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.bold)), + ), + ], + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('هدیه‌ی روزانه', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold)), + Text( + streak > 0 ? '$streak روز پیاپی 🔥' : 'زنجیره‌ات رو شروع کن!', + style: const TextStyle(color: Colors.white70, fontSize: 12), + ), + ], + ), + const Spacer(), + if (ready) + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row(mainAxisSize: MainAxisSize.min, children: [ + const Icon(Icons.monetization_on, + color: AppColors.gold, size: 16), + const SizedBox(width: 3), + Text('+$next', + style: const TextStyle( + color: AppColors.gold, + fontWeight: FontWeight.bold)), + ]), + const Text('دریافت', + style: TextStyle(color: Colors.white70, fontSize: 11)), + ], + ) + else + const Row(mainAxisSize: MainAxisSize.min, children: [ + Icon(Icons.check_circle, color: AppColors.gold, size: 18), + SizedBox(width: 4), + Text('امروز گرفتی', + style: TextStyle(color: Colors.white70, fontSize: 12)), + ]), + ], ), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppColors.gold, width: 1.4), - ), - child: Row( - children: const [ - Icon(Icons.card_giftcard, color: Colors.white, size: 26), - SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('هدیه‌ی روزانه', - style: TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.bold)), - Text('هر روز سکه‌ی رایگان بگیر', - style: TextStyle(color: Colors.white70, fontSize: 12)), - ], - ), - Spacer(), - Icon(Icons.redeem, color: AppColors.gold, size: 22), - ], ), ), ), diff --git a/lib/feature/wallet/presentation/widgets/streak_dialog.dart b/lib/feature/wallet/presentation/widgets/streak_dialog.dart new file mode 100644 index 0000000..07ffe0f --- /dev/null +++ b/lib/feature/wallet/presentation/widgets/streak_dialog.dart @@ -0,0 +1,230 @@ +import 'package:flutter/material.dart'; + +import '../../../../core/theme/app_theme.dart'; + +/// دیالوگِ استریکِ روزانه به سبکِ دولینگو: شعله‌ی بزرگ + شمارِ روز + نوارِ +/// پیشرفتِ هفتگی به‌سویِ پاداشِ ۵۰۰ سکه‌ای + دکمه‌ی دریافت. +class StreakDialog extends StatelessWidget { + final int streak; + final int best; + final bool ready; + final int nextReward; + final VoidCallback onClaim; + + const StreakDialog({ + super.key, + required this.streak, + required this.best, + required this.ready, + required this.nextReward, + required this.onClaim, + }); + + static Future show( + BuildContext context, { + required int streak, + required int best, + required bool ready, + required int nextReward, + required VoidCallback onClaim, + }) { + return showDialog( + context: context, + barrierColor: Colors.black.withValues(alpha: 0.7), + builder: (_) => StreakDialog( + streak: streak, + best: best, + ready: ready, + nextReward: nextReward, + onClaim: onClaim, + ), + ); + } + + @override + Widget build(BuildContext context) { + // پیشرفت تا پاداشِ هفتگی (۷تایی). اگر روی مضربِ ۷ باشد، کاملِ ۷. + int weekProg = streak % 7; + if (weekProg == 0 && streak > 0) weekProg = 7; + final toBonus = 7 - weekProg; + + return Dialog( + backgroundColor: Colors.transparent, + insetPadding: const EdgeInsets.symmetric(horizontal: 28, vertical: 40), + child: Container( + padding: const EdgeInsets.fromLTRB(20, 24, 20, 20), + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [AppColors.lapisMid, AppColors.lapisLo], + ), + borderRadius: BorderRadius.circular(24), + border: Border.all(color: AppColors.gold, width: 1.8), + boxShadow: [ + BoxShadow( + color: const Color(0xFFFF7A1A).withValues(alpha: 0.25), + blurRadius: 26), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // شعله‌ی بزرگ با عددِ استریک. + SizedBox( + height: 118, + child: Stack( + alignment: Alignment.center, + children: [ + const Icon(Icons.local_fire_department, + color: Color(0xFFFF7A1A), size: 118), + Padding( + padding: const EdgeInsets.only(top: 22), + child: Text( + '$streak', + style: const TextStyle( + color: Colors.white, + fontSize: 44, + fontWeight: FontWeight.w900, + shadows: [Shadow(color: Colors.black45, blurRadius: 6)], + ), + ), + ), + ], + ), + ), + const SizedBox(height: 4), + Text( + streak > 0 ? '$streak روز پیاپی!' : 'زنجیره‌ات رو شروع کن', + style: const TextStyle( + color: AppColors.gold, + fontSize: 20, + fontWeight: FontWeight.bold), + ), + const SizedBox(height: 2), + Text('بهترین رکورد: $best روز', + style: const TextStyle(color: Colors.white54, fontSize: 12)), + const SizedBox(height: 18), + + // نوارِ پیشرفتِ هفتگی. + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.22), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColors.goldFaint), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + for (var i = 0; i < 7; i++) _dayCell(i, weekProg), + ], + ), + const SizedBox(height: 10), + Text( + toBonus == 0 + ? '🎉 امروز پاداشِ هفتگی +۵۰۰ سکه!' + : '$toBonus روز تا پاداشِ هفتگی (+۵۰۰ سکه)', + style: const TextStyle(color: Colors.white70, fontSize: 12), + ), + ], + ), + ), + const SizedBox(height: 18), + + // دکمه‌ی دریافت / وضعیتِ دریافت‌شده. + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + ready ? AppColors.success : AppColors.lapisHi, + padding: const EdgeInsets.symmetric(vertical: 13), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + side: const BorderSide(color: AppColors.gold, width: 1.3), + ), + ), + onPressed: ready + ? () { + Navigator.pop(context); + onClaim(); + } + : null, + child: ready + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('دریافت هدیه', + style: TextStyle( + color: Colors.white, + fontSize: 15, + fontWeight: FontWeight.bold)), + const SizedBox(width: 8), + const Icon(Icons.monetization_on, + color: AppColors.gold, size: 20), + const SizedBox(width: 3), + Text('+$nextReward', + style: const TextStyle( + color: Colors.white, + fontSize: 15, + fontWeight: FontWeight.bold)), + ], + ) + : const Text('فردا دوباره سر بزن ✓', + style: TextStyle( + color: Colors.white70, + fontSize: 14, + fontWeight: FontWeight.bold)), + ), + ), + const SizedBox(height: 6), + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('بستن', + style: TextStyle(color: Colors.white38, fontSize: 13)), + ), + ], + ), + ), + ); + } + + // یک خانه‌ی روز در نوارِ هفتگی. خانه‌های پرشده تا weekProg شعله دارند؛ خانه‌ی + // پایانی (روزِ هفتم) پاداشِ سکه‌ای است. + Widget _dayCell(int i, int weekProg) { + final done = i < weekProg; + final isBonus = i == 6; + final Color bg = done + ? (isBonus ? AppColors.gold : const Color(0xFFFF7A1A)) + : AppColors.lapisHi; + return Column( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: bg.withValues(alpha: done ? 1 : 0.35), + shape: BoxShape.circle, + border: Border.all( + color: done ? AppColors.gold : Colors.white24, + width: done ? 1.4 : 1, + ), + ), + child: Icon( + isBonus + ? Icons.monetization_on + : (done ? Icons.local_fire_department : Icons.circle_outlined), + color: done ? Colors.white : Colors.white38, + size: isBonus ? 18 : 16, + ), + ), + const SizedBox(height: 4), + Text('${i + 1}', + style: const TextStyle(color: Colors.white38, fontSize: 10)), + ], + ); + } +}