├── languages ├── cn.js ├── en.js └── index.js ├── global └── config.js ├── assets ├── images │ ├── icon.png │ ├── splash.png │ ├── robot-dev.png │ └── robot-prod.png └── fonts │ └── SpaceMono-Regular.ttf ├── babel.config.js ├── .gitignore ├── components ├── StyledText.js ├── __tests__ │ ├── StyledText-test.js │ └── __snapshots__ │ │ └── StyledText-test.js.snap └── TabBarIcon.js ├── constants ├── Layout.js └── Colors.js ├── store ├── actions │ ├── actionTypes.js │ ├── userActions.js │ └── articleActions.js └── reducers │ ├── articlesReducer.js │ └── usersReducer.js ├── .expo-shared └── assets.json ├── __tests__ ├── __snapshots__ │ └── App-test.js.snap └── App-test.js ├── navigation ├── AppNavigator.web.js ├── AppNavigator.js └── MainTabNavigator.js ├── app.json ├── README.md ├── screens ├── SettingsScreen.js ├── LinksScreen.js └── HomeScreen.js ├── package.json └── App.js /languages/cn.js: -------------------------------------------------------------------------------- 1 | export default cn = { 2 | hello: "你好!", 3 | }; -------------------------------------------------------------------------------- /languages/en.js: -------------------------------------------------------------------------------- 1 | export default en = { 2 | hello: "Hello, How are you?" 3 | }; -------------------------------------------------------------------------------- /global/config.js: -------------------------------------------------------------------------------- 1 | export default config = { 2 | secretKey: 'secret', 3 | apiServer: '', 4 | }; -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/best-react-native.project/HEAD/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/best-react-native.project/HEAD/assets/images/splash.png -------------------------------------------------------------------------------- /assets/images/robot-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/best-react-native.project/HEAD/assets/images/robot-dev.png -------------------------------------------------------------------------------- /assets/images/robot-prod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/best-react-native.project/HEAD/assets/images/robot-prod.png -------------------------------------------------------------------------------- /assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/best-react-native.project/HEAD/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | web-report/ 12 | 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /components/StyledText.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text } from 'react-native'; 3 | 4 | export function MonoText(props) { 5 | return ( 6 | 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /languages/index.js: -------------------------------------------------------------------------------- 1 | import LocalizedStrings from 'react-localization'; 2 | import en from './en'; 3 | import cn from './cn'; 4 | 5 | let strings = new LocalizedStrings({ 6 | en: {...en}, 7 | cn: {...cn} 8 | }); 9 | 10 | export default strings; -------------------------------------------------------------------------------- /constants/Layout.js: -------------------------------------------------------------------------------- 1 | import { Dimensions } from 'react-native'; 2 | 3 | const width = Dimensions.get('window').width; 4 | const height = Dimensions.get('window').height; 5 | 6 | export default { 7 | window: { 8 | width, 9 | height, 10 | }, 11 | isSmallDevice: width < 375, 12 | }; 13 | -------------------------------------------------------------------------------- /store/actions/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const GOT_ALL_ARTICLES = 'GOT_ALL_ARTICLES'; 2 | export const GOT_MY_ARTICLES = 'GOT_MY_ARTICLES'; 3 | export const GOT_SINGLE_ARTICLE = 'GOT_SINGLE_ARTICLE'; 4 | 5 | export const LOGIN_SUCCESSFUL = 'LOGIN_SUCCESSFUL'; 6 | export const LOGOUT_USER = 'LOGOUT_USER'; 7 | export const USER_TEST = 'USER_TEST'; -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "0cae4d70c6df3e5e96ee8b5c442b59d55c8ab8deb466992ab9abc523822f2a1b": true, 3 | "e997a5256149a4b76e6bfd6cbf519c5e5a0f1d278a3d8fa1253022b03c90473b": true, 4 | "af683c96e0ffd2cf81287651c9433fa44debc1220ca7cb431fe482747f34a505": true, 5 | "e7fc0741cc6562975a990e3d9ef820571588dab20aba97032df9f00caa9cd57a": true 6 | } -------------------------------------------------------------------------------- /components/__tests__/StyledText-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | import { MonoText } from '../StyledText'; 5 | 6 | it(`renders correctly`, () => { 7 | const tree = renderer.create(Snapshot test!).toJSON(); 8 | 9 | expect(tree).toMatchSnapshot(); 10 | }); 11 | -------------------------------------------------------------------------------- /components/__tests__/__snapshots__/StyledText-test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders correctly 1`] = ` 4 | 14 | Snapshot test! 15 | 16 | `; 17 | -------------------------------------------------------------------------------- /constants/Colors.js: -------------------------------------------------------------------------------- 1 | const tintColor = '#2f95dc'; 2 | 3 | export default { 4 | tintColor, 5 | tabIconDefault: '#ccc', 6 | tabIconSelected: tintColor, 7 | tabBar: '#fefefe', 8 | errorBackground: 'red', 9 | errorText: '#fff', 10 | warningBackground: '#EAEB5E', 11 | warningText: '#666804', 12 | noticeBackground: tintColor, 13 | noticeText: '#fff', 14 | }; 15 | -------------------------------------------------------------------------------- /components/TabBarIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Ionicons } from '@expo/vector-icons'; 3 | 4 | import Colors from '../constants/Colors'; 5 | 6 | export default function TabBarIcon(props) { 7 | return ( 8 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/App-test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`App renders the loading screen 1`] = ` 4 | 9 | `; 10 | 11 | exports[`App renders the root without loading screen 1`] = ` 12 | 20 | 21 | 22 | `; 23 | -------------------------------------------------------------------------------- /navigation/AppNavigator.web.js: -------------------------------------------------------------------------------- 1 | import { createBrowserApp } from '@react-navigation/web'; 2 | import { createSwitchNavigator } from 'react-navigation'; 3 | 4 | import MainTabNavigator from './MainTabNavigator'; 5 | 6 | const switchNavigator = createSwitchNavigator({ 7 | // You could add another route here for authentication. 8 | // Read more at https://reactnavigation.org/docs/en/auth-flow.html 9 | Main: MainTabNavigator, 10 | }); 11 | switchNavigator.path = ''; 12 | 13 | export default createBrowserApp(switchNavigator, { history: 'hash' }); 14 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "checkoutmaster", 4 | "slug": "checkoutmasterslug", 5 | "privacy": "public", 6 | "sdkVersion": "36.0.0", 7 | "platforms": [ 8 | "ios", 9 | "android", 10 | "web" 11 | ], 12 | "version": "1.0.0", 13 | "orientation": "portrait", 14 | "icon": "./assets/images/icon.png", 15 | "splash": { 16 | "image": "./assets/images/splash.png", 17 | "resizeMode": "contain", 18 | "backgroundColor": "#ffffff" 19 | }, 20 | "updates": { 21 | "fallbackToCacheTimeout": 0 22 | }, 23 | "assetBundlePatterns": [ 24 | "**/*" 25 | ], 26 | "ios": { 27 | "supportsTablet": true 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /__tests__/App-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import NavigationTestUtils from 'react-navigation/NavigationTestUtils'; 3 | import renderer from 'react-test-renderer'; 4 | 5 | import App from '../App'; 6 | 7 | jest.mock('expo', () => ({ 8 | AppLoading: 'AppLoading', 9 | })); 10 | 11 | jest.mock('../navigation/AppNavigator', () => 'AppNavigator'); 12 | 13 | describe('App', () => { 14 | jest.useFakeTimers(); 15 | 16 | beforeEach(() => { 17 | NavigationTestUtils.resetInternalState(); 18 | }); 19 | 20 | it(`renders the loading screen`, () => { 21 | const tree = renderer.create().toJSON(); 22 | expect(tree).toMatchSnapshot(); 23 | }); 24 | 25 | it(`renders the root without loading screen`, () => { 26 | const tree = renderer.create().toJSON(); 27 | expect(tree).toMatchSnapshot(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-expo-master 2 | React native startup project with features of navigation, multilingual, redux/redux-thunk middleware, jwt token for rest api, Asyncstorage, NativeBase, react-native-elements 3 | 4 | This project has been built in [React native Expo](https://docs.expo.io), [NativeBase](https://docs.nativebase.io), 5 | [react-native-elements](https://react-native-elements.github.io/react-native-elements),[react-navigation](https://reactnavigation.org), 6 | [redux](https://redux.js.org), [react-localization](https://github.com/stefalda/react-localization), expo-jwt 7 | 8 | 9 | ## Global Setting 10 | Firstly, nodejs should be installed. 11 | 12 | npm install -g expo-cli 13 | 14 | npm install -g yarn 15 | 16 | 17 | ## Install 18 | 19 | yarn install 20 | 21 | 22 | ## Run development server 23 | First, you need to run android or ios phone (or emulator) 24 | 25 | yarn start 26 | 27 | 28 | -------------------------------------------------------------------------------- /screens/SettingsScreen.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { ExpoConfigView } from '@expo/samples'; 3 | import { connect } from 'react-redux'; 4 | import { testUser } from '../store/actions/userActions'; 5 | 6 | class SettingsScreen extends Component { 7 | static navigationOptions = { 8 | title: 'app.json', 9 | }; 10 | 11 | render() { 12 | /** 13 | * Go ahead and delete ExpoConfigView and replace it with your content; 14 | * we just wanted to give you a quick view of your config. 15 | */ 16 | return ; 17 | } 18 | } 19 | 20 | const mapStateToProps = state => { 21 | return { 22 | testData: state.users.testData, 23 | }; 24 | }; 25 | 26 | const mapDispatchToProps = dispatch => { 27 | return { 28 | testUser: ()=>dispatch(testUser()), 29 | }; 30 | }; 31 | 32 | export default connect(mapStateToProps, mapDispatchToProps)(SettingsScreen); 33 | -------------------------------------------------------------------------------- /store/reducers/articlesReducer.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from '../actions/actionTypes'; 2 | 3 | const initialState = { 4 | articles: [], 5 | article: {}, 6 | myArticles: {}, 7 | }; 8 | 9 | const articlesReducer = (state = initialState, action) => { 10 | switch (action.type) { 11 | case actionTypes.GOT_ALL_ARTICLES: 12 | return { 13 | ...state, 14 | articles: action.articles 15 | }; 16 | case actionTypes.GOT_MY_ARTICLES: { 17 | return { 18 | ...state, 19 | myArticles: action.myArticles 20 | } 21 | } 22 | case actionTypes.GOT_SINGLE_ARTICLE: 23 | return { 24 | ...state, 25 | article: action.article 26 | }; 27 | default: 28 | return state; 29 | } 30 | }; 31 | 32 | export default articlesReducer; 33 | -------------------------------------------------------------------------------- /navigation/AppNavigator.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { createAppContainer, createSwitchNavigator } from 'react-navigation'; 3 | 4 | import MainTabNavigator from './MainTabNavigator'; 5 | import { Provider } from 'react-redux'; 6 | import { combineReducers, createStore, applyMiddleware } from 'redux'; 7 | import articlesReducer from '../store/reducers/articlesReducer'; 8 | import usersReducer from '../store/reducers/usersReducer'; 9 | import thunk from 'redux-thunk'; 10 | 11 | let Navigation = createAppContainer( 12 | createSwitchNavigator({ 13 | // You could add another route here for authentication. 14 | // Read more at https://reactnavigation.org/docs/en/auth-flow.html 15 | Main: MainTabNavigator, 16 | }) 17 | ); 18 | 19 | const rootReducer = combineReducers({ 20 | articles: articlesReducer, 21 | users: usersReducer 22 | }); 23 | 24 | const store = createStore(rootReducer, applyMiddleware(thunk)); 25 | 26 | export default class AppNavigator extends Component { 27 | render() { 28 | return 29 | 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /screens/LinksScreen.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { ScrollView, StyleSheet, View, Text } from 'react-native'; 3 | import { ExpoLinksView } from '@expo/samples'; 4 | import { connect } from 'react-redux'; 5 | import { testUser } from '../store/actions/userActions'; 6 | 7 | class LinksScreen extends Component { 8 | static navigationOptions = { 9 | header: null, 10 | }; 11 | constructor(props){ 12 | super(props); 13 | } 14 | render() { 15 | return ( 16 | 17 | {/** 18 | * Go ahead and delete ExpoLinksView and replace it with your content; 19 | * we just wanted to provide you with some helpful links. 20 | */} 21 | 22 | Currently Redux valud is 23 | 24 | 25 | 26 | { this.props.testData } 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | } 34 | 35 | const styles = StyleSheet.create({ 36 | container: { 37 | flex: 1, 38 | paddingTop: 15, 39 | backgroundColor: '#fff', 40 | }, 41 | }); 42 | 43 | const mapStateToProps = state => { 44 | return { 45 | testData: state.users.testData, 46 | }; 47 | }; 48 | 49 | const mapDispatchToProps = dispatch => { 50 | return { 51 | testUser: ()=>dispatch(testUser), 52 | }; 53 | }; 54 | 55 | export default connect(mapStateToProps, mapDispatchToProps)(LinksScreen); -------------------------------------------------------------------------------- /store/reducers/usersReducer.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from '../actions/actionTypes' 2 | import jwt from 'expo-jwt'; 3 | import { AsyncStorage } from 'react-native'; 4 | import config from '../../global/config'; 5 | 6 | const validCredentials = async () => { 7 | const authorizationToken = await AsyncStorage.getItem('jwtToken'); 8 | if (authorizationToken === null) 9 | return false; 10 | try { 11 | jwt.decode(authorizationToken, config.secretKey); 12 | return true; 13 | } catch(err) { 14 | return false; 15 | } 16 | } 17 | 18 | const initialState = { 19 | isAuthenticated: validCredentials(), 20 | authenticatedUsername: validCredentials() === false ? '' : '', 21 | testData: 'old uset test data', 22 | }; 23 | 24 | const usersReducer = (state = initialState, action) => { 25 | switch (action.type) { 26 | case actionTypes.LOGIN_SUCCESSFUL: 27 | return { 28 | ...state, 29 | isAuthenticated: true, 30 | authenticatedUsername: action.authenticatedUsername, 31 | } 32 | case actionTypes.LOGOUT_USER: { 33 | return { 34 | ...state, 35 | isAuthenticated: false, 36 | authenticatedUsername: '' 37 | } 38 | } 39 | case actionTypes.USER_TEST: { 40 | return { 41 | ...state, 42 | testData: action.testData, 43 | } 44 | } 45 | default: 46 | return state; 47 | } 48 | }; 49 | 50 | export default usersReducer; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject", 9 | "test": "jest --watchAll" 10 | }, 11 | "jest": { 12 | "preset": "jest-expo" 13 | }, 14 | "dependencies": { 15 | "@expo/samples": "~36.0.0", 16 | "@expo/vector-icons": "~10.0.0", 17 | "@react-navigation/web": "~1.0.0-alpha.9", 18 | "expo": "~36.0.0", 19 | "expo-asset": "~8.0.0", 20 | "expo-constants": "~8.0.0", 21 | "expo-font": "~8.0.0", 22 | "expo-jwt": "^1.2.0", 23 | "expo-web-browser": "~8.0.0", 24 | "native-base": "^2.13.8", 25 | "react": "~16.9.0", 26 | "react-dom": "~16.9.0", 27 | "react-localization": "^1.0.15", 28 | "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.0.tar.gz", 29 | "react-native-easy-grid": "^0.2.2", 30 | "react-native-elements": "^1.2.7", 31 | "react-native-gesture-handler": "~1.5.0", 32 | "react-native-reanimated": "~1.4.0", 33 | "react-native-screens": "2.0.0-alpha.12", 34 | "react-native-web": "~0.11.7", 35 | "react-navigation": "~4.0.10", 36 | "react-navigation-stack": "~1.10.3", 37 | "react-navigation-tabs": "~2.6.2", 38 | "react-redux": "^7.1.3", 39 | "redux": "^4.0.5", 40 | "redux-thunk": "^2.3.0" 41 | }, 42 | "devDependencies": { 43 | "@babel/core": "^7.0.0", 44 | "babel-preset-expo": "~8.0.0", 45 | "jest-expo": "~36.0.1", 46 | "redux-devtools": "^3.5.0" 47 | }, 48 | "private": true 49 | } 50 | -------------------------------------------------------------------------------- /store/actions/userActions.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from './actionTypes' 2 | import jwt from 'expo-jwt'; 3 | import { AsyncStorage } from 'react-native'; 4 | import config from '../../global/config'; 5 | 6 | const options = data => { 7 | return { 8 | headers: { 9 | 'Content-Type': 'application/json' 10 | }, 11 | method: 'post', 12 | body: JSON.stringify(data) 13 | }; 14 | }; 15 | 16 | export const checkUserUniqueness = ({ field, value }) => { 17 | return dispatch => { 18 | return fetch(config.apiServer + '/api/users/validate', options({ field, value })) 19 | } 20 | } 21 | 22 | export const userSignupRequest = (userSignupDetails) => { 23 | return dispatch => { 24 | return fetch(config.apiServer + '/api/users/signup', options(userSignupDetails)) 25 | } 26 | } 27 | 28 | export const userLoginRequest = (userLoginDetails) => { 29 | return dispatch => { 30 | return fetch(config.apiServer + '/api/users/login', options(userLoginDetails)) 31 | .then(res => res.json()) 32 | .then(res => { 33 | if (res.success) { 34 | const token = res.token; 35 | delete res.token; 36 | AsyncStorage.setItem('jwtToken', token); 37 | dispatch({ type: actionTypes.LOGIN_SUCCESSFUL, authorizationToken: token, authenticatedUsername: jwt.decode(token, config.secretKey).username }); 38 | } 39 | return res; 40 | }) 41 | } 42 | } 43 | 44 | export const userLogoutRequest = () => { 45 | return dispatch => { 46 | AsyncStorage.removeItem('BasicMERNStackAppMyArticles'); 47 | AsyncStorage.removeItem('jwtToken'); 48 | dispatch({ type: actionTypes.LOGOUT_USER }); 49 | } 50 | } 51 | 52 | export const testUser = (testData) => { 53 | return dispatch =>{ 54 | dispatch({type: actionTypes.USER_TEST, testData: testData}); 55 | } 56 | } -------------------------------------------------------------------------------- /navigation/MainTabNavigator.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Platform } from 'react-native'; 3 | import { createStackNavigator } from 'react-navigation-stack'; 4 | import { createBottomTabNavigator } from 'react-navigation-tabs'; 5 | 6 | import TabBarIcon from '../components/TabBarIcon'; 7 | import HomeScreen from '../screens/HomeScreen'; 8 | import LinksScreen from '../screens/LinksScreen'; 9 | import SettingsScreen from '../screens/SettingsScreen'; 10 | 11 | const config = Platform.select({ 12 | web: { headerMode: 'screen' }, 13 | default: {}, 14 | }); 15 | 16 | const HomeStack = createStackNavigator( 17 | { 18 | Home: HomeScreen, 19 | }, 20 | config 21 | ); 22 | 23 | HomeStack.navigationOptions = { 24 | tabBarLabel: 'Home', 25 | tabBarIcon: ({ focused }) => ( 26 | 34 | ), 35 | }; 36 | 37 | HomeStack.path = ''; 38 | 39 | const LinksStack = createStackNavigator( 40 | { 41 | Links: LinksScreen, 42 | }, 43 | config 44 | ); 45 | 46 | LinksStack.navigationOptions = { 47 | tabBarLabel: 'Links', 48 | tabBarIcon: ({ focused }) => ( 49 | 50 | ), 51 | }; 52 | 53 | LinksStack.path = ''; 54 | 55 | const SettingsStack = createStackNavigator( 56 | { 57 | Settings: SettingsScreen, 58 | }, 59 | config 60 | ); 61 | 62 | SettingsStack.navigationOptions = { 63 | tabBarLabel: 'Settings', 64 | tabBarIcon: ({ focused }) => ( 65 | 66 | ), 67 | }; 68 | 69 | SettingsStack.path = ''; 70 | 71 | const tabNavigator = createBottomTabNavigator({ 72 | HomeStack, 73 | LinksStack, 74 | SettingsStack, 75 | }); 76 | 77 | tabNavigator.path = ''; 78 | 79 | export default tabNavigator; 80 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import { AppLoading } from 'expo'; 2 | import { Asset } from 'expo-asset'; 3 | import * as Font from 'expo-font'; 4 | import React, { Component } from 'react'; 5 | import { Platform, StatusBar, StyleSheet, View } from 'react-native'; 6 | import { Ionicons } from '@expo/vector-icons'; 7 | 8 | import AppNavigator from './navigation/AppNavigator'; 9 | 10 | export default class App extends Component { 11 | 12 | constructor(props) { 13 | super(props); 14 | console.log('hello'); 15 | this.state = { 16 | isLoadingComplete: false 17 | }; 18 | } 19 | 20 | componentDidMount = async()=>{ 21 | await Promise.all([ 22 | Asset.loadAsync([ 23 | require('./assets/images/robot-dev.png'), 24 | require('./assets/images/robot-prod.png'), 25 | ]), 26 | Font.loadAsync({ 27 | // We include SpaceMono because we use it in HomeScreen.js. Feel free to 28 | // remove this if you are not using it in your app 29 | 'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'), 30 | Roboto: require('native-base/Fonts/Roboto.ttf'), 31 | Roboto_medium: require('native-base/Fonts/Roboto_medium.ttf'), 32 | ...Ionicons.font, 33 | }) 34 | ]) 35 | 36 | this.setLoadingComplete(true); 37 | } 38 | 39 | setLoadingComplete = (isLoadingComplete)=>{ 40 | this.setState({isLoadingComplete}); 41 | } 42 | 43 | render(){ 44 | let { isLoadingComplete } = this.state; 45 | if (!isLoadingComplete && !this.props.skipLoadingScreen) { 46 | return ( 47 | this.setLoadingComplete(true)} 50 | /> 51 | ); 52 | } else { 53 | return ( 54 | 55 | {Platform.OS === 'ios' && } 56 | 57 | 58 | ); 59 | } 60 | } 61 | } 62 | 63 | function handleLoadingError(error) { 64 | // In this case, you might want to report the error to your error reporting 65 | // service, for example Sentry 66 | console.warn(error); 67 | } 68 | 69 | const styles = StyleSheet.create({ 70 | container: { 71 | flex: 1, 72 | backgroundColor: '#fff', 73 | }, 74 | }); 75 | -------------------------------------------------------------------------------- /store/actions/articleActions.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from './actionTypes'; 2 | import { AsyncStorage } from 'react-native'; 3 | 4 | const options = async (data) => { 5 | return { 6 | headers: { 7 | 'Authorization': 'Bearer ' + await AsyncStorage.getItem('jwtToken'), 8 | 'Content-Type': 'application/json', 9 | 'Accept': 'application/json' 10 | }, 11 | method: 'post', 12 | body: JSON.stringify(data) 13 | }; 14 | }; 15 | 16 | export const getAllArticles = () => { 17 | return dispatch => { 18 | fetch(config.apiServer + '/api/articles') 19 | .then(res => res.json()) 20 | .then(res => { 21 | AsyncStorage.setItem('BasicMERNStackAppAllArticles', JSON.stringify(res.articles)); 22 | dispatch({ type: actionTypes.GOT_ALL_ARTICLES, articles: res.articles }) 23 | }) 24 | }; 25 | }; 26 | 27 | export const getMyArticles = () => { 28 | return async dispatch => { 29 | fetch(config.apiServer + '/api/articles/myarticles', { 30 | headers: { 31 | 'Authorization': 'Bearer ' + await AsyncStorage.getItem(), 32 | 'Content-Type': 'application/json' 33 | }, 34 | method: 'GET' 35 | }) 36 | .then(res => res.json()) 37 | .then(res => { 38 | AsyncStorage.setItem('BasicMERNStackAppMyArticles', JSON.stringify(res.articles)); 39 | dispatch({ type: actionTypes.GOT_MY_ARTICLES, myArticles: res.articles }) 40 | }) 41 | }; 42 | }; 43 | 44 | export const getArticle = (articleId) => { 45 | return dispatch => { 46 | fetch(config.apiServer + '/api/articles/' + articleId) 47 | .then(res => res.json()) 48 | .then(res => { 49 | dispatch({ type: actionTypes.GOT_SINGLE_ARTICLE, article: res.article }) 50 | }) 51 | }; 52 | }; 53 | 54 | export const submitNewArticle = (articleData) => { 55 | return dispatch => { 56 | return fetch(config.apiServer + '/api/articles/add', options(articleData)) 57 | .then(res => res.json()) 58 | } 59 | }; 60 | 61 | export const saveArticle = (articleId, articleData) => { 62 | return dispatch => { 63 | return fetch(config.apiServer + '/api/articles/edit/' + articleId, options(articleData)) 64 | .then(res => res.json()) 65 | } 66 | } 67 | 68 | export const deleteArticle = (articleId) => { 69 | return async dispatch => { 70 | return fetch(config.apiServer + '/api/articles/delete/' + articleId, { 71 | headers: { 72 | 'Authorization': 'Bearer ' + await AsyncStorage.getItem('jwtToken'), 73 | 'Content-Type': 'application/json' 74 | }, 75 | method: 'delete' 76 | }) 77 | .then(res => res.json()) 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /screens/HomeScreen.js: -------------------------------------------------------------------------------- 1 | import * as WebBrowser from 'expo-web-browser'; 2 | import React, { Component } from 'react'; 3 | import { 4 | Image, 5 | Platform, 6 | ScrollView, 7 | StyleSheet, 8 | Text, 9 | TouchableOpacity, 10 | View, 11 | } from 'react-native'; 12 | 13 | import { MonoText } from '../components/StyledText'; 14 | import { connect } from 'react-redux'; 15 | import { testUser } from '../store/actions/userActions'; 16 | import strings from '../languages'; 17 | 18 | class HomeScreen extends Component { 19 | 20 | static navigationOptions = { 21 | header: null, 22 | }; 23 | 24 | render(){ 25 | return ( 26 | 27 | 30 | 31 | 39 | 40 | 41 | 42 | 43 | 44 | Get started by opening 45 | 46 | 48 | screens/HomeScreen.js 49 | 50 | 51 | 52 | 53 | This part is test for multilingual 54 | {strings.hello} 55 | 56 | {strings.setLanguage(strings.getLanguage()==='en'?'cn':'en'); this.setState({})}}> 57 | Change Language 58 | 59 | 60 | 61 | this.props.testUser("New Test Data")} style={styles.helpLink}> 62 | 63 | Set Test Data for Redux. 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | This is a tab bar. You can edit it in: 72 | 73 | 74 | 76 | 77 | navigation/MainTabNavigator.js 78 | 79 | 80 | 81 | 82 | ); 83 | } 84 | 85 | } 86 | 87 | function DevelopmentModeNotice() { 88 | if (__DEV__) { 89 | const learnMoreButton = ( 90 | 91 | Learn more 92 | 93 | ); 94 | 95 | return ( 96 | 97 | Development mode is enabled: your app will be slower but you can use 98 | useful development tools. {learnMoreButton} 99 | 100 | ); 101 | } else { 102 | return ( 103 | 104 | You are not in development mode: your app will run at full speed. 105 | 106 | ); 107 | } 108 | } 109 | 110 | function handleLearnMorePress() { 111 | WebBrowser.openBrowserAsync( 112 | 'https://docs.expo.io/versions/latest/workflow/development-mode/' 113 | ); 114 | } 115 | 116 | function handleHelpPress() { 117 | WebBrowser.openBrowserAsync( 118 | 'https://docs.expo.io/versions/latest/workflow/up-and-running/#cant-see-your-changes' 119 | ); 120 | } 121 | 122 | const styles = StyleSheet.create({ 123 | container: { 124 | flex: 1, 125 | backgroundColor: '#fff', 126 | }, 127 | developmentModeText: { 128 | marginBottom: 20, 129 | color: 'rgba(0,0,0,0.4)', 130 | fontSize: 14, 131 | lineHeight: 19, 132 | textAlign: 'center', 133 | }, 134 | contentContainer: { 135 | paddingTop: 30, 136 | }, 137 | welcomeContainer: { 138 | alignItems: 'center', 139 | marginTop: 10, 140 | marginBottom: 20, 141 | }, 142 | welcomeImage: { 143 | width: 100, 144 | height: 80, 145 | resizeMode: 'contain', 146 | marginTop: 3, 147 | marginLeft: -10, 148 | }, 149 | getStartedContainer: { 150 | alignItems: 'center', 151 | marginHorizontal: 50, 152 | }, 153 | homeScreenFilename: { 154 | marginVertical: 7, 155 | }, 156 | codeHighlightText: { 157 | color: 'rgba(96,100,109, 0.8)', 158 | }, 159 | codeHighlightContainer: { 160 | backgroundColor: 'rgba(0,0,0,0.05)', 161 | borderRadius: 3, 162 | paddingHorizontal: 4, 163 | }, 164 | getStartedText: { 165 | fontSize: 17, 166 | color: 'rgba(96,100,109, 1)', 167 | lineHeight: 24, 168 | textAlign: 'center', 169 | }, 170 | tabBarInfoContainer: { 171 | position: 'absolute', 172 | bottom: 0, 173 | left: 0, 174 | right: 0, 175 | ...Platform.select({ 176 | ios: { 177 | shadowColor: 'black', 178 | shadowOffset: { width: 0, height: -3 }, 179 | shadowOpacity: 0.1, 180 | shadowRadius: 3, 181 | }, 182 | android: { 183 | elevation: 20, 184 | }, 185 | }), 186 | alignItems: 'center', 187 | backgroundColor: '#fbfbfb', 188 | paddingVertical: 20, 189 | }, 190 | tabBarInfoText: { 191 | fontSize: 17, 192 | color: 'rgba(96,100,109, 1)', 193 | textAlign: 'center', 194 | }, 195 | navigationFilename: { 196 | marginTop: 5, 197 | }, 198 | helpContainer: { 199 | marginTop: 15, 200 | alignItems: 'center', 201 | }, 202 | helpLink: { 203 | paddingVertical: 15, 204 | }, 205 | helpLinkText: { 206 | fontSize: 14, 207 | color: '#2e78b7', 208 | }, 209 | }); 210 | 211 | const mapStateToProps = state => { 212 | return { 213 | testData: state.users.testData, 214 | }; 215 | }; 216 | 217 | const mapDispatchToProps = dispatch => { 218 | return { 219 | testUser: (val) => dispatch(testUser(val)), 220 | }; 221 | }; 222 | 223 | export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen); --------------------------------------------------------------------------------