919 lines
32 KiB
Markdown
919 lines
32 KiB
Markdown
# Intaleq Driver App — Complete Ride Lifecycle Analysis Report
|
|
|
|
## Table of Contents
|
|
|
|
1. [System Architecture Overview](#1-system-architecture-overview)
|
|
2. [Ride State Machine](#2-ride-state-machine)
|
|
3. [Phase 1: Ride Request Ingress (Socket → Order Request)](#3-phase-1-ride-request-ingress)
|
|
4. [Phase 2: Accept Order & Navigation to Pickup](#4-phase-2-accept-order--navigation-to-pickup)
|
|
5. [Phase 3: Arrival & Begin Ride](#5-phase-3-arrival--begin-ride)
|
|
6. [Phase 4: In-Ride Navigation & Polyline System](#6-phase-4-in-ride-navigation--polyline-system)
|
|
7. [Phase 5: Finish Ride & Payment](#7-phase-5-finish-ride--payment)
|
|
8. [Phase 6: Post-Ride (Rating, Review)](#8-phase-6-post-ride-rating-review)
|
|
9. [Pricing Engine](#9-pricing-engine)
|
|
10. [Socket.IO Communication](#10-socketio-communication)
|
|
11. [HTTP Backend API Endpoints](#11-http-backend-api-endpoints)
|
|
12. [Polyline Engine Deep Dive](#12-polyline-engine-deep-dive)
|
|
13. [Location Tracking System](#13-location-tracking-system)
|
|
14. [Voice Call Signaling](#14-voice-call-signaling)
|
|
15. [Key Architectural Patterns & Fixes](#15-key-architectural-patterns--fixes)
|
|
16. [Data Flow Diagrams](#16-data-flow-diagrams)
|
|
|
|
---
|
|
|
|
## 1. System Architecture Overview
|
|
|
|
### High-Level Component Map
|
|
|
|
```mermaid
|
|
graph TB
|
|
subgraph "Driver App sefer_driver"
|
|
A[LocationController] -->|Socket.IO| B[Location Server]
|
|
C[OrderRequestController] -->|HTTP| D[Ride Server]
|
|
E[MapDriverController] -->|HTTP| D
|
|
F[NavigationController] -->|HTTP| G[Map SaaS Server]
|
|
H[SignalingService] -->|WebSocket| I[Call Server]
|
|
J[WalletController] -->|HTTP| K[Payment Server]
|
|
end
|
|
|
|
subgraph "Passenger App Intaleq"
|
|
L[RideLifecycleController] -->|Polling/HTTP| D
|
|
M[MapEngineController] -->|HTTP| G
|
|
end
|
|
|
|
B -->|Socket.IO Events| A
|
|
B <-->|update_location| A
|
|
D <-->|Ride CRUD| E
|
|
D <-->|Ride CRUD| L
|
|
|
|
subgraph "Backend Servers"
|
|
B[Location Server: location.intaleq.xyz]
|
|
D[Ride Server: rides.intaleq.xyz]
|
|
G[Map SaaS: map-saas.intaleqapp.com]
|
|
I[Call Server: calls.intaleqapp.com]
|
|
K[Payment Server: walletintaleq.intaleq.xyz]
|
|
end
|
|
|
|
style A fill:#4a90d9,color:#fff
|
|
style C fill:#4a90d9,color:#fff
|
|
style E fill:#4a90d9,color:#fff
|
|
style F fill:#4a90d9,color:#fff
|
|
style L fill:#e67e22,color:#fff
|
|
style M fill:#e67e22,color:#fff
|
|
```
|
|
|
|
### Server Infrastructure
|
|
|
|
| Server | Base URL | Purpose |
|
|
|--------|----------|---------|
|
|
| API Server | `https://api.intaleq.xyz/intaleq_v3` | Auth, CRUD operations, ride management |
|
|
| Ride Server | `https://rides.intaleq.xyz/intaleq/ride` | Ride-specific CRUD |
|
|
| Location Server | `https://location.intaleq.xyz` | Socket.IO real-time location, batch uploads, behavior recording |
|
|
| Map SaaS | `https://map-saas.intaleqapp.com/api/maps/route` | Route/polyline generation |
|
|
| Payment Server | `https://walletintaleq.intaleq.xyz/v1/main` | Wallet management, payment processing |
|
|
| Call Server | `wss://calls.intaleqapp.com/ws` | WebRTC signaling for voice/video calls |
|
|
|
|
### Key Technologies
|
|
|
|
- **State Management**: GetX (`GetxController`)
|
|
- **Real-time**: Socket.IO (`socket_io_client: 1.0.2`) at `https://location.intaleq.xyz`
|
|
- **Map**: Custom `intaleq_maps` package (local path: `../map-saas/packages/flutter-sdk/`)
|
|
- **Routing API**: OSRM-compatible response format via SaaS
|
|
- **Navigation**: Step-by-step with TTS (`flutter_tts: ^4.0.2`)
|
|
- **Background**: `flutter_background_service: ^5.1.0`, `flutter_overlay_window: ^0.5.0`
|
|
|
|
---
|
|
|
|
## 2. Ride State Machine
|
|
|
|
The driver app uses an implicit state machine managed via a master `status` variable in [`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart).
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> NoRide: App start / Return to home
|
|
NoRide --> Searching: Socket new_ride_request received
|
|
Searching --> DriverApplied: Driver taps Accept Order
|
|
DriverApplied --> Searching: Ride taken by another driver
|
|
DriverApplied --> DriverArrived: Driver arrives at pickup point
|
|
DriverArrived --> InProgress: Driver taps Start Ride / Begin
|
|
InProgress --> Finished: Driver taps Finish Ride
|
|
Finished --> PreCheckReview: Driver begins review process
|
|
PreCheckReview --> NoRide: Review complete / Rate passenger
|
|
Finished --> NoRide: Skip review
|
|
|
|
note right of NoRide: Location streaming active, listening for orders
|
|
note right of Searching: OrderRequestPage shown, route calculation
|
|
note right of DriverApplied: Navigation to passenger pickup point
|
|
note right of DriverArrived: Waiting timer counting, passenger notified
|
|
note right of InProgress: Navigation to destination, pricing timer active
|
|
```
|
|
|
|
**State Management Pattern**: The status is stored as a string variable (`status`) and checked throughout with switch/if blocks. Additionally, a `Box` (GetStorage) is used for persistence across app restarts. Key fields in Box include `box.read('status')`, `box.read('ride_id')`, `box.read('tokenPassenger')`.
|
|
|
|
---
|
|
|
|
## 3. Phase 1: Ride Request Ingress
|
|
|
|
### Flow Diagram
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant S as Location Server Socket.IO
|
|
participant LC as LocationController
|
|
participant ORC as OrderRequestController
|
|
participant UI as OrderRequestPage
|
|
|
|
S->>LC: new_ride_request event
|
|
Note over LC: Data format: List or Map
|
|
LC->>LC: handleIncomingOrder()
|
|
Note over LC: Validate key '16' exists
|
|
LC->>LC: Extract DriverList structure
|
|
LC->>LC: Sort by distance
|
|
LC->>UI: Get.to OrderRequestPage
|
|
UI->>ORC: initState -> _initializeData()
|
|
ORC->>ORC: Parse List or Map format
|
|
ORC->>ORC: _calculateFullJourney()
|
|
ORC->>MapSaaS: getRoute for pickup + trip
|
|
MapSaaS-->>ORC: Return polylines + distance + duration
|
|
ORC->>UI: Update UI with routes
|
|
```
|
|
|
|
### Socket Event: `new_ride_request`
|
|
|
|
Received in [`location_controller.dart`](lib/controller/functions/location_controller.dart) (lines ~230-323).
|
|
|
|
**Data Payload** (supports **two formats**):
|
|
|
|
**Format A — List** (original):
|
|
```dart
|
|
[
|
|
"lat,lng", // [0] start coordinates
|
|
"lat,lng", // [1] end coordinates
|
|
"price", // [2]
|
|
"duration_sec", // [3] trip duration
|
|
"total_sec", // [4] total duration
|
|
"distance_m", // [5] trip distance
|
|
"unknown", // [6]
|
|
"passenger_id", // [7]
|
|
"customer_name", // [8]
|
|
"customer_token", // [9]
|
|
"phone", // [10]
|
|
"unknown", // [11]
|
|
"dist_to_driver_m", // [12] distance to driver
|
|
"unknown", // [13]
|
|
"unknown", // [14]
|
|
"duration_to_driver_sec", // [15]
|
|
"ride_id", // [16] - Validation key
|
|
...
|
|
"start_address", // [29]
|
|
"end_address", // [30]
|
|
"ride_type", // [31] Speed/Comfort/Lady/etc
|
|
"passenger_rate", // [33]
|
|
]
|
|
```
|
|
|
|
**Format B — Map** (newer):
|
|
```dart
|
|
{
|
|
'myListString': { ... },
|
|
'DriverList': [
|
|
{ lat, lng, price, duration, ... },
|
|
...
|
|
]
|
|
}
|
|
```
|
|
|
|
### Validation Gate ([`location_controller.dart`](lib/controller/functions/location_controller.dart), lines 327-399)
|
|
|
|
```dart
|
|
void handleIncomingOrder(dynamic data) {
|
|
// Check if data has key '16' (ride_id) — if so, treat as List format
|
|
// Otherwise, extract from myListString/DriverList
|
|
// Convert all to sorted DriverList format
|
|
// Navigate to OrderRequestPage
|
|
}
|
|
```
|
|
|
|
### Ride Taken Prevention ([`order_request_controller.dart`](lib/controller/home/captin/order_request_controller.dart), lines 624-649)
|
|
|
|
A dedicated socket listener prevents double-accept:
|
|
|
|
```dart
|
|
socket.on('ride_taken', (data) {
|
|
// Check if ride_id matches current displayed order
|
|
// If so, show "ride taken" dialogue and return to home
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Phase 2: Accept Order & Navigation to Pickup
|
|
|
|
### Accept Flow ([`order_request_controller.dart`](lib/controller/home/captin/order_request_controller.dart), lines 672-783)
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant D as Driver
|
|
participant ORC as OrderRequestController
|
|
participant API as Ride Server
|
|
participant Box as GetStorage
|
|
participant PDP as PassengerLocationMapPage
|
|
participant MDC as MapDriverController
|
|
|
|
D->>ORC: Tap Accept
|
|
ORC->>API: GET acceptRide.php?ride_id=X&driver_id=Y
|
|
API-->>ORC: Success response
|
|
ORC->>Box: Write rideArgs: ride_id, status=applied, tokenPassenger, carType, kazan, etc.
|
|
ORC->>PDP: Get.to(PassengerLocationMapPage, arguments: rideArgs)
|
|
PDP->>MDC: argumentLoading() parses rideArgs
|
|
MDC->>Box: Persist all ride data
|
|
MDC->>MapSaaS: getRoute(for pickup location)
|
|
MapSaaS-->>MDC: Polyline + steps for driver->passenger route
|
|
MDC->>MDC: Draw polyline on map
|
|
MDC->>MDC: Start GPS tracking, step-by-step navigation
|
|
```
|
|
|
|
### Ride Arguments Map ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 2212-2279)
|
|
|
|
The complete `rideArgs` map written to Box:
|
|
|
|
| Field | Source | Description |
|
|
|-------|--------|-------------|
|
|
| `passenger_lat`, `passenger_lng` | From order data | Passenger pickup location |
|
|
| `passenger_destination_lat`, `passenger_destination_lng` | From order data | Trip destination |
|
|
| `ride_id` | From order data | Unique ride identifier |
|
|
| `tokenPassenger` | From order data | Passenger auth token |
|
|
| `carType` | Driver profile | Speed/Fixed/Comfort/Lady/Electric/Van/Delivery |
|
|
| `kazan` | From order data | Commission percentage |
|
|
| `status` | Set to `applied` | Current ride state |
|
|
| `price` | From order data | Trip price |
|
|
| `customerName` | From order data | Passenger name |
|
|
| `tripDistance` | From order data | Total trip distance |
|
|
| `tripDurationMin` | Calculated | Trip duration in minutes |
|
|
|
|
---
|
|
|
|
## 5. Phase 3: Arrival & Begin Ride
|
|
|
|
### Arrival Detection
|
|
|
|
The driver's GPS is continuously monitored. When the driver reaches within ~150m of the passenger, the "Arrived" UI state activates.
|
|
|
|
### Begin Ride ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 838-952)
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant D as Driver
|
|
participant MDC as MapDriverController
|
|
participant API as Ride Server
|
|
participant Box as GetStorage
|
|
participant PSGR as Passenger App
|
|
|
|
D->>MDC: Tap "Begin Ride" / startRideFromDriver()
|
|
MDC->>MDC: Validate distance from passenger < 150m
|
|
MDC->>API: GET start_ride.php?ride_id=X&driver_id=Y&passenger_id=Z×tamp=...
|
|
API-->>MDC: Success
|
|
MDC->>Box: Update status to 'begin'
|
|
MDC->>PSGR: Socket emit update_location with status=begin
|
|
MDC->>MapSaaS: getRoute(for destination, from current location)
|
|
MapSaaS-->>MDC: New polyline from current pos -> destination
|
|
MDC->>MDC: Redraw polyline on map
|
|
MDC->>MDC: Start pricing timer (rideIsBeginPassengerTimer)
|
|
MDC->>MDC: Start step-by-step navigation
|
|
```
|
|
|
|
**Key Validation**: Distance from passenger must be <150m before ride can begin. This prevents starting the ride prematurely.
|
|
|
|
---
|
|
|
|
## 6. Phase 4: In-Ride Navigation & Polyline System
|
|
|
|
### Full Navigation Stack
|
|
|
|
```mermaid
|
|
graph TB
|
|
subgraph "Navigation System"
|
|
GPS[Geolocator Stream] --> Filter[Jitter Filter <2m]
|
|
Filter --> NavCtrl[NavigationController]
|
|
Filter --> MDC[MapDriverController]
|
|
|
|
NavCtrl --> RouteMatch[RouteMatcherWorker Isolate]
|
|
NavCtrl --> PolylineDecode[DecodePolylineIsolate]
|
|
|
|
RouteMatch --> SmartSnap[Smart Sliding Window Snapping]
|
|
SmartSnap --> SplitPoly[Split Traveled vs Upcoming]
|
|
|
|
PolylineDecode --> RouteCoords[Full Route Coordinates]
|
|
RouteCoords --> StepNav[Step-by-Step Instructions]
|
|
StepNav --> TTS[Flutter TTS Voice]
|
|
|
|
SplitPoly --> GreyPoly[Grey Traveled Polyline]
|
|
SplitPoly --> ColorPoly[Colored Upcoming Polyline]
|
|
end
|
|
|
|
GPS --> Camera[Adaptive Camera]
|
|
Camera --> Zoom[Speed-Based Zoom: 15-19]
|
|
Camera --> Tilt[Speed-Based Tilt: 0-55deg]
|
|
|
|
MDC --> Pricing8[Pricing Timer: 1s interval]
|
|
```
|
|
|
|
### Polyline Rendering (Dual Polyline System)
|
|
|
|
The driver app renders **two polylines simultaneously**:
|
|
|
|
1. **Upcoming Route** (`polyline` in `MapDriverController`): Colored polyline from the driver's current snapped position to the destination
|
|
2. **Traveled Route** (`polyline2` in `MapDriverController`): Grey polyline showing the path already driven
|
|
|
|
### Smart Sliding Window Snapping ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 2563-2622)
|
|
|
|
```dart
|
|
void _updateTraveledPolylineSmart(LatLng currentLocation) {
|
|
// 1. Define sliding window of 60 points around last known index
|
|
// 2. Find closest point on the original route polyline within that window
|
|
// 3. Split the original route at the matched index:
|
|
// - Points [0..matchedIndex] -> traveled (grey polyline)
|
|
// - Points [matchedIndex..end] -> upcoming (colored polyline)
|
|
// 4. Update map with both polylines
|
|
}
|
|
```
|
|
|
|
### Isolate-Based Route Matching ([`route_matcher_worker.dart`](lib/controller/home/navigation/route_matcher_worker.dart))
|
|
|
|
The heavy computation of finding the closest point on the route polyline is offloaded to a **dedicated isolate**:
|
|
|
|
```
|
|
Messages: init, match, dispose
|
|
Response: matchResult { index, lat, lng, dist }
|
|
```
|
|
|
|
- Uses **Float64List** for zero-copy memory sharing
|
|
- Sliding window search (default: 120 points, configurable)
|
|
- **Haversine distance** for accurate meter-level distance calculation
|
|
- **Projection onto line segments** for sub-point accuracy
|
|
|
|
### Step-by-Step Navigation ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 211-273)
|
|
|
|
```dart
|
|
void startListeningStepNavigation() {
|
|
// 1. Subscribe to Geolocator stream with jitter filter (<2m ignore)
|
|
// 2. Smooth animation via AnimationController
|
|
// 3. Snap to route (update traveled polyline)
|
|
// 4. Check proximity to next step waypoint
|
|
// 5. If near next waypoint:
|
|
// - Speak next instruction via TTS
|
|
// - Update currentStepIndex
|
|
// - Show next instruction distance
|
|
// 6. Update camera position (adaptive zoom/tilt based on speed)
|
|
}
|
|
```
|
|
|
|
**Adaptive Camera**:
|
|
|
|
| Speed | Zoom | Tilt |
|
|
|-------|------|------|
|
|
| < 15 km/h | 19 | 0° |
|
|
| < 40 km/h | 18 | 40° |
|
|
| < 70 km/h | 17 | 55° |
|
|
| < 100 km/h | 16 | 55° |
|
|
| 100+ km/h | 15 | 55° |
|
|
|
|
---
|
|
|
|
## 7. Phase 5: Finish Ride & Payment
|
|
|
|
### Finish Ride Flow ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 1236-1354)
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant D as Driver
|
|
participant MDC as MapDriverController
|
|
participant API as Ride Server
|
|
participant PayAPI as Payment Server
|
|
participant Box as GetStorage
|
|
|
|
D->>MDC: Tap "Finish Ride"
|
|
Note over MDC: Validate trip distance anti-fraud
|
|
par Parallel Execution
|
|
MDC->>API: finish_ride_updates.php
|
|
MDC->>PayAPI: process_ride_payments.php
|
|
end
|
|
API-->>MDC: Ride status updated to finished
|
|
PayAPI-->>MDC: Payment processed
|
|
MDC->>MDC: Stop pricing timer
|
|
MDC->>MDC: Stop navigation / polyline
|
|
MDC->>Box: Update status to 'finished'
|
|
MDC->>Box: Save ride price to payment_summary
|
|
MDC->>MDC: Clear polyline, markers, camera
|
|
MDC->>MDC: Show ride summary / review UI
|
|
```
|
|
|
|
### Anti-Fraud Distance Validation ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines ~1290-1330)
|
|
|
|
```dart
|
|
_validateTripDistance() {
|
|
// Actual traveled distance must be >= 1/5 of expected trip distance
|
|
// If not, auto-reject as potential fraud
|
|
// This prevents drivers from starting and immediately finishing rides
|
|
}
|
|
```
|
|
|
|
### Payment Processing ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines ~1330-1354)
|
|
|
|
- Payment is processed in parallel with ride finish
|
|
- Payment server generates secure tokens for wallet transactions
|
|
- Supports: Stripe, PayMob, MTN, Syriatel, eCash, ShamCash
|
|
|
|
---
|
|
|
|
## 8. Phase 6: Post-Ride (Rating, Review)
|
|
|
|
After finishing, the driver enters the `preCheckReview` state:
|
|
|
|
1. **Rating**: Rate the passenger (`addRateToPassenger.php`)
|
|
2. **Review Screen**: `ride_calculate_driver.dart` shows:
|
|
- Trip price breakdown
|
|
- Commission (kazan%)
|
|
- Net earnings
|
|
- Distance/time summary
|
|
3. **Return to Home**: Status reset to `noRide`, ready for next order
|
|
|
|
---
|
|
|
|
## 9. Pricing Engine
|
|
|
|
### Core Pricing Timer ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 1481-1570)
|
|
|
|
```dart
|
|
void rideIsBeginPassengerTimer() {
|
|
Timer.periodic(Duration(seconds: 1), (timer) {
|
|
// 1. Calculate distance delta since last tick
|
|
// 2. Fetch current car type pricing config
|
|
// 3. Apply time-of-day multiplier
|
|
// 4. Apply distance-based thresholds
|
|
// 5. Apply airport surcharge if applicable
|
|
// 6. Calculate commission (kazan%)
|
|
// 7. Update live price display
|
|
});
|
|
}
|
|
```
|
|
|
|
### Price Calculation Formula ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 1572-1662)
|
|
|
|
```dart
|
|
double _calculateCurrentPrice() {
|
|
// Base price depends on carType:
|
|
// Speed, Fixed, Comfort, Lady, Electric, Van, Delivery
|
|
//
|
|
// Time-of-day bands:
|
|
// nature (normal), late (evening/night), heavy (peak)
|
|
//
|
|
// Distance thresholds:
|
|
// 25km, 35km, 40km - different pricing tiers
|
|
//
|
|
// Commission: kazan% taken by platform
|
|
//
|
|
// Airport contexts: additional surcharge
|
|
//
|
|
// Formula (simplified):
|
|
// basePrice = carType.baseRate * timeMultiplier
|
|
// distancePrice = distance * carType.perKmRate
|
|
// if distance > 25km: apply longTripMultiplier
|
|
// if airport: add airportSurcharge
|
|
// finalPrice = (basePrice + distancePrice) * (1 + kazan/100)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Socket.IO Communication
|
|
|
|
### Connection Setup ([`location_controller.dart`](lib/controller/functions/location_controller.dart), lines 183-228)
|
|
|
|
```dart
|
|
void initSocket() {
|
|
socket = io(
|
|
'https://location.intaleq.xyz',
|
|
<String, dynamic>{
|
|
'transports': ['websocket'], // WebSocket-only transport
|
|
'query': {
|
|
'driver_id': driverId,
|
|
'token': authToken,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
```
|
|
|
|
### Events Summary
|
|
|
|
| Event | Direction | Frequency | Purpose |
|
|
|-------|-----------|-----------|---------|
|
|
| `new_ride_request` | Server → Driver | On demand | Incoming ride request |
|
|
| `ride_taken` | Server → Driver | On demand | Ride accepted by another driver |
|
|
| `cancel_ride` | Server → Driver | On demand | Passenger cancelled the ride |
|
|
| `update_location` | Driver → Server | Every 5-10s | Driver location broadcast |
|
|
| `connect` | Bidirectional | On connect | Socket established |
|
|
| `disconnect` | Bidirectional | On disconnect | Socket lost |
|
|
| Heartbeat (ping/pong) | Bidirectional | Every 25s | Keep-alive |
|
|
|
|
### Cancel Ride Handler ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 339-410)
|
|
|
|
```dart
|
|
void processRideCancelledByPassenger() {
|
|
// Gatekeeper: stop all timers immediately (Fix 2)
|
|
// Stop: pricingTimer, waitingTimer, navigation
|
|
// Show cancellation dialog
|
|
// Clear ride data from Box
|
|
// Reset status to noRide
|
|
// Return to HomeCaptain
|
|
}
|
|
```
|
|
|
|
### Location Upload ([`location_controller.dart`](lib/controller/functions/location_controller.dart), lines 420-453)
|
|
|
|
```dart
|
|
void emitLocationToSocket() {
|
|
Map<String, dynamic> data = {
|
|
'driver_id': driverId,
|
|
'lat': currentLat,
|
|
'lng': currentLng,
|
|
'heading': heading,
|
|
'speed': speed,
|
|
'status': currentStatus,
|
|
'distance': distance,
|
|
};
|
|
|
|
// If ride active, inject passenger_id and ride_id
|
|
if (rideActive) {
|
|
data['passenger_id'] = passengerId;
|
|
data['ride_id'] = rideId;
|
|
}
|
|
|
|
socket.emit('update_location', data);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 11. HTTP Backend API Endpoints
|
|
|
|
### Ride Lifecycle Endpoints
|
|
|
|
| Endpoint | Method | Phase | Purpose |
|
|
|----------|--------|-------|---------|
|
|
| [`acceptRide.php`](lib/constant/links.dart) | GET | Accept | Driver accepts ride offer |
|
|
| [`start_ride.php`](lib/constant/links.dart) | GET | Begin | Start the ride trip |
|
|
| [`finish_ride_updates.php`](lib/constant/links.dart) | GET | Finish | Complete ride (parallel) |
|
|
| [`process_ride_payments.php`](lib/constant/links.dart) | GET | Finish | Process payment (parallel) |
|
|
| [`cancelRide/add.php`](lib/constant/links.dart) | POST | Any | Log cancellation |
|
|
| [`addCancelTripFromDriverAfterApplied.php`](lib/constant/links.dart) | POST | Applied | Driver cancels after accepting |
|
|
|
|
### Ride Data Endpoints
|
|
|
|
| Endpoint | Method | Purpose |
|
|
|----------|--------|---------|
|
|
| [`rides/add.php`](lib/constant/links.dart) | POST | Create ride record |
|
|
| [`rides/get.php`](lib/constant/links.dart) | GET | Retrieve ride details |
|
|
| [`rides/update.php`](lib/constant/links.dart) | POST | Update ride status |
|
|
| [`rides/delete.php`](lib/constant/links.dart) | DELETE | Remove ride record |
|
|
| [`getRideStatus.php`](lib/constant/links.dart) | GET | Check ride status |
|
|
| [`getRideOrderID.php`](lib/constant/links.dart) | GET | Get order ID for ride |
|
|
| [`updateRideAndCheckIfApplied.php`](lib/constant/links.dart) | POST | Atomic status check + update |
|
|
| [`getRideStatusFromStartApp.php`](lib/constant/links.dart) | GET | Recover ride status on app start |
|
|
|
|
### Location Endpoints
|
|
|
|
| Endpoint | Method | Purpose |
|
|
|----------|--------|---------|
|
|
| [`add_batch.php`](lib/constant/links.dart) | POST | Batch location upload |
|
|
| [`save_behavior.php`](lib/constant/links.dart) | POST | Record driving behavior |
|
|
| [`get.php`](lib/constant/links.dart) | GET | Get car locations |
|
|
| [`getRidesDriverByDay.php`](lib/constant/links.dart) | GET | Daily ride history |
|
|
| [`getTotalDriverDuration.php`](lib/constant/links.dart) | GET | Total driving time |
|
|
|
|
### Map SaaS Endpoint
|
|
|
|
| Endpoint | Method | Purpose |
|
|
|----------|--------|---------|
|
|
| `https://map-saas.intaleqapp.com/api/maps/route` | POST | Route calculation with polyline |
|
|
| `https://map-saas.intaleqapp.com/api/geocoding/places` | POST | Place search/geocoding |
|
|
|
|
**Route API Response Format** (OSRM-compatible):
|
|
```json
|
|
{
|
|
"routes": [{
|
|
"geometry": {
|
|
"coordinates": [[lng, lat], ...],
|
|
"points": "encoded_polyline_string"
|
|
},
|
|
"legs": [{
|
|
"steps": [
|
|
{
|
|
"maneuver": { "location": [lng, lat], "modifier": "straight" },
|
|
"instruction": "Continue straight on Main St",
|
|
"distance": 123.4,
|
|
"duration": 45.6
|
|
}
|
|
],
|
|
"distance": 5000.0,
|
|
"duration": 600.0
|
|
}],
|
|
"distance": 5000.0,
|
|
"duration": 600.0
|
|
}]
|
|
}
|
|
```
|
|
|
|
### Payment Endpoints
|
|
|
|
| Endpoint | Method | Purpose |
|
|
|----------|--------|---------|
|
|
| [`payment/add.php`](lib/constant/links.dart) | POST | Record payment |
|
|
| [`payment/get.php`](lib/constant/links.dart) | GET | Get today's payments |
|
|
| [`getAllPaymentFromRide.php`](lib/constant/links.dart) | GET | All payments for a ride |
|
|
| [`addPaymentTokenDriver.php`](lib/constant/links.dart) | POST | Generate payment token |
|
|
| [`payWithPayMobCardDriver.php`](lib/constant/links.dart) | POST | PayMob card payment |
|
|
| [`payWithWallet.php`](lib/constant/links.dart) | POST | Wallet payment |
|
|
| [`payWithMTNConfirm.php`](lib/constant/links.dart) | POST | MTN payment confirmation |
|
|
| [`payWithSyriatelConfirm.php`](lib/constant/links.dart) | POST | Syriatel payment confirmation |
|
|
|
|
---
|
|
|
|
## 12. Polyline Engine Deep Dive
|
|
|
|
### Polyline Decoding ([`decode_polyline_isolate.dart`](lib/controller/home/navigation/decode_polyline_isolate.dart))
|
|
|
|
Standard Google Encoded Polyline Format v5:
|
|
```dart
|
|
List<LatLng> decodePolylineIsolate(String encoded) {
|
|
// Standard algorithm:
|
|
// 1. Read 5-bit chunks from charCode - 63
|
|
// 2. Reconstruct signed value using ZigZag decoding
|
|
// 3. Accumulate lat/lng and divide by 1E5
|
|
// 4. Add to points list
|
|
}
|
|
```
|
|
|
|
Runs in **separate isolate** via `compute(PolylineUtils.decode, ...)` to avoid jank.
|
|
|
|
### Route Fetching ([`order_request_controller.dart`](lib/controller/home/captin/order_request_controller.dart), lines 341-405)
|
|
|
|
```dart
|
|
Future<Map<String, dynamic>> _fetchRouteData(LatLng from, LatLng to) async {
|
|
// 1. POST to AppLink.mapSaasRoute with coordinates
|
|
// 2. Parse OSRM-style response
|
|
// 3. Decode polyline via compute(_decodePolyline, ...)
|
|
// 4. Return {distance, duration, polyline, steps}
|
|
}
|
|
```
|
|
|
|
### Dual Polyline System (Visual)
|
|
|
|
```
|
|
Before traveling:
|
|
[Passenger] ============================================> [Destination]
|
|
(Full route in blue/colored)
|
|
|
|
During travel:
|
|
[Driver] ~~~~~~~~~~> [Current Position] ===============> [Destination]
|
|
(Grey traveled) (Colored upcoming)
|
|
```
|
|
|
|
### Smart Snapping Visualization
|
|
|
|
```
|
|
Original route points:
|
|
P0 --- P1 --- P2 --- P3 --- P4 --- P5 --- P6 --- P7 --- P8
|
|
|
|
Driver at position X (near P2-P3 segment):
|
|
Sliding window: [P0---P1---P2---P3---P4---P5] (window=60)
|
|
Closest projection: on segment P2-P3 at point C
|
|
|
|
Result:
|
|
Traveled: P0---P1---P2---C (grey)
|
|
Upcoming: C---P3---P4---P5---P6---P7---P8 (colored)
|
|
```
|
|
|
|
---
|
|
|
|
## 13. Location Tracking System
|
|
|
|
### Dual-Interval Architecture ([`location_controller.dart`](lib/controller/functions/location_controller.dart))
|
|
|
|
```mermaid
|
|
graph LR
|
|
subgraph "Normal Mode"
|
|
GPS3[GPS every 5s] --> Record3[Record to buffer every 3s]
|
|
Record3 --> Upload2[Upload batch every 2min]
|
|
Upload2 --> Socket[Socket.IO emit]
|
|
Upload2 --> HTTP[HTTP batch upload]
|
|
end
|
|
|
|
subgraph "Power Save Mode"
|
|
GPS10[GPS every 10s] --> Record10[Record to buffer every 10s]
|
|
Record10 --> Upload5[Upload batch every 5min]
|
|
Upload5 --> Socket
|
|
Upload5 --> HTTP
|
|
end
|
|
```
|
|
|
|
### Behavior Recording
|
|
|
|
In addition to location, the system records **driving behavior**:
|
|
- Acceleration/deceleration events
|
|
- Speed threshold violations
|
|
- Uploaded to `save_behavior.php`
|
|
|
|
### Battery Optimization
|
|
|
|
- **Wakelock**: Maintained during active rides (`wakelock_plus`)
|
|
- **Background Service**: `flutter_background_service` keeps location streaming alive
|
|
- **Overlay Window**: `flutter_overlay_window` shows driver status even when app is backgrounded
|
|
|
|
---
|
|
|
|
## 14. Voice Call Signaling
|
|
|
|
### WebSocket Signaling ([`signaling_service.dart`](lib/services/signaling_service.dart))
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant D as Driver App
|
|
participant WS as WebSocket wss://calls.intaleqapp.com/ws
|
|
participant P as Passenger App
|
|
|
|
D->>WS: authenticate { session_id, user_id }
|
|
WS-->>D: authenticated
|
|
P->>WS: authenticate { session_id, user_id }
|
|
WS-->>P: authenticated
|
|
|
|
P->>WS: call_request { target_user_id }
|
|
WS->>D: participant_joined { user_id }
|
|
|
|
D->>WS: offer { sdp }
|
|
WS->>P: offer { sdp }
|
|
P->>WS: answer { sdp }
|
|
WS->>D: answer { sdp }
|
|
|
|
D->>WS: ice_candidate { candidate }
|
|
WS->>P: ice_candidate { candidate }
|
|
P->>WS: ice_candidate { candidate }
|
|
WS->>D: ice_candidate { candidate }
|
|
|
|
Note over D,P: WebRTC Peer Connection established
|
|
|
|
D->>WS: call_ended
|
|
WS->>P: call_ended
|
|
```
|
|
|
|
---
|
|
|
|
## 15. Key Architectural Patterns & Fixes
|
|
|
|
### Documented Fixes
|
|
|
|
| Fix | Issue | Solution |
|
|
|-----|-------|----------|
|
|
| Fix 1 | Two competing GPS listeners | Merged into single stream subscription |
|
|
| Fix 2 | Timer leak on cancel | Stop ALL timers immediately at gatekeeper |
|
|
| Fix 3 | Polyline decode blocking UI | Moved to `compute()` isolate |
|
|
| Fix 4 | Wrong distance unit in validation | Fixed `_validateTripDistance()` unit conversion |
|
|
| Fix 5 | `Future.delayed` without `await` | Added proper `await` |
|
|
| Fix 6 | Redundant heartbeat during stream | Skip heartbeat if location stream active |
|
|
|
|
### Architecture Patterns
|
|
|
|
1. **Controller-per-Screen**: Each screen has its own `GetxController`
|
|
2. **Box Persistence**: GetStorage used for ride state recovery across app restarts
|
|
3. **Socket Decoupling**: Location data flows through Socket.IO, but ride CRUD uses HTTP REST
|
|
4. **Isolate Offloading**: Heavy polyline operations run in isolates via `compute()`
|
|
5. **Parallel Execution**: Finish ride + payment run concurrently via `Future.wait`
|
|
6. **Dual Data Format Support**: Socket data arrives as either List or Map — both handled
|
|
7. **Gatekeeper Pattern**: Cancellation handler stops all active processes at a single entry point
|
|
|
|
---
|
|
|
|
## 16. Data Flow Diagrams
|
|
|
|
### Complete Ride Lifecycle Data Flow
|
|
|
|
```mermaid
|
|
graph TB
|
|
subgraph "Pre-Ride"
|
|
A[Socket: new_ride_request] --> B[Parse List/Map]
|
|
B --> C[OrderRequestPage]
|
|
C --> D[Driver Accepts]
|
|
D --> E[HTTP: acceptRide.php]
|
|
E --> F[Write rideArgs to Box]
|
|
F --> G[Navigate to Map]
|
|
end
|
|
|
|
subgraph "To-Passenger"
|
|
G --> H[Route: Driver -> Passenger]
|
|
H --> I[Drew colored polyline]
|
|
I --> J[Step Nav + TTS]
|
|
J --> K[GPS updates every 5s]
|
|
K --> L[Socket: update_location]
|
|
L --> M[Smart snap to route]
|
|
end
|
|
|
|
subgraph "At Passenger"
|
|
M --> N[Arrive ~150m]
|
|
N --> O[HTTP: start_ride.php]
|
|
O --> P[Redraw route -> Destination]
|
|
end
|
|
|
|
subgraph "To-Destination"
|
|
P --> Q[Pricing Timer 1s]
|
|
Q --> R[Live price display]
|
|
R --> S[Step Nav + TTS]
|
|
S --> T[Dual polyline: grey + colored]
|
|
T --> U[Socket: update_location with status]
|
|
end
|
|
|
|
subgraph "Finish"
|
|
U --> V[HTTP: finish_ride_updates.php]
|
|
V --> W[HTTP: process_ride_payments.php]
|
|
W --> X[Stop all timers]
|
|
X --> Y[Clear polylines]
|
|
Y --> Z[Show summary / rating]
|
|
Z --> AA[Reset to noRide]
|
|
end
|
|
|
|
style A fill:#e74c3c,color:#fff
|
|
style O fill:#2ecc71,color:#fff
|
|
style V fill:#2ecc71,color:#fff
|
|
style W fill:#2ecc71,color:#fff
|
|
style AA fill:#3498db,color:#fff
|
|
```
|
|
|
|
### Socket Event Flow During Ride
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Driver
|
|
participant LS as Location Server
|
|
participant PSGR as Passenger App
|
|
|
|
Note over Driver: Searching for ride
|
|
LS->>Driver: new_ride_request { data }
|
|
Driver->>LS: update_location { status: searching }
|
|
|
|
Note over Driver: Ride accepted
|
|
Driver->>LS: update_location { status: applied, ride_id }
|
|
|
|
Note over Driver: En route to passenger
|
|
Driver->>LS: update_location { lat, lng, heading, speed, status: goingToPassenger }
|
|
LS->>PSGR: driverLocationUpdate { ... }
|
|
|
|
Note over Driver: Arrived at passenger
|
|
Driver->>LS: update_location { status: arrived }
|
|
|
|
Note over Driver: Ride started
|
|
Driver->>LS: update_location { status: inProgress, ride_id }
|
|
|
|
Note over Driver: En route to destination
|
|
Driver->>LS: update_location { lat, lng, heading, speed, status: inProgress }
|
|
LS->>PSGR: driverLocationUpdate { ... }
|
|
|
|
Note over Driver: Ride finished
|
|
Driver->>LS: update_location { status: finished }
|
|
|
|
Note over Driver: Back to idle
|
|
Driver->>LS: update_location { status: online }
|
|
```
|
|
|
|
### Key Backend Endpoints Used Per Phase
|
|
|
|
| Phase | Endpoint | Purpose |
|
|
|-------|----------|---------|
|
|
| Accept | `acceptRide.php?ride_id=X&driver_id=Y` | Accept ride |
|
|
| Begin | `start_ride.php?ride_id=X&driver_id=Y&passenger_id=Z` | Start trip |
|
|
| In-Ride | `update_location` (Socket) | Location streaming |
|
|
| In-Ride | `updateRideAndCheckIfApplied.php` | Status sync |
|
|
| In-Ride | `getKazanPercent.php` | Commission config |
|
|
| Finish | `finish_ride_updates.php` | Complete ride |
|
|
| Finish | `process_ride_payments.php` | Payment processing |
|
|
| Finish | `addRateToPassenger.php` | Passenger rating |
|
|
| Post-Ride | `getAllPaymentFromRide.php` | Payment summary |
|
|
| Any | `getRideStatusFromStartApp.php` | State recovery on restart |
|
|
|
|
---
|
|
|
|
## Appendix: Key File Reference
|
|
|
|
| File | Lines | Purpose |
|
|
|------|-------|---------|
|
|
| [`lib/controller/home/captin/map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart) | 2644 | Core driver ride lifecycle, navigation, polyline, pricing |
|
|
| [`lib/controller/home/captin/order_request_controller.dart`](lib/controller/home/captin/order_request_controller.dart) | 828 | Ride request handling, accept logic, route display |
|
|
| [`lib/controller/functions/location_controller.dart`](lib/controller/functions/location_controller.dart) | 794 | Socket.IO, location tracking, batch upload, behavior |
|
|
| [`lib/controller/home/navigation/navigation_controller.dart`](lib/controller/home/navigation/navigation_controller.dart) | 1383 | Step-by-step navigation, route matching, alternative routes |
|
|
| [`lib/controller/home/navigation/route_matcher_worker.dart`](lib/controller/home/navigation/route_matcher_worker.dart) | 146 | Isolate-based route matching with sliding window |
|
|
| [`lib/controller/home/navigation/decode_polyline_isolate.dart`](lib/controller/home/navigation/decode_polyline_isolate.dart) | 32 | Polyline decode in isolate |
|
|
| [`lib/models/model/order_data.dart`](lib/models/model/order_data.dart) | 188 | Order data model (List + Map constructors) |
|
|
| [`lib/constant/links.dart`](lib/constant/links.dart) | 424 | All backend API endpoints |
|
|
| [`lib/services/signaling_service.dart`](lib/services/signaling_service.dart) | 112 | WebRTC call signaling |
|
|
| [`lib/services/offline_map_service.dart`](lib/services/offline_map_service.dart) | - | Offline map tile service |
|
|
| [`pubspec.yaml`](pubspec.yaml) | 144 | Dependencies and package config |
|