import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/constant/links.dart'; import 'package:sefer_driver/controller/functions/crud.dart'; import '../../main.dart'; // ════════════════════════════════════════════ // نماذج البيانات // ════════════════════════════════════════════ class DriverLevel { final String id; final String nameEn; final String nameAr; final String emoji; final Color color; final Color gradientEnd; final int minPoints; final int maxPoints; final double commissionDiscount; // نسبة الخصم على العمولة final List perks; const DriverLevel({ required this.id, required this.nameEn, required this.nameAr, required this.emoji, required this.color, required this.gradientEnd, required this.minPoints, required this.maxPoints, required this.commissionDiscount, required this.perks, }); } class Achievement { final String id; final String titleEn; final String titleAr; final String descriptionEn; final String descriptionAr; final IconData icon; final Color color; final int target; final String type; // 'trips', 'rating', 'earnings', 'streak', 'referral' bool isUnlocked; int currentProgress; final DateTime? unlockedAt; Achievement({ required this.id, required this.titleEn, required this.titleAr, required this.descriptionEn, required this.descriptionAr, required this.icon, required this.color, required this.target, required this.type, this.isUnlocked = false, this.currentProgress = 0, this.unlockedAt, }); double get progress => (currentProgress / target).clamp(0.0, 1.0); } // ════════════════════════════════════════════ // المستويات الثابتة // ════════════════════════════════════════════ class DriverLevels { static const List all = [ DriverLevel( id: 'bronze', nameEn: 'Bronze', nameAr: 'برونزي', emoji: '🥉', color: Color(0xFFCD7F32), gradientEnd: Color(0xFFE8A854), minPoints: 0, maxPoints: 999, commissionDiscount: 0, perks: ['Basic features', 'Standard support'], ), DriverLevel( id: 'silver', nameEn: 'Silver', nameAr: 'فضي', emoji: '🥈', color: Color(0xFF9CA3AF), gradientEnd: Color(0xFFC0C7D1), minPoints: 1000, maxPoints: 4999, commissionDiscount: 1, perks: ['Priority medium', 'Silver badge', '-1% commission'], ), DriverLevel( id: 'gold', nameEn: 'Gold', nameAr: 'ذهبي', emoji: '🥇', color: Color(0xFFFFD700), gradientEnd: Color(0xFFFFA500), minPoints: 5000, maxPoints: 14999, commissionDiscount: 2, perks: ['High priority', 'Gold badge', '-2% commission'], ), DriverLevel( id: 'diamond', nameEn: 'Diamond', nameAr: 'ألماسي', emoji: '💎', color: Color(0xFF00BCD4), gradientEnd: Color(0xFF3F51B5), minPoints: 15000, maxPoints: 999999, commissionDiscount: 5, perks: ['VIP first', 'Diamond badge', '-5% commission', 'Priority support'], ), ]; static DriverLevel getLevel(int points) { for (int i = all.length - 1; i >= 0; i--) { if (points >= all[i].minPoints) return all[i]; } return all.first; } static DriverLevel? getNextLevel(int points) { final current = getLevel(points); final idx = all.indexOf(current); if (idx < all.length - 1) return all[idx + 1]; return null; } static double getProgressToNext(int points) { final current = getLevel(points); final next = getNextLevel(points); if (next == null) return 1.0; return ((points - current.minPoints) / (next.minPoints - current.minPoints)) .clamp(0.0, 1.0); } } // ════════════════════════════════════════════ // الـ Controller // ════════════════════════════════════════════ class GamificationController extends GetxController { bool isLoading = false; int totalTrips = 0; int totalPoints = 0; double averageRating = 5.0; int totalReferrals = 0; int consecutiveDays = 0; // أيام متتالية double totalEarnings = 0; // === Driving Behavior === double behaviorScore = 100.0; int hardBrakes = 0; double maxSpeed = 0.0; late DriverLevel currentLevel; DriverLevel? nextLevel; double progressToNext = 0; List achievements = []; // === Daily Goal === double dailyGoal = 0; double dailyEarnings = 0; double get dailyGoalProgress => dailyGoal > 0 ? (dailyEarnings / dailyGoal).clamp(0.0, 1.0) : 0.0; bool get isDailyGoalMet => dailyGoalProgress >= 1.0; @override void onInit() { super.onInit(); _loadLocalData(); _initializeAchievements(); _calculateLevel(); fetchGamificationData(); } // ═══════ تحميل البيانات المحلية ═══════ void _loadLocalData() { dailyGoal = (box.read('dailyGoal') ?? 0).toDouble(); totalTrips = box.read('gamification_totalTrips') ?? 0; consecutiveDays = box.read('gamification_consecutiveDays') ?? 0; } // ═══════ حفظ الهدف اليومي ═══════ void setDailyGoal(double goal) { dailyGoal = goal; box.write('dailyGoal', goal); update(); } void updateDailyEarnings(double earnings) { dailyEarnings = earnings; update(); } // ═══════ حساب المستوى ═══════ void _calculateLevel() { currentLevel = DriverLevels.getLevel(totalPoints); nextLevel = DriverLevels.getNextLevel(totalPoints); progressToNext = DriverLevels.getProgressToNext(totalPoints); update(); } // ═══════ تهيئة الإنجازات ═══════ void _initializeAchievements() { achievements = [ Achievement( id: 'first_trip', titleEn: 'First Trip', titleAr: 'أول رحلة', descriptionEn: 'Complete your first trip', descriptionAr: 'أكمل أول رحلة لك', icon: Icons.flag_rounded, color: const Color(0xFF4CAF50), target: 1, type: 'trips', ), Achievement( id: 'trip_50', titleEn: 'Road Warrior', titleAr: 'محارب الطريق', descriptionEn: 'Complete 50 trips', descriptionAr: 'أكمل 50 رحلة', icon: Icons.local_taxi_rounded, color: const Color(0xFF2196F3), target: 50, type: 'trips', ), Achievement( id: 'trip_100', titleEn: 'Century Rider', titleAr: 'سائق المئة', descriptionEn: 'Complete 100 trips', descriptionAr: 'أكمل 100 رحلة', icon: Icons.emoji_events_rounded, color: const Color(0xFFFF9800), target: 100, type: 'trips', ), Achievement( id: 'trip_500', titleEn: 'Road Legend', titleAr: 'أسطورة الطريق', descriptionEn: 'Complete 500 trips', descriptionAr: 'أكمل 500 رحلة', icon: Icons.stars_rounded, color: const Color(0xFFE91E63), target: 500, type: 'trips', ), Achievement( id: 'five_star', titleEn: 'Five Star Driver', titleAr: 'سائق 5 نجوم', descriptionEn: 'Maintain 5.0 rating', descriptionAr: 'حافظ على تقييم 5.0', icon: Icons.star_rounded, color: const Color(0xFFFFD700), target: 5, type: 'rating', ), Achievement( id: 'streak_7', titleEn: 'Weekly Streak', titleAr: 'سلسلة أسبوعية', descriptionEn: 'Work 7 consecutive days', descriptionAr: 'اعمل 7 أيام متتالية', icon: Icons.whatshot_rounded, color: const Color(0xFFFF5722), target: 7, type: 'streak', ), Achievement( id: 'streak_30', titleEn: 'Monthly Streak', titleAr: 'سلسلة شهرية', descriptionEn: 'Work 30 consecutive days', descriptionAr: 'اعمل 30 يوم متتالي', icon: Icons.local_fire_department_rounded, color: const Color(0xFFD32F2F), target: 30, type: 'streak', ), Achievement( id: 'referral_5', titleEn: 'Social Butterfly', titleAr: 'الفراشة الاجتماعية', descriptionEn: 'Refer 5 drivers', descriptionAr: 'ادعُ 5 سائقين', icon: Icons.people_rounded, color: const Color(0xFF9C27B0), target: 5, type: 'referral', ), ]; } // ═══════ تحديث تقدم الإنجازات ═══════ void _updateAchievementProgress() { for (var ach in achievements) { switch (ach.type) { case 'trips': ach.currentProgress = totalTrips; break; case 'rating': ach.currentProgress = averageRating >= 5 ? 5 : averageRating.floor(); break; case 'streak': ach.currentProgress = consecutiveDays; break; case 'referral': ach.currentProgress = totalReferrals; break; } ach.isUnlocked = ach.currentProgress >= ach.target; } update(); } // ═══════ جلب البيانات من السيرفر ═══════ Future fetchGamificationData() async { isLoading = true; update(); try { // 1. جلب عدد الرحلات الكلي var tripRes = await CRUD().get( link: AppLink.getTripCountByCaptain, payload: {'driver_id': box.read(BoxName.driverID).toString()}, ); if (tripRes != null && tripRes != 'failure') { var data = jsonDecode(tripRes); totalTrips = int.tryParse(data['message']?[0]?['count']?.toString() ?? '0') ?? 0; box.write('gamification_totalTrips', totalTrips); } // 2. جلب التقييم var rateRes = await CRUD().get( link: AppLink.getDriverRate, payload: {'driver_id': box.read(BoxName.driverID).toString()}, ); if (rateRes != null && rateRes != 'failure') { var data = jsonDecode(rateRes); averageRating = double.tryParse(data['message']?[0]?['rating']?.toString() ?? '5') ?? 5.0; } // 3. جلب النقاط (الرصيد) var pointsRes = await CRUD().getWallet( link: AppLink.getDriverPaymentPoints, payload: {'driverID': box.read(BoxName.driverID).toString()}, ); if (pointsRes != null && pointsRes != 'failure') { var data = jsonDecode(pointsRes); totalPoints = double.tryParse( data['message']?[0]?['total_amount']?.toString() ?? '0') ?.abs() .toInt() ?? 0; } // 4. جلب عدد الدعوات var invRes = await CRUD().get( link: AppLink.getInviteDriver, payload: {'driver_id': box.read(BoxName.driverID).toString()}, ); if (invRes != null && invRes != 'failure') { var data = jsonDecode(invRes); if (data['message'] is List) { totalReferrals = (data['message'] as List).length; } } // 5. جلب أرباح اليوم var todayRes = await CRUD().getWallet( link: AppLink.getDriverPaymentToday, payload: {'driverID': box.read(BoxName.driverID).toString()}, ); if (todayRes != null && todayRes != 'failure') { var data = jsonDecode(todayRes); dailyEarnings = double.tryParse( data['message']?[0]?['todayAmount']?.toString() ?? '0') ?? 0; } // 6. جلب تقييم سلوك القيادة var behaviorRes = await CRUD().get( link: AppLink.getDriverBehavior, payload: {'driver_id': box.read(BoxName.driverID).toString()}, ); if (behaviorRes != null && behaviorRes != 'failure') { var data = jsonDecode(behaviorRes); if (data['message'] is List && data['message'].isNotEmpty) { var behavior = data['message'][0]; behaviorScore = double.tryParse(behavior['avg_score']?.toString() ?? '100') ?? 100.0; hardBrakes = int.tryParse(behavior['total_hard_brakes']?.toString() ?? '0') ?? 0; maxSpeed = double.tryParse(behavior['max_speed']?.toString() ?? '0') ?? 0.0; } } // 7. حساب الأيام المتتالية (محلياً) _calculateConsecutiveDays(); } catch (e) { debugPrint('❌ [Gamification] Error fetching data: $e'); } _calculateLevel(); _updateAchievementProgress(); isLoading = false; update(); } void _calculateConsecutiveDays() { String? lastActiveDate = box.read('lastActiveDate'); String today = DateTime.now().toIso8601String().split('T')[0]; // 2026-05-08 if (lastActiveDate == null) { consecutiveDays = 1; } else if (lastActiveDate == today) { // نفس اليوم — لا تغيير } else { DateTime last = DateTime.parse(lastActiveDate); DateTime now = DateTime.parse(today); if (now.difference(last).inDays == 1) { consecutiveDays++; } else { consecutiveDays = 1; } } box.write('lastActiveDate', today); box.write('gamification_consecutiveDays', consecutiveDays); } // ═══════ إحصائيات سريعة ═══════ int get unlockedCount => achievements.where((a) => a.isUnlocked).length; int get totalAchievements => achievements.length; }