├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .watchmanconfig
├── App.js
├── README.md
├── app.json
├── package.json
├── public
├── app.css
├── favicon.ico
├── index.html
├── keycloak.json
└── manifest.json
├── src
├── App.js
├── App.test.js
├── __snapshots__
│ └── App.test.js.snap
├── assets
│ ├── fonts
│ │ └── SpaceMono-Regular.ttf
│ └── images
│ │ ├── icon.png
│ │ ├── robot-dev.png
│ │ ├── robot-dev@2x.png
│ │ ├── robot-dev@3x.png
│ │ ├── robot-prod.png
│ │ └── splash.png
├── components
│ ├── Dimensions.js
│ ├── StyledText.js
│ └── __tests__
│ │ ├── StyledText-test.js
│ │ └── __snapshots__
│ │ └── StyledText-test.js.snap
├── constants
│ ├── Colors.js
│ └── Layout.js
├── index.css
├── index.js
├── infrastructure
│ ├── registerForPushNotificationsAsync.js
│ └── registerServiceWorker.js
├── navigation
│ ├── AppLogin.js
│ ├── AppPhone
│ │ ├── Main.android.js
│ │ ├── Main.ios.js
│ │ ├── Main.js
│ │ ├── Root.js
│ │ └── index.js
│ ├── AppTablet
│ │ ├── Main.js
│ │ ├── Root.js
│ │ └── index.js
│ ├── WebMonitor
│ │ ├── Main.js
│ │ ├── Root.js
│ │ └── index.js
│ ├── WebPhone
│ │ ├── Main.js
│ │ ├── Root.js
│ │ └── index.js
│ ├── WebTablet
│ │ ├── Main.js
│ │ ├── Root.js
│ │ └── index.js
│ └── index.js
└── screens
│ ├── AppHomeScreen.js
│ ├── LinksScreen.js
│ ├── LoginScreen.js
│ ├── SettingsScreen.js
│ ├── WebDocumentationScreen.js
│ ├── WebHomeScreen.js
│ ├── monitorsize
│ ├── AppHomeScreen.js
│ └── WebHomeScreen.js
│ ├── phonesize
│ ├── AppHomeScreen.js
│ └── WebHomeScreen.js
│ └── tabletsize
│ ├── AppHomeScreen.js
│ └── WebHomeScreen.js
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["babel-preset-expo", "react-native-dotenv"],
3 | "env": {
4 | "development": {
5 | "plugins": ["transform-react-jsx-source"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "sourceType": "module",
6 | "ecmaFeatures": {
7 | "jsx": true,
8 | "modules": true,
9 | "experimentalObjectRestSpread": true
10 | }
11 | },
12 | "plugins": [
13 | "react",
14 | "react-native"
15 | ],
16 | "extends": "airbnb",
17 | "env": {
18 | "browser": true,
19 | "jest": true,
20 | "react-native/react-native": true
21 | },
22 | "rules": {
23 | "global-require": 0,
24 | "no-console": 0,
25 | "max-len": [
26 | 2,
27 | {
28 | "code": 120,
29 | "ignoreComments": true,
30 | "ignoreUrls": true
31 | }
32 | ],
33 | "no-underscore-dangle": 0,
34 | "no-use-before-define": 0,
35 | "react/forbid-prop-types": 0,
36 | "react/jsx-filename-extension": [
37 | 2,
38 | {
39 | "extensions": [".js", ".jsx"]
40 | }
41 | ],
42 | "no-param-reassign": [
43 | 2,
44 | {
45 | "props": false
46 | }
47 | ],
48 | "react-native/no-unused-styles": 2,
49 | "react-native/split-platform-components": 2,
50 | "react-native/no-inline-styles": 0,
51 | "react/prefer-stateless-function": 0,
52 | "react-native/no-color-literals": 0
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generic
2 | *.swp
3 |
4 | .idea
5 |
6 | ######## Expo/RN ignored files ########
7 | node_modules/**/*
8 | .expo/*
9 | npm-debug.*
10 |
11 | ######## react-native-web ignored files ########
12 |
13 | # dependencies
14 | /node_modules
15 |
16 | # testing
17 | /coverage
18 |
19 | # production
20 | /build
21 | /public/assets
22 |
23 | # misc
24 | .DS_Store
25 | .env
26 | .env.local
27 | .env.development.local
28 | .env.test.local
29 | .env.production.local
30 |
31 | npm-debug.log*
32 | yarn-debug.log*
33 | yarn-error.log*
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | import App from './src/App';
2 |
3 | export default App;
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # expo-react-native-web
2 |
3 | **SECURITY NOTE: This repo is unmaintained, only kept alive for posterity.
4 |
5 | **NOTE: My expo-web repo has been [merged](https://github.com/expo/expo-sdk/pull/107) in official Expo, Yay!**
6 |
7 | **NOTE2: Unfortunately the PR had to be retracted due to dependency concerns.**
8 |
9 | Also I'm now doubting if this is the correct way to move forward.
10 | It seems Vincent Riemers [React Native DOM](https://github.com/vincentriemer/react-native-dom) is the better way to go than react-native-web.
11 |
12 | **I will soon update this repo to use that instead. (update: not going to happen)**
13 |
14 | This repo shows how an Expo app (the default tabs template) can be extended with React Native Web support.
15 |
16 | **NOTE3: Expo now has support for web. It's still in beta, but coming along nicely. They chose react-native-web.
17 |
18 | [See my blog post here](https://medium.com/@ron.arts/web-support-for-create-react-native-app-80b16f930326).
19 |
20 | ## How to use
21 |
22 | Clone this repo, and run `yarn`.
23 |
24 | Then start `exp start -c` in one terminal, in another start `exp ios`.
25 |
26 | In yet another terminal start `yarn start` and connect your browser to `http://localhost:8080`.
27 |
28 | Try resizing the browser.
29 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "app",
4 | "description": "A very interesting project.",
5 | "slug": "app",
6 | "privacy": "public",
7 | "sdkVersion": "28.0.0",
8 | "platforms": [
9 | "ios",
10 | "android"
11 | ],
12 | "version": "1.0.0",
13 | "icon": "./src/assets/images/icon.png",
14 | "splash": {
15 | "image": "./src/assets/images/splash.png",
16 | "resizeMode": "contain",
17 | "backgroundColor": "#ffffff"
18 | },
19 | "ios": {
20 | "supportsTablet": true
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "0.1.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "private": true,
6 | "scripts": {
7 | "start": "./node_modules/.bin/webpack-dev-server -d --config ./webpack.config.js --inline --hot --colors --content-base public/",
8 | "build": "NODE_ENV=production ./node_modules/.bin/webpack -p --config ./webpack.config.js",
9 | "test": "node_modules/.bin/jest",
10 | "eject": "react-scripts eject",
11 | "lint": "eslint --ignore-path .gitignore .",
12 | "lint:fix": "eslint --ignore-path .gitignore . --fix",
13 | "prettier": "prettier --write '*.js'",
14 | "format-code": "yarn run prettier && yarn run lint:fix",
15 | "precommit": "lint-staged"
16 | },
17 | "lint-staged": {
18 | "*.js": [
19 | "yarn run format-code",
20 | "git add"
21 | ]
22 | },
23 | "jest": {
24 | "preset": "jest-expo"
25 | },
26 | "dependencies": {
27 | "@expo/samples": "2.1.1",
28 | "@expo/vector-icons": "^6.3.0",
29 | "babel-eslint": "8.1.1",
30 | "expo": "^28.0.0",
31 | "expo-web": "^0.0.14",
32 | "husky": "^0.14.3",
33 | "lint-staged": "^6.1.1",
34 | "prop-types": "^15.6.0",
35 | "query-string": "^5.1.0",
36 | "react": "16.3.1",
37 | "react-art": "^16.4.1",
38 | "react-dom": "16.0.0",
39 | "react-native": "https://github.com/expo/react-native/archive/sdk-28.0.0.tar.gz",
40 | "react-native-dotenv": "^0.1.1",
41 | "react-native-svg-web": "^1.0.1",
42 | "react-native-web": "^0.7.0",
43 | "react-navigation": "^1.1.0-rc.4",
44 | "react-scripts": "1.0.17",
45 | "webpack": "^3.11.0"
46 | },
47 | "devDependencies": {
48 | "babel-loader": "^7.1.2",
49 | "babel-plugin-expo-web": "^0.0.5",
50 | "babel-plugin-react-native-web": "^0.4.0",
51 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
52 | "babel-plugin-transform-imports": "^1.4.1",
53 | "eslint": "^4.18.0",
54 | "eslint-config-airbnb": "^16.1.0",
55 | "eslint-plugin-import": "^2.8.0",
56 | "eslint-plugin-jsx-a11y": "^6.0.3",
57 | "eslint-plugin-react": "^7.6.1",
58 | "eslint-plugin-react-native": "^3.2.1",
59 | "file-loader": "^1.1.7",
60 | "jest-cli": "^22.4.0",
61 | "jest-expo": "^28.0.0",
62 | "prettier": "^1.10.2",
63 | "react-style-proptype": "^3.2.0",
64 | "react-test-renderer": "^16.2.0"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/public/app.css:
--------------------------------------------------------------------------------
1 | #root {
2 | display: flex;
3 | flex-direction: column;
4 | min-height: 100vh;
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raarts/expo-react-native-web/ad2d5c8a798a8e769aae68ebaead8bd6683181c6/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | React App
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/public/keycloak.json:
--------------------------------------------------------------------------------
1 | {
2 | "realm": "expo",
3 | "auth-server-url": "https://oidc.production.swarm.xe100.host/auth",
4 | "ssl-required": "external",
5 | "resource": "00DA24BF-218A-4FAC-8F8B-F47ECB97A280",
6 | "public-client": true
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Platform, StatusBar, StyleSheet, View } from 'react-native';
3 | import { AppLoading, Asset, Constants, Font } from 'expo';
4 | import { Ionicons } from '@expo/vector-icons';
5 | import { version as reactNativeVersion } from 'react-native/package.json';
6 | import RootNavigation from './navigation';
7 |
8 | export default class App extends React.Component {
9 | state = {
10 | isLoadingComplete: false,
11 | currentUser: null,
12 | };
13 |
14 | async componentWillMount() {
15 | console.log('Expo', Constants); // eslint-disable-line no-console
16 | console.log('React', React); // eslint-disable-line no-console
17 | console.log('React Native Version: ', reactNativeVersion); // eslint-disable-line no-console
18 |
19 | const [
20 | ignoredAssets, // eslint-disable-line no-unused-vars
21 | ignoredFonts, // eslint-disable-line no-unused-vars
22 | ] = await Promise.all([
23 | Asset.loadAsync([
24 | require('./assets/images/robot-dev.png'),
25 | require('./assets/images/robot-prod.png'),
26 | ]),
27 | Font.loadAsync({
28 | // This is the font that we are using for our tab bar
29 | ...Ionicons.font,
30 | // We include SpaceMono because we use it in AppHomeScreen.js. Feel free
31 | // to remove this if you are not using it in your app
32 | 'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'),
33 | }),
34 | ]);
35 | this.setState({
36 | isLoadingComplete: true,
37 | });
38 | }
39 |
40 | doLogin = () => {
41 | console.log('doLogin()');
42 | this.setState({
43 | currentUser: 'yes',
44 | });
45 | };
46 |
47 | doLogout = () => {
48 | console.log('doLogout()');
49 | this.setState({
50 | currentUser: null,
51 | });
52 | };
53 |
54 | render() {
55 | if (!this.state.isLoadingComplete) {
56 | return (
57 |
58 | );
59 | }
60 | return (
61 |
62 | {Platform.OS === 'ios' && }
63 | {Platform.OS === 'android' && }
64 |
65 |
66 | );
67 | }
68 | }
69 |
70 | const styles = StyleSheet.create({
71 | container: {
72 | flex: 1,
73 | backgroundColor: '#fff',
74 | },
75 | statusBarUnderlay: {
76 | height: 24,
77 | backgroundColor: 'rgba(0,0,0,0.2)',
78 | },
79 | });
80 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import 'react-native';
2 | import React from 'react';
3 | import renderer from 'react-test-renderer';
4 | import App from './App';
5 |
6 | it('renders correctly', async () => {
7 | expect.assertions(1);
8 | const tree = renderer.create().toJSON();
9 | expect(tree).toMatchSnapshot();
10 | });
11 |
--------------------------------------------------------------------------------
/src/__snapshots__/App.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders correctly 1`] = ``;
4 |
--------------------------------------------------------------------------------
/src/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raarts/expo-react-native-web/ad2d5c8a798a8e769aae68ebaead8bd6683181c6/src/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/src/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raarts/expo-react-native-web/ad2d5c8a798a8e769aae68ebaead8bd6683181c6/src/assets/images/icon.png
--------------------------------------------------------------------------------
/src/assets/images/robot-dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raarts/expo-react-native-web/ad2d5c8a798a8e769aae68ebaead8bd6683181c6/src/assets/images/robot-dev.png
--------------------------------------------------------------------------------
/src/assets/images/robot-dev@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raarts/expo-react-native-web/ad2d5c8a798a8e769aae68ebaead8bd6683181c6/src/assets/images/robot-dev@2x.png
--------------------------------------------------------------------------------
/src/assets/images/robot-dev@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raarts/expo-react-native-web/ad2d5c8a798a8e769aae68ebaead8bd6683181c6/src/assets/images/robot-dev@3x.png
--------------------------------------------------------------------------------
/src/assets/images/robot-prod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raarts/expo-react-native-web/ad2d5c8a798a8e769aae68ebaead8bd6683181c6/src/assets/images/robot-prod.png
--------------------------------------------------------------------------------
/src/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raarts/expo-react-native-web/ad2d5c8a798a8e769aae68ebaead8bd6683181c6/src/assets/images/splash.png
--------------------------------------------------------------------------------
/src/components/Dimensions.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, Text, View } from 'react-native';
3 | import PropTypes from 'prop-types';
4 |
5 | const Dimensions = (props) => {
6 | const { dimensions } = props;
7 | return (
8 |
9 |
10 |
11 | { dimensions.size.width } x { dimensions.size.height } ({ dimensions.orientation })
12 |
13 |
14 |
15 |
16 | { dimensions.screenFormFactor } scaled by { dimensions.scale.toFixed(2) }
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | Dimensions.propTypes = {
24 | dimensions: PropTypes.shape({
25 | size: PropTypes.shape({
26 | width: PropTypes.number.isRequired,
27 | height: PropTypes.number.isRequired,
28 | }),
29 | screenFormFactor: PropTypes.string.isRequired,
30 | orientation: PropTypes.string.isRequired,
31 | scale: PropTypes.number.isRequired,
32 | }).isRequired,
33 | };
34 |
35 | export default Dimensions;
36 |
37 | const styles = StyleSheet.create({
38 | container: {
39 | flex: 1,
40 | backgroundColor: '#fff',
41 | },
42 | dimensionsContainer: {
43 | alignItems: 'center',
44 | marginHorizontal: 50,
45 | },
46 | dimensionsText: {
47 | fontSize: 17,
48 | color: 'rgba(96,100,109, 1)',
49 | lineHeight: 24,
50 | textAlign: 'center',
51 | },
52 | });
53 |
--------------------------------------------------------------------------------
/src/components/StyledText.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Text } from 'react-native';
3 |
4 | const MonoText = props => (
5 |
6 | );
7 |
8 | MonoText.propTypes = Text.propTypes;
9 |
10 | export default MonoText;
11 |
--------------------------------------------------------------------------------
/src/components/__tests__/StyledText-test.js:
--------------------------------------------------------------------------------
1 | import 'react-native';
2 | import React from 'react';
3 | import renderer from 'react-test-renderer';
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 |
--------------------------------------------------------------------------------
/src/components/__tests__/__snapshots__/StyledText-test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders correctly 1`] = `
4 |
17 | Snapshot test!
18 |
19 | `;
20 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/constants/Layout.js:
--------------------------------------------------------------------------------
1 | import { Dimensions } from 'react-native';
2 |
3 | const { width, height } = Dimensions.get('window');
4 |
5 | export default {
6 | window: {
7 | width,
8 | height,
9 | },
10 | isSmallDevice: width < 375,
11 | };
12 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import registerServiceWorker from './infrastructure/registerServiceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/src/infrastructure/registerForPushNotificationsAsync.js:
--------------------------------------------------------------------------------
1 | import { Constants, Permissions, Notifications } from 'expo';
2 | import { Platform } from 'react-native';
3 |
4 | // Example server, implemented in Rails: https://git.io/vKHKv
5 | const PUSH_ENDPOINT = 'https://expo-push-server.herokuapp.com/tokens';
6 |
7 | export default (async function registerForPushNotificationsAsync() {
8 | // Remote notifications do not work in simulators, only on device
9 | if (!Constants.isDevice || Platform.OS === 'web') {
10 | return;
11 | }
12 |
13 | // Android remote notification permissions are granted during the app
14 | // install, so this will only ask on iOS
15 | const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS);
16 |
17 | // Stop here if the user did not grant permissions
18 | if (status !== 'granted') {
19 | return;
20 | }
21 |
22 | // Get the token that uniquely identifies this device
23 | const token = await Notifications.getExpoPushTokenAsync();
24 |
25 | // POST the token to our backend so we can use it to send pushes from there
26 | fetch(PUSH_ENDPOINT, {
27 | method: 'POST',
28 | headers: {
29 | Accept: 'application/json',
30 | 'Content-Type': 'application/json',
31 | },
32 | body: JSON.stringify({
33 | token: {
34 | value: token,
35 | },
36 | }),
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/src/infrastructure/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(window.location.hostname === 'localhost' ||
12 | // [::1] is the IPv6 localhost address.
13 | window.location.hostname === '[::1]' ||
14 | // 127.0.0.1/8 is considered localhost for IPv4.
15 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));
16 |
17 | export default function register() {
18 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
19 | // The URL constructor is available in all browsers that support SW.
20 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
21 | if (publicUrl.origin !== window.location.origin) {
22 | // Our service worker won't work if PUBLIC_URL is on a different origin
23 | // from what our page is served on. This might happen if a CDN is used to
24 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
25 | return;
26 | }
27 |
28 | window.addEventListener('load', () => {
29 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
30 |
31 | if (isLocalhost) {
32 | // This is running on localhost. Lets check if a service worker still exists or not.
33 | checkValidServiceWorker(swUrl);
34 |
35 | // Add some additional logging to localhost, pointing developers to the
36 | // service worker/PWA documentation.
37 | navigator.serviceWorker.ready.then(() => {
38 | console.log('This web app is being served cache-first by a service ' +
39 | 'worker. To learn more, visit https://goo.gl/SC7cgQ');
40 | });
41 | } else {
42 | // Is not local host. Just register service worker
43 | registerValidSW(swUrl);
44 | }
45 | });
46 | }
47 | }
48 |
49 | function registerValidSW(swUrl) {
50 | navigator.serviceWorker
51 | .register(swUrl)
52 | .then((registration) => {
53 | registration.onupdatefound = () => {
54 | const installingWorker = registration.installing;
55 | installingWorker.onstatechange = () => {
56 | if (installingWorker.state === 'installed') {
57 | if (navigator.serviceWorker.controller) {
58 | // At this point, the old content will have been purged and
59 | // the fresh content will have been added to the cache.
60 | // It's the perfect time to display a "New content is
61 | // available; please refresh." message in your web app.
62 | console.log('New content is available; please refresh.');
63 | } else {
64 | // At this point, everything has been precached.
65 | // It's the perfect time to display a
66 | // "Content is cached for offline use." message.
67 | console.log('Content is cached for offline use.');
68 | }
69 | }
70 | };
71 | };
72 | })
73 | .catch((error) => {
74 | console.log('Error during service worker registration:', error);
75 | });
76 | }
77 |
78 | function checkValidServiceWorker(swUrl) {
79 | // Check if the service worker can be found. If it can't reload the page.
80 | fetch(swUrl)
81 | .then((response) => {
82 | // Ensure service worker exists, and that we really are getting a JS file.
83 | if (
84 | response.status === 404 ||
85 | response.headers.get('content-type').indexOf('javascript') === -1
86 | ) {
87 | // No service worker found. Probably a different app. Reload the page.
88 | navigator.serviceWorker.ready.then((registration) => {
89 | registration.unregister().then(() => {
90 | window.location.reload();
91 | });
92 | });
93 | } else {
94 | // Service worker found. Proceed as normal.
95 | registerValidSW(swUrl);
96 | }
97 | })
98 | .catch(() => {
99 | console.log('No internet connection found. App is running in offline mode.');
100 | });
101 | }
102 |
103 | export function unregister() {
104 | if ('serviceWorker' in navigator) {
105 | navigator.serviceWorker.ready.then((registration) => {
106 | registration.unregister();
107 | });
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/navigation/AppLogin.js:
--------------------------------------------------------------------------------
1 | import { StackNavigator } from 'react-navigation';
2 | import LoginScreen from '../screens/LoginScreen';
3 |
4 | const AppLoginStackNavigator = StackNavigator(
5 | {
6 | Main: {
7 | screen: LoginScreen,
8 | },
9 | },
10 | {
11 | navigationOptions: () => ({
12 | headerTitleStyle: {
13 | fontWeight: 'normal',
14 | },
15 | }),
16 | },
17 | );
18 |
19 | export default AppLoginStackNavigator;
20 |
--------------------------------------------------------------------------------
/src/navigation/AppPhone/Main.android.js:
--------------------------------------------------------------------------------
1 | import { TabNavigator, TabBarTop } from 'react-navigation';
2 |
3 | import AppHomeScreen from '../../screens/phonesize/AppHomeScreen';
4 | import LinksScreen from '../../screens/LinksScreen';
5 | import SettingsScreen from '../../screens/SettingsScreen';
6 |
7 | const MainTabNavigator = TabNavigator(
8 | {
9 | Home: {
10 | screen: AppHomeScreen,
11 | },
12 | Links: {
13 | screen: LinksScreen,
14 | },
15 | Settings: {
16 | screen: SettingsScreen,
17 | },
18 | },
19 | {
20 | navigationOptions: (parm) => {
21 | const { navigation } = parm;
22 | const { routeName } = navigation.state;
23 |
24 | return ({
25 | tabBarLabel: routeName,
26 | });
27 | },
28 | tabBarComponent: TabBarTop,
29 | tabBarPosition: 'top',
30 | animationEnabled: false,
31 | swipeEnabled: true,
32 | },
33 | );
34 |
35 | export default MainTabNavigator;
36 |
--------------------------------------------------------------------------------
/src/navigation/AppPhone/Main.ios.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Ionicons } from '@expo/vector-icons';
3 | import { TabNavigator, TabBarBottom } from 'react-navigation';
4 |
5 | import Colors from '../../constants/Colors';
6 |
7 | import AppHomeScreen from '../../screens/phonesize/AppHomeScreen';
8 | import LinksScreen from '../../screens/LinksScreen';
9 | import SettingsScreen from '../../screens/SettingsScreen';
10 |
11 | const MainTabNavigator = TabNavigator(
12 | {
13 | Home: {
14 | screen: AppHomeScreen,
15 | },
16 | Links: {
17 | screen: LinksScreen,
18 | },
19 | Settings: {
20 | screen: SettingsScreen,
21 | },
22 | },
23 | {
24 | navigationOptions: (parm) => {
25 | const { navigation } = parm;
26 | const { routeName } = navigation.state;
27 |
28 | return ({
29 | tabBarIcon: (param) => { // eslint-disable-line react/prop-types
30 | const { focused } = param;
31 | let iconName;
32 | switch (routeName) {
33 | case 'Home':
34 | iconName = `ios-information-circle${focused ? '' : '-outline'}`;
35 | break;
36 | case 'Links':
37 | iconName = `ios-link${focused ? '' : '-outline'}`;
38 | break;
39 | case 'Settings':
40 | iconName = `ios-options${focused ? '' : '-outline'}`;
41 | break;
42 | default:
43 | break;
44 | }
45 | return (
46 |
52 | );
53 | },
54 | tabBarLabel: routeName,
55 | });
56 | },
57 | tabBarComponent: TabBarBottom,
58 | tabBarPosition: 'bottom',
59 | animationEnabled: false,
60 | swipeEnabled: false,
61 | },
62 | );
63 |
64 | export default MainTabNavigator;
65 |
--------------------------------------------------------------------------------
/src/navigation/AppPhone/Main.js:
--------------------------------------------------------------------------------
1 | import { TabNavigator, TabBarTop } from 'react-navigation';
2 |
3 | import AppHomeScreen from '../../screens/phonesize/AppHomeScreen';
4 | import LinksScreen from '../../screens/LinksScreen';
5 | import SettingsScreen from '../../screens/SettingsScreen';
6 |
7 | const MainTabNavigator = TabNavigator(
8 | {
9 | Home: {
10 | screen: AppHomeScreen,
11 | },
12 | Links: {
13 | screen: LinksScreen,
14 | },
15 | Settings: {
16 | screen: SettingsScreen,
17 | },
18 | },
19 | {
20 | navigationOptions: (parm) => {
21 | const { navigation } = parm;
22 | const { routeName } = navigation.state;
23 |
24 | return ({
25 | tabBarLabel: routeName,
26 | });
27 | },
28 | tabBarComponent: TabBarTop,
29 | tabBarPosition: 'top',
30 | animationEnabled: false,
31 | swipeEnabled: true,
32 | },
33 | );
34 |
35 | export default MainTabNavigator;
36 |
--------------------------------------------------------------------------------
/src/navigation/AppPhone/Root.js:
--------------------------------------------------------------------------------
1 | import { StackNavigator } from 'react-navigation';
2 | import MainTabNavigator from './Main';
3 |
4 | const RootStackNavigator = StackNavigator(
5 | {
6 | Main: {
7 | screen: MainTabNavigator,
8 | },
9 | },
10 | {
11 | navigationOptions: () => ({
12 | headerTitleStyle: {
13 | fontWeight: 'normal',
14 | },
15 | }),
16 | },
17 | );
18 |
19 | export default RootStackNavigator;
20 |
--------------------------------------------------------------------------------
/src/navigation/AppPhone/index.js:
--------------------------------------------------------------------------------
1 | import RootStackNavigator from './Root';
2 | import MainTabNavigator from './Main';
3 |
4 | export {
5 | RootStackNavigator as AppPhoneRootNavigator,
6 | MainTabNavigator as AppPhoneMainTabNavigator,
7 | };
8 |
--------------------------------------------------------------------------------
/src/navigation/AppTablet/Main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Platform } from 'react-native';
3 | import { Ionicons } from '@expo/vector-icons';
4 | import { TabNavigator, TabBarBottom } from 'react-navigation';
5 |
6 | import Colors from '../../constants/Colors';
7 |
8 | import AppHomeScreen from '../../screens/tabletsize/AppHomeScreen';
9 | import LinksScreen from '../../screens/LinksScreen';
10 | import SettingsScreen from '../../screens/SettingsScreen';
11 |
12 | const MainTabNavigator = TabNavigator(
13 | {
14 | Home: {
15 | screen: AppHomeScreen,
16 | },
17 | Links: {
18 | screen: LinksScreen,
19 | },
20 | Settings: {
21 | screen: SettingsScreen,
22 | },
23 | },
24 | {
25 | navigationOptions: ({ navigation }) => ({
26 | tabBarIcon: ({ focused }) => { // eslint-disable-line react/prop-types
27 | const { routeName } = navigation.state;
28 | let iconName;
29 | switch (routeName) {
30 | case 'Home':
31 | iconName =
32 | Platform.OS === 'ios'
33 | ? `ios-information-circle${focused ? '' : '-outline'}`
34 | : 'md-information-circle';
35 | break;
36 | case 'Links':
37 | iconName = Platform.OS === 'ios' ? `ios-link${focused ? '' : '-outline'}` : 'md-link';
38 | break;
39 | case 'Settings':
40 | iconName =
41 | Platform.OS === 'ios' ? `ios-options${focused ? '' : '-outline'}` : 'md-options';
42 | break;
43 | default:
44 | break;
45 | }
46 | return (
47 |
53 | );
54 | },
55 | }),
56 | tabBarComponent: TabBarBottom,
57 | tabBarPosition: 'bottom',
58 | animationEnabled: false,
59 | swipeEnabled: false,
60 | },
61 | );
62 |
63 | export default MainTabNavigator;
64 |
--------------------------------------------------------------------------------
/src/navigation/AppTablet/Root.js:
--------------------------------------------------------------------------------
1 | import { StackNavigator } from 'react-navigation';
2 | import MainTabNavigator from './Main';
3 |
4 | const RootStackNavigator = StackNavigator(
5 | {
6 | Main: {
7 | screen: MainTabNavigator,
8 | },
9 | },
10 | {
11 | navigationOptions: () => ({
12 | headerTitleStyle: {
13 | fontWeight: 'normal',
14 | },
15 | }),
16 | },
17 | );
18 |
19 | export default RootStackNavigator;
20 |
--------------------------------------------------------------------------------
/src/navigation/AppTablet/index.js:
--------------------------------------------------------------------------------
1 | import RootStackNavigator from './Root';
2 | import MainTabNavigator from './Main';
3 |
4 | export {
5 | RootStackNavigator as AppTabletRootNavigator,
6 | MainTabNavigator as TabletMainTabNavigator,
7 | };
8 |
--------------------------------------------------------------------------------
/src/navigation/WebMonitor/Main.js:
--------------------------------------------------------------------------------
1 | import { View } from 'react-native';
2 | import { TabNavigator, TabBarTop } from 'react-navigation';
3 |
4 | import WebHomeScreen from '../../screens/monitorsize/WebHomeScreen';
5 | import AppHomeScreen from '../../screens/monitorsize/AppHomeScreen';
6 |
7 | import WebDocumentationScreen from '../../screens/WebDocumentationScreen';
8 | import LinksScreen from '../../screens/LinksScreen';
9 | import SettingsScreen from '../../screens/SettingsScreen';
10 |
11 | const WebMainTabNavigator = TabNavigator(
12 | {
13 | WebHome: {
14 | screen: WebHomeScreen,
15 | },
16 | Documentation: {
17 | screen: WebDocumentationScreen,
18 | },
19 | AppHome: {
20 | screen: AppHomeScreen,
21 | },
22 | Links: {
23 | screen: LinksScreen,
24 | },
25 | Settings: {
26 | screen: SettingsScreen,
27 | },
28 | LoginOut: {
29 | screen: View,
30 | },
31 | },
32 | {
33 | navigationOptions: (parm) => {
34 | const { screenProps, navigation } = parm;
35 | const { routeName } = navigation.state;
36 | let label = routeName;
37 | const appRoutes = ['AppHome', 'Links', 'Settings'];
38 |
39 | if (routeName === 'LoginOut') {
40 | label = screenProps.currentUser ? 'Logout' : 'Login/Signup';
41 | }
42 | if (appRoutes.includes(routeName) && !screenProps.currentUser) {
43 | label = ' ';
44 | }
45 | return ({
46 | tabBarLabel: label,
47 | tabBarOnPress: ({ /* previousScene, */ scene, jumpToIndex }) => {
48 | if (scene.route.routeName === 'LoginOut') {
49 | if (screenProps.currentUser) {
50 | screenProps.doLogout();
51 | } else {
52 | screenProps.doLogin();
53 | }
54 | }
55 | if (appRoutes.includes(routeName) && !screenProps.currentUser) {
56 | return;
57 | }
58 | jumpToIndex(scene.index);
59 | },
60 | header: null,
61 | });
62 | },
63 | tabBarComponent: TabBarTop,
64 | tabBarPosition: 'top',
65 | animationEnabled: false,
66 | swipeEnabled: false,
67 | tabBarOptions: {
68 | upperCaseLabel: false,
69 | },
70 | },
71 | );
72 |
73 | export default WebMainTabNavigator;
74 |
--------------------------------------------------------------------------------
/src/navigation/WebMonitor/Root.js:
--------------------------------------------------------------------------------
1 | import { StackNavigator } from 'react-navigation';
2 | import MainTabNavigator from './Main';
3 |
4 | const RootStackNavigator = StackNavigator(
5 | {
6 | Main: {
7 | screen: MainTabNavigator,
8 | },
9 | },
10 | {
11 | navigationOptions: () => ({
12 | headerTitleStyle: {
13 | fontWeight: 'normal',
14 | },
15 | }),
16 | },
17 | );
18 |
19 | export default RootStackNavigator;
20 |
--------------------------------------------------------------------------------
/src/navigation/WebMonitor/index.js:
--------------------------------------------------------------------------------
1 | import RootStackNavigator from './Root';
2 |
3 | export default RootStackNavigator;
4 |
--------------------------------------------------------------------------------
/src/navigation/WebPhone/Main.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/no-multi-comp */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import { TabNavigator, TabBarTop } from 'react-navigation';
5 |
6 | import WebHomeScreen from '../../screens/phonesize/WebHomeScreen';
7 | import AppHomeScreen from '../../screens/phonesize/AppHomeScreen';
8 |
9 | import WebDocumentationScreen from '../../screens/WebDocumentationScreen';
10 | import LinksScreen from '../../screens/LinksScreen';
11 | import SettingsScreen from '../../screens/SettingsScreen';
12 |
13 | const defaultProps = {
14 | screenProps: {
15 | currentUser: null,
16 | },
17 | };
18 |
19 | const propTypes = {
20 | screenProps: PropTypes.shape({
21 | currentUser: PropTypes.string,
22 | }),
23 | };
24 |
25 | class HomeScreen extends React.Component {
26 | static navigationOptions = {
27 | header: null,
28 | };
29 | render() {
30 | const { currentUser } = this.props.screenProps;
31 |
32 | if (currentUser) {
33 | return ;
34 | }
35 | return ;
36 | }
37 | }
38 |
39 | HomeScreen.defaultProps = defaultProps;
40 | HomeScreen.propTypes = propTypes;
41 |
42 | class SecondScreen extends React.Component {
43 | static navigationOptions = {
44 | header: null,
45 | };
46 | render() {
47 | const { currentUser } = this.props.screenProps;
48 |
49 | if (currentUser) {
50 | return ;
51 | }
52 | return ;
53 | }
54 | }
55 |
56 | SecondScreen.defaultProps = defaultProps;
57 | SecondScreen.propTypes = propTypes;
58 |
59 | class ThirdScreen extends React.Component {
60 | static navigationOptions = {
61 | header: null,
62 | };
63 | render() {
64 | const { currentUser } = this.props.screenProps;
65 |
66 | if (currentUser) {
67 | return ;
68 | }
69 | return ;
70 | }
71 | }
72 |
73 | ThirdScreen.defaultProps = defaultProps;
74 | ThirdScreen.propTypes = propTypes;
75 |
76 | const WebMainTabNavigator = TabNavigator(
77 | {
78 | Home: {
79 | screen: HomeScreen,
80 | },
81 | Second: {
82 | screen: SecondScreen,
83 | },
84 | Third: {
85 | screen: ThirdScreen,
86 | },
87 | },
88 | {
89 | navigationOptions: (parm) => {
90 | const { screenProps, navigation } = parm;
91 | const { routeName } = navigation.state;
92 | let label = routeName;
93 |
94 | if (routeName === 'Home') {
95 | label = screenProps.currentUser ? 'Home' : 'Home';
96 | }
97 | if (routeName === 'Second') {
98 | label = screenProps.currentUser ? 'Links' : 'Documentation';
99 | }
100 | if (routeName === 'Third') {
101 | label = screenProps.currentUser ? 'Settings' : 'Login/Signup';
102 | }
103 |
104 | return ({
105 | tabBarLabel: label,
106 | tabBarOnPress: ({ /* previousScene, */ scene, jumpToIndex }) => {
107 | if (scene.route.routeName === 'Third' && !screenProps.currentUser) {
108 | // login();
109 | }
110 | jumpToIndex(scene.index);
111 | },
112 | header: null,
113 | });
114 | },
115 | tabBarComponent: TabBarTop,
116 | tabBarPosition: 'top',
117 | animationEnabled: true,
118 | swipeEnabled: false,
119 | tabBarOptions: {
120 | upperCaseLabel: false,
121 | },
122 | },
123 | );
124 |
125 | export default WebMainTabNavigator;
126 |
--------------------------------------------------------------------------------
/src/navigation/WebPhone/Root.js:
--------------------------------------------------------------------------------
1 | import { StackNavigator } from 'react-navigation';
2 | import MainTabNavigator from './Main';
3 |
4 | const RootStackNavigator = StackNavigator(
5 | {
6 | Main: {
7 | screen: MainTabNavigator,
8 | },
9 | },
10 | {
11 | navigationOptions: () => ({
12 | headerTitleStyle: {
13 | fontWeight: 'normal',
14 | },
15 | }),
16 | },
17 | );
18 |
19 | export default RootStackNavigator;
20 |
--------------------------------------------------------------------------------
/src/navigation/WebPhone/index.js:
--------------------------------------------------------------------------------
1 | import RootStackNavigator from './Root';
2 |
3 | export default RootStackNavigator;
4 |
--------------------------------------------------------------------------------
/src/navigation/WebTablet/Main.js:
--------------------------------------------------------------------------------
1 | import { View } from 'react-native';
2 | import { TabNavigator, TabBarTop } from 'react-navigation';
3 |
4 | import WebHomeScreen from '../../screens/tabletsize/WebHomeScreen';
5 | import AppHomeScreen from '../../screens/tabletsize/AppHomeScreen';
6 |
7 | import WebDocumentationScreen from '../../screens/WebDocumentationScreen';
8 | import LinksScreen from '../../screens/LinksScreen';
9 | import SettingsScreen from '../../screens/SettingsScreen';
10 |
11 | const WebMainTabNavigator = TabNavigator(
12 | {
13 | WebHome: {
14 | screen: WebHomeScreen,
15 | },
16 | Documentation: {
17 | screen: WebDocumentationScreen,
18 | },
19 | AppHome: {
20 | screen: AppHomeScreen,
21 | },
22 | Links: {
23 | screen: LinksScreen,
24 | },
25 | Settings: {
26 | screen: SettingsScreen,
27 | },
28 | LoginOut: {
29 | screen: View,
30 | },
31 | },
32 | {
33 | navigationOptions: (parm) => {
34 | const { screenProps, navigation } = parm;
35 | const { routeName } = navigation.state;
36 | let label = routeName;
37 | const appRoutes = ['AppHome', 'Links', 'Settings'];
38 |
39 | if (routeName === 'LoginOut') {
40 | label = screenProps.currentUser ? 'Logout' : 'Login/Signup';
41 | }
42 | if (appRoutes.includes(routeName) && !screenProps.currentUser) {
43 | label = ' ';
44 | }
45 | return ({
46 | tabBarLabel: label,
47 | tabBarOnPress: ({ /* previousScene, */ scene, jumpToIndex }) => {
48 | if (scene.route.routeName === 'LoginOut') {
49 | if (screenProps.currentUser) {
50 | screenProps.doLogout();
51 | } else {
52 | screenProps.doLogin();
53 | }
54 | }
55 | if (appRoutes.includes(routeName) && !screenProps.currentUser) {
56 | return;
57 | }
58 | jumpToIndex(scene.index);
59 | },
60 | header: null,
61 | });
62 | },
63 | tabBarComponent: TabBarTop,
64 | tabBarPosition: 'top',
65 | animationEnabled: false,
66 | swipeEnabled: false,
67 | tabBarOptions: {
68 | upperCaseLabel: false,
69 | },
70 | },
71 | );
72 |
73 | export default WebMainTabNavigator;
74 |
--------------------------------------------------------------------------------
/src/navigation/WebTablet/Root.js:
--------------------------------------------------------------------------------
1 | import { StackNavigator } from 'react-navigation';
2 | import MainTabNavigator from './Main';
3 |
4 | const RootStackNavigator = StackNavigator(
5 | {
6 | Main: {
7 | screen: MainTabNavigator,
8 | },
9 | },
10 | {
11 | navigationOptions: () => ({
12 | headerTitleStyle: {
13 | fontWeight: 'normal',
14 | },
15 | }),
16 | },
17 | );
18 |
19 | export default RootStackNavigator;
20 |
--------------------------------------------------------------------------------
/src/navigation/WebTablet/index.js:
--------------------------------------------------------------------------------
1 | import RootStackNavigator from './Root';
2 |
3 | export default RootStackNavigator;
4 |
--------------------------------------------------------------------------------
/src/navigation/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Dimensions, Platform } from 'react-native';
4 | import { Notifications } from 'expo';
5 |
6 | import { AppPhoneRootNavigator } from './AppPhone/index';
7 | import { AppTabletRootNavigator } from './AppTablet/index';
8 | import MenuPhoneRootNavigator from './WebPhone/index';
9 | import MenuTabletRootNavigator from './WebTablet/index';
10 | import MenuMonitorRootNavigator from './WebMonitor/index';
11 |
12 | import AppPhoneLoginNavigator from './AppLogin'; // eslint-disable-line import/no-duplicates
13 | import AppTabletLoginNavigator from './AppLogin'; // eslint-disable-line import/no-duplicates
14 |
15 | import registerForPushNotificationsAsync from '../infrastructure/registerForPushNotificationsAsync';
16 |
17 | const IPHONE7_WIDTH = 375;
18 | const IPHONE7_HEIGHT = 667;
19 | const IPAD_WIDTH = 768;
20 | const IPAD_HEIGHT = 1024;
21 | const IPAD_PRO_WIDTH = 1024;
22 | const IPAD_PRO_HEIGHT = 1366; // eslint-disable-line no-unused-vars
23 |
24 | const defaultProps = {
25 | currentUser: null,
26 | };
27 |
28 | const propTypes = {
29 | currentUser: PropTypes.string,
30 | };
31 |
32 | class RootNavigator extends React.Component {
33 | constructor(s, c) {
34 | super(s, c);
35 | this.state = this.setNewDimensions({ screen: Dimensions.get('screen'), window: Dimensions.get('window') });
36 | }
37 |
38 | componentDidMount() {
39 | this._notificationSubscription = this._registerForPushNotifications();
40 | Dimensions.addEventListener('change', this.onDimensionChange);
41 | }
42 |
43 | componentWillUnmount() {
44 | Dimensions.removeEventListener('change', this.onDimensionChange);
45 | if (this._notificationSubscription) {
46 | this._notificationSubscription.remove();
47 | }
48 | }
49 |
50 | onDimensionChange = (dim) => {
51 | this.setState(this.setNewDimensions(dim));
52 | };
53 |
54 | setNewDimensions = (dim) => {
55 | let orientation;
56 | let screenFormFactor = 'phonesize';
57 | let scale;
58 |
59 | let { width, height } = dim.screen;
60 | if (Platform.OS === 'web') {
61 | ({ width, height } = dim.window);
62 | }
63 | if (width > height) { // LANDSCAPE MODE
64 | orientation = 'landscape';
65 | scale = width / IPHONE7_HEIGHT;
66 | if (width > IPHONE7_HEIGHT) {
67 | screenFormFactor = 'tabletsize';
68 | scale = width / IPAD_HEIGHT;
69 | }
70 | if (width > IPAD_HEIGHT) {
71 | screenFormFactor = 'monitorsize';
72 | scale = width / IPAD_PRO_HEIGHT;
73 | }
74 | } else {
75 | orientation = 'portrait'; // PORTRAIT MODE
76 | scale = width / IPHONE7_WIDTH;
77 | // if (width > IPHONE7_WIDTH) {
78 | // screenFormFactor = 'tabletsize';
79 | // scale = width / IPAD_WIDTH;
80 | // }
81 | if (width > IPAD_WIDTH) {
82 | screenFormFactor = 'monitorsize';
83 | scale = width / IPAD_PRO_WIDTH;
84 | }
85 | }
86 | const state = {
87 | size: { width, height }, screenFormFactor, orientation, scale,
88 | };
89 | // console.log(width, height, state.screenFormFactor, state.orientation, state.scale);
90 | return (state);
91 | };
92 |
93 | _registerForPushNotifications() {
94 | // Send our push token over to our backend so we can receive notifications
95 | // You can comment the following line out if you want to stop receiving
96 | // a notification every time you open the app. Check out the source
97 | // for this function in infrastructure/registerForPushNotificationsAsync.js
98 | registerForPushNotificationsAsync();
99 |
100 | // Watch for incoming notifications
101 | this._notificationSubscription = Notifications.addListener(this._handleNotification);
102 | }
103 |
104 | _handleNotification = ({ origin, data }) => {
105 | console.log(`Push notification ${origin} with data: ${JSON.stringify(data)}`);
106 | };
107 |
108 | // decide on which RootNavigator is going to be used. This depends on the environment as follows:
109 | // - resizable (browser, osx/windows): use one of three menu-based layout (depending on the size)
110 | // - small/medium fixed size (phonesize/tabletsize): use one of two app-based layouts
111 | // - big fixed size: use the big menu-based layout
112 | render() {
113 | let Root;
114 | const { props } = this;
115 |
116 | const screenProps = {
117 | currentUser: props.currentUser,
118 | doLogin: props.doLogin,
119 | doLogout: props.doLogout,
120 | dimensions: this.state,
121 | };
122 |
123 | if (Platform.OS === 'ios' || Platform.OS === 'android') {
124 | switch (this.state.screenFormFactor) { // eslint-disable-line default-case
125 | case 'phonesize':
126 | Root = this.props.currentUser ? AppPhoneRootNavigator : AppPhoneLoginNavigator;
127 | break;
128 | case 'tabletsize':
129 | Root = this.props.currentUser ? AppTabletRootNavigator : AppTabletLoginNavigator;
130 | break;
131 | }
132 | }
133 | if (Platform.OS === 'web') {
134 | switch (this.state.screenFormFactor) { // eslint-disable-line default-case
135 | case 'phonesize':
136 | Root = MenuPhoneRootNavigator;
137 | break;
138 | case 'tabletsize':
139 | Root = MenuTabletRootNavigator;
140 | break;
141 | case 'monitorsize':
142 | Root = MenuMonitorRootNavigator;
143 | break;
144 | }
145 | }
146 | return ();
147 | }
148 | }
149 |
150 | RootNavigator.propTypes = propTypes;
151 | RootNavigator.defaultProps = defaultProps;
152 |
153 | export default RootNavigator;
154 |
--------------------------------------------------------------------------------
/src/screens/AppHomeScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {
4 | Image,
5 | Platform,
6 | ScrollView,
7 | StyleSheet,
8 | Text,
9 | TouchableOpacity,
10 | View,
11 | } from 'react-native';
12 | import { WebBrowser } from 'expo';
13 |
14 | import MonoText from '../components/StyledText';
15 | import Dimensions from '../components/Dimensions';
16 |
17 | class HomeScreen extends React.Component {
18 | static navigationOptions = {
19 | header: null,
20 | };
21 |
22 | _maybeRenderDevelopmentModeWarning() {
23 | if (__DEV__) {
24 | const learnMoreButton = (
25 |
26 | Learn more
27 |
28 | );
29 |
30 | return (
31 |
32 | Development mode is enabled, your app will be slower but you can use useful development
33 | tools. {learnMoreButton}
34 |
35 | );
36 | }
37 | return (
38 |
39 | You are not in development mode, your app will run at full speed.
40 |
41 | );
42 | }
43 |
44 | _handleLearnMorePress = () => {
45 | WebBrowser.openBrowserAsync('https://docs.expo.io/versions/latest/guides/development-mode');
46 | };
47 |
48 | _handleHelpPress = () => {
49 | WebBrowser.openBrowserAsync('https://docs.expo.io/versions/latest/guides/up-and-running.html#can-t-see-your-changes');
50 | };
51 |
52 | _handleLogout = () => {
53 | this.props.screenProps.doLogout();
54 | };
55 |
56 | render() {
57 | const { dimensions } = this.props.screenProps;
58 |
59 | return (
60 |
61 |
62 |
63 |
71 |
72 |
73 |
74 | {this._maybeRenderDevelopmentModeWarning()}
75 |
76 | Get started by opening
77 |
78 |
79 | screens/phone/HomeScreen.js
80 |
81 |
82 |
83 | Change this text and your app will automatically reload
84 |
85 |
86 |
87 |
88 |
89 | Help, it didn’t automatically reload!
90 |
91 |
92 |
93 |
94 |
95 | Logout
96 |
97 |
98 |
99 |
100 |
101 |
102 | {(Platform.OS === 'ios' || Platform.OS === 'android') &&
103 |
104 | This is a tab bar. You can edit it in:
105 |
106 |
107 | navigation/MainTabNavigator.js
108 |
109 |
110 | }
111 |
112 | );
113 | }
114 | }
115 |
116 | HomeScreen.defaultProps = {
117 | screenProps: {
118 | currentUser: null,
119 | },
120 | };
121 |
122 | HomeScreen.propTypes = {
123 | screenProps: PropTypes.shape({
124 | currentUser: PropTypes.string,
125 | doLogin: PropTypes.func.isRequired,
126 | doLogout: PropTypes.func.isRequired,
127 | dimensions: PropTypes.shape({
128 | size: PropTypes.shape({
129 | width: PropTypes.number.isRequired,
130 | height: PropTypes.number.isRequired,
131 | }),
132 | screenFormFactor: PropTypes.string.isRequired,
133 | orientation: PropTypes.string.isRequired,
134 | scale: PropTypes.number.isRequired,
135 | }),
136 | }),
137 | };
138 |
139 | export default HomeScreen;
140 |
141 | const styles = StyleSheet.create({
142 | container: {
143 | flex: 1,
144 | backgroundColor: '#fff',
145 | },
146 | developmentModeText: {
147 | marginBottom: 20,
148 | color: 'rgba(0,0,0,0.4)',
149 | fontSize: 14,
150 | lineHeight: 19,
151 | textAlign: 'center',
152 | },
153 | contentContainer: {
154 | paddingTop: 30,
155 | },
156 | welcomeContainer: {
157 | alignItems: 'center',
158 | marginTop: 10,
159 | marginBottom: 20,
160 | },
161 | welcomeImage: {
162 | width: 100,
163 | height: 80,
164 | resizeMode: 'contain',
165 | marginTop: 3,
166 | marginLeft: -10,
167 | },
168 | getStartedContainer: {
169 | alignItems: 'center',
170 | marginHorizontal: 50,
171 | },
172 | homeScreenFilename: {
173 | marginVertical: 7,
174 | },
175 | codeHighlightText: {
176 | color: 'rgba(96,100,109, 0.8)',
177 | },
178 | codeHighlightContainer: {
179 | backgroundColor: 'rgba(0,0,0,0.05)',
180 | borderRadius: 3,
181 | paddingHorizontal: 4,
182 | },
183 | getStartedText: {
184 | fontSize: 17,
185 | color: 'rgba(96,100,109, 1)',
186 | lineHeight: 24,
187 | textAlign: 'center',
188 | },
189 | tabBarInfoContainer: {
190 | position: 'absolute',
191 | bottom: 0,
192 | left: 0,
193 | right: 0,
194 | ...Platform.select({
195 | ios: {
196 | shadowColor: 'black',
197 | shadowOffset: { height: -3 },
198 | shadowOpacity: 0.1,
199 | shadowRadius: 3,
200 | },
201 | android: {
202 | elevation: 20,
203 | },
204 | }),
205 | alignItems: 'center',
206 | backgroundColor: '#fbfbfb',
207 | paddingVertical: 20,
208 | },
209 | tabBarInfoText: {
210 | fontSize: 17,
211 | color: 'rgba(96,100,109, 1)',
212 | textAlign: 'center',
213 | },
214 | navigationFilename: {
215 | marginTop: 5,
216 | },
217 | helpContainer: {
218 | marginTop: 15,
219 | alignItems: 'center',
220 | },
221 | helpLink: {
222 | paddingVertical: 15,
223 | },
224 | helpLinkText: {
225 | fontSize: 14,
226 | color: '#2e78b7',
227 | },
228 | });
229 |
--------------------------------------------------------------------------------
/src/screens/LinksScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ScrollView, StyleSheet } from 'react-native';
3 | import { ExpoLinksView } from '@expo/samples';
4 |
5 | export default class LinksScreen extends React.Component {
6 | static navigationOptions = {
7 | title: 'Links',
8 | };
9 |
10 | render() {
11 | return (
12 |
13 | {/* Go ahead and delete ExpoLinksView and replace it with your
14 | * content, we just wanted to provide you with some helpful links */}
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | const styles = StyleSheet.create({
22 | container: {
23 | flex: 1,
24 | paddingTop: 15,
25 | backgroundColor: '#fff',
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/screens/LoginScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Image,
4 | ScrollView,
5 | StyleSheet,
6 | Text,
7 | TouchableOpacity,
8 | View,
9 | } from 'react-native';
10 | import PropTypes from 'prop-types';
11 |
12 | export default class LoginScreen extends React.Component {
13 | static navigationOptions = {
14 | header: null,
15 | };
16 |
17 | _handleLoginPress = () => {
18 | this.props.screenProps.doLogin();
19 | };
20 |
21 | render() {
22 | return (
23 |
24 |
25 |
26 |
34 |
35 |
36 |
37 |
38 | Web Login!
39 |
40 |
41 |
42 |
43 | );
44 | }
45 | }
46 |
47 | LoginScreen.propTypes = {
48 | screenProps: PropTypes.shape({
49 | currentUser: PropTypes.string,
50 | doLogin: PropTypes.func.isRequired,
51 | doLogout: PropTypes.func.isRequired,
52 | dimensions: PropTypes.shape({
53 | size: PropTypes.shape({
54 | width: PropTypes.number.isRequired,
55 | height: PropTypes.number.isRequired,
56 | }),
57 | screenFormFactor: PropTypes.string.isRequired,
58 | orientation: PropTypes.string.isRequired,
59 | scale: PropTypes.number.isRequired,
60 | }),
61 | }).isRequired,
62 | };
63 |
64 | const styles = StyleSheet.create({
65 | container: {
66 | flex: 1,
67 | backgroundColor: '#fff',
68 | },
69 | contentContainer: {
70 | paddingTop: 30,
71 | },
72 | welcomeContainer: {
73 | alignItems: 'center',
74 | marginTop: 10,
75 | marginBottom: 20,
76 | },
77 | welcomeImage: {
78 | width: 100,
79 | height: 80,
80 | resizeMode: 'contain',
81 | marginTop: 3,
82 | marginLeft: -10,
83 | },
84 | helpContainer: {
85 | marginTop: 15,
86 | alignItems: 'center',
87 | },
88 | helpLink: {
89 | paddingVertical: 15,
90 | },
91 | helpLinkText: {
92 | fontSize: 14,
93 | color: '#2e78b7',
94 | },
95 | });
96 |
--------------------------------------------------------------------------------
/src/screens/SettingsScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ExpoConfigView } from '@expo/samples';
3 |
4 | export default class SettingsScreen extends React.Component {
5 | static navigationOptions = {
6 | title: 'app.json',
7 | };
8 |
9 | render() {
10 | /* Go ahead and delete ExpoConfigView and replace it with your
11 | * content, we just wanted to give you a quick view of your config */
12 | return ;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/screens/WebDocumentationScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | ScrollView,
4 | StyleSheet,
5 | Text,
6 | View,
7 | } from 'react-native';
8 |
9 | export default class WebDocumentationScreen extends React.Component {
10 | render() {
11 | return (
12 |
13 |
14 |
15 |
16 | Please take a look at the documentation
17 |
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
25 | const styles = StyleSheet.create({
26 | container: {
27 | flex: 1,
28 | backgroundColor: '#fff',
29 | },
30 | contentContainer: {
31 | paddingTop: 30,
32 | },
33 | getStartedContainer: {
34 | alignItems: 'center',
35 | marginHorizontal: 50,
36 | },
37 | getStartedText: {
38 | fontSize: 17,
39 | color: 'rgba(96,100,109, 1)',
40 | lineHeight: 24,
41 | textAlign: 'center',
42 | },
43 | });
44 |
--------------------------------------------------------------------------------
/src/screens/WebHomeScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {
4 | ScrollView,
5 | StyleSheet,
6 | Text,
7 | View,
8 | } from 'react-native';
9 | import Dimensions from '../components/Dimensions';
10 |
11 | export default class WebHomeScreen extends React.Component {
12 | render() {
13 | const { dimensions } = this.props.screenProps;
14 |
15 | return (
16 |
17 |
18 |
19 |
20 | Welcome to our Website!
21 |
22 |
23 |
24 |
25 |
26 | );
27 | }
28 | }
29 |
30 | WebHomeScreen.defaultProps = {
31 | screenProps: {
32 | currentUser: null,
33 | },
34 | };
35 |
36 | WebHomeScreen.propTypes = {
37 | screenProps: PropTypes.shape({
38 | currentUser: PropTypes.string,
39 | doLogin: PropTypes.func.isRequired,
40 | doLogout: PropTypes.func.isRequired,
41 | dimensions: PropTypes.shape({
42 | size: PropTypes.shape({
43 | width: PropTypes.number.isRequired,
44 | height: PropTypes.number.isRequired,
45 | }),
46 | screenFormFactor: PropTypes.string.isRequired,
47 | orientation: PropTypes.string.isRequired,
48 | scale: PropTypes.number.isRequired,
49 | }),
50 | }),
51 | };
52 |
53 | const styles = StyleSheet.create({
54 | container: {
55 | flex: 1,
56 | backgroundColor: '#fff',
57 | },
58 | contentContainer: {
59 | paddingTop: 30,
60 | },
61 | getStartedContainer: {
62 | alignItems: 'center',
63 | marginHorizontal: 50,
64 | },
65 | getStartedText: {
66 | fontSize: 17,
67 | color: 'rgba(96,100,109, 1)',
68 | lineHeight: 24,
69 | textAlign: 'center',
70 | },
71 | });
72 |
--------------------------------------------------------------------------------
/src/screens/monitorsize/AppHomeScreen.js:
--------------------------------------------------------------------------------
1 | import AppHomeScreen from '../AppHomeScreen';
2 |
3 | export default AppHomeScreen;
4 |
--------------------------------------------------------------------------------
/src/screens/monitorsize/WebHomeScreen.js:
--------------------------------------------------------------------------------
1 | import WebHomeScreen from '../WebHomeScreen';
2 |
3 | export default WebHomeScreen;
4 |
--------------------------------------------------------------------------------
/src/screens/phonesize/AppHomeScreen.js:
--------------------------------------------------------------------------------
1 | import AppHomeScreen from '../AppHomeScreen';
2 |
3 | export default AppHomeScreen;
4 |
--------------------------------------------------------------------------------
/src/screens/phonesize/WebHomeScreen.js:
--------------------------------------------------------------------------------
1 | import WebHomeScreen from '../WebHomeScreen';
2 |
3 | export default WebHomeScreen;
4 |
--------------------------------------------------------------------------------
/src/screens/tabletsize/AppHomeScreen.js:
--------------------------------------------------------------------------------
1 | import AppHomeScreen from '../AppHomeScreen';
2 |
3 | export default AppHomeScreen;
4 |
--------------------------------------------------------------------------------
/src/screens/tabletsize/WebHomeScreen.js:
--------------------------------------------------------------------------------
1 | import WebHomeScreen from '../WebHomeScreen';
2 |
3 | export default WebHomeScreen;
4 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | // web/webpack.config.js
2 |
3 | const path = require('path');
4 | const webpack = require('webpack');
5 |
6 | const appDirectory = path.resolve(__dirname, './');
7 |
8 | // Many OSS React Native packages are not compiled to ES5 before being
9 | // published. If you depend on uncompiled packages they may cause webpack build
10 | // errors. To fix this webpack can be configured to compile to the necessary
11 | // 'node_module'.
12 | const babelLoaderConfiguration = {
13 | test: /\.js$/,
14 | // Add every directory that needs to be compiled by Babel during the build.
15 | include: [
16 | path.resolve(appDirectory, 'src'),
17 | path.resolve(appDirectory, 'node_modules/react-navigation'),
18 | path.resolve(appDirectory, 'node_modules/react-native-'),
19 | path.resolve(appDirectory, 'node_modules/@expo/samples'),
20 | path.resolve(appDirectory, 'node_modules/@expo/vector-icons'),
21 | ],
22 | use: {
23 | loader: 'babel-loader',
24 | options: {
25 | // cacheDirectory: false,
26 | babelrc: false,
27 | // Babel configuration (or use .babelrc)
28 | // This aliases 'react-native' to 'react-native-web' and includes only
29 | // the modules needed by the app.
30 | plugins: [
31 | 'expo-web',
32 | 'react-native-web',
33 | 'transform-decorators-legacy',
34 | [
35 | 'transform-runtime',
36 | { helpers: false, polyfill: false, regenerator: true },
37 | ],
38 | ],
39 | // The 'react-native' preset is recommended to match React Native's packager
40 | presets: ['react-native'],
41 | },
42 | },
43 | };
44 |
45 | // This is needed for loading css
46 | const cssLoaderConfiguration = {
47 | test: /\.css$/,
48 | use: ['style-loader', 'css-loader'],
49 | };
50 |
51 | const imageLoaderConfiguration = {
52 | test: /\.(gif|jpe?g|png|svg)$/,
53 | use: {
54 | loader: 'file-loader',
55 | options: {
56 | name: '[name].[ext]',
57 | },
58 | },
59 | };
60 |
61 | const ttfLoaderConfiguration = {
62 | test: /\.ttf$/,
63 | use: [
64 | {
65 | loader: 'file-loader',
66 | options: {
67 | name: './fonts/[hash].[ext]',
68 | },
69 | },
70 | ],
71 | include: [
72 | path.resolve(appDirectory, './src/assets/fonts'),
73 | path.resolve(appDirectory, 'node_modules/react-native-vector-icons'),
74 | path.resolve(appDirectory, 'node_modules/@expo/vector-icons/fonts'),
75 | path.resolve(
76 | appDirectory,
77 | 'node_modules/expo-web/node_modules/react-native-vector-icons/Fonts',
78 | ),
79 | path.resolve(
80 | appDirectory,
81 | 'node_modules/expo/node_modules/@expo/vector-icons/fonts',
82 | ),
83 | ],
84 | };
85 |
86 | module.exports = {
87 | // your web-specific entry file
88 | entry: path.resolve(appDirectory, 'src/index.js'),
89 | devtool: 'cheap-module-source-map',
90 |
91 | // configures where the build ends up
92 | output: {
93 | filename: 'bundle.js',
94 | publicPath: '/assets/',
95 | path: path.resolve(appDirectory, './public/assets'),
96 | },
97 |
98 | module: {
99 | rules: [
100 | babelLoaderConfiguration,
101 | cssLoaderConfiguration,
102 | imageLoaderConfiguration,
103 | ttfLoaderConfiguration,
104 | ],
105 | },
106 |
107 | plugins: [
108 | // process.env.NODE_ENV === 'production' must be true for production
109 | // builds to eliminate development checks and reduce build size. You may
110 | // wish to include additional optimizations.
111 | new webpack.DefinePlugin({
112 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
113 | __DEV__: process.env.NODE_ENV === 'production' || true,
114 | }),
115 | ],
116 |
117 | resolve: {
118 | // If you're working on a multi-platform React Native app, web-specific
119 | // module implementations should be written in files using the extension
120 | // '.web.js'.
121 | symlinks: false,
122 | extensions: ['.web.js', '.js'],
123 | alias: {
124 | './assets/images/expo-icon.png': './assets/images/expo-icon@2x.png',
125 | './assets/images/slack-icon.png': './assets/images/slack-icon@2x.png',
126 | '@expo/vector-icons': 'expo-web',
127 | expo: 'expo-web',
128 | 'react-native': 'react-native-web',
129 | 'react-native-svg': 'react-native-svg-web',
130 | },
131 | },
132 | };
133 |
--------------------------------------------------------------------------------