feat: redesign behavior page, add fatigue monitoring and fix map controller
This commit is contained in:
286
lib/controller/gamification/challenges_controller.dart
Normal file
286
lib/controller/gamification/challenges_controller.dart
Normal file
@@ -0,0 +1,286 @@
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user