chore: استيراد أولي من سيرو (ecfe7568) — بلا أي تعديل

نسخة كاملة من مستودع سيرو عند ecfe7568 لتكون أساس تطبيق «انطلق».
نُسخ المتعقَّب في git فقط (12,509 ملفاً / 302 م.ب) بـ git archive، لا
`cp -r` — فاستُثنيت تلقائياً مخلفات البناء (build · node_modules ·
.dart_tool · .gradle · Pods ≈ 10.7 غ.ب) وكل ما يستثنيه .gitignore.

هذا الكوميت **بلا أي تعديل عمداً** حتى يكون كل ما يليه فرقاً مقروءاً
مقابل سيرو الأصلي. سيرو نفسه لم يُمسّ.

⚠️ لا يبني بعد: `.env` و`lib/env/env.g.dart` غير متعقَّبين في سيرو (وهذا
صحيح — أسرار لكل مستأجر). كل تطبيق فلاتر هنا يحتاج .env خاصاً بانطلق ثم
توليد env.g.dart عبر build_runner. لا تُنسخ أسرار سيرو.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 05:10:29 +03:00
co-authored by Claude Opus 5
commit 92dc6b3641
3654 changed files with 896478 additions and 0 deletions
@@ -0,0 +1,293 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/models/model/driver/rides_summary_model.dart';
import '../../../main.dart';
class StatisticsController extends GetxController {
bool isLoading = false;
// ═══ Weekly Data ═══
List<DayStat> weeklyStats = [];
double weeklyEarnings = 0;
int weeklyTrips = 0;
double weeklyHours = 0;
// ═══ Monthly Data ═══
List<MonthlyPriceDriverModel> monthlyEarnings = [];
List<MonthlyRideModel> monthlyRides = [];
List<MonthlyDataModel> monthlyDuration = [];
double monthlyTotalEarnings = 0;
int monthlyTotalTrips = 0;
double monthlyTotalHours = 0;
String bestDay = '--';
double bestDayEarnings = 0;
// ═══ Tab State ═══
int selectedTab = 0; // 0=weekly, 1=monthly
@override
void onInit() {
super.onInit();
reloadData();
}
void changeTab(int tab) {
selectedTab = tab;
update();
}
// ═══════ جلب البيانات الأسبوعية ═══════
Future<void> fetchWeeklyData() async {
isLoading = true;
update();
try {
var res = await CRUD().get(
link: AppLink.getWeeklyAggregate,
payload: {
'driver_id': box.read(BoxName.driverID).toString(),
},
);
if (res != null && res != 'failure') {
box.write('stats_weekly_cache', res);
var data = jsonDecode(res);
if (data['message'] is List) {
weeklyStats = (data['message'] as List)
.map((e) => DayStat.fromJson(e))
.toList();
weeklyEarnings = weeklyStats.fold(0, (s, d) => s + d.earnings);
weeklyTrips = weeklyStats.fold(0, (s, d) => s + d.trips);
weeklyHours = weeklyStats.fold(0, (s, d) => s + d.hours);
}
}
} catch (e) {
debugPrint('❌ [Stats] Weekly fetch error: $e');
// Fallback: generate from local data
_generateLocalWeeklyData();
}
isLoading = false;
update();
}
// ═══════ جلب البيانات الشهرية ═══════
Future<void> fetchMonthlyData() async {
try {
// 1. أرباح شهرية
var earningsRes = await CRUD().getWallet(
link: AppLink.getAllPaymentFromRide,
payload: {'driverID': box.read(BoxName.driverID).toString()},
);
if (earningsRes != null && earningsRes != 'failure') {
box.write('stats_monthly_earnings_cache', earningsRes);
var data = jsonDecode(earningsRes);
if (data['message'] is List) {
monthlyEarnings = (data['message'] as List)
.map((e) => MonthlyPriceDriverModel.fromJson(e))
.toList();
monthlyTotalEarnings =
monthlyEarnings.fold(0, (s, d) => s + d.pricePerDay);
// أفضل يوم
if (monthlyEarnings.isNotEmpty) {
var best = monthlyEarnings
.reduce((a, b) => a.pricePerDay > b.pricePerDay ? a : b);
bestDay = best.day.toString();
bestDayEarnings = best.pricePerDay;
}
}
}
// 2. رحلات شهرية
var ridesRes = await CRUD().get(
link: AppLink.getRidesDriverByDay,
payload: {'driver_id': box.read(BoxName.driverID).toString()},
);
if (ridesRes != null && ridesRes != 'failure') {
box.write('stats_monthly_rides_cache', ridesRes);
var data = jsonDecode(ridesRes);
if (data['message'] is List) {
monthlyRides = (data['message'] as List)
.map((e) => MonthlyRideModel.fromJson(e))
.toList();
monthlyTotalTrips = monthlyRides.fold(0, (s, d) => s + d.countRide);
}
}
// 3. ساعات شهرية
var durationRes = await CRUD().get(
link: AppLink.getTotalDriverDuration,
payload: {'driver_id': box.read(BoxName.driverID).toString()},
);
if (durationRes != null && durationRes != 'failure') {
box.write('stats_monthly_duration_cache', durationRes);
var data = jsonDecode(durationRes);
if (data['message'] is List) {
monthlyDuration = (data['message'] as List)
.map((e) => MonthlyDataModel.fromJson(e))
.toList();
monthlyTotalHours =
monthlyDuration.fold(0, (s, d) => s + d.totalDuration.toDouble());
}
}
} catch (e) {
debugPrint('❌ [Stats] Monthly fetch error: $e');
}
update();
}
void _generateLocalWeeklyData() {
// Fallback بيانات محلية عند عدم توفر الـ API
final now = DateTime.now();
weeklyStats = List.generate(7, (i) {
final day = now.subtract(Duration(days: 6 - i));
return DayStat(
date: day,
dayName: _getDayName(day.weekday),
earnings: 0,
trips: 0,
hours: 0,
);
});
}
String _getDayName(int weekday) {
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
return days[weekday - 1];
}
bool _isFetching = false;
Future<void> reloadData({bool force = false}) async {
if (_isFetching) return;
_isFetching = true;
// التحقق من التخزين المؤقت (3 ساعات)
String? lastFetchStr = box.read('stats_last_fetch');
if (!force && lastFetchStr != null) {
try {
DateTime lastFetch = DateTime.parse(lastFetchStr);
if (DateTime.now().difference(lastFetch).inHours < 3) {
debugPrint('ℹ️ [Statistics] Loading cached statistics (last fetch: $lastFetchStr)');
// تحميل الكاش الأسبوعي
String? weeklyCache = box.read('stats_weekly_cache');
if (weeklyCache != null) {
var data = jsonDecode(weeklyCache);
if (data['message'] is List) {
weeklyStats = (data['message'] as List)
.map((e) => DayStat.fromJson(e))
.toList();
weeklyEarnings = weeklyStats.fold(0, (s, d) => s + d.earnings);
weeklyTrips = weeklyStats.fold(0, (s, d) => s + d.trips);
weeklyHours = weeklyStats.fold(0, (s, d) => s + d.hours);
}
}
// تحميل الكاش الشهري (الأرباح)
String? earningsCache = box.read('stats_monthly_earnings_cache');
if (earningsCache != null) {
var data = jsonDecode(earningsCache);
if (data['message'] is List) {
monthlyEarnings = (data['message'] as List)
.map((e) => MonthlyPriceDriverModel.fromJson(e))
.toList();
monthlyTotalEarnings =
monthlyEarnings.fold(0, (s, d) => s + d.pricePerDay);
if (monthlyEarnings.isNotEmpty) {
var best = monthlyEarnings
.reduce((a, b) => a.pricePerDay > b.pricePerDay ? a : b);
bestDay = best.day.toString();
bestDayEarnings = best.pricePerDay;
}
}
}
// تحميل الكاش الشهري (الرحلات)
String? ridesCache = box.read('stats_monthly_rides_cache');
if (ridesCache != null) {
var data = jsonDecode(ridesCache);
if (data['message'] is List) {
monthlyRides = (data['message'] as List)
.map((e) => MonthlyRideModel.fromJson(e))
.toList();
monthlyTotalTrips = monthlyRides.fold(0, (s, d) => s + d.countRide);
}
}
// تحميل الكاش الشهري (المدد)
String? durationCache = box.read('stats_monthly_duration_cache');
if (durationCache != null) {
var data = jsonDecode(durationCache);
if (data['message'] is List) {
monthlyDuration = (data['message'] as List)
.map((e) => MonthlyDataModel.fromJson(e))
.toList();
monthlyTotalHours =
monthlyDuration.fold(0, (s, d) => s + d.totalDuration.toDouble());
}
}
_isFetching = false;
update();
return;
}
} catch (e) {
debugPrint('⚠️ [Statistics] Failed to load cached statistics: $e');
}
}
isLoading = true;
update();
try {
debugPrint('📊 [Statistics] Reloading data from server...');
await fetchWeeklyData();
await fetchMonthlyData();
box.write('stats_last_fetch', DateTime.now().toIso8601String());
debugPrint('📊 [Statistics] Data reload complete.');
} catch (e) {
debugPrint('❌ [Statistics] Error reloading data: $e');
} finally {
_isFetching = false;
isLoading = false;
update();
}
}
}
// ═══════ نموذج إحصائية اليوم ═══════
class DayStat {
final DateTime date;
final String dayName;
final double earnings;
final int trips;
final double hours;
DayStat({
required this.date,
required this.dayName,
required this.earnings,
required this.trips,
required this.hours,
});
factory DayStat.fromJson(Map<String, dynamic> json) {
final date =
DateTime.tryParse(json['day']?.toString() ?? '') ?? DateTime.now();
const dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
return DayStat(
date: date,
dayName: dayNames[date.weekday - 1],
earnings: double.tryParse(json['earnings']?.toString() ?? '0') ?? 0,
trips: int.tryParse(json['trips']?.toString() ?? '0') ?? 0,
hours: double.tryParse(json['hours']?.toString() ?? '0') ?? 0,
);
}
}