├── .gitignore ├── .watchmanconfig ├── App.js ├── README.md ├── app.json ├── app ├── Navigate.js ├── components │ ├── Avatar │ │ └── index.js │ ├── FancyBottomTab │ │ └── index.js │ ├── FancyDrawer │ │ ├── Drawer.js │ │ ├── DrawerItem.js │ │ └── index.js │ ├── Header │ │ └── index.js │ ├── NavigationWraper │ │ └── index.js │ ├── Tweet │ │ └── index.js │ └── TweetBubble │ │ └── index.js ├── index.js ├── mock.js ├── screens │ ├── Home │ │ └── index.js │ ├── Message │ │ ├── MessageCard.js │ │ └── index.js │ ├── Notification │ │ ├── NotificationCard.js │ │ └── index.js │ ├── Search │ │ ├── FeaturedNews.js │ │ ├── TrendCard.js │ │ ├── Trends.js │ │ └── index.js │ └── UnderConstruction │ │ └── index.js └── utils.js ├── assets ├── avatar │ ├── user1.jpg │ ├── user2.jpg │ ├── user3.jpg │ ├── user4.jpg │ ├── user5.jpg │ └── user6.jpg ├── design │ ├── 1.jpeg │ ├── 10.jpeg │ └── 5.jpeg ├── icon.png ├── message.png ├── splash.png ├── thunder.png ├── topGear.png ├── topMore.png ├── topStar.png ├── tweet.png └── wizardsunite.png ├── babel.config.js ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .expo 3 | npm-debug.* 4 | *.jks 5 | *.p12 6 | *.key 7 | *.mobileprovision 8 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import App from "./app/index"; 2 | export default App; 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | 6 | 7 |

8 |

9 | 10 | 11 | 12 |

13 |

14 | 15 |

16 | 17 | 18 | # Twitter Clone App 19 | 20 | Twitter app built with [React Native](https://github.com/facebook/react-native) and [Expo](https://github.com/expo/expo). 21 | 22 | ## Details 23 | 24 | Created with **create-react-native-app**. 25 | 26 | Packages used: 27 | 28 | - **Expo** 29 | - **React Navigation** 30 | 31 | ## Installation 32 | 33 | ### with Expo 34 | 35 | - Clone this repo `git clone git@github.com/dev-andremonteiro/react-native-twitter-clone.git` 36 | - `cd react-native-twitter-clone` 37 | - run `npm install` 38 | - run `expo start` 39 | 40 | ### without Expo 41 | 42 | **Make sure you have ExpoCLI installed `npm install -g expo-cli`** 43 | 44 | - Clone this repo `git clone git@github.com/dev-andremonteiro/react-native-twitter-clone.git` 45 | - `cd react-native-twitter-clone` 46 | - `expo eject` 47 | 48 | ## Help me Improve :) 49 | 50 | - [x] separete components used on FancyDrawer 51 | - [x] add custom avatar 52 | - [ ] limit scroll on extremes of horizontal scrollview 53 | - [ ] touchable not activating fast on Profile page 54 | - [ ] FIX swipe and back function of navigation 55 | - [ ] integrate FancyDrawer to react-navigation CustomNavigators 56 | - [ ] Haptic FeedBack on Profile Button 57 | - [ ] Use Cards to rework search screen 58 | - [ ] Keep title on top while scrolling on search screen 59 | - [ ] add Image and Video support 60 | - [ ] Infinite scrolling on home page 61 | - [ ] PullRefresh animation 62 | 63 | ## Author 64 | 65 | André Monteiro [dev-andremonteiro](https://github.com/dev-andremonteiro) 66 | 67 | [Follow me on Twitter!](https://twitter.com/DAndremonteiro) 68 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Twitter App 2019", 4 | "slug": "twitter-app-2019", 5 | "privacy": "public", 6 | "platforms": [ 7 | "ios", 8 | "android" 9 | ], 10 | "version": "1.0.0", 11 | "orientation": "portrait", 12 | "icon": "./assets/icon.png", 13 | "splash": { 14 | "image": "./assets/splash.png", 15 | "resizeMode": "contain", 16 | "backgroundColor": "#ffffff" 17 | }, 18 | "updates": { 19 | "fallbackToCacheTimeout": 0 20 | }, 21 | "assetBundlePatterns": [ 22 | "**/*" 23 | ], 24 | "ios": { 25 | "supportsTablet": true 26 | }, 27 | "description": "A Twitter clone of the app as of 2019.", 28 | "githubUrl": "https://github.com/dev-andremonteiro/react-native-twitter-clone" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Navigate.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { StatusBar } from "react-native"; 4 | 5 | import { 6 | createAppContainer, 7 | createSwitchNavigator, 8 | createStackNavigator 9 | } from "react-navigation"; 10 | 11 | import Home from "./screens/Home"; 12 | import Search from "./screens/Search"; 13 | import Notification from "./screens/Notification"; 14 | import Message from "./screens/Message"; 15 | 16 | import UnderConstruction from "./screens/UnderConstruction"; 17 | 18 | export default createAppContainer( 19 | createSwitchNavigator( 20 | { 21 | Main: createSwitchNavigator( 22 | { 23 | Home: Home, 24 | Search: Search, 25 | Notification: Notification, 26 | Message: Message 27 | }, 28 | { initialRouteName: "Home" } 29 | ), 30 | Drawer: createStackNavigator({ 31 | Profile: UnderConstruction, 32 | Popular: UnderConstruction, 33 | Saved: UnderConstruction, 34 | Discover: UnderConstruction, 35 | Configuration: UnderConstruction, 36 | "Help Center": UnderConstruction 37 | }), 38 | Details: createStackNavigator({ 39 | Tweet: UnderConstruction, 40 | "New Tweet": UnderConstruction, 41 | "New Message": UnderConstruction, 42 | DynamicTitle: UnderConstruction 43 | }) 44 | }, 45 | { 46 | initialRouteName: "Main", 47 | headerMode: "none", 48 | cardStyle: { paddingTop: StatusBar.currentHeight } 49 | } 50 | ) 51 | ); 52 | -------------------------------------------------------------------------------- /app/components/Avatar/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, Image } from "react-native"; 3 | import { colors } from "../../utils"; 4 | 5 | const Avatar = function(props) { 6 | if (props.photo) { 7 | let size = 30; 8 | if (props.size) size = props.size; 9 | 10 | return ( 11 | 23 | ); 24 | } 25 | 26 | let avatarSize = 30; 27 | if (props.size) avatarSize = props.size; 28 | 29 | let headSize = avatarSize; 30 | return ( 31 | 43 | 52 | 61 | 62 | ); 63 | }; 64 | 65 | export default Avatar; 66 | -------------------------------------------------------------------------------- /app/components/FancyBottomTab/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { View, Image, StyleSheet, TouchableOpacity } from "react-native"; 4 | import { width, colors } from "../../utils"; 5 | 6 | import { MaterialCommunityIcons } from "@expo/vector-icons"; 7 | 8 | const TabOption = function(props) { 9 | return ( 10 | 20 | {props.icon} 21 | 22 | ); 23 | }; 24 | 25 | class FancyBottomTab extends React.Component { 26 | iconList = [ 27 | , 32 | , 37 | , 42 | 47 | ]; 48 | 49 | iconActiveList = [ 50 | , 51 | , 56 | , 57 | 58 | ]; 59 | 60 | componentWillMount() { 61 | this.iconList[this.props.selected] = this.iconActiveList[ 62 | this.props.selected 63 | ]; 64 | } 65 | 66 | render() { 67 | return ( 68 | 78 | 82 | 86 | 90 | 94 | 95 | ); 96 | } 97 | } 98 | 99 | const styles = StyleSheet.create({ 100 | container: { 101 | flex: 1, 102 | width: width, 103 | backgroundColor: "#fff" 104 | }, 105 | content: { 106 | alignItems: "center", 107 | justifyContent: "center", 108 | padding: 15 109 | } 110 | }); 111 | 112 | export default FancyBottomTab; 113 | -------------------------------------------------------------------------------- /app/components/FancyDrawer/Drawer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | ScrollView, 4 | View, 5 | Text, 6 | Image, 7 | StyleSheet, 8 | TouchableOpacity 9 | } from "react-native"; 10 | import Avatar from "../../components/Avatar"; 11 | import DrawerItem from "./DrawerItem.js"; 12 | 13 | import { FontAwesome, Feather } from "@expo/vector-icons"; 14 | 15 | import { 16 | rgbaColors, 17 | colors, 18 | profileWidth, 19 | width, 20 | drawerOptionList 21 | } from "../../utils"; 22 | 23 | class Drawer extends React.Component { 24 | constructor(props) { 25 | super(props); 26 | this.state = { 27 | showBorderTop: false, 28 | showBoderBottom: false, 29 | list: drawerOptionList, 30 | borderColor: "", 31 | customExtra: false 32 | }; 33 | } 34 | 35 | componentWillMount() { 36 | this.setState({ 37 | borderColor: rgbaColors.light_gray 38 | }); 39 | 40 | if (typeof this.props.profileExtra !== "string") 41 | this.setState({ customExtra: true }); 42 | } 43 | 44 | _showBorders = event => { 45 | let y = event.nativeEvent.contentOffset.y; 46 | if (y < 8 && y > -8) 47 | this.setState({ showBorderTop: false, showBoderBottom: false }); 48 | if (this.state.showBorderTop || this.state.showBoderBottom) return; 49 | 50 | if (y > 10) this.setState({ showBorderTop: true }); 51 | if (y < -10) this.setState({ showBoderBottom: true }); 52 | }; 53 | 54 | render() { 55 | return ( 56 | 64 | 70 | 73 | 74 | 81 | {this.props.profileName} 82 | 83 | 90 | {this.props.profileUserName} 91 | 92 | 93 | {this.state.customExtra ? ( 94 | this.props.profileExtra 95 | ) : ( 96 | 97 | {this.props.profileExtra} 98 | 99 | )} 100 | 101 | 102 | 108 | 109 | 114 | 115 | 116 | 117 | 118 | 127 | 128 | 135 | {this.state.list.map((item, index) => ( 136 | 141 | ))} 142 | 143 | 144 | 153 | 154 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | ); 169 | } 170 | } 171 | 172 | const styles = StyleSheet.create({ 173 | container: { 174 | flex: 1, 175 | width: width, 176 | backgroundColor: colors.white 177 | }, 178 | profileContainer: { 179 | width: profileWidth, 180 | paddingRight: 15, 181 | paddingLeft: 25, 182 | paddingVertical: 10 183 | }, 184 | borderBottom: { 185 | borderBottomColor: colors.dark_gray, 186 | borderBottomWidth: StyleSheet.hairlineWidth 187 | } 188 | }); 189 | 190 | export default Drawer; 191 | -------------------------------------------------------------------------------- /app/components/FancyDrawer/DrawerItem.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, Text, StyleSheet, TouchableHighlight } from "react-native"; 3 | import { colors, profileWidth } from "../../utils"; 4 | 5 | const DrawerItem = function(props) { 6 | if (!props.data) return ; 7 | 8 | return ( 9 | props.changeScreen(props.data.nav)} 14 | > 15 | 22 | {props.data.icon && ( 23 | {props.data.icon} 24 | )} 25 | 33 | {props.data.text} 34 | 35 | 36 | 37 | ); 38 | }; 39 | 40 | const styles = StyleSheet.create({ 41 | line: { 42 | marginVertical: 10, 43 | width: profileWidth, 44 | height: StyleSheet.hairlineWidth, 45 | backgroundColor: colors.exlight_gray 46 | }, 47 | profileContainer: { 48 | width: profileWidth, 49 | paddingRight: 15, 50 | paddingLeft: 25, 51 | paddingVertical: 15 52 | }, 53 | iconWraper: { 54 | height: 24, 55 | width: 24, 56 | marginRight: 20, 57 | alignItems: "center", 58 | justifyContent: "center" 59 | } 60 | }); 61 | 62 | export default DrawerItem; 63 | -------------------------------------------------------------------------------- /app/components/FancyDrawer/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | ScrollView, 4 | View, 5 | SafeAreaView, 6 | StyleSheet, 7 | TouchableOpacity 8 | } from "react-native"; 9 | 10 | import Drawer from "./Drawer"; 11 | 12 | import { rgbaColors, colors, width, height, profileWidth } from "../../utils"; 13 | 14 | function mappingNumber(x, in_min, in_max, out_min, out_max) { 15 | return ((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min; 16 | } 17 | 18 | export default class FancyDrawer extends React.Component { 19 | constructor(props) { 20 | super(props); 21 | this.state = { 22 | isScrolled: false, 23 | opacity: 0 24 | }; 25 | } 26 | 27 | componentWillMount() { 28 | this.setState({ 29 | fadedColor: rgbaColors.exlight_gray 30 | }); 31 | } 32 | 33 | _showProfile = z => this.scroll.scrollTo({ x: z, y: 0, animated: true }); 34 | 35 | _handleScroll = event => { 36 | let x = event.nativeEvent.contentOffset.x; 37 | if (x >= profileWidth && !this.state.isScrolled) return; 38 | else if (x >= profileWidth && this.state.isScrolled) { 39 | this.setState({ isScrolled: false }); 40 | this.props.fading(0); 41 | } else { 42 | let y = mappingNumber(x, 300, 0, 0, 0.7); 43 | this.setState({ 44 | isScrolled: true, 45 | opacity: y 46 | }); 47 | y = mappingNumber(x, 300, 0, 0, 1); 48 | this.props.fading(y); 49 | } 50 | }; 51 | 52 | render() { 53 | return ( 54 | 55 | (this.scroll = ref)} 64 | > 65 | 71 | 72 | 81 | {this.state.isScrolled && ( 82 | 98 | )} 99 | {this.props.children} 100 | 101 | 102 | 103 | ); 104 | } 105 | } 106 | 107 | const styles = StyleSheet.create({ 108 | container: { 109 | flex: 1, 110 | width: width, 111 | backgroundColor: colors.white 112 | } 113 | }); 114 | -------------------------------------------------------------------------------- /app/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, StyleSheet, TouchableOpacity } from "react-native"; 3 | import { width, rgbaColors } from "../../utils"; 4 | 5 | import Avatar from "../Avatar"; 6 | 7 | class Header extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | faded: 0 12 | }; 13 | } 14 | 15 | componentWillMount() { 16 | this.setState({ 17 | borderColor: rgbaColors.light_gray 18 | }); 19 | } 20 | 21 | _fadeAvatar(n) { 22 | this.setState({ faded: n }); 23 | } 24 | 25 | render() { 26 | let icon = this.props.rightIcon; 27 | if (!this.props.rightIcon) { 28 | icon = ; 29 | } 30 | 31 | return ( 32 | 33 | 43 | 44 | {this.props.title} 45 | {icon} 46 | 47 | ); 48 | } 49 | } 50 | 51 | const styles = StyleSheet.create({ 52 | container: { 53 | flexDirection: "row", 54 | alignItems: "center", 55 | justifyContent: "space-between", 56 | paddingHorizontal: 10, 57 | width: width, 58 | height: 40, 59 | borderBottomColor: "#ccc", 60 | borderBottomWidth: StyleSheet.hairlineWidth 61 | }, 62 | touchableMask: { 63 | position: "absolute", 64 | top: 5, 65 | left: 10, 66 | width: 30, 67 | height: 30, 68 | zIndex: 9, 69 | padding: 5 70 | } 71 | }); 72 | 73 | export default Header; 74 | -------------------------------------------------------------------------------- /app/components/NavigationWraper/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, Text } from "react-native"; 3 | 4 | import FancyDrawer from "../FancyDrawer"; 5 | import Header from "../Header"; 6 | import FancyBottomTab from "../FancyBottomTab"; 7 | import TweetBubble from "../TweetBubble"; 8 | 9 | class NavigationWraper extends React.Component { 10 | _handleShowDrawer = () => this.drawer._showProfile(0); 11 | _handleHeaderFade = i => this.header._fadeAvatar(i); 12 | 13 | _changeScreen = screen => 14 | this.props.navigation.navigate(screen, { 15 | last: this.props.navigation.state.routeName 16 | }); 17 | 18 | render() { 19 | return ( 20 | (this.drawer = ref)} 23 | navigation={this._changeScreen} 24 | profileName={"dev-andremonteiro"} 25 | profileUserName={"@DAndremonteiro"} 26 | profileExtra={ 27 | 28 | {"10 "} 29 | 37 | {"Following"} 38 | 39 | {"20 "} 40 | 41 | {"Followers"} 42 | 43 | 44 | } 45 | > 46 |
(this.header = ref)} 48 | showProfile={this._handleShowDrawer} 49 | title={this.props.title} 50 | rightIcon={this.props.rightIcon} 51 | style={this.props.headerStyle} 52 | /> 53 | {this.props.children} 54 | 62 | 66 | 67 | ); 68 | } 69 | } 70 | 71 | export default NavigationWraper; 72 | -------------------------------------------------------------------------------- /app/components/Tweet/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; 3 | 4 | import Avatar from "../../components/Avatar"; 5 | 6 | import { generateTwitterText, colors, width } from "../../utils"; 7 | import SimpleLineIcons from "@expo/vector-icons/SimpleLineIcons"; 8 | import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; 9 | 10 | export default class Tweet extends React.Component { 11 | renderTopText(type, text) { 12 | text += type === "retweet" ? " retweet" : " response"; 13 | 14 | return ( 15 | 24 | {type === "retweet" ? ( 25 | 30 | ) : ( 31 | 36 | )} 37 | {text} 38 | 39 | ); 40 | } 41 | 42 | renderBottomIcons(name, text) { 43 | let finalText = text === 0 ? null : text.toString(); 44 | 45 | return ( 46 | 53 | 58 | 59 | {finalText} 60 | 61 | 62 | ); 63 | } 64 | 65 | render() { 66 | let { 67 | type, 68 | user, 69 | userName, 70 | avatar, 71 | time, 72 | message, 73 | comments, 74 | retweets, 75 | likes 76 | } = this.props.data; 77 | 78 | return ( 79 | 90 | {type === "retweet" && this.renderTopText(type, this.props.data.from)} 91 | {type === "responseTo" && this.renderTopText(type, user)} 92 | {type === "responseTo" && } 93 | 94 | 95 | 96 | 104 | 111 | {user} 112 | 113 | {userName} 114 | 115 | 124 | {time} 125 | 126 | 131 | 132 | 133 | {generateTwitterText( 134 | 141 | {message} 142 | 143 | )} 144 | 145 | 154 | {this.renderBottomIcons("comment-outline", comments)} 155 | {this.renderBottomIcons("twitter-retweet", retweets)} 156 | {this.renderBottomIcons("heart-outline", likes)} 157 | {this.renderBottomIcons("share-outline", 0)} 158 | 159 | 160 | 161 | 162 | ); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /app/components/TweetBubble/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { View, Image, TouchableOpacity } from "react-native"; 4 | 5 | import { width, height, colors } from "../../utils"; 6 | 7 | export default class TweetBubble extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | render() { 13 | return ( 14 | 22 | 32 | {this.props.message ? ( 33 | 37 | ) : ( 38 | 42 | )} 43 | 44 | 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Animated, Easing } from 'react-native'; 3 | 4 | import { Asset } from 'expo-asset'; 5 | 6 | import Navigator from './Navigate'; 7 | 8 | const TwitterLogo = require('../assets/icon.png'); 9 | 10 | function cacheImages(images) { 11 | return images.map((image) => { 12 | if (typeof image === 'string') { 13 | return Image.prefetch(image); 14 | } else { 15 | return Asset.fromModule(image).downloadAsync(); 16 | } 17 | }); 18 | } 19 | 20 | class Loading extends React.Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | iconSize: new Animated.Value(80), 25 | }; 26 | } 27 | 28 | _startAnimation = () => 29 | Animated.timing(this.state.iconSize, { 30 | toValue: 2500, 31 | duration: 350, 32 | easing: Easing.back(0.4), 33 | }).start(() => this.props.animationEnd()); 34 | 35 | render() { 36 | return ( 37 | 45 | 54 | 55 | ); 56 | } 57 | } 58 | 59 | export default class RootComponent extends React.Component { 60 | constructor(props) { 61 | super(props); 62 | this.state = { 63 | isLoading: true, 64 | }; 65 | } 66 | 67 | async _loadAssets() { 68 | const imageAssets = cacheImages([ 69 | require('../assets/topMore.png'), 70 | require('../assets/thunder.png'), 71 | require('../assets/topStar.png'), 72 | require('../assets/topGear.png'), 73 | require('../assets/tweet.png'), 74 | require('../assets/message.png'), 75 | require('../assets/wizardsunite.png'), 76 | require('../assets/avatar/user1.jpg'), 77 | require('../assets/avatar/user2.jpg'), 78 | require('../assets/avatar/user3.jpg'), 79 | require('../assets/avatar/user4.jpg'), 80 | require('../assets/avatar/user5.jpg'), 81 | require('../assets/avatar/user6.jpg'), 82 | ]); 83 | await Promise.all([...imageAssets]).then(this.load._startAnimation); 84 | } 85 | 86 | componentDidMount = this._loadAssets; 87 | 88 | render() { 89 | if (this.state.isLoading) 90 | return ( 91 | (this.load = ref)} 93 | animationEnd={() => this.setState({ isLoading: false })} 94 | /> 95 | ); 96 | 97 | return ; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/mock.js: -------------------------------------------------------------------------------- 1 | export const homeFeed = [ 2 | { 3 | type: "tweet", 4 | 5 | user: "Rodney Diaz", 6 | userName: "@rodiaz", 7 | avatar: require("../assets/avatar/user1.jpg"), 8 | 9 | time: "1h", 10 | message: 11 | 'Sometimes I see english words that make me smile - because they are clear "transpositions" of their latin/italian counterparts', 12 | comments: 1, 13 | retweets: 0, 14 | likes: 2 15 | }, 16 | { 17 | type: "retweet", 18 | from: "Wayne Spencer", 19 | 20 | user: "Tammy Warren", 21 | userName: "@tammy2", 22 | avatar: null, 23 | 24 | time: "2h", 25 | message: 26 | 'Aqui les dejo el link a mi charla de ayer del #ReactWeekMedelin : "Some Video" youtube.com/watch?v=WC0Wkx #react #redux #RxJS', 27 | comments: 0, 28 | retweets: 3, 29 | likes: 6 30 | }, 31 | { 32 | type: "response", 33 | to: "@ryanflorence", 34 | 35 | user: "Jacqueline Hayes", 36 | userName: "@jacqyes68", 37 | avatar: require("../assets/avatar/user2.jpg"), 38 | 39 | time: "1h", 40 | message: 41 | "Hard work is all you need if you're a hunter gatherer, but you're not.\n\nYouneed to convince people around you that whatever thing it is that you want to happen is something that they want to happen too.", 42 | comments: 3, 43 | retweets: 6, 44 | likes: 55 45 | }, 46 | { 47 | type: "response", 48 | to: "@esablack58", 49 | 50 | user: "Wyatt Fleming", 51 | userName: "@attFleming", 52 | avatar: require("../assets/avatar/user3.jpg"), 53 | 54 | time: "32m", 55 | message: "How do you learn sales?", 56 | comments: 0, 57 | retweets: 0, 58 | likes: 0 59 | }, 60 | { 61 | type: "retweet", 62 | from: "Earl Baker", 63 | 64 | user: "Craig Griffin", 65 | userName: "@cgriffin", 66 | avatar: require("../assets/avatar/user4.jpg"), 67 | 68 | time: "22h", 69 | message: "I'm jumping on this AMA. Come keep me company.", 70 | comments: 2, 71 | retweets: 6, 72 | likes: 15 73 | }, 74 | { 75 | type: "responseTo", 76 | 77 | original: { 78 | type: null, 79 | user: "Josephine Gray", 80 | userName: null, 81 | avatar: require("../assets/avatar/user5.jpg"), 82 | 83 | time: "3h", 84 | message: 85 | "I need 7 more followers to get to 1000. If I get 1000 by end of day I will Venmo @Dandremonteiro $7.", 86 | comments: 5, 87 | retweets: 1, 88 | likes: 1 89 | }, 90 | 91 | user: "Terry Davidson", 92 | userName: "@davidson", 93 | avatar: require("../assets/avatar/user6.jpg"), 94 | 95 | time: "1h", 96 | message: "I want @Dandremonteiro to pay you", 97 | comments: 1, 98 | retweets: 0, 99 | likes: 0 100 | } 101 | ]; 102 | 103 | export const searchFeed = { 104 | main: { 105 | title: 106 | "See how will be 'Wizards Unite' new phone game inspired on Harry Potter", 107 | topic: "Games", 108 | time: "Earlier Today", 109 | image: require("../assets/wizardsunite.png") 110 | }, 111 | trends: [ 112 | { 113 | title: "Worldwide trends" 114 | }, 115 | { 116 | title: "Twitter Clone App", 117 | tweets: "1,3M tweets" 118 | }, 119 | { 120 | title: "Zidane", 121 | tweets: "609k tweets" 122 | }, 123 | { 124 | title: "Noick Foles", 125 | tweets: "21,7k tweets" 126 | }, 127 | { 128 | title: "#FlyEaglesFly", 129 | tweets: "12,3k tweets" 130 | }, 131 | { title: "Show more" } 132 | ] 133 | }; 134 | 135 | export const notificationFeed = { 136 | all: [ 137 | { 138 | type: "discover", 139 | users: [require("../assets/avatar/user1.jpg")], 140 | title: "*Rodney Diaz* liked the photo of Tammy Warren", 141 | desc: "I don't have words for this! pic.twitter.com/tw1tt3rcl0n31s4w3s0m3" 142 | }, 143 | { 144 | type: "follow", 145 | users: [require("../assets/avatar/user2.jpg")], 146 | title: "*Jacqueline Hayes* followed you", 147 | desc: null 148 | }, 149 | { 150 | type: "like", 151 | users: [ 152 | require("../assets/avatar/user3.jpg"), 153 | require("../assets/avatar/user1.jpg"), 154 | require("../assets/avatar/user4.jpg"), 155 | require("../assets/avatar/user2.jpg") 156 | ], 157 | title: 158 | "*Wyatt Fleming* and other 3 liked a Tweet in wich you were mentioned", 159 | desc: 160 | "Congrats for @DAndremonteiro for releasing Twitter Clone app! #ReactNative #Expo" 161 | }, 162 | { 163 | type: "discover", 164 | users: [require("../assets/avatar/user6.jpg")], 165 | title: "*Terry Davidson* liked the photo of Elon Musk", 166 | desc: "That's a beautiful app indeed" 167 | } 168 | ], 169 | mentions: [ 170 | { 171 | type: "tweet", 172 | 173 | user: "Rodney Diaz", 174 | userName: "@rodiaz", 175 | avatar: require("../assets/avatar/user1.jpg"), 176 | 177 | time: "05/03/2019", 178 | message: 179 | 'Sometimes I see english words that make me smile - because they are clear "transpositions" of their latin/italian counterparts', 180 | comments: 0, 181 | retweets: 0, 182 | likes: 3 183 | }, 184 | { 185 | type: "retweet", 186 | from: "Wayne Spencer", 187 | 188 | user: "Tammy Warren", 189 | userName: "@tammy2", 190 | avatar: null, 191 | 192 | time: "03/03/2019", 193 | message: 194 | 'Aqui les dejo el link a mi charla de ayer del #ReactWeekMedelin : "Some Video" youtube.com/watch?v=WC0Wkx #react #redux #RxJS', 195 | comments: 13, 196 | retweets: 30, 197 | likes: 20 198 | } 199 | ] 200 | }; 201 | 202 | export const messageFeed = [ 203 | { 204 | user: "Rodney Diaz", 205 | userName: "@rodiaz", 206 | avatar: require("../assets/avatar/user1.jpg"), 207 | 208 | time: "1m", 209 | message: "You: Check out my project!" 210 | }, 211 | { 212 | user: "Terry Davidson", 213 | userName: "@davidson", 214 | avatar: require("../assets/avatar/user6.jpg"), 215 | 216 | time: "2m", 217 | message: "Realy Nice project man, congrats." 218 | }, 219 | { 220 | user: "Craig Griffin", 221 | userName: "@cgriffin", 222 | avatar: require("../assets/avatar/user4.jpg"), 223 | 224 | time: "1d", 225 | message: "Hey, I saw your twitter clone, very nice!" 226 | }, 227 | { 228 | user: "Wyatt Fleming", 229 | userName: "@attFleming", 230 | avatar: require("../assets/avatar/user3.jpg"), 231 | 232 | time: "3d", 233 | message: "You: Thank you !" 234 | } 235 | ]; 236 | -------------------------------------------------------------------------------- /app/screens/Home/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | ScrollView, 4 | TouchableHighlight, 5 | Text, 6 | StyleSheet, 7 | TouchableOpacity, 8 | Image 9 | } from "react-native"; 10 | 11 | import { width, colors } from "../../utils"; 12 | import { homeFeed } from "../../mock"; 13 | 14 | import NavigationWraper from "../../components/NavigationWraper"; 15 | import Tweet from "../../components/Tweet"; 16 | 17 | class Home extends React.Component { 18 | render() { 19 | return ( 20 | 25 | 30 | 31 | } 32 | title={ 33 | 40 | {"Home"} 41 | 42 | } 43 | > 44 | 45 | {homeFeed.map((i, n) => ( 46 | 53 | this.props.navigation.navigate("Tweet", { last: "Home" }) 54 | } 55 | > 56 | 57 | 58 | ))} 59 | 60 | 61 | ); 62 | } 63 | } 64 | 65 | const styles = StyleSheet.create({ 66 | container: { 67 | flex: 1, 68 | width: width, 69 | backgroundColor: colors.exexlight_gray 70 | } 71 | }); 72 | 73 | export default Home; 74 | -------------------------------------------------------------------------------- /app/screens/Message/MessageCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Image, Text, StyleSheet, View } from "react-native"; 3 | import { width, colors } from "../../utils"; 4 | 5 | import Avatar from "../../components/Avatar"; 6 | 7 | class MessageCard extends React.Component { 8 | render() { 9 | let { user, userName, avatar, time, message } = this.props.data; 10 | return ( 11 | 24 | 25 | 33 | 40 | 47 | {user} 48 | 55 | {userName} 56 | 57 | 58 | 64 | {message} 65 | 66 | 67 | 68 | 69 | {time} 70 | 71 | 72 | ); 73 | } 74 | } 75 | 76 | const styles = StyleSheet.create({ 77 | container: { 78 | flex: 1, 79 | width: width, 80 | backgroundColor: colors.exexlight_gray 81 | } 82 | }); 83 | 84 | export default MessageCard; 85 | -------------------------------------------------------------------------------- /app/screens/Message/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Image, 4 | View, 5 | ScrollView, 6 | Text, 7 | StyleSheet, 8 | TouchableOpacity 9 | } from "react-native"; 10 | import { width, colors } from "../../utils"; 11 | 12 | import { messageFeed } from "../../mock"; 13 | 14 | import NavigationWraper from "../../components/NavigationWraper"; 15 | import MessageCard from "./MessageCard"; 16 | 17 | class Message extends React.Component { 18 | render() { 19 | return ( 20 | 32 | {"Messages"} 33 | 34 | } 35 | > 36 | 37 | {messageFeed.map((i, n) => ( 38 | 45 | this.props.navigation.navigate("DynamicTitle", { 46 | last: "Message" 47 | }) 48 | } 49 | > 50 | 51 | 52 | ))} 53 | 54 | 55 | ); 56 | } 57 | } 58 | 59 | const styles = StyleSheet.create({ 60 | container: { 61 | flex: 1, 62 | width: width, 63 | backgroundColor: colors.exexlight_gray 64 | }, 65 | content: { 66 | alignItems: "center", 67 | justifyContent: "center", 68 | padding: 15 69 | } 70 | }); 71 | 72 | export default Message; 73 | -------------------------------------------------------------------------------- /app/screens/Notification/NotificationCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Image, Text, StyleSheet, View } from "react-native"; 3 | import { width, colors } from "../../utils"; 4 | 5 | import SimpleLineIcons from "@expo/vector-icons/SimpleLineIcons"; 6 | import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; 7 | import Avatar from "../../components/Avatar"; 8 | 9 | const bolderText = text => { 10 | if (text.indexOf("*") === -1) return text; 11 | 12 | let boldFormatedText; 13 | let textSplit = text.split("*"); 14 | for (let i = 0; i < textSplit.length; i++) { 15 | let item = textSplit[i]; 16 | if (item !== "") { 17 | boldFormatedText = ( 18 | 19 | {boldFormatedText} 20 | {item} 21 | 22 | ); 23 | } 24 | } 25 | 26 | return {boldFormatedText}; 27 | }; 28 | 29 | class NotificationCard extends React.Component { 30 | state = { 31 | discover: ( 32 | 38 | ), 39 | follow: ( 40 | 46 | ), 47 | like: ( 48 | 54 | ) 55 | }; 56 | 57 | render() { 58 | let { type, users, title, desc } = this.props.data; 59 | 60 | let icon = this.state[type]; 61 | 62 | return ( 63 | 73 | {icon} 74 | 82 | 83 | {users.map((i, n) => ( 84 | 91 | ))} 92 | 93 | 100 | {bolderText(title)} 101 | 106 | {desc} 107 | 108 | 109 | 110 | 111 | 116 | 117 | 118 | ); 119 | } 120 | } 121 | 122 | const styles = StyleSheet.create({ 123 | container: { 124 | flex: 1, 125 | width: width, 126 | backgroundColor: "#FFF" 127 | }, 128 | content: { 129 | alignItems: "center", 130 | justifyContent: "center", 131 | padding: 15 132 | } 133 | }); 134 | 135 | export default NotificationCard; 136 | -------------------------------------------------------------------------------- /app/screens/Notification/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Image, 4 | ScrollView, 5 | View, 6 | Text, 7 | StyleSheet, 8 | TouchableOpacity 9 | } from "react-native"; 10 | 11 | import { notificationFeed } from "../../mock"; 12 | import { width, colors } from "../../utils"; 13 | 14 | import NavigationWraper from "../../components/NavigationWraper"; 15 | import Tweet from "../../components/Tweet"; 16 | import NotificationCard from "./NotificationCard"; 17 | 18 | class Notification extends React.Component { 19 | state = { sec: 0, pos: 0 }; 20 | 21 | mappingNumber(x, in_min, in_max, out_min, out_max) { 22 | return ((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min; 23 | } 24 | 25 | animateHeader = event => { 26 | let x = event.nativeEvent.contentOffset.x / 2; 27 | this.setState({ pos: x }); 28 | this.setState({ sec: x >= width / 4 ? 1 : 0 }); 29 | }; 30 | 31 | render() { 32 | return ( 33 | 39 | 44 | 45 | } 46 | title={ 47 | 54 | {"Notifications"} 55 | 56 | } 57 | > 58 | 65 | 72 | 80 | this.scroll.scrollTo({ x: 0, y: 0, animated: true }) 81 | } 82 | > 83 | 93 | {"All"} 94 | 95 | 96 | 104 | this.scroll.scrollTo({ x: width, y: 0, animated: true }) 105 | } 106 | > 107 | 117 | {"Mentions"} 118 | 119 | 120 | 121 | 130 | 131 | 132 | { 134 | this.scroll = ref; 135 | }} 136 | style={styles.container} 137 | horizontal 138 | pagingEnabled 139 | showsHorizontalScrollIndicator={false} 140 | onScroll={this.animateHeader} 141 | scrollEventThrottle={16} 142 | > 143 | 144 | {notificationFeed.all.map((item, n) => { 145 | return ( 146 | 153 | this.props.navigation.navigate("DynamicTitle", { 154 | last: "Notification" 155 | }) 156 | } 157 | > 158 | 159 | 160 | ); 161 | })} 162 | 163 | 172 | {notificationFeed.mentions.map((item, n) => { 173 | return ( 174 | 181 | this.props.navigation.navigate("Tweet", { 182 | last: "Notification" 183 | }) 184 | } 185 | > 186 | 187 | 188 | ); 189 | })} 190 | 191 | 192 | 193 | ); 194 | } 195 | } 196 | 197 | const styles = StyleSheet.create({ 198 | container: { 199 | flex: 1, 200 | width: width, 201 | backgroundColor: colors.exexlight_gray 202 | }, 203 | content: { 204 | alignItems: "center", 205 | justifyContent: "center", 206 | padding: 15 207 | } 208 | }); 209 | 210 | export default Notification; 211 | -------------------------------------------------------------------------------- /app/screens/Search/FeaturedNews.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { View, Text, TouchableOpacity, ImageBackground } from "react-native"; 4 | 5 | import { width } from "../../utils"; 6 | 7 | export default class FeaturedNew extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | render() { 13 | let { title, topic, time, image } = this.props.data; 14 | 15 | return ( 16 | 17 | 28 | 35 | 36 | {topic} 37 | 38 | 47 | {time} 48 | 49 | 50 | {title} 51 | 52 | 53 | 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/screens/Search/TrendCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { View, Text, StyleSheet } from "react-native"; 4 | 5 | import { width, colors } from "../../utils"; 6 | import SimpleLineIcons from "@expo/vector-icons/SimpleLineIcons"; 7 | 8 | export default class TrendCard extends React.Component { 9 | render() { 10 | let { title, tweets } = this.props.data; 11 | let { index } = this.props; 12 | 13 | if (index === 0) { 14 | return ( 15 | 16 | {title} 17 | 18 | ); 19 | } 20 | if (index === 5) { 21 | return ( 22 | 32 | {title} 33 | 38 | 39 | ); 40 | } 41 | 42 | return ( 43 | 53 | 54 | 61 | {index.toString()} 62 | 63 | 64 | 70 | {title} 71 | 72 | 79 | {tweets} 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | ); 92 | } 93 | } 94 | 95 | const styles = StyleSheet.create({ 96 | content: { 97 | flex: 1, 98 | width: width, 99 | backgroundColor: colors.white, 100 | padding: 15 101 | }, 102 | sectionText: { 103 | fontSize: 20, 104 | fontWeight: "600" 105 | }, 106 | showMoreText: { 107 | color: colors.primary, 108 | fontSize: 16, 109 | fontWeight: "300" 110 | } 111 | }); 112 | -------------------------------------------------------------------------------- /app/screens/Search/Trends.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { View, Text, StyleSheet } from "react-native"; 4 | 5 | import { width, height, colors } from "../../utils"; 6 | 7 | import SimpleLineIcons from "@expo/vector-icons/SimpleLineIcons"; 8 | 9 | const line = StyleSheet.hairlineWidth; 10 | 11 | export default class Trends extends React.Component { 12 | render() { 13 | return ( 14 | 20 | 27 | {} 28 | 29 | 30 | {this.props.data.map((item, index) => { 31 | let { title, tweets } = item; 32 | 33 | return ( 34 | 46 | 47 | 54 | {(index + 1).toString()} 55 | 56 | 57 | 63 | {title} 64 | 65 | 72 | {tweets} 73 | 74 | 75 | 76 | 77 | 78 | 83 | 84 | 85 | ); 86 | })} 87 | 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/screens/Search/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Image, 4 | ScrollView, 5 | StyleSheet, 6 | TouchableOpacity, 7 | View, 8 | Text 9 | } from "react-native"; 10 | import { width, colors } from "../../utils"; 11 | 12 | import NavigationWraper from "../../components/NavigationWraper"; 13 | 14 | import { searchFeed } from "../../mock"; 15 | 16 | import { MaterialCommunityIcons } from "@expo/vector-icons"; 17 | 18 | import FeaturedNews from "./FeaturedNews"; 19 | import Trends from "./Trends"; 20 | import TrendCard from "./TrendCard"; 21 | 22 | class SearchBar extends React.Component { 23 | render() { 24 | return ( 25 | 37 | 43 | 44 | {"Search on Twitter"} 45 | 46 | 47 | ); 48 | } 49 | } 50 | 51 | class Search extends React.Component { 52 | render() { 53 | return ( 54 | 59 | 64 | 65 | } 66 | title={} 67 | > 68 | 69 | 72 | this.props.navigation.navigate("DynamicTitle", { last: "Search" }) 73 | } 74 | /> 75 | 83 | 84 | {searchFeed.trends.map((i, n) => ( 85 | 92 | this.props.navigation.navigate("DynamicTitle", { 93 | last: "Search" 94 | }) 95 | } 96 | > 97 | 98 | 99 | ))} 100 | 101 | 102 | ); 103 | } 104 | } 105 | 106 | const styles = StyleSheet.create({ 107 | container: { 108 | flex: 1, 109 | width: width, 110 | backgroundColor: colors.exexlight_gray 111 | }, 112 | content: { 113 | alignItems: "center", 114 | justifyContent: "center", 115 | padding: 15 116 | } 117 | }); 118 | 119 | export default Search; 120 | -------------------------------------------------------------------------------- /app/screens/UnderConstruction/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, Text, TouchableOpacity, Linking } from "react-native"; 3 | import { HeaderBackButton, NavigationActions } from "react-navigation"; 4 | 5 | class app extends React.Component { 6 | static navigationOptions = ({ navigation }) => ({ 7 | headerLeft: ( 8 | 10 | navigation.navigate( 11 | navigation.state.params.last, 12 | {}, 13 | NavigationActions.navigate({ routeName: "Main" }) 14 | ) 15 | } 16 | /> 17 | ), 18 | headerTitle: navigation.state.routeName 19 | }); 20 | 21 | render() { 22 | return ( 23 | 30 | 31 | {"Sadly, This screen is not implemented yet."} 32 | 33 | 34 | {"You can help me improve this project by forking it."} 35 | 36 | 39 | Linking.openURL( 40 | "https://github.com/dev-andremonteiro/react-native-twitter-clone" 41 | ) 42 | } 43 | > 44 | 52 | {"https://github.com/dev-andremonteiro/react-native-twitter-clone"} 53 | 54 | 55 | 56 | {"Any help is appreciated! :)"} 57 | 58 | 59 | ); 60 | } 61 | } 62 | 63 | export default app; 64 | -------------------------------------------------------------------------------- /app/utils.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Image, Dimensions } from "react-native"; 3 | 4 | import { MaterialIcons, Ionicons, FontAwesome } from "@expo/vector-icons"; 5 | 6 | export const colors = { 7 | primary: "#1DA1F2", 8 | secondary: "#14171A", 9 | dark_gray: "#657786", 10 | light_gray: "#AAB8C2", 11 | exlight_gray: "#E1E8ED", 12 | exexlight_gray: "#F5F8FA", 13 | white: "#FFF" 14 | }; 15 | 16 | export const drawerOptionList = [ 17 | { 18 | icon: , 19 | text: "Profile", 20 | nav: "Profile" 21 | }, 22 | { 23 | icon: , 24 | text: "Popular", 25 | nav: "Popular" 26 | }, 27 | { 28 | icon: ( 29 | 34 | ), 35 | text: "Saved", 36 | nav: "Saved" 37 | }, 38 | { 39 | icon: ( 40 | 45 | ), 46 | text: "Discover", 47 | nav: "Discover" 48 | }, 49 | null, 50 | { 51 | text: "Configuration", 52 | nav: "Configuration" 53 | }, 54 | { 55 | text: "Help Center", 56 | nav: "Help Center" 57 | } 58 | ]; 59 | 60 | export const width = Dimensions.get("window").width; 61 | export const profileWidth = width - width * 0.15; 62 | export const height = Dimensions.get("window").height; 63 | 64 | function hexToRGBArray(hex) { 65 | let hexArr = hex.match(/[A-Za-z0-9]{2}/g).map(v => parseInt(v, 16)); 66 | return `rgba(${hexArr[0]},${hexArr[1]},${hexArr[2]},`; 67 | } 68 | 69 | export const rgbaColors = { 70 | primary: hexToRGBArray(colors.primary), 71 | secondary: hexToRGBArray(colors.secondary), 72 | dark_gray: hexToRGBArray(colors.dark_gray), 73 | light_gray: hexToRGBArray(colors.light_gray), 74 | exlight_gray: hexToRGBArray(colors.exlight_gray), 75 | exexlight_gray: hexToRGBArray(colors.exexlight_gray), 76 | white: hexToRGBArray(colors.white) 77 | }; 78 | 79 | import Hyperlink from "react-native-hyperlink"; 80 | const customLinkify = require("linkify-it")() 81 | .add("@", { 82 | validate: function(text, pos, self) { 83 | var tail = text.slice(pos); 84 | 85 | if (!self.re.twitter) { 86 | self.re.twitter = new RegExp( 87 | "^([a-zA-Z0-9_]){1,15}(?!_)(?=$|" + self.re.src_ZPCc + ")" 88 | ); 89 | } 90 | if (self.re.twitter.test(tail)) { 91 | // Linkifier allows punctuation chars before prefix, 92 | // but we additionally disable `@` ("@@mention" is invalid) 93 | if (pos >= 2 && tail[pos - 2] === "@") { 94 | return false; 95 | } 96 | return tail.match(self.re.twitter)[0].length; 97 | } 98 | return 0; 99 | }, 100 | normalize: function(match) { 101 | match.url = "https://twitter.com/" + match.url.replace(/^@/, ""); 102 | } 103 | }) 104 | .add("#", { 105 | validate: function(text, pos, self) { 106 | var tail = text.slice(pos); 107 | 108 | if (!self.re.twitter) { 109 | self.re.twitter = new RegExp( 110 | "^([a-zA-Z0-9_]){1,30}(?!_)(?=$|" + self.re.src_ZPCc + ")" 111 | ); 112 | } 113 | if (self.re.twitter.test(tail)) { 114 | // Linkifier allows punctuation chars before prefix, 115 | // but we additionally disable `@` ("@@mention" is invalid) 116 | if (pos >= 2 && tail[pos - 2] === "#") { 117 | return false; 118 | } 119 | return tail.match(self.re.twitter)[0].length; 120 | } 121 | return 0; 122 | }, 123 | normalize: () => {} 124 | }); 125 | 126 | export function generateTwitterText(text) { 127 | return ( 128 | 132 | {text} 133 | 134 | ); 135 | } 136 | -------------------------------------------------------------------------------- /assets/avatar/user1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/avatar/user1.jpg -------------------------------------------------------------------------------- /assets/avatar/user2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/avatar/user2.jpg -------------------------------------------------------------------------------- /assets/avatar/user3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/avatar/user3.jpg -------------------------------------------------------------------------------- /assets/avatar/user4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/avatar/user4.jpg -------------------------------------------------------------------------------- /assets/avatar/user5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/avatar/user5.jpg -------------------------------------------------------------------------------- /assets/avatar/user6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/avatar/user6.jpg -------------------------------------------------------------------------------- /assets/design/1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/design/1.jpeg -------------------------------------------------------------------------------- /assets/design/10.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/design/10.jpeg -------------------------------------------------------------------------------- /assets/design/5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/design/5.jpeg -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/icon.png -------------------------------------------------------------------------------- /assets/message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/message.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/splash.png -------------------------------------------------------------------------------- /assets/thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/thunder.png -------------------------------------------------------------------------------- /assets/topGear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/topGear.png -------------------------------------------------------------------------------- /assets/topMore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/topMore.png -------------------------------------------------------------------------------- /assets/topStar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/topStar.png -------------------------------------------------------------------------------- /assets/tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/tweet.png -------------------------------------------------------------------------------- /assets/wizardsunite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dev-andremonteiro/react-native-twitter-clone/cc4d4f342075c2fbccce49536c2fa58b389545e5/assets/wizardsunite.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /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 | "eject": "expo eject" 8 | }, 9 | "dependencies": { 10 | "expo": "^40.0.0", 11 | "react": "16.13.1", 12 | "react-native": "https://github.com/expo/react-native/archive/sdk-40.0.1.tar.gz", 13 | "react-native-hyperlink": "0.0.14", 14 | "react-navigation": "^3.3.2", 15 | "react-native-gesture-handler": "~1.8.0", 16 | "react-native-screens": "~2.15.2", 17 | "expo-asset": "~8.2.1" 18 | }, 19 | "devDependencies": { 20 | "babel-preset-expo": "8.3.0" 21 | }, 22 | "private": true 23 | } 24 | --------------------------------------------------------------------------------