439 lines
14 KiB
Dart
439 lines
14 KiB
Dart
import 'dart:async';
|
|
import 'dart:math' show cos, pi, sin;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:intaleq_maps/intaleq_maps.dart';
|
|
|
|
import '../../../print.dart';
|
|
import 'map_engine_controller.dart';
|
|
import 'map_socket_controller.dart';
|
|
import 'nearby_drivers_controller.dart';
|
|
import 'ride_lifecycle_controller.dart';
|
|
import 'ride_state.dart';
|
|
import 'location_search_controller.dart';
|
|
|
|
class RideSimulationController extends GetxController {
|
|
Timer? _simulationTimer;
|
|
int _step = 0;
|
|
bool _isSimulating = false;
|
|
|
|
List<LatLng> _simulatedRoute = [];
|
|
int _driverRouteIndex = 0;
|
|
|
|
String get simulationStatus => _isSimulating
|
|
? 'Simulating... Step $_step'
|
|
: 'Idle';
|
|
|
|
// آمود نقاط المسار (Jordan area: Amman center ~ 31.95, 35.91)
|
|
static const double _centerLat = 31.95;
|
|
static const double _centerLng = 35.91;
|
|
static const double _routeDelta = 0.03;
|
|
static const int _routePointCount = 100;
|
|
|
|
void _generateRoutePoints() {
|
|
_simulatedRoute = [];
|
|
for (int i = 0; i < _routePointCount; i++) {
|
|
double fraction = i / (_routePointCount - 1);
|
|
double lat = _centerLat + fraction * _routeDelta;
|
|
double lng = _centerLng + fraction * _routeDelta +
|
|
0.005 * sin(fraction * pi * 2);
|
|
_simulatedRoute.add(LatLng(lat, lng));
|
|
}
|
|
}
|
|
|
|
void startFullSimulation() {
|
|
if (_isSimulating) return;
|
|
_isSimulating = true;
|
|
_step = 0;
|
|
_driverRouteIndex = 0;
|
|
_generateRoutePoints();
|
|
|
|
Log.print('=' * 70);
|
|
Log.print('🚀 RIDE SIMULATION STARTED — Full lifecycle test');
|
|
Log.print('=' * 70);
|
|
|
|
_simulationTimer = Timer.periodic(const Duration(seconds: 2), (timer) {
|
|
switch (_step) {
|
|
case 0:
|
|
_simulateSearchingState();
|
|
break;
|
|
case 1:
|
|
_simulateDriverAppliedState();
|
|
break;
|
|
case 2:
|
|
_simulateDriverEnRoute(isFirst: true);
|
|
break;
|
|
case 3:
|
|
_simulateDriverEnRoute(isFirst: false);
|
|
break;
|
|
case 4:
|
|
_simulateDriverArrivedState();
|
|
break;
|
|
case 5:
|
|
_simulateRideBeginState();
|
|
break;
|
|
case 6:
|
|
_simulateRideInProgress();
|
|
break;
|
|
case 7:
|
|
_simulateRideFinishedState();
|
|
timer.cancel();
|
|
_isSimulating = false;
|
|
Log.print('=' * 70);
|
|
Log.print('✅ RIDE SIMULATION COMPLETED');
|
|
Log.print('=' * 70);
|
|
break;
|
|
}
|
|
_step++;
|
|
update();
|
|
});
|
|
}
|
|
|
|
void stopSimulation() {
|
|
_simulationTimer?.cancel();
|
|
_simulationTimer = null;
|
|
_isSimulating = false;
|
|
_step = 0;
|
|
_driverRouteIndex = 0;
|
|
Log.print('🛑 Simulation stopped');
|
|
update();
|
|
}
|
|
|
|
void _logPolylineState(String stage) {
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
Log.print('');
|
|
Log.print('╔══ Polyline State [$stage] ══╗');
|
|
if (mapEngine.polyLines.isEmpty) {
|
|
Log.print(' ║ (no polylines)');
|
|
}
|
|
for (final p in mapEngine.polyLines) {
|
|
String colorStr;
|
|
if (p.color == Colors.amber) {
|
|
colorStr = 'AMBER';
|
|
} else if (p.color.value == 0xFF2196F3) {
|
|
colorStr = 'BLUE';
|
|
} else if (p.color == Colors.blueGrey) {
|
|
colorStr = 'BLUE_GREY';
|
|
} else {
|
|
colorStr = '#${p.color.value.toRadixString(16).padLeft(8, '0')}';
|
|
}
|
|
Log.print(' ║ • ${p.polylineId.value}: ${p.points.length} pts, '
|
|
'color: $colorStr, w: ${p.width}');
|
|
}
|
|
Log.print('╠══ Markers [$stage] ══╣');
|
|
if (mapEngine.markers.isEmpty) {
|
|
Log.print(' ║ (no markers)');
|
|
}
|
|
for (final m in mapEngine.markers) {
|
|
Log.print(' ║ • ${m.markerId.value}: '
|
|
'(${m.position.latitude.toStringAsFixed(5)}, '
|
|
'${m.position.longitude.toStringAsFixed(5)}), '
|
|
'heading: ${m.rotation.toStringAsFixed(1)}');
|
|
}
|
|
Log.print('╚══════════════════════╝');
|
|
Log.print('');
|
|
}
|
|
|
|
void _verifyPolylineCondition(bool condition, String description) {
|
|
if (!condition) {
|
|
Log.print(' ❌ VERIFY FAILED: $description');
|
|
} else {
|
|
Log.print(' ✅ VERIFY PASSED: $description');
|
|
}
|
|
}
|
|
|
|
LatLng _interpolateRoutePoint(double fraction) {
|
|
if (_simulatedRoute.isEmpty) return LatLng(_centerLat, _centerLng);
|
|
int total = _simulatedRoute.length;
|
|
double exactIdx = fraction * (total - 1);
|
|
int idx = exactIdx.floor();
|
|
if (idx >= total - 1) return _simulatedRoute.last;
|
|
double t = exactIdx - idx;
|
|
return LatLng(
|
|
_simulatedRoute[idx].latitude +
|
|
t * (_simulatedRoute[idx + 1].latitude - _simulatedRoute[idx].latitude),
|
|
_simulatedRoute[idx].longitude +
|
|
t * (_simulatedRoute[idx + 1].longitude - _simulatedRoute[idx].longitude),
|
|
);
|
|
}
|
|
|
|
void _simulateSearchingState() {
|
|
Log.print('🟡 [Step $_step] STATE: SEARCHING');
|
|
final ride = Get.find<RideLifecycleController>();
|
|
final locSearch = Get.find<LocationSearchController>();
|
|
|
|
ride.currentRideState.value = RideState.searching;
|
|
ride.rideId = 'sim_ride_001';
|
|
ride.isSearchingWindow = true;
|
|
|
|
// آمود موقع الراكب
|
|
LatLng passengerLoc = _simulatedRoute.isNotEmpty
|
|
? _simulatedRoute.first
|
|
: LatLng(_centerLat, _centerLng);
|
|
locSearch.passengerLocation = passengerLoc;
|
|
ride.passengerLocation = passengerLoc;
|
|
ride.update();
|
|
|
|
_logPolylineState('searching');
|
|
_verifyPolylineCondition(
|
|
Get.find<MapEngineController>().polyLines.isEmpty,
|
|
'No route polylines during searching');
|
|
Log.print('');
|
|
}
|
|
|
|
void _simulateDriverAppliedState() {
|
|
Log.print('🟢 [Step $_step] STATE: DRIVER APPLIED');
|
|
final ride = Get.find<RideLifecycleController>();
|
|
final nearbyDrivers = Get.find<NearbyDriversController>();
|
|
final locSearch = Get.find<LocationSearchController>();
|
|
|
|
ride.currentRideState.value = RideState.driverApplied;
|
|
ride.statusRide = 'Apply';
|
|
ride.isSearchingWindow = false;
|
|
|
|
// آمود معلومات السائق
|
|
ride.driverId = 'sim_driver_001';
|
|
ride.driverName = 'Simulated Captain';
|
|
ride.make = 'Toyota';
|
|
ride.model = 'Corolla';
|
|
ride.carColor = 'White';
|
|
ride.licensePlate = 'SIM-123';
|
|
ride.driverRate = '4.8';
|
|
ride.driverPhone = '+9627XXXXXXXX';
|
|
ride.driverToken = 'sim_token_001';
|
|
|
|
// آمود مواقع البداية والنهاية
|
|
ride.passengerLocation = _simulatedRoute.first;
|
|
ride.myDestination = _simulatedRoute.last;
|
|
locSearch.passengerLocation = _simulatedRoute.first;
|
|
locSearch.myDestination = _simulatedRoute.last;
|
|
|
|
// محاكاة موقع السائق (45% على طول المسار)
|
|
LatLng driverPos = _interpolateRoutePoint(0.45);
|
|
nearbyDrivers.driverCarsLocationToPassengerAfterApplied = [driverPos];
|
|
|
|
// رسم مسار السائق إلى الراكب
|
|
ride.calculateDriverToPassengerRoute(driverPos, ride.passengerLocation);
|
|
|
|
// وضع ماركر السائق على الخريطة
|
|
ride.updateDriverMarker(driverPos, 45.0);
|
|
ride.update();
|
|
|
|
_logPolylineState('driver_applied');
|
|
|
|
// التحقق من وجود الخطوط المطلوبة
|
|
final polylines = Get.find<MapEngineController>().polyLines;
|
|
_verifyPolylineCondition(
|
|
polylines.any((p) => p.polylineId.value == 'driver_route_solid'),
|
|
'driver_route_solid (amber solid line) exists');
|
|
_verifyPolylineCondition(
|
|
polylines.any((p) => p.polylineId.value.startsWith('passenger_walk_line')),
|
|
'passenger_walk_line (dashed walk line) exists');
|
|
_verifyPolylineCondition(
|
|
polylines.any((p) => p.color == Colors.amber),
|
|
'Amber-colored polyline exists (driver route)');
|
|
_verifyPolylineCondition(
|
|
polylines.any((p) => p.color == Colors.blueGrey),
|
|
'BlueGrey-colored polyline exists (walk dashes)');
|
|
Log.print('');
|
|
}
|
|
|
|
void _simulateDriverEnRoute({required bool isFirst}) {
|
|
String label = isFirst ? '1' : '2';
|
|
Log.print('🔵 [Step $_step] STATE: DRIVER APPROACHING (update #$label)');
|
|
final ride = Get.find<RideLifecycleController>();
|
|
final nearbyDrivers = Get.find<NearbyDriversController>();
|
|
|
|
_driverRouteIndex += 18;
|
|
if (_driverRouteIndex > _routePointCount - 1) {
|
|
_driverRouteIndex = _routePointCount - 1;
|
|
}
|
|
|
|
LatLng newPos = _simulatedRoute[_driverRouteIndex];
|
|
|
|
// تحديث موقع السائق
|
|
nearbyDrivers.driverCarsLocationToPassengerAfterApplied = [newPos];
|
|
|
|
ride.updateDriverMarker(newPos, 50.0);
|
|
ride.updateRemainingRoute(newPos);
|
|
ride.update();
|
|
|
|
_logPolylineState('driver_en_route_$label');
|
|
|
|
// التحقق: driver_route_solid يبقى موجود ويتقلص
|
|
final polylines = Get.find<MapEngineController>().polyLines;
|
|
_verifyPolylineCondition(
|
|
polylines.any((p) => p.polylineId.value == 'driver_route_solid'),
|
|
'driver_route_solid still exists (shrinking)');
|
|
_verifyPolylineCondition(
|
|
polylines.any((p) => p.polylineId.value.startsWith('passenger_walk_line')),
|
|
'passenger_walk_line still exists');
|
|
Log.print('');
|
|
}
|
|
|
|
void _simulateDriverArrivedState() {
|
|
Log.print('🟣 [Step $_step] STATE: DRIVER ARRIVED');
|
|
final ride = Get.find<RideLifecycleController>();
|
|
|
|
ride.currentRideState.value = RideState.driverArrived;
|
|
ride.statusRide = 'Arrived';
|
|
|
|
// محاكاة وصول السائق: تحديد الموقع كآخر نقطة طريق
|
|
LatLng driverPos = _simulatedRoute.first;
|
|
|
|
// تطبيق arrived logic
|
|
ride.polyLines = ride.polyLines
|
|
.where((p) =>
|
|
p.polylineId.value != 'main_route' &&
|
|
p.polylineId.value != 'route_direct' &&
|
|
!p.polylineId.value.startsWith('driver_route'))
|
|
.toSet();
|
|
|
|
// رسم المسار الجديد (من السائق إلى الوجهة النهائية)
|
|
ride.calculateDriverToPassengerRoute(driverPos, ride.myDestination,
|
|
isBeginPhase: true);
|
|
|
|
ride.update();
|
|
|
|
_logPolylineState('driver_arrived');
|
|
|
|
final polylines = Get.find<MapEngineController>().polyLines;
|
|
// في حالة الوصول: يجب أن يظهر main_route (أزرق) بدلاً من driver_route_solid
|
|
_verifyPolylineCondition(
|
|
!polylines.any((p) => p.polylineId.value == 'driver_route_solid'),
|
|
'driver_route_solid (amber) has been cleared');
|
|
_verifyPolylineCondition(
|
|
polylines.any((p) => p.polylineId.value == 'main_route'),
|
|
'main_route (blue) has been drawn to destination');
|
|
Log.print('');
|
|
}
|
|
|
|
void _simulateRideBeginState() {
|
|
Log.print('🔴 [Step $_step] STATE: RIDE IN PROGRESS (Begin)');
|
|
final ride = Get.find<RideLifecycleController>();
|
|
|
|
ride.currentRideState.value = RideState.inProgress;
|
|
ride.statusRide = 'Begin';
|
|
|
|
LatLng driverPos = _simulatedRoute.first;
|
|
|
|
// محاكاة processRideBegin: مسح الخطوط القديمة ورسم الخط الجديد
|
|
ride.polyLines = ride.polyLines
|
|
.where((p) =>
|
|
p.polylineId.value != 'main_route' &&
|
|
p.polylineId.value != 'route_direct' &&
|
|
!p.polylineId.value.startsWith('driver_route'))
|
|
.toSet();
|
|
|
|
ride.calculateDriverToPassengerRoute(driverPos, ride.myDestination,
|
|
isBeginPhase: true);
|
|
|
|
ride.rideIsBeginPassengerTimer();
|
|
ride.update();
|
|
|
|
_logPolylineState('ride_begin');
|
|
|
|
final polylines = Get.find<MapEngineController>().polyLines;
|
|
_verifyPolylineCondition(
|
|
polylines.any((p) => p.polylineId.value == 'main_route'),
|
|
'main_route (blue) exists for trip');
|
|
_verifyPolylineCondition(
|
|
!polylines.any((p) => p.polylineId.value.startsWith('driver_route')),
|
|
'No driver_route* polylines remain');
|
|
_verifyPolylineCondition(
|
|
polylines.any((p) => p.color.value == 0xFF2196F3),
|
|
'Blue polyline (main route) is present');
|
|
Log.print('');
|
|
}
|
|
|
|
void _simulateRideInProgress() {
|
|
Log.print('📱 [Step $_step] STATE: CAR MOVING (remaining route update)');
|
|
final ride = Get.find<RideLifecycleController>();
|
|
final nearbyDrivers = Get.find<NearbyDriversController>();
|
|
|
|
_driverRouteIndex += 30;
|
|
if (_driverRouteIndex > _routePointCount - 1) {
|
|
_driverRouteIndex = _routePointCount - 1;
|
|
}
|
|
|
|
LatLng newPos = _simulatedRoute[_driverRouteIndex];
|
|
|
|
nearbyDrivers.driverCarsLocationToPassengerAfterApplied = [newPos];
|
|
|
|
ride.updateDriverMarker(newPos, 70.0);
|
|
ride.updateRemainingRoute(newPos, updateEta: true);
|
|
ride.update();
|
|
|
|
_logPolylineState('ride_in_progress');
|
|
|
|
final polylines = Get.find<MapEngineController>().polyLines;
|
|
_verifyPolylineCondition(
|
|
polylines.any((p) => p.polylineId.value == 'main_route'),
|
|
'main_route (blue) exists with remaining points');
|
|
_verifyPolylineCondition(
|
|
!polylines.any((p) => p.polylineId.value.startsWith('driver_route')),
|
|
'No driver_route* polylines remain');
|
|
Log.print('');
|
|
}
|
|
|
|
void _simulateRideFinishedState() {
|
|
Log.print('🏁 [Step $_step] STATE: RIDE FINISHED');
|
|
final ride = Get.find<RideLifecycleController>();
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
|
|
ride.currentRideState.value = RideState.finished;
|
|
ride.statusRide = 'Finished';
|
|
ride.isSearchingWindow = false;
|
|
|
|
ride.stopAllTimers();
|
|
mapEngine.clearPolyline();
|
|
mapEngine.markers = {};
|
|
ride.update();
|
|
|
|
_logPolylineState('ride_finished');
|
|
|
|
_verifyPolylineCondition(
|
|
Get.find<MapEngineController>().polyLines.isEmpty,
|
|
'All polylines cleared');
|
|
_verifyPolylineCondition(
|
|
Get.find<MapEngineController>().markers.isEmpty,
|
|
'All markers removed');
|
|
Log.print('');
|
|
}
|
|
|
|
// Simulate a single WebSocket location update event
|
|
void simulateSocketLocationUpdate(LatLng position, double heading) {
|
|
final ride = Get.find<RideLifecycleController>();
|
|
final nearbyDrivers = Get.find<NearbyDriversController>();
|
|
final mapSocket = Get.find<MapSocketController>();
|
|
|
|
Log.print('📡 Simulating socket location update: '
|
|
'(${position.latitude.toStringAsFixed(5)}, ${position.longitude.toStringAsFixed(5)})');
|
|
|
|
nearbyDrivers.driverCarsLocationToPassengerAfterApplied = [position];
|
|
ride.driverCarsLocationToPassengerAfterApplied = [position];
|
|
|
|
ride.checkAndRecalculateIfDeviated(
|
|
position,
|
|
heading: heading,
|
|
speed: 30.0,
|
|
);
|
|
|
|
final mapEngine = Get.find<MapEngineController>();
|
|
if (mapEngine.mapController != null) {
|
|
mapEngine.mapController!
|
|
.animateCamera(CameraUpdate.newLatLngZoom(position, 16.5));
|
|
}
|
|
|
|
ride.updateDriverMarker(position, heading);
|
|
ride.updateRemainingRoute(position);
|
|
ride.update();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
_simulationTimer?.cancel();
|
|
super.onClose();
|
|
}
|
|
}
|