├── .prettierignore ├── src ├── constants │ ├── preloadFonts.js │ ├── index.js │ ├── fonts.js │ ├── colors.js │ ├── device.js │ ├── functions.js │ ├── globalStyles.js │ └── preloadImages.js ├── assets │ ├── icon.png │ ├── splash.png │ └── images │ │ ├── movies │ │ ├── a.jpg │ │ ├── b.jpg │ │ ├── f.jpg │ │ ├── h.jpg │ │ ├── i.jpg │ │ ├── p.jpg │ │ ├── z.jpg │ │ ├── ae.jpg │ │ ├── aiw.jpg │ │ ├── batb.jpg │ │ ├── cacw.jpg │ │ ├── cm.jpg │ │ ├── fz.jpg │ │ ├── sb.jpg │ │ ├── tlk.jpg │ │ ├── tlm.jpg │ │ ├── tm.jpg │ │ ├── ts.jpg │ │ ├── anhe4.jpg │ │ ├── aotce2.jpg │ │ ├── roasws.jpg │ │ ├── swatsd.jpg │ │ ├── tesbe5.jpg │ │ ├── tfae7.jpg │ │ ├── thond.jpg │ │ ├── tpme1.jpg │ │ └── tsits.jpg │ │ ├── logo │ │ ├── disney.png │ │ ├── marvel.png │ │ ├── pixar.png │ │ ├── star-wars.png │ │ └── national-geographic.png │ │ ├── profiles │ │ ├── elsa.jpg │ │ ├── yoda.jpg │ │ ├── iron-man.jpg │ │ └── stormtrooper.jpg │ │ └── slides │ │ ├── avatar.png │ │ ├── captain-marvel.png │ │ ├── avengers-endgame.png │ │ └── star-wars-mandalorian.png ├── navigation │ ├── defaultOptions.js │ ├── StackSearch.js │ ├── TabNavigator.js │ ├── StackHome.js │ ├── Stack.js │ ├── StackDownloads.js │ └── StackProfile.js ├── mockdata │ ├── hdr.json │ ├── hits.json │ ├── originals.json │ ├── trending.json │ ├── data.js │ ├── recommended.json │ ├── dumbData.json │ └── vault.json ├── screens │ ├── Search.js │ ├── ProfileWatchlist.js │ ├── ModalVideo.js │ ├── ModalWebView.js │ ├── Downloads.js │ ├── Home.js │ ├── Profile.js │ ├── ModalManageProfiles.js │ ├── ModalAddProfile.js │ └── ProfileAppSettings.js └── components │ ├── icons │ ├── Svg.Home.js │ ├── Svg.Plus.js │ ├── Svg.ArrowLeft.js │ ├── Svg.Edit.js │ ├── Svg.ArrowRight.js │ ├── Svg.Background.js │ ├── Svg.Downloads.js │ ├── Svg.CategoryBackground.js │ ├── Svg.Trash.js │ ├── Svg.Search.js │ └── Svg.DisneyPlusLogo.js │ ├── SlideShow.js │ ├── ImageSlide.js │ ├── TouchLineItemElement.js │ ├── MediaItemScroller.js │ ├── TouchLineItem.js │ ├── TouchLineItemApp.js │ ├── Categories.js │ ├── Header.js │ ├── HeaderManage.js │ └── HeaderAccounts.js ├── .gitattributes ├── .gh-assets └── screenshare-4.png ├── .gitignore ├── babel.config.js ├── app.json ├── .eslintrc ├── LICENSE ├── App.js ├── package.json └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /src/constants/preloadFonts.js: -------------------------------------------------------------------------------- 1 | export default [{}]; 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/icon.png -------------------------------------------------------------------------------- /src/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/splash.png -------------------------------------------------------------------------------- /.gh-assets/screenshare-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/.gh-assets/screenshare-4.png -------------------------------------------------------------------------------- /src/assets/images/movies/a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/a.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/b.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/f.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/h.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/h.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/i.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/i.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/p.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/p.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/z.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/z.jpg -------------------------------------------------------------------------------- /src/assets/images/logo/disney.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/logo/disney.png -------------------------------------------------------------------------------- /src/assets/images/logo/marvel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/logo/marvel.png -------------------------------------------------------------------------------- /src/assets/images/logo/pixar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/logo/pixar.png -------------------------------------------------------------------------------- /src/assets/images/movies/ae.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/ae.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/aiw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/aiw.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/batb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/batb.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/cacw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/cacw.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/cm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/cm.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/fz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/fz.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/sb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/sb.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/tlk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/tlk.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/tlm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/tlm.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/tm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/tm.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/ts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/ts.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/anhe4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/anhe4.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/aotce2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/aotce2.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/roasws.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/roasws.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/swatsd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/swatsd.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/tesbe5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/tesbe5.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/tfae7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/tfae7.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/thond.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/thond.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/tpme1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/tpme1.jpg -------------------------------------------------------------------------------- /src/assets/images/movies/tsits.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/movies/tsits.jpg -------------------------------------------------------------------------------- /src/assets/images/profiles/elsa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/profiles/elsa.jpg -------------------------------------------------------------------------------- /src/assets/images/profiles/yoda.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/profiles/yoda.jpg -------------------------------------------------------------------------------- /src/assets/images/slides/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/slides/avatar.png -------------------------------------------------------------------------------- /src/assets/images/logo/star-wars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/logo/star-wars.png -------------------------------------------------------------------------------- /src/assets/images/profiles/iron-man.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/profiles/iron-man.jpg -------------------------------------------------------------------------------- /src/assets/images/profiles/stormtrooper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/profiles/stormtrooper.jpg -------------------------------------------------------------------------------- /src/assets/images/slides/captain-marvel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/slides/captain-marvel.png -------------------------------------------------------------------------------- /src/assets/images/slides/avengers-endgame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/slides/avengers-endgame.png -------------------------------------------------------------------------------- /src/assets/images/logo/national-geographic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/logo/national-geographic.png -------------------------------------------------------------------------------- /src/navigation/defaultOptions.js: -------------------------------------------------------------------------------- 1 | import { gStyle } from '../constants'; 2 | 3 | export default () => ({ 4 | headerStyle: gStyle.navHeaderStyle 5 | }); 6 | -------------------------------------------------------------------------------- /src/assets/images/slides/star-wars-mandalorian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calebnance/expo-disneyplus/HEAD/src/assets/images/slides/star-wars-mandalorian.png -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | 4 | return { 5 | presets: ['babel-preset-expo'], 6 | plugins: ['react-native-reanimated/plugin'] 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /src/mockdata/hdr.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "image": "tfae7" 5 | }, 6 | { 7 | "id": 2, 8 | "image": "roasws" 9 | }, 10 | { 11 | "id": 3, 12 | "image": "ae" 13 | }, 14 | { 15 | "id": 4, 16 | "image": "cm" 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /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/mockdata/hits.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "image": "tlm" 5 | }, 6 | { 7 | "id": 2, 8 | "image": "tlk" 9 | }, 10 | { 11 | "id": 3, 12 | "image": "thond" 13 | }, 14 | { 15 | "id": 4, 16 | "image": "roasws" 17 | }, 18 | { 19 | "id": 5, 20 | "image": "ae" 21 | }, 22 | { 23 | "id": 6, 24 | "image": "fz" 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /src/mockdata/originals.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "image": "tm" 5 | }, 6 | { 7 | "id": 2, 8 | "image": "f" 9 | }, 10 | { 11 | "id": 3, 12 | "image": "tlk" 13 | }, 14 | { 15 | "id": 4, 16 | "image": "sb" 17 | }, 18 | { 19 | "id": 5, 20 | "image": "fz" 21 | }, 22 | { 23 | "id": 6, 24 | "image": "i" 25 | }, 26 | { 27 | "id": 7, 28 | "image": "b" 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /src/mockdata/trending.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "image": "ae" 5 | }, 6 | { 7 | "id": 2, 8 | "image": "tm" 9 | }, 10 | { 11 | "id": 3, 12 | "image": "ts" 13 | }, 14 | { 15 | "id": 4, 16 | "image": "cm" 17 | }, 18 | { 19 | "id": 5, 20 | "image": "tlk" 21 | }, 22 | { 23 | "id": 6, 24 | "image": "z" 25 | }, 26 | { 27 | "id": 7, 28 | "image": "a" 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /src/mockdata/data.js: -------------------------------------------------------------------------------- 1 | import dumbData from './dumbData.json'; 2 | 3 | import hdr from './hdr.json'; 4 | import hits from './hits.json'; 5 | import originals from './originals.json'; 6 | import recommended from './recommended.json'; 7 | import trending from './trending.json'; 8 | import vault from './vault.json'; 9 | 10 | export default { 11 | dumbData, 12 | 13 | hdr, 14 | hits, 15 | originals, 16 | recommended, 17 | trending, 18 | vault 19 | }; 20 | -------------------------------------------------------------------------------- /src/mockdata/recommended.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "image": "ae" 5 | }, 6 | { 7 | "id": 2, 8 | "image": "anhe4" 9 | }, 10 | { 11 | "id": 3, 12 | "image": "cm" 13 | }, 14 | { 15 | "id": 4, 16 | "image": "tpme1" 17 | }, 18 | { 19 | "id": 5, 20 | "image": "tfae7" 21 | }, 22 | { 23 | "id": 6, 24 | "image": "cacw" 25 | }, 26 | { 27 | "id": 7, 28 | "image": "tesbe5" 29 | }, 30 | { 31 | "id": 8, 32 | "image": "aotce2" 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/mockdata/dumbData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "title": "movie 1" 5 | }, 6 | { 7 | "id": 2, 8 | "title": "movie 2" 9 | }, 10 | { 11 | "id": 3, 12 | "title": "movie 3" 13 | }, 14 | { 15 | "id": 4, 16 | "title": "movie 4" 17 | }, 18 | { 19 | "id": 5, 20 | "title": "movie 5" 21 | }, 22 | { 23 | "id": 6, 24 | "title": "movie 6" 25 | }, 26 | { 27 | "id": 7, 28 | "title": "movie 7" 29 | }, 30 | { 31 | "id": 8, 32 | "title": "movie 8" 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /src/screens/Search.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ScrollView, View } from 'react-native'; 3 | import { gStyle } from '../constants'; 4 | 5 | // icons 6 | import SvgBackground from '../components/icons/Svg.Background'; 7 | 8 | const Search = () => ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | 20 | export default Search; 21 | -------------------------------------------------------------------------------- /src/screens/ProfileWatchlist.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { View } from 'react-native'; 3 | import { gStyle } from '../constants'; 4 | 5 | // components 6 | import Header from '../components/Header'; 7 | 8 | // icons 9 | import SvgBackground from '../components/icons/Svg.Background'; 10 | 11 | const ProfileWatchlist = () => ( 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | ); 20 | 21 | export default ProfileWatchlist; 22 | -------------------------------------------------------------------------------- /src/mockdata/vault.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "image": "a" 5 | }, 6 | { 7 | "id": 2, 8 | "image": "aiw" 9 | }, 10 | { 11 | "id": 3, 12 | "image": "b" 13 | }, 14 | { 15 | "id": 4, 16 | "image": "batb" 17 | }, 18 | { 19 | "id": 5, 20 | "image": "h" 21 | }, 22 | { 23 | "id": 6, 24 | "image": "f" 25 | }, 26 | { 27 | "id": 7, 28 | "image": "p" 29 | }, 30 | { 31 | "id": 8, 32 | "image": "sb" 33 | }, 34 | { 35 | "id": 9, 36 | "image": "swatsd" 37 | }, 38 | { 39 | "id": 10, 40 | "image": "thond" 41 | }, 42 | { 43 | "id": 11, 44 | "image": "tsits" 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /src/screens/ModalVideo.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Text, View } from 'react-native'; 3 | import { ScreenOrientation } from 'expo'; 4 | import { gStyle } from '../constants'; 5 | 6 | class ModalVideo extends React.Component { 7 | componentDidMount() { 8 | ScreenOrientation.allowAsync(ScreenOrientation.Orientation.LANDSCAPE); 9 | } 10 | 11 | componentWillUnmount() { 12 | ScreenOrientation.allowAsync(ScreenOrientation.Orientation.PORTRAIT_UP); 13 | } 14 | 15 | render() { 16 | return ( 17 | 18 | Modal :: Video 19 | 20 | ); 21 | } 22 | } 23 | 24 | export default ModalVideo; 25 | -------------------------------------------------------------------------------- /src/constants/fonts.js: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native'; 2 | 3 | const bold = Platform.select({ 4 | android: 'sans-serif-condensed', 5 | ios: 'HelveticaNeue-Bold', 6 | web: 'Helvetica Neue' 7 | }); 8 | 9 | const light = Platform.select({ 10 | android: 'sans-serif-light', 11 | ios: 'HelveticaNeue-Light', 12 | web: 'Helvetica Neue' 13 | }); 14 | 15 | const medium = Platform.select({ 16 | android: 'sans-serif-medium', 17 | ios: 'HelveticaNeue-Medium', 18 | web: 'Helvetica Neue' 19 | }); 20 | 21 | const regular = Platform.select({ 22 | android: 'sans-serif', 23 | ios: 'HelveticaNeue', 24 | web: 'Helvetica Neue' 25 | }); 26 | 27 | export default { 28 | bold, 29 | light, 30 | medium, 31 | regular 32 | }; 33 | -------------------------------------------------------------------------------- /src/components/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 | 8 | 12 | 13 | ); 14 | 15 | SvgHome.defaultProps = { 16 | active: true, 17 | size: 32 18 | }; 19 | 20 | SvgHome.propTypes = { 21 | // optional 22 | active: PropTypes.bool, 23 | size: PropTypes.number 24 | }; 25 | 26 | export default React.memo(SvgHome); 27 | -------------------------------------------------------------------------------- /src/components/icons/Svg.Plus.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 SvgPlus = ({ active, size }) => ( 7 | 8 | 12 | 13 | ); 14 | 15 | SvgPlus.defaultProps = { 16 | active: true, 17 | size: 24 18 | }; 19 | 20 | SvgPlus.propTypes = { 21 | // optional 22 | active: PropTypes.bool, 23 | size: PropTypes.number 24 | }; 25 | 26 | export default React.memo(SvgPlus); 27 | -------------------------------------------------------------------------------- /src/screens/ModalWebView.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { View, WebView } from 'react-native'; 4 | import { gStyle } from '../constants'; 5 | 6 | // components 7 | import Header from '../components/Header'; 8 | 9 | const ModalWebView = ({ navigation }) => ( 10 | 11 |
12 | 13 | 20 | 21 | ); 22 | 23 | ModalWebView.propTypes = { 24 | // required 25 | navigation: PropTypes.object.isRequired 26 | }; 27 | 28 | export default ModalWebView; 29 | -------------------------------------------------------------------------------- /src/components/icons/Svg.ArrowLeft.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 SvgArrowLeft = ({ active, size }) => ( 7 | 8 | 12 | 13 | ); 14 | 15 | SvgArrowLeft.defaultProps = { 16 | active: true, 17 | size: 24 18 | }; 19 | 20 | SvgArrowLeft.propTypes = { 21 | // optional 22 | active: PropTypes.bool, 23 | size: PropTypes.number 24 | }; 25 | 26 | export default React.memo(SvgArrowLeft); 27 | -------------------------------------------------------------------------------- /src/components/icons/Svg.Edit.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 SvgEdit = ({ active, size }) => ( 7 | 8 | 12 | 13 | ); 14 | 15 | SvgEdit.defaultProps = { 16 | active: true, 17 | size: 24 18 | }; 19 | 20 | SvgEdit.propTypes = { 21 | // optional 22 | active: PropTypes.bool, 23 | size: PropTypes.number 24 | }; 25 | 26 | export default React.memo(SvgEdit); 27 | -------------------------------------------------------------------------------- /src/components/icons/Svg.ArrowRight.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 SvgArrowRight = ({ active, size }) => ( 7 | 8 | 12 | 13 | ); 14 | 15 | SvgArrowRight.defaultProps = { 16 | active: true, 17 | size: 24 18 | }; 19 | 20 | SvgArrowRight.propTypes = { 21 | // optional 22 | active: PropTypes.bool, 23 | size: PropTypes.number 24 | }; 25 | 26 | export default React.memo(SvgArrowRight); 27 | -------------------------------------------------------------------------------- /src/navigation/StackSearch.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { createStackNavigator } from 'react-navigation-stack'; 4 | 5 | import navigationOptions from './defaultOptions'; 6 | 7 | import SearchScreen from '../screens/Search'; 8 | import SvgSearch from '../components/icons/Svg.Search'; 9 | 10 | const Icon = ({ focused }) => ; 11 | 12 | Icon.propTypes = { 13 | // required 14 | focused: PropTypes.bool.isRequired 15 | }; 16 | 17 | export default createStackNavigator( 18 | { 19 | SearchMain: { 20 | screen: SearchScreen, 21 | navigationOptions 22 | } 23 | }, 24 | { 25 | headerMode: 'none', 26 | navigationOptions: { 27 | tabBarLabel: 'Search', 28 | tabBarIcon: Icon 29 | } 30 | } 31 | ); 32 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Expo Disney+", 4 | "description": "Disney+ UI Clone with Expo", 5 | "scheme": "disneyplusclone", 6 | "slug": "disneyplusclone", 7 | "privacy": "public", 8 | "version": "0.0.1", 9 | "platforms": ["android", "ios", "web"], 10 | "githubUrl": "https://github.com/calebnance/expo-disneyplus", 11 | "orientation": "portrait", 12 | "primaryColor": "#070525", 13 | "icon": "src/assets/icon.png", 14 | "splash": { 15 | "backgroundColor": "#070525", 16 | "image": "src/assets/splash.png", 17 | "resizeMode": "contain" 18 | }, 19 | "assetBundlePatterns": ["**/*"], 20 | "android": { 21 | "versionCode": 1 22 | }, 23 | "ios": { 24 | "buildNumber": "0.0.1", 25 | "supportsTablet": true 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/navigation/TabNavigator.js: -------------------------------------------------------------------------------- 1 | import { createBottomTabNavigator } from 'react-navigation-tabs'; 2 | import { colors } from '../constants'; 3 | 4 | // grabs stacks 5 | import StackHome from './StackHome'; 6 | import StackSearch from './StackSearch'; 7 | import StackDownloads from './StackDownloads'; 8 | import StackProfile from './StackProfile'; 9 | 10 | export default createBottomTabNavigator( 11 | { 12 | StackHome, 13 | StackSearch, 14 | StackDownloads, 15 | StackProfile 16 | }, 17 | { 18 | initialRouteName: 'StackHome', 19 | tabBarOptions: { 20 | activeTintColor: colors.white, 21 | inactiveTintColor: colors.inactiveGrey, 22 | showLabel: false, 23 | style: { 24 | backgroundColor: colors.tabBackground, 25 | borderTopWidth: 0 26 | } 27 | } 28 | } 29 | ); 30 | -------------------------------------------------------------------------------- /src/navigation/StackHome.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { createStackNavigator } from 'react-navigation-stack'; 4 | import { gStyle } from '../constants'; 5 | 6 | import HomeScreen from '../screens/Home'; 7 | 8 | import SvgHome from '../components/icons/Svg.Home'; 9 | 10 | const Icon = ({ focused }) => ; 11 | 12 | Icon.propTypes = { 13 | // required 14 | focused: PropTypes.bool.isRequired 15 | }; 16 | 17 | export default createStackNavigator( 18 | { 19 | HomeMain: { 20 | screen: HomeScreen, 21 | navigationOptions: { 22 | headerStyle: gStyle.navHeaderStyle 23 | } 24 | } 25 | }, 26 | { 27 | headerMode: 'none', 28 | navigationOptions: { 29 | tabBarLabel: 'Home', 30 | tabBarIcon: Icon 31 | } 32 | } 33 | ); 34 | -------------------------------------------------------------------------------- /src/constants/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | black: '#000', 3 | black20: 'rgba(0, 0, 0, 0.2)', 4 | black40: 'rgba(0, 0, 0, 0.4)', 5 | black50: 'rgba(0, 0, 0, 0.5)', 6 | white: '#fff', 7 | 8 | background: '#0b0d15', 9 | tabBackground: '#202024', 10 | heading: '#cacaca', 11 | categoryBorder: '#384569', 12 | categoryGradStart: '#0b173d', 13 | categoryGradEnd: '#204c9a', 14 | profileBackground: '#50525d', 15 | profileEditBackground: '#404249', 16 | storageBlue: '#3070cb', 17 | 18 | inactiveGrey: '#a4a3a2', 19 | 20 | // shared 21 | infoGrey: '#a4a4a4', 22 | 23 | // search 24 | searchBarBg: '#323232', 25 | searchIcon: '#7f7f7f', 26 | 27 | // more 28 | moreAddProfileBg: '#0b0b0b', 29 | moreUsed: '#4a4a4a', 30 | moreFree: '#d8d8d8', 31 | moreSectionText: '#979797', 32 | moreSectionBorder: '#3e3e3f', 33 | moreSaveText: '#595959' 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/icons/Svg.Background.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Svg, { Defs, LinearGradient, Rect, Stop } from 'react-native-svg'; 3 | import { device } from '../../constants'; 4 | 5 | const SvgBackground = () => { 6 | const height = (812 / 375) * device.width; 7 | 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default React.memo(SvgBackground); 24 | -------------------------------------------------------------------------------- /src/components/icons/Svg.Downloads.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 SvgDownloads = ({ active, fill, size }) => { 7 | let fillColor = fill; 8 | 9 | if (fillColor === null) { 10 | fillColor = active ? colors.white : colors.inactiveGrey; 11 | } 12 | 13 | return ( 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | SvgDownloads.defaultProps = { 21 | active: true, 22 | fill: null, 23 | size: 24 24 | }; 25 | 26 | SvgDownloads.propTypes = { 27 | // optional 28 | active: PropTypes.bool, 29 | fill: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 30 | size: PropTypes.number 31 | }; 32 | 33 | export default React.memo(SvgDownloads); 34 | -------------------------------------------------------------------------------- /src/components/icons/Svg.CategoryBackground.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Svg, { Defs, LinearGradient, Rect, Stop } from 'react-native-svg'; 4 | import { colors } from '../../constants'; 5 | 6 | const SvgCategoryBackground = ({ height, width }) => ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | SvgCategoryBackground.defaultProps = { 19 | height: 24, 20 | width: 24 21 | }; 22 | 23 | SvgCategoryBackground.propTypes = { 24 | // optional 25 | height: PropTypes.number, 26 | width: PropTypes.number 27 | }; 28 | 29 | export default React.memo(SvgCategoryBackground); 30 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier"], 3 | "parser": "babel-eslint", 4 | "plugins": [ 5 | "prettier", 6 | "react", 7 | "react-native", 8 | "eslint-plugin-import-helpers" 9 | ], 10 | "rules": { 11 | "prettier/prettier": ["error"], 12 | "no-use-before-define": 0, 13 | "react/forbid-prop-types": 0, 14 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 15 | "react/jsx-fragments": 0, 16 | "react/jsx-props-no-spreading": 0, 17 | "react-native/no-inline-styles": 2, 18 | "react-native/no-single-element-style-arrays": 2, 19 | "react-native/no-unused-styles": 2, 20 | "react-native/sort-styles": [ 21 | "error", 22 | "asc", 23 | { "ignoreClassNames": true, "ignoreStyleProperties": false } 24 | ] 25 | }, 26 | "globals": { 27 | "__DEV__": "readonly", 28 | "describe": "readonly", 29 | "expect": "readonly", 30 | "it": "readonly", 31 | "jest": "readonly" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/navigation/Stack.js: -------------------------------------------------------------------------------- 1 | import { createAppContainer } from 'react-navigation'; 2 | import { createStackNavigator } from 'react-navigation-stack'; 3 | 4 | // grab screens 5 | import ModalAddProfile from '../screens/ModalAddProfile'; 6 | import ModalManageProfiles from '../screens/ModalManageProfiles'; 7 | import ModalVideo from '../screens/ModalVideo'; 8 | import ModalWebView from '../screens/ModalWebView'; 9 | 10 | // grab tabbed stacks 11 | import TabNavigator from './TabNavigator'; 12 | 13 | const StackNavigator = createStackNavigator( 14 | { 15 | Main: { 16 | screen: TabNavigator 17 | }, 18 | 19 | // Modals 20 | // ///////////////////////////////////////////////////////////////////////// 21 | ModalAddProfile, 22 | ModalManageProfiles, 23 | ModalVideo, 24 | ModalWebView 25 | }, 26 | { 27 | headerMode: 'none', 28 | initialRouteName: 'Main', 29 | mode: 'modal' 30 | } 31 | ); 32 | 33 | const App = createAppContainer(StackNavigator); 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /src/components/SlideShow.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Carousel from 'react-native-snap-carousel'; 3 | import { device, images } from '../constants'; 4 | 5 | // components 6 | import ImageSlide from './ImageSlide'; 7 | 8 | // data 9 | const slidesData = [ 10 | { image: 'slideStarWarsMandalorian' }, 11 | { image: 'slideAvengersEndgame' }, 12 | { image: 'slideAvatar' }, 13 | { image: 'slideCaptainMarvel' } 14 | ]; 15 | 16 | class SlideShow extends React.Component { 17 | render() { 18 | const itemWidth = device.width - 52; 19 | 20 | return ( 21 | { 23 | this.carousel = c; 24 | }} 25 | autoplay 26 | autoplayInterval={5000} 27 | data={slidesData} 28 | loop 29 | renderItem={({ item: { image } }) => ( 30 | 31 | )} 32 | sliderWidth={device.width} 33 | itemWidth={itemWidth} 34 | /> 35 | ); 36 | } 37 | } 38 | 39 | export default SlideShow; 40 | -------------------------------------------------------------------------------- /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, 13, and 14 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 ( 22 | height === 812 || 23 | height === 844 || 24 | height === 852 || 25 | height === 896 || 26 | height === 926 || 27 | height === 932 28 | ) { 29 | iPhoneNotch = true; 30 | } 31 | } 32 | 33 | export default { 34 | android, 35 | aspectRatio, 36 | height, 37 | iOS, 38 | iPhoneNotch, 39 | isPad, 40 | web, 41 | width 42 | }; 43 | -------------------------------------------------------------------------------- /src/components/icons/Svg.Trash.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 SvgTrash = ({ active, size }) => ( 7 | 8 | 12 | 16 | 17 | ); 18 | 19 | SvgTrash.defaultProps = { 20 | active: false, 21 | size: 24 22 | }; 23 | 24 | SvgTrash.propTypes = { 25 | // optional 26 | active: PropTypes.bool, 27 | size: PropTypes.number 28 | }; 29 | 30 | export default React.memo(SvgTrash); 31 | -------------------------------------------------------------------------------- /src/navigation/StackDownloads.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { createStackNavigator } from 'react-navigation-stack'; 4 | 5 | import navigationOptions from './defaultOptions'; 6 | 7 | import DownloadsScreen from '../screens/Downloads'; 8 | import SvgDownloads from '../components/icons/Svg.Downloads'; 9 | 10 | const Icon = ({ focused }) => ; 11 | 12 | Icon.propTypes = { 13 | // required 14 | focused: PropTypes.bool.isRequired 15 | }; 16 | 17 | export default createStackNavigator( 18 | { 19 | DownloadsMain: { 20 | screen: DownloadsScreen, 21 | navigationOptions 22 | } 23 | }, 24 | { 25 | headerMode: 'none', 26 | navigationOptions: { 27 | tabBarLabel: 'Downloads', 28 | tabBarIcon: Icon 29 | } 30 | } 31 | ); 32 | 33 | // example of navigation state manipulation 34 | // StackDownloads.navigationOptions = ({ navigation }) => { 35 | // const { index } = navigation.state; 36 | // 37 | // return { 38 | // tabBarLabel: 'Downloads', 39 | // tabBarIcon: Icon 40 | // }; 41 | // }; 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Caleb Nance 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/icons/Svg.Search.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 SvgSearch = ({ active, fill, size }) => { 7 | let fillColor = fill; 8 | 9 | if (fillColor === null) { 10 | fillColor = active ? colors.white : colors.inactiveGrey; 11 | } 12 | 13 | return ( 14 | 15 | 19 | 20 | ); 21 | }; 22 | 23 | SvgSearch.defaultProps = { 24 | active: true, 25 | fill: null, 26 | size: 32 27 | }; 28 | 29 | SvgSearch.propTypes = { 30 | // optional 31 | active: PropTypes.bool, 32 | fill: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 33 | size: PropTypes.number 34 | }; 35 | 36 | export default React.memo(SvgSearch); 37 | -------------------------------------------------------------------------------- /src/components/ImageSlide.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Image } from 'react-native'; 4 | import { device } from '../constants'; 5 | 6 | class ImageSlide extends React.PureComponent { 7 | constructor(props) { 8 | super(props); 9 | 10 | this.state = { 11 | height: 0, 12 | width: 0 13 | }; 14 | } 15 | 16 | componentDidMount() { 17 | const { source, width: imageWidth } = this.props; 18 | 19 | if (source) { 20 | const { height, width } = Image.resolveAssetSource(source); 21 | const responsiveHeight = Math.round((imageWidth * height) / width); 22 | 23 | this.setState({ 24 | height: responsiveHeight, 25 | width: imageWidth 26 | }); 27 | } 28 | } 29 | 30 | render() { 31 | const { source } = this.props; 32 | const { height, width } = this.state; 33 | 34 | return ; 35 | } 36 | } 37 | 38 | ImageSlide.defaultProps = { 39 | source: null, 40 | width: device.width 41 | }; 42 | 43 | ImageSlide.propTypes = { 44 | source: PropTypes.number, 45 | width: PropTypes.number 46 | }; 47 | 48 | export default ImageSlide; 49 | -------------------------------------------------------------------------------- /src/components/TouchLineItemElement.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; 4 | import { colors, fonts } from '../constants'; 5 | 6 | const TouchLineItemElement = ({ element, onPress, text }) => ( 7 | 12 | {text} 13 | {React.cloneElement(element)} 14 | 15 | ); 16 | 17 | TouchLineItemElement.propTypes = { 18 | // required 19 | element: PropTypes.element.isRequired, 20 | onPress: PropTypes.func.isRequired, 21 | text: PropTypes.string.isRequired 22 | }; 23 | 24 | const styles = StyleSheet.create({ 25 | container: { 26 | flexDirection: 'row', 27 | justifyContent: 'space-between', 28 | paddingHorizontal: 8, 29 | paddingVertical: 16 30 | }, 31 | text: { 32 | color: colors.heading, 33 | fontFamily: fonts.regular, 34 | fontSize: 16 35 | }, 36 | element: { 37 | justifyContent: 'center', 38 | marginRight: 4 39 | } 40 | }); 41 | 42 | export default TouchLineItemElement; 43 | -------------------------------------------------------------------------------- /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 | export default { 5 | container: { 6 | backgroundColor: colors.background, 7 | flex: 1 8 | }, 9 | flex1: { 10 | flex: 1 11 | }, 12 | posAbsolute: { 13 | position: 'absolute' 14 | }, 15 | navHeaderStyle: { 16 | backgroundColor: colors.black, 17 | borderBottomWidth: 0, 18 | elevation: 0 19 | }, 20 | heading: { 21 | color: colors.heading, 22 | fontFamily: fonts.regular, 23 | fontSize: 16, 24 | marginBottom: 4, 25 | marginTop: 16, 26 | paddingLeft: 16 27 | }, 28 | spacer24: { 29 | height: 24, 30 | width: '100%' 31 | }, 32 | spacer96: { 33 | height: 96, 34 | width: '100%' 35 | }, 36 | mB8: { 37 | marginBottom: 8 38 | }, 39 | mR8: { 40 | marginRight: 8 41 | }, 42 | mR16: { 43 | marginRight: 16 44 | }, 45 | mV16: { 46 | marginVertical: 16 47 | }, 48 | mV24: { 49 | marginVertical: 24 50 | }, 51 | mV32: { 52 | marginVertical: 32 53 | }, 54 | p4: { 55 | padding: 4 56 | }, 57 | p8: { 58 | padding: 8 59 | }, 60 | p16: { 61 | padding: 16 62 | }, 63 | pH4: { 64 | paddingHorizontal: 4 65 | }, 66 | pH8: { 67 | paddingHorizontal: 8 68 | }, 69 | pH16: { 70 | paddingHorizontal: 16 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { StatusBar, View } from 'react-native'; 3 | import * as SplashScreen from 'expo-splash-screen'; 4 | import { func } from './src/constants'; 5 | 6 | // main navigation stack 7 | import Stack from './src/navigation/Stack'; 8 | 9 | SplashScreen.preventAutoHideAsync(); 10 | 11 | const App = () => { 12 | const [isLoading, setIsLoading] = React.useState(true); 13 | 14 | React.useEffect(() => { 15 | async function prepare() { 16 | try { 17 | // pre-load/cache assets: images, fonts, and videos 18 | await func.loadAssetsAsync(); 19 | } catch (e) { 20 | // console.warn(e); 21 | } finally { 22 | // loading is complete 23 | setIsLoading(false); 24 | } 25 | } 26 | 27 | prepare(); 28 | }, []); 29 | 30 | const onLayoutRootView = React.useCallback(async () => { 31 | if (isLoading === false) { 32 | // loading is complete, hide Splash Screen and show app 33 | await SplashScreen.hideAsync(); 34 | } 35 | }, [isLoading]); 36 | 37 | if (isLoading) { 38 | return null; 39 | } 40 | 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ); 50 | }; 51 | 52 | export default App; 53 | -------------------------------------------------------------------------------- /src/components/MediaItemScroller.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { FlatList, Image, StyleSheet, View } from 'react-native'; 4 | import { colors, images } from '../constants'; 5 | 6 | import mockData from '../mockdata/data'; 7 | 8 | const MediaItemScroller = ({ dataset }) => { 9 | const dataArray = Object.values(mockData[dataset]); 10 | 11 | return ( 12 | id.toString()} 17 | renderItem={({ item }) => { 18 | const renderItem = item.image ? ( 19 | 20 | ) : ( 21 | 22 | ); 23 | 24 | return {renderItem}; 25 | }} 26 | showsHorizontalScrollIndicator={false} 27 | /> 28 | ); 29 | }; 30 | 31 | MediaItemScroller.defaultProps = { 32 | dataset: 'dumbData' 33 | }; 34 | 35 | MediaItemScroller.propTypes = { 36 | // optional 37 | dataset: PropTypes.string 38 | }; 39 | 40 | const styles = StyleSheet.create({ 41 | containerFlatList: { 42 | paddingLeft: 16, 43 | paddingRight: 8 44 | }, 45 | item: { 46 | borderRadius: 4, 47 | height: 130, 48 | marginRight: 8, 49 | overflow: 'hidden', 50 | width: 93 51 | }, 52 | placeholder: { 53 | backgroundColor: colors.infoGrey, 54 | height: '100%', 55 | width: '100%' 56 | }, 57 | image: { 58 | height: '100%', 59 | resizeMode: 'contain', 60 | width: '100%' 61 | } 62 | }); 63 | 64 | export default MediaItemScroller; 65 | -------------------------------------------------------------------------------- /src/navigation/StackProfile.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Image, StyleSheet, View } from 'react-native'; 4 | import { createStackNavigator } from 'react-navigation-stack'; 5 | import { colors, images } from '../constants'; 6 | 7 | import navigationOptions from './defaultOptions'; 8 | 9 | // screens 10 | import Profile from '../screens/Profile'; 11 | import ProfileAppSettings from '../screens/ProfileAppSettings'; 12 | import ProfileWatchlist from '../screens/ProfileWatchlist'; 13 | 14 | const Icon = ({ focused }) => { 15 | const borderColor = focused ? { borderColor: colors.white } : {}; 16 | 17 | return ( 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | Icon.propTypes = { 25 | // required 26 | focused: PropTypes.bool.isRequired 27 | }; 28 | 29 | export default createStackNavigator( 30 | { 31 | Profile: { 32 | screen: Profile, 33 | navigationOptions 34 | }, 35 | ProfileAppSettings, 36 | ProfileWatchlist 37 | }, 38 | { 39 | initialRouteName: 'Profile', 40 | headerMode: 'none', 41 | navigationOptions: { 42 | tabBarLabel: 'More', 43 | tabBarIcon: Icon 44 | } 45 | } 46 | ); 47 | 48 | const styles = StyleSheet.create({ 49 | containerProfile: { 50 | alignItems: 'center', 51 | borderColor: 'transparent', 52 | borderRadius: 20, 53 | borderWidth: 2, 54 | height: 40, 55 | justifyContent: 'center', 56 | overflow: 'hidden', 57 | width: 40 58 | }, 59 | avatar: { 60 | height: '100%', 61 | resizeMode: 'contain', 62 | width: '100%' 63 | } 64 | }); 65 | -------------------------------------------------------------------------------- /src/components/TouchLineItem.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; 4 | import { colors, fonts } from '../constants'; 5 | 6 | import SvgArrowRight from './icons/Svg.ArrowRight'; 7 | 8 | const TouchLineItem = ({ icon, iconSize, onPress, text }) => ( 9 | 14 | {icon && ( 15 | 16 | {React.cloneElement(icon, { size: iconSize })} 17 | 18 | )} 19 | {text} 20 | 21 | 22 | 23 | 24 | ); 25 | 26 | TouchLineItem.defaultProps = { 27 | icon: null, 28 | iconSize: 20 29 | }; 30 | 31 | TouchLineItem.propTypes = { 32 | // required 33 | onPress: PropTypes.func.isRequired, 34 | text: PropTypes.string.isRequired, 35 | 36 | // optional 37 | icon: PropTypes.element, 38 | iconSize: PropTypes.number 39 | }; 40 | 41 | const styles = StyleSheet.create({ 42 | container: { 43 | borderBottomColor: colors.inactiveGrey, 44 | borderBottomWidth: 1, 45 | flexDirection: 'row', 46 | justifyContent: 'space-between', 47 | marginLeft: 16, 48 | paddingRight: 16, 49 | paddingVertical: 20 50 | }, 51 | icon: { 52 | justifyContent: 'center', 53 | marginRight: 16 54 | }, 55 | text: { 56 | color: colors.white, 57 | flex: 2, 58 | fontFamily: fonts.regular, 59 | fontSize: 16 60 | }, 61 | arrow: { 62 | justifyContent: 'center' 63 | } 64 | }); 65 | 66 | export default TouchLineItem; 67 | -------------------------------------------------------------------------------- /src/components/TouchLineItemApp.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; 4 | import { colors, fonts } from '../constants'; 5 | 6 | import SvgArrowRight from './icons/Svg.ArrowRight'; 7 | 8 | const TouchLineItemApp = ({ iconSize, onPress, showArrow, tagline, text }) => ( 9 | 14 | 15 | {text} 16 | {tagline && {tagline}} 17 | 18 | {showArrow && ( 19 | 20 | 21 | 22 | )} 23 | 24 | ); 25 | 26 | TouchLineItemApp.defaultProps = { 27 | iconSize: 20, 28 | showArrow: true, 29 | tagline: null 30 | }; 31 | 32 | TouchLineItemApp.propTypes = { 33 | // required 34 | onPress: PropTypes.func.isRequired, 35 | text: PropTypes.string.isRequired, 36 | 37 | // optional 38 | iconSize: PropTypes.number, 39 | showArrow: PropTypes.bool, 40 | tagline: PropTypes.string 41 | }; 42 | 43 | const styles = StyleSheet.create({ 44 | container: { 45 | flexDirection: 'row', 46 | justifyContent: 'space-between', 47 | paddingHorizontal: 8, 48 | paddingVertical: 16 49 | }, 50 | tagline: { 51 | color: colors.moreSectionText, 52 | fontFamily: fonts.regular, 53 | fontSize: 12, 54 | marginTop: 4 55 | }, 56 | text: { 57 | color: colors.heading, 58 | fontFamily: fonts.regular, 59 | fontSize: 16 60 | }, 61 | arrow: { 62 | justifyContent: 'center' 63 | } 64 | }); 65 | 66 | export default TouchLineItemApp; 67 | -------------------------------------------------------------------------------- /src/screens/Downloads.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | import { colors, fonts, gStyle } from '../constants'; 4 | 5 | // components 6 | import Header from '../components/Header'; 7 | 8 | // icons 9 | import SvgBackground from '../components/icons/Svg.Background'; 10 | import SvgDownloads from '../components/icons/Svg.Downloads'; 11 | 12 | const Downloads = () => ( 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | You have no downloads 26 | 27 | 28 | Movies and series you download will appear here. 29 | 30 | 31 | 32 | ); 33 | 34 | const styles = StyleSheet.create({ 35 | containerContent: { 36 | alignItems: 'center', 37 | flex: 1, 38 | justifyContent: 'center' 39 | }, 40 | containerIcon: { 41 | alignItems: 'center', 42 | borderColor: colors.profileBackground, 43 | borderRadius: 50, 44 | borderWidth: 2, 45 | height: 100, 46 | justifyContent: 'center', 47 | marginBottom: 32, 48 | marginTop: 48, 49 | width: 100 50 | }, 51 | heading: { 52 | color: colors.white, 53 | fontFamily: fonts.medium, 54 | fontSize: 18, 55 | marginBottom: 16, 56 | textAlign: 'center', 57 | width: 300 58 | }, 59 | description: { 60 | color: colors.heading, 61 | fontFamily: fonts.regular, 62 | fontSize: 16, 63 | marginBottom: 48, 64 | textAlign: 'center', 65 | width: 300 66 | } 67 | }); 68 | 69 | export default Downloads; 70 | -------------------------------------------------------------------------------- /src/screens/Home.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | import { ScrollView } from 'react-navigation'; 4 | import { device, gStyle } from '../constants'; 5 | 6 | // components 7 | import Categories from '../components/Categories'; 8 | import MediaItemScroller from '../components/MediaItemScroller'; 9 | import SlideShow from '../components/SlideShow'; 10 | 11 | // icons 12 | import SvgBackground from '../components/icons/Svg.Background'; 13 | import SvgDisneyPlusLogo from '../components/icons/Svg.DisneyPlusLogo'; 14 | 15 | const Home = () => ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Originals 31 | 32 | 33 | Recommended For You 34 | 35 | 36 | Hit Movies 37 | 38 | 39 | Trending 40 | 41 | 42 | Out of the Vault 43 | 44 | 45 | Ultra HD and HDR 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | 53 | const styles = StyleSheet.create({ 54 | containerHeader: { 55 | alignItems: 'center', 56 | marginBottom: 8, 57 | paddingTop: device.iPhoneNotch ? 36 : 6 58 | } 59 | }); 60 | 61 | export default Home; 62 | -------------------------------------------------------------------------------- /src/screens/Profile.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Alert, ScrollView, StyleSheet, Text, View } from 'react-native'; 4 | import Constants from 'expo-constants'; 5 | import { colors, fonts, gStyle } from '../constants'; 6 | 7 | // components 8 | import HeaderAccounts from '../components/HeaderAccounts'; 9 | import TouchLineItem from '../components/TouchLineItem'; 10 | 11 | // icons 12 | import SvgBackground from '../components/icons/Svg.Background'; 13 | 14 | const alertSignOut = () => { 15 | Alert.alert( 16 | 'Sign Out', 17 | 'Are you sure that you want to sign out?', 18 | [{ text: 'No' }, { text: 'Yes' }], 19 | { cancelable: false } 20 | ); 21 | }; 22 | 23 | const Profile = ({ navigation }) => ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | navigation.navigate('ProfileWatchlist')} 34 | text="Watchlist" 35 | /> 36 | navigation.navigate('ProfileAppSettings')} 38 | text="App Settings" 39 | /> 40 | null} text="Account" /> 41 | null} text="Legal" /> 42 | null} text="Help" /> 43 | alertSignOut()} text="Log Out" /> 44 | 45 | 46 | {`Version: ${Constants.manifest.version}`} 47 | 48 | 49 | 50 | ); 51 | 52 | Profile.propTypes = { 53 | // required 54 | navigation: PropTypes.object.isRequired 55 | }; 56 | 57 | const styles = StyleSheet.create({ 58 | versionText: { 59 | color: colors.inactiveGrey, 60 | fontFamily: fonts.regular, 61 | fontSize: 18, 62 | marginLeft: 16, 63 | paddingVertical: 16 64 | } 65 | }); 66 | 67 | export default Profile; 68 | -------------------------------------------------------------------------------- /src/components/Categories.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Image, StyleSheet, TouchableOpacity, View } from 'react-native'; 3 | import { colors, device, images } from '../constants'; 4 | 5 | // icons 6 | import SvgCategoryBackground from './icons/Svg.CategoryBackground'; 7 | 8 | // data 9 | const categoriesData = [ 10 | { id: 1, image: 'logoDisney' }, 11 | { id: 2, image: 'logoPixar' }, 12 | { id: 3, image: 'logoMarvel' }, 13 | { id: 4, image: 'logoStarWars' }, 14 | { id: 5, image: 'logoNatGeo' } 15 | ]; 16 | 17 | const Categories = () => { 18 | const { length } = categoriesData; 19 | const bgWidth = Math.ceil((device.width - 16 - length * 18) / length); 20 | 21 | return ( 22 | 23 | {categoriesData.map((item) => { 24 | return ( 25 | null} 29 | style={[styles.containerBlock, { height: bgWidth }]} 30 | > 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | })} 38 | 39 | ); 40 | }; 41 | 42 | const styles = StyleSheet.create({ 43 | container: { 44 | alignItems: 'flex-start', 45 | flexDirection: 'row', 46 | justifyContent: 'space-between', 47 | paddingBottom: 8, 48 | paddingLeft: 16, 49 | paddingTop: 24 50 | }, 51 | containerBlock: { 52 | alignItems: 'center', 53 | borderColor: colors.categoryBorder, 54 | borderRadius: 4, 55 | borderWidth: 1, 56 | flex: 1, 57 | justifyContent: 'center', 58 | marginRight: 16 59 | }, 60 | containerBlockBackground: { 61 | borderRadius: 2, 62 | overflow: 'hidden', 63 | position: 'absolute' 64 | }, 65 | image: { 66 | height: 36, 67 | width: 64 68 | } 69 | }); 70 | 71 | export default Categories; 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expo-disneyplus", 3 | "version": "0.0.1", 4 | "description": "Disney+ UI Clone with Expo", 5 | "author": "Caleb Nance", 6 | "license": "MIT", 7 | "keywords": [ 8 | "expo", 9 | "react native", 10 | "react navigation", 11 | "disneyplus" 12 | ], 13 | "main": "node_modules/expo/AppEntry.js", 14 | "scripts": { 15 | "dev": "expo start", 16 | "start": "expo start", 17 | "lint": "eslint ./src", 18 | "android": "expo start --android", 19 | "ios": "expo start --ios", 20 | "web": "expo start --web", 21 | "web-build": "expo build:web", 22 | "eject": "expo eject" 23 | }, 24 | "dependencies": { 25 | "@expo/metro-runtime": "^3.1.1", 26 | "@react-native-community/masked-view": "0.1.10", 27 | "expo": "^50.0.0", 28 | "expo-asset": "~9.0.2", 29 | "expo-constants": "~15.4.5", 30 | "expo-device": "~5.9.3", 31 | "expo-font": "~11.10.2", 32 | "expo-splash-screen": "~0.26.3", 33 | "expo-updates": "~0.24.8", 34 | "prop-types": "^15.7.2", 35 | "react": "18.2.0", 36 | "react-dom": "18.2.0", 37 | "react-native": "0.73.2", 38 | "react-native-gesture-handler": "~2.14.0", 39 | "react-native-reanimated": "~3.6.0", 40 | "react-native-safe-area-context": "4.8.2", 41 | "react-native-screens": "~3.29.0", 42 | "react-native-snap-carousel": "^4.0.0-beta.6", 43 | "react-native-svg": "14.1.0", 44 | "react-native-web": "~0.19.6", 45 | "react-navigation": "^4.4.4", 46 | "react-navigation-stack": "^2.10.4", 47 | "react-navigation-tabs": "^2.11.1" 48 | }, 49 | "devDependencies": { 50 | "@babel/core": "^7.20.0", 51 | "babel-eslint": "^10.1.0", 52 | "babel-preset-expo": "^10.0.0", 53 | "eslint": "^7.8.1", 54 | "eslint-config-airbnb": "^18.2.0", 55 | "eslint-config-prettier": "^6.11.0", 56 | "eslint-plugin-import": "^2.22.0", 57 | "eslint-plugin-import-helpers": "^1.1.0", 58 | "eslint-plugin-jsx-a11y": "^6.3.1", 59 | "eslint-plugin-prettier": "^3.1.4", 60 | "eslint-plugin-react": "^7.20.6", 61 | "eslint-plugin-react-hooks": "^4.2.0", 62 | "eslint-plugin-react-native": "^3.11.0", 63 | "prettier": "^2.1.1" 64 | }, 65 | "prettier": { 66 | "singleQuote": true, 67 | "trailingComma": "none" 68 | }, 69 | "eslintIgnore": [ 70 | "babel.config.js" 71 | ], 72 | "private": true 73 | } 74 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; 4 | import { withNavigation } from 'react-navigation'; 5 | import { colors, device, fonts, gStyle } from '../constants'; 6 | 7 | import SvgArrowLeft from './icons/Svg.ArrowLeft'; 8 | 9 | const Header = ({ close, closeText, navigation, showBack, title }) => ( 10 | 11 | {showBack && ( 12 | navigation.goBack(null)} 15 | style={styles.back} 16 | > 17 | 18 | 19 | )} 20 | 21 | {title && ( 22 | 23 | {title} 24 | 25 | )} 26 | 27 | {showBack && !close && } 28 | 29 | {close && ( 30 | navigation.goBack(null)} 33 | style={styles.close} 34 | > 35 | {closeText} 36 | 37 | )} 38 | 39 | ); 40 | 41 | Header.defaultProps = { 42 | close: false, 43 | closeText: 'Cancel', 44 | showBack: false, 45 | title: null 46 | }; 47 | 48 | Header.propTypes = { 49 | // required 50 | navigation: PropTypes.object.isRequired, 51 | 52 | // optional 53 | close: PropTypes.bool, 54 | closeText: PropTypes.string, 55 | showBack: PropTypes.bool, 56 | title: PropTypes.string 57 | }; 58 | 59 | const styles = StyleSheet.create({ 60 | container: { 61 | alignItems: 'flex-start', 62 | flexDirection: 'row', 63 | justifyContent: 'space-between', 64 | paddingBottom: 4, 65 | paddingHorizontal: 16, 66 | paddingTop: device.iPhoneNotch ? 54 : 30 67 | }, 68 | back: { 69 | alignSelf: 'center', 70 | flex: 1 71 | }, 72 | containerTitle: { 73 | flex: 4, 74 | height: 35, 75 | justifyContent: 'flex-end' 76 | }, 77 | title: { 78 | color: colors.white, 79 | fontSize: 18, 80 | paddingBottom: 4, 81 | textAlign: 'center' 82 | }, 83 | close: { 84 | alignItems: 'flex-end', 85 | flex: 1, 86 | height: 35, 87 | justifyContent: 'center' 88 | }, 89 | closeText: { 90 | color: colors.white, 91 | fontFamily: fonts.light, 92 | fontSize: 16 93 | } 94 | }); 95 | 96 | export default withNavigation(Header); 97 | -------------------------------------------------------------------------------- /src/components/HeaderManage.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; 4 | import { withNavigation } from 'react-navigation'; 5 | import { colors, device, fonts, gStyle } from '../constants'; 6 | 7 | const HeaderManage = ({ backText, navigation, save, saveActive, title }) => { 8 | const saveColor = saveActive ? { color: colors.white } : {}; 9 | 10 | return ( 11 | 12 | navigation.goBack(null)} 15 | style={styles.back} 16 | > 17 | {backText} 18 | 19 | 20 | {title && ( 21 | 22 | {title} 23 | 24 | )} 25 | 26 | {!save && } 27 | 28 | {save && ( 29 | navigation.goBack(null)} 32 | style={styles.save} 33 | > 34 | Save 35 | 36 | )} 37 | 38 | ); 39 | }; 40 | 41 | HeaderManage.defaultProps = { 42 | backText: 'Done', 43 | save: false, 44 | saveActive: false, 45 | title: 'Manage Profiles' 46 | }; 47 | 48 | HeaderManage.propTypes = { 49 | // required 50 | navigation: PropTypes.object.isRequired, 51 | 52 | // optional 53 | backText: PropTypes.string, 54 | save: PropTypes.bool, 55 | saveActive: PropTypes.bool, 56 | title: PropTypes.string 57 | }; 58 | 59 | const styles = StyleSheet.create({ 60 | container: { 61 | alignItems: 'flex-start', 62 | backgroundColor: colors.black, 63 | flexDirection: 'row', 64 | justifyContent: 'space-between', 65 | paddingBottom: 4, 66 | paddingHorizontal: 16, 67 | paddingTop: device.iPhoneNotch ? 54 : 30 68 | }, 69 | back: { 70 | alignItems: 'flex-start', 71 | flex: 1, 72 | height: 35, 73 | justifyContent: 'center' 74 | }, 75 | backText: { 76 | color: colors.white, 77 | fontFamily: fonts.bold 78 | }, 79 | containerTitle: { 80 | flex: 4, 81 | height: 35, 82 | justifyContent: 'flex-end' 83 | }, 84 | title: { 85 | color: colors.heading, 86 | fontSize: 18, 87 | paddingBottom: 4, 88 | textAlign: 'center' 89 | }, 90 | save: { 91 | alignItems: 'flex-end', 92 | flex: 1, 93 | height: 35, 94 | justifyContent: 'center' 95 | }, 96 | saveText: { 97 | color: colors.moreSaveText, 98 | fontFamily: fonts.bold 99 | } 100 | }); 101 | 102 | export default withNavigation(HeaderManage); 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Disney+: UI Clone with React Native / Expo 2 | 3 | [![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) 4 | 5 |

6 | 7 |

8 | 9 | ## Table of Contents 10 | 11 | - [Install & Build](#install--build) 12 | - [Features](#features) 13 | - [Linting](#linting) 14 | - [Release Notes](#release-notes) 15 | 16 | ## Install & Build 17 | 18 | First, make sure you have installed on your machine: 19 | - [Yarn](https://classic.yarnpkg.com/en/docs/install): `npm install --global yarn` 20 | - Expo CLI: `npm install -g expo-cli` 21 | 22 | Install: `yarn` or `yarn install` 23 | 24 | Run Project Locally: `yarn dev` or `expo start` 25 | 26 | ## Features 27 | 28 | - Expo SDK 46 29 | - iOS and Android 30 | - React Navigation v4 31 | - PropTypes 32 | 33 | ## Linting 34 | 35 | - run: `yarn lint` for a list of linting warnings/error in cli 36 | - prettier and airbnb config 37 | - make sure you have prettier package installed: 38 | - [prettier for atom](https://atom.io/packages/prettier-atom) 39 | - [prettier for vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 40 | - then make sure to enable these options (packages → prettier): 41 | - eslint integration 42 | - stylelint integration 43 | - automatic format on save (toggle format on save) 44 | - be aware of the `.prettierignore` file 45 | 46 | ## Release Notes 47 | 48 | **version 0.0.1 (current)** 49 | 50 | - upgraded to [Expo SDK 46](https://blog.expo.dev/expo-sdk-46-c2a1655f63f7) 51 | - upgraded to [Expo SDK 45](https://blog.expo.dev/expo-sdk-45-f4e332954a68) 52 | - upgraded to [Expo SDK 44](https://blog.expo.dev/expo-sdk-44-4c4b8306584a) 53 | - upgraded to [Expo SDK 43](https://blog.expo.dev/expo-sdk-43-aa9b3c7d5541) 54 | - upgraded to [Expo SDK 42](https://blog.expo.io/expo-sdk-42-579aee2348b6) 55 | - upgraded to [Expo SDK 41](https://blog.expo.io/expo-sdk-41-12cc5232f2ef) 56 | - upgraded to [Expo SDK 40](https://blog.expo.io/expo-sdk-40-is-now-available-d4d73e67da33) 57 | - upgraded to [Expo SDK 39](https://dev.to/expo/expo-sdk-39-is-now-available-1lm8) 58 | - upgraded to [Expo SDK 38](https://blog.expo.io/expo-sdk-38-is-now-available-ab6cd30ca2ee) 59 | - upgraded to [React Navigation v4](https://reactnavigation.org/docs/4.x/getting-started) 60 | - upgraded to [Expo SDK 37](https://blog.expo.io/expo-sdk-37-is-now-available-dd5770f066a6) 61 | - upgraded to [Expo SDK 36](https://blog.expo.io/expo-sdk-36-is-now-available-b91897b437fe) 62 | - iOS and Android 63 | - Tab Navigation 64 | - Home 65 | - Search 66 | - Downloads 67 | - Profile 68 | - started with [React Navigation v3](https://reactnavigation.org/docs/3.x/getting-started) 69 | - started with [Expo SDK 35](https://blog.expo.io/expo-sdk-35-is-now-available-beee0dfafbf4) 70 | -------------------------------------------------------------------------------- /src/constants/preloadImages.js: -------------------------------------------------------------------------------- 1 | // logos 2 | const logoDisney = require('../assets/images/logo/disney.png'); 3 | const logoMarvel = require('../assets/images/logo/marvel.png'); 4 | const logoNatGeo = require('../assets/images/logo/national-geographic.png'); 5 | const logoPixar = require('../assets/images/logo/pixar.png'); 6 | const logoStarWars = require('../assets/images/logo/star-wars.png'); 7 | 8 | // slides 9 | const slideAvatar = require('../assets/images/slides/avatar.png'); 10 | const slideAvengersEndgame = require('../assets/images/slides/avengers-endgame.png'); 11 | const slideCaptainMarvel = require('../assets/images/slides/captain-marvel.png'); 12 | const slideStarWarsMandalorian = require('../assets/images/slides/star-wars-mandalorian.png'); 13 | 14 | // movies 15 | const a = require('../assets/images/movies/a.jpg'); 16 | const ae = require('../assets/images/movies/ae.jpg'); 17 | const aiw = require('../assets/images/movies/aiw.jpg'); 18 | const anhe4 = require('../assets/images/movies/anhe4.jpg'); 19 | const aotce2 = require('../assets/images/movies/aotce2.jpg'); 20 | const b = require('../assets/images/movies/b.jpg'); 21 | const batb = require('../assets/images/movies/batb.jpg'); 22 | const cacw = require('../assets/images/movies/cacw.jpg'); 23 | const cm = require('../assets/images/movies/cm.jpg'); 24 | const f = require('../assets/images/movies/f.jpg'); 25 | const fz = require('../assets/images/movies/fz.jpg'); 26 | const h = require('../assets/images/movies/h.jpg'); 27 | const i = require('../assets/images/movies/i.jpg'); 28 | const p = require('../assets/images/movies/p.jpg'); 29 | const roasws = require('../assets/images/movies/roasws.jpg'); 30 | const sb = require('../assets/images/movies/sb.jpg'); 31 | const swatsd = require('../assets/images/movies/swatsd.jpg'); 32 | const tesbe5 = require('../assets/images/movies/tesbe5.jpg'); 33 | const tfae7 = require('../assets/images/movies/tfae7.jpg'); 34 | const thond = require('../assets/images/movies/thond.jpg'); 35 | const tlk = require('../assets/images/movies/tlk.jpg'); 36 | const tlm = require('../assets/images/movies/tlm.jpg'); 37 | const tm = require('../assets/images/movies/tm.jpg'); 38 | const tpme1 = require('../assets/images/movies/tpme1.jpg'); 39 | const ts = require('../assets/images/movies/ts.jpg'); 40 | const tsits = require('../assets/images/movies/tsits.jpg'); 41 | const z = require('../assets/images/movies/z.jpg'); 42 | 43 | // profiles 44 | const elsa = require('../assets/images/profiles/elsa.jpg'); 45 | const ironMan = require('../assets/images/profiles/iron-man.jpg'); 46 | const stormtrooper = require('../assets/images/profiles/stormtrooper.jpg'); 47 | const yoda = require('../assets/images/profiles/yoda.jpg'); 48 | 49 | export default { 50 | // logos 51 | logoDisney, 52 | logoMarvel, 53 | logoNatGeo, 54 | logoPixar, 55 | logoStarWars, 56 | 57 | // slides 58 | slideAvatar, 59 | slideAvengersEndgame, 60 | slideCaptainMarvel, 61 | slideStarWarsMandalorian, 62 | 63 | // movies 64 | a, 65 | ae, 66 | aiw, 67 | anhe4, 68 | aotce2, 69 | b, 70 | batb, 71 | cacw, 72 | cm, 73 | f, 74 | fz, 75 | h, 76 | i, 77 | p, 78 | roasws, 79 | sb, 80 | swatsd, 81 | tesbe5, 82 | tfae7, 83 | thond, 84 | tlk, 85 | tlm, 86 | tm, 87 | tpme1, 88 | ts, 89 | tsits, 90 | z, 91 | 92 | // profiles 93 | elsa, 94 | ironMan, 95 | stormtrooper, 96 | yoda 97 | }; 98 | -------------------------------------------------------------------------------- /src/screens/ModalManageProfiles.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; 4 | import { colors, fonts, gStyle, images } from '../constants'; 5 | 6 | // components 7 | import HeaderManage from '../components/HeaderManage'; 8 | 9 | // icons 10 | import SvgEdit from '../components/icons/Svg.Edit'; 11 | import SvgPlus from '../components/icons/Svg.Plus'; 12 | 13 | const ModalManageProfiles = ({ navigation }) => ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | Caleb 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Kim 30 | 31 | 32 | 33 | 34 | 35 | 36 | navigation.navigate('ModalAddProfile')} 39 | style={styles.containerUser} 40 | > 41 | 42 | 43 | 44 | 45 | 46 | Add Profile 47 | 48 | 49 | 50 | ); 51 | 52 | ModalManageProfiles.propTypes = { 53 | // required 54 | navigation: PropTypes.object.isRequired 55 | }; 56 | 57 | const BLOCK_SIZE = 108; 58 | 59 | const styles = StyleSheet.create({ 60 | container: { 61 | alignSelf: 'center', 62 | flexDirection: 'row', 63 | flexWrap: 'wrap', 64 | justifyContent: 'space-between', 65 | paddingHorizontal: 16, 66 | paddingVertical: 60, 67 | width: 280 68 | }, 69 | containerUser: { 70 | marginBottom: 16 71 | }, 72 | containerSvg: { 73 | alignItems: 'center', 74 | height: BLOCK_SIZE, 75 | justifyContent: 'center', 76 | position: 'absolute', 77 | width: BLOCK_SIZE 78 | }, 79 | overlay: { 80 | backgroundColor: colors.black50, 81 | height: BLOCK_SIZE, 82 | position: 'absolute', 83 | top: 0, 84 | width: BLOCK_SIZE 85 | }, 86 | avatar: { 87 | height: BLOCK_SIZE, 88 | resizeMode: 'contain', 89 | width: BLOCK_SIZE 90 | }, 91 | text: { 92 | color: colors.white, 93 | fontFamily: fonts.regular, 94 | fontSize: 16, 95 | marginTop: 8, 96 | textAlign: 'center' 97 | }, 98 | containerPlus: { 99 | alignItems: 'center', 100 | height: BLOCK_SIZE, 101 | justifyContent: 'center', 102 | width: BLOCK_SIZE 103 | }, 104 | plusBackground: { 105 | alignItems: 'center', 106 | backgroundColor: colors.moreAddProfileBg, 107 | borderRadius: 34, 108 | height: 68, 109 | justifyContent: 'center', 110 | width: 68 111 | } 112 | }); 113 | 114 | export default ModalManageProfiles; 115 | -------------------------------------------------------------------------------- /src/screens/ModalAddProfile.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { 3 | Alert, 4 | Image, 5 | StyleSheet, 6 | Switch, 7 | Text, 8 | TextInput, 9 | View 10 | } from 'react-native'; 11 | import { colors, fonts, gStyle, images } from '../constants'; 12 | 13 | // components 14 | import HeaderManage from '../components/HeaderManage'; 15 | 16 | class ModalAddProfile extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | 20 | this.state = { 21 | forKidsValue: false, 22 | text: '' 23 | }; 24 | 25 | this.handleSwitchChange = this.handleSwitchChange.bind(this); 26 | } 27 | 28 | handleSwitchChange(value) { 29 | // warn on switch off from kids settings... 30 | if (value === false) { 31 | Alert.alert( 32 | 'This profile will now allow access to TV shows and movies of all maturity levels.', 33 | '', 34 | [{ text: 'OK' }], 35 | { cancelable: false } 36 | ); 37 | } 38 | 39 | this.setState({ 40 | forKidsValue: value 41 | }); 42 | } 43 | 44 | render() { 45 | const { forKidsValue, text } = this.state; 46 | 47 | return ( 48 | 49 | 55 | 56 | 57 | 58 | CHANGE 59 | 60 | this.setState({ text: input })} 65 | selectionColor={colors.storageBlue} 66 | style={styles.input} 67 | value={text} 68 | /> 69 | 70 | 71 | For Kids 72 | this.handleSwitchChange(val)} 74 | value={forKidsValue} 75 | /> 76 | 77 | 78 | 79 | ); 80 | } 81 | } 82 | 83 | const BLOCK_SIZE = 108; 84 | 85 | const styles = StyleSheet.create({ 86 | container: { 87 | alignItems: 'center', 88 | alignSelf: 'center', 89 | paddingHorizontal: 16, 90 | paddingVertical: 60 91 | }, 92 | avatar: { 93 | height: BLOCK_SIZE, 94 | resizeMode: 'contain', 95 | width: BLOCK_SIZE 96 | }, 97 | text: { 98 | color: colors.white, 99 | fontFamily: fonts.regular, 100 | fontSize: 16, 101 | marginBottom: 24, 102 | marginTop: 8, 103 | textAlign: 'center' 104 | }, 105 | input: { 106 | borderColor: colors.white, 107 | borderWidth: 1, 108 | color: colors.white, 109 | fontSize: 16, 110 | paddingHorizontal: 8, 111 | paddingVertical: 12, 112 | width: 260 113 | }, 114 | containerSwitch: { 115 | alignItems: 'center', 116 | flexDirection: 'row', 117 | marginTop: 16 118 | }, 119 | switchLabel: { 120 | color: colors.white, 121 | fontFamily: fonts.light, 122 | fontSize: 16, 123 | marginRight: 8, 124 | textTransform: 'uppercase' 125 | } 126 | }); 127 | 128 | export default ModalAddProfile; 129 | -------------------------------------------------------------------------------- /src/components/HeaderAccounts.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Image, StyleSheet, TouchableOpacity, Text, View } from 'react-native'; 4 | import { withNavigation } from 'react-navigation'; 5 | import { colors, device, fonts, gStyle, images } from '../constants'; 6 | 7 | // icons 8 | import SvgPlus from './icons/Svg.Plus'; 9 | 10 | const ICON_SIZE = 74; 11 | 12 | const HeaderAccounts = ({ navigation }) => ( 13 | 14 | 15 | 16 | 17 | Caleb 18 | 19 | 20 | 21 | 22 | 23 | Kim 24 | 25 | 26 | navigation.navigate('ModalAddProfile')} 29 | style={styles.containerUser} 30 | > 31 | 32 | 33 | 34 | Add Profile 35 | 36 | 37 | 38 | navigation.navigate('ModalManageProfiles')} 41 | style={styles.containerEditProfiles} 42 | > 43 | Edit Profiles 44 | 45 | 46 | ); 47 | 48 | HeaderAccounts.propTypes = { 49 | // required 50 | navigation: PropTypes.object.isRequired 51 | }; 52 | 53 | const styles = StyleSheet.create({ 54 | container: { 55 | alignItems: 'center', 56 | width: '100%' 57 | }, 58 | containerAccounts: { 59 | alignItems: 'center', 60 | flexDirection: 'row', 61 | justifyContent: 'center', 62 | paddingBottom: 30, 63 | paddingTop: device.iPhoneNotch ? 64 : 40, 64 | width: '100%' 65 | }, 66 | containerUser: { 67 | alignItems: 'center', 68 | marginHorizontal: 10 69 | }, 70 | avatar: { 71 | borderRadius: ICON_SIZE / 2, 72 | height: ICON_SIZE, 73 | marginBottom: 6, 74 | overflow: 'hidden', 75 | resizeMode: 'contain', 76 | width: ICON_SIZE 77 | }, 78 | avatarActive: { 79 | ...gStyle.posAbsolute, 80 | borderColor: colors.white, 81 | borderRadius: ICON_SIZE / 2, 82 | borderWidth: 2, 83 | height: ICON_SIZE, 84 | width: ICON_SIZE 85 | }, 86 | username: { 87 | color: colors.inactiveGrey, 88 | fontFamily: fonts.medium, 89 | fontSize: 12, 90 | marginTop: 4 91 | }, 92 | usernameActive: { 93 | color: colors.white, 94 | fontFamily: fonts.bold 95 | }, 96 | containerPlus: { 97 | alignItems: 'center', 98 | backgroundColor: colors.profileBackground, 99 | borderRadius: ICON_SIZE / 2, 100 | height: ICON_SIZE, 101 | justifyContent: 'center', 102 | marginBottom: 4, 103 | width: ICON_SIZE 104 | }, 105 | containerEditProfiles: { 106 | alignItems: 'center', 107 | backgroundColor: colors.profileEditBackground, 108 | borderRadius: 4, 109 | flexDirection: 'row', 110 | justifyContent: 'center', 111 | marginBottom: 24 112 | }, 113 | editProfilesText: { 114 | color: colors.white, 115 | fontFamily: fonts.medium, 116 | paddingHorizontal: 16, 117 | paddingVertical: 8, 118 | textTransform: 'uppercase' 119 | } 120 | }); 121 | 122 | export default withNavigation(HeaderAccounts); 123 | -------------------------------------------------------------------------------- /src/components/icons/Svg.DisneyPlusLogo.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Svg, { Path } from 'react-native-svg'; 3 | 4 | const SvgDisneyPlusLogo = () => ( 5 | 6 | 10 | 11 | ); 12 | 13 | export default React.memo(SvgDisneyPlusLogo); 14 | -------------------------------------------------------------------------------- /src/screens/ProfileAppSettings.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Alert, ScrollView, StyleSheet, Text, View } from 'react-native'; 3 | import Constants from 'expo-constants'; 4 | import * as Device from 'expo-device'; 5 | import { colors, fonts, gStyle } from '../constants'; 6 | 7 | // components 8 | import Header from '../components/Header'; 9 | import TouchLineItemApp from '../components/TouchLineItemApp'; 10 | import TouchLineItemElement from '../components/TouchLineItemElement'; 11 | 12 | // icons 13 | import SvgBackground from '../components/icons/Svg.Background'; 14 | import SvgTrash from '../components/icons/Svg.Trash'; 15 | 16 | const alertDeleteDownloads = () => { 17 | Alert.alert( 18 | 'Delete All Downloads', 19 | 'Are you sure you want to delete this one download?', 20 | [ 21 | { 22 | text: 'Cancel' 23 | }, 24 | { 25 | style: 'destructive', 26 | text: 'Delete' 27 | } 28 | ], 29 | { 30 | cancelable: false 31 | } 32 | ); 33 | }; 34 | 35 | const ProfileAppSettings = () => { 36 | const { platform } = Constants; 37 | let deviceType = 'Unknown Device'; 38 | 39 | // is iOS? 40 | if (typeof platform.ios !== 'undefined') { 41 | deviceType = Device.modelName; 42 | } 43 | 44 | return ( 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | 53 | 54 | Video Playback 55 | 56 | 57 | null} 59 | tagline="Automatic" 60 | text="Cellular Data Usage" 61 | /> 62 | 63 | 64 | Downloads 65 | 66 | 67 | null} 69 | tagline="Standard" 70 | text="Video Quality" 71 | /> 72 | 73 | alertDeleteDownloads()} 75 | element={} 76 | text="Delete All Downloads" 77 | /> 78 | 79 | 80 | {deviceType} 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | Used 89 | 90 | 91 | 92 | Disney+ 93 | 94 | 95 | 96 | Free 97 | 98 | 99 | 100 | 101 | 102 | ); 103 | }; 104 | 105 | const styles = StyleSheet.create({ 106 | containerHeading: { 107 | borderBottomColor: colors.moreSectionBorder, 108 | borderBottomWidth: 1, 109 | paddingHorizontal: 8, 110 | paddingVertical: 16 111 | }, 112 | heading: { 113 | color: colors.moreSectionText, 114 | fontFamily: fonts.light, 115 | fontSize: 16, 116 | textTransform: 'uppercase' 117 | }, 118 | containerDevice: { 119 | borderBottomColor: colors.moreSectionBorder, 120 | borderBottomWidth: StyleSheet.hairlineWidth, 121 | marginHorizontal: 8, 122 | paddingVertical: 8 123 | }, 124 | deviceText: { 125 | color: colors.white 126 | }, 127 | containerStorage: { 128 | backgroundColor: colors.moreFree, 129 | flexDirection: 'row', 130 | height: 10, 131 | marginVertical: 8, 132 | width: '100%' 133 | }, 134 | storageUsed: { 135 | backgroundColor: colors.moreUsed, 136 | height: '100%', 137 | width: '24%' 138 | }, 139 | storageDisneyPlus: { 140 | backgroundColor: colors.storageBlue, 141 | height: '100%', 142 | width: '4%' 143 | }, 144 | containerIndex: { 145 | flexDirection: 'row', 146 | justifyContent: 'space-between' 147 | }, 148 | containerIndexBlock: { 149 | alignItems: 'center', 150 | flexDirection: 'row', 151 | justifyContent: 'center' 152 | }, 153 | indexBlock: { 154 | borderRadius: 3, 155 | height: 14, 156 | marginRight: 10, 157 | width: 14 158 | }, 159 | storage: { 160 | backgroundColor: colors.moreFree 161 | }, 162 | used: { 163 | backgroundColor: colors.moreUsed 164 | }, 165 | disneyPlus: { 166 | backgroundColor: colors.storageBlue 167 | } 168 | }); 169 | 170 | export default ProfileAppSettings; 171 | --------------------------------------------------------------------------------