Skip to main content

Trip Lifecycle

Once you accept a trip request, follow this workflow to complete the ride successfully.
1

Navigate to Pickup

Drive to the passenger’s pickup location using integrated navigation
2

Arrive at Pickup

Mark yourself as arrived and wait for the passenger
3

Start Trip

Confirm passenger is in vehicle and start the trip
4

Navigate to Destination

Follow GPS navigation to the drop-off location
5

Complete Trip

Mark trip as complete when you reach destination
6

Collect Payment

Receive cash payment from passenger
7

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

Active Trip Screen
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

Fetch Route to Pickup
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');
  }
}
Navigation Card Widget
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:
1

Tap 'I've Arrived'

Button becomes enabled when within 100m of pickup
Check Proximity
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
}
2

Notify Passenger

System sends notification to passenger that you’ve arrived
Mark Arrived
Future<void> _markArrived() async {
  final result = await TripService.markDriverArrived(
    viajeId: widget.viajeId,
    conductorId: widget.conductorId,
  );
  
  if (result['success'] == true) {
    setState(() => _hasArrived = true);
    _showArrivalConfirmation();
  }
}
3

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:
Start Trip Button
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

Start Trip Function
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

Get Destination Route
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:
Location Tracking
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:
Speed Display Widget
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:
1

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();
  }
}
2

Tap Complete

Press “Complete Trip” button
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),
  ),
)
3

Calculate Fare

System calculates final trip cost
Trip Completion
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

API 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:
Earnings Calculation
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:
Rating Dialog
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
Cancel Trip
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

Build docs developers (and LLMs) love