├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 | [](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 |
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 |
--------------------------------------------------------------------------------