first commit
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import 'dart:async';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import '../models/gps_coordinate.dart';
|
||||
import '../models/workout.dart';
|
||||
import '../screens/tracking_screen.dart';
|
||||
import '../services/database_service.dart';
|
||||
import '../services/gps_service.dart';
|
||||
import '../services/sync_service.dart';
|
||||
import '../services/analytics_service.dart';
|
||||
import '../utils/polyline_utility.dart';
|
||||
import '../utils/geo_utils.dart';
|
||||
|
||||
/// المتحكم الرئيسي للتمرين (Main Workout Controller)
|
||||
/// يدير حالة التتبع الحية، التفاعل مع الـ GPS، وحفظ البيانات.
|
||||
class WorkoutController extends GetxController {
|
||||
final GpsService _gps = Get.find<GpsService>();
|
||||
final DatabaseService _db = Get.find<DatabaseService>();
|
||||
final SyncService _sync = Get.find<SyncService>();
|
||||
final AnalyticsService _analytics = Get.find<AnalyticsService>();
|
||||
|
||||
// حالة التتبع الحية
|
||||
final Rxn<Workout> currentWorkout = Rxn<Workout>();
|
||||
final RxList<GpsCoordinate> liveCoordinates = <GpsCoordinate>[].obs;
|
||||
final RxInt elapsedSeconds = 0.obs;
|
||||
final RxDouble liveDistance = 0.0.obs;
|
||||
final RxDouble liveSpeed = 0.0.obs;
|
||||
|
||||
Timer? _timer;
|
||||
|
||||
// ─── التحكم في الجلسة ──────────────────────────────────────────
|
||||
|
||||
Future<void> startWorkout(WorkoutType type) async {
|
||||
bool ok = await _gps.startTracking(distanceFilter: 5);
|
||||
if (!ok) {
|
||||
Get.snackbar("خطأ", "فشل الوصول إلى الموقع (GPS). تأكد من الصلاحيات.");
|
||||
return;
|
||||
}
|
||||
|
||||
final id = const Uuid().v4();
|
||||
currentWorkout.value = Workout(id: id, workoutType: type);
|
||||
currentWorkout.value!.start();
|
||||
|
||||
liveCoordinates.clear();
|
||||
elapsedSeconds.value = 0;
|
||||
liveDistance.value = 0;
|
||||
liveSpeed.value = 0;
|
||||
|
||||
// ربط مستمع الإحداثيات
|
||||
_gps.onNewCoordinate = _handleNewCoordinate;
|
||||
|
||||
// الانتقال لشاشة التتبع
|
||||
Get.to(() => const TrackingScreen());
|
||||
|
||||
// بدء المؤقت الزمني
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (currentWorkout.value != null && !currentWorkout.value!.isPaused) {
|
||||
elapsedSeconds.value = currentWorkout.value!.activeDurationSeconds;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void pauseWorkout() {
|
||||
currentWorkout.value?.pause();
|
||||
update();
|
||||
}
|
||||
|
||||
void resumeWorkout() {
|
||||
currentWorkout.value?.resume();
|
||||
update();
|
||||
}
|
||||
|
||||
Future<void> stopWorkout() async {
|
||||
if (currentWorkout.value == null) return;
|
||||
|
||||
await _gps.stopTracking();
|
||||
_timer?.cancel();
|
||||
|
||||
final workout = currentWorkout.value!;
|
||||
workout.stop();
|
||||
|
||||
// إسناد الإحداثيات النهائية
|
||||
workout.coordinates = List.from(liveCoordinates);
|
||||
workout.distanceMeters = liveDistance.value;
|
||||
|
||||
// التحليل الذكي للمقاطع
|
||||
workout.segments =
|
||||
_analytics.analyzeSegments(workout.coordinates, workout.workoutType);
|
||||
|
||||
// تحويل المسار إلى Polyline مضغوط
|
||||
workout.routePolyline = PolylineUtility.encode(workout.coordinates);
|
||||
|
||||
if (workout.coordinates.isNotEmpty) {
|
||||
workout.startLat = workout.coordinates.first.latitude;
|
||||
workout.startLng = workout.coordinates.first.longitude;
|
||||
workout.endLat = workout.coordinates.last.latitude;
|
||||
workout.endLng = workout.coordinates.last.longitude;
|
||||
}
|
||||
|
||||
// حفظ في قاعدة البيانات المحلية
|
||||
await _db.saveWorkout(workout);
|
||||
await _db.saveSegments(workout.id, workout.segments);
|
||||
|
||||
// مسح الإحداثيات المؤقتة من جدول الـ GPS (لأنها حفظت في الـ Polyline)
|
||||
// أو يمكن تركها إذا أردت دقة عالية جداً لاحقاً
|
||||
|
||||
// إضافة إلى طابور المزامنة
|
||||
await _sync.queueWorkout(workout);
|
||||
|
||||
currentWorkout.value = null;
|
||||
Get.snackbar("تم الحفظ", "تم حفظ التمرين بنجاح وجاري المزامنة...");
|
||||
}
|
||||
|
||||
void _handleNewCoordinate(GpsCoordinate coord) {
|
||||
if (currentWorkout.value == null || currentWorkout.value!.isPaused) return;
|
||||
|
||||
liveCoordinates.add(coord);
|
||||
liveSpeed.value = coord.speed ?? 0;
|
||||
|
||||
// حفظ فوري في DB (للحماية من انهيار التطبيق)
|
||||
_db.saveCoordinate(currentWorkout.value!.id, coord);
|
||||
|
||||
// حساب المسافة التراكمية حياً
|
||||
if (liveCoordinates.length > 1) {
|
||||
// تم إضافة المسافة في الموديل أو هنا مباشرة
|
||||
// للتسهيل سنقوم بالتحديث هنا
|
||||
}
|
||||
|
||||
// تحديث المسافة الإجمالية (تبسيطاً سنعتمد على المجموع)
|
||||
_calculateLiveDistance();
|
||||
}
|
||||
|
||||
void _calculateLiveDistance() {
|
||||
if (liveCoordinates.length < 2) return;
|
||||
|
||||
double totalDist = 0;
|
||||
for (int i = 1; i < liveCoordinates.length; i++) {
|
||||
totalDist += GeoUtils.haversineDistance(
|
||||
liveCoordinates[i - 1].latitude,
|
||||
liveCoordinates[i - 1].longitude,
|
||||
liveCoordinates[i].latitude,
|
||||
liveCoordinates[i].longitude);
|
||||
}
|
||||
liveDistance.value = totalDist;
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_timer?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user