├── 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);
--------------------------------------------------------------------------------