From dcb9ff3beecda8018d78b925c9242adc04fb4adc Mon Sep 17 00:00:00 2001 From: Sanidhya Kumar Verma Date: Fri, 30 Aug 2024 13:28:30 +0000 Subject: [PATCH] current location map ui implemented --- app/(root)/(tabs)/home.tsx | 31 +++++++- components/google-text-input.tsx | 3 +- components/map.tsx | 117 ++++++++++++++++++++++++++++- lib/map.ts | 122 +++++++++++++++++++++++++++++++ lib/utils.ts | 2 +- store/index.ts | 53 ++++++++++++++ 6 files changed, 321 insertions(+), 7 deletions(-) create mode 100644 lib/map.ts create mode 100644 store/index.ts diff --git a/app/(root)/(tabs)/home.tsx b/app/(root)/(tabs)/home.tsx index 3764418..982f1a8 100644 --- a/app/(root)/(tabs)/home.tsx +++ b/app/(root)/(tabs)/home.tsx @@ -1,4 +1,6 @@ import { useUser } from "@clerk/clerk-expo"; +import * as Location from "expo-location"; +import { useEffect, useState } from "react"; import { ActivityIndicator, FlatList, @@ -13,6 +15,7 @@ import { GoogleTextInput } from "@/components/google-text-input"; import { Map } from "@/components/map"; import { RideCard } from "@/components/ride-card"; import { icons, images } from "@/constants"; +import { useLocationStore } from "@/store"; const recentRides = [ { @@ -126,12 +129,38 @@ const recentRides = [ ]; const Home = () => { + const { setUserLocation, setDestinationLocation } = useLocationStore(); const { user } = useUser(); const isLoading = false; + const [hasPermissions, setHasPermissions] = useState(false); + const handleSignOut = () => {}; const handleDestinationPress = () => {}; + useEffect(() => { + const requestLocation = async () => { + let { status } = await Location.requestForegroundPermissionsAsync(); + + if (status !== "granted") return setHasPermissions(false); + + let location = await Location.getCurrentPositionAsync(); + + const address = await Location.reverseGeocodeAsync({ + longitude: location.coords?.longitude, + latitude: location.coords?.latitude, + }); + + setUserLocation({ + latitude: location.coords.latitude, + longitude: location.coords.longitude, + address: `${address[0].name}, ${address[0].region}`, + }); + }; + + requestLocation(); + }, [setUserLocation]); + return ( { /> - Your current location + Your Current Location diff --git a/components/google-text-input.tsx b/components/google-text-input.tsx index e7db93b..0ee55b7 100644 --- a/components/google-text-input.tsx +++ b/components/google-text-input.tsx @@ -1,6 +1,7 @@ -import type { GoogleInputProps } from "@/types/type"; import { Text, View } from "react-native"; +import type { GoogleInputProps } from "@/types/type"; + export const GoogleTextInput = ({ icon, initialLocation, diff --git a/components/map.tsx b/components/map.tsx index 050927a..ce3942b 100644 --- a/components/map.tsx +++ b/components/map.tsx @@ -1,9 +1,118 @@ -import { Text, View } from "react-native"; +import { useEffect, useState } from "react"; +import MapView, { Marker, PROVIDER_DEFAULT } from "react-native-maps"; + +import { calculateRegion, generateMarkersFromData } from "@/lib/map"; +import { useDriverStore, useLocationStore } from "@/store"; +import type { MarkerData } from "@/types/type"; +import { icons } from "@/constants"; + +const drivers = [ + { + id: "1", + driver_id: 1, + first_name: "James", + last_name: "Wilson", + profile_image_url: + "https://ucarecdn.com/dae59f69-2c1f-48c3-a883-017bcf0f9950/-/preview/1000x666/", + car_image_url: + "https://ucarecdn.com/a2dc52b2-8bf7-4e49-9a36-3ffb5229ed02/-/preview/465x466/", + car_seats: 4, + rating: 4.8, + }, + { + id: "2", + driver_id: 2, + first_name: "David", + last_name: "Brown", + profile_image_url: + "https://ucarecdn.com/6ea6d83d-ef1a-483f-9106-837a3a5b3f67/-/preview/1000x666/", + car_image_url: + "https://ucarecdn.com/a3872f80-c094-409c-82f8-c9ff38429327/-/preview/930x932/", + car_seats: 5, + rating: 4.6, + }, + { + id: "3", + driver_id: 3, + first_name: "Michael", + last_name: "Johnson", + profile_image_url: + "https://ucarecdn.com/0330d85c-232e-4c30-bd04-e5e4d0e3d688/-/preview/826x822/", + car_image_url: + "https://ucarecdn.com/289764fb-55b6-4427-b1d1-f655987b4a14/-/preview/930x932/", + car_seats: 4, + rating: 4.7, + }, + { + id: "4", + driver_id: 4, + first_name: "Robert", + last_name: "Green", + profile_image_url: + "https://ucarecdn.com/fdfc54df-9d24-40f7-b7d3-6f391561c0db/-/preview/626x417/", + car_image_url: + "https://ucarecdn.com/b6fb3b55-7676-4ff3-8484-fb115e268d32/-/preview/930x932/", + car_seats: 4, + rating: 4.9, + }, +]; export const Map = () => { + const { + userLatitude, + userLongitude, + destinationLatitude, + destinationLongitude, + } = useLocationStore(); + const { selectedDriver, setDrivers } = useDriverStore(); + const [markers, setMarkers] = useState([]); + + const region = calculateRegion({ + userLatitude, + userLongitude, + destinationLatitude, + destinationLongitude, + }); + + useEffect(() => { + if (Array.isArray(drivers)) { + if (!userLatitude || !userLongitude) return; + + const newMarkers = generateMarkersFromData({ + data: drivers, + userLatitude, + userLongitude, + }); + + setMarkers(newMarkers); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [drivers]); + return ( - - Map - + + {markers.map((marker) => ( + + ))} + ); }; diff --git a/lib/map.ts b/lib/map.ts new file mode 100644 index 0000000..7f2bab1 --- /dev/null +++ b/lib/map.ts @@ -0,0 +1,122 @@ +import type { Driver, MarkerData } from "@/types/type"; + +const directionsAPI = process.env.EXPO_PUBLIC_GOOGLE_API_KEY; + +export const generateMarkersFromData = ({ + data, + userLatitude, + userLongitude, +}: { + data: Driver[]; + userLatitude: number; + userLongitude: number; +}): MarkerData[] => { + return data.map((driver, i) => { + const latOffset = (Math.random() - 0.5) * 0.01; // Random offset between -0.005 and 0.005 + const lngOffset = (Math.random() - 0.5) * 0.01; // Random offset between -0.005 and 0.005 + + return { + id: i, + latitude: userLatitude + latOffset, + longitude: userLongitude + lngOffset, + title: `${driver.first_name} ${driver.last_name}`, + ...driver, + }; + }); +}; + +export const calculateRegion = ({ + userLatitude, + userLongitude, + destinationLatitude, + destinationLongitude, +}: { + userLatitude: number | null; + userLongitude: number | null; + destinationLatitude?: number | null; + destinationLongitude?: number | null; +}) => { + if (!userLatitude || !userLongitude) { + return { + latitude: 37.78825, + longitude: -122.4324, + latitudeDelta: 0.01, + longitudeDelta: 0.01, + }; + } + + if (!destinationLatitude || !destinationLongitude) { + return { + latitude: userLatitude, + longitude: userLongitude, + latitudeDelta: 0.01, + longitudeDelta: 0.01, + }; + } + + const minLat = Math.min(userLatitude, destinationLatitude); + const maxLat = Math.max(userLatitude, destinationLatitude); + const minLng = Math.min(userLongitude, destinationLongitude); + const maxLng = Math.max(userLongitude, destinationLongitude); + + const latitudeDelta = (maxLat - minLat) * 1.3; // Adding some padding + const longitudeDelta = (maxLng - minLng) * 1.3; // Adding some padding + + const latitude = (userLatitude + destinationLatitude) / 2; + const longitude = (userLongitude + destinationLongitude) / 2; + + return { + latitude, + longitude, + latitudeDelta, + longitudeDelta, + }; +}; + +export const calculateDriverTimes = async ({ + markers, + userLatitude, + userLongitude, + destinationLatitude, + destinationLongitude, +}: { + markers: MarkerData[]; + userLatitude: number | null; + userLongitude: number | null; + destinationLatitude: number | null; + destinationLongitude: number | null; +}) => { + if ( + !userLatitude || + !userLongitude || + !destinationLatitude || + !destinationLongitude + ) + return; + + try { + const timesPromises = markers.map(async (marker) => { + const responseToUser = await fetch( + `https://maps.googleapis.com/maps/api/directions/json?origin=${marker.latitude},${marker.longitude}&destination=${userLatitude},${userLongitude}&key=${directionsAPI}`, + ); + const dataToUser = await responseToUser.json(); + const timeToUser = dataToUser.routes[0].legs[0].duration.value; // Time in seconds + + const responseToDestination = await fetch( + `https://maps.googleapis.com/maps/api/directions/json?origin=${userLatitude},${userLongitude}&destination=${destinationLatitude},${destinationLongitude}&key=${directionsAPI}`, + ); + const dataToDestination = await responseToDestination.json(); + const timeToDestination = + dataToDestination.routes[0].legs[0].duration.value; // Time in seconds + + const totalTime = (timeToUser + timeToDestination) / 60; // Total time in minutes + const price = (totalTime * 0.5).toFixed(2); // Calculate price based on time + + return { ...marker, time: totalTime, price }; + }); + + return await Promise.all(timesPromises); + } catch (error) { + console.error("Error calculating driver times:", error); + } +}; diff --git a/lib/utils.ts b/lib/utils.ts index 2ff6c91..4bf7fbe 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,4 +1,4 @@ -import { Ride } from "@/types/type"; +import type { Ride } from "@/types/type"; export const sortRides = (rides: Ride[]): Ride[] => { const result = rides.sort((a, b) => { diff --git a/store/index.ts b/store/index.ts new file mode 100644 index 0000000..3e9882e --- /dev/null +++ b/store/index.ts @@ -0,0 +1,53 @@ +import type { DriverStore, LocationStore, MarkerData } from "@/types/type"; +import { create } from "zustand"; + +export const useLocationStore = create((set) => ({ + userAddress: null, + userLongitude: null, + userLatitude: null, + destinationLongitude: null, + destinationLatitude: null, + destinationAddress: null, + + setUserLocation: ({ + latitude, + longitude, + address, + }: { + latitude: number; + longitude: number; + address: string; + }) => { + set(() => ({ + userLatitude: latitude, + userLongitude: longitude, + userAddress: address, + })); + }, + + setDestinationLocation: ({ + latitude, + longitude, + address, + }: { + latitude: number; + longitude: number; + address: string; + }) => { + set(() => ({ + destinationLatitude: latitude, + destinationLongitude: longitude, + destinationAddress: address, + })); + }, +})); + +export const useDriverStore = create((set) => ({ + drivers: [] as MarkerData[], + selectedDriver: null, + + setSelectedDriver: (driverId: number) => + set(() => ({ selectedDriver: driverId })), + setDrivers: (drivers: MarkerData[]) => set(() => ({ drivers })), + clearSelectedDriver: () => set(() => ({ selectedDriver: null })), +}));