287 lines
8.8 KiB
Dart
287 lines
8.8 KiB
Dart
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 Challenge {
|
|
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 int reward; // نقاط
|
|
final String type; // 'daily' or 'weekly'
|
|
final String metric; // 'trips', 'earnings', 'hours', 'acceptance_rate'
|
|
int currentProgress;
|
|
bool isClaimed;
|
|
|
|
Challenge({
|
|
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.reward,
|
|
required this.type,
|
|
required this.metric,
|
|
this.currentProgress = 0,
|
|
this.isClaimed = false,
|
|
});
|
|
|
|
double get progress => (currentProgress / target).clamp(0.0, 1.0);
|
|
bool get isCompleted => currentProgress >= target;
|
|
}
|
|
|
|
// ════════════════════════════════════════════
|
|
// Controller
|
|
// ════════════════════════════════════════════
|
|
|
|
class ChallengesController extends GetxController {
|
|
bool isLoading = false;
|
|
List<Challenge> dailyChallenges = [];
|
|
List<Challenge> weeklyChallenges = [];
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
_generateChallenges();
|
|
_loadProgress();
|
|
fetchChallengeProgress();
|
|
}
|
|
|
|
void _generateChallenges() {
|
|
final now = DateTime.now();
|
|
final isWeekend = now.weekday == 5 || now.weekday == 6; // الجمعة والسبت
|
|
|
|
dailyChallenges = [
|
|
Challenge(
|
|
id: 'daily_trips_5',
|
|
titleEn: 'Road Runner',
|
|
titleAr: 'سائق سريع',
|
|
descriptionEn: 'Complete 5 trips today',
|
|
descriptionAr: 'أكمل 5 رحلات اليوم',
|
|
icon: Icons.local_taxi_rounded,
|
|
color: const Color(0xFF2196F3),
|
|
target: 5,
|
|
reward: 50,
|
|
type: 'daily',
|
|
metric: 'trips',
|
|
),
|
|
Challenge(
|
|
id: 'daily_trips_10',
|
|
titleEn: 'Marathon Driver',
|
|
titleAr: 'سائق الماراثون',
|
|
descriptionEn: 'Complete 10 trips today',
|
|
descriptionAr: 'أكمل 10 رحلات اليوم',
|
|
icon: Icons.directions_car_rounded,
|
|
color: const Color(0xFFFF9800),
|
|
target: 10,
|
|
reward: 150,
|
|
type: 'daily',
|
|
metric: 'trips',
|
|
),
|
|
Challenge(
|
|
id: 'daily_earnings',
|
|
titleEn: 'Money Maker',
|
|
titleAr: 'صانع المال',
|
|
descriptionEn: 'Earn 3000 SYP today',
|
|
descriptionAr: 'اربح 3000 ل.س اليوم',
|
|
icon: Icons.monetization_on_rounded,
|
|
color: const Color(0xFF4CAF50),
|
|
target: 3000,
|
|
reward: 100,
|
|
type: 'daily',
|
|
metric: 'earnings',
|
|
),
|
|
if (isWeekend)
|
|
Challenge(
|
|
id: 'daily_weekend_bonus',
|
|
titleEn: 'Weekend Warrior',
|
|
titleAr: 'محارب عطلة نهاية الأسبوع',
|
|
descriptionEn: 'Complete 8 trips on the weekend',
|
|
descriptionAr: 'أكمل 8 رحلات في عطلة نهاية الأسبوع',
|
|
icon: Icons.celebration_rounded,
|
|
color: const Color(0xFFE91E63),
|
|
target: 8,
|
|
reward: 200,
|
|
type: 'daily',
|
|
metric: 'trips',
|
|
),
|
|
];
|
|
|
|
weeklyChallenges = [
|
|
Challenge(
|
|
id: 'weekly_trips_30',
|
|
titleEn: 'Weekly Champion',
|
|
titleAr: 'بطل الأسبوع',
|
|
descriptionEn: 'Complete 30 trips this week',
|
|
descriptionAr: 'أكمل 30 رحلة هذا الأسبوع',
|
|
icon: Icons.emoji_events_rounded,
|
|
color: const Color(0xFFFFD700),
|
|
target: 30,
|
|
reward: 300,
|
|
type: 'weekly',
|
|
metric: 'trips',
|
|
),
|
|
Challenge(
|
|
id: 'weekly_earnings',
|
|
titleEn: 'Big Earner',
|
|
titleAr: 'الربح الكبير',
|
|
descriptionEn: 'Earn 20,000 SYP this week',
|
|
descriptionAr: 'اربح 20,000 ل.س هذا الأسبوع',
|
|
icon: Icons.account_balance_wallet_rounded,
|
|
color: const Color(0xFF9C27B0),
|
|
target: 20000,
|
|
reward: 500,
|
|
type: 'weekly',
|
|
metric: 'earnings',
|
|
),
|
|
Challenge(
|
|
id: 'weekly_hours',
|
|
titleEn: 'Time Master',
|
|
titleAr: 'سيد الوقت',
|
|
descriptionEn: 'Drive for 20 hours this week',
|
|
descriptionAr: 'اقضِ 20 ساعة في القيادة هذا الأسبوع',
|
|
icon: Icons.timer_rounded,
|
|
color: const Color(0xFF00BCD4),
|
|
target: 20,
|
|
reward: 400,
|
|
type: 'weekly',
|
|
metric: 'hours',
|
|
),
|
|
];
|
|
}
|
|
|
|
void _loadProgress() {
|
|
final today = DateTime.now().toIso8601String().split('T')[0];
|
|
final savedDate = box.read('challenges_date');
|
|
|
|
if (savedDate != today) {
|
|
// يوم جديد — إعادة تعيين التحديات اليومية
|
|
box.write('challenges_date', today);
|
|
for (var c in dailyChallenges) {
|
|
box.write('challenge_${c.id}_claimed', false);
|
|
}
|
|
}
|
|
|
|
// تحميل حالة المطالبة
|
|
for (var c in dailyChallenges) {
|
|
c.isClaimed = box.read('challenge_${c.id}_claimed') ?? false;
|
|
}
|
|
for (var c in weeklyChallenges) {
|
|
c.isClaimed = box.read('challenge_${c.id}_claimed') ?? false;
|
|
}
|
|
}
|
|
|
|
Future<void> fetchChallengeProgress() async {
|
|
isLoading = true;
|
|
update();
|
|
|
|
try {
|
|
// جلب رحلات اليوم
|
|
var todayRes = await CRUD().getWallet(
|
|
link: AppLink.getDriverPaymentToday,
|
|
payload: {'driverID': box.read(BoxName.driverID).toString()},
|
|
);
|
|
|
|
int todayTrips = 0;
|
|
double todayEarnings = 0;
|
|
|
|
if (todayRes != null && todayRes != 'failure') {
|
|
var data = jsonDecode(todayRes);
|
|
todayEarnings = double.tryParse(data['message']?[0]?['todayAmount']?.toString() ?? '0') ?? 0;
|
|
todayTrips = int.tryParse(data['message']?[0]?['todayCount']?.toString() ?? '0') ?? 0;
|
|
}
|
|
|
|
// تحديث التحديات اليومية
|
|
for (var c in dailyChallenges) {
|
|
switch (c.metric) {
|
|
case 'trips':
|
|
c.currentProgress = todayTrips;
|
|
break;
|
|
case 'earnings':
|
|
c.currentProgress = todayEarnings.toInt();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Fetch weekly aggregate for weekly challenges
|
|
var weeklyRes = await CRUD().get(
|
|
link: AppLink.getWeeklyAggregate,
|
|
payload: {'driver_id': box.read(BoxName.driverID).toString()},
|
|
);
|
|
|
|
int weeklyTrips = 0;
|
|
double weeklyEarnings = 0;
|
|
double weeklyHours = 0;
|
|
|
|
if (weeklyRes != null && weeklyRes != 'failure') {
|
|
var data = jsonDecode(weeklyRes);
|
|
if (data['message'] is List) {
|
|
for (var day in data['message']) {
|
|
weeklyTrips += int.tryParse(day['trips']?.toString() ?? '0') ?? 0;
|
|
weeklyEarnings += double.tryParse(day['earnings']?.toString() ?? '0') ?? 0;
|
|
weeklyHours += double.tryParse(day['hours']?.toString() ?? '0') ?? 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var c in weeklyChallenges) {
|
|
switch (c.metric) {
|
|
case 'trips':
|
|
c.currentProgress = weeklyTrips;
|
|
break;
|
|
case 'earnings':
|
|
c.currentProgress = weeklyEarnings.toInt();
|
|
break;
|
|
case 'hours':
|
|
c.currentProgress = weeklyHours.toInt();
|
|
break;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
debugPrint('❌ [Challenges] Error: $e');
|
|
}
|
|
|
|
isLoading = false;
|
|
update();
|
|
}
|
|
|
|
Future<void> claimReward(Challenge challenge) async {
|
|
if (!challenge.isCompleted || challenge.isClaimed) return;
|
|
|
|
try {
|
|
var res = await CRUD().post(
|
|
link: AppLink.claimChallengeReward,
|
|
payload: {
|
|
'driver_id': box.read(BoxName.driverID).toString(),
|
|
'challenge_id': challenge.id,
|
|
'points': challenge.reward.toString(),
|
|
},
|
|
);
|
|
|
|
if (res != null && res != 'failure') {
|
|
challenge.isClaimed = true;
|
|
box.write('challenge_${challenge.id}_claimed', true);
|
|
debugPrint('🎉 Claimed ${challenge.reward} points for ${challenge.id}');
|
|
update();
|
|
}
|
|
} catch (e) {
|
|
debugPrint('❌ [Challenges] Claim error: $e');
|
|
}
|
|
}
|
|
}
|