├── assets ├── icon.png ├── splash.png ├── favicon.png └── adaptive-icon.png ├── src ├── constants │ ├── colors.js │ ├── index.js │ └── styles.js ├── components │ ├── index.js │ ├── Button.js │ ├── CloseButton.js │ ├── NoMoreMatches.js │ ├── MatchButton.js │ ├── UserCard.js │ └── ChatItem.js └── screens │ ├── UserProfile.js │ ├── AllMessages.js │ ├── Profile.js │ ├── Chat.js │ └── Explore.js ├── screenshots ├── chat.png ├── explore.png └── messages.png ├── babel.config.js ├── .expo-shared └── assets.json ├── .gitignore ├── fakedata ├── users.json └── messages.json ├── app.json ├── package.json ├── README.md └── App.js /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronmoreau/react-native-dating-app/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronmoreau/react-native-dating-app/HEAD/assets/splash.png -------------------------------------------------------------------------------- /src/constants/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | primaryColor: "#FA795C", 3 | textColor: "#333", 4 | }; 5 | -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronmoreau/react-native-dating-app/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /screenshots/chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronmoreau/react-native-dating-app/HEAD/screenshots/chat.png -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronmoreau/react-native-dating-app/HEAD/assets/adaptive-icon.png -------------------------------------------------------------------------------- /screenshots/explore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronmoreau/react-native-dating-app/HEAD/screenshots/explore.png -------------------------------------------------------------------------------- /screenshots/messages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronmoreau/react-native-dating-app/HEAD/screenshots/messages.png -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | import Colors from "./colors"; 2 | import Styles from "./styles"; 3 | 4 | export { Colors, Styles }; 5 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | 12 | # macOS 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import ChatItem from "./ChatItem"; 2 | import UserCard from "./UserCard"; 3 | import MatchButton from "./MatchButton"; 4 | import Button from "./Button"; 5 | import NoMoreMatches from "./NoMoreMatches"; 6 | import CloseButton from "./CloseButton"; 7 | 8 | export { ChatItem, UserCard, MatchButton, Button, CloseButton, NoMoreMatches }; 9 | -------------------------------------------------------------------------------- /src/constants/styles.js: -------------------------------------------------------------------------------- 1 | import { Colors } from "./"; 2 | 3 | const Header = { 4 | headerTintColor: Colors.primaryColor, 5 | headerStyle: { 6 | backgroundColor: "#FFF", 7 | }, 8 | headerTitleStyle: { 9 | fontWeight: "800", 10 | color: Colors.textColor, 11 | }, 12 | }; 13 | 14 | const Tabs = { 15 | tabBarActiveTintColor: "#F87961", 16 | tabBarStyle: { 17 | backgroundColor: "white", 18 | borderTopWidth: 0, 19 | shadowOpacity: 0.05, 20 | shadowRadius: 4, 21 | }, 22 | }; 23 | 24 | export default { Header, Tabs }; 25 | -------------------------------------------------------------------------------- /fakedata/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "1337": { 3 | "name": "Ryan", 4 | "avatar": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Ryan_Gosling_2_Cannes_2011_%28cropped%29.jpg/220px-Ryan_Gosling_2_Cannes_2011_%28cropped%29.jpg" 5 | }, 6 | "1": { 7 | "name": "Sarah", 8 | "avatar": "https://randomuser.me/api/portraits/women/28.jpg" 9 | }, 10 | "2": { 11 | "name": "Allie", 12 | "avatar": "https://randomuser.me/api/portraits/women/69.jpg" 13 | }, 14 | "3": { 15 | "name": "Shelby", 16 | "avatar": "https://s-media-cache-ak0.pinimg.com/736x/e2/8d/68/e28d686dc0fd1d9da33ecea00a3229e7--girl-photography-photography-ideas.jpg" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Button.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Text, TouchableOpacity } from "react-native"; 3 | import { Colors } from "../constants"; 4 | 5 | const Button = ({ children, onPress, style }) => { 6 | return ( 7 | 8 | {children} 9 | 10 | ); 11 | }; 12 | 13 | const styles = { 14 | container: { 15 | backgroundColor: Colors.primaryColor, 16 | borderRadius: 4, 17 | padding: 10, 18 | alignItems: "center", 19 | }, 20 | text: { 21 | color: "white", 22 | fontSize: 16, 23 | fontWeight: "600", 24 | }, 25 | }; 26 | 27 | export default Button; 28 | -------------------------------------------------------------------------------- /src/components/CloseButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TouchableOpacity, StyleSheet } from "react-native"; 3 | import { Ionicons } from "@expo/vector-icons"; 4 | 5 | const CloseButton = ({ style, onPress }) => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | const styles = StyleSheet.create({ 14 | container: { 15 | width: 36, 16 | height: 36, 17 | borderRadius: 18, 18 | backgroundColor: "rgba(0,0,0,0.6)", 19 | alignItems: "center", 20 | justifyContent: "center", 21 | paddingTop: 2, 22 | }, 23 | }); 24 | 25 | export default CloseButton; 26 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "matched", 4 | "slug": "matched", 5 | "version": "1.1.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 | "ios": { 18 | "supportsTablet": true 19 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/adaptive-icon.png", 23 | "backgroundColor": "#FFFFFF" 24 | } 25 | }, 26 | "web": { 27 | "favicon": "./assets/favicon.png" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/screens/UserProfile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View, Image } from "react-native"; 3 | import { CloseButton } from "../components"; 4 | 5 | import Users from "../../fakedata/users.json"; 6 | 7 | class UserProfile extends Component { 8 | render() { 9 | const { navigation, route } = this.props; 10 | const { imageUrl } = route.params; 11 | 12 | return ( 13 | 14 | 15 | navigation.goBack()} 17 | style={{ 18 | position: "absolute", 19 | top: 35, 20 | left: 20, 21 | }} 22 | /> 23 | 24 | ); 25 | } 26 | } 27 | 28 | export default UserProfile; 29 | -------------------------------------------------------------------------------- /fakedata/messages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "user": 2, 5 | "messages": [ 6 | { 7 | "text": "🙃", 8 | "user": 1337 9 | }, 10 | { 11 | "text": "No", 12 | "user": 2 13 | }, 14 | { 15 | "text": "Hey, when you hve a headache, do you take an Advil or Allie-ve?", 16 | "user": 1337 17 | } 18 | ] 19 | }, 20 | { 21 | "id": 2, 22 | "user": 1, 23 | "messages": [ 24 | { 25 | "text": "Hey, visit http://virus.com/exe to chat", 26 | "user": 1 27 | }, 28 | { 29 | "text": "Hi", 30 | "user": 1337 31 | } 32 | ] 33 | }, 34 | { 35 | "id": 3, 36 | "user": 3, 37 | "messages": [ 38 | { 39 | "text": "Shelby has unmatched with you", 40 | "user": 3 41 | }, 42 | { 43 | "text": "Date me pls", 44 | "user": 1337 45 | } 46 | ] 47 | } 48 | ] 49 | -------------------------------------------------------------------------------- /src/components/NoMoreMatches.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Text, View, StyleSheet } from "react-native"; 3 | import { Ionicons } from "@expo/vector-icons"; 4 | 5 | import Button from "./Button"; 6 | 7 | const NoMoreMatches = ({ onReloadPress }) => { 8 | return ( 9 | 10 | 11 | 12 | No more matches for today, see you tomorrow 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | const styles = StyleSheet.create({ 20 | container: { 21 | flex: 1, 22 | alignItems: "center", 23 | justifyContent: "center", 24 | padding: 22, 25 | }, 26 | text: { 27 | color: "rgba(0,0,0,0.2)", 28 | fontSize: 22, 29 | fontWeight: "700", 30 | textAlign: "center", 31 | marginBottom: 12, 32 | }, 33 | }); 34 | 35 | export default NoMoreMatches; 36 | -------------------------------------------------------------------------------- /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 | "@react-native-community/masked-view": "0.1.10", 12 | "@react-navigation/bottom-tabs": "^6.0.9", 13 | "@react-navigation/native": "^6.0.6", 14 | "@react-navigation/native-stack": "^6.2.5", 15 | "@react-navigation/stack": "^5.11.1", 16 | "expo": "^43.0.0", 17 | "expo-linear-gradient": "~10.0.3", 18 | "react": "17.0.1", 19 | "react-dom": "17.0.1", 20 | "react-native": "https://github.com/expo/react-native/archive/sdk-43.tar.gz", 21 | "react-native-animatable": "^1.3.3", 22 | "react-native-gifted-chat": "^0.16.3", 23 | "react-native-safe-area-context": "3.3.2", 24 | "react-native-screens": "~3.8.0" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.12.9" 28 | }, 29 | "private": true 30 | } 31 | -------------------------------------------------------------------------------- /src/components/MatchButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TouchableOpacity, StyleSheet } from "react-native"; 3 | import { Ionicons } from "@expo/vector-icons"; 4 | 5 | import * as Animatable from "react-native-animatable"; 6 | 7 | const MatchButton = ({ icon, iconColor, onPress }) => { 8 | return ( 9 | 10 | 16 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | const SIZE = 60; 28 | const styles = StyleSheet.create({ 29 | container: { 30 | width: SIZE, 31 | height: SIZE, 32 | backgroundColor: "white", 33 | borderRadius: SIZE / 2, 34 | alignItems: "center", 35 | justifyContent: "center", 36 | shadowOpacity: 0.2, 37 | shadowRadius: 4, 38 | shadowOffset: { width: 0.5, height: 0.5 }, 39 | }, 40 | }); 41 | 42 | export default MatchButton; 43 | -------------------------------------------------------------------------------- /src/screens/AllMessages.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View, FlatList } from "react-native"; 3 | 4 | import { ChatItem } from "../components"; 5 | 6 | // Fake data 7 | import Messages from "../../fakedata/messages.json"; 8 | import Users from "../../fakedata/users.json"; 9 | 10 | class AllMessages extends Component { 11 | _chatItemPressed = (chatId) => { 12 | this.props.navigation.navigate("Chat", { id: chatId }); 13 | }; 14 | 15 | _renderChatItem = ({ item }) => { 16 | const id = item.id; 17 | const message = item.messages[0]; 18 | const user = Users[item.user]; 19 | 20 | return ( 21 | this._chatItemPressed(id)} 26 | /> 27 | ); 28 | }; 29 | 30 | render() { 31 | return ( 32 | 33 | 35 | Object.assign({ key: item.id.toString() }, item) 36 | )} 37 | renderItem={this._renderChatItem} 38 | /> 39 | 40 | ); 41 | } 42 | } 43 | 44 | export default AllMessages; 45 | -------------------------------------------------------------------------------- /src/components/UserCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TouchableOpacity, Image, StyleSheet } from "react-native"; 3 | import { LinearGradient } from "expo-linear-gradient"; 4 | 5 | import * as Animatable from "react-native-animatable"; 6 | 7 | const UserCard = ({ imageUrl, onPress }) => { 8 | return ( 9 | 10 | 17 | 18 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | const styles = StyleSheet.create({ 28 | container: { 29 | flex: 1, 30 | borderRadius: 8, 31 | position: "relative", 32 | shadowOpacity: 0.2, 33 | shadowRadius: 4, 34 | shadowOffset: { width: 0.5, height: 0.5 }, 35 | }, 36 | image: { 37 | flex: 1, 38 | borderRadius: 8, 39 | }, 40 | gradient: { 41 | position: "absolute", 42 | left: 0, 43 | right: 0, 44 | top: 0, 45 | bottom: 0, 46 | borderRadius: 8, 47 | }, 48 | }); 49 | 50 | export default UserCard; 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Matched 2 | 3 | This project was created as an example app to using React Native with Managed Expo. The app also used React-Navigation. 4 | 5 | Managed Expo v0.39 6 | 7 | ### Demo 8 | 9 | [Demo on Expo Client](https://expo.io/@cameronmoreau/matched) 10 | 11 | or type `exp://exp.host/@cameronmoreau/matched` 12 | 13 | ### Screenshots 14 | 15 | Explore 16 | Messages 17 | Chat 18 | 19 | ## Libraries 20 | 21 | - [expo](https://github.com/expo/expo): Platform on top of react-native to manage native platform code 22 | - [react-navigation](https://github.com/react-community/react-navigation): Routing, changing pages, tabs/navbar, all that good stuff 23 | - [react-native-animatable](https://github.com/oblador/react-native-animatable): Super easy animations 24 | - [react-native-gifted-chat](https://github.com/FaridSafi/react-native-gifted-chat): Chat bubbles like iMessage 25 | 26 | ## Resources 27 | 28 | - [Learn flexbox - Game #1](http://flexboxfroggy.com/) 29 | - [Learn flexbox - Game #2](http://www.flexboxdefense.com/) 30 | - [React Native Radio - Podcast](https://devchat.tv/react-native-radio) 31 | - [Example App - F8](https://github.com/fbsamples/f8app) 32 | - [Example App - HackerNews](https://github.com/iSimar/HackerNews-React-Native) 33 | -------------------------------------------------------------------------------- /src/screens/Profile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View, Text, Image, StyleSheet, Alert } from "react-native"; 3 | import { Button } from "../components"; 4 | import { Colors } from "../constants"; 5 | 6 | import Users from "../../fakedata/users.json"; 7 | 8 | class Profile extends Component { 9 | _logout = () => { 10 | Alert.alert("// TODO: Make logout", "Well this is awkward"); 11 | }; 12 | 13 | render() { 14 | const user = Users[1337]; 15 | 16 | return ( 17 | 18 | 19 | 20 | {user.name} 21 | 22 | 25 | 26 | ); 27 | } 28 | } 29 | 30 | const styles = StyleSheet.create({ 31 | container: { 32 | flex: 1, 33 | backgroundColor: "#F8F8F9", 34 | padding: 8, 35 | }, 36 | profileCard: { 37 | backgroundColor: "white", 38 | padding: 8, 39 | borderRadius: 8, 40 | shadowOpacity: 0.08, 41 | shadowRadius: 4, 42 | shadowOffset: { width: 0.5, height: 0.5 }, 43 | alignItems: "center", 44 | }, 45 | avatar: { 46 | width: 90, 47 | height: 90, 48 | borderRadius: 45, 49 | }, 50 | name: { 51 | color: Colors.textColor, 52 | fontSize: 28, 53 | fontWeight: "700", 54 | }, 55 | }); 56 | 57 | export default Profile; 58 | -------------------------------------------------------------------------------- /src/components/ChatItem.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, Text, Image, TouchableOpacity, StyleSheet } from "react-native"; 3 | import { Colors } from "../constants"; 4 | 5 | const ChatItem = ({ user, text, date, onPress }) => { 6 | const { name, avatar } = user; 7 | 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | {name} 15 | {date} 16 | 17 | {text} 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | const AVATAR_SIZE = 60; 25 | 26 | const styles = StyleSheet.create({ 27 | container: { 28 | flexDirection: "row", 29 | padding: 8, 30 | borderBottomWidth: 1, 31 | borderBottomColor: "rgba(0,0,0,0.08)", 32 | }, 33 | contentContainer: { 34 | flex: 1, 35 | marginLeft: 8, 36 | }, 37 | header: { 38 | flexDirection: "row", 39 | justifyContent: "space-between", 40 | marginBottom: 4, 41 | }, 42 | avatar: { 43 | width: AVATAR_SIZE, 44 | height: AVATAR_SIZE, 45 | borderRadius: AVATAR_SIZE / 2, 46 | }, 47 | nameText: { 48 | fontWeight: "800", 49 | fontSize: 18, 50 | color: Colors.textColor, 51 | }, 52 | messageText: { 53 | color: Colors.textColor, 54 | }, 55 | dateText: { 56 | color: "#929292", 57 | }, 58 | }); 59 | 60 | export default ChatItem; 61 | -------------------------------------------------------------------------------- /src/screens/Chat.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View } from "react-native"; 3 | import { GiftedChat } from "react-native-gifted-chat"; 4 | 5 | // Fake data 6 | import Messages from "../../fakedata/messages.json"; 7 | import Users from "../../fakedata/users.json"; 8 | 9 | class Chat extends Component { 10 | static navigationOptions = ({ route }) => { 11 | const chatId = route.params.id; 12 | const chat = Messages.filter((item) => item.id === chatId)[0]; 13 | const user = Users[chat.user]; 14 | 15 | return { title: user.name }; 16 | }; 17 | 18 | state = { 19 | chat: null, 20 | messages: [], 21 | }; 22 | 23 | componentDidMount() { 24 | /** 25 | * Store chat and GiftedChat type messages 26 | * in state so they arent generated on 27 | * every render 28 | * https://github.com/FaridSafi/react-native-gifted-chat 29 | */ 30 | 31 | const chatId = this.props.route.params.id; 32 | const chat = Messages.filter((item) => item.id === chatId)[0]; 33 | 34 | this.setState({ 35 | chat, 36 | messages: chat.messages.map((message, index) => { 37 | const user = Users[message.user]; 38 | return { 39 | _id: index, 40 | text: message.text, 41 | user: { 42 | _id: message.user, 43 | avatar: user.avatar, 44 | name: user.name, 45 | }, 46 | }; 47 | }), 48 | }); 49 | } 50 | 51 | _sendMessage = (messages) => { 52 | this.setState((previousState) => ({ 53 | messages: GiftedChat.append(previousState.messages, messages), 54 | })); 55 | }; 56 | 57 | render() { 58 | const { messages } = this.state; 59 | return ( 60 | 61 | 68 | 69 | ); 70 | } 71 | } 72 | 73 | export default Chat; 74 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { NavigationContainer } from "@react-navigation/native"; 3 | import { createNativeStackNavigator } from "@react-navigation/native-stack"; 4 | import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; 5 | import { Ionicons } from "@expo/vector-icons"; 6 | 7 | import { Colors, Styles } from "./src/constants"; 8 | 9 | import Explore from "./src/screens/Explore"; 10 | import AllMessages from "./src/screens/AllMessages"; 11 | import Profile from "./src/screens/Profile"; 12 | 13 | import UserProfile from "./src/screens/UserProfile"; 14 | import Chat from "./src/screens/Chat"; 15 | 16 | function getTabIcon(routeName) { 17 | switch (routeName) { 18 | case "Explore": 19 | return "ios-beer"; 20 | case "Messages": 21 | return "ios-chatbubbles"; 22 | case "Profile": 23 | return "ios-person"; 24 | } 25 | } 26 | 27 | const Tab = createBottomTabNavigator(); 28 | function HomeTabScreen() { 29 | return ( 30 | ({ 32 | ...Styles.Header, 33 | ...Styles.Tabs, 34 | tabBarIcon: ({ color }) => ( 35 | 41 | ), 42 | })} 43 | > 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | 51 | const MainStack = createNativeStackNavigator(); 52 | function MainStackScreen() { 53 | return ( 54 | 60 | 61 | 66 | 67 | ); 68 | } 69 | 70 | const AppStack = createNativeStackNavigator(); 71 | export default function App() { 72 | return ( 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/screens/Explore.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View, StyleSheet, Alert, ActivityIndicator } from "react-native"; 3 | import { UserCard, MatchButton, NoMoreMatches } from "../components"; 4 | import { Colors } from "../constants"; 5 | 6 | import * as Animatable from "react-native-animatable"; 7 | 8 | class Explore extends Component { 9 | state = { 10 | loading: false, 11 | userIndex: 0, 12 | users: [], 13 | }; 14 | 15 | componentDidMount() { 16 | this._fetchUsers(); 17 | } 18 | 19 | _fetchUsers = async () => { 20 | try { 21 | this.setState({ loading: true }); 22 | const response = await fetch("https://api.randomuser.me/?results=5"); 23 | const { results } = await response.json(); 24 | this.setState({ loading: false, users: results, userIndex: 0 }); 25 | } catch (e) { 26 | this.setState({ loading: false }); 27 | Alert.alert("Failed to load", "There was an issue loading users"); 28 | } 29 | }; 30 | 31 | _userLike = () => { 32 | // TODO: User like stuff 33 | this._nextUser(); 34 | }; 35 | 36 | _userDislike = () => { 37 | // TODO: User dislike stuff 38 | this._nextUser(); 39 | }; 40 | 41 | _userPressed = (user) => { 42 | this.props.navigation.navigate("UserProfile", { 43 | imageUrl: user.picture.large, 44 | }); 45 | }; 46 | 47 | _nextUser = () => 48 | this.setState({ 49 | userIndex: this.state.userIndex + 1, 50 | }); 51 | 52 | render() { 53 | const { userIndex, users, loading } = this.state; 54 | const user = users[userIndex]; 55 | 56 | if (loading) { 57 | return ( 58 | 59 | 60 | 61 | ); 62 | } 63 | 64 | // Check if end of users 65 | if (userIndex >= users.length) { 66 | return ; 67 | } 68 | 69 | // Display users 70 | return ( 71 | 72 | this._userPressed(user)} 74 | imageUrl={user.picture.large} 75 | /> 76 | 77 | 82 | 88 | {user.name.first} 89 | 90 | 95 | 96 | 97 | ); 98 | } 99 | } 100 | 101 | const styles = StyleSheet.create({ 102 | container: { 103 | flex: 1, 104 | padding: 8, 105 | position: "relative", 106 | backgroundColor: "#F8F8F9", 107 | }, 108 | loading: { 109 | flex: 1, 110 | alignItems: "center", 111 | justifyContent: "center", 112 | }, 113 | buttons: { 114 | marginTop: 8, 115 | flexDirection: "row", 116 | justifyContent: "space-between", 117 | position: "absolute", 118 | bottom: 24, 119 | left: 24, 120 | right: 24, 121 | }, 122 | name: { 123 | color: "white", 124 | fontSize: 28, 125 | fontWeight: "700", 126 | backgroundColor: "transparent", 127 | alignSelf: "center", 128 | }, 129 | }); 130 | 131 | export default Explore; 132 | --------------------------------------------------------------------------------