import React, { useState, useEffect } from 'react'; import { View, Text, Alert, BackHandler } from 'react-native'; import { useAppTheme } from '@theme'; import { PrimaryButton, MapViewComponent } from '@components'; import { useAppDispatch, useAppSelector } from '@store'; import { advanceJobStatus } from '@store/commonReducers/job'; import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { AppStackParamList } from '@navigation/navigationTypes'; import { RouteNames, JobStatus } from '@utils/constants'; import { getStyles } from './liveTrackingScreen.styles'; import { Coordinates } from '@app-types/index'; import { requestLocationPermission, showPermissionDeniedAlert, getCurrentPosition, getHaversineDistance, } from '@services/locationService'; import { socketService } from '@services/socketService'; import Geolocation from 'react-native-geolocation-service'; export const LiveTrackingScreen: React.FC = () => { const { colors } = useAppTheme(); const styles = getStyles(colors); const dispatch = useAppDispatch(); const navigation = useNavigation>(); const route = useRoute>(); const { jobId } = route.params; const { activeDeliveries } = useAppSelector(state => state.deliveries); const activeJob = activeDeliveries?.delivery; const [currentLocation, setCurrentLocation] = useState( null, ); const [distance, setDistance] = useState(null); const [speed, setSpeed] = useState(24); // default speed in km/h // Block the hardware back button — once delivery starts the partner // must not navigate back to the OrderPickedUp screen. // useEffect(() => { // const onBackPress = () => true; // return true = event consumed, back suppressed // const subscription = BackHandler.addEventListener('hardwareBackPress', onBackPress); // return () => subscription.remove(); // }, []); useEffect(() => { let watchId: number | null = null; const startTracking = async () => { try { const hasPermission = await requestLocationPermission(); if (!hasPermission) { showPermissionDeniedAlert(); return; } // Get initial current position const initialPos = await getCurrentPosition(); setCurrentLocation(initialPos); // Send initial location update to socket socketService.updateLocation(initialPos); // Start watching position watchId = Geolocation.watchPosition( position => { const coords: Coordinates = { latitude: position.coords.latitude, longitude: position.coords.longitude, }; setCurrentLocation(coords); // Emit location update via socket safely try { socketService.updateLocation(coords); } catch (err) { console.warn('Socket location update error:', err); } // Update speed if available (speed in m/s from GPS) if (position.coords.speed !== null && position.coords.speed > 0) { const speedKmH = position.coords.speed * 3.6; setSpeed(Math.max(speedKmH, 10)); // keep speed at least 10 km/h for a realistic ETA } }, error => { console.error('watchPosition error:', error); }, { enableHighAccuracy: true, distanceFilter: 10, // every 10 meters interval: 5000, fastestInterval: 2000, }, ); } catch (error) { console.error('Error starting location tracking:', error); } }; startTracking(); return () => { if (watchId !== null) { Geolocation.clearWatch(watchId); } }; }, []); // Compute live distance to drop-off address useEffect(() => { if (currentLocation && activeJob?.order?.dropAddress) { const dropCoords = { latitude: activeJob.order.dropAddress.latitude, longitude: activeJob.order.dropAddress.longitude, }; const km = getHaversineDistance(currentLocation, dropCoords); setDistance(km); } }, [currentLocation, activeJob?.order?.dropAddress]); const handleArrived = () => { dispatch(advanceJobStatus(JobStatus.ArrivedAtCustomer)); navigation.navigate(RouteNames.DeliverOrder, { jobId: jobId, }); }; const handleContact = () => { Alert.alert( 'Contact Customer', `Calling ${activeJob?.order?.customer?.user?.name || 'Customer'} at ${ activeJob?.order?.customer?.user?.phone } `, ); }; // Calculate ETA in minutes based on distance and speed (distance / speed * 60) const etaMinutes = distance !== null ? Math.max(Math.round((distance / speed) * 60), 1) : null; return ( {/* Top Floating ETA Card */} {etaMinutes !== null ? `Arriving in ${etaMinutes} mins` : 'Calculating ETA…'} Distance: {distance !== null ? `${distance.toFixed(2)} km` : '...'} • Speed: {Math.round(speed)} km/h {/* Map Content */} {activeJob?.order?.dropAddress ? ( ) : ( Loading Map... )} {/* Customer Location Bottom Card */} Deliver to {activeJob?.order?.customer?.user?.name} {activeJob?.order?.dropAddress ? `${activeJob.order.dropAddress.houseNumber}, ${activeJob.order.dropAddress.addressLine1}` : 'Loading address...'} ); }; export default LiveTrackingScreen;