├── .expo-shared └── assets.json ├── .gitignore ├── App.js ├── app.json ├── assets ├── icon.png └── splash.png ├── babel.config.js ├── components ├── Dashboard │ ├── Dashboard.js │ └── styles.js ├── Map │ ├── MapView.js │ └── styles.js ├── Menu │ ├── Menu.js │ └── styles.js ├── Navigation │ └── Navigator.js ├── Place │ ├── PlaceList.js │ └── styles.js ├── Profile │ ├── Profile.js │ └── styles.js ├── Review │ └── ReviewStars.js └── SearchBar │ ├── SearchBar.js │ └── styles.js ├── package.json ├── yarn-error.log └── yarn.lock /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true, 3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | .env 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | web-report/ 13 | 14 | # macOS 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | //Components 3 | import Navigator from "./components/Navigation/Navigator"; 4 | //Encapsulate every other component inside the navigation 5 | export default function App() { 6 | return ( 7 | <> 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Real-world React Native app using Google Places and Maps", 4 | "slug": "rn-google-maps-places", 5 | "privacy": "public", 6 | "sdkVersion": "36.0.0", 7 | "platforms": ["ios", "android", "web"], 8 | "version": "1.0.0", 9 | "orientation": "portrait", 10 | "icon": "./assets/icon.png", 11 | "splash": { 12 | "image": "./assets/splash.png", 13 | "resizeMode": "contain", 14 | "backgroundColor": "#ffffff" 15 | }, 16 | "updates": { 17 | "fallbackToCacheTimeout": 0 18 | }, 19 | "assetBundlePatterns": ["**/*"], 20 | "ios": { 21 | "supportsTablet": true 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blarzHernandez/react-native-google-map/e91a31f116f457e0ed6abf31137228687a3fc025/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blarzHernandez/react-native-google-map/e91a31f116f457e0ed6abf31137228687a3fc025/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo", "module:react-native-dotenv"] 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /components/Dashboard/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Container, Content } from "native-base"; 3 | 4 | //Components 5 | import SearchBar from "../SearchBar/SearchBar"; 6 | import MenuItem from "../Menu/Menu"; 7 | 8 | //Styles 9 | import styles from "./styles"; 10 | class Dashboard extends Component { 11 | static navigationOptions = { 12 | headerTitle: "Find all Around Me!" 13 | }; 14 | render() { 15 | const { navigation } = this.props; 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | } 26 | 27 | export default Dashboard; 28 | -------------------------------------------------------------------------------- /components/Dashboard/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | flex: 1, 6 | justifyContent: "center", 7 | alignItems: "center", 8 | flexWrap: "wrap", 9 | alignContent: "center", 10 | paddingHorizontal: 20, 11 | paddingVertical: 20 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /components/Map/MapView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import MapView, { PROVIDER_GOOGLE } from "react-native-maps"; 3 | import { View } from "react-native"; 4 | import { GOOGLE_API_KEY } from "react-native-dotenv"; 5 | 6 | //Components 7 | import PlaceList from "../Place/PlaceList"; 8 | //Styles 9 | import styles from "./styles"; 10 | class MapScreen extends Component { 11 | //Set the HeaderTitle screen 12 | static navigationOptions = props => { 13 | const placeName = props.navigation.getParam("placeName"); 14 | return { headerTitle: placeName.toUpperCase() }; 15 | }; 16 | constructor(props) { 17 | super(props); 18 | //Initial State 19 | this.state = { 20 | lat: null, 21 | long: null, 22 | places: [], 23 | isLoading: false, 24 | placeType: "restaurant" 25 | }; 26 | } 27 | componentDidMount() { 28 | console.log(this.props); 29 | const { navigation } = this.props; 30 | const placeType = navigation.getParam("placeType"); 31 | this.setState({ placeType: placeType }); 32 | 33 | this.getCurrentLocation(); 34 | } 35 | /** 36 | * Get current user's position 37 | */ 38 | getCurrentLocation() { 39 | navigator.geolocation.getCurrentPosition(position => { 40 | const lat = position.coords.latitude; 41 | const long = position.coords.longitude; 42 | this.setState({ lat: lat, long: long }); 43 | this.getPlaces(); 44 | }); 45 | } 46 | 47 | /** 48 | * Get the Place URL 49 | */ 50 | getPlacesUrl(lat, long, radius, type, apiKey) { 51 | const baseUrl = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?`; 52 | const location = `location=${lat},${long}&radius=${radius}`; 53 | const typeData = `&types=${type}`; 54 | const api = `&key=${apiKey}`; 55 | return `${baseUrl}${location}${typeData}${api}`; 56 | } 57 | 58 | getPlaces() { 59 | const { lat, long, placeType } = this.state; 60 | const markers = []; 61 | const url = this.getPlacesUrl(lat, long, 1500, placeType, GOOGLE_API_KEY); 62 | fetch(url) 63 | .then(res => res.json()) 64 | .then(res => { 65 | res.results.map((element, index) => { 66 | const marketObj = {}; 67 | marketObj.id = element.id; 68 | marketObj.name = element.name; 69 | marketObj.photos = element.photos; 70 | marketObj.rating = element.rating; 71 | marketObj.vicinity = element.vicinity; 72 | marketObj.marker = { 73 | latitude: element.geometry.location.lat, 74 | longitude: element.geometry.location.lng 75 | }; 76 | 77 | markers.push(marketObj); 78 | }); 79 | //update our places array 80 | this.setState({ places: markers }); 81 | }); 82 | } 83 | 84 | render() { 85 | const { lat, long, places } = this.state; 86 | return ( 87 | 88 | 89 | 101 | {places.map((marker, i) => ( 102 | 110 | ))} 111 | 112 | 113 | 114 | 115 | 116 | 117 | ); 118 | } 119 | } 120 | 121 | export default MapScreen; 122 | -------------------------------------------------------------------------------- /components/Map/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | flex: 1, 6 | justifyContent: "center" 7 | }, 8 | mapView: { 9 | flex: 1, 10 | justifyContent: "center", 11 | height: "50%", 12 | width: "100%" 13 | }, 14 | placeList: { 15 | flex: 1, 16 | justifyContent: "center" 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /components/Menu/Menu.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Text, View } from "native-base"; 3 | import { TouchableOpacity } from "react-native"; 4 | import { Icon } from "react-native-elements"; 5 | import styles from "./styles"; 6 | 7 | class Menu extends Component { 8 | /** 9 | * @param {String} name Icon name 10 | * @param {String} text place name 11 | * @param {Number} size Icon size 12 | * @param {String} color Icon color 13 | * @param {String} type Icon type 14 | * @param {String} placeType Place type to look up 15 | 16 | */ 17 | getItem = (name, text, size, color, type, placeType) => ( 18 | 20 | this.props.navigation.navigate("MapView", { 21 | placeType: placeType, 22 | placeName: text 23 | }) 24 | } 25 | > 26 | 27 | 35 | {text} 36 | 37 | 38 | ); 39 | render() { 40 | return ( 41 | 42 | 43 | {this.getItem("beer", "Beers", 40, "#f50", "font-awesome", "bar")} 44 | {this.getItem("bank", "Bank", 40, "#031068", "font-awesome", "bank")} 45 | {this.getItem( 46 | "coffee", 47 | "Coffee", 48 | 40, 49 | "#300423", 50 | "font-awesome", 51 | "cafe" 52 | )} 53 | {this.getItem("md-fitness", "Gym", 40, "#0B6CFB", "ionicon", "gym")} 54 | {this.getItem( 55 | "bus", 56 | "Bus Station", 57 | 40, 58 | "#056C6B", 59 | "font-awesome", 60 | "bus_station" 61 | )} 62 | {this.getItem( 63 | "hotel", 64 | "Hotel", 65 | 40, 66 | "#0A23A6", 67 | "font-awesome", 68 | "book_store" 69 | )} 70 | {this.getItem( 71 | "local-pharmacy", 72 | "Pharmacy", 73 | 40, 74 | "#f50", 75 | "materialicons", 76 | "pharmacy" 77 | )} 78 | {this.getItem("movie", "Movie", 40, "#000000", "materialicons")} 79 | {this.getItem("favorite", "Favorities", 40, "#f66", "materialicons")} 80 | 81 | 82 | ); 83 | } 84 | } 85 | 86 | export default Menu; 87 | -------------------------------------------------------------------------------- /components/Menu/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | const FONT_SIZE = 18; 3 | export default StyleSheet.create({ 4 | container: { 5 | flex: 1, 6 | justifyContent: "center", 7 | alignItems: "center" 8 | }, 9 | iconContainer: { 10 | width: "100%", 11 | flexDirection: "row", 12 | flexWrap: "wrap" 13 | }, 14 | iconStyle: { 15 | flexDirection: "column", 16 | alignItems: "center", 17 | padding: 5 18 | }, 19 | textStyle: { 20 | fontSize: FONT_SIZE 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /components/Navigation/Navigator.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createAppContainer } from "react-navigation"; 3 | import { createBottomTabNavigator } from "react-navigation-tabs"; 4 | import { createStackNavigator } from "react-navigation-stack"; 5 | import Ionicons from "react-native-vector-icons/Ionicons"; 6 | import Icon from "react-native-vector-icons/FontAwesome"; 7 | import DashboardScreen from "../../components/Dashboard/Dashboard"; 8 | import ProfileScreen from "../../components/Profile/Profile"; 9 | import MapScreen from "../../components/Map/MapView"; 10 | //Screen in the Home tab 11 | const DashboardContainer = createStackNavigator( 12 | { 13 | Home: DashboardScreen, 14 | MapView: MapScreen 15 | }, 16 | { 17 | initialRouteName: "Home" 18 | } 19 | ); 20 | //The Main Tab =>Home - Profile add more tabs here.. 21 | const bottomTab = createBottomTabNavigator( 22 | { 23 | Home: { 24 | screen: DashboardContainer, 25 | navigationOptions: { 26 | tabBarLabel: "Home", 27 | tabBarIcon: ({ focused }) => ( 28 | 33 | ), 34 | style: { 35 | backgroundColor: "red" 36 | } 37 | } 38 | }, 39 | Profile: { 40 | screen: ProfileScreen, 41 | navigationOptions: ({ navigation }) => ({ 42 | title: "Profile", 43 | tabBarIcon: ({ focused }) => ( 44 | 49 | ) 50 | }) 51 | } 52 | }, 53 | { 54 | navigationOptions: { 55 | tabBarOptions: { 56 | activeTintColor: "#e90000", 57 | inactiveTintColor: "#575757", 58 | style: { 59 | backgroundColor: "#f2f2f2", 60 | height: 60 61 | } 62 | } 63 | } 64 | } 65 | ); 66 | //Getting the tab header title 67 | bottomTab.navigationOptions = ({ navigation }) => { 68 | const { routeName } = navigation.state.routes[navigation.state.index]; 69 | const headerTitle = routeName; 70 | return { 71 | headerTitle 72 | }; 73 | }; 74 | 75 | //Root navigator 76 | const AppNavigator = createStackNavigator( 77 | { 78 | Home: bottomTab 79 | }, 80 | { 81 | initialRouteName: "Home", 82 | headerMode: "none" 83 | } 84 | ); 85 | export default createAppContainer(AppNavigator); 86 | -------------------------------------------------------------------------------- /components/Place/PlaceList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { 3 | FlatList, 4 | TouchableOpacity, 5 | View, 6 | ActivityIndicator 7 | } from "react-native"; 8 | import { ListItem, Text } from "react-native-elements"; 9 | import { Container, Content } from "native-base"; 10 | import { GOOGLE_API_KEY } from "react-native-dotenv"; 11 | 12 | //Components 13 | import RenderStarReview from "../../components/Review/ReviewStars"; 14 | import styles from "./styles"; 15 | 16 | class PlaceList extends Component { 17 | render() { 18 | const { places } = this.props; 19 | const baseImage = 20 | "https://images.unsplash.com/photo-1552334405-4929565998d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80"; 21 | return ( 22 | 23 | 24 | {places.length <= 0 && ( 25 | 26 | 27 | 28 | )} 29 | {places.length > 0 && ( 30 | ( 33 | 34 | 38 | {item.name} 39 | 1.4km 40 | 41 | } 42 | subtitle={ 43 | item.rating && ( 44 | 45 | 46 | 47 | {item.rating.toFixed(1)} 48 | 49 | 50 | {item.vicinity} 51 | 52 | 53 | ) 54 | } 55 | leftAvatar={{ 56 | rounded: false, 57 | size: "large", 58 | source: item.photos && { 59 | uri: 60 | item.photos.length > 0 61 | ? `https://maps.googleapis.com/maps/api/place/photo?photoreference=${item.photos[0].photo_reference}&sensor=false&maxheight=${item.photos[0].height}&maxwidth=${item.photos[0].width}&key=${GOOGLE_API_KEY}` 62 | : baseImage 63 | } 64 | }} 65 | bottomDivider 66 | chevron={{ color: "#e90000", size: 30 }} 67 | /> 68 | 69 | )} 70 | keyExtractor={item => item.id.toString()} 71 | /> 72 | )} 73 | 74 | 75 | ); 76 | } 77 | } 78 | 79 | export default PlaceList; 80 | -------------------------------------------------------------------------------- /components/Place/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | flex: 1, 6 | justifyContent: "space-between" 7 | }, 8 | container2: { 9 | flex: 1, 10 | justifyContent: "center" 11 | }, 12 | menuTitle: { 13 | fontSize: 16, 14 | fontFamily: "Poppins-Medium", 15 | color: "#575757", 16 | marginLeft: 20, 17 | marginTop: 10 18 | }, 19 | mapView: { 20 | flex: 1, 21 | justifyContent: "center" 22 | }, 23 | restaurantList: { 24 | flex: 1, 25 | justifyContent: "center" 26 | }, 27 | chevron: { 28 | color: "#e90000" 29 | }, 30 | rowDirection: { 31 | flexDirection: "row", 32 | justifyContent: "space-between" 33 | }, 34 | startReviewsContainer: { 35 | flexDirection: "row", 36 | justifyContent: "flex-start" 37 | }, 38 | loaderContainer: { 39 | flex: 1, 40 | justifyContent: "center" 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /components/Profile/Profile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Container, View } from "native-base"; 3 | import { Image } from "react-native"; 4 | import { Text } from "react-native-elements"; 5 | import styles from "./styles"; 6 | 7 | class ProfileScreen extends Component { 8 | static navigationOptions = { 9 | headerTitle: "Profile" 10 | }; 11 | 12 | render() { 13 | const imagePlaceholder = "https://via.placeholder.com/150"; 14 | 15 | return ( 16 | 17 | 28 | 29 | 37 | 44 | Name: 45 | Name 46 | 47 | 54 | Email: 55 | Email 56 | 57 | 58 | 59 | ); 60 | } 61 | } 62 | 63 | export default ProfileScreen; 64 | -------------------------------------------------------------------------------- /components/Profile/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | flex: 1, 6 | justifyContent: "center", 7 | alignItems: "center", 8 | flexWrap: "wrap", 9 | marginTop: 0, 10 | paddingHorizontal: 20 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /components/Review/ReviewStars.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View } from "react-native"; 3 | import { Icon } from "react-native-elements"; 4 | 5 | class ReviewStars extends Component { 6 | FullStar = key => ( 7 | 8 | ); 9 | 10 | HalfStar = key => ( 11 | 18 | ); 19 | 20 | EmptyStar = key => ( 21 | 28 | ); 29 | render() { 30 | const { stars } = this.props; 31 | let starReviews = []; 32 | for (let i = 1; i <= 5; i++) { 33 | let star = this.FullStar(i); 34 | if (i > stars) { 35 | star = this.EmptyStar(i); 36 | } 37 | starReviews.push(star); 38 | } 39 | return {starReviews}; 40 | } 41 | } 42 | 43 | export default ReviewStars; 44 | -------------------------------------------------------------------------------- /components/SearchBar/SearchBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { SearchBar } from "react-native-elements"; 3 | import styles from "./styles"; 4 | export default class Search extends Component { 5 | render() { 6 | return ( 7 | 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /components/SearchBar/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | 3 | export default StyleSheet.create({ 4 | searchContainer: { 5 | borderBottomColor: "#ECECEC", 6 | borderBottomWidth: 2 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject" 9 | }, 10 | "dependencies": { 11 | "expo": "~36.0.0", 12 | "native-base": "^2.13.8", 13 | "react": "~16.9.0", 14 | "react-dom": "~16.9.0", 15 | "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.0.tar.gz", 16 | "react-native-dotenv": "^0.2.0", 17 | "react-native-elements": "^1.2.7", 18 | "react-native-gesture-handler": "~1.5.0", 19 | "react-native-maps": "0.26.1", 20 | "react-native-reanimated": "~1.4.0", 21 | "react-native-screens": "2.0.0-alpha.12", 22 | "react-native-web": "~0.11.7", 23 | "react-navigation": "^4.0.10", 24 | "react-navigation-stack": "^1.10.3", 25 | "react-navigation-tabs": "^2.6.2" 26 | }, 27 | "devDependencies": { 28 | "babel-preset-expo": "~8.0.0" 29 | }, 30 | "private": true 31 | } 32 | --------------------------------------------------------------------------------