├── .eslintignore ├── .eslintrc.js ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── _babelrc ├── _buckconfig ├── _flowconfig ├── _gitignore ├── _watchmanconfig ├── app.json ├── build-conf.json ├── dependencies.json ├── devDependencies.json ├── index.js ├── jest-setup.js ├── package.json ├── scripts └── init ├── src ├── App.js ├── components │ └── .gitkeep ├── navigators │ ├── AppNavigator.js │ ├── BrowserAppContainer.js │ ├── NavigationService.js │ ├── routeConfigMap.js │ └── stackConfig.js ├── resources │ ├── images │ │ └── index.js │ ├── locales │ │ ├── en.js │ │ ├── index.js │ │ ├── zh-Hans.js │ │ └── zh-Hant.js │ └── themes │ │ └── customize │ │ └── index.js ├── screens │ ├── Example │ │ ├── Example.js │ │ └── index.js │ ├── Home │ │ ├── Home.js │ │ └── index.js │ └── Initialize │ │ ├── Initialize.js │ │ └── index.js ├── services │ ├── ApiClient │ │ ├── ApiClient.js │ │ ├── http │ │ │ └── HttpClient.js │ │ └── index.js │ └── index.js └── utilities │ ├── Constants.js │ └── I18n.js ├── web ├── config │ ├── env.js │ ├── jest │ │ ├── cssTransform.js │ │ └── fileTransform.js │ ├── paths.js │ ├── polyfills.js │ ├── webpack.config.dev.js │ ├── webpack.config.prod.js │ └── webpackDevServer.config.js ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── scripts │ ├── build.js │ └── start.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | /web/** 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "jest": true, 6 | "browser": true, 7 | }, 8 | "rules": { 9 | "no-alert": 0, 10 | "class-methods-use-this": 0, 11 | "react/prop-types": [2, { ignore: ["navigation"], customValidators: [] }], 12 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }] 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "workbench.iconTheme": "vscode-icons", 4 | "editor.renderWhitespace": "all", 5 | "files.trimTrailingWhitespace": true 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 JianyingLi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-template-ui-based 2 | 3 | [![npm version][npm-image]][npm-url] 4 | [![npm][npm-dm-image]][npm-dm-url] 5 | [![Join the chat at https://t.me/reactnativesz][telegram-image]][telegram-url] 6 | 7 | [npm-image]: https://img.shields.io/npm/v/react-native-template-ui-based.svg 8 | [npm-url]: https://www.npmjs.com/package/react-native-template-ui-based 9 | [npm-dm-image]: https://img.shields.io/npm/dm/react-native-template-ui-based.svg 10 | [npm-dm-url]: https://www.npmjs.com/package/react-native-template-ui-based 11 | [telegram-image]: https://img.shields.io/badge/chat-on%20telegram-blue.svg 12 | [telegram-url]: https://t.me/reactnativesz 13 | 14 | Table of Contents 15 | ================= 16 | 17 | * [Introduction](#introduction) 18 | * [Features](#features) 19 | * [Quick Start](#quick-start) 20 | * [Prerequisites](#prerequisites) 21 | * [Installation](#installation) 22 | * [Run the new app](#run-the-new-app) 23 | * [Discussion](#discussion) 24 | * [License](#license) 25 | 26 | Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) 27 | 28 | ## Introduction 29 | 30 | *A React Native cross-platform app template for use with [@blankapp/ui](https://github.com/blankapp/ui).* 31 | 32 | ### Features 33 | 34 | - Multi-language support. 35 | - ... 36 | 37 | ## Quick Start 38 | 39 | ### Prerequisites 40 | 41 | Before starting make sure you have: 42 | 43 | - Installed [Yarn](https://yarnpkg.com/) 44 | - Installed [React Native](https://facebook.github.io/react-native/) 45 | 46 | ### Usage 47 | 48 | Create a new React Native App: 49 | 50 | ```bash 51 | $ react-native init MyApp --template ui-based 52 | $ cd MyApp && ./scripts/init 53 | ``` 54 | 55 | ### Run the new app 56 | 57 | - Running on Android 58 | 59 | ```bash 60 | $ react-native run-android 61 | $ adb reverse tcp:8081 tcp:8081 # required to ensure the Android app can 62 | ``` 63 | 64 | - Running on iOS 65 | 66 | ```bash 67 | $ react-native run-ios 68 | ``` 69 | 70 | - Running on Web 71 | 72 | ```bash 73 | $ yarn run-web 74 | ``` 75 | 76 | Or 77 | 78 | ```bash 79 | $ node web/scripts/start.js 80 | ``` 81 | 82 | ## Discussion 83 | 84 | If you have any suggestions or questions about this project, you can discuss it by [Telegram][telegram-url] or my private wechat. 85 | 86 | ![](http://blankapp.org/assets/images/wechat_qrcode.png) 87 | 88 | ## License 89 | 90 | ``` 91 | MIT License 92 | 93 | Copyright (c) 2017 JianyingLi 94 | 95 | Permission is hereby granted, free of charge, to any person obtaining a copy 96 | of this software and associated documentation files (the "Software"), to deal 97 | in the Software without restriction, including without limitation the rights 98 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 99 | copies of the Software, and to permit persons to whom the Software is 100 | furnished to do so, subject to the following conditions: 101 | 102 | The above copyright notice and this permission notice shall be included in all 103 | copies or substantial portions of the Software. 104 | 105 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 106 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 107 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 108 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 109 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 110 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 111 | SOFTWARE. 112 | ``` 113 | -------------------------------------------------------------------------------- /_babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react-native" 4 | ], 5 | "plugins": [ 6 | [ 7 | "transform-runtime", 8 | { 9 | "helpers": false, 10 | "polyfill": false, 11 | "regenerator": true, 12 | "moduleName": "babel-runtime" 13 | } 14 | ] 15 | ] 16 | } -------------------------------------------------------------------------------- /_buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 -------------------------------------------------------------------------------- /_flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | 16 | ; Ignore polyfills 17 | .*/Libraries/polyfills/.* 18 | 19 | [include] 20 | 21 | [libs] 22 | node_modules/react-native/Libraries/react-native/react-native-interface.js 23 | node_modules/react-native/flow/ 24 | 25 | [options] 26 | emoji=true 27 | 28 | module.system=haste 29 | 30 | munge_underscores=true 31 | 32 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 33 | 34 | suppress_type=$FlowIssue 35 | suppress_type=$FlowFixMe 36 | suppress_type=$FlowFixMeProps 37 | suppress_type=$FlowFixMeState 38 | suppress_type=$FixMe 39 | 40 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 41 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 42 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 43 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 44 | 45 | unsafe.enable_getters_and_setters=true 46 | 47 | [version] 48 | ^0.53.0 -------------------------------------------------------------------------------- /_gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots -------------------------------------------------------------------------------- /_watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HelloWorld", 3 | "displayName": "HelloWorld" 4 | } -------------------------------------------------------------------------------- /build-conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "versionCode": 1 3 | } -------------------------------------------------------------------------------- /dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "@blankapp/ui": "git+https://github.com/blankapp/ui.git#develop", 3 | "@blankapp/ui-pro": "git+https://github.com/blankapp/ui-pro.git#develop", 4 | "prop-types": "^15.6.1", 5 | "react": "16.4.0", 6 | "react-art": "^16.4.0", 7 | "react-dom": "^16.4.0", 8 | "react-native": "^0.55.4", 9 | "react-native-web": "^0.7.3", 10 | "react-navigation": "^2.0.4" 11 | } -------------------------------------------------------------------------------- /devDependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoprefixer": "^7.2.6", 3 | "babel-core": "^6.26.0", 4 | "babel-eslint": "^8.2.1", 5 | "babel-jest": "22.2.2", 6 | "babel-loader": "^7.1.2", 7 | "babel-plugin-react-native-web": "^0.6.1", 8 | "babel-plugin-transform-react-remove-prop-types": "^0.4.13", 9 | "babel-plugin-transform-runtime": "^6.23.0", 10 | "babel-preset-react-native": "4.0.0", 11 | "babel-runtime": "^6.26.0", 12 | "case-sensitive-paths-webpack-plugin": "^2.1.1", 13 | "chalk": "^2.3.0", 14 | "css-loader": "^0.28.9", 15 | "dotenv": "^5.0.0", 16 | "dotenv-expand": "4.2.0", 17 | "eslint": "^4.17.0", 18 | "eslint-config-airbnb": "^16.1.0", 19 | "eslint-loader": "^1.9.0", 20 | "eslint-plugin-flowtype": "^2.43.0", 21 | "eslint-plugin-import": "^2.8.0", 22 | "eslint-plugin-jsx-a11y": "^6.0.3", 23 | "eslint-plugin-react": "^7.6.1", 24 | "extract-text-webpack-plugin": "^3.0.2", 25 | "file-loader": "^1.1.6", 26 | "fs-extra": "^5.0.0", 27 | "html-webpack-plugin": "^2.30.1", 28 | "jest": "22.2.2", 29 | "object-assign": "^4.1.1", 30 | "postcss-flexbugs-fixes": "^3.3.0", 31 | "postcss-loader": "^2.1.0", 32 | "pre-commit": "^1.2.2", 33 | "promise": "^8.0.1", 34 | "raf": "^3.4.0", 35 | "react-dev-utils": "^5.0.0", 36 | "react-test-renderer": "16.2.0", 37 | "style-loader": "^0.20.1", 38 | "sw-precache-webpack-plugin": "^0.11.4", 39 | "url-loader": "^0.6.2", 40 | "webpack": "^3.10.0", 41 | "webpack-dev-server": "^2.11.1", 42 | "webpack-manifest-plugin": "^1.3.2", 43 | "whatwg-fetch": "^2.0.3" 44 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry, Platform } from 'react-native'; 2 | import iconFont from 'react-native-vector-icons/Fonts/MaterialIcons.ttf'; 3 | import App from './src/App'; 4 | 5 | AppRegistry.registerComponent('HelloWorld', () => App); 6 | if (Platform.OS === 'web') { 7 | const iconFontStyles = `@font-face { 8 | src: url(${iconFont}); 9 | font-family: 'Material Icons'; 10 | }`; 11 | 12 | // Create stylesheet 13 | const style = document.createElement('style'); 14 | style.type = 'text/css'; 15 | if (style.styleSheet) { 16 | style.styleSheet.cssText = iconFontStyles; 17 | } else { 18 | style.appendChild(document.createTextNode(iconFontStyles)); 19 | } 20 | 21 | // Inject stylesheet 22 | document.head.appendChild(style); 23 | AppRegistry.runApplication('HelloWorld', { 24 | rootTag: document.getElementById('root'), 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /jest-setup.js: -------------------------------------------------------------------------------- 1 | // See https://github.com/facebook/jest/issues/2208 2 | jest.mock('Linking', () => ({ 3 | addEventListener: jest.fn(), 4 | removeEventListener: jest.fn(), 5 | openURL: jest.fn(), 6 | canOpenURL: jest.fn(), 7 | getInitialURL: jest.fn().mockImplementation(value => Promise.resolve(value)), 8 | })); 9 | 10 | // See https://github.com/facebook/react-native/issues/11659 11 | jest.mock('ScrollView', () => { 12 | const RealComponent = require.requireActual('ScrollView'); 13 | class ScrollView extends RealComponent { 14 | scrollTo = () => {} 15 | } 16 | return ScrollView; 17 | }); 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-template-ui-based", 3 | "version": "1.0.20", 4 | "main": "index.js", 5 | "repository": "git@github.com:blankapp/react-native-template-ui-based.git", 6 | "author": "JianyingLi ", 7 | "license": "MIT" 8 | } 9 | -------------------------------------------------------------------------------- /scripts/init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable */ 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const { execSync } = require('child_process'); 8 | 9 | const exec = function() { 10 | const appPath = path.join(__dirname, '..'); 11 | 12 | // Install development dependencies 13 | const devDependencies = JSON.parse(fs.readFileSync(path.join(appPath, 'devDependencies.json'), 'utf8')); 14 | 15 | // Setup the script rules 16 | let appPackage = JSON.parse(fs.readFileSync(path.join(appPath, 'package.json'))); 17 | appPackage.devDependencies = Object.assign(appPackage.devDependencies, devDependencies); 18 | appPackage.scripts = Object.assign(appPackage.scripts, { 19 | "run-android": "node_modules/.bin/react-native run-android", 20 | "run-ios": "node_modules/.bin/react-native run-ios", 21 | "run-web": "node web/scripts/start.js", 22 | "clean": "watchman watch-del-all && rm -rf $TMPDIR/react-* && rm -rf node_modules/ && yarn install", 23 | "lint": "node_modules/.bin/eslint src/**", 24 | "test": "node_modules/.bin/jest", 25 | }); 26 | 27 | fs.writeFileSync( 28 | path.join(appPath, 'package.json'), 29 | JSON.stringify(appPackage, null, 2) 30 | ); 31 | 32 | execSync(`yarn`, { stdio: 'inherit' }); 33 | execSync(`rm ${path.join(appPath, 'devDependencies.json')}`); 34 | execSync(`rm ${path.join(appPath, 'App.js')}`); 35 | } 36 | 37 | exec(); 38 | /* eslint-enable */ 39 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Platform, StatusBar } from 'react-native'; 3 | import Theme, { ThemeProvider } from '@blankapp/ui'; 4 | import defaultTheme from '@blankapp/ui/src/resources/themes/default'; 5 | import defaultThemePro from '@blankapp/ui-pro/src/resources/themes/default'; 6 | import AppNavigator from './navigators/AppNavigator'; 7 | import NavigationService from './navigators/NavigationService'; 8 | 9 | Theme.registerTheme('default', [ 10 | defaultTheme, 11 | defaultThemePro, 12 | ]); 13 | 14 | class App extends Component { 15 | constructor(props) { 16 | super(props); 17 | if (Platform.OS === 'android') { 18 | StatusBar.setTranslucent(true); 19 | StatusBar.setBackgroundColor('rgba(0, 0, 0, 0.2)'); 20 | } 21 | StatusBar.setBarStyle('dark-content'); 22 | } 23 | 24 | render() { 25 | return ( 26 | 27 | { 29 | this.topLevelNavigator = navigatorRef; 30 | NavigationService.setTopLevelNavigator(navigatorRef); 31 | }} 32 | /> 33 | 34 | ); 35 | } 36 | } 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /src/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijy91-archives-repos/react-native-template-ui-based/164f3520fe48e66e6d78510b0e6f864ad8efc330/src/components/.gitkeep -------------------------------------------------------------------------------- /src/navigators/AppNavigator.js: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native'; 2 | import { createStackNavigator } from 'react-navigation'; 3 | import BrowserAppContainer from './BrowserAppContainer'; 4 | 5 | import routeConfigMap from './routeConfigMap'; 6 | import stackConfig from './stackConfig'; 7 | 8 | const DefaultNavigator = createStackNavigator(routeConfigMap, stackConfig); 9 | // eslint-disable-next-line 10 | const AppNavigator = Platform.OS === 'web' ? BrowserAppContainer(DefaultNavigator) : DefaultNavigator; 11 | 12 | export default DefaultNavigator; 13 | 14 | -------------------------------------------------------------------------------- /src/navigators/BrowserAppContainer.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { NavigationActions, StackActions } from 'react-navigation'; 4 | 5 | const getAction = (router, path, params) => { 6 | const action = router.getActionForPathAndParams(path, params); 7 | if (action) { 8 | return action; 9 | } 10 | return StackActions.reset({ 11 | index: 0, 12 | actions: [NavigationActions.navigate({ routeName: 'Initialize' })], 13 | }); 14 | }; 15 | 16 | const addListener = () => {}; 17 | 18 | export default (NavigationAwareView) => { 19 | const initialAction = getAction( 20 | NavigationAwareView.router, 21 | window.location.pathname.substr(1), 22 | ); 23 | const initialState = NavigationAwareView.router.getStateForAction(initialAction); 24 | 25 | class NavigationContainer extends PureComponent { 26 | static childContextTypes = { 27 | getActionForPathAndParams: PropTypes.func.isRequired, 28 | getURIForAction: PropTypes.func.isRequired, 29 | dispatch: PropTypes.func.isRequired, 30 | }; 31 | constructor(props) { 32 | super(props); 33 | 34 | this.state = initialState; 35 | } 36 | getChildContext() { 37 | return { 38 | getActionForPathAndParams: this.getActionForPathAndParams, 39 | getURIForAction: this.getURIForAction, 40 | dispatch: this.dispatch, 41 | }; 42 | } 43 | componentDidMount() { 44 | const navigation = { 45 | state: this.state.routes[this.state.index], 46 | dispatch: this.dispatch, 47 | addListener, 48 | }; 49 | const screenOptions = NavigationAwareView.router.getScreenOptions(navigation); 50 | document.title = screenOptions.title; 51 | window.onpopstate = (e) => { 52 | e.preventDefault(); 53 | const action = getAction( 54 | NavigationAwareView.router, 55 | window.location.pathname.substr(1), 56 | ); 57 | if (action) this.dispatch(action); 58 | }; 59 | } 60 | componentWillUpdate(props, state) { 61 | const { 62 | path, 63 | params, 64 | } = NavigationAwareView.router.getPathAndParamsForState(state); 65 | const maybeHash = params && params.hash ? `#${params.hash}` : ''; 66 | const uri = `/${path}${maybeHash}`; 67 | if (window.location.pathname !== uri) { 68 | window.history.pushState({}, state.title, uri); 69 | } 70 | const navigation = { 71 | state: state.routes[state.index], 72 | dispatch: this.dispatch, 73 | addListener, 74 | }; 75 | const screenOptions = NavigationAwareView.router.getScreenOptions(navigation); 76 | document.title = screenOptions.title; 77 | } 78 | componentDidUpdate() { 79 | const { 80 | params, 81 | } = NavigationAwareView.router.getPathAndParamsForState(this.state); 82 | if (params && params.hash) { 83 | document.getElementById(params.hash).scrollIntoView(); 84 | } 85 | } 86 | getURIForAction(action) { 87 | const state = 88 | NavigationAwareView.router.getStateForAction(action, this.state) || 89 | this.state; 90 | const { 91 | path, 92 | } = NavigationAwareView.router.getPathAndParamsForState(state); 93 | return `/${path}`; 94 | } 95 | getActionForPathAndParams(path, params) { 96 | return NavigationAwareView.router.getActionForPathAndParams(path, params); 97 | } 98 | dispatch = (action) => { 99 | const state = NavigationAwareView.router.getStateForAction( 100 | action, 101 | this.state, 102 | ); 103 | 104 | if (!state) { 105 | return true; 106 | } 107 | 108 | if (state !== this.state) { 109 | this.setState(state); 110 | return true; 111 | } 112 | return false; 113 | }; 114 | render() { 115 | return ( 116 | 123 | ); 124 | } 125 | } 126 | return NavigationContainer; 127 | }; 128 | -------------------------------------------------------------------------------- /src/navigators/NavigationService.js: -------------------------------------------------------------------------------- 1 | import { NavigationActions } from 'react-navigation'; 2 | 3 | let topLevelNavigator; 4 | 5 | const setTopLevelNavigator = (navigatorRef) => { 6 | topLevelNavigator = navigatorRef; 7 | }; 8 | 9 | const navigate = (routeName, params) => { 10 | const options = NavigationActions.navigate({ 11 | type: NavigationActions.NAVIGATE, 12 | routeName, 13 | params, 14 | }); 15 | topLevelNavigator.dispatch(options); 16 | }; 17 | 18 | export default { 19 | setTopLevelNavigator, 20 | navigate, 21 | }; 22 | -------------------------------------------------------------------------------- /src/navigators/routeConfigMap.js: -------------------------------------------------------------------------------- 1 | // 请按名称排序放置 2 | import Example from '../screens/Example'; 3 | import Home from '../screens/Home'; 4 | import Initialize from '../screens/Initialize'; 5 | 6 | const routeConfigMap = { 7 | Example: { 8 | screen: Example, 9 | }, 10 | Home: { 11 | screen: Home, 12 | }, 13 | Initialize: { 14 | screen: Initialize, 15 | }, 16 | }; 17 | 18 | export default routeConfigMap; 19 | -------------------------------------------------------------------------------- /src/navigators/stackConfig.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { AppBar } from '@blankapp/ui-pro'; 3 | 4 | const stackConfig = { 5 | initialRouteName: 'Initialize', 6 | headerMode: 'screen', 7 | navigationOptions: { 8 | header: props => , 9 | }, 10 | }; 11 | 12 | export default stackConfig; 13 | -------------------------------------------------------------------------------- /src/resources/images/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | }; 3 | -------------------------------------------------------------------------------- /src/resources/locales/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | appName: 'HelloWorld', 3 | globals: { 4 | }, 5 | components: { 6 | dialog: { 7 | ok: 'Ok', 8 | cancel: 'Cancel', 9 | yes: 'Yes', 10 | no: 'No', 11 | }, 12 | }, 13 | screens: { 14 | example: { 15 | title: 'Example', 16 | }, 17 | initialize: { 18 | title: 'Initialize', 19 | }, 20 | home: { 21 | title: 'Home', 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/resources/locales/index.js: -------------------------------------------------------------------------------- 1 | import en from './en'; 2 | import zhHans from './zh-Hans'; 3 | import zhHant from './zh-Hant'; 4 | 5 | const locales = { 6 | en, 7 | 'en-US': en, 8 | 'en-CN': en, 9 | 'zh-CN': zhHans, 10 | 'zh-HK': zhHant, 11 | 'zh-TW': zhHant, 12 | 'zh-Hans': zhHans, // 简体中文 13 | 'zh-Hans-CN': zhHans, // 大陆地区使用的简体中文 14 | 'zh-Hans-HK': zhHans, // 香港地区使用的简体中文 15 | 'zh-Hans-MO': zhHans, // 澳门使用的简体中文 16 | 'zh-Hans-SG': zhHans, // 新加坡使用的简体中文 17 | 'zh-Hans-TW': zhHans, // 台湾使用的简体中文 18 | 'zh-Hant': zhHant, // 繁体中文 19 | 'zh-Hant-CN': zhHant, // 大陆地区使用的繁体中文 20 | 'zh-Hant-HK': zhHant, // 香港地区使用的繁体中文 21 | 'zh-Hant-MO': zhHant, // 澳门使用的繁体中文 22 | 'zh-Hant-SG': zhHant, // 新加坡使用的繁体中文 23 | 'zh-Hant-TW': zhHant, // 台湾使用的繁体中文 24 | }; 25 | 26 | export default locales; 27 | -------------------------------------------------------------------------------- /src/resources/locales/zh-Hans.js: -------------------------------------------------------------------------------- 1 | export default { 2 | appName: 'HelloWorld', 3 | globals: { 4 | }, 5 | components: { 6 | dialog: { 7 | ok: '确定', 8 | cancel: '取消', 9 | yes: '是', 10 | no: '否', 11 | }, 12 | }, 13 | screens: { 14 | example: { 15 | title: '示例', 16 | }, 17 | initialize: { 18 | title: '初始化', 19 | }, 20 | home: { 21 | title: '首页', 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/resources/locales/zh-Hant.js: -------------------------------------------------------------------------------- 1 | export default { 2 | appName: 'HelloWorld', 3 | globals: { 4 | }, 5 | components: { 6 | dialog: { 7 | ok: '确定', 8 | cancel: '取消', 9 | yes: '是', 10 | no: '否', 11 | }, 12 | }, 13 | screens: { 14 | example: { 15 | title: '示例', 16 | }, 17 | initialize: { 18 | title: '初始化', 19 | }, 20 | home: { 21 | title: '首頁', 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/resources/themes/customize/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | }; 3 | -------------------------------------------------------------------------------- /src/screens/Example/Example.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Screen, Text } from '@blankapp/ui'; 3 | import { t } from '../../utilities/I18n'; 4 | 5 | class Example extends Component { 6 | // eslint-disable-next-line 7 | static navigationOptions = ({ navigation, screenProps }) => { 8 | return { 9 | title: t('screens.example.title'), 10 | }; 11 | }; 12 | 13 | constructor(props) { 14 | super(props); 15 | this.navigation = this.props.navigation; 16 | this.navigationParams = this.navigation.state.params; 17 | } 18 | 19 | render() { 20 | return ( 21 | 22 | Example 23 | 24 | ); 25 | } 26 | } 27 | 28 | export default Example; 29 | -------------------------------------------------------------------------------- /src/screens/Example/index.js: -------------------------------------------------------------------------------- 1 | import Example from './Example'; 2 | 3 | export default Example; 4 | -------------------------------------------------------------------------------- /src/screens/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Platform } from 'react-native'; 3 | import { Screen, Title, Text } from '@blankapp/ui'; 4 | 5 | class Home extends Component { 6 | static navigationOptions = { 7 | title: 'Home', 8 | }; 9 | 10 | constructor(props) { 11 | super(props); 12 | this.navigation = this.props.navigation; 13 | } 14 | 15 | render() { 16 | const instructions = Platform.select({ 17 | ios: 'Press Cmd+R to reload,\n' + 18 | 'Cmd+D or shake for dev menu', 19 | android: 'Double tap R on your keyboard to reload,\n' + 20 | 'Shake or press menu button for dev menu', 21 | }); 22 | return ( 23 | 30 | 31 | Welcome to React Native! 32 | 33 | 34 | To get started, edit App.js 35 | 36 | 41 | {instructions} 42 | 43 | 44 | ); 45 | } 46 | } 47 | 48 | export default Home; 49 | -------------------------------------------------------------------------------- /src/screens/Home/index.js: -------------------------------------------------------------------------------- 1 | import Home from './Home'; 2 | 3 | export default Home; 4 | -------------------------------------------------------------------------------- /src/screens/Initialize/Initialize.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { ActivityIndicator, Screen } from '@blankapp/ui'; 3 | import { NavigationActions, StackActions } from 'react-navigation'; 4 | import { t } from '../../utilities/I18n'; 5 | 6 | class Initialize extends Component { 7 | // eslint-disable-next-line 8 | static navigationOptions = ({ navigation, screenProps }) => { 9 | return { 10 | title: t('screens.initialize.title'), 11 | header: null, 12 | }; 13 | }; 14 | 15 | constructor(props) { 16 | super(props); 17 | this.navigation = this.props.navigation; 18 | } 19 | 20 | componentDidMount() { 21 | setTimeout(() => { 22 | const routeName = 'Home'; 23 | 24 | this.navigation.dispatch(StackActions.reset({ 25 | index: 0, 26 | actions: [NavigationActions.navigate({ routeName })], 27 | })); 28 | this.navigation.dispatch(NavigationActions.navigate({ routeName })); 29 | }, 300); 30 | } 31 | 32 | render() { 33 | return ( 34 | 41 | 42 | 43 | ); 44 | } 45 | } 46 | 47 | export default Initialize; 48 | -------------------------------------------------------------------------------- /src/screens/Initialize/index.js: -------------------------------------------------------------------------------- 1 | import Initialize from './Initialize'; 2 | 3 | export default Initialize; 4 | -------------------------------------------------------------------------------- /src/services/ApiClient/ApiClient.js: -------------------------------------------------------------------------------- 1 | import HttpClient from './http/HttpClient'; 2 | import configuredStore from '../../redux/store'; 3 | import { DeviceEventEmitter, DeviceInfo } from '../../modules'; 4 | 5 | class ApiClient { 6 | constructor(defaults = { 7 | baseURL: 'https://127.0.0.1:8000', 8 | }) { 9 | this.http = new HttpClient(defaults); 10 | 11 | this.http.interceptors.request.push((url, options) => { 12 | const { auth } = configuredStore.store.getState(); 13 | const nextOptions = options; 14 | 15 | if (auth && auth.isLoggedIn) { 16 | const { access_token: accessToken } = auth.jwtToken; 17 | nextOptions.headers.Authorization = `Bearer ${accessToken}`; 18 | } 19 | nextOptions.headers['X-USER-DEVICE-LANGUAGE'] = DeviceInfo.getDeviceLocale(); 20 | nextOptions.headers['X-USER-DEVICE-TIMEZONE'] = DeviceInfo.getTimezone(); 21 | nextOptions.headers['X-USER-APP-BUILD'] = DeviceInfo.getBuildNumber(); 22 | nextOptions.headers['X-USER-APP-VERSION'] = DeviceInfo.getVersion(); 23 | 24 | return { url, options: nextOptions }; 25 | }); 26 | this.http.interceptors.response.push((response) => { 27 | const { auth } = configuredStore.store.getState(); 28 | const { status } = response; 29 | 30 | if (auth && auth.isLoggedIn && status === 401) { 31 | DeviceEventEmitter.emit('tokenExpiredEvent'); 32 | } 33 | return response; 34 | }); 35 | } 36 | 37 | sendRequest(url, options) { 38 | return this.http.sendRequest(url, options); 39 | } 40 | } 41 | 42 | export default ApiClient; 43 | -------------------------------------------------------------------------------- /src/services/ApiClient/http/HttpClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Very simple http client 3 | * @exports services/ApiClient/http/HttpClient 4 | * @class 5 | */ 6 | class HttpClient { 7 | /** 8 | * Create a new instance of HttpClient. 9 | * 10 | * @example 11 | * // ... 12 | * @param {Object} defaults - The default config for the instance. 13 | * @param {string} defaults.baseURL - Coming soon. 14 | */ 15 | constructor(defaults) { 16 | this.defaults = defaults; 17 | this.interceptors = { 18 | request: [], 19 | response: [], 20 | }; 21 | } 22 | 23 | /** 24 | * Sends a single request to server. 25 | * 26 | * @example 27 | * // ... 28 | * @param {string} url - Coming soon. 29 | * @param {Object} options - Coming soon. 30 | */ 31 | async sendRequest(url, options = {}) { 32 | let requestUrl = `${this.defaults.baseURL}${url}`; 33 | let requestOptions = options; 34 | 35 | if (!requestOptions.headers) { 36 | requestOptions.headers = {}; 37 | } 38 | 39 | requestOptions.headers = Object.assign( 40 | { 41 | Accept: 'application/json', 42 | 'Content-Type': 'application/json', 43 | }, 44 | requestOptions.headers, 45 | ); 46 | 47 | this.interceptors.request.forEach((interceptor) => { 48 | const request = interceptor(requestUrl, requestOptions); 49 | requestUrl = request.url; 50 | requestOptions = request.options; 51 | }); 52 | 53 | let response = await fetch(requestUrl, requestOptions); 54 | this.interceptors.response.forEach((interceptor) => { 55 | response = interceptor(response); 56 | }); 57 | 58 | if (!response.ok) { 59 | throw new Error(`${response.status} - ${response.statusText}`); 60 | } 61 | 62 | const responseJson = await response.json(); 63 | return responseJson; 64 | } 65 | } 66 | 67 | export default HttpClient; 68 | -------------------------------------------------------------------------------- /src/services/ApiClient/index.js: -------------------------------------------------------------------------------- 1 | import ApiClient from './ApiClient'; 2 | 3 | export default ApiClient; 4 | -------------------------------------------------------------------------------- /src/services/index.js: -------------------------------------------------------------------------------- 1 | // 请按名称排序放置 2 | import ApiClient from './ApiClient'; 3 | 4 | const sharedApiClient = new ApiClient(); 5 | 6 | export { 7 | ApiClient, 8 | sharedApiClient, 9 | }; 10 | -------------------------------------------------------------------------------- /src/utilities/Constants.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijy91-archives-repos/react-native-template-ui-based/164f3520fe48e66e6d78510b0e6f864ad8efc330/src/utilities/Constants.js -------------------------------------------------------------------------------- /src/utilities/I18n.js: -------------------------------------------------------------------------------- 1 | import defaultLocales from '../resources/locales'; 2 | 3 | let language = 'en'; 4 | // let language = 'zh-Hans'; 5 | 6 | const get = (object, path) => { 7 | if (typeof object === 'object' && path) { 8 | return path 9 | .split(/[\.\["'\]]/) // eslint-disable-line 10 | .filter(Boolean) 11 | .reduce((acc, current) => acc && acc[current], object); 12 | } 13 | return object; 14 | }; 15 | 16 | /** 17 | * t 18 | * 19 | * @param {*} key - key 20 | * @param {*} data - data 21 | */ 22 | const t = (key, args = {}) => { 23 | let value = get(defaultLocales, `${language}.${key}`); 24 | 25 | if (value === undefined) { 26 | value = get(defaultLocales, `en.${key}`); 27 | } 28 | 29 | if (value === undefined) { 30 | value = `Missing ${language}.${key}`; 31 | } 32 | 33 | if (args) { 34 | const keys = Object.keys(args); 35 | keys.forEach((argKey) => { 36 | const argValue = args[argKey]; 37 | value = value.replace(`__${argKey}__`, argValue); 38 | }); 39 | } 40 | 41 | return value; 42 | }; 43 | 44 | 45 | /** 46 | * Use Language 47 | * 48 | * @param {*} language - Language 49 | */ 50 | const useLanguage = (l) => { 51 | language = l; 52 | }; 53 | 54 | const I18n = { 55 | useLanguage, 56 | t, 57 | }; 58 | 59 | export { 60 | I18n as default, 61 | t, 62 | useLanguage, 63 | }; 64 | -------------------------------------------------------------------------------- /web/config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebookincubator/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 50 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in Webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | } 81 | ); 82 | // Stringify all values so we can feed into Webpack DefinePlugin 83 | const stringified = { 84 | 'process.env': Object.keys(raw).reduce((env, key) => { 85 | env[key] = JSON.stringify(raw[key]); 86 | return env; 87 | }, {}), 88 | }; 89 | 90 | return { raw, stringified }; 91 | } 92 | 93 | module.exports = getClientEnvironment; 94 | -------------------------------------------------------------------------------- /web/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /web/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /web/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right