├── .eslintrc ├── .gitignore ├── .prettierignore ├── .watchmanconfig ├── App.js ├── README.md ├── app.json ├── babel.config.js ├── package.json ├── screenshots ├── multi-screens_0.0.2.gif └── screenshare-6.png ├── src ├── assets │ ├── animation │ │ └── diamond.json │ ├── fonts │ │ └── pacifico.ttf │ ├── icon.png │ ├── images │ │ ├── rabbit-dev.png │ │ └── rabbit-prod.png │ └── splash.png ├── components │ ├── NavigationBack.js │ └── Touch.js ├── constants │ ├── colors.js │ ├── device.js │ ├── fonts.js │ ├── functions.js │ ├── globalStyles.js │ ├── index.js │ ├── preloadFonts.js │ └── preloadImages.js ├── icons │ ├── Svg.CircleLeft.js │ ├── Svg.Cog.js │ ├── Svg.Home.js │ ├── Svg.Pages.js │ └── Svg.Stats.js ├── navigation │ ├── RootStack.js │ ├── StackHome.js │ ├── StackMulti.js │ ├── StackSettings.js │ ├── StackStats.js │ └── TabNavigation.js └── screens │ ├── Home.js │ ├── MultiBase.js │ ├── MultiLevel2.js │ ├── Settings.js │ └── Stats.js ├── web ├── _index.html └── favicon.ico └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier"], 3 | "parser": "babel-eslint", 4 | "plugins": ["prettier", "eslint-plugin-import-helpers"], 5 | "rules": { 6 | "prettier/prettier": ["error"], 7 | "no-use-before-define": 0, 8 | "react/forbid-prop-types": 0, 9 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 10 | "react/jsx-fragments": 0 11 | }, 12 | "globals": { 13 | "__DEV__": "readonly", 14 | "describe": "readonly", 15 | "expect": "readonly", 16 | "it": "readonly", 17 | "jest": "readonly" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | web-build/* 4 | .expo-shared/* 5 | web-build/* 6 | npm-debug.* 7 | *.jks 8 | *.p12 9 | *.key 10 | *.mobileprovision 11 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Appearance, StatusBar } from 'react-native'; 3 | import { ScreenOrientation } from 'expo'; 4 | import AppLoading from 'expo-app-loading'; 5 | import { device, func } from './src/constants'; 6 | 7 | // main navigation stack 8 | import RootStack from './src/navigation/RootStack'; 9 | 10 | class App extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.state = { 15 | isLoading: true, 16 | theme: 'light' 17 | }; 18 | 19 | // is iPad? 20 | if (device.isPad) { 21 | ScreenOrientation.allowAsync( 22 | ScreenOrientation.Orientation.ALL_BUT_UPSIDE_DOWN 23 | ); 24 | } 25 | 26 | this.updateTheme = this.updateTheme.bind(this); 27 | } 28 | 29 | componentDidMount() { 30 | // get system preference 31 | const colorScheme = Appearance.getColorScheme(); 32 | console.log('react-native::Appearance', colorScheme); 33 | 34 | // if light or dark 35 | if (colorScheme !== 'no-preference') { 36 | this.setState({ 37 | theme: colorScheme 38 | }); 39 | } 40 | } 41 | 42 | updateTheme(themeType) { 43 | this.setState({ 44 | theme: themeType 45 | }); 46 | } 47 | 48 | render() { 49 | const { isLoading, theme } = this.state; 50 | const iOSStatusType = theme === 'light' ? 'dark-content' : 'light-content'; 51 | 52 | if (isLoading) { 53 | return ( 54 | { 56 | // console.warn 57 | }} 58 | onFinish={() => this.setState({ isLoading: false })} 59 | startAsync={func.loadAssetsAsync} 60 | /> 61 | ); 62 | } 63 | 64 | return ( 65 | 66 | 67 | 68 | 69 | 70 | ); 71 | } 72 | } 73 | 74 | export default App; 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi Screen Starter with Expo 2 | 3 | web demo: [Expo Multi Screen Starter](https://expo-multi-screen-starter.vercel.app) 4 | 5 | [![follow @calebnance](https://img.shields.io/twitter/follow/calebnance.svg?style=for-the-badge&logo=TWITTER&logoColor=FFFFFF&labelColor=00aced&logoWidth=20&color=lightgray)](https://twitter.com/calebnance) 6 | 7 |

8 | 9 |

10 | 11 | ## Table of Contents 12 | 13 | - [Install & Build](#install--build) 14 | - [Features](#features) 15 | - [Linting](#linting) 16 | - [Assets Used](#assets-used) 17 | - [Device Learnings](#device-learnings) 18 | - [Release Notes](#release-notes) 19 | - [Demo](#demo) 20 | 21 | ## Install & Build 22 | 23 | First, make sure you have Expo CLI installed: `npm install -g expo-cli` 24 | 25 | Install: `yarn` or `yarn install` 26 | 27 | Run Project Locally: `yarn dev` or `yarn start` 28 | 29 | ## Features 30 | 31 | - Expo SDK 42 32 | - React Navigation v6 33 | - Preloading/caching local assets 34 | - SVG custom icon usage 35 | - Checker for the iOS notch: iPhoneX, iPhoneXs, iPhoneXr, iPhoneXs Max 36 | 37 | ## Linting 38 | 39 | - run: `yarn lint` for a list of linting warnings/error in cli 40 | - prettier and airbnb config 41 | - make sure you have [prettier package](https://atom.io/packages/prettier-atom) installed on your atom/vscode editor 42 | - then make sure to enable these options (packages → prettier): 43 | - eslint integration 44 | - stylelint integration 45 | - automatic format on save (toggle format on save) 46 | - be aware of the `.prettierignore` file 47 | 48 | ## Assets Used 49 | 50 | - [black rabbit icon](https://thenounproject.com/search/?q=rabbit&i=1211060) was bought (royalty-free license) 51 | - SVG Icons from [icomoon](https://icomoon.io) 52 | 53 | ## Device Learnings 54 | 55 | **ios:** the notch on iPhoneX, iPhoneXs, iPhoneXr, iPhoneXs Max is **30px** from top 56 | 57 | ## Release Notes 58 | 59 | ### version: 0.0.2 (current) 60 | 61 | - upgraded to [React Navigation v6](https://reactnavigation.org/docs/getting-started) 62 | - upgraded to [React Navigation v5](https://reactnavigation.org/docs/5.x/getting-started) 63 | - upgraded to [Expo SDK 42](https://blog.expo.io/expo-sdk-42-579aee2348b6) 64 | - upgraded to [Expo SDK 41](https://blog.expo.io/expo-sdk-41-12cc5232f2ef) 65 | - upgraded to [Expo SDK 40](https://blog.expo.io/expo-sdk-40-is-now-available-d4d73e67da33) 66 | - upgraded to [Expo SDK 39](https://blog.expo.io/expo-sdk-39-is-now-available-4c10aa825e3f) 67 | - upgraded to [Expo SDK 38](https://blog.expo.io/expo-sdk-38-is-now-available-ab6cd30ca2ee) 68 | - upgraded to [Expo SDK 37](https://blog.expo.io/expo-sdk-37-is-now-available-dd5770f066a6) 69 | - upgraded to [Expo SDK 36](https://blog.expo.io/expo-sdk-36-is-now-available-b91897b437fe) 70 | - upgraded to [Expo SDK 35](https://blog.expo.io/expo-sdk-35-is-now-available-beee0dfafbf4) 71 | - upgraded to [React Navigation v4](https://reactnavigation.org/docs/4.x/getting-started) 72 | - upgraded to [Expo SDK 34](https://blog.expo.io/expo-sdk-34-is-now-available-4f7825239319) 73 | - React Navigation Theme Support / Example Usage ([themes docs](https://reactnavigation.org/docs/4.x/themes)) 74 | - upgraded to [React Navigation v3](https://reactnavigation.org/docs/3.x/getting-started) 75 | 76 | ### version: 0.0.1 77 | 78 | this starter for a multi screens/stack app is at a good state 79 | 80 | - started with [React Navigation v2](https://reactnavigation.org/docs/2.x/getting-started) 81 | - preloading/caching local images (with splash loading screen) 82 | - utility checker for the notch: iPhoneX, iPhoneXs, iPhoneXr, iPhoneXs Max 83 | - simple jest tests 84 | - svg usage 85 | 86 | ## Demo 87 | 88 |

89 | 90 |

91 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Multi-screen Starter", 4 | "description": "react native multi screen application starter with expo", 5 | "scheme": "expomultiscreenstarter", 6 | "slug": "expomultiscreenstarter", 7 | "privacy": "public", 8 | "version": "0.0.2", 9 | "platforms": ["android", "ios", "web"], 10 | "githubUrl": "https://github.com/calebnance/expo-multi-screen-starter", 11 | "orientation": "portrait", 12 | "primaryColor": "#2a2a2a", 13 | "icon": "src/assets/icon.png", 14 | "splash": { 15 | "backgroundColor": "#2a2a2a", 16 | "image": "src/assets/splash.png", 17 | "resizeMode": "contain" 18 | }, 19 | "updates": { 20 | "fallbackToCacheTimeout": 0 21 | }, 22 | "assetBundlePatterns": ["src/assets/**/*"], 23 | "android": { 24 | "versionCode": 2 25 | }, 26 | "ios": { 27 | "buildNumber": "0.0.2", 28 | "supportsTablet": true 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | 4 | return { 5 | presets: ['babel-preset-expo'] 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expo-multi-screen-starter", 3 | "version": "0.0.2", 4 | "description": "react native multi screen application starter with expo", 5 | "author": "Caleb Nance", 6 | "license": "MIT", 7 | "keywords": [ 8 | "expo", 9 | "react native", 10 | "react navigation" 11 | ], 12 | "main": "node_modules/expo/AppEntry.js", 13 | "scripts": { 14 | "dev": "expo start", 15 | "start": "expo start", 16 | "lint": "eslint ./src", 17 | "android": "expo start --android", 18 | "ios": "expo start --ios", 19 | "web": "expo start --web", 20 | "web-build": "expo build:web", 21 | "eject": "expo eject" 22 | }, 23 | "dependencies": { 24 | "@react-native-community/masked-view": "0.1.10", 25 | "@react-navigation/bottom-tabs": "^6.0.5", 26 | "@react-navigation/native": "^6.0.2", 27 | "@react-navigation/stack": "^6.0.7", 28 | "expo": "^42.0.0", 29 | "expo-app-loading": "^1.0.1", 30 | "expo-asset": "~8.3.3", 31 | "expo-font": "~9.2.1", 32 | "prop-types": "^15.7.2", 33 | "react": "16.13.1", 34 | "react-dom": "16.13.1", 35 | "react-native": "https://github.com/expo/react-native/archive/sdk-42.0.0.tar.gz", 36 | "react-native-appearance": "~0.3.3", 37 | "react-native-gesture-handler": "^1.10.3", 38 | "react-native-reanimated": "^2.2.0", 39 | "react-native-safe-area-context": "3.2.0", 40 | "react-native-screens": "~3.4.0", 41 | "react-native-svg": "12.1.1", 42 | "react-native-web": "~0.13.12" 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "~7.9.0", 46 | "babel-eslint": "^10.1.0", 47 | "babel-preset-expo": "8.3.0", 48 | "eslint": "^7.8.1", 49 | "eslint-config-airbnb": "^18.2.0", 50 | "eslint-config-prettier": "^6.11.0", 51 | "eslint-plugin-import": "^2.22.0", 52 | "eslint-plugin-import-helpers": "^1.1.0", 53 | "eslint-plugin-jsx-a11y": "^6.3.1", 54 | "eslint-plugin-prettier": "^3.1.4", 55 | "eslint-plugin-react": "^7.20.6", 56 | "eslint-plugin-react-hooks": "^4.2.0", 57 | "prettier": "^2.1.1" 58 | }, 59 | "prettier": { 60 | "singleQuote": true, 61 | "trailingComma": "none" 62 | }, 63 | "eslintIgnore": [ 64 | "babel.config.js" 65 | ], 66 | "private": true 67 | } 68 | -------------------------------------------------------------------------------- /screenshots/multi-screens_0.0.2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-multi-screen-starter/9768f2674da05166bc97dddfc42a1b5ae5909b9d/screenshots/multi-screens_0.0.2.gif -------------------------------------------------------------------------------- /screenshots/screenshare-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-multi-screen-starter/9768f2674da05166bc97dddfc42a1b5ae5909b9d/screenshots/screenshare-6.png -------------------------------------------------------------------------------- /src/assets/animation/diamond.json: -------------------------------------------------------------------------------- 1 | {"ip":0,"fr":60,"v":"5.1.20","assets":[],"layers":[{"ty":4,"nm":"diamond icon","ip":0,"st":0,"ind":1,"hix":1,"ks":{"o":{"a":1,"k":[{"t":0,"s":[100],"e":[100],"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"t":100,"s":[100],"e":[0],"i":{"x":[0.515],"y":[0.955]},"o":{"x":[0.455],"y":[0.03]}},{"t":120}]},"or":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[24.5,19.5,0]},"p":{"s":true,"x":{"a":0,"k":200},"y":{"a":0,"k":200}},"rx":{"a":0,"k":0},"ry":{"a":0,"k":0},"rz":{"a":0,"k":0},"s":{"a":1,"k":[{"t":0,"s":[800,800],"e":[200,200],"i":{"x":[0.515,0.515],"y":[0.955,0.955]},"o":{"x":[0.455,0.455],"y":[0.03,0.03]}},{"t":120}]}},"shapes":[{"ty":"gr","nm":"diamond icon shape group","it":[{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[48.7407915,14.92],[41.0040915,4.52],[39.9724915,4],[9.02549149,4],[7.99399149,4.52],[0.257191488,14.92],[0.329391488,16.5684],[23.5396915,42.5684],[24.4989915,43],[25.4583915,42.5684],[48.6685915,16.5684]],"i":[[0.3714000000000013,0.4966000000000008],[0,0],[0.4049000000000049,0],[0,0],[0.24240000000000084,-0.32759999999999945],[0,0],[-0.4100000002,-0.4602000000000004],[0,0],[-0.3661999999999992,0],[-0.245000000000001,0.2756000000000043],[0,0]],"o":[[0,0],[-0.2424000000000035,-0.32759999999999945],[0,0],[-0.4047999999999998,0],[0,0],[-0.3687,0.4966000000000008],[0,0],[0.245000000000001,0.27300000000000324],[0.36620000000000275,0],[0,0],[0.41270000000000095,-0.46280000000000143]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[30.5027915,17],[24.4989915,37.176],[18.4952915,17]],"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[19.3411915,14.4],[24.4989915,7.4658],[29.6568915,14.4]],"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[27.0779915,6.6],[37.3935915,6.6],[32.2357915,13.5342]],"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[16.7622915,13.5342],[11.6044915,6.6],[21.9200915,6.6]],"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[14.1833915,14.4],[3.86769149,14.4],[9.02549149,7.4658]],"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[15.8028915,17],[21.6131915,36.5234],[4.18489149,17]],"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[33.1950915,17],[44.8130915,17],[27.3847915,36.5234]],"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]]}}},{"ty":"sh","ks":{"a":0,"k":{"c":true,"v":[[34.8146915,14.4],[39.9724915,7.4658],[45.1303915,14.4]],"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]]}}},{"ty":"st","o":{"a":0,"k":0},"w":{"a":0,"k":0},"c":{"a":0,"k":[0,0,0,0]},"lc":3,"lj":1},{"ty":"fl","o":{"a":0,"k":100},"r":2,"c":{"a":0,"k":[0,0,0,1]}},{"ty":"tr","o":{"a":0,"k":100},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"p":{"a":0,"k":[0,-4]},"r":{"a":0,"k":0}}]}],"op":120}],"op":120,"w":400,"h":400} -------------------------------------------------------------------------------- /src/assets/fonts/pacifico.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-multi-screen-starter/9768f2674da05166bc97dddfc42a1b5ae5909b9d/src/assets/fonts/pacifico.ttf -------------------------------------------------------------------------------- /src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-multi-screen-starter/9768f2674da05166bc97dddfc42a1b5ae5909b9d/src/assets/icon.png -------------------------------------------------------------------------------- /src/assets/images/rabbit-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-multi-screen-starter/9768f2674da05166bc97dddfc42a1b5ae5909b9d/src/assets/images/rabbit-dev.png -------------------------------------------------------------------------------- /src/assets/images/rabbit-prod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-multi-screen-starter/9768f2674da05166bc97dddfc42a1b5ae5909b9d/src/assets/images/rabbit-prod.png -------------------------------------------------------------------------------- /src/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-multi-screen-starter/9768f2674da05166bc97dddfc42a1b5ae5909b9d/src/assets/splash.png -------------------------------------------------------------------------------- /src/components/NavigationBack.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { StyleSheet, TouchableOpacity } from 'react-native'; 3 | import { useNavigation } from '@react-navigation/native'; 4 | import { gStyle } from '../constants'; 5 | 6 | // icons 7 | import SvgCircleLeft from '../icons/Svg.CircleLeft'; 8 | 9 | const NavigationBack = () => { 10 | const navigation = useNavigation(); 11 | 12 | return ( 13 | navigation.goBack(null)} 16 | style={styles.container} 17 | > 18 | 19 | 20 | ); 21 | }; 22 | 23 | const styles = StyleSheet.create({ 24 | container: { 25 | marginLeft: 16 26 | } 27 | }); 28 | 29 | export default NavigationBack; 30 | -------------------------------------------------------------------------------- /src/components/Touch.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Text, TouchableOpacity } from 'react-native'; 4 | import { gStyle } from '../constants'; 5 | 6 | const Touch = ({ accessible, onPress, style, text, textStyle }) => ( 7 | 13 | {text} 14 | 15 | ); 16 | 17 | Touch.defaultProps = { 18 | accessible: true, 19 | style: gStyle.btn, 20 | textStyle: gStyle.btnText 21 | }; 22 | 23 | Touch.propTypes = { 24 | // required 25 | onPress: PropTypes.func.isRequired, 26 | text: PropTypes.string.isRequired, 27 | 28 | // optional 29 | accessible: PropTypes.bool, 30 | style: PropTypes.oneOfType([ 31 | PropTypes.array, 32 | PropTypes.number, 33 | PropTypes.object 34 | ]), 35 | textStyle: PropTypes.object 36 | }; 37 | 38 | export default Touch; 39 | -------------------------------------------------------------------------------- /src/constants/colors.js: -------------------------------------------------------------------------------- 1 | // colors 2 | // ///////////////////////////////////////////////////////////////////////////// 3 | const darkColor = '#2a2a2a'; 4 | const darkHighlightColor = '#3a3a3a'; 5 | const grey = '#d0ccd0'; 6 | const white20 = 'rgba(255, 255, 255, 0.2)'; 7 | 8 | export default { 9 | black: '#000000', 10 | white: '#ffffff', 11 | white20, 12 | 13 | darkColor, 14 | darkHighlightColor, 15 | grey, 16 | 17 | activeTintColor: { 18 | light: darkColor, 19 | dark: grey 20 | }, 21 | inactiveTintColor: { 22 | light: grey, 23 | dark: white20 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/constants/device.js: -------------------------------------------------------------------------------- 1 | import { Dimensions, Platform } from 'react-native'; 2 | 3 | // android 4 | const android = Platform.OS === 'android'; 5 | 6 | const iOS = Platform.OS === 'ios'; 7 | const web = Platform.OS === 'web'; 8 | const windowInfo = Dimensions.get('window'); 9 | const { height, width } = windowInfo; 10 | const aspectRatio = height / width; 11 | 12 | // is iPad 13 | const { isPad } = Platform; 14 | 15 | // is iPhone with Notch? 16 | // iPhoneX, iPhoneXs, iPhoneXr, iPhoneXs Max, iPhone 11 & 12 17 | let iPhoneNotch = false; 18 | if (iOS) { 19 | // iphone screen breakdown 20 | // https://blog.calebnance.com/development/iphone-ipad-pixel-sizes-guide-complete-list.html 21 | if (height === 812 || height === 844 || height === 896 || height === 926) { 22 | iPhoneNotch = true; 23 | } 24 | } 25 | 26 | export default { 27 | android, 28 | aspectRatio, 29 | height, 30 | iOS, 31 | iPhoneNotch, 32 | isPad, 33 | web, 34 | width 35 | }; 36 | -------------------------------------------------------------------------------- /src/constants/fonts.js: -------------------------------------------------------------------------------- 1 | // fonts 2 | // ///////////////////////////////////////////////////////////////////////////// 3 | export default { 4 | pacifico: 'pacifico' 5 | }; 6 | -------------------------------------------------------------------------------- /src/constants/functions.js: -------------------------------------------------------------------------------- 1 | import { Image } from 'react-native'; 2 | import { Asset } from 'expo-asset'; 3 | import * as Font from 'expo-font'; 4 | 5 | import preloadFonts from './preloadFonts'; 6 | import preloadImages from './preloadImages'; 7 | 8 | // cache fonts 9 | // ///////////////////////////////////////////////////////////////////////////// 10 | const cacheFonts = (fonts) => fonts.map((font) => Font.loadAsync(font)); 11 | 12 | // cache images 13 | // ///////////////////////////////////////////////////////////////////////////// 14 | const cacheImages = (images) => { 15 | return Object.values(images).map((image) => { 16 | if (typeof image === 'string') { 17 | return Image.prefetch(image); 18 | } 19 | 20 | return Asset.fromModule(image).downloadAsync(); 21 | }); 22 | }; 23 | 24 | // preload async 25 | // ///////////////////////////////////////////////////////////////////////////// 26 | const loadAssetsAsync = async () => { 27 | // preload assets 28 | const fontAssets = cacheFonts(preloadFonts); 29 | const imageAssets = cacheImages(preloadImages); 30 | 31 | // promise load all 32 | return Promise.all([...fontAssets, ...imageAssets]); 33 | }; 34 | 35 | export default { 36 | cacheFonts, 37 | cacheImages, 38 | loadAssetsAsync 39 | }; 40 | -------------------------------------------------------------------------------- /src/constants/globalStyles.js: -------------------------------------------------------------------------------- 1 | import colors from './colors'; 2 | import fonts from './fonts'; 3 | 4 | // utility styles 5 | // ///////////////////////////////////////////////////////////////////////////// 6 | export default { 7 | activeOpacity: 0.7, 8 | 9 | // containers 10 | // /////////////////////////////////////////////////////////////////////////// 11 | container: { 12 | dark: { 13 | backgroundColor: colors.darkHighlightColor, 14 | flex: 1 15 | }, 16 | light: { 17 | backgroundColor: colors.white, 18 | flex: 1 19 | } 20 | }, 21 | contentContainer: { 22 | alignItems: 'center', 23 | paddingTop: 32 24 | }, 25 | 26 | // navigation styles 27 | // /////////////////////////////////////////////////////////////////////////// 28 | headerTitleStyle: { 29 | textAlign: 'center' 30 | }, 31 | headerBaseEnds: { 32 | flex: 1, 33 | justifyContent: 'center' 34 | }, 35 | 36 | // button 37 | // /////////////////////////////////////////////////////////////////////////// 38 | btn: { 39 | alignItems: 'center', 40 | backgroundColor: colors.darkColor, 41 | borderColor: colors.darkColor, 42 | borderWidth: 1, 43 | borderRadius: 4, 44 | height: 48, 45 | justifyContent: 'center', 46 | marginBottom: 16, 47 | paddingHorizontal: 24, 48 | paddingVertical: 8 49 | }, 50 | btnText: { 51 | color: colors.white, 52 | textAlign: 'center' 53 | }, 54 | 55 | // text 56 | // /////////////////////////////////////////////////////////////////////////// 57 | text: { 58 | dark: { 59 | color: colors.white 60 | }, 61 | light: { 62 | color: colors.darkColor 63 | } 64 | }, 65 | textPacifico: { 66 | fontFamily: fonts.pacifico, 67 | fontSize: 20 68 | }, 69 | 70 | // spacers 71 | // /////////////////////////////////////////////////////////////////////////// 72 | spacer16: { 73 | height: 16, 74 | width: '100%' 75 | }, 76 | spacer64: { 77 | height: 64, 78 | width: '100%' 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | import colors from './colors'; 2 | import device from './device'; 3 | import fonts from './fonts'; 4 | import func from './functions'; 5 | import gStyle from './globalStyles'; 6 | import images from './preloadImages'; 7 | 8 | export { colors, device, fonts, func, gStyle, images }; 9 | -------------------------------------------------------------------------------- /src/constants/preloadFonts.js: -------------------------------------------------------------------------------- 1 | // app fonts 2 | // ///////////////////////////////////////////////////////////////////////////// 3 | 4 | const pacifico = require('../assets/fonts/pacifico.ttf'); 5 | 6 | export default [ 7 | { 8 | pacifico 9 | } 10 | ]; 11 | -------------------------------------------------------------------------------- /src/constants/preloadImages.js: -------------------------------------------------------------------------------- 1 | // app images 2 | // ///////////////////////////////////////////////////////////////////////////// 3 | 4 | const rabbitDev = require('../assets/images/rabbit-dev.png'); 5 | const rabbitProd = require('../assets/images/rabbit-prod.png'); 6 | 7 | export default { 8 | rabbitDev, 9 | rabbitProd 10 | }; 11 | -------------------------------------------------------------------------------- /src/icons/Svg.CircleLeft.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Svg, { Path } from 'react-native-svg'; 4 | import { colors } from '../constants'; 5 | 6 | const SvgCircleLeft = ({ active, size }) => { 7 | const theme = 'dark'; 8 | const fill = active 9 | ? colors.activeTintColor[theme] 10 | : colors.inactiveTintColor[theme]; 11 | 12 | return ( 13 | 14 | 18 | 22 | 23 | ); 24 | }; 25 | 26 | SvgCircleLeft.defaultProps = { 27 | active: false, 28 | size: 24 29 | }; 30 | 31 | SvgCircleLeft.propTypes = { 32 | // optional 33 | active: PropTypes.bool, 34 | size: PropTypes.number 35 | }; 36 | 37 | export default SvgCircleLeft; 38 | -------------------------------------------------------------------------------- /src/icons/Svg.Cog.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Svg, { Path } from 'react-native-svg'; 4 | import { colors } from '../constants'; 5 | 6 | const SvgCog = ({ active, size }) => { 7 | const theme = 'dark'; 8 | const fill = active 9 | ? colors.activeTintColor[theme] 10 | : colors.inactiveTintColor[theme]; 11 | 12 | return ( 13 | 14 | 18 | 19 | ); 20 | }; 21 | 22 | SvgCog.defaultProps = { 23 | active: false, 24 | size: 20 25 | }; 26 | 27 | SvgCog.propTypes = { 28 | // optional 29 | active: PropTypes.bool, 30 | size: PropTypes.number 31 | }; 32 | 33 | export default SvgCog; 34 | -------------------------------------------------------------------------------- /src/icons/Svg.Home.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Svg, { Path } from 'react-native-svg'; 4 | import { colors } from '../constants'; 5 | 6 | const SvgHome = ({ active, size }) => { 7 | const theme = 'dark'; 8 | const fill = active 9 | ? colors.activeTintColor[theme] 10 | : colors.inactiveTintColor[theme]; 11 | 12 | return ( 13 | 14 | 18 | 19 | ); 20 | }; 21 | 22 | SvgHome.defaultProps = { 23 | active: false, 24 | size: 20 25 | }; 26 | 27 | SvgHome.propTypes = { 28 | // optional 29 | active: PropTypes.bool, 30 | size: PropTypes.number 31 | }; 32 | 33 | export default SvgHome; 34 | -------------------------------------------------------------------------------- /src/icons/Svg.Pages.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Svg, { Path } from 'react-native-svg'; 4 | import { colors } from '../constants'; 5 | 6 | const SvgPages = ({ active, size }) => { 7 | const theme = 'dark'; 8 | const fill = active 9 | ? colors.activeTintColor[theme] 10 | : colors.inactiveTintColor[theme]; 11 | 12 | return ( 13 | 14 | 18 | 19 | ); 20 | }; 21 | 22 | SvgPages.defaultProps = { 23 | active: false, 24 | size: 20 25 | }; 26 | 27 | SvgPages.propTypes = { 28 | // optional 29 | active: PropTypes.bool, 30 | size: PropTypes.number 31 | }; 32 | 33 | export default SvgPages; 34 | -------------------------------------------------------------------------------- /src/icons/Svg.Stats.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Svg, { Path } from 'react-native-svg'; 4 | import { colors } from '../constants'; 5 | 6 | const SvgStats = ({ active, size }) => { 7 | const theme = 'dark'; 8 | const fill = active 9 | ? colors.activeTintColor[theme] 10 | : colors.inactiveTintColor[theme]; 11 | 12 | return ( 13 | 14 | 18 | 19 | ); 20 | }; 21 | 22 | SvgStats.defaultProps = { 23 | active: false, 24 | size: 20 25 | }; 26 | 27 | SvgStats.propTypes = { 28 | // optional 29 | active: PropTypes.bool, 30 | size: PropTypes.number 31 | }; 32 | 33 | export default SvgStats; 34 | -------------------------------------------------------------------------------- /src/navigation/RootStack.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { NavigationContainer } from '@react-navigation/native'; 3 | import { 4 | CardStyleInterpolators, 5 | createStackNavigator 6 | } from '@react-navigation/stack'; 7 | 8 | // tab naviation 9 | import TabNavigation from './TabNavigation'; 10 | 11 | const Stack = createStackNavigator(); 12 | 13 | export default () => ( 14 | 15 | 21 | 28 | 29 | 30 | ); 31 | -------------------------------------------------------------------------------- /src/navigation/StackHome.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { createStackNavigator } from '@react-navigation/stack'; 3 | 4 | // screens 5 | import HomeScreen from '../screens/Home'; 6 | 7 | const Stack = createStackNavigator(); 8 | 9 | export default () => ( 10 | 11 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /src/navigation/StackMulti.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { createStackNavigator } from '@react-navigation/stack'; 3 | 4 | // screens 5 | import MultiBaseScreen from '../screens/MultiBase'; 6 | import MultiLevel2Screen from '../screens/MultiLevel2'; 7 | 8 | // components 9 | import NavigationBack from '../components/NavigationBack'; 10 | 11 | const Stack = createStackNavigator(); 12 | 13 | export default () => ( 14 | 15 | 24 | , 29 | headerTintColor: '#432818', 30 | headerStyle: { backgroundColor: '#bb9457' }, 31 | title: 'Multi Level 2' 32 | }} 33 | /> 34 | 35 | ); 36 | -------------------------------------------------------------------------------- /src/navigation/StackSettings.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { createStackNavigator } from '@react-navigation/stack'; 3 | 4 | // screens 5 | import SettingsScreen from '../screens/Settings'; 6 | 7 | const Stack = createStackNavigator(); 8 | 9 | export default () => ( 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/navigation/StackStats.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { createStackNavigator } from '@react-navigation/stack'; 3 | 4 | // screens 5 | import StatsScreen from '../screens/Stats'; 6 | 7 | const Stack = createStackNavigator(); 8 | 9 | export default () => ( 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/navigation/TabNavigation.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Appearance } from 'react-native-appearance'; 3 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; 4 | import { colors } from '../constants'; 5 | 6 | // grabs stacks 7 | import StackHome from './StackHome'; 8 | import StackMulti from './StackMulti'; 9 | import StackSettings from './StackSettings'; 10 | import StackStats from './StackStats'; 11 | 12 | // icons 13 | import SvgCog from '../icons/Svg.Cog'; 14 | import SvgHome from '../icons/Svg.Home'; 15 | import SvgPages from '../icons/Svg.Pages'; 16 | import SvgStats from '../icons/Svg.Stats'; 17 | 18 | const Tab = createBottomTabNavigator(); 19 | 20 | // activeTintColor: { 21 | // light: colors.darkColor, 22 | // dark: colors.grey 23 | // }, 24 | // inactiveTintColor: { 25 | // light: colors.grey, 26 | // dark: colors.white20 27 | // } 28 | 29 | export default () => { 30 | // get system preference 31 | const colorScheme = Appearance.getColorScheme(); 32 | console.log('react-native-appearance::Appearance', colorScheme); 33 | 34 | return ( 35 | 50 | , 55 | tabBarLabel: 'Home' 56 | }} 57 | /> 58 | , 63 | tabBarLabel: 'Multi' 64 | }} 65 | /> 66 | , 71 | tabBarLabel: 'Stats' 72 | }} 73 | /> 74 | , 79 | tabBarLabel: 'Settings' 80 | }} 81 | /> 82 | 83 | ); 84 | }; 85 | -------------------------------------------------------------------------------- /src/screens/Home.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { ScrollView, Text, View } from 'react-native'; 4 | import { gStyle } from '../constants'; 5 | 6 | // components 7 | import Touch from '../components/Touch'; 8 | 9 | const Home = ({ navigation }) => { 10 | const theme = 'dark'; 11 | 12 | return ( 13 | 14 | 15 | Home content area 16 | 17 | 18 | 19 | navigation.navigate('StackMulti')} 21 | text="Jump to Multi tab" 22 | /> 23 | 24 | null} text="Light theme" /> 25 | null} text="Dark theme" /> 26 | 27 | 28 | ); 29 | }; 30 | 31 | Home.propTypes = { 32 | // required 33 | navigation: PropTypes.object.isRequired 34 | }; 35 | 36 | export default Home; 37 | -------------------------------------------------------------------------------- /src/screens/MultiBase.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { ScrollView, Text, View } from 'react-native'; 4 | import { gStyle } from '../constants'; 5 | 6 | // components 7 | import Touch from '../components/Touch'; 8 | 9 | const MultiBase = ({ navigation }) => { 10 | const theme = 'dark'; 11 | 12 | return ( 13 | 17 | Multi screen content area 18 | 19 | 20 | 21 | navigation.navigate('MultiLevel2')} 23 | text="Go to level 2" 24 | /> 25 | 26 | ); 27 | }; 28 | 29 | MultiBase.propTypes = { 30 | // required 31 | navigation: PropTypes.object.isRequired 32 | }; 33 | 34 | export default MultiBase; 35 | -------------------------------------------------------------------------------- /src/screens/MultiLevel2.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ScrollView, Text } from 'react-native'; 3 | import { gStyle } from '../constants'; 4 | 5 | const MultiLevel2 = () => { 6 | const theme = 'dark'; 7 | 8 | return ( 9 | 13 | Multi screen level 2 content area 14 | 15 | ); 16 | }; 17 | 18 | export default MultiLevel2; 19 | -------------------------------------------------------------------------------- /src/screens/Settings.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Image, ScrollView, Text, View } from 'react-native'; 3 | import { gStyle, images } from '../constants'; 4 | 5 | const headerImage = __DEV__ ? 'rabbitDev' : 'rabbitProd'; 6 | 7 | const Settings = () => { 8 | const theme = 'dark'; 9 | 10 | return ( 11 | 15 | Settings content area 16 | 17 | 18 | 19 | 20 | Pacifico font example 21 | 22 | 23 | ); 24 | }; 25 | 26 | Settings.navigationOptions = ({ theme }) => { 27 | return { 28 | headerLeft: () => ( 29 | 30 | left 31 | 32 | ), 33 | headerRight: () => ( 34 | 35 | right 36 | 37 | ), 38 | headerTitle: () => ( 39 | 40 | 44 | 45 | ) 46 | }; 47 | }; 48 | 49 | /* 50 | // shoutout @notbrent: https://snack.expo.io/H105kxsG7 51 | const shouldShowBackButton = stackRouteNavigation => { 52 | const parent = stackRouteNavigation.dangerouslyGetParent(); 53 | return parent.state.routes.indexOf(stackRouteNavigation.state) > 0; 54 | }; 55 | 56 | SettingsScreen.navigationOptions = ({ navigation }) => ({ 57 | 58 | headerLeft: !shouldShowBackButton(navigation) ? ( 59 | 60 | left 61 | 62 | ) : null, 63 | */ 64 | 65 | export default Settings; 66 | -------------------------------------------------------------------------------- /src/screens/Stats.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ScrollView, Text } from 'react-native'; 3 | import { gStyle } from '../constants'; 4 | 5 | const Stats = () => { 6 | const theme = 'dark'; 7 | 8 | return ( 9 | 13 | Stats content area 14 | 15 | ); 16 | }; 17 | 18 | Stats.navigationOptions = { 19 | headerTitleStyle: gStyle.headerTitleStyle, 20 | title: 'Stats' 21 | }; 22 | 23 | export default Stats; 24 | -------------------------------------------------------------------------------- /web/_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Multi-screen Starter 7 | 14 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-multi-screen-starter/9768f2674da05166bc97dddfc42a1b5ae5909b9d/web/favicon.ico --------------------------------------------------------------------------------