Trip Lifecycle
Once you accept a trip request, follow this workflow to complete the ride successfully.
Navigate to Pickup
Drive to the passenger’s pickup location using integrated navigation
Arrive at Pickup
Mark yourself as arrived and wait for the passenger
Start Trip
Confirm passenger is in vehicle and start the trip
Navigate to Destination
Follow GPS navigation to the drop-off location
Complete Trip
Mark trip as complete when you reach destination
Collect Payment
Receive cash payment from passenger
Rate Passenger
Provide feedback and rating for the passenger
Active Trip Screen
The active trip screen provides all information and controls you need:
Screen Components
class ConductorActiveTripScreen extends StatefulWidget {
final int conductorId;
final int solicitudId;
final int ? viajeId;
final int clienteId;
// Pickup location
final double origenLat;
final double origenLng;
final String direccionOrigen;
// Drop-off location
final double destinoLat;
final double destinoLng;
final String direccionDestino;
// Passenger info
final String clienteNombre;
final String ? clienteFoto;
final double ? clienteCalificacion;
}
Map View Full-screen map showing:
Your current location
Pickup/destination markers
Optimized route
Turn-by-turn navigation
Navigation Card Real-time navigation guidance:
Next turn instruction
Distance to next turn
Estimated arrival time
Traffic conditions
Passenger Info Passenger details:
Name and photo
Phone number (call button)
Rating history
Special instructions
Action Panel Trip controls:
Arrived button
Start trip button
Complete trip button
Cancel trip option
Phase 1: Navigate to Pickup
Getting Route to Passenger
Future < void > _fetchRouteToClient () async {
if (_currentLocation == null ) return ;
try {
final route = await MapboxService . getRoute (
waypoints : [
_currentLocation ! ,
LatLng (widget.origenLat, widget.origenLng),
],
);
if ( ! mounted) return ;
setState (() {
_routeToClient = route;
_estimatedArrival = DateTime . now (). add (
Duration (seconds : route.duration. toInt ())
);
});
} catch (e) {
debugPrint ( 'Error fetching route: $ e ' );
}
}
Widget _buildNavigationCard () {
final route = _routeToClient;
if (route == null ) return SizedBox . shrink ();
return Container (
margin : EdgeInsets . all ( 16 ),
padding : EdgeInsets . all ( 16 ),
decoration : BoxDecoration (
color : Colors .white,
borderRadius : BorderRadius . circular ( 16 ),
boxShadow : [
BoxShadow (
color : Colors .black. withOpacity ( 0.1 ),
blurRadius : 10 ,
offset : Offset ( 0 , 4 ),
),
],
),
child : Column (
crossAxisAlignment : CrossAxisAlignment .start,
children : [
Row (
children : [
Icon ( Icons .navigation, color : AppColors .primary),
SizedBox (width : 8 ),
Text (
'Navigate to Pickup' ,
style : TextStyle (
fontSize : 16 ,
fontWeight : FontWeight .bold,
),
),
],
),
SizedBox (height : 12 ),
Row (
mainAxisAlignment : MainAxisAlignment .spaceBetween,
children : [
Column (
crossAxisAlignment : CrossAxisAlignment .start,
children : [
Text ( 'Distance' , style : TextStyle (color : Colors .grey)),
Text (
' ${ route . distance . toStringAsFixed ( 1 )} km' ,
style : TextStyle (
fontSize : 18 ,
fontWeight : FontWeight .bold,
),
),
],
),
Column (
crossAxisAlignment : CrossAxisAlignment .start,
children : [
Text ( 'Duration' , style : TextStyle (color : Colors .grey)),
Text (
' ${( route . duration / 60 ). toStringAsFixed ( 0 )} min' ,
style : TextStyle (
fontSize : 18 ,
fontWeight : FontWeight .bold,
),
),
],
),
],
),
],
),
);
}
Marking Arrival
When you reach the pickup location:
Tap 'I've Arrived'
Button becomes enabled when within 100m of pickup bool get isNearPickup {
if (_currentLocation == null ) return false ;
final distance = Geolocator . distanceBetween (
_currentLocation ! .latitude,
_currentLocation ! .longitude,
widget.origenLat,
widget.origenLng,
);
return distance <= 100 ; // Within 100 meters
}
Notify Passenger
System sends notification to passenger that you’ve arrived Future < void > _markArrived () async {
final result = await TripService . markDriverArrived (
viajeId : widget.viajeId,
conductorId : widget.conductorId,
);
if (result[ 'success' ] == true ) {
setState (() => _hasArrived = true );
_showArrivalConfirmation ();
}
}
Wait for Passenger
Passenger receives notification and comes to your location
Pro Tip: Call the passenger if they don’t appear within 2-3 minutes of your arrival. Use the call button in the passenger info card.
Phase 2: Start Trip
Starting the Journey
Once the passenger is in your vehicle:
ElevatedButton (
onPressed : _hasArrived ? _startTrip : null ,
style : ElevatedButton . styleFrom (
backgroundColor : AppColors .success,
foregroundColor : Colors .white,
padding : EdgeInsets . symmetric (horizontal : 32 , vertical : 16 ),
shape : RoundedRectangleBorder (
borderRadius : BorderRadius . circular ( 12 ),
),
),
child : Row (
mainAxisSize : MainAxisSize .min,
children : [
Icon ( Icons .play_arrow_rounded),
SizedBox (width : 8 ),
Text ( 'Start Trip' , style : TextStyle (fontSize : 16 )),
],
),
)
Start Trip Logic
Future < void > _startTrip () async {
try {
// Verify passenger is ready
final confirmed = await _confirmPassengerReady ();
if ( ! confirmed) return ;
// Call API to start trip
final result = await TripService . startTrip (
viajeId : widget.viajeId,
conductorId : widget.conductorId,
startTime : DateTime . now (),
);
if (result[ 'success' ] == true ) {
setState (() {
_tripStarted = true ;
_tripStartTime = DateTime . now ();
});
// Fetch route to destination
await _fetchRouteToDestination ();
// Start real-time tracking
_startLocationTracking ();
// Show success message
ScaffoldMessenger . of (context). showSnackBar (
SnackBar (
content : Text ( 'Trip started! Navigate to destination.' ),
backgroundColor : AppColors .success,
),
);
}
} catch (e) {
_showError ( 'Failed to start trip: $ e ' );
}
}
Phase 3: Navigate to Destination
Route to Destination
Future < void > _fetchRouteToDestination () async {
if (_currentLocation == null ) return ;
try {
final route = await MapboxService . getRoute (
waypoints : [
_currentLocation ! ,
LatLng (widget.destinoLat, widget.destinoLng),
],
optimize : true ,
avoidTraffic : true ,
);
setState (() {
_routeToDestination = route;
_updateNavigationInstructions (route);
});
} catch (e) {
debugPrint ( 'Error fetching destination route: $ e ' );
}
}
Real-Time Location Updates
Your location is continuously tracked and sent to the server:
void _startLocationTracking () {
_positionStream = Geolocator . getPositionStream (
locationSettings : LocationSettings (
accuracy : LocationAccuracy .high,
distanceFilter : 10 , // Update every 10 meters
),
). listen (( Position position) {
if ( ! mounted) return ;
// Update UI
setState (() {
_currentLocation = LatLng (position.latitude, position.longitude);
});
// Send to server for passenger tracking
TripService . updateDriverLocation (
viajeId : widget.viajeId,
latitude : position.latitude,
longitude : position.longitude,
speed : position.speed,
heading : position.heading,
);
// Check if near destination
_checkDestinationProximity ();
});
}
Speed Indicator
Monitor your current speed:
Widget _buildSpeedIndicator () {
final speed = _currentSpeed ?? 0.0 ;
final speedKmh = (speed * 3.6 ). toInt (); // Convert m/s to km/h
return Container (
padding : EdgeInsets . all ( 12 ),
decoration : BoxDecoration (
color : Colors .black. withOpacity ( 0.7 ),
borderRadius : BorderRadius . circular ( 12 ),
),
child : Column (
children : [
Text (
' $ speedKmh ' ,
style : TextStyle (
color : Colors .white,
fontSize : 32 ,
fontWeight : FontWeight .bold,
),
),
Text (
'km/h' ,
style : TextStyle (
color : Colors .white70,
fontSize : 12 ,
),
),
],
),
);
}
Phase 4: Complete Trip
Arrival at Destination
When you reach the destination:
Proximity Detection
System detects when you’re within 50m of destination Check Destination Proximity
void _checkDestinationProximity () {
if (_currentLocation == null ) return ;
final distance = Geolocator . distanceBetween (
_currentLocation ! .latitude,
_currentLocation ! .longitude,
widget.destinoLat,
widget.destinoLng,
);
if (distance <= 50 && ! _nearDestination) {
setState (() => _nearDestination = true );
_showDestinationAlert ();
}
}
Tap Complete
Press “Complete Trip” button ElevatedButton . icon (
onPressed : _nearDestination ? _completeTrip : null ,
icon : Icon ( Icons .check_circle),
label : Text ( 'Complete Trip' ),
style : ElevatedButton . styleFrom (
backgroundColor : AppColors .success,
padding : EdgeInsets . symmetric (horizontal : 32 , vertical : 16 ),
),
)
Calculate Fare
System calculates final trip cost Future < void > _completeTrip () async {
final result = await TripService . completeTrip (
viajeId : widget.viajeId,
conductorId : widget.conductorId,
endTime : DateTime . now (),
finalDistance : _totalDistance,
finalDuration : _tripDuration,
);
if (result[ 'success' ] == true ) {
final fare = result[ 'precio_final' ];
await _showPaymentDialog (fare);
}
}
Trip Completion Response
{
"success" : true ,
"message" : "Trip completed successfully" ,
"data" : {
"viaje_id" : 123 ,
"distancia_real" : 5.8 ,
"duracion_minutos" : 18 ,
"precio_final" : 14500 ,
"comision_viax" : 2175 ,
"ganancia_conductor" : 12325 ,
"fecha_fin" : "2026-03-05 15:30:00"
}
}
Payment Collection
Cash Payment Dialog
Payment Confirmation Dialog
Future < void > _showPaymentDialog ( double fare) async {
return showDialog (
context : context,
barrierDismissible : false ,
builder : (context) => AlertDialog (
title : Text ( 'Collect Payment' ),
content : Column (
mainAxisSize : MainAxisSize .min,
children : [
Icon (
Icons .payments_rounded,
size : 64 ,
color : AppColors .success,
),
SizedBox (height : 16 ),
Text (
'Trip Fare' ,
style : TextStyle (color : Colors .grey[ 600 ]),
),
Text (
formatCurrency (fare),
style : TextStyle (
fontSize : 32 ,
fontWeight : FontWeight .bold,
color : AppColors .success,
),
),
SizedBox (height : 16 ),
Text (
'Collect this amount in cash from the passenger' ,
textAlign : TextAlign .center,
style : TextStyle (fontSize : 14 ),
),
],
),
actions : [
TextButton (
onPressed : () {
Navigator . pop (context);
_proceedToRating ();
},
child : Text ( 'Payment Received' ),
),
],
),
);
}
All payments in Viax are cash only . You receive money directly from the passenger at the end of each trip.
Commission Tracking
Your earnings are calculated automatically:
class TripEarnings {
final double precioTotal; // Total fare
final double comisionViax; // Platform commission (15%)
final double gananciaDriver; // Your earnings (85%)
TripEarnings ({
required this .precioTotal,
}) : comisionViax = precioTotal * 0.15 ,
gananciaDriver = precioTotal * 0.85 ;
}
Rating the Passenger
After payment, rate your experience:
Future < void > _proceedToRating () async {
final rating = await showDialog < int >(
context : context,
builder : (context) => RatingDialog (
title : 'Rate ${ widget . clienteNombre } ' ,
subtitle : 'How was your experience with this passenger?' ,
),
);
if (rating != null ) {
await _submitRating (rating);
}
// Return to home screen
Navigator . popUntil (context, (route) => route.isFirst);
}
Future < void > _submitRating ( int rating) async {
await TripService . ratePassenger (
viajeId : widget.viajeId,
conductorId : widget.conductorId,
usuarioId : widget.clienteId,
calificacion : rating,
);
}
Trip Cancellation
If you need to cancel a trip:
Cancellation Policy:
Frequent cancellations may affect your driver rating
Provide a valid reason when canceling
Passenger will be notified immediately
Future < void > _cancelTrip () async {
final reason = await _showCancelDialog ();
if (reason == null ) return ;
final result = await TripService . cancelTrip (
viajeId : widget.viajeId,
conductorId : widget.conductorId,
motivo : reason,
);
if (result[ 'success' ] == true ) {
Navigator . popUntil (context, (route) => route.isFirst);
}
}
Next Steps
Navigation & Maps Learn advanced navigation features and map controls
Earnings Tracking Monitor your income and view trip history