├── .editorconfig ├── README.md ├── driver-app ├── .env ├── App.js ├── Root.js ├── app.json ├── index.js ├── package.json └── src │ ├── helpers │ └── location.js │ └── screens │ ├── ContactCustomer.js │ └── OrderMap.js ├── ordering-app ├── .env ├── App.js ├── GlobalContext.js ├── Root.js ├── app.json ├── index.js ├── package.json └── src │ ├── components │ ├── ListCard.js │ ├── NavHeaderRight.js │ └── PageCard.js │ ├── helpers │ ├── getSubTotal.js │ └── location.js │ └── screens │ ├── ContactDriver.js │ ├── FoodDetails.js │ ├── FoodList.js │ ├── OrderSummary.js │ └── TrackOrder.js └── server ├── .env ├── data └── foods.js ├── images ├── fried-mee-sua.jpg ├── gyoza.jpg ├── honey-garlic-chicken.jpg ├── pork-with-veggies.jpg ├── red-bbq-pork-noodles.jpg ├── rice-with-roasted-pork.jpg ├── salmon-sashimi.jpg ├── sesame-chicken-noodle.jpg ├── spicy-teriyaki.jpg ├── tori-karaage.jpg ├── vietnamese-pho.jpg └── white-bee-hoon.jpg ├── index.js └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = false 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | quote_type = double 9 | 10 | [*.js] 11 | indent_style = space 12 | indent_size = 2 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-Native-Food-Delivery 2 | A food delivery app built with React Native, Pusher Channels, Chatkit, and Beams. 3 | 4 | You can read the tutorial here: 5 | 6 | - [Create a food ordering app in React Native - Part 1: Making an order](http://pusher.com/tutorials/food-ordering-app-react-native-part-1) 7 | - [Create a food ordering app in React Native - Part 2: Adding the driver app and chat functionality](http://pusher.com/tutorials/food-ordering-app-react-native-part-2) 8 | - [Create a food ordering app in React Native - Part 3: Adding push notifications](http://pusher.com/tutorials/food-ordering-app-react-native-part-3) 9 | 10 | It includes the following features: 11 | 12 | - Food ordering 13 | - Real-time location tracking 14 | - One on one chat 15 | - Push notifications 16 | 17 | Each branch contains the code on each part of the tutorial: 18 | 19 | - `starter` - contains the starting point of the delivery app. 20 | - `food-ordering` - the final output for part 1 of the series. This contains the initial code for the food ordering app. 21 | - `driver-app` - the final output of part 2 of the series. This contains the code for the driver app as well as the code for implementing Chat. 22 | - `push-notifications` - the final output of part 3 of the series. This contains the code for implementing push notifications. 23 | - `master` - contains the most updated code. 24 | 25 | ## Prerequisites 26 | 27 | - React Native development environment 28 | - [Node.js](https://nodejs.org/en/) 29 | - [Yarn](https://yarnpkg.com/en/) 30 | - [Google Cloud Console account](https://console.cloud.google.com/) - enable [Google Maps](https://developers.google.com/maps/gmp-get-started). 31 | - [Channels app instance](https://pusher.com/channels) 32 | - [Chatkit app instance](https://pusher.com/chatkit) 33 | - [Firebase app instance](https://console.firebase.google.com/) - one for each app (food delivery and driver app). 34 | - [Beams app instance](https://pusher.com/beams) - one for each app. 35 | - [ngrok account](https://ngrok.com/) 36 | 37 | ## Getting started 38 | 39 | 1. Clone the repo: 40 | 41 | ``` 42 | git clone https://github.com/anchetaWern/React-Native-Food-Delivery.git 43 | ``` 44 | 45 | 2. Create a new React Native app for each project 46 | 47 | ``` 48 | react-native init RNFoodDelivery 49 | react-native init RNFoodDeliveryDriver 50 | ``` 51 | 52 | 3. Copy the contents of the `ordering-app` folder in the repo to the `RNFoodDelivery` project. Do the same for the `driver-app` folder and copy its contents over to the `RNFoodDeliveryDriver` project. Then move the `server` folder to your working directory. 53 | 54 | 4. Install the dependencies for both projects as well as the server: 55 | 56 | ``` 57 | cd RNFoodDelivery 58 | yarn install 59 | ``` 60 | 61 | ``` 62 | cd RNFoodDeliveryDriver 63 | yarn install 64 | ``` 65 | 66 | ``` 67 | cd server 68 | yarn install 69 | ``` 70 | 71 | 5. Update the `.env` file on both projects with your credentials. Do the same for the `server/.env` file. 72 | 73 | 6. Link the following packages manually: 74 | 75 | - react-native-permissions 76 | - react-native-config 77 | - react-native-google-places 78 | 79 | 7. Update the `android/app/src/main/AndroidManifest.xml` file with the required permissions and Google API key: 80 | 81 | ``` 82 | 84 | 85 | 86 | 87 | 88 | 89 | ... 90 | 91 | ``` 92 | 93 | ``` 94 | 95 | 98 | 99 | ``` 100 | 101 | 8. Run the server: 102 | 103 | ``` 104 | cd server 105 | node index.js 106 | ``` 107 | 108 | 9. Expose the server using ngrok 109 | 110 | ``` 111 | ~/ngrok http 5000 112 | ``` 113 | 114 | 10. Run the two apps. The first instance runs the bundler on a different port from the default one. Be sure to set the port (from the dev settings) then disconnect the device before running the second app: 115 | 116 | ``` 117 | cd RNFoodDelivery 118 | react-native start --port=8080 119 | react-native run-android 120 | ``` 121 | 122 | ``` 123 | cd RNFoodDeliveryDriver 124 | react-native start 125 | react-native run-android 126 | ``` 127 | 128 | ## Built with 129 | 130 | - [React Native](https://facebook.github.io/react-native/) 131 | - [Channels](https://pusher.com/channels) 132 | - [Chatkit](https://pusher.com/chatkit) 133 | - [Beams](https://pusher.com/beams) 134 | - [React Native Maps](https://github.com/react-native-community/react-native-maps) 135 | 136 | ## Donation 137 | 138 | If this project helped you reduce time to develop, please consider buying me a cup of coffee :) 139 | 140 | Buy Me A Coffee 141 | -------------------------------------------------------------------------------- /driver-app/.env: -------------------------------------------------------------------------------- 1 | CHANNELS_APP_KEY="YOUR CHANNELS APP KEY" 2 | CHANNELS_APP_CLUSTER="YOUR CHANNELS APP CLUSTER" 3 | 4 | CHATKIT_INSTANCE_LOCATOR_ID="YOUR CHATKIT INSTANCE LOCATOR ID" 5 | CHATKIT_SECRET_KEY="YOUR CHATKIT SECRET KEY" 6 | CHATKIT_TOKEN_PROVIDER_ENDPOINT="YOUR CHATKIT TOKEN PROVIDER ENDPOINT" 7 | 8 | GOOGLE_API_KEY="YOUR GOOGLE API KEY" 9 | 10 | NGROK_HTTPS_URL="YOUR NGROK HTTPS URL" 11 | 12 | BEAMS_INSTANCE_ID="YOUR DRIVER APP BEAMS INSTANCE ID" -------------------------------------------------------------------------------- /driver-app/App.js: -------------------------------------------------------------------------------- 1 | import React, {Fragment} from 'react'; 2 | import {SafeAreaView, StatusBar, View, StyleSheet} from 'react-native'; 3 | 4 | import Root from './Root'; 5 | 6 | const App = () => { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | // 18 | 19 | const styles = StyleSheet.create({ 20 | container: { 21 | flex: 1, 22 | }, 23 | }); 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /driver-app/Root.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {YellowBox} from 'react-native'; 3 | 4 | import {createAppContainer} from 'react-navigation'; 5 | import {createStackNavigator} from 'react-navigation-stack'; 6 | 7 | import OrderMap from './src/screens/OrderMap'; 8 | import ContactCustomer from './src/screens/ContactCustomer'; 9 | 10 | YellowBox.ignoreWarnings(['Setting a timer']); 11 | 12 | const RootStack = createStackNavigator( 13 | { 14 | OrderMap, 15 | ContactCustomer, 16 | }, 17 | { 18 | initialRouteName: 'OrderMap', 19 | }, 20 | ); 21 | 22 | const AppContainer = createAppContainer(RootStack); 23 | 24 | class Router extends Component { 25 | render() { 26 | return ; 27 | } 28 | } 29 | 30 | export default Router; 31 | -------------------------------------------------------------------------------- /driver-app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RNFoodDeliveryDriver", 3 | "displayName": "RNFoodDeliveryDriver" 4 | } -------------------------------------------------------------------------------- /driver-app/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /driver-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RNFoodDeliveryDriver", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "test": "jest", 10 | "lint": "eslint ." 11 | }, 12 | "dependencies": { 13 | "@pusher/chatkit-client": "^1.13.1", 14 | "@react-native-community/netinfo": "^4.4.0", 15 | "axios": "^0.19.0", 16 | "pusher-js": "^5.0.2", 17 | "react": "16.9.0", 18 | "react-native": "0.61.2", 19 | "react-native-config": "^0.11.7", 20 | "react-native-geocoding": "^0.4.0", 21 | "react-native-geolocation-service": "^3.1.0", 22 | "react-native-gesture-handler": "^1.4.1", 23 | "react-native-gifted-chat": "^0.11.0", 24 | "react-native-maps": "0.25.0", 25 | "react-native-maps-directions": "^1.7.3", 26 | "react-native-modal": "^11.4.0", 27 | "react-native-permissions": "^2.0.2", 28 | "react-native-reanimated": "^1.3.0", 29 | "react-native-screens": "^1.0.0-alpha.23", 30 | "react-navigation": "^4.0.10", 31 | "react-navigation-stack": "^1.9.4", 32 | "react-native-pusher-push-notifications": "git+http://git@github.com/ZeptInc/react-native-pusher-push-notifications#v.2.4.0-zept-master" 33 | }, 34 | "devDependencies": { 35 | "@babel/core": "^7.6.3", 36 | "@babel/runtime": "^7.6.3", 37 | "@react-native-community/eslint-config": "^0.0.5", 38 | "babel-jest": "^24.9.0", 39 | "eslint": "^6.5.1", 40 | "jest": "^24.9.0", 41 | "metro-react-native-babel-preset": "^0.56.0", 42 | "react-test-renderer": "16.9.0" 43 | }, 44 | "jest": { 45 | "preset": "react-native" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /driver-app/src/helpers/location.js: -------------------------------------------------------------------------------- 1 | export function regionFrom(lat, lon, accuracy) { 2 | const oneDegreeOfLongitudeInMeters = 111.32 * 1000; 3 | const circumference = (40075 / 360) * 1000; 4 | 5 | const latDelta = accuracy * (1 / (Math.cos(lat) * circumference)); 6 | const lonDelta = accuracy / oneDegreeOfLongitudeInMeters; 7 | 8 | return { 9 | latitude: lat, 10 | longitude: lon, 11 | latitudeDelta: Math.max(0, latDelta), 12 | longitudeDelta: Math.max(0, lonDelta), 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /driver-app/src/screens/ContactCustomer.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {View, StyleSheet} from 'react-native'; 3 | 4 | import {GiftedChat} from 'react-native-gifted-chat'; 5 | import {ChatManager, TokenProvider} from '@pusher/chatkit-client'; 6 | 7 | import Config from 'react-native-config'; 8 | 9 | const CHATKIT_INSTANCE_LOCATOR_ID = Config.CHATKIT_INSTANCE_LOCATOR_ID; 10 | const CHATKIT_SECRET_KEY = Config.CHATKIT_SECRET_KEY; 11 | const CHATKIT_TOKEN_PROVIDER_ENDPOINT = Config.CHATKIT_TOKEN_PROVIDER_ENDPOINT; 12 | 13 | class ContactCustomer extends Component { 14 | static navigationOptions = ({navigation}) => { 15 | return { 16 | title: 'Contact Customer', 17 | }; 18 | }; 19 | 20 | state = { 21 | messages: [], 22 | }; 23 | 24 | constructor(props) { 25 | super(props); 26 | this.user_id = this.props.navigation.getParam('user_id'); 27 | this.room_id = this.props.navigation.getParam('room_id'); 28 | } 29 | 30 | async componentDidMount() { 31 | try { 32 | const chatManager = new ChatManager({ 33 | instanceLocator: CHATKIT_INSTANCE_LOCATOR_ID, 34 | userId: this.user_id, 35 | tokenProvider: new TokenProvider({ 36 | url: CHATKIT_TOKEN_PROVIDER_ENDPOINT, 37 | }), 38 | }); 39 | 40 | let currentUser = await chatManager.connect(); 41 | this.currentUser = currentUser; 42 | 43 | await this.currentUser.subscribeToRoomMultipart({ 44 | roomId: this.room_id, 45 | hooks: { 46 | onMessage: this._onMessage, 47 | }, 48 | messageLimit: 30, 49 | }); 50 | } catch (err) { 51 | console.log('chatkit error: ', err); 52 | } 53 | } 54 | 55 | _onMessage = data => { 56 | const {message} = this._getMessage(data); 57 | 58 | this.setState(previousState => ({ 59 | messages: GiftedChat.append(previousState.messages, message), 60 | })); 61 | }; 62 | 63 | _getMessage = ({id, sender, parts, createdAt}) => { 64 | const text = parts.find(part => part.partType === 'inline').payload.content; 65 | 66 | const msg_data = { 67 | _id: id, 68 | text: text, 69 | createdAt: new Date(createdAt), 70 | user: { 71 | _id: sender.id.toString(), 72 | name: sender.name, 73 | avatar: `https://na.ui-avatars.com/api/?name=${sender.name}`, 74 | }, 75 | }; 76 | 77 | return { 78 | message: msg_data, 79 | }; 80 | }; 81 | 82 | render() { 83 | const {messages} = this.state; 84 | 85 | return ( 86 | 87 | this._onSend(messages)} 90 | showUserAvatar={true} 91 | user={{ 92 | _id: this.user_id, 93 | }} 94 | /> 95 | 96 | ); 97 | } 98 | 99 | _onSend = async ([message]) => { 100 | try { 101 | await this.currentUser.sendSimpleMessage({ 102 | roomId: this.room_id, 103 | text: message.text, 104 | }); 105 | } catch (send_msg_err) { 106 | console.log('error sending message: ', send_msg_err); 107 | } 108 | }; 109 | } 110 | // 111 | 112 | const styles = StyleSheet.create({ 113 | wrapper: { 114 | flex: 1, 115 | }, 116 | }); 117 | 118 | export default ContactCustomer; 119 | -------------------------------------------------------------------------------- /driver-app/src/screens/OrderMap.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | View, 4 | Text, 5 | Button, 6 | TouchableOpacity, 7 | Alert, 8 | StyleSheet, 9 | } from 'react-native'; 10 | 11 | import MapView from 'react-native-maps'; 12 | import Pusher from 'pusher-js/react-native'; 13 | 14 | import {check, request, PERMISSIONS, RESULTS} from 'react-native-permissions'; 15 | 16 | import Geolocation from 'react-native-geolocation-service'; 17 | import Modal from 'react-native-modal'; 18 | import Config from 'react-native-config'; 19 | 20 | import MapViewDirections from 'react-native-maps-directions'; 21 | import axios from 'axios'; 22 | import RNPusherPushNotifications from 'react-native-pusher-push-notifications'; 23 | 24 | import {regionFrom} from '../helpers/location'; 25 | 26 | const CHANNELS_APP_KEY = Config.CHANNELS_APP_KEY; 27 | const CHANNELS_APP_CLUSTER = Config.CHANNELS_APP_CLUSTER; 28 | const BASE_URL = Config.NGROK_HTTPS_URL; 29 | 30 | const GOOGLE_API_KEY = Config.GOOGLE_API_KEY; 31 | 32 | RNPusherPushNotifications.setInstanceId(Config.BEAMS_INSTANCE_ID); 33 | 34 | const subscribeToRoom = room_id => { 35 | RNPusherPushNotifications.subscribe( 36 | room_id, 37 | (statusCode, response) => { 38 | console.error(statusCode, response); 39 | }, 40 | () => { 41 | console.log('Success'); 42 | }, 43 | ); 44 | }; 45 | 46 | const triggerNotification = async (room_id, push_type, data) => { 47 | try { 48 | await axios.post(`${BASE_URL}/push/${room_id}`, { 49 | push_type, 50 | data, 51 | }); 52 | } catch (err) { 53 | console.log('error triggering notification: ', err); 54 | } 55 | }; 56 | 57 | class OrderMap extends Component { 58 | static navigationOptions = ({navigation}) => { 59 | const showHeaderButton = navigation.getParam('showHeaderButton'); 60 | return { 61 | title: 'Order Map', 62 | headerRight: showHeaderButton ? ( 63 | 64 |