├── .babelrc ├── .gitignore ├── README.md ├── assets ├── .DS_Store ├── fonts │ └── SpaceMono-Regular.ttf └── images │ ├── exponent-icon@2x.png │ ├── exponent-icon@3x.png │ └── exponent-wordmark.png ├── components ├── StyledText.js └── __tests__ │ ├── StyledText-test.js │ └── __snapshots__ │ └── StyledText-test.js.snap ├── constants └── Colors.js ├── demo.gif ├── demo2.gif ├── exp.json ├── main.js ├── navigation ├── RootNavigation.js ├── Router.js └── fadeIn.js ├── package.json ├── screens ├── Landing.js ├── LinksScreen.js ├── SettingsScreen.js ├── common │ ├── button-bar.js │ ├── button.js │ ├── chat-item.js │ ├── chat-search-bar.js │ ├── create-post.js │ ├── drawer.js │ ├── image-post.js │ ├── newsfeed-item.js │ ├── onYourMind.js │ ├── search-bar.js │ └── single-image.js ├── helpers │ └── index.js ├── imageScreen.js └── img │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── bob.png │ ├── cookiemonster.jpeg │ ├── elmo.jpg │ └── me.png └── utilities ├── __tests__ └── cacheAssetsAsync-test.js ├── cacheAssetsAsync.js └── cacheHelpers.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-exponent"], 3 | "env": { 4 | "development": { 5 | "plugins": ["transform-react-jsx-source"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .exponent/**/* 3 | .idea 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-facebook-ui 2 | Pure javascript prototype of iOS Facebook UI for React Native framework. This only includes the newsfeed page--more to be updated in the future! 3 | This application may not run correctly on Android. Feel free to submit a pull reqeust for the fixed styling for Android version. 4 | 5 | ## Sponsored by Spurwing 6 | 7 | ![img](https://github.com/ggomaeng/public/blob/main/spurwing-cover.png?raw=true) 8 | 9 | This repo is sponsored by Spurwing, where their API Makes Adding Scheduling Quick, Reliable and Scalable. 10 | 11 | - Scheduling API 12 | - Enterprise Scheduling API 13 | - Scheduling API for Business 14 | - Scheduling and Calendar Management API 15 | - Booking API 16 | 17 | Check them out [here](https://github.com/Spurwing/Appointment-Scheduling-API)! 18 | 19 | 20 | ## Inspiration 21 | I was inspired by an app I use everyday--Facebook! :-) 22 | 23 | ## #Demo 24 | ![demo](https://raw.githubusercontent.com/ggomaeng/react-native-facebook-ui/master/demo.gif) 25 | 26 | Working on images: (1/5/2017) 27 | 28 | ![demo](https://raw.githubusercontent.com/ggomaeng/react-native-facebook-ui/master/demo2.gif) 29 | 30 | ## Try it out 31 | 32 | Try it with Exponent: https://getexponent.com/@sungwoopark95/react-native-facebook-ui 33 | 34 | ## Run it locally 35 | 36 | To install, there are two steps: 37 | 38 | 1. Install Exponent XDE [following this 39 | guide](https://docs.getexponent.com/versions/latest/introduction/installation.html). 40 | Also install the Exponent app on your phone if you want to test it on 41 | your device, otherwise you don't need to do anything for the simulator. 42 | 2. Clone this repo and run `npm install` 43 | ```bash 44 | git clone https://github.com/ggomaeng/react-native-facebook-ui.git facebook 45 | 46 | cd facebook 47 | npm install 48 | ``` 49 | 3. Open the project with Exponent XDE and run it. 50 | 51 | The MIT License (MIT) 52 | ===================== 53 | 54 | Copyright © 2016 Sung Woo Park 55 | 56 | Permission is hereby granted, free of charge, to any person 57 | obtaining a copy of this software and associated documentation 58 | files (the “Software”), to deal in the Software without 59 | restriction, including without limitation the rights to use, 60 | copy, modify, merge, publish, distribute, sublicense, and/or sell 61 | copies of the Software, and to permit persons to whom the 62 | Software is furnished to do so, subject to the following 63 | conditions: 64 | 65 | The above copyright notice and this permission notice shall be 66 | included in all copies or substantial portions of the Software. 67 | 68 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 69 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 70 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 71 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 72 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 73 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 74 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 75 | OTHER DEALINGS IN THE SOFTWARE. 76 | -------------------------------------------------------------------------------- /assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggomaeng/react-native-facebook-ui/0971331b329be32fe1e8ea8c0913a595db8784b6/assets/.DS_Store -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggomaeng/react-native-facebook-ui/0971331b329be32fe1e8ea8c0913a595db8784b6/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /assets/images/exponent-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggomaeng/react-native-facebook-ui/0971331b329be32fe1e8ea8c0913a595db8784b6/assets/images/exponent-icon@2x.png -------------------------------------------------------------------------------- /assets/images/exponent-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggomaeng/react-native-facebook-ui/0971331b329be32fe1e8ea8c0913a595db8784b6/assets/images/exponent-icon@3x.png -------------------------------------------------------------------------------- /assets/images/exponent-wordmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggomaeng/react-native-facebook-ui/0971331b329be32fe1e8ea8c0913a595db8784b6/assets/images/exponent-wordmark.png -------------------------------------------------------------------------------- /components/StyledText.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text } from 'react-native'; 3 | 4 | export class MonoText extends React.Component { 5 | render() { 6 | return ( 7 | 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /components/__tests__/StyledText-test.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import { MonoText } from '../StyledText'; 4 | import renderer from 'react-test-renderer'; 5 | 6 | it('renders correctly', () => { 7 | const tree = renderer.create( 8 | Snapshot test! 9 | ).toJSON(); 10 | 11 | expect(tree).toMatchSnapshot(); 12 | }); 13 | -------------------------------------------------------------------------------- /components/__tests__/__snapshots__/StyledText-test.js.snap: -------------------------------------------------------------------------------- 1 | exports[`test renders correctly 1`] = ` 2 | 14 | Snapshot test! 15 | 16 | `; 17 | -------------------------------------------------------------------------------- /constants/Colors.js: -------------------------------------------------------------------------------- 1 | const tintColor = '#4F8AFF'; 2 | 3 | export default { 4 | tintColor, 5 | tabIconDefault: '#888', 6 | main: "#3B5998", 7 | searchBar: '#384778', 8 | black: '#212121', 9 | grayText: '#898F9C', 10 | gray: '#D3D6DB', 11 | chat_bg: '#3F4045', 12 | chat_line: '#72757A', 13 | like: '#AFB4BD', 14 | liked: '#5890FF', 15 | lightGray: '#FAFAFA', 16 | tabIconSelected: tintColor, 17 | tabBar: '#fefefe', 18 | errorBackground: 'red', 19 | errorText: '#fff', 20 | warningBackground: '#EAEB5E', 21 | warningText: '#666804', 22 | noticeBackground: tintColor, 23 | noticeText: '#fff', 24 | }; 25 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggomaeng/react-native-facebook-ui/0971331b329be32fe1e8ea8c0913a595db8784b6/demo.gif -------------------------------------------------------------------------------- /demo2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggomaeng/react-native-facebook-ui/0971331b329be32fe1e8ea8c0913a595db8784b6/demo2.gif -------------------------------------------------------------------------------- /exp.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-facebook-ui", 3 | "description": "No description", 4 | "slug": "react-native-facebook-ui", 5 | "sdkVersion": "12.0.0", 6 | "version": "1.0.0", 7 | "orientation": "portrait", 8 | "primaryColor": "#cccccc", 9 | "iconUrl": "https://s3.amazonaws.com/exp-brand-assets/ExponentEmptyManifest_192.png", 10 | "notification": { 11 | "iconUrl": "https://s3.amazonaws.com/exp-us-standard/placeholder-push-icon-blue-circle.png", 12 | "color": "#000000" 13 | }, 14 | "loading": { 15 | "iconUrl": "https://s3.amazonaws.com/exp-brand-assets/ExponentEmptyManifest_192.png", 16 | "hideExponentText": false 17 | }, 18 | "packagerOpts": { 19 | "assetExts": ["ttf"] 20 | }, 21 | "ios": { 22 | "supportsTablet": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import Exponent from 'exponent'; 2 | import React from 'react'; 3 | import { 4 | AppRegistry, 5 | Platform, 6 | StatusBar, 7 | StyleSheet, 8 | View, 9 | } from 'react-native'; 10 | import { 11 | NavigationProvider, 12 | StackNavigation, 13 | } from '@exponent/ex-navigation'; 14 | import { 15 | Ionicons, 16 | } from '@exponent/vector-icons'; 17 | 18 | import Router from './navigation/Router'; 19 | import cacheAssetsAsync from './utilities/cacheAssetsAsync'; 20 | 21 | class AppContainer extends React.Component { 22 | state = { 23 | appIsReady: false, 24 | } 25 | 26 | componentWillMount() { 27 | this._loadAssetsAsync(); 28 | } 29 | 30 | async _loadAssetsAsync() { 31 | try { 32 | await cacheAssetsAsync({ 33 | images: [ 34 | require('./screens/img/bob.png'), 35 | require('./screens/img/cookiemonster.jpeg'), 36 | require('./screens/img/elmo.jpg'), 37 | require('./screens/img/me.png'), 38 | require('./screens/img/1.jpg'), 39 | require('./screens/img/2.jpg'), 40 | require('./screens/img/3.jpg'), 41 | require('./screens/img/4.jpg'), 42 | require('./screens/img/5.jpg'), 43 | ], 44 | fonts: [ 45 | Ionicons.font, 46 | ] 47 | }); 48 | } catch(e) { 49 | console.warn( 50 | 'There was an error caching assets (see: main.js), perhaps due to a ' + 51 | 'network timeout, so we skipped caching. Reload the app to try again.' 52 | ); 53 | console.log(e.message); 54 | } finally { 55 | this.setState({appIsReady: true}); 56 | } 57 | } 58 | 59 | render() { 60 | if (this.state.appIsReady) { 61 | return ( 62 | 63 | 64 | 65 | 66 | 67 | {Platform.OS === 'ios' && } 68 | {Platform.OS === 'android' && } 69 | 70 | ); 71 | } else { 72 | return ( 73 | 74 | ); 75 | } 76 | } 77 | } 78 | 79 | const styles = StyleSheet.create({ 80 | container: { 81 | flex: 1, 82 | backgroundColor: '#fff', 83 | }, 84 | statusBarUnderlay: { 85 | height: 24, 86 | backgroundColor: 'rgba(0,0,0,0.2)', 87 | }, 88 | }); 89 | 90 | Exponent.registerRootComponent(AppContainer); 91 | -------------------------------------------------------------------------------- /navigation/RootNavigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | StyleSheet, 4 | View, 5 | } from 'react-native'; 6 | import { 7 | StackNavigation, 8 | TabNavigation, 9 | TabNavigationItem, 10 | } from '@exponent/ex-navigation'; 11 | import { 12 | Ionicons, 13 | } from '@exponent/vector-icons'; 14 | 15 | import Colors from '../constants/Colors'; 16 | 17 | export default class RootNavigation extends React.Component { 18 | 19 | render() { 20 | return ( 21 | 24 | this._renderIcon('ios-paper', isSelected)}> 27 | 28 | 29 | 30 | this._renderIcon('ios-basket', isSelected)}> 33 | 34 | 35 | 36 | this._renderIcon('md-globe', isSelected)}> 39 | 40 | 41 | this._renderIcon('ios-menu', isSelected)}> 44 | 45 | 46 | 47 | ); 48 | } 49 | 50 | _renderIcon(name, isSelected) { 51 | return ( 52 | 57 | ); 58 | } 59 | 60 | 61 | } 62 | 63 | const styles = StyleSheet.create({ 64 | container: { 65 | flex: 1, 66 | backgroundColor: '#fff', 67 | }, 68 | selectedTab: { 69 | color: Colors.tabIconSelected, 70 | }, 71 | }); 72 | -------------------------------------------------------------------------------- /navigation/Router.js: -------------------------------------------------------------------------------- 1 | import { 2 | createRouter, 3 | } from '@exponent/ex-navigation'; 4 | 5 | import Landing from '../screens/Landing'; 6 | import LinksScreen from '../screens/LinksScreen'; 7 | import ImageScreen from '../screens/imageScreen'; 8 | import SettingsScreen from '../screens/SettingsScreen'; 9 | import RootNavigation from './RootNavigation'; 10 | 11 | 12 | export default createRouter(() => ({ 13 | landing: () => Landing, 14 | images: () => ImageScreen, 15 | links: () => LinksScreen, 16 | settings: () => SettingsScreen, 17 | rootNavigation: () => RootNavigation, 18 | })); 19 | -------------------------------------------------------------------------------- /navigation/fadeIn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ggoma on 1/2/17. 3 | */ 4 | import { 5 | Animated, 6 | Easing 7 | } from 'react-native'; 8 | 9 | const configureTimingTransition = (transitionProps, previousTransitionProps) => ({ 10 | timing: Animated.timing, 11 | easing: Easing.inOut(Easing.linear), 12 | duration: 150, 13 | }); 14 | 15 | export default FadedZoom = { 16 | configureTransition: configureTimingTransition, 17 | sceneAnimations: (props) => { 18 | const { 19 | position, 20 | scene, 21 | } = props; 22 | 23 | const index = scene.index; 24 | const inputRange = [index - 1, index, index + 1]; 25 | 26 | const opacity = position.interpolate({ 27 | inputRange, 28 | outputRange: [0, 1, 0], 29 | }); 30 | 31 | const scale = position.interpolate({ 32 | inputRange, 33 | outputRange: [1.2, 1, 1.2], 34 | }); 35 | 36 | return { 37 | opacity, 38 | transform: [ 39 | { translateX: 0 }, 40 | { translateY: 0 }, 41 | { scale: scale }, 42 | ], 43 | }; 44 | }, 45 | gestures: null, 46 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-facebook-ui", 3 | "version": "0.0.0", 4 | "description": "Hello Exponent!", 5 | "author": null, 6 | "main": "main.js", 7 | "scripts": { 8 | "test": "jest" 9 | }, 10 | "jest": { 11 | "preset": "jest-exponent" 12 | }, 13 | "dependencies": { 14 | "@exponent/ex-navigation": "~2.1.0", 15 | "@exponent/samples": "~1.0.2", 16 | "@exponent/vector-icons": "~2.0.3", 17 | "exponent": "~12.0.3", 18 | "lodash": "^4.17.2", 19 | "moment": "^2.17.1", 20 | "react": "~15.3.2", 21 | "react-native": "git+https://github.com/exponentjs/react-native#sdk-12.0.0" 22 | }, 23 | "devDependencies": { 24 | "jest-exponent": "~0.1.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /screens/Landing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ggoma on 12/17/16. 3 | */ 4 | import React, {Component} from 'react'; 5 | import { 6 | Animated, 7 | View, 8 | Text, 9 | Dimensions, 10 | RefreshControl, 11 | Modal, 12 | ScrollView, 13 | ListView, 14 | StyleSheet 15 | } from 'react-native'; 16 | 17 | const {width, height} = Dimensions.get('window'); 18 | 19 | import Colors from '../constants/Colors'; 20 | import SearchBar from './common/search-bar'; 21 | import ButtonBar from './common/button-bar'; 22 | import OnYourMind from './common/onYourMind'; 23 | import NewsFeedItem from './common/newsfeed-item'; 24 | import CreatePost from './common/create-post'; 25 | 26 | 27 | import Drawer from './common/drawer'; 28 | import _ from 'lodash'; 29 | 30 | //1 is regular post, 2 is image 31 | const data = ['0', 32 | {type: 'image', images: ['1']}, 33 | {type: 'image', images: ['1', '2']}, 34 | {type: 'image', images: ['1', '2', '3']}, 35 | {type: 'image', images: ['1', '2', '3', '4']}, 36 | {type: 'image', images: ['1', '2', '3', '4', '5']}, 37 | {type: 'image', images: ['1', '2', '3', '4', '5', '6']}, 38 | {type: 'post'}, 39 | {type: 'post'}, 40 | {type: 'post'} 41 | ]; 42 | const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); 43 | 44 | export default class Landing extends Component { 45 | constructor(props) { 46 | super(props); 47 | 48 | this.state = { 49 | modal: false, 50 | refreshing: false, 51 | loading: false, 52 | opacity: new Animated.Value(1), 53 | header_height: new Animated.Value(96), 54 | 55 | dataSource: ds.cloneWithRows(data) 56 | }; 57 | 58 | this.offsetY = 0; 59 | this.offsetX = new Animated.Value(0); 60 | this.content_height = 0; 61 | this._onScroll = this._onScroll.bind(this); 62 | this.loadMore = _.debounce(this.loadMore, 300); 63 | this._onDrawerOpen = this._onDrawerOpen.bind(this); 64 | } 65 | 66 | componentDidMount() { 67 | setTimeout(() => {this.measureView()}, 0); 68 | } 69 | 70 | measureView() { 71 | this.refs.view.measure((a, b, w, h, px, py) => { 72 | this.content_height = h; 73 | }); 74 | } 75 | 76 | 77 | _onRefresh() { 78 | this.setState({refreshing: true}); 79 | setTimeout(() => { 80 | this.setState({refreshing: false}); 81 | }, 1500) 82 | } 83 | 84 | _renderRow(data) { 85 | 86 | if (data == '0') { 87 | return this.setState({modal: true})}/> 88 | } 89 | 90 | return 91 | } 92 | 93 | renderModal() { 94 | return ( 95 | this.setState({modal: false})} 100 | > 101 | this.setState({modal: false})} /> 102 | 103 | ) 104 | 105 | } 106 | 107 | loadMore() { 108 | console.log('should load more'); 109 | this.setState({loading: true}); 110 | //add two more child views 111 | data.push('1'); 112 | data.push('1'); 113 | this.setState({dataSource: ds.cloneWithRows(data)}); 114 | 115 | } 116 | 117 | _onScroll(event) { 118 | const e = event.nativeEvent; 119 | const l_height = e.contentSize.height; 120 | const offset = e.contentOffset.y; 121 | 122 | if(offset > this.offsetY) { 123 | console.log('scrolling down'); 124 | if(!(offset < 32)) { 125 | this.refs.buttonBar.hide(); 126 | } 127 | 128 | if(!(offset < 56)) { 129 | this.refs.searchBar.hide(); 130 | } 131 | 132 | //if 133 | } else { 134 | console.log('scrolling up'); 135 | 136 | this.refs.buttonBar.show(); 137 | setTimeout(() => {this.refs.searchBar.show();}, 150); 138 | 139 | } 140 | 141 | this.offsetY = offset; 142 | 143 | 144 | if(offset + this.content_height >= l_height) { 145 | console.log('end'); 146 | this.loadMore(); 147 | } 148 | 149 | // console.log(e); 150 | } 151 | 152 | getStyle() { 153 | return { 154 | opacity: this.offsetX.interpolate({ 155 | inputRange: [0, width * 4/5], 156 | outputRange: [1, 0], 157 | }), 158 | } 159 | } 160 | 161 | renderFade() { 162 | return ( 163 | 164 | 165 | ) 166 | } 167 | 168 | renderDrawer() { 169 | return ( 170 | 171 | ) 172 | } 173 | 174 | _onDrawerOpen(event) { 175 | const e = event.nativeEvent; 176 | const offset = e.contentOffset.x; 177 | this.offsetX.setValue(offset); 178 | } 179 | 180 | openChat() { 181 | this.refs.scrollview.scrollTo({x: width * 4/5, y: 0, animated: true}); 182 | } 183 | 184 | 185 | render() { 186 | 187 | return ( 188 | 189 | {this.renderDrawer()} 190 | 199 | 200 | 201 | 202 | 203 | 209 | } 210 | 211 | onScroll={this._onScroll} 212 | dataSource={this.state.dataSource} 213 | renderRow={(data) => this._renderRow(data)} 214 | /> 215 | 216 | {this.renderFade()} 217 | 218 | {this.renderModal()} 219 | 220 | ) 221 | } 222 | } 223 | 224 | const styles= StyleSheet.create({ 225 | container: { 226 | flex: 1, 227 | width, 228 | height, 229 | backgroundColor: Colors.gray 230 | }, 231 | fade: { 232 | height, 233 | backgroundColor: 'black', 234 | width: width * 4/5, 235 | }, 236 | drawer: { 237 | height, 238 | padding: 8, 239 | paddingTop: 20, 240 | width: width * 4/5, 241 | position: 'absolute', 242 | backgroundColor: Colors.chat_bg, 243 | right: 0 244 | 245 | } 246 | }) -------------------------------------------------------------------------------- /screens/LinksScreen.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ScrollView, 4 | StyleSheet, 5 | } from 'react-native'; 6 | import { 7 | ExponentLinksView, 8 | } from '@exponent/samples'; 9 | 10 | export default class LinksScreen extends React.Component { 11 | static route = { 12 | navigationBar: { 13 | title: 'Links', 14 | }, 15 | } 16 | 17 | render() { 18 | return ( 19 | 22 | 23 | { /* Go ahead and delete ExponentLinksView and replace it with your 24 | * content, we just wanted to provide you with some helpful links */ } 25 | 26 | 27 | 28 | ); 29 | } 30 | 31 | } 32 | 33 | const styles = StyleSheet.create({ 34 | container: { 35 | flex: 1, 36 | paddingTop: 15, 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /screens/SettingsScreen.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ScrollView, 4 | StyleSheet, 5 | } from 'react-native'; 6 | import { 7 | ExponentConfigView, 8 | } from '@exponent/samples'; 9 | 10 | export default class SettingsScreen extends React.Component { 11 | static route = { 12 | navigationBar: { 13 | title: 'exp.json' 14 | }, 15 | } 16 | 17 | render() { 18 | return ( 19 | 22 | 23 | { /* Go ahead and delete ExponentConfigView and replace it with your 24 | * content, we just wanted to give you a quick view of your config */ } 25 | 26 | 27 | 28 | ); 29 | } 30 | } 31 | 32 | const styles = StyleSheet.create({ 33 | container: { 34 | flex: 1, 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /screens/common/button-bar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ggoma on 12/17/16. 3 | */ 4 | import React, {Component} from 'react'; 5 | import { 6 | Animated, 7 | View, 8 | Text, 9 | StyleSheet 10 | } from 'react-native'; 11 | 12 | import {Ionicons} from '@exponent/vector-icons' 13 | import Colors from '../../constants/Colors'; 14 | 15 | export default class ButtonBar extends Component { 16 | constructor() { 17 | super(); 18 | this.state = { 19 | height: new Animated.Value(36), 20 | buttons: ['Live', 'Photo', 'Check In'], 21 | icons: ['ios-videocam', 'ios-camera', 'ios-pin'] 22 | }; 23 | } 24 | 25 | componentDidMount() { 26 | setTimeout(() => {this.measureView()}, 0) 27 | } 28 | 29 | measureView() { 30 | console.log('measuring view'); 31 | this.refs.container.measure((a, b, w, h, x, y) => { 32 | this.setState({height: new Animated.Value(h), original: h}); 33 | }); 34 | } 35 | 36 | hide() { 37 | 38 | if(this.state.animating) { 39 | return; 40 | } 41 | console.log('animating'); 42 | 43 | this.setState({animating: true}); 44 | Animated.timing( 45 | this.state.height, 46 | {toValue: 0} 47 | ).start(); 48 | } 49 | 50 | show() { 51 | if(!this.state.animating) { 52 | return; 53 | } 54 | console.log('animating'); 55 | this.setState({animating: false}); 56 | Animated.timing( 57 | this.state.height, 58 | {toValue: this.state.original} 59 | ).start(); 60 | } 61 | 62 | renderButtons() { 63 | const {buttons, icons} = this.state; 64 | return buttons.map((button, i) => { 65 | return ( 66 | 67 | 68 | {button} 69 | 70 | ) 71 | }) 72 | 73 | } 74 | 75 | getStyle() { 76 | const {height} = this.state; 77 | 78 | 79 | return {height, opacity: height.interpolate({ 80 | inputRange: [0, 36], 81 | outputRange: [0, 1], 82 | })} 83 | } 84 | 85 | render() { 86 | 87 | return ( 88 | 89 | 90 | {this.renderButtons()} 91 | 92 | 93 | ) 94 | } 95 | } 96 | 97 | const styles = StyleSheet.create({ 98 | container: { 99 | flexDirection: 'row', 100 | height: 36, 101 | backgroundColor: Colors.lightGray, 102 | borderBottomWidth: StyleSheet.hairlineWidth 103 | }, 104 | 105 | buttonItem: { 106 | flex: 1, 107 | backgroundColor: 'transparent', 108 | flexDirection: 'row', 109 | justifyContent: 'center', 110 | alignItems: 'center', 111 | borderLeftWidth: StyleSheet.hairlineWidth, 112 | borderColor: '#ddd' 113 | }, 114 | 115 | text: { 116 | fontSize: 14, 117 | backgroundColor: 'transparent', 118 | fontWeight: '700', 119 | marginLeft: 8, 120 | color: Colors.black 121 | } 122 | }); -------------------------------------------------------------------------------- /screens/common/button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ggoma on 12/17/16. 3 | */ 4 | import React, {Component} from 'react'; 5 | import { 6 | View, 7 | Text, 8 | TouchableOpacity, 9 | StyleSheet 10 | } from 'react-native'; 11 | 12 | import Colors from '../../constants/Colors'; 13 | import {Ionicons} from '@exponent/vector-icons'; 14 | 15 | export default class Button extends Component { 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | pressed: false, 20 | name: props.name, 21 | icon: props.icon 22 | } 23 | } 24 | 25 | pressed(name) { 26 | if(name == 'Like') { 27 | this.setState({pressed: !this.state.pressed}); 28 | if(!this.state.pressed) { 29 | this.props.onPress('Like'); 30 | } else { 31 | this.props.onPress('Dislike'); 32 | } 33 | 34 | 35 | } 36 | 37 | 38 | 39 | 40 | 41 | 42 | } 43 | 44 | render() { 45 | const {pressed, name, icon} = this.state; 46 | 47 | return ( 48 | this.pressed(name)} style={styles.buttonItem}> 49 | 50 | {name} 51 | 52 | ) 53 | } 54 | } 55 | 56 | const styles = StyleSheet.create({ 57 | buttonContainer: { 58 | flexDirection: 'row', 59 | height: 36, 60 | borderBottomWidth: StyleSheet.hairlineWidth 61 | }, 62 | 63 | 64 | buttonItem: { 65 | flex: 1, 66 | backgroundColor: 'transparent', 67 | flexDirection: 'row', 68 | justifyContent: 'center', 69 | alignItems: 'center', 70 | }, 71 | 72 | text: { 73 | backgroundColor: 'transparent', 74 | fontSize: 14, 75 | fontWeight: '700', 76 | marginLeft: 8, 77 | } 78 | }) -------------------------------------------------------------------------------- /screens/common/chat-item.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ggoma on 12/21/16. 3 | */ 4 | import React from 'react'; 5 | import { 6 | View, 7 | Image, 8 | Text, 9 | StyleSheet 10 | } from 'react-native'; 11 | 12 | import Colors from '../../constants/Colors'; 13 | import {randomProfile} from '../helpers'; 14 | import {Ionicons} from '@exponent/vector-icons'; 15 | 16 | export default () => { 17 | 18 | const avatar = randomProfile(); 19 | 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | {avatar.name} 27 | {avatar.online ? 28 | · 29 | : 30 | 31 | 30m 32 | 33 | 34 | } 35 | 36 | 37 | 38 | ) 39 | } 40 | 41 | const styles = StyleSheet.create({ 42 | container: { 43 | paddingLeft: 12, 44 | flexDirection: 'row', 45 | alignItems: 'center', 46 | height: 48, 47 | borderBottomColor: Colors.chat_line, 48 | borderBottomWidth: StyleSheet.hairlineWidth 49 | }, 50 | 51 | desc: { 52 | flex: 1, 53 | flexDirection: 'row', 54 | justifyContent: 'space-between', 55 | alignItems: 'center', 56 | }, 57 | 58 | descText: { 59 | color: Colors.chat_line, 60 | fontSize: 12, 61 | marginRight: 8, 62 | 63 | }, 64 | 65 | phone: { 66 | flexDirection: 'row', 67 | alignItems: 'center', 68 | marginRight: 12, 69 | }, 70 | 71 | text: { 72 | color: 'white', 73 | paddingLeft: 10, 74 | fontSize: 14, 75 | fontWeight: '500' 76 | }, 77 | 78 | img: { 79 | height: 40, 80 | width: 40 81 | } 82 | }); -------------------------------------------------------------------------------- /screens/common/chat-search-bar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ggoma on 12/21/16. 3 | */ 4 | import React from 'react'; 5 | import { 6 | View, 7 | Text, 8 | TextInput, 9 | StyleSheet 10 | } from 'react-native'; 11 | 12 | import {Ionicons} from '@exponent/vector-icons'; 13 | import Colors from '../../constants/Colors'; 14 | 15 | export default () => { 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ) 32 | } 33 | 34 | const styles = StyleSheet.create({ 35 | container: { 36 | marginTop: 8, 37 | padding: 12, 38 | 39 | }, 40 | 41 | icon: { 42 | alignItems: 'center', 43 | justifyContent: 'center', 44 | marginLeft: 8, 45 | }, 46 | 47 | 48 | searchContainer: { 49 | height: 28, 50 | flexDirection: 'row' 51 | }, 52 | 53 | search: { 54 | flex: 1, 55 | backgroundColor: '#292C34', 56 | justifyContent: 'center', 57 | alignItems: 'center', 58 | padding: 4, 59 | fontSize: 12, 60 | color: 'white', 61 | borderRadius: 5, 62 | }, 63 | 64 | searchBarContainer: { 65 | flex: 1, 66 | flexDirection: 'row', 67 | alignItems: 'center', 68 | height: 28, 69 | backgroundColor: '#292C34', 70 | borderRadius: 5, 71 | padding: 8, 72 | }, 73 | 74 | searchIcon: { 75 | justifyContent: 'center', 76 | alignItems: 'center', 77 | }, 78 | 79 | }) -------------------------------------------------------------------------------- /screens/common/create-post.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ggoma on 12/21/16. 3 | */ 4 | import React, {Component} from 'react'; 5 | import { 6 | View, 7 | Image, 8 | Dimensions, 9 | Keyboard, 10 | Text, 11 | TextInput, 12 | TouchableOpacity, 13 | StatusBar, 14 | StyleSheet 15 | } from 'react-native'; 16 | 17 | const {width, height} = Dimensions.get('window'); 18 | 19 | 20 | 21 | import Colors from '../../constants/Colors'; 22 | import {Ionicons} from '@exponent/vector-icons'; 23 | 24 | export default class CreatePost extends Component{ 25 | constructor() { 26 | super(); 27 | this.state = { 28 | visibleHeight: Dimensions.get('window').height, 29 | k_visible: false, 30 | } 31 | } 32 | 33 | componentDidMount () { 34 | Keyboard.addListener('keyboardWillShow', this.keyboardWillShow.bind(this)); 35 | Keyboard.addListener('keyboardWillHide', this.keyboardWillHide.bind(this)); 36 | } 37 | 38 | componentWillUnmount() { 39 | Keyboard.removeListener('keyboardWillShow'); 40 | Keyboard.removeListener('keyboardWillHide'); 41 | } 42 | 43 | keyboardWillShow (e) { 44 | let newSize = Dimensions.get('window').height - e.endCoordinates.height 45 | this.setState({visibleHeight: newSize, k_visible: true}) 46 | } 47 | 48 | keyboardWillHide (e) { 49 | if(this.componentDidMount) { 50 | this.setState({visibleHeight: Dimensions.get('window').height, k_visible: false}) 51 | } 52 | 53 | } 54 | 55 | 56 | renderHeader() { 57 | return ( 58 | 61 | 62 | Cancel 63 | 64 | Update Status 65 | Post 66 | 67 | ) 68 | } 69 | 70 | renderAvatar() { 71 | return ( 72 | 73 | 74 | 75 | Sung Woo Park 76 | 77 | 79 | 80 | Public 81 | 82 | 83 | 85 | 86 | Seoul 87 | 88 | 89 | 90 | 91 | 92 | ) 93 | } 94 | 95 | renderText() { 96 | return ( 97 | 98 | 99 | 100 | ) 101 | } 102 | 103 | renderMenu() { 104 | const {k_visible} = this.state; 105 | if(k_visible) { 106 | return ( 107 | {Keyboard.dismiss()}} 109 | style={{height: 56, borderTopWidth: StyleSheet.hairlineWidth, borderColor: Colors.chat_line, 110 | flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingLeft: 16}}> 111 | Add to your post 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | ) 121 | } 122 | 123 | return ( 124 | this.renderList() 125 | ) 126 | } 127 | 128 | renderList() { 129 | const objs = 130 | [ 131 | { 132 | icon: 'md-camera', 133 | color: '#93B75F', 134 | name: 'Photo/Video' 135 | }, 136 | { 137 | icon: 'md-videocam', 138 | color: '#E7404E', 139 | name: 'Live Video' 140 | }, 141 | { 142 | icon: 'md-pin', 143 | color: '#D8396F', 144 | name: 'Check In' 145 | }, 146 | { 147 | icon: 'ios-happy', 148 | color: '#EDC370', 149 | name: 'Feeling/Activity' 150 | }, 151 | { 152 | icon: 'ios-person-add', 153 | color: '#628FF6', 154 | name: 'Tag Friends' 155 | } 156 | ]; 157 | 158 | return objs.map((o, i) => { 159 | return ( 160 | 162 | 163 | {o.name} 164 | 165 | ) 166 | }) 167 | } 168 | 169 | render() { 170 | return ( 171 | 172 | 173 | {this.renderHeader()} 174 | {this.renderAvatar()} 175 | {this.renderText()} 176 | {this.renderMenu()} 177 | 178 | ) 179 | } 180 | } 181 | 182 | const styles = StyleSheet.create({ 183 | container: { 184 | flex: 1, 185 | }, 186 | 187 | img: { 188 | width: 40, 189 | height: 40 190 | }, 191 | 192 | icon: { 193 | marginLeft: 10 194 | } 195 | }); -------------------------------------------------------------------------------- /screens/common/drawer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ggoma on 12/21/16. 3 | */ 4 | import React, {Component} from 'react'; 5 | import { 6 | View, 7 | Dimensions, 8 | ListView, 9 | Text, 10 | StyleSheet 11 | } from 'react-native'; 12 | 13 | const {width, height} = Dimensions.get('window'); 14 | import Colors from '../../constants/Colors'; 15 | import ChatItem from './chat-item'; 16 | import ChatSearchBar from './chat-search-bar'; 17 | 18 | const data = ['1', '1', '1', '1', '1', '1', '1', '1', '1']; 19 | const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); 20 | 21 | export default class Drawer extends Component { 22 | constructor() { 23 | super(); 24 | this.state = { 25 | dataSource: ds.cloneWithRows(data) 26 | } 27 | } 28 | 29 | _renderRow(data) { 30 | return ( 31 | 32 | ) 33 | } 34 | 35 | 36 | render() { 37 | return ( 38 | 39 | 40 | this._renderRow(data)} 44 | /> 45 | 46 | 47 | ) 48 | } 49 | } 50 | 51 | const styles = StyleSheet.create({ 52 | drawer: { 53 | height, 54 | paddingTop: 20, 55 | width: width * 4/5, 56 | position: 'absolute', 57 | backgroundColor: Colors.chat_bg, 58 | right: 0 59 | 60 | } 61 | }); -------------------------------------------------------------------------------- /screens/common/image-post.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ggoma on 1/1/17. 3 | */ 4 | import React, {Component} from 'react'; 5 | import { 6 | View, 7 | Dimensions, 8 | Text, 9 | StyleSheet, 10 | TouchableWithoutFeedback, 11 | Image, 12 | } from 'react-native'; 13 | 14 | import {getImage} from '../helpers'; 15 | const {width, height} = Dimensions.get('window'); 16 | import { withNavigation } from '@exponent/ex-navigation'; 17 | 18 | import SingleImage from './single-image'; 19 | 20 | 21 | @withNavigation 22 | export default class ImagePost extends Component { 23 | 24 | 25 | renderImages() { 26 | const {imageCount, images} = this.props; 27 | 28 | switch(imageCount) { 29 | //1 image 30 | case 1: 31 | return ( 32 | 33 | 34 | 35 | ); 36 | break; 37 | 38 | case 2: 39 | return( 40 | 41 | 42 | 43 | 44 | 45 | 46 | ); 47 | break; 48 | case 3: 49 | return( 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ); 60 | break; 61 | case 4: 62 | return( 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ); 76 | break; 77 | case 5: 78 | return( 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | ); 93 | break; 94 | default: 95 | //for cases with 5+ pictures 96 | //@TODO render images with more than 6 97 | return( 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | + {imageCount - 5} 111 | 112 | 113 | 114 | 115 | 116 | 117 | ); 118 | 119 | 120 | } 121 | } 122 | 123 | openImages() { 124 | const {images} = this.props; 125 | 126 | this.props.navigator.push('images', {images}); 127 | } 128 | 129 | render() { 130 | const {imageCount} = this.props; 131 | return ( 132 | 133 | 134 | This is an image post with {imageCount} image(s). 135 | 136 | {this.renderImages()} 137 | 138 | ) 139 | } 140 | } 141 | 142 | const styles = StyleSheet.create({ 143 | imageContainer: { 144 | height: height/2.5, 145 | }, 146 | 147 | img: { 148 | flex: 1, 149 | width: null, 150 | height: null 151 | }, 152 | textContainer: { 153 | padding: 16, 154 | paddingTop: 0, 155 | paddingBottom: 8 156 | } 157 | }); -------------------------------------------------------------------------------- /screens/common/newsfeed-item.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ggoma on 12/17/16. 3 | */ 4 | import React, {Component} from 'react'; 5 | import { 6 | View, 7 | Text, 8 | Image, 9 | StyleSheet 10 | } from 'react-native'; 11 | 12 | import Colors from '../../constants/Colors'; 13 | import {Ionicons} from '@exponent/vector-icons'; 14 | import {randomProfile} from '../helpers'; 15 | import moment from 'moment'; 16 | import ImagePost from './image-post'; 17 | 18 | import Button from './button'; 19 | 20 | export default class NewsFeedItem extends Component { 21 | constructor() { 22 | super(); 23 | this.state = { 24 | profile: randomProfile(), 25 | time: moment().format('hh:mm A MMM Do'), 26 | buttons: ['Like', 'Comment', 'Share'], 27 | icons: ['md-thumbs-up', 'md-chatbubbles', 'ios-share-alt'], 28 | likes: 0, 29 | comments: 0 30 | }; 31 | } 32 | 33 | buttonOnPress(name) { 34 | console.log(name); 35 | switch(name) { 36 | case 'Like': 37 | this.setState({likes: this.state.likes + 1}); 38 | break; 39 | case 'Dislike': 40 | this.setState({likes: this.state.likes - 1}); 41 | break; 42 | case 'Comment': 43 | this.setState({comments: this.state.comments + 1}); 44 | break; 45 | default: 46 | return 47 | } 48 | } 49 | 50 | renderAvatar() { 51 | const {profile, time} = this.state; 52 | return ( 53 | 54 | 55 | 56 | {profile.name} 57 | {time} 58 | 59 | 60 | ) 61 | } 62 | 63 | renderLikesAndComments() { 64 | const {likes, comments} = this.state; 65 | 66 | if(likes == 0 && comments == 0) { 67 | return 68 | } 69 | 70 | return ( 71 | 72 | {likes > 0 ? : ''}{likes == 0 ? '' : ' ' + likes} 73 | {comments == 0 ? '' : comments + ' Comments'} 74 | 75 | ) 76 | } 77 | 78 | renderLikeBar() { 79 | const {buttons, icons} = this.state; 80 | return buttons.map((button, i) => { 81 | return ( 82 |