├── .expo-shared └── assets.json ├── .gitignore ├── App.js ├── README.md ├── app.gif ├── app.json ├── assets ├── adaptive-icon.png ├── favicon.png ├── icon.png └── splash.png ├── babel.config.js ├── components ├── Map.js ├── NavFavourites.js ├── NavOptions.js ├── NavigateCard.js └── RideOptionsCard.js ├── package-lock.json ├── package.json ├── screens ├── HomeScreen.js └── MapScreen.js ├── slices └── navSlice.js └── store.js /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | # macOS 14 | .DS_Store 15 | 16 | .env 17 | 18 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Text, View } from "react-native"; 2 | import { Provider } from "react-redux"; 3 | import store from "./store"; 4 | import { SafeAreaProvider } from "react-native-safe-area-context"; 5 | import HomeScreen from "./screens/HomeScreen"; 6 | import { NavigationContainer } from "@react-navigation/native"; 7 | import { createStackNavigator } from "@react-navigation/stack"; 8 | import MapScreen from "./screens/MapScreen"; 9 | import "react-native-gesture-handler"; 10 | 11 | export default function App() { 12 | const Stack = createStackNavigator(); 13 | return ( 14 | 15 | 16 | 17 | 18 | 25 | 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | const styles = StyleSheet.create({}); 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uber-Clone React-Native-App 2 | 3 | Built a React Native application using EXPO Framework which allows users to enter start location and destination and it displays the cost of cabs based on the type selected. 4 | 5 | ## Preview 6 | 7 | ![](/app.gif) 8 | 9 | ## Run on Expo App on IOS & Android 10 | 11 | - Go to [Uber-Clone](https://expo.dev/@tanishka_yadav/clone-uber?serviceType=classic&distribution=expo-go) 12 | - Scan the QR code and install the Expo app 13 | - Click on uber-clone 14 | -------------------------------------------------------------------------------- /app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tanishka-dev/Uber-Clone-React-Native/8410f80ff74eb85f70dba9dc41d8ca9026c7336a/app.gif -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "clone-uber", 4 | "slug": "clone-uber", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "splash": { 9 | "image": "./assets/splash.png", 10 | "resizeMode": "contain", 11 | "backgroundColor": "#ffffff" 12 | }, 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": [ 17 | "**/*" 18 | ], 19 | "ios": { 20 | "supportsTablet": true 21 | }, 22 | "android": { 23 | "adaptiveIcon": { 24 | "foregroundImage": "./assets/adaptive-icon.png", 25 | "backgroundColor": "#FFFFFF" 26 | } 27 | }, 28 | "web": { 29 | "favicon": "./assets/favicon.png" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tanishka-dev/Uber-Clone-React-Native/8410f80ff74eb85f70dba9dc41d8ca9026c7336a/assets/adaptive-icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tanishka-dev/Uber-Clone-React-Native/8410f80ff74eb85f70dba9dc41d8ca9026c7336a/assets/favicon.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tanishka-dev/Uber-Clone-React-Native/8410f80ff74eb85f70dba9dc41d8ca9026c7336a/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tanishka-dev/Uber-Clone-React-Native/8410f80ff74eb85f70dba9dc41d8ca9026c7336a/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"], 5 | "plugins": [ 6 | ["module:react-native-dotenv", { 7 | "envName": "APP_ENV", 8 | "moduleName": "@env", 9 | "path": ".env", 10 | }], 11 | ], 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /components/Map.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Text, View } from "react-native"; 2 | import React, { useEffect, useRef } from "react"; 3 | import MapViewDirections from "react-native-maps-directions"; 4 | import tw from "tailwind-react-native-classnames"; 5 | import MapView, { Marker } from "react-native-maps"; 6 | import { useDispatch, useSelector } from "react-redux"; 7 | import { GOOGLE_MAPS_APIKEY } from "@env"; 8 | import { 9 | selectDestination, 10 | selectOrigin, 11 | setTimeTravelInformation, 12 | } from "../slices/navSlice"; 13 | 14 | const Map = () => { 15 | const origin = useSelector(selectOrigin); 16 | const destination = useSelector(selectDestination); 17 | const mapRef = useRef(null); 18 | const dispatch = useDispatch(); 19 | 20 | useEffect(() => { 21 | if (!origin || !destination) return; 22 | 23 | mapRef.current.fitToSuppliedMarkers(["origin", "destination"], { 24 | edgePadding: { top: 60, right: 60, bottom: 60, left: 60 }, 25 | }); 26 | 27 | //Zoom 28 | }, [origin, destination]); 29 | 30 | useEffect(() => { 31 | if (!origin || !destination) return; 32 | 33 | const getTravelTime = async () => { 34 | fetch(`https://maps.googleapis.com/maps/api/distancematrix/json? 35 | units=imperial&origins=${origin.description}&destinations=${destination.description}&key=${GOOGLE_MAPS_APIKEY}`) 36 | .then((res) => res.json()) 37 | .then((data) => { 38 | dispatch(setTimeTravelInformation(data.rows[0].elements[0])); 39 | }); 40 | }; 41 | 42 | getTravelTime(); 43 | }, [origin, destination, GOOGLE_MAPS_APIKEY]); 44 | return ( 45 | 56 | {origin && destination && ( 57 | 64 | )} 65 | 66 | {origin?.location && ( 67 | 76 | )} 77 | {destination?.location && ( 78 | 87 | )} 88 | 89 | ); 90 | }; 91 | 92 | export default Map; 93 | 94 | const styles = StyleSheet.create({}); 95 | -------------------------------------------------------------------------------- /components/NavFavourites.js: -------------------------------------------------------------------------------- 1 | import { 2 | FlatList, 3 | StyleSheet, 4 | Text, 5 | TouchableOpacity, 6 | View, 7 | } from "react-native"; 8 | import React from "react"; 9 | import { Icon } from "react-native-elements"; 10 | import tw from "tailwind-react-native-classnames"; 11 | import { useDispatch } from "react-redux"; 12 | import { setDestination, setOrigin } from "../slices/navSlice"; 13 | import { useNavigation } from "@react-navigation/native"; 14 | const data = [ 15 | { 16 | id: "123", 17 | icon: "home", 18 | location: "Home", 19 | destination: "Noida, India", 20 | }, 21 | { 22 | id: "456", 23 | icon: "briefcase", 24 | location: "Work", 25 | destination: "Gurgaon, India", 26 | }, 27 | ]; 28 | 29 | const NavFavourites = () => { 30 | const dispatch = useDispatch(); 31 | const navigation = useNavigation(); 32 | return ( 33 | item.id} 36 | ItemSeparatorComponent={() => ( 37 | 38 | )} 39 | renderItem={({ item: { location, destination, icon } }) => ( 40 | {}} 42 | fetchDetails={true} 43 | style={tw`flex-row items-center p-5 `} 44 | > 45 | 52 | 53 | {location} 54 | {destination} 55 | 56 | 57 | )} 58 | /> 59 | ); 60 | }; 61 | 62 | export default NavFavourites; 63 | 64 | const styles = StyleSheet.create({}); 65 | -------------------------------------------------------------------------------- /components/NavOptions.js: -------------------------------------------------------------------------------- 1 | import { 2 | FlatList, 3 | StyleSheet, 4 | Text, 5 | Image, 6 | TouchableOpacity, 7 | View, 8 | } from "react-native"; 9 | import React from "react"; 10 | import tw from "tailwind-react-native-classnames"; 11 | import { Icon } from "react-native-elements"; 12 | import { useNavigation } from "@react-navigation/native"; 13 | import { useSelector } from "react-redux"; 14 | import { selectOrigin } from "../slices/navSlice"; 15 | 16 | const array = [ 17 | { 18 | id: 1, 19 | title: "Get a ride", 20 | image: "https://links.papareact.com/3pn", 21 | screen: "MapScreen", 22 | }, 23 | 24 | //Uber Eats can be implemented here 25 | ]; 26 | const NavOptions = () => { 27 | const navigation = useNavigation(); 28 | const origin = useSelector(selectOrigin); 29 | return ( 30 | ( 34 | navigation.navigate(item.screen)} 36 | style={tw`p-2 pb-8 pl-6 pt-4 bg-gray-200 m-3 w-60`} 37 | disabled={!origin} 38 | > 39 | 40 | 44 | {item.title} 45 | 51 | 52 | 53 | )} 54 | /> 55 | ); 56 | }; 57 | 58 | export default NavOptions; 59 | -------------------------------------------------------------------------------- /components/NavigateCard.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Text, View, TouchableOpacity } from "react-native"; 2 | import React from "react"; 3 | import { SafeAreaView } from "react-native-safe-area-context"; 4 | import tw from "tailwind-react-native-classnames"; 5 | import { GooglePlacesAutocomplete } from "react-native-google-places-autocomplete"; 6 | import { GOOGLE_MAPS_APIKEY } from "@env"; 7 | import { useDispatch } from "react-redux"; 8 | import { setDestination } from "../slices/navSlice"; 9 | import { useNavigation } from "@react-navigation/native"; 10 | import NavFavourites from "./NavFavourites"; 11 | 12 | import { Icon } from "react-native-elements"; 13 | 14 | const NavigateCard = () => { 15 | const dispatch = useDispatch(); 16 | const navigation = useNavigation(); 17 | 18 | return ( 19 | 20 | 21 | 22 | { 29 | dispatch( 30 | setDestination({ 31 | location: details.geometry.location, 32 | description: data.description, 33 | }) 34 | ); 35 | 36 | navigation.navigate("RideOptionsCard"); 37 | }} 38 | query={{ 39 | key: GOOGLE_MAPS_APIKEY, 40 | language: "en", 41 | }} 42 | nearbyPlacesAPI="GooglePlacesSearch" 43 | debounce={400} 44 | /> 45 | 46 | 47 | 48 | 49 | navigation.navigate("RideOptionsCard")} 51 | style={tw` flex flex-row bg-black w-24 px-4 py-3 rounded-full `} 52 | > 53 | 54 | Rides 55 | 56 | 57 | 58 | ); 59 | }; 60 | 61 | // add button for UberEats 62 | export default NavigateCard; 63 | 64 | const toInputBoxStyle = StyleSheet.create({ 65 | container: { 66 | backgroundColor: "white", 67 | paddingTop: 20, 68 | flex: 0, 69 | }, 70 | textInput: { 71 | backgroundColor: "#DDDDDF", 72 | borderRadius: 0, 73 | fontSize: 18, 74 | }, 75 | textInputContainer: { 76 | paddingHorizontal: 20, 77 | paddingBottom: 0, 78 | }, 79 | }); 80 | -------------------------------------------------------------------------------- /components/RideOptionsCard.js: -------------------------------------------------------------------------------- 1 | import { Text, TouchableOpacity, View, FlatList, Image } from "react-native"; 2 | import React, { useState } from "react"; 3 | import { SafeAreaView } from "react-native-safe-area-context"; 4 | import tw from "tailwind-react-native-classnames"; 5 | import { Icon } from "react-native-elements"; 6 | import { useNavigation } from "@react-navigation/native"; 7 | import { useSelector } from "react-redux"; 8 | import { selectTimeTravelInformation } from "../slices/navSlice"; 9 | 10 | const data = [ 11 | { 12 | id: "Uber-X-123", 13 | title: "Uber X", 14 | multiplier: 1, 15 | image: "https://links.papareact.com/3pn", 16 | }, 17 | { 18 | id: "Uber-XL-456", 19 | title: "Uber XL", 20 | multiplier: 1.2, 21 | image: "https://links.papareact.com/5w8", 22 | }, 23 | { 24 | id: "Uber-LUX-789", 25 | title: "Uber LUX", 26 | multiplier: 1.75, 27 | image: "https://links.papareact.com/7pf", 28 | }, 29 | ]; 30 | 31 | const SURGE_CHARGE_RATE = 1.5; 32 | const RideOptionsCard = () => { 33 | const navigation = useNavigation(); 34 | 35 | const [selected, setSelected] = useState(null); 36 | const travelTimeInformation = useSelector(selectTimeTravelInformation); 37 | return ( 38 | 39 | 40 | navigation.navigate("NavigateCard")} 43 | > 44 | 45 | 46 | 47 | Choose A Ride for- {travelTimeInformation?.distance?.text} 48 | 49 | 50 | item.id} 53 | renderItem={({ item: { id, title, multiplier, image }, item }) => ( 54 | setSelected(item)} 59 | > 60 | 64 | 65 | {title} 66 | Travel Time-{travelTimeInformation?.duration?.text} 67 | 68 | 69 | ₹ 70 | {( 71 | (travelTimeInformation?.duration?.value * 72 | SURGE_CHARGE_RATE * 73 | multiplier) / 74 | 100 75 | ).toFixed()} 76 | 77 | 78 | )} 79 | /> 80 | 81 | 85 | 86 | Choose {selected?.title} 87 | 88 | 89 | 90 | 91 | ); 92 | }; 93 | 94 | export default RideOptionsCard; 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clone-uber", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "postinstall": "npm run install", 7 | "start": "expo start", 8 | "android": "expo start --android", 9 | "ios": "expo start --ios", 10 | "web": "expo start --web", 11 | "eject": "expo eject" 12 | }, 13 | "dependencies": { 14 | "@react-native-community/masked-view": "^0.1.11", 15 | "@react-navigation/native": "^6.0.8", 16 | "@react-navigation/native-stack": "^6.5.0", 17 | "@react-navigation/stack": "^6.1.1", 18 | "@reduxjs/toolkit": "^1.8.0", 19 | "expo": "~44.0.0", 20 | "expo-status-bar": "~1.2.0", 21 | "react": "17.0.1", 22 | "react-dom": "17.0.1", 23 | "react-native": "0.64.3", 24 | "react-native-dotenv": "^3.3.1", 25 | "react-native-elements": "^3.4.2", 26 | "react-native-gesture-handler": "~2.1.0", 27 | "react-native-google-places-autocomplete": "^2.4.1", 28 | "react-native-maps": "0.29.4", 29 | "react-native-maps-directions": "^1.8.0", 30 | "react-native-reanimated": "~2.3.1", 31 | "react-native-safe-area-context": "3.3.2", 32 | "react-native-screens": "~3.10.1", 33 | "react-native-vector-icons": "^9.1.0", 34 | "react-native-web": "0.17.1", 35 | "react-redux": "^7.2.6", 36 | "tailwind-react-native-classnames": "^1.5.1", 37 | "expo-updates": "~0.11.6" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.12.9" 41 | }, 42 | "private": true 43 | } 44 | -------------------------------------------------------------------------------- /screens/HomeScreen.js: -------------------------------------------------------------------------------- 1 | import { View, Text, SafeAreaView, Image } from "react-native"; 2 | import React from "react"; 3 | import tw from "tailwind-react-native-classnames"; 4 | import NavOptions from "../components/NavOptions"; 5 | import { GooglePlacesAutocomplete } from "react-native-google-places-autocomplete"; 6 | import { GOOGLE_MAPS_APIKEY } from "@env"; 7 | import { useDispatch } from "react-redux"; 8 | import { setDestination, setOrigin } from "../slices/navSlice"; 9 | import NavFavourites from "../components/NavFavourites"; 10 | const HomeScreen = () => { 11 | const dispatch = useDispatch(); 12 | return ( 13 | 14 | 15 | 21 | { 32 | dispatch( 33 | setOrigin({ 34 | location: details.geometry.location, 35 | description: data.description, 36 | }) 37 | ); 38 | dispatch(setDestination(null)); 39 | }} 40 | fetchDetails={true} 41 | enablePoweredByContainer={false} 42 | returnKeyType={"search"} 43 | query={{ 44 | key: GOOGLE_MAPS_APIKEY, 45 | language: "en", 46 | }} 47 | nearbyPlacesAPI="GooglePlacesSearch" 48 | debounce={400} 49 | /> 50 | 51 | 52 | {/* */} 53 | 54 | 55 | ); 56 | }; 57 | 58 | export default HomeScreen; 59 | -------------------------------------------------------------------------------- /screens/MapScreen.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; 2 | import React from "react"; 3 | import tw from "tailwind-react-native-classnames"; 4 | import Map from "../components/Map"; 5 | import { createStackNavigator } from "@react-navigation/stack"; 6 | import NavigateCard from "../components/NavigateCard"; 7 | import RideOptionsCard from "../components/RideOptionsCard"; 8 | import { Icon } from "react-native-elements/dist/icons/Icon"; 9 | import { useNavigation } from "@react-navigation/native"; 10 | const MapScreen = () => { 11 | const Stack = createStackNavigator(); 12 | const navigation = useNavigation(); 13 | return ( 14 | 15 | navigation.navigate("HomeScreen")} 17 | style={tw`absolute top-10 left-6 bg-gray-100 z-50 p-3 rounded-full shadow-lg`} 18 | > 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default MapScreen; 47 | 48 | const styles = StyleSheet.create({}); 49 | -------------------------------------------------------------------------------- /slices/navSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | origin: null, 5 | destination: null, 6 | travelTimeInformation: null, 7 | }; 8 | export const navSlice = createSlice({ 9 | name: "nav", 10 | initialState, 11 | reducers: { 12 | setOrigin: (state, action) => { 13 | state.origin = action.payload; 14 | }, 15 | setDestination: (state, action) => { 16 | state.destination = action.payload; 17 | }, 18 | setTimeTravelInformation: (state, action) => { 19 | state.travelTimeInformation = action.payload; 20 | }, 21 | }, 22 | }); 23 | 24 | export const { setOrigin, setDestination, setTimeTravelInformation } = 25 | navSlice.actions; 26 | 27 | //Selectors 28 | export const selectOrigin = (state) => state.nav.origin; 29 | export const selectDestination = (state) => state.nav.destination; 30 | export const selectTimeTravelInformation = (state) => 31 | state.nav.travelTimeInformation; 32 | 33 | export default navSlice.reducer; 34 | -------------------------------------------------------------------------------- /store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import navReducer from "./slices/navSlice"; 3 | 4 | export default configureStore({ 5 | reducer: { 6 | nav: navReducer, 7 | }, 8 | }); 9 | --------------------------------------------------------------------------------