├── .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 |
--------------------------------------------------------------------------------