├── .babelrc ├── .env.example ├── .eslintrc.js ├── .flowconfig ├── .gitignore ├── .watchmanconfig ├── README.md ├── __tests__ ├── App.test.js ├── __mocks__ │ └── orientation.js ├── index.android.js └── index.ios.js ├── android ├── app │ ├── BUCK │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ └── fonts │ │ │ ├── Entypo.ttf │ │ │ ├── EvilIcons.ttf │ │ │ ├── Feather.ttf │ │ │ ├── FontAwesome.ttf │ │ │ ├── Foundation.ttf │ │ │ ├── Ionicons.ttf │ │ │ ├── MaterialCommunityIcons.ttf │ │ │ ├── MaterialIcons.ttf │ │ │ ├── Octicons.ttf │ │ │ ├── SimpleLineIcons.ttf │ │ │ └── Zocial.ttf │ │ ├── java │ │ └── com │ │ │ └── moviedb │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ │ └── res │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystores │ ├── BUCK │ ├── android.keystore │ └── debug.keystore.properties └── settings.gradle ├── index.android.js ├── index.ios.js ├── ios ├── MovieDB-tvOS │ └── Info.plist ├── MovieDB-tvOSTests │ └── Info.plist ├── MovieDB.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcuserdata │ │ │ ├── sandipnirmal.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── synerzip.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ └── xcschemes │ │ │ ├── MovieDB-tvOS.xcscheme │ │ │ └── MovieDB.xcscheme │ └── xcuserdata │ │ └── sandipnirmal.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── MovieDB │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@1x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-76x76@3x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ ├── Icon-Small-50x50@1x.png │ │ │ ├── Icon-Small-50x50@2x.png │ │ │ └── ItunesArtwork@2x.png │ │ ├── Contents.json │ │ ├── README.md │ │ ├── iTunesArtwork@1x.png │ │ ├── iTunesArtwork@2x.png │ │ └── iTunesArtwork@3x.png │ ├── Info.plist │ └── main.m └── MovieDBTests │ ├── Info.plist │ └── MovieDBTests.m ├── package.json ├── screenshots └── demo.gif └── src ├── Actions.js ├── App.js ├── State.js ├── actions ├── castActions.js ├── moviesActions.js ├── searchActions.js ├── seasonActions.js ├── settingsActions.js └── tvShowsActions.js ├── components ├── AppNavigation.js ├── SplashScreen.js ├── base │ ├── Details.js │ ├── Image.js │ └── Shows.js ├── common │ ├── BackgroundImage.js │ ├── Carousel.js │ ├── CastDetails.js │ ├── CastList.js │ ├── ImageList.js │ ├── ListItem.js │ ├── ShareButton.js │ ├── ShowOverview.js │ ├── Touchable.js │ ├── TrailerItem.js │ ├── TrailerList.js │ └── VideoPlayer.js ├── movies │ ├── AllMovies.js │ ├── MovieDetails.js │ └── Movies.js ├── search │ ├── PopularSearch.js │ ├── Search.js │ ├── SearchItem.js │ └── SearchResult.js ├── settings │ ├── RegionSettings.js │ ├── Settings.js │ └── SettingsDetail.js └── tv │ ├── AllTvShows.js │ ├── EpisodeItem.js │ ├── EpisodeList.js │ ├── SeasonDetails.js │ ├── TvShowDetails.js │ └── TvShows.js ├── index.js ├── reducers ├── casts.js ├── common.js ├── configuration.js ├── movies.js ├── navigation.js ├── root.js ├── search.js ├── seasons.js ├── settings.js ├── tabNavHelper.js └── tvShows.js ├── router.js ├── routes ├── Common.js ├── Movies.js ├── Search.js ├── Settings.js └── TvShows.js ├── services ├── index.js ├── person.js ├── search.js ├── seasons.js └── shows.js ├── styles └── styles.js └── utilities ├── constants.js └── utils.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react-native" 4 | ] 5 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # MovieDB API Endpoint 2 | API_ENDPOINT='https://api.themoviedb.org/3' 3 | 4 | # MovieDB API Key 5 | API_KEY='movieDB-API-key' -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "ecmaFeatures": { 9 | "experimentalObjectRestSpread": true, 10 | "jsx": true 11 | }, 12 | "sourceType": "module" 13 | }, 14 | "plugins": [ 15 | "react", 16 | "react-native" 17 | ], 18 | "rules": { 19 | "indent": [ 20 | "error", 21 | "tab" 22 | ], 23 | "linebreak-style": [ 24 | "error", 25 | "unix" 26 | ], 27 | "quotes": [ 28 | "error", 29 | "single" 30 | ], 31 | "semi": [ 32 | "error", 33 | "never" 34 | ] 35 | } 36 | }; -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore templates for 'react-native init' 6 | /node_modules/react-native/local-cli/templates/.* 7 | 8 | ; Ignore RN jest 9 | /node_modules/react-native/jest/.* 10 | 11 | ; Ignore RNTester 12 | /node_modules/react-native/RNTester/.* 13 | 14 | ; Ignore the website subdir 15 | /node_modules/react-native/website/.* 16 | 17 | ; Ignore the Dangerfile 18 | /node_modules/react-native/danger/dangerfile.js 19 | 20 | ; Ignore Fbemitter 21 | /node_modules/fbemitter/.* 22 | 23 | ; Ignore "BUCK" generated dirs 24 | /node_modules/react-native/\.buckd/ 25 | 26 | ; Ignore unexpected extra "@providesModule" 27 | .*/node_modules/.*/node_modules/fbjs/.* 28 | 29 | ; Ignore polyfills 30 | /node_modules/react-native/Libraries/polyfills/.* 31 | 32 | ; Ignore various node_modules 33 | /node_modules/react-native-gesture-handler/.* 34 | /node_modules/expo/.* 35 | /node_modules/react-navigation/.* 36 | /node_modules/xdl/.* 37 | /node_modules/reqwest/.* 38 | /node_modules/metro-bundler/.* 39 | 40 | [include] 41 | 42 | [libs] 43 | node_modules/react-native/Libraries/react-native/react-native-interface.js 44 | node_modules/react-native/flow/ 45 | node_modules/expo/flow/ 46 | 47 | [options] 48 | emoji=true 49 | 50 | module.system=haste 51 | 52 | module.file_ext=.js 53 | module.file_ext=.jsx 54 | module.file_ext=.json 55 | module.file_ext=.ios.js 56 | 57 | munge_underscores=true 58 | 59 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 60 | 61 | suppress_type=$FlowIssue 62 | suppress_type=$FlowFixMe 63 | suppress_type=$FlowFixMeProps 64 | suppress_type=$FlowFixMeState 65 | suppress_type=$FixMe 66 | 67 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\) 68 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+ 69 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 70 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 71 | 72 | unsafe.enable_getters_and_setters=true 73 | 74 | [version] 75 | ^0.56.0 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # expo 4 | .expo/ 5 | 6 | .DS_Store 7 | .vscode/ 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # misc 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | .env 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | yarn.lock 23 | package.json-lock 24 | 25 | *.orig 26 | .env 27 | ios/build/ 28 | ios/main.jsbundle 29 | ios/main.jsbundle.meta 30 | ios/MovieDB.xcodeproj/project.xcworkspace/* 31 | ios/MovieDB.xcodeproj/xcuserdata/* 32 | android/app/build 33 | android/build 34 | android/.gradle/ 35 | package-lock.json 36 | android/app/*.keystore 37 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Movie DB using React Native 2 | 3 | This is cross platform app built with React Native and by using the MovieDB apis. Purpose of this project was to get our hands dirty with React Native and redux, where we just want to focus on application UI development. 4 | 5 | ### Getting Started 6 | 7 | #### Clone the app 8 | 9 | ``` 10 | git clone https://github.com/SandipNirmal/React-Native-MovieDB 11 | ``` 12 | 13 | #### Install Dependancies 14 | 15 | ``` 16 | npm i 17 | ``` 18 | 19 | #### Add Configurations ( one time activity ) 20 | 21 | Create ```.env``` file in application root similar to ```.env.example```. Replace dummy values present in ```.env.example``` with your values. 22 | Create acccount on MovieDB and generate API_KEY. 23 | 24 | ```javascript 25 | # MovieDB API key 26 | API_KEY='YOUR_API_KEY' 27 | 28 | # Other key value pairs 29 | ``` 30 | 31 | #### Link Dependancies ( one time activity ) 32 | 33 | ``` JavaScript 34 | // link orientation 35 | react-native link react-native-orientation 36 | 37 | // link vector icons 38 | react-native link react-native-vector-icons 39 | 40 | // link environment variables 41 | react-native link react-native-config 42 | 43 | // link react-native-theme 44 | react-native link react-native-theme 45 | 46 | ``` 47 | #### Run Application 48 | 49 | ##### iOS Simulator 50 | ``` 51 | react-native run-ios 52 | 53 | npm start 54 | ``` 55 | 56 | ##### Android emulator 57 | 58 | Start AVD (emulator) before following commands 59 | 60 | ``` 61 | react-native run-android 62 | 63 | npm start 64 | ``` 65 | 66 | #### Bundle Application for Device 67 | 68 | ``` JavaScript 69 | react-native bundle --entry-file src/index.js --platform ios --dev false --bundle-output ios/main.jsbundle --assets-dest ios 70 | ``` 71 | 72 | #### For iOS 73 | 74 | ##### Modify AppDelegate.m 75 | 76 | ``` JavaScript 77 | NSURL *jsCodeLocation; 78 | jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 79 | 80 | // jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 81 | ``` 82 | 83 | #### For Android 84 | 85 | ##### Generate keyStore file 86 | Use following instructions to generate keystore file 87 | 88 | https://facebook.github.io/react-native/docs/signed-apk-android.html 89 | 90 | ``` Javascript 91 | 1. Update the alias and the password in the file android/gradle.properties 92 | 2. Run cd android && ./gradlew assembleRelease; cd .. 93 | 3. The Apk should be availabe at android/app/build/outputs/apk/ 94 | ``` 95 | 96 | ### Screens 97 |
98 | 99 |
100 | 101 | 102 | ### Available on: 103 | ~~Our app is now available on Google Play.~~ (Not available now) 104 | 105 | Get it on Google Play 106 | 107 | -------------------------------------------------------------------------------- /__tests__/App.test.js: -------------------------------------------------------------------------------- 1 | import 'isomorphic-fetch' 2 | import React from 'react' 3 | import App from './App' 4 | 5 | import renderer from 'react-test-renderer' 6 | 7 | it('renders without crashing', () => { 8 | const rendered = renderer.create().toJSON() 9 | expect(rendered).toBeTruthy() 10 | }) 11 | -------------------------------------------------------------------------------- /__tests__/__mocks__/orientation.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | class Orientation extends Component { 4 | 5 | lockToPortrait() {} 6 | 7 | lockToLandscape() {} 8 | 9 | render() { 10 | return null; 11 | } 12 | } 13 | 14 | 15 | export default Orientation; 16 | -------------------------------------------------------------------------------- /__tests__/index.android.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import "isomorphic-fetch"; 3 | import React from 'react'; 4 | import App from '../App'; 5 | 6 | // import Orientation from './__mocks__/orientation'; 7 | 8 | // Note: test renderer must be required after react-native. 9 | import renderer from 'react-test-renderer'; 10 | 11 | jest.mock('react-native-orientation', () => { 12 | 'Orientation' 13 | }); 14 | 15 | it('renders correctly', () => { 16 | const tree = renderer.create(); 17 | }); 18 | -------------------------------------------------------------------------------- /__tests__/index.ios.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import "isomorphic-fetch"; 3 | import React from 'react'; 4 | import App from '../App'; 5 | 6 | // import Orientation from './__mocks__/orientation'; 7 | 8 | // Note: test renderer must be required after react-native. 9 | import renderer from 'react-test-renderer'; 10 | 11 | jest.mock('react-native-orientation', () => { 12 | 'Orientation' 13 | }); 14 | 15 | it('renders correctly', () => { 16 | const tree = renderer.create(); 17 | }); 18 | -------------------------------------------------------------------------------- /android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | lib_deps = [] 12 | 13 | for jarfile in glob(['libs/*.jar']): 14 | name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')] 15 | lib_deps.append(':' + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | 21 | for aarfile in glob(['libs/*.aar']): 22 | name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')] 23 | lib_deps.append(':' + name) 24 | android_prebuilt_aar( 25 | name = name, 26 | aar = aarfile, 27 | ) 28 | 29 | android_library( 30 | name = "all-libs", 31 | exported_deps = lib_deps, 32 | ) 33 | 34 | android_library( 35 | name = "app-code", 36 | srcs = glob([ 37 | "src/main/java/**/*.java", 38 | ]), 39 | deps = [ 40 | ":all-libs", 41 | ":build_config", 42 | ":res", 43 | ], 44 | ) 45 | 46 | android_build_config( 47 | name = "build_config", 48 | package = "com.moviedb", 49 | ) 50 | 51 | android_resource( 52 | name = "res", 53 | package = "com.moviedb", 54 | res = "src/main/res", 55 | ) 56 | 57 | android_binary( 58 | name = "app", 59 | keystore = "//android/keystores:debug", 60 | manifest = "src/main/AndroidManifest.xml", 61 | package_type = "debug", 62 | deps = [ 63 | ":app-code", 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // whether to bundle JS and assets in debug mode 22 | * bundleInDebug: false, 23 | * 24 | * // whether to bundle JS and assets in release mode 25 | * bundleInRelease: true, 26 | * 27 | * // whether to bundle JS and assets in another build variant (if configured). 28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 29 | * // The configuration property can be in the following formats 30 | * // 'bundleIn${productFlavor}${buildType}' 31 | * // 'bundleIn${buildType}' 32 | * // bundleInFreeDebug: true, 33 | * // bundleInPaidRelease: true, 34 | * // bundleInBeta: true, 35 | * 36 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 37 | * // for example: to disable dev mode in the staging build type (if configured) 38 | * devDisabledInStaging: true, 39 | * // The configuration property can be in the following formats 40 | * // 'devDisabledIn${productFlavor}${buildType}' 41 | * // 'devDisabledIn${buildType}' 42 | * 43 | * // the root of your project, i.e. where "package.json" lives 44 | * root: "../../", 45 | * 46 | * // where to put the JS bundle asset in debug mode 47 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 48 | * 49 | * // where to put the JS bundle asset in release mode 50 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 51 | * 52 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 53 | * // require('./image.png')), in debug mode 54 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 55 | * 56 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 57 | * // require('./image.png')), in release mode 58 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 59 | * 60 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 61 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 62 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 63 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 64 | * // for example, you might want to remove it from here. 65 | * inputExcludes: ["android/**", "ios/**"], 66 | * 67 | * // override which node gets called and with what additional arguments 68 | * nodeExecutableAndArgs: ["node"], 69 | * 70 | * // supply additional arguments to the packager 71 | * extraPackagerArgs: [] 72 | * ] 73 | */ 74 | 75 | project.ext.react = [ 76 | entryFile: "index.android.js" 77 | ] 78 | 79 | apply from: "../../node_modules/react-native/react.gradle" 80 | 81 | /** 82 | * Set this to true to create two separate APKs instead of one: 83 | * - An APK that only works on ARM devices 84 | * - An APK that only works on x86 devices 85 | * The advantage is the size of the APK is reduced by about 4MB. 86 | * Upload all the APKs to the Play Store and people will download 87 | * the correct one based on the CPU architecture of their device. 88 | */ 89 | def enableSeparateBuildPerCPUArchitecture = false 90 | 91 | /** 92 | * Run Proguard to shrink the Java bytecode in release builds. 93 | */ 94 | def enableProguardInReleaseBuilds = false 95 | 96 | android { 97 | compileSdkVersion 23 98 | buildToolsVersion "23.0.1" 99 | 100 | defaultConfig { 101 | applicationId "com.moviedb" 102 | minSdkVersion 16 103 | targetSdkVersion 26 104 | versionCode 2 105 | versionName "1.1" 106 | ndk { 107 | abiFilters "armeabi-v7a", "x86" 108 | } 109 | } 110 | signingConfigs { 111 | release { 112 | if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) { 113 | storeFile file(MYAPP_RELEASE_STORE_FILE) 114 | storePassword MYAPP_RELEASE_STORE_PASSWORD 115 | keyAlias MYAPP_RELEASE_KEY_ALIAS 116 | keyPassword MYAPP_RELEASE_KEY_PASSWORD 117 | } 118 | } 119 | } 120 | splits { 121 | abi { 122 | reset() 123 | enable enableSeparateBuildPerCPUArchitecture 124 | universalApk false // If true, also generate a universal APK 125 | include "armeabi-v7a", "x86" 126 | } 127 | } 128 | buildTypes { 129 | release { 130 | minifyEnabled enableProguardInReleaseBuilds 131 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 132 | signingConfig signingConfigs.release 133 | } 134 | } 135 | // applicationVariants are e.g. debug, release 136 | applicationVariants.all { variant -> 137 | variant.outputs.each { output -> 138 | // For each separate APK per architecture, set a unique version code as described here: 139 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 140 | def versionCodes = ["armeabi-v7a":1, "x86":2] 141 | def abi = output.getFilter(OutputFile.ABI) 142 | if (abi != null) { // null for the universal-debug, universal-release variants 143 | output.versionCodeOverride = 144 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 145 | } 146 | } 147 | } 148 | } 149 | 150 | dependencies { 151 | compile project(':react-native-config') 152 | compile project(':react-native-orientation') 153 | compile project(':react-native-vector-icons') 154 | compile fileTree(dir: "libs", include: ["*.jar"]) 155 | compile "com.android.support:appcompat-v7:23.0.1" 156 | compile "com.facebook.react:react-native:+" // From node_modules 157 | } 158 | 159 | // Run this once to be able to run the application with BUCK 160 | // puts all compile dependencies into folder libs for BUCK to use 161 | task copyDownloadableDepsToLibs(type: Copy) { 162 | from configurations.compile 163 | into 'libs' 164 | } 165 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # TextLayoutBuilder uses a non-public Android constructor within StaticLayout. 54 | # See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details. 55 | -dontwarn android.text.StaticLayout 56 | 57 | # okhttp 58 | 59 | -keepattributes Signature 60 | -keepattributes *Annotation* 61 | -keep class okhttp3.** { *; } 62 | -keep interface okhttp3.** { *; } 63 | -dontwarn okhttp3.** 64 | 65 | # okio 66 | 67 | -keep class sun.misc.Unsafe { *; } 68 | -dontwarn java.nio.file.* 69 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 70 | -dontwarn okio.** 71 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/assets/fonts/Entypo.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/EvilIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/assets/fonts/EvilIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Feather.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/assets/fonts/Feather.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/assets/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Foundation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/assets/fonts/Foundation.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/assets/fonts/Ionicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/assets/fonts/MaterialIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/assets/fonts/Octicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/SimpleLineIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/assets/fonts/SimpleLineIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Zocial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/assets/fonts/Zocial.ttf -------------------------------------------------------------------------------- /android/app/src/main/java/com/moviedb/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.moviedb; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "MovieDB"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/moviedb/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.moviedb; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.react.ReactApplication; 6 | import com.lugg.ReactNativeConfig.ReactNativeConfigPackage; 7 | import com.github.yamill.orientation.OrientationPackage; 8 | import com.oblador.vectoricons.VectorIconsPackage; 9 | import com.facebook.react.ReactNativeHost; 10 | import com.facebook.react.ReactPackage; 11 | import com.facebook.react.shell.MainReactPackage; 12 | import com.facebook.soloader.SoLoader; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | public class MainApplication extends Application implements ReactApplication { 18 | 19 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 20 | @Override 21 | public boolean getUseDeveloperSupport() { 22 | return BuildConfig.DEBUG; 23 | } 24 | 25 | @Override 26 | protected List getPackages() { 27 | return Arrays.asList( 28 | new MainReactPackage(), 29 | new ReactNativeConfigPackage(), 30 | new OrientationPackage(), 31 | new VectorIconsPackage() 32 | ); 33 | } 34 | 35 | @Override 36 | protected String getJSMainModuleName() { 37 | return "index.android"; 38 | } 39 | }; 40 | 41 | @Override 42 | public ReactNativeHost getReactNativeHost() { 43 | return mReactNativeHost; 44 | } 45 | 46 | @Override 47 | public void onCreate() { 48 | super.onCreate(); 49 | SoLoader.init(this, /* native exopackage */ false); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MovieDB 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | maven { 20 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 21 | url "$rootDir/../node_modules/react-native/android" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # android.useDeprecatedNdk=true 21 | MYAPP_RELEASE_STORE_FILE=android.keystore 22 | MYAPP_RELEASE_KEY_ALIAS=appkey 23 | MYAPP_RELEASE_STORE_PASSWORD=sandip 24 | MYAPP_RELEASE_KEY_PASSWORD=sandip 25 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = "debug", 3 | properties = "debug.keystore.properties", 4 | store = "debug.keystore", 5 | visibility = [ 6 | "PUBLIC", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /android/keystores/android.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/android/keystores/android.keystore -------------------------------------------------------------------------------- /android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'MovieDB' 2 | include ':react-native-config' 3 | project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android') 4 | include ':react-native-orientation' 5 | project(':react-native-orientation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-orientation/android') 6 | include ':react-native-vector-icons' 7 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') 8 | 9 | include ':app' 10 | -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native' 2 | import App from './src/App' 3 | 4 | AppRegistry.registerComponent('MovieDB', () => App) 5 | -------------------------------------------------------------------------------- /index.ios.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native' 2 | import App from './src/App' 3 | 4 | AppRegistry.registerComponent('MovieDB', () => App) 5 | -------------------------------------------------------------------------------- /ios/MovieDB-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UIViewControllerBasedStatusBarAppearance 38 | 39 | NSLocationWhenInUseUsageDescription 40 | 41 | NSAppTransportSecurity 42 | 43 | 44 | NSExceptionDomains 45 | 46 | localhost 47 | 48 | NSExceptionAllowsInsecureHTTPLoads 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ios/MovieDB-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/MovieDB.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/MovieDB.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ios/MovieDB.xcodeproj/project.xcworkspace/xcuserdata/sandipnirmal.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB.xcodeproj/project.xcworkspace/xcuserdata/sandipnirmal.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ios/MovieDB.xcodeproj/project.xcworkspace/xcuserdata/sandipnirmal.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | EnabledFullIndexStoreVisibility 12 | 13 | IssueFilterStyle 14 | ShowActiveSchemeOnly 15 | LiveSourceIssuesEnabled 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ios/MovieDB.xcodeproj/project.xcworkspace/xcuserdata/synerzip.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB.xcodeproj/project.xcworkspace/xcuserdata/synerzip.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ios/MovieDB.xcodeproj/xcshareddata/xcschemes/MovieDB-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 62 | 68 | 69 | 70 | 71 | 72 | 78 | 79 | 80 | 81 | 82 | 83 | 94 | 96 | 102 | 103 | 104 | 105 | 106 | 107 | 113 | 115 | 121 | 122 | 123 | 124 | 126 | 127 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /ios/MovieDB.xcodeproj/xcshareddata/xcschemes/MovieDB.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 62 | 68 | 69 | 70 | 71 | 72 | 78 | 79 | 80 | 81 | 82 | 83 | 94 | 96 | 102 | 103 | 104 | 105 | 106 | 107 | 113 | 115 | 121 | 122 | 123 | 124 | 126 | 127 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /ios/MovieDB.xcodeproj/xcuserdata/sandipnirmal.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MovieDB-tvOS.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | MovieDB.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ios/MovieDB/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ios/MovieDB/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | 12 | #import 13 | #import 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | NSURL *jsCodeLocation; 20 | 21 | //jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 22 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 23 | 24 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 25 | moduleName:@"MovieDB" 26 | initialProperties:nil 27 | launchOptions:launchOptions]; 28 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 29 | 30 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 31 | UIViewController *rootViewController = [UIViewController new]; 32 | rootViewController.view = rootView; 33 | self.window.rootViewController = rootViewController; 34 | [self.window makeKeyAndVisible]; 35 | return YES; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /ios/MovieDB/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images":[ 3 | { 4 | "idiom":"iphone", 5 | "size":"20x20", 6 | "scale":"2x", 7 | "filename":"Icon-App-20x20@2x.png" 8 | }, 9 | { 10 | "idiom":"iphone", 11 | "size":"20x20", 12 | "scale":"3x", 13 | "filename":"Icon-App-20x20@3x.png" 14 | }, 15 | { 16 | "idiom":"iphone", 17 | "size":"29x29", 18 | "scale":"1x", 19 | "filename":"Icon-App-29x29@1x.png" 20 | }, 21 | { 22 | "idiom":"iphone", 23 | "size":"29x29", 24 | "scale":"2x", 25 | "filename":"Icon-App-29x29@2x.png" 26 | }, 27 | { 28 | "idiom":"iphone", 29 | "size":"29x29", 30 | "scale":"3x", 31 | "filename":"Icon-App-29x29@3x.png" 32 | }, 33 | { 34 | "idiom":"iphone", 35 | "size":"40x40", 36 | "scale":"1x", 37 | "filename":"Icon-App-40x40@1x.png" 38 | }, 39 | { 40 | "idiom":"iphone", 41 | "size":"40x40", 42 | "scale":"2x", 43 | "filename":"Icon-App-40x40@2x.png" 44 | }, 45 | { 46 | "idiom":"iphone", 47 | "size":"40x40", 48 | "scale":"3x", 49 | "filename":"Icon-App-40x40@3x.png" 50 | }, 51 | { 52 | "idiom":"iphone", 53 | "size":"57x57", 54 | "scale":"1x", 55 | "filename":"Icon-App-57x57@1x.png" 56 | }, 57 | { 58 | "idiom":"iphone", 59 | "size":"57x57", 60 | "scale":"2x", 61 | "filename":"Icon-App-57x57@2x.png" 62 | }, 63 | { 64 | "idiom":"iphone", 65 | "size":"60x60", 66 | "scale":"1x", 67 | "filename":"Icon-App-60x60@1x.png" 68 | }, 69 | { 70 | "idiom":"iphone", 71 | "size":"60x60", 72 | "scale":"2x", 73 | "filename":"Icon-App-60x60@2x.png" 74 | }, 75 | { 76 | "idiom":"iphone", 77 | "size":"60x60", 78 | "scale":"3x", 79 | "filename":"Icon-App-60x60@3x.png" 80 | }, 81 | { 82 | "idiom":"iphone", 83 | "size":"76x76", 84 | "scale":"1x", 85 | "filename":"Icon-App-76x76@1x.png" 86 | }, 87 | { 88 | "idiom":"ipad", 89 | "size":"20x20", 90 | "scale":"1x", 91 | "filename":"Icon-App-20x20@1x.png" 92 | }, 93 | { 94 | "idiom":"ipad", 95 | "size":"20x20", 96 | "scale":"2x", 97 | "filename":"Icon-App-20x20@2x.png" 98 | }, 99 | { 100 | "idiom":"ipad", 101 | "size":"29x29", 102 | "scale":"1x", 103 | "filename":"Icon-App-29x29@1x.png" 104 | }, 105 | { 106 | "idiom":"ipad", 107 | "size":"29x29", 108 | "scale":"2x", 109 | "filename":"Icon-App-29x29@2x.png" 110 | }, 111 | { 112 | "idiom":"ipad", 113 | "size":"40x40", 114 | "scale":"1x", 115 | "filename":"Icon-App-40x40@1x.png" 116 | }, 117 | { 118 | "idiom":"ipad", 119 | "size":"40x40", 120 | "scale":"2x", 121 | "filename":"Icon-App-40x40@2x.png" 122 | }, 123 | { 124 | "size" : "50x50", 125 | "idiom" : "ipad", 126 | "filename" : "Icon-Small-50x50@1x.png", 127 | "scale" : "1x" 128 | }, 129 | { 130 | "size" : "50x50", 131 | "idiom" : "ipad", 132 | "filename" : "Icon-Small-50x50@2x.png", 133 | "scale" : "2x" 134 | }, 135 | { 136 | "idiom":"ipad", 137 | "size":"72x72", 138 | "scale":"1x", 139 | "filename":"Icon-App-72x72@1x.png" 140 | }, 141 | { 142 | "idiom":"ipad", 143 | "size":"72x72", 144 | "scale":"2x", 145 | "filename":"Icon-App-72x72@2x.png" 146 | }, 147 | { 148 | "idiom":"ipad", 149 | "size":"76x76", 150 | "scale":"1x", 151 | "filename":"Icon-App-76x76@1x.png" 152 | }, 153 | { 154 | "idiom":"ipad", 155 | "size":"76x76", 156 | "scale":"2x", 157 | "filename":"Icon-App-76x76@2x.png" 158 | }, 159 | { 160 | "idiom":"ipad", 161 | "size":"76x76", 162 | "scale":"3x", 163 | "filename":"Icon-App-76x76@3x.png" 164 | }, 165 | { 166 | "idiom":"ipad", 167 | "size":"83.5x83.5", 168 | "scale":"2x", 169 | "filename":"Icon-App-83.5x83.5@2x.png" 170 | }, 171 | { 172 | "size" : "1024x1024", 173 | "idiom" : "ios-marketing", 174 | "filename" : "ItunesArtwork@2x.png", 175 | "scale" : "1x" 176 | } 177 | ], 178 | "info":{ 179 | "version":1, 180 | "author":"makeappicon" 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/README.md: -------------------------------------------------------------------------------- 1 | ## iTunesArtwork & iTunesArtwork@2x (App Icon) file extension: 2 | 3 | PNG extension is prepended to these two files - 4 | 5 | While Apple suggested to omit the extension for these files, 6 | the '.png' extension is actually required for iTunesConnect submission. 7 | 8 | This is done for you so you don't have to. 9 | 10 | However, for Ad_hoc or Enterprise distirbution, the extension should be removed 11 | from the files before adding to XCode to avoid error. 12 | 13 | refs: https://developer.apple.com/library/ios/qa/qa1686/_index.html 14 | 15 | ## iTunesArtwork & iTunesArtwork@2x (App Icon) transparency handling: 16 | 17 | As images with alpha channels or transparencies cannot be set as an application's icon on 18 | iTunesConnect, all transparent pixels in your images will be converted into 19 | solid blacks. 20 | 21 | To achieve the best result, you're advised to adjust the transparency settings 22 | in your source files before converting them with makeAppIcon. 23 | 24 | refs: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/AppIcons.html 25 | -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/iTunesArtwork@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/iTunesArtwork@1x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /ios/MovieDB/Images.xcassets/iTunesArtwork@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/ios/MovieDB/Images.xcassets/iTunesArtwork@3x.png -------------------------------------------------------------------------------- /ios/MovieDB/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | MovieDB 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | localhost 32 | 33 | NSExceptionAllowsInsecureHTTPLoads 34 | 35 | 36 | 37 | 38 | NSLocationWhenInUseUsageDescription 39 | 40 | UIAppFonts 41 | 42 | Entypo.ttf 43 | EvilIcons.ttf 44 | Feather.ttf 45 | FontAwesome.ttf 46 | Foundation.ttf 47 | Ionicons.ttf 48 | MaterialCommunityIcons.ttf 49 | MaterialIcons.ttf 50 | Octicons.ttf 51 | SimpleLineIcons.ttf 52 | Zocial.ttf 53 | 54 | UILaunchStoryboardName 55 | LaunchScreen 56 | UIRequiredDeviceCapabilities 57 | 58 | armv7 59 | 60 | UIStatusBarStyle 61 | UIStatusBarStyleLightContent 62 | UISupportedInterfaceOrientations 63 | 64 | UIInterfaceOrientationPortrait 65 | UIInterfaceOrientationLandscapeLeft 66 | UIInterfaceOrientationLandscapeRight 67 | 68 | UIViewControllerBasedStatusBarAppearance 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /ios/MovieDB/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ios/MovieDBTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/MovieDBTests/MovieDBTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | #import 14 | #import 15 | 16 | #define TIMEOUT_SECONDS 600 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface MovieDBTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation MovieDBTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-moviedb", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "test": "jest" 8 | }, 9 | "dependencies": { 10 | "axios": "0.17.1", 11 | "npm": "^5.7.1", 12 | "react": "16.2.0", 13 | "react-native": "0.52.0", 14 | "react-native-elements": "0.18.5", 15 | "react-native-image-placeholder": "^1.0.14", 16 | "react-native-orientation": "3.1.3", 17 | "react-native-theme": "^0.1.8", 18 | "react-native-vector-icons": "4.5.0", 19 | "react-navigation": "1.0.3", 20 | "react-navigation-redux-helpers": "1.0.1", 21 | "react-redux": "5.0.6", 22 | "redux": "3.7.2", 23 | "redux-promise": "0.5.3" 24 | }, 25 | "devDependencies": { 26 | "babel-jest": "22.1.0", 27 | "babel-preset-react-native": "4.0.0", 28 | "jest": "22.1.2", 29 | "react-native-config": "^0.11.5", 30 | "react-test-renderer": "16.2.0", 31 | "standard": "11.0.0" 32 | }, 33 | "jest": { 34 | "preset": "react-native", 35 | "transformIgnorePatterns": [ 36 | "node_modules/(?!react-native|react-navigation)/" 37 | ], 38 | "testPathIgnorePatterns": [ 39 | "/node_modules/", 40 | "__tests_/__mocks__" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /screenshots/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SandipNirmal/React-Native-MovieDB/df70f3429206942a758ff7568b91819099c1316f/screenshots/demo.gif -------------------------------------------------------------------------------- /src/Actions.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | // CONFIGURATION 4 | export const CONFIG_FETCHED = 'CONFIG_FETCHED' 5 | export const configFetched = config => ({ type: 'CONFIG_FETCHED', config }) 6 | 7 | // Layout 8 | export const LAYOUT_CHANGED = 'LAYOUT_CHANGED' 9 | export const layoutChanged = () => ({ type: LAYOUT_CHANGED }) 10 | 11 | export * from './actions/castActions' 12 | export * from './actions/moviesActions' 13 | export * from './actions/tvShowsActions' 14 | export * from './actions/settingsActions' 15 | export * from './actions/searchActions' 16 | export * from './actions/seasonActions' 17 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { createStore, applyMiddleware } from 'redux' 3 | import { Provider, connect } from 'react-redux' 4 | import { StyleSheet, View, StatusBar, AsyncStorage } from 'react-native' 5 | import Orientation from 'react-native-orientation' 6 | import promise from 'redux-promise' 7 | import theme, { styles } from 'react-native-theme' 8 | 9 | import { defaultStyle, lightTheme, darkTheme } from './styles/styles' 10 | import AppNavigation from './components/AppNavigation' 11 | import SplashScreen from './components/SplashScreen' 12 | import MovieDB from './reducers/root' 13 | import { layoutChanged } from './Actions' 14 | 15 | theme.add(defaultStyle) 16 | theme.add(lightTheme, 'Light') 17 | theme.add(darkTheme, 'Dark') 18 | 19 | class Screen extends Component { 20 | render() { 21 | const { isFetching, onLayoutChange } = this.props 22 | const color = isFetching ? '#000000' : '#222222' 23 | return ( 24 | 25 | 26 | {isFetching ? : } 27 | 28 | ) 29 | } 30 | } 31 | 32 | const mapStateToProps = state => ({ isFetching: state.movies.isFetching }) 33 | const mapDispatchToProps = dispatch => ({ 34 | onLayoutChange: e => { 35 | dispatch(layoutChanged()) 36 | } 37 | }) 38 | 39 | const AppRoot = connect(mapStateToProps, mapDispatchToProps)(Screen) 40 | 41 | export default class App extends Component { 42 | // Component did mount event 43 | componentDidMount() { 44 | Orientation.lockToPortrait() 45 | } 46 | 47 | componentWillMount() { 48 | theme.setRoot(this) 49 | 50 | AsyncStorage.getItem('Settings') 51 | .then(settings => { 52 | const themeName = JSON.parse(settings).theme 53 | theme.active(themeName) 54 | }) 55 | .catch(err => { 56 | theme.active('Dark') 57 | }) 58 | } 59 | 60 | render() { 61 | // TODO - Use redux-promise middleware properly 62 | // https://medium.com/react-native-training/redux-4-ways-95a130da0cdc 63 | const createStoreWithMiddleware = applyMiddleware(promise)(createStore) 64 | const store = createStoreWithMiddleware(MovieDB) 65 | 66 | return ( 67 | 68 | 69 | 70 | ) 71 | } 72 | } 73 | 74 | const style = StyleSheet.create({ 75 | container: { 76 | flex: 1, 77 | backgroundColor: '#040404' 78 | } 79 | }) 80 | -------------------------------------------------------------------------------- /src/State.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'movies': { 3 | 'isFetching': true, 4 | 'categories': { 5 | 'nowShowing': [], 6 | 'comingSoon': [], 7 | 'popular': [] 8 | }, 9 | 'details': {}, 10 | 'cast': { 11 | 'isFetching': false, 12 | 'details': {} 13 | } 14 | }, 15 | 'tvShows': { 16 | 'isFetching': false, 17 | 'categories': { 18 | 'showingToday': [], 19 | 'topRated': [], 20 | 'popular': [] 21 | }, 22 | 'details': {}, 23 | 'cast': { 24 | 'isFetching': false, 25 | 'details': {} 26 | } 27 | }, 28 | 'search': { 29 | 'isSearching': false, 30 | 'selectedIndex': 0, 31 | 'results': [], 32 | 'details': {}, 33 | 'cast': { 34 | 'isFetching': false, 35 | 'details': {} 36 | } 37 | }, 38 | 'config': { 39 | 'apiKey': '', 40 | 'image': { 41 | 'secureBaseUrl': '', 42 | 'numColumns': 3, // default 43 | 'backdropSize': '', 44 | 'posterSizeForBackground': '', 45 | 'posterSizeForImageList': '', 46 | 'profileSize': '', 47 | 'stillSize': '' 48 | }, 49 | 'style': { 50 | 'posterSize': {}, 51 | 'backdropSize': {}, 52 | 'carousel': {} 53 | } 54 | }, 55 | 'settings': { 56 | 'language': 'IN-hi', 57 | 'region': 'IN', 58 | 'theme': 'Dark' 59 | }, 60 | // TODO: We should not be using the following. Study the right way of doing this 61 | 'helper': { 62 | 'currentTab': 'Movies' 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/actions/castActions.js: -------------------------------------------------------------------------------- 1 | import { getCastDetails } from './../services/person' 2 | 3 | // CAST 4 | export const MOVIES_CAST_SELECTED = 'MOVIES_CAST_SELECTED' 5 | export const TVSHOWS_CAST_SELECTED = 'TVSHOWS_CAST_SELECTED' 6 | export const SEARCH_CAST_SELECTED = 'SEARCH_CAST_SELECTED' 7 | export const castSelected = (cast, screen) => ({ 8 | type: `${screen.toUpperCase()}_CAST_SELECTED`, 9 | cast 10 | }) 11 | 12 | export const FETCHING_MOVIES_CAST_DETAILS = 'FETCHING_MOVIES_CAST_DETAILS' 13 | export const FETCHING_TVSHOWS_CAST_DETAILS = 'FETCHING_TVSHOWS_CAST_DETAILS' 14 | export const FETCHING_SEARCH_CAST_DETAILS = 'FETCHING_SEARCH_CAST_DETAILS' 15 | export const fetchingCastDetails = screen => ({ 16 | type: `FETCHING_${screen.toUpperCase()}_CAST_DETAILS` 17 | }) 18 | 19 | export const MOVIES_CAST_DETAILS_FETCHED = 'MOVIES_CAST_DETAILS_FETCHED' 20 | export const TVSHOWS_CAST_DETAILS_FETCHED = 'TVSHOWS_CAST_DETAILS_FETCHED' 21 | export const SEARCH_CAST_DETAILS_FETCHED = 'SEARCH_CAST_DETAILS_FETCHED' 22 | export const castDetailsFetched = (details, category, screen) => ({ 23 | type: `${screen.toUpperCase()}_CAST_DETAILS_FETCHED`, 24 | details, 25 | category 26 | }) 27 | 28 | export const FETCH_CAST_DETAILS = 'FETCH_CAST_DETAILS' 29 | 30 | export function fetchCastDetails(castId) { 31 | const request = getCastDetails(castId) 32 | 33 | return { type: FETCH_CAST_DETAILS, payload: request } 34 | } 35 | -------------------------------------------------------------------------------- /src/actions/moviesActions.js: -------------------------------------------------------------------------------- 1 | // MOVIES 2 | export const FETCHING_MOVIES = 'FETCHING_MOVIES' 3 | export const fetchingMovies = () => ({ type: 'FETCHING_MOVIES' }) 4 | 5 | export const NOW_SHOWING_MOVIES_FETCHED = 'NOW_SHOWING_MOVIES_FETCHED' 6 | export const COMING_SOON_MOVIES_FETCHED = 'COMING_SOON_MOVIES_FETCHED' 7 | export const POPULAR_MOVIES_FETCHED = 'POPULAR_MOVIES_FETCHED' 8 | 9 | export const movieFetched = (category, movies) => ({ 10 | type: category.toUnderScore().toUpperCase() + '_MOVIES_FETCHED', 11 | movies 12 | }) 13 | 14 | export const MOVIE_SELECTED = 'MOVIE_SELECTED' 15 | export const selectedMovie = movie => ({ type: 'MOVIE_SELECTED', movie }) 16 | 17 | export const MOVIE_DETAILS_FETCHED = 'MOVIE_DETAILS_FETCHED' 18 | export const movieDetailsFetched = (details, category) => ({ 19 | type: 'MOVIE_DETAILS_FETCHED', 20 | details, 21 | category 22 | }) 23 | -------------------------------------------------------------------------------- /src/actions/searchActions.js: -------------------------------------------------------------------------------- 1 | // SEARCH 2 | export const SEARCHING_MOVIES_ETC = 'SEARCHING_MOVIES_ETC' 3 | 4 | export const searchingForMoviesEtc = () => ({ type: 'SEARCHING_MOVIES_ETC' }) 5 | 6 | export const DONE_SEARCHING_MOVIES_ETC = 'DONE_SEARCHING_MOVIES_ETC' 7 | export const doneSearchingMoviesEtc = results => ({ 8 | type: 'DONE_SEARCHING_MOVIES_ETC', 9 | results 10 | }) 11 | 12 | export const SEARCH_FILTER_CHANGED = 'SEARCH_FILTER_CHANGED' 13 | export const searchFilterChanged = index => ({ 14 | type: 'SEARCH_FILTER_CHANGED', 15 | index 16 | }) 17 | 18 | export const SEARCH_RESULT_SELECTED = 'SEARCH_RESULT_SELECTED' 19 | export const searchResultSelected = (result, mediaType) => ({ 20 | type: 'SEARCH_RESULT_SELECTED', 21 | result, 22 | mediaType 23 | }) 24 | 25 | export const SEARCH_ITEM_DETAILS_FETCHED = 'SEARCH_ITEM_DETAILS_FETCHED' 26 | export const searchItemDetailsFetched = (details, category) => ({ 27 | type: 'SEARCH_ITEM_DETAILS_FETCHED', 28 | details, 29 | category 30 | }) 31 | -------------------------------------------------------------------------------- /src/actions/seasonActions.js: -------------------------------------------------------------------------------- 1 | import { getSeasonDetails } from './../services/seasons' 2 | 3 | // SEASONS 4 | export const FETCH_SEASON_DETAILS = 'FETCH_SEASON_DETAILS' 5 | 6 | export const fetchSeasonDetails = (showId, season_number, success, err) => { 7 | const request = getSeasonDetails(showId, season_number) 8 | 9 | return { type: FETCH_SEASON_DETAILS, payload: request } 10 | } 11 | -------------------------------------------------------------------------------- /src/actions/settingsActions.js: -------------------------------------------------------------------------------- 1 | import { AsyncStorage } from 'react-native' 2 | import theme from 'react-native-theme' 3 | 4 | import initialState from './../State' 5 | 6 | // Settings 7 | const SETTINGS_KEY = 'Settings' 8 | export const SETTINGS_LANGUAGE_CHANGED = 'SETTINGS_LANGUAGE_CHANGED' 9 | export const SETTINGS_REGION_CHANGED = 'SETTINGS_REGION_CHANGED' 10 | export const SETTINGS_THEME_CHANGED = 'SETTINGS_THEME_CHANGED' 11 | export const FETCH_SETTINGS = 'FETCH_SETTINGS' 12 | export const SAVE_SETTINGS = 'SAVE_SETTINGS' 13 | 14 | export const fetchSettingsAction = async () => { 15 | const payload = 16 | JSON.parse(await AsyncStorage.getItem(SETTINGS_KEY)) || 17 | initialState['settings'] 18 | return { type: FETCH_SETTINGS, payload } 19 | } 20 | 21 | export const saveSettingsAction = async (values = initialState['settings']) => { 22 | try { 23 | await AsyncStorage.setItem(SETTINGS_KEY, JSON.stringify(values)) 24 | 25 | // TODO - Might need to seperate each settings action handlers 26 | if (theme.name !== values.theme) { 27 | theme.active(values.theme) 28 | } 29 | 30 | return { type: SAVE_SETTINGS, payload: values } 31 | } catch (e) { 32 | return { type: SAVE_SETTINGS, payload: null } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/actions/tvShowsActions.js: -------------------------------------------------------------------------------- 1 | // TV SHOWS 2 | export const FETCHING_TV_SHOWS = 'FETCHING_TV_SHOWS' 3 | export const fetchingTvShows = () => ({ type: 'FETCHING_TV_SHOWS' }) 4 | 5 | export const SHOWING_TODAY_TV_SHOWS_FETCHED = 'SHOWING_TODAY_TV_SHOWS_FETCHED' 6 | export const TOP_RATED_TV_SHOWS_FETCHED = 'TOP_RATED_TV_SHOWS_FETCHED' 7 | export const POPULAR_TV_SHOWS_FETCHED = 'POPULAR_TV_SHOWS_FETCHED' 8 | 9 | export const tvShowsFetched = (category, tvShows) => ({ 10 | type: category.toUnderScore().toUpperCase() + '_TV_SHOWS_FETCHED', 11 | tvShows 12 | }) 13 | 14 | export const TV_SHOW_SELECTED = 'TV_SHOW_SELECTED' 15 | export const selectedTvShow = tvShow => ({ type: 'TV_SHOW_SELECTED', tvShow }) 16 | 17 | export const TV_SHOW_DETAILS_FETCHED = 'TV_SHOW_DETAILS_FETCHED' 18 | export const tvShowDetailsFetched = (details, category) => ({ 19 | type: 'TV_SHOW_DETAILS_FETCHED', 20 | details, 21 | category 22 | }) 23 | -------------------------------------------------------------------------------- /src/components/AppNavigation.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { BackHandler } from 'react-native' 3 | import { connect } from 'react-redux' 4 | import { addNavigationHelpers } from 'react-navigation' 5 | import { NavigationActions } from 'react-navigation' 6 | 7 | import MainScreen from '../router' 8 | 9 | // Refer https://reactnavigation.org/docs/redux-integration.html 10 | 11 | // following is to resolve issue https://github.com/infinitered/ignite/issues/1225 12 | // for react-navigation 1.0.0-beta.30 13 | import { 14 | createReduxBoundAddListener, 15 | createReactNavigationReduxMiddleware 16 | } from 'react-navigation-redux-helpers' 17 | 18 | const middleware = createReactNavigationReduxMiddleware( 19 | 'root', 20 | state => state.nav 21 | ) 22 | const addListener = createReduxBoundAddListener('root') 23 | // end for react-navigation 1.0.0-beta.30 24 | 25 | class AppNavigation extends Component { 26 | componentDidMount() { 27 | BackHandler.addEventListener('hardwareBackPress', this.onBackPress) 28 | } 29 | 30 | componentWillUnmount() { 31 | BackHandler.removeEventListener('hardwareBackPress', this.onBackPress) 32 | } 33 | 34 | /** 35 | * Close the app if we are on the main screen of the current tab and a back 36 | * button is hit 37 | */ 38 | closeApp = () => { 39 | const { navigationState } = this.props 40 | const index = navigationState.index 41 | // Are we on the main screen of the tab ? i.e. not on any details page 42 | return navigationState.routes[index].index == 0 ? true : false 43 | } 44 | 45 | onBackPress = () => { 46 | if (this.closeApp()) return false 47 | this.props.dispatch(NavigationActions.back()) 48 | return true 49 | } 50 | 51 | render() { 52 | const { navigationState, dispatch } = this.props 53 | return ( 54 | 61 | ) 62 | } 63 | } 64 | 65 | const mapStateToProps = state => ({ 66 | navigationState: state.navigation 67 | }) 68 | 69 | export default connect(mapStateToProps)(AppNavigation) 70 | -------------------------------------------------------------------------------- /src/components/SplashScreen.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { ActivityIndicator, Text, View, Alert } from 'react-native' 3 | import * as _ from 'lodash' 4 | import { connect } from 'react-redux' 5 | import axios from 'axios' 6 | import { styles } from 'react-native-theme' 7 | import { Avatar } from 'react-native-elements' 8 | 9 | import { configFetched, movieFetched } from '../Actions' 10 | import { getUriPopulated } from '../utilities/utils' 11 | import { getConfiguration } from '../services/index' 12 | import { getShows } from '../services/shows' 13 | 14 | import { primaryColor } from '../styles/styles' 15 | 16 | class SplashScreen extends Component { 17 | componentDidMount() { 18 | const { onFetchCompleted, onConfigFetched, config, settings } = this.props 19 | 20 | getConfiguration() 21 | .then(({ data }) => { 22 | onConfigFetched(data) 23 | getShows('/movie/now_playing', settings.language, settings.region) 24 | .then(({ data }) => { 25 | onFetchCompleted( 26 | 'nowShowing', 27 | getUriPopulated(data.results, config, 'posterSizeForImageList') 28 | ) 29 | }) 30 | .catch(error => { 31 | Alert.alert('Error', 'Error fetching Data!') 32 | }) 33 | }) 34 | .catch(error => { 35 | Alert.alert('Error', 'Error fetching Data!') 36 | }) 37 | } 38 | 39 | render() { 40 | return ( 41 | 44 | 56 | 57 | MovieDB 58 | 59 | 65 | 66 | 67 | 68 | For everyone in love with movies and TV Shows 69 | 70 | 71 | ) 72 | } 73 | } 74 | 75 | const mapStateToProps = state => ({ 76 | config: state.configuration, 77 | settings: state.settings 78 | }) 79 | 80 | const mapDispatchToProps = dispatch => ({ 81 | onFetchCompleted: (category, movies) => { 82 | dispatch(movieFetched(category, movies)) 83 | }, 84 | onConfigFetched: config => { 85 | dispatch(configFetched(config)) 86 | } 87 | }) 88 | 89 | export default connect(mapStateToProps, mapDispatchToProps)(SplashScreen) 90 | -------------------------------------------------------------------------------- /src/components/base/Details.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { 3 | ActivityIndicator, 4 | Button, 5 | ScrollView, 6 | StyleSheet, 7 | Text, 8 | View, 9 | Linking, 10 | Platform 11 | } from "react-native"; 12 | import axios from "axios"; 13 | import { styles } from "react-native-theme"; 14 | 15 | import BackgroundImage from "../common/BackgroundImage"; 16 | import ShowOverview from "../common/ShowOverview"; 17 | import HorizontalImageList from "../common/ImageList"; 18 | import CastList from "../common/CastList"; 19 | import TrailerList from "../common/TrailerList"; 20 | import CastDetails from "../common/CastDetails"; 21 | import { getShowDetails, getShowCredits } from "../../services/shows"; 22 | 23 | import { getUriPopulated } from "../../utilities/utils"; 24 | import { marginTop } from "../../styles/styles"; 25 | 26 | class Details extends Component { 27 | constructor(props) { 28 | super(props); 29 | this.state = { 30 | opacity: 1, 31 | blur: 0 32 | }; 33 | } 34 | 35 | componentDidMount() { 36 | console.log("Override!!"); 37 | } 38 | 39 | getSpecialComponent() { 40 | console.log("Override!!"); 41 | } 42 | 43 | fetchDetails(route, id) { 44 | const { onDetailsFetched, currentTab, config } = this.props; 45 | 46 | getShowDetails(route, id) 47 | .then(({ data }) => { 48 | const { images, videos } = data; 49 | data.images = getUriPopulated(images.backdrops, config, "backdropSize"); 50 | data.videos = this.formVideoUrls(videos.results); 51 | onDetailsFetched(data, "imagesAndVideos", currentTab); 52 | }) 53 | .catch(error => { 54 | console.log(error.response); 55 | }); 56 | 57 | getShowCredits(route, id) 58 | .then(({ data }) => { 59 | const { crew, cast } = data; 60 | const people = { 61 | directors: getUriPopulated( 62 | crew.filter(member => member.job === "Director"), 63 | config, 64 | "profileSize" 65 | ), 66 | casts: getUriPopulated( 67 | cast.sort((a, b) => a.order - b.order), 68 | config, 69 | "profileSize" 70 | ) 71 | }; 72 | onDetailsFetched(people, "directorsAndCast", currentTab); 73 | }) 74 | .catch(error => { 75 | console.log(error.response); 76 | }); 77 | } 78 | 79 | /** 80 | * Forms video urls 81 | */ 82 | formVideoUrls(videos) { 83 | const filteredVideos = videos.filter(video => video.site === "YouTube"); 84 | return filteredVideos.map(video => { 85 | return { 86 | name: video.name, 87 | url: `https://www.youtube.com/embed/${video.key}?&autoplay=1` 88 | }; 89 | }); 90 | } 91 | 92 | showCastDetails(cast) { 93 | this.props.onCastSelected(cast, this.props.currentTab); 94 | } 95 | 96 | playVideo(url) { 97 | if (Platform.OS === "ios") { 98 | this.props.navigation.navigate("VideoPlayer", { url }); 99 | } else if (Platform.OS === "android") { 100 | Linking.openURL(url).catch(err => console.log("An error occurred", err)); 101 | } 102 | } 103 | 104 | handleOnScroll = e => { 105 | const yOffset = e.nativeEvent.contentOffset.y; 106 | const blurConstant = 10; 107 | // If Y scroll position is more than detail poster then blur it 108 | if (yOffset > marginTop) { 109 | this.setState({ opacity: 0, blur: blurConstant }); 110 | } else { 111 | const opacity = 1 - yOffset / marginTop; 112 | const blur = parseInt(yOffset * blurConstant / marginTop, 10); 113 | this.setState({ opacity, blur }); 114 | } 115 | }; 116 | 117 | render() { 118 | const { 119 | config: { 120 | image: { secureBaseUrl, posterSizeForBackground }, 121 | style: { backdropSize } 122 | }, 123 | details: { 124 | title, 125 | images, 126 | videos, 127 | release_date, 128 | casts, 129 | first_air_date, 130 | runtime, 131 | vote_average, 132 | overview, 133 | poster_path 134 | } 135 | } = this.props; 136 | const bgImage = `${secureBaseUrl}${posterSizeForBackground}/${poster_path}`; 137 | 138 | return ( 139 | 140 | 145 | 146 | 147 | { 148 | // TODO - Disabling onScroll blur. Need better solution 149 | /* onScroll={this.handleOnScroll} scrollEventThrottle={160} */ 150 | } 151 | 152 | {title} 153 | 154 | 159 | 160 | {overview} 161 | 162 | 167 | 168 | 172 | 173 | {this.getSpecialComponent()} 174 | 175 | 180 | 181 | 182 | 183 | ); 184 | } 185 | } 186 | 187 | export default Details; 188 | -------------------------------------------------------------------------------- /src/components/base/Image.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Image, View, StyleSheet} from 'react-native' 3 | import PropTypes from 'prop-types' 4 | 5 | const grey = { 6 | lighter: '#FAFAFA', 7 | light: '#F5F5F5', 8 | normal: '#EEEEEE', 9 | darker: '#E0E0E0', 10 | dark: '#BDBDBD' 11 | } 12 | 13 | class ImageComponent extends Component { 14 | constructor (props) { 15 | super(props) 16 | this.state = { 17 | loading: false 18 | } 19 | } 20 | 21 | onLoading = () => { 22 | this.setState({loading: true}) 23 | } 24 | 25 | loadSuccess = () => { 26 | this.setState({loading: false}) 27 | } 28 | 29 | render () { 30 | const {url, ...others} = this.props 31 | const {loading} = this.state 32 | 33 | return ( 34 | 35 | 44 | {loading && } 45 | 46 | ) 47 | } 48 | } 49 | 50 | ImageComponent.propTypes = { 51 | url: PropTypes.string 52 | } 53 | 54 | const style = StyleSheet.create({ 55 | // '@keyframes loading': { 56 | // '0%': {backgroundColor: grey.light}, 57 | // '20%': {backgroundColor: grey.normal}, 58 | // '40%': {backgroundColor: grey.darker}, 59 | // '60%': {backgroundColor: grey.dark}, 60 | // '85%': {backgroundColor: grey.darker}, 61 | // '100%': {backgroundColor: grey.normal} 62 | // }, 63 | loading: { 64 | // animationDuration: `1s`, 65 | // animationName: `loading`, 66 | // animationIterationCount: `infinite`, 67 | // animationTimingFunction: 'ease-out' 68 | backgroundColor: '#FFFFFF', 69 | width: 80, 70 | height: 80, 71 | borderRadius: 40 72 | }, 73 | }) 74 | 75 | export default ImageComponent 76 | -------------------------------------------------------------------------------- /src/components/base/Shows.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import { 3 | ActivityIndicator, 4 | ScrollView 5 | } from 'react-native' 6 | import axios from 'axios' 7 | import {styles} from 'react-native-theme' 8 | 9 | import HorizontalImageList from '../common/ImageList' 10 | import Carousel from '../common/Carousel' 11 | import { getUriPopulated } from '../../utilities/utils' 12 | import { getShows } from '../../services/shows' 13 | 14 | import { primaryColor } from '../../styles/styles' 15 | 16 | String.prototype.toTitle = function () { 17 | return this.replace(/([A-Z])/g, ' $1').replace(/(.)/, c => c.toUpperCase()) 18 | } 19 | 20 | class Shows extends Component { 21 | componentDidMount () { 22 | console.log('Need to override this in base class') 23 | } 24 | 25 | fetch (category, route) { 26 | const { onFetchCompleted, config, settings } = this.props 27 | getShows(route, settings.language, settings.region) 28 | .then(({data}) => { 29 | onFetchCompleted(category, 30 | getUriPopulated(data.results, config, 'posterSizeForImageList')) 31 | }) 32 | .catch(error => console.log(error.response)) 33 | } 34 | 35 | showDetails (show) { 36 | console.log('need to override this in base class') 37 | } 38 | 39 | showAll (category, shows) { 40 | console.log('need to override this in base class') 41 | } 42 | 43 | render () { 44 | const { isFetching, onShowDetails, categories, config } = this.props 45 | 46 | return ( 47 | isFetching 48 | ? 49 | 50 | 51 | : 52 | 56 | {Object.keys(categories).map((category, index) => ( 57 | 67 | ))} 68 | 69 | ) 70 | } 71 | } 72 | 73 | export default Shows 74 | -------------------------------------------------------------------------------- /src/components/common/BackgroundImage.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Image, StyleSheet, Text} from 'react-native' 3 | import PropTypes from 'prop-types' 4 | 5 | const BackgroundImage = (props) => { 6 | return ( 7 | 13 | ) 14 | } 15 | 16 | BackgroundImage.propTypes = { 17 | uri: PropTypes.string.isRequired, 18 | opacity: PropTypes.number, 19 | blur: PropTypes.number 20 | } 21 | 22 | const Style = StyleSheet.create({ 23 | absoluteImage: { 24 | position: 'absolute', 25 | top: 0, 26 | bottom: 0, 27 | left: 0, 28 | right: 0, 29 | justifyContent: 'center', 30 | alignItems: 'center' 31 | } 32 | }) 33 | 34 | export default BackgroundImage 35 | -------------------------------------------------------------------------------- /src/components/common/Carousel.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { 4 | Dimensions, 5 | ScrollView, StyleSheet, 6 | Text, TouchableOpacity, 7 | View 8 | } from 'react-native' 9 | import * as _ from 'lodash' 10 | import {styles} from 'react-native-theme' 11 | import ImageLoad from 'react-native-image-placeholder' 12 | 13 | class Carousel extends Component { 14 | componentDidMount () { 15 | this.currentIndex = 0 16 | 17 | const scrollNext = () => { 18 | const children = _.get(this, 'scrollView.props.children', false) 19 | const {width} = this.props.carouselStyle 20 | 21 | if (children) { 22 | if ((this.currentIndex + 1) < children.length) { 23 | this.currentIndex += 1 24 | } else { 25 | this.currentIndex = 0 26 | } 27 | } 28 | this.scrollView && 29 | this.scrollView.scrollTo({x: this.currentIndex * width, y: 0, animated: false}) 30 | clearTimeout(this.scrollTimeout) 31 | this.scrollTimeout = setTimeout(scrollNext, 5000) 32 | } 33 | scrollNext() 34 | } 35 | 36 | componentDidUpdate () { 37 | const {width} = this.props.carouselStyle 38 | this.scrollView && 39 | this.scrollView.scrollTo({x: this.currentIndex * width, y: 0, animated: false}) 40 | } 41 | 42 | componentDidUnMount () { 43 | clearTimeout(this.scrollTimeout) 44 | } 45 | 46 | render () { 47 | const { onPress, images, carouselStyle } = this.props 48 | return ( 49 | this.scrollView = ref} 53 | showsHorizontalScrollIndicator={false} 54 | style={style.flexContainer}> 55 | 56 | {images.map((image, index) => ( 57 | onPress(image)}> 61 | 62 | 63 | ))} 64 | 65 | ) 66 | } 67 | } 68 | 69 | const ImageWithTitle = (props) => ( 70 | 71 | 75 | 76 | 77 | {props.image.original_title || props.image.original_name} 78 | 79 | 80 | 81 | ) 82 | 83 | const style = StyleSheet.create({ 84 | absoluteTitle: { 85 | position: 'absolute', 86 | top: 0, 87 | bottom: 0, 88 | left: 0, 89 | right: 10, 90 | justifyContent: 'flex-end', 91 | alignItems: 'flex-end' 92 | }, 93 | titleText: { 94 | fontSize: 20, 95 | backgroundColor: 'transparent', 96 | color: 'white' 97 | } 98 | }) 99 | 100 | const mapStateToProps = state => ({carouselStyle: state.configuration.style.carousel}) 101 | export default connect(mapStateToProps)(Carousel) 102 | -------------------------------------------------------------------------------- /src/components/common/CastDetails.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {NavigationActions} from 'react-navigation' 3 | import {connect} from 'react-redux' 4 | import {bindActionCreators} from 'redux' 5 | import {View, Text, Image, ActivityIndicator, ScrollView} from 'react-native' 6 | import axios from 'axios' 7 | import {styles} from 'react-native-theme' 8 | 9 | import HorizontalImageList from './ImageList' 10 | import {getUriPopulated} from '../../utilities/utils' 11 | import {getCastDetails, getCastKnowFor} from '../../services/person' 12 | import { 13 | castDetailsFetched, 14 | fetchingCastDetails, 15 | selectedTvShow, 16 | selectedMovie, 17 | searchItemDetailsFetched, 18 | fetchCastDetails} from '../../Actions' 19 | 20 | import {primaryColor} from '../../styles/styles' 21 | 22 | class CastDetails extends Component { 23 | componentDidMount () { 24 | const castId = this.props.details.id 25 | this.props.fetchCastDetails(castId) 26 | 27 | const { 28 | config, 29 | currentTab, 30 | config: { 31 | image: { 32 | secureBaseUrl, 33 | posterSizeForBackground 34 | } 35 | } 36 | } = this.props 37 | 38 | if (currentTab !== 'Search') // search tab already has the data available 39 | { this.props.onFetching(currentTab) } 40 | 41 | // fetch Cast Details 42 | getCastDetails(castId) 43 | .then(({data}) => { 44 | data.imageSrc = `${secureBaseUrl}${posterSizeForBackground}${data['profile_path']}` 45 | this.props.onDetailsFetched(data, 'bio', this.props.currentTab) 46 | }) 47 | .catch(error => console.log(error.response)) 48 | 49 | // fetch Casts other movies 50 | getCastKnowFor(castId) 51 | .then(({data}) => { 52 | const movieList = [ 53 | ...data.cast, 54 | ...data.crew 55 | ] 56 | this.props.onDetailsFetched(getUriPopulated(movieList, config, 'posterSizeForImageList'), 57 | 'movies', 58 | this.props.currentTab) 59 | }) 60 | .catch(error => console.log(error.response)) 61 | } 62 | 63 | showDetails(item) { 64 | const { onShowDetails, currentTab, searchIndex } = this.props 65 | onShowDetails(item, currentTab, searchIndex) 66 | } 67 | 68 | render () { 69 | const {isFetching, details, config} = this.props 70 | 71 | if (isFetching) { 72 | return ( 73 | 74 | 75 | 76 | ) 77 | } 78 | 79 | return ( 80 | 82 | 83 | 84 | 89 | 90 | {details.name} 91 | 92 | 93 | {details.birthday} 94 | 95 | 96 | {details.place_of_birth} 97 | 98 | 99 | 100 | 101 | 102 | {details.biography} 103 | 104 | 105 | 111 | 112 | 113 | 114 | ) 115 | } 116 | } 117 | 118 | const mapStateToProps = state => { 119 | const currentTab = state.tabNavHelper.currentTab 120 | const mapScreenToStateProps = { 121 | 'Movies': 'movies', 122 | 'TvShows': 'tvShows', 123 | 'Search': 'search' 124 | } 125 | 126 | return { 127 | config: state.configuration, 128 | currentTab, 129 | searchIndex: (currentTab === 'Search') && state.search.selectedIndex, 130 | ...state[mapScreenToStateProps[currentTab]].cast, 131 | } 132 | } 133 | 134 | const mapDispatchToProps = dispatch => ({ 135 | onFetching: (currentTab) => { 136 | dispatch(fetchingCastDetails(currentTab)) 137 | }, 138 | onDetailsFetched: (details, category, currentTab) => { 139 | if (currentTab === 'Search') { 140 | dispatch(searchItemDetailsFetched(details, category)) 141 | } else { 142 | dispatch(castDetailsFetched(details, category, currentTab)) 143 | } 144 | }, 145 | onShowDetails: (item, currentTab, searchIndex) => { 146 | routeName = (currentTab === 'Movies') ? 'MovieDetails' : 'TvShowDetails' 147 | 148 | if (currentTab === 'Search' && searchIndex) { 149 | if (searchIndex === 0) { 150 | routeName = 'MovieDetails' 151 | } else if (searchIndex === 1) { 152 | routeName = 'TvShowDetails' 153 | } else { 154 | // For people search - known for results only have movie results and not tv shows 155 | routeName = 'MovieDetails' 156 | } 157 | } 158 | funcToCall = (routeName === 'TvShowDetails') ? selectedTvShow : selectedMovie 159 | 160 | dispatch(funcToCall(item)) 161 | dispatch(NavigationActions.navigate({ 162 | routeName: routeName, 163 | params: { 164 | name: item.name || item.original_title, 165 | id: item.id 166 | } 167 | })) 168 | }, 169 | fetchCastDetails 170 | }) 171 | 172 | export default connect(mapStateToProps, mapDispatchToProps)(CastDetails) 173 | -------------------------------------------------------------------------------- /src/components/common/CastList.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import { 3 | Image, 4 | Text, 5 | TouchableOpacity, 6 | ScrollView, 7 | StyleSheet, 8 | View, 9 | Alert 10 | } from 'react-native' 11 | import PropTypes from 'prop-types' 12 | import {styles} from 'react-native-theme' 13 | // import Image from './../base/Image' 14 | 15 | const CastList = (props) => { 16 | return ( 17 | 18 | 19 | {props.title} 20 | 21 | 22 | {props.items.map((item, index) => ( 23 | props.onPress(item)}> 27 | 32 | 36 | {item.name} 37 | 38 | 39 | ))} 40 | 41 | 42 | ) 43 | } 44 | 45 | export default CastList 46 | -------------------------------------------------------------------------------- /src/components/common/ImageList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { 4 | Image, 5 | FlatList, 6 | Text, TouchableOpacity, 7 | ScrollView, StyleSheet, 8 | View 9 | } from 'react-native' 10 | import {styles} from 'react-native-theme' 11 | import * as _ from 'lodash' 12 | 13 | import { TouchableImage, TouchableText } from './Touchable' 14 | 15 | const HorizontalImageList = (props) => ( 16 | 17 | 18 | <ScrollView horizontal 19 | showsHorizontalScrollIndicator={false} 20 | style={Styles.posterList}> 21 | {props.images.map((image, index) => ( 22 | props.isTouchableImage 23 | ? <TouchableImage 24 | key={index} 25 | style={[styles.imagePlaceholder, props.style]} 26 | onPress={() => props.onPress(image)} 27 | uri={image.uri} 28 | /> 29 | : <Image 30 | key={index} 31 | style={[styles.imagePlaceholder, props.style]} 32 | source={{uri: _.isString(image) ? image : image.uri}} 33 | /> 34 | ))} 35 | </ScrollView> 36 | </View> 37 | ) 38 | 39 | // TODO: See All should be of normatText. Nesting of text should fix it 40 | // since TouchableText is involved, the situaion gets complicated.Fix this 41 | const Title = (props) => ( 42 | <View style={Styles.titleContainer}> 43 | <Text style={[styles.text, styles.headingText]}> 44 | {props.title} 45 | </Text> 46 | { 47 | props.hasSeeAllOption 48 | ? <TouchableText 49 | onPress={() => props.onShowAll(props.title, props.images)} 50 | text='See All >' 51 | /> 52 | : <Text>''</Text> 53 | } 54 | </View> 55 | ) 56 | 57 | Title.propTypes = HorizontalImageList.propTypes = { 58 | title: PropTypes.string.isRequired, 59 | images: PropTypes.array.isRequired, 60 | isTouchableButton: PropTypes.bool, 61 | hasSeeAllOption: PropTypes.bool, 62 | onPress: (props, thisProp) => { 63 | if (props['isTouchableButton'] && 64 | (!props[thisProp] || typeof (props[thisProp]) !== 'function')) { 65 | return new Error('onPress is required when isTouchableButton is true!') 66 | } else { 67 | return null 68 | } 69 | }, 70 | onShowAll: (props, thisProp) => { 71 | if (props['hasSeeAllOption'] && 72 | (!props[thisProp] || typeof (props[thisProp]) !== 'function')) { 73 | return new Error('onShowAll is required when hasSeeAllOption is true!') 74 | } else { 75 | return null 76 | } 77 | } 78 | } 79 | 80 | // Note, Changing numColumns on the fly is not supported. Change the key prop 81 | // on FlatList when changing the number of columns to force refresh 82 | const FlatImageList = (props) => ( 83 | <FlatList 84 | key={'dummy_key_' + props.numColumns} 85 | style={props.style.bgColor} 86 | numColumns={props.numColumns} 87 | data={props.images} 88 | renderItem={({item}) => 89 | <TouchableImage 90 | key={item.id} 91 | onPress={() => props.onPress(item)} 92 | style={[props.style.imageStyle, Styles.flatList]} 93 | uri={item.uri} 94 | /> 95 | } 96 | keyExtractor={(item, index) => index} 97 | /> 98 | ) 99 | 100 | FlatImageList.protoTypes = { 101 | images: PropTypes.array.isRequired, 102 | onPress: PropTypes.func.isRequired, 103 | numColumns: PropTypes.number.isRequired, 104 | style: PropTypes.oneOfType([ 105 | PropTypes.number, 106 | PropTypes.object 107 | ]) 108 | } 109 | 110 | const Styles = StyleSheet.create({ 111 | // TODO: does ios have a standard? if so use that 112 | // Image size for poster size w92 on TMDB 113 | posterList: { 114 | flexDirection: 'row' 115 | }, 116 | titleContainer: { 117 | flexDirection: 'row', 118 | justifyContent: 'space-between', 119 | marginLeft: 5 120 | }, 121 | flatList: { 122 | justifyContent: 'space-between' 123 | } 124 | }) 125 | 126 | export {FlatImageList} 127 | export default HorizontalImageList 128 | -------------------------------------------------------------------------------- /src/components/common/ListItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, Text, TouchableOpacity } from 'react-native' 3 | import { Icon } from 'react-native-elements' 4 | import { styles } from 'react-native-theme' 5 | 6 | const ListItem = ({name, value, selected}) => { 7 | return ( 8 | <View style={styles.listContainer}> 9 | <View style={styles.listTitle}> 10 | <Text style={[styles.text, styles.subHeadingText]}> 11 | {name} 12 | </Text> 13 | </View> 14 | <View style={styles.listValue}> 15 | <Text style={[styles.text, styles.normalText]}> 16 | {value} 17 | </Text> 18 | </View> 19 | 20 | {name && (name === selected) && 21 | <View> 22 | <Icon 23 | name='check' 24 | color='#00aced' 25 | /> 26 | </View>} 27 | </View> 28 | ) 29 | } 30 | 31 | const MovieDBListItem = (props) => { 32 | const {name, value} = props 33 | return ( 34 | <View> 35 | <ListItem name={name} value={value}/> 36 | </View> 37 | ) 38 | } 39 | 40 | const TouchableListItem = ({name, value, selected, onPress}) => { 41 | return ( 42 | <TouchableOpacity style={styles.listContainer} onPress={onPress}> 43 | <ListItem name={name} value={value} selected={selected} /> 44 | </TouchableOpacity> 45 | ) 46 | } 47 | 48 | export { 49 | MovieDBListItem, 50 | TouchableListItem 51 | } 52 | -------------------------------------------------------------------------------- /src/components/common/ShareButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Share, Platform} from 'react-native' 3 | import {Icon} from 'react-native-elements' 4 | import PropTypes from 'prop-types' 5 | 6 | const os = Platform.OS 7 | 8 | const onShare = (title, media_type, media_id) => { 9 | Share.share({ 10 | message: `Checkout ${title} on @themoviedb via MovieDB.`, 11 | url: `https://www.themoviedb.org/${media_type}/${media_id}` 12 | }, { 13 | // Android only: 14 | dialogTitle: 'Share Movie Info', 15 | // iOS only: 16 | excludedActivityTypes: [ 17 | // 'com.apple.UIKit.activity.PostToTwitter' 18 | ] 19 | }) 20 | } 21 | 22 | const ShareButton = (props) => { 23 | const {name, type, id} = props 24 | return ( 25 | <Icon 26 | name={(os === 'ios') ? 'ios-share-outline' : 'share'} 27 | type={(os === 'ios') ? 'ionicon' : ''} 28 | color='#32CD32' 29 | size={30} 30 | underlayColor='#222222' 31 | onPress={() => { onShare(name, type, id) }} /> 32 | ) 33 | } 34 | 35 | ShareButton.propTypes = { 36 | name: PropTypes.string.isRequired, 37 | type: PropTypes.string.isRequired, 38 | id: PropTypes.number.isRequired 39 | } 40 | 41 | export default ShareButton 42 | -------------------------------------------------------------------------------- /src/components/common/ShowOverview.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {View, Text, StyleSheet} from 'react-native' 3 | import {Icon} from 'react-native-elements' 4 | import PropTypes from 'prop-types' 5 | import {styles} from 'react-native-theme' 6 | 7 | import {primaryColor} from '../../styles/styles' 8 | 9 | const ShowOverview = (props) => { 10 | return ( 11 | <View style={Style.container}> 12 | <View style={Style.infoItems}> 13 | <Icon name='event' color={primaryColor} size={30}/> 14 | <Text style={[styles.text, Style.infoText]}> 15 | {props.date} 16 | </Text> 17 | </View> 18 | {props.runtime && <View style={Style.infoItems}> 19 | <Icon name='schedule' color={primaryColor}/> 20 | <Text style={[styles.text, Style.infoText]}> 21 | {props.runtime} 22 | Min 23 | </Text> 24 | </View> 25 | } 26 | <View style={Style.infoItems}> 27 | {props.ratings 28 | ? <Icon name='stars' color={primaryColor}/> 29 | : props.episodes 30 | ? <Text style={[styles.text, Style.titleText]}>Episodes: 31 | </Text> 32 | : null 33 | } 34 | <Text style={[styles.text, Style.infoText]}> 35 | {props.ratings || props.episodes} 36 | </Text> 37 | </View> 38 | </View> 39 | ) 40 | } 41 | 42 | const Style = StyleSheet.create({ 43 | container: { 44 | display: 'flex', 45 | paddingTop: 6, 46 | marginBottom: 15, 47 | flexDirection: 'row', 48 | justifyContent: 'space-between', 49 | marginLeft: 5 50 | }, 51 | infoItems: { 52 | display: 'flex', 53 | flexDirection: 'row' 54 | }, 55 | infoText: { 56 | fontSize: 14, 57 | lineHeight: 24, 58 | paddingLeft: 3 59 | }, 60 | titleText: { 61 | fontSize: 18, 62 | color: primaryColor 63 | } 64 | }) 65 | 66 | ShowOverview.propTypes = { 67 | date: PropTypes.string.isRequired, 68 | runtime: PropTypes.number, 69 | ratings: PropTypes.number, 70 | episodes: PropTypes.number 71 | } 72 | 73 | export default ShowOverview 74 | -------------------------------------------------------------------------------- /src/components/common/Touchable.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import {Image, Text, TouchableOpacity} from 'react-native' 4 | import {styles} from 'react-native-theme' 5 | import ImageLoad from 'react-native-image-placeholder' 6 | 7 | export const TouchableImage = (props) => ( 8 | <TouchableOpacity 9 | onPress={() => props.onPress()}> 10 | <ImageLoad 11 | style={props.style} 12 | source={{uri: props.uri}} 13 | /> 14 | </TouchableOpacity> 15 | ) 16 | 17 | TouchableImage.propTypes = { 18 | style: PropTypes.oneOfType([ 19 | PropTypes.object, 20 | PropTypes.number, 21 | PropTypes.array 22 | ]).isRequired, 23 | uri: PropTypes.string.isRequired, 24 | onPress: PropTypes.func.isRequired 25 | } 26 | 27 | export const TouchableText = (props) => ( 28 | <TouchableOpacity onPress={() => props.onPress()} style={styles.textStickToBottom}> 29 | <Text style={[styles.text, styles.normalText]}> 30 | {props.text} 31 | </Text> 32 | </TouchableOpacity> 33 | ) 34 | 35 | TouchableText.propTypes = { 36 | text: PropTypes.string.isRequired, 37 | onPress: PropTypes.func.isRequired 38 | } 39 | -------------------------------------------------------------------------------- /src/components/common/TrailerItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Text, View, TouchableOpacity} from 'react-native' 3 | import {Icon} from 'react-native-elements' 4 | import {connect} from 'react-redux' 5 | import {styles} from 'react-native-theme' 6 | 7 | const TrailerItem = ({style, video, onPlay}) => { 8 | return ( 9 | <View> 10 | <Text style={[styles.text, styles.normalText, styles.trailerTitle]}> 11 | {video.name} 12 | </Text> 13 | <TouchableOpacity onPress={() => { onPlay(video.url) }}> 14 | <View 15 | style={[style.backdropSize, styles.centerContentContainer, styles.trailerContainer]}> 16 | <Icon 17 | name='youtube-play' 18 | type='font-awesome' 19 | size={50} 20 | color='#ff0000' 21 | style={styles.trailerPlayIcon} /> 22 | </View> 23 | </TouchableOpacity> 24 | </View> 25 | ) 26 | } 27 | 28 | const mapStateToProps = state => ({ 29 | style: state.configuration.style 30 | }) 31 | export default connect(mapStateToProps)(TrailerItem) 32 | -------------------------------------------------------------------------------- /src/components/common/TrailerList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {View, Text, ScrollView} from 'react-native' 3 | import {styles} from 'react-native-theme' 4 | 5 | import TrailerItem from './TrailerItem' 6 | 7 | const Trailer = (props) => { 8 | const showTrailers = () => { 9 | return ( 10 | <ScrollView 11 | horizontal 12 | showsHorizontalScrollIndicator={false} 13 | style={styles.posterList}> 14 | 15 | {props.videos.map((video, index) => ( 16 | <TrailerItem key={index} video={video} onPlay={props.playVideo} /> 17 | ))} 18 | </ScrollView> 19 | ) 20 | } 21 | return ( 22 | <View> 23 | <Text style={[styles.text, styles.headingText, styles.detailHeadings]}>Trailers</Text> 24 | 25 | {props.videos.length 26 | ? showTrailers() 27 | : <View style={{height: 100, alignContent: 'center', alignItems: 'center'}}> 28 | <Text 29 | style={[styles.text, styles.subHeadingText]}> 30 | No Trailers 31 | </Text> 32 | </View> 33 | } 34 | </View> 35 | ) 36 | } 37 | 38 | export default Trailer 39 | -------------------------------------------------------------------------------- /src/components/common/VideoPlayer.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {WebView, StyleSheet} from 'react-native' 3 | import {styles} from 'react-native-theme' 4 | 5 | import Orientation from 'react-native-orientation' 6 | 7 | export default class VideoPlayer extends Component { 8 | constructor (props) { 9 | super(props) 10 | this.state = { 11 | url: this.props.navigation.state.params.url 12 | } 13 | } 14 | 15 | componentWillMount () { 16 | Orientation.lockToLandscape() 17 | } 18 | 19 | componentWillUnmount () { 20 | Orientation.lockToPortrait() 21 | } 22 | 23 | render () { 24 | return ( 25 | <WebView 26 | style={styles.flexContainer} 27 | javaScriptEnabled 28 | domStorageEnabled 29 | allowsInlineMediaPlayback 30 | mediaPlaybackRequiresUserAction={false} 31 | source={{uri: this.state.url}} 32 | // source={{html: `<iframe width="560" height="315" 33 | // src="https://www.youtube.com/embed/D6Ac5JpCHmI?&autoplay=1&controls=1"></iframe>` }} 34 | /> 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/movies/AllMovies.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Dimensions } from 'react-native' 3 | import { StackNavigator } from 'react-navigation' 4 | import { styles } from 'react-native-theme' 5 | import { connect } from 'react-redux' 6 | import { NavigationActions } from 'react-navigation' 7 | 8 | import { FlatImageList } from '../common/ImageList' 9 | import { selectedMovie } from '../../Actions' 10 | 11 | // return device width and height 12 | const { height, width } = Dimensions.get('window') 13 | const numColumns = parseInt(width / (92 + 5 * 2)) 14 | 15 | class AllMovies extends Component { 16 | render() { 17 | const { onShowDetails, categories, config } = this.props 18 | const { category } = this.props.navigation.state.params 19 | 20 | return ( 21 | // TODO: Move config.style to FlatImageList? Think customization ! 22 | <FlatImageList 23 | numColumns={config.image.numColumns} 24 | style={{ 25 | bgColor: styles.screenBackgroundColor, 26 | imageStyle: config.style.posterSize 27 | }} 28 | images={categories[category.toCategory()]} 29 | onPress={onShowDetails.bind(this)} 30 | /> 31 | ) 32 | } 33 | } 34 | 35 | const mapStateToProps = state => ({ 36 | ...state.movies, 37 | config: state.configuration 38 | }) 39 | 40 | const mapDispatchToProps = dispatch => ({ 41 | onShowDetails: movie => { 42 | dispatch(selectedMovie(movie)) 43 | dispatch( 44 | NavigationActions.navigate({ 45 | routeName: 'MovieDetails', 46 | params: { 47 | name: movie.name, 48 | id: movie.id 49 | } 50 | }) 51 | ) 52 | } 53 | }) 54 | 55 | export default connect(mapStateToProps, mapDispatchToProps)(AllMovies) 56 | -------------------------------------------------------------------------------- /src/components/movies/MovieDetails.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import { connect } from 'react-redux' 3 | import { NavigationActions } from 'react-navigation' 4 | 5 | import Details from '../base/Details' 6 | import CastList from '../common/CastList' 7 | import { 8 | castSelected, 9 | searchItemDetailsFetched, 10 | movieDetailsFetched 11 | } from '../../Actions' 12 | 13 | class MovieDetails extends Details { 14 | componentDidMount () { 15 | this.fetchDetails('/movie/', this.props.details.id || this.props.navigation.state.params.id) 16 | } 17 | 18 | getSpecialComponent () { 19 | return <CastList 20 | title='Director' 21 | items={this.props.details.directors || []} 22 | onPress={this.showCastDetails.bind(this)}/> 23 | } 24 | } 25 | 26 | const mapStateToProps = ({tabNavHelper, search, movies, configuration}) => ({ 27 | details: tabNavHelper.currentTab === 'Search' ? search.details : movies.details, 28 | currentTab: tabNavHelper.currentTab, 29 | config: configuration 30 | }) 31 | 32 | const mapDispatchToProps = dispatch => ({ 33 | onDetailsFetched: (details, category, currentTab) => { 34 | if (currentTab === 'Search') { 35 | dispatch(searchItemDetailsFetched(details, category)) 36 | } else { 37 | dispatch(movieDetailsFetched(details, category)) 38 | } 39 | }, 40 | onCastSelected: (cast, currentTab) => { 41 | dispatch(castSelected(cast, currentTab)) 42 | dispatch(NavigationActions.navigate({routeName: 'CastDetails', 43 | params: { 44 | name: cast.name, 45 | id: cast.id 46 | }})) 47 | } 48 | }) 49 | 50 | export default connect(mapStateToProps, mapDispatchToProps)(MovieDetails) 51 | -------------------------------------------------------------------------------- /src/components/movies/Movies.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import Shows from '../base/Shows' 3 | import {connect} from 'react-redux' 4 | import {selectedMovie, fetchingMovies, movieFetched} from '../../Actions' 5 | import {NavigationActions} from 'react-navigation' 6 | 7 | class Movies extends Shows { 8 | constructor (props) { 9 | super(props) 10 | this.carouselCategory = 'nowShowing' 11 | } 12 | 13 | /** 14 | * @overrides 15 | */ 16 | componentDidMount () { 17 | // calls base class functions 18 | this.fetch('nowShowing', '/movie/now_playing') 19 | this.fetch('comingSoon', '/movie/upcoming') 20 | this.fetch('popular', '/movie/popular') 21 | } 22 | 23 | /** 24 | * @overrides 25 | */ 26 | showAll (category) { 27 | this 28 | .props 29 | .navigation 30 | .navigate('AllMovies', {category: category}) 31 | } 32 | } 33 | 34 | const mapStateToProps = state => ({ 35 | ...state.movies, 36 | config: state.configuration, 37 | settings: state.settings 38 | }) 39 | 40 | const mapDispatchToProps = dispatch => ({ 41 | onFetching: () => { 42 | dispatch(fetchingMovies()) 43 | }, 44 | onFetchCompleted: (category, movies) => { 45 | dispatch(movieFetched(category, movies)) 46 | }, 47 | onShowDetails: (movie) => { 48 | dispatch(selectedMovie(movie)) 49 | dispatch(NavigationActions.navigate({ 50 | routeName: 'MovieDetails', 51 | params: { 52 | id: movie.id, 53 | name: movie.original_title 54 | } 55 | })) 56 | } 57 | }) 58 | 59 | export default connect(mapStateToProps, mapDispatchToProps)(Movies) 60 | -------------------------------------------------------------------------------- /src/components/search/PopularSearch.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { View, TouchableOpacity, Text } from 'react-native' 3 | import { styles } from 'react-native-theme' 4 | 5 | const PopularSearch = ({ data, onSelect }) => { 6 | return ( 7 | <View style={[styles.popularSearchContainer]}> 8 | <Text style={[styles.text, styles.headingText]}>Popular Searches</Text> 9 | 10 | {data.map(popular => ( 11 | <TouchableOpacity 12 | key={popular.id} 13 | style={[styles.popularSearch]} 14 | onPress={() => { 15 | onSelect(popular.original_title || popular.original_name) 16 | }} 17 | > 18 | <Text style={[styles.text, styles.subHeadingText]}> 19 | {popular.original_title || popular.original_name} 20 | </Text> 21 | </TouchableOpacity> 22 | ))} 23 | </View> 24 | ) 25 | } 26 | 27 | export default PopularSearch 28 | -------------------------------------------------------------------------------- /src/components/search/Search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, StyleSheet, ActivityIndicator } from 'react-native' 3 | import { SearchBar, ButtonGroup } from 'react-native-elements' 4 | import * as _ from 'lodash' 5 | import axios from 'axios' 6 | import { NavigationActions } from 'react-navigation' 7 | import { connect } from 'react-redux' 8 | import { styles } from 'react-native-theme' 9 | 10 | import { primaryColor } from './../../styles/styles' 11 | import Constant from '../../utilities/constants' 12 | import SearchResult from './SearchResult' 13 | import { 14 | doneSearchingMoviesEtc, 15 | searchFilterChanged, 16 | searchingForMoviesEtc, 17 | searchResultSelected, 18 | selectedMovie, 19 | selectedTvShow 20 | } from '../../Actions' 21 | 22 | import { searchItem } from '../../services/search' 23 | 24 | const buttons = ['Movie', 'Tv', 'Person'] 25 | 26 | class Search extends Component { 27 | constructor(props) { 28 | super(props) 29 | this.state = { 30 | value: '' 31 | } 32 | } 33 | 34 | onTextChange = e => { 35 | // Set value 36 | this.setState({ value: e }) 37 | _.debounce(this.onSearch, 500)() 38 | } 39 | 40 | onSearch = () => { 41 | // Search only if there is any value 42 | const { value } = this.state 43 | if (value) { 44 | this.props.onSearchingForMoviesEtc() 45 | const { 46 | settings: { language, region } 47 | } = this.props 48 | 49 | searchItem(value, language, region) 50 | .then(({ data }) => { 51 | this.props.onDoneSearchingMoviesEtc(data.results) 52 | }) 53 | .catch(error => console.log(error.response.data.message)) 54 | } 55 | } 56 | 57 | onClearText = () => { 58 | this.setState({ value: '' }) 59 | } 60 | 61 | /** 62 | * Filters search results based on media_type 63 | */ 64 | filterSearchResults = (results, index) => { 65 | return results.filter(result => { 66 | return result.media_type.toLowerCase() === buttons[index].toLowerCase() 67 | }) 68 | } 69 | 70 | render() { 71 | const { 72 | results, 73 | isSearching, 74 | onFilterChanged, 75 | onSearchResultSelected, 76 | selectedIndex, 77 | config, 78 | popular 79 | } = this.props 80 | const filteredResults = this.filterSearchResults(results, selectedIndex) 81 | 82 | return ( 83 | <View style={[{ flex: 1 }, styles.screenBackgroundColor]}> 84 | <SearchBar 85 | style={{ 86 | marginTop: 20 87 | }} // lightTheme 88 | round 89 | onChangeText={this.onTextChange} 90 | onClearText={this.onClearText} 91 | placeholder="Search" 92 | value={this.state.value} 93 | /> 94 | 95 | <ButtonGroup 96 | lightTheme={false} 97 | onPress={onFilterChanged.bind(this)} 98 | selectedIndex={selectedIndex} 99 | buttons={buttons} 100 | containerStyle={{ 101 | height: 30, 102 | backgroundColor: '#e1e1e1', 103 | marginTop: 10 104 | }} 105 | /> 106 | 107 | <View> 108 | {isSearching ? ( 109 | <ActivityIndicator size="large" color={primaryColor} /> 110 | ) : ( 111 | <SearchResult 112 | config={config} 113 | items={filteredResults} 114 | popular={selectedIndex < 2 ? popular[selectedIndex] : []} 115 | onSelectPopular={this.onTextChange} 116 | onSelect={onSearchResultSelected} 117 | /> 118 | )} 119 | </View> 120 | </View> 121 | ) 122 | } 123 | } 124 | 125 | const mapStateToProps = state => ({ 126 | config: state.configuration, 127 | popular: [state.movies.categories.popular, state.tvShows.categories.popular], 128 | settings: state.settings, 129 | ...state.search 130 | }) 131 | 132 | const mapDispatchToProps = dispatch => ({ 133 | onFilterChanged: index => { 134 | dispatch(searchFilterChanged(index)) 135 | }, 136 | onSearchingForMoviesEtc: () => { 137 | dispatch(searchingForMoviesEtc()) 138 | }, 139 | onDoneSearchingMoviesEtc: results => { 140 | dispatch(doneSearchingMoviesEtc(results)) 141 | }, 142 | onSearchResultSelected: result => { 143 | const params = { 144 | name: result.name || result.title, 145 | id: result.id 146 | } 147 | 148 | dispatch(searchResultSelected(result, result.media_type)) 149 | switch (result.media_type) { 150 | case 'movie': 151 | dispatch( 152 | NavigationActions.navigate({ routeName: 'MovieDetails', params }) 153 | ) 154 | break 155 | case 'tv': 156 | dispatch( 157 | NavigationActions.navigate({ routeName: 'TvShowDetails', params }) 158 | ) 159 | break 160 | case 'person': 161 | dispatch( 162 | NavigationActions.navigate({ routeName: 'CastDetails', params }) 163 | ) 164 | break 165 | default: 166 | console.log('Unrecognised media type') 167 | break 168 | } 169 | } 170 | }) 171 | 172 | export default connect(mapStateToProps, mapDispatchToProps)(Search) 173 | -------------------------------------------------------------------------------- /src/components/search/SearchItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {View, Text, Image} from 'react-native' 3 | import {styles} from 'react-native-theme' 4 | 5 | const SearchItem = ({item, config}) => { 6 | const { secureBaseUrl, profileSize, posterSizeForImageList } = config.image 7 | const { name, title, media_type, profile_path, poster_path } = item 8 | 9 | let size = posterSizeForImageList 10 | let path = poster_path 11 | if (media_type === 'person') { 12 | size = profileSize 13 | path = profile_path 14 | } 15 | 16 | const uri = `${secureBaseUrl}${size}${path}` 17 | 18 | return ( 19 | <View style={styles.searchItem}> 20 | <View style={[styles.searchItemImage, styles.imagePlaceholder]}> 21 | <Image source={{uri}} style={styles.searchItemImage} /> 22 | </View> 23 | <View style={styles.searchItemData}> 24 | <Text style={[styles.text, styles.headingText]}>{name || title}</Text> 25 | <Text style={[styles.text, styles.normalText]}>{media_type.toUpperCase()}</Text> 26 | </View> 27 | </View> 28 | ) 29 | } 30 | 31 | export default SearchItem 32 | -------------------------------------------------------------------------------- /src/components/search/SearchResult.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {View, ScrollView, TouchableOpacity} from 'react-native' 3 | import {styles} from 'react-native-theme' 4 | 5 | import PopularSearch from './../search/PopularSearch'; 6 | import SearchItem from './SearchItem' 7 | 8 | const SearchResult = ({items, popular, config, onSelect, onSelectPopular}) => { 9 | return ( 10 | <View> 11 | <ScrollView style={styles.searchResult}> 12 | {items.length ? 13 | items.map((item, index) => ( 14 | <TouchableOpacity key={index} onPress={() => { onSelect(item) }}> 15 | <SearchItem item={item} config={config} /> 16 | </TouchableOpacity> 17 | )) 18 | : <PopularSearch data={popular} onSelect={onSelectPopular}/> 19 | } 20 | </ScrollView> 21 | </View> 22 | ) 23 | } 24 | 25 | export default SearchResult 26 | -------------------------------------------------------------------------------- /src/components/settings/RegionSettings.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {View, Text} from 'react-native' 3 | 4 | const RegionSettings = (props) => { 5 | return ( 6 | <View> 7 | <Text>Region Settings</Text> 8 | </View> 9 | ) 10 | } 11 | 12 | export default RegionSettings 13 | -------------------------------------------------------------------------------- /src/components/settings/Settings.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, ScrollView, Text, Button } from 'react-native'; 3 | import { NavigationActions } from 'react-navigation'; 4 | import { connect } from 'react-redux'; 5 | import {styles} from 'react-native-theme'; 6 | 7 | import { saveSettingsAction } from './../../Actions' 8 | import { MovieDBListItem, TouchableListItem } from './../common/ListItem'; 9 | 10 | const appInfo = [ 11 | { 12 | name: 'App Name', 13 | value: 'MovieDB' 14 | }, { 15 | name: 'App Version', 16 | value: '0.0.1' 17 | } 18 | ]; 19 | 20 | const settings = [ 21 | { 22 | name: 'Language', 23 | values: ['IN-hi', 'US-en', 'UK-en'] 24 | }, { 25 | name: 'Region', 26 | values: ['IN', 'US', 'UK'] 27 | }, { 28 | name: 'Theme', 29 | values: ['Light', 'Dark'] 30 | } 31 | ] 32 | 33 | class Settings extends Component { 34 | 35 | onSettingsChange = (values) => { 36 | this.props.saveSettingsAction(values); 37 | } 38 | 39 | render() { 40 | return ( 41 | <View style={[{flex: 1}, styles.screenBackgroundColor]}> 42 | <ScrollView style={{marginTop: 20, minHeight: 480}}> 43 | <Text style={[styles.text, styles.subHeading, styles.settingDetailsTitle]}> 44 | About 45 | </Text> 46 | {appInfo.map((info, index) => ( 47 | <MovieDBListItem name={info.name} value={info.value} key={index}/> 48 | ))} 49 | 50 | <View style={{marginTop: 20}}> 51 | <Text style={[styles.text, styles.subHeading, styles.settingDetailsTitle]}> 52 | Language and Region 53 | </Text> 54 | 55 | {settings.map(({name, values}) => ( 56 | <TouchableListItem 57 | key={name} 58 | name={name} 59 | onPress={() => { 60 | this.props.navigation.dispatch(NavigationActions.navigate({ 61 | routeName: 'SettingDetails', 62 | params: { 63 | name, 64 | values, 65 | onSelect: this.onSettingsChange 66 | } 67 | })) 68 | }}/> 69 | ))} 70 | </View> 71 | </ScrollView> 72 | </View> 73 | ); 74 | } 75 | } 76 | 77 | const mapStateToProps = ({settings}) => ({ 78 | settings 79 | }) 80 | 81 | export default connect(mapStateToProps, { saveSettingsAction })(Settings); 82 | -------------------------------------------------------------------------------- /src/components/settings/SettingsDetail.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {View, Text} from 'react-native' 3 | import {connect} from 'react-redux' 4 | import theme, {styles} from 'react-native-theme' 5 | 6 | import {fetchSettingsAction} from './../../Actions' 7 | import {TouchableListItem} from './../common/ListItem' 8 | 9 | class SettingDetails extends Component { 10 | 11 | componentDidMount() { 12 | this 13 | .props 14 | .fetchSettingsAction(); 15 | } 16 | 17 | _changeSettings = (key, value) => { 18 | let changes = {}; 19 | changes[key] = value; 20 | this 21 | .props 22 | .navigation 23 | .state 24 | .params 25 | .onSelect(Object.assign({}, this.props.settings, changes)); 26 | } 27 | 28 | render() { 29 | const {name, values} = this.props.navigation.state.params; 30 | const selected = this.props.settings[name.toLowerCase()]; 31 | 32 | return ( 33 | <View style={[{flex: 1}, styles.screenBackgroundColor]}> 34 | <View> 35 | <Text style={[styles.text, styles.subHeading, styles.settingDetailsTitle]}> 36 | {`${name} Settings`} 37 | </Text> 38 | 39 | {values.map((value) => ( 40 | <TouchableListItem 41 | key={value} 42 | name={value} 43 | selected={selected} 44 | onPress={() => { 45 | this._changeSettings(name.toLowerCase(), value) 46 | }}/>))} 47 | </View> 48 | </View> 49 | ) 50 | } 51 | } 52 | 53 | function mapStateToProps({settings}) { 54 | return {settings} 55 | } 56 | 57 | export default connect(mapStateToProps, {fetchSettingsAction})(SettingDetails); 58 | -------------------------------------------------------------------------------- /src/components/tv/AllTvShows.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Dimensions} from 'react-native' 3 | import { connect } from 'react-redux' 4 | import {styles} from 'react-native-theme' 5 | import { NavigationActions } from 'react-navigation' 6 | 7 | import { selectedTvShow } from '../../Actions' 8 | import { FlatImageList } from '../common/ImageList' 9 | 10 | // return device width and height 11 | const {height, width} = Dimensions.get('window') 12 | const numColumns = parseInt(width / (92 + (5 * 2))) 13 | 14 | class AllTvShows extends Component { 15 | render () { 16 | const { onShowDetails, categories, config } = this.props 17 | const { category } = this.props.navigation.state.params 18 | 19 | return ( 20 | <FlatImageList 21 | numColumns={config.image.numColumns} 22 | style={{ 23 | bgColor: styles.screenBackgroundColor, 24 | imageStyle: config.style.posterSize 25 | }} 26 | images={categories[category.toCategory()]} 27 | onPress={onShowDetails.bind(this)} 28 | /> 29 | ) 30 | } 31 | } 32 | 33 | const mapStateToProps = state => ({ 34 | ...state.tvShows, 35 | config: state.configuration 36 | }) 37 | 38 | const mapDispatchToProps = dispatch => ({ 39 | onShowDetails: (tvShow) => { 40 | dispatch(selectedTvShow(tvShow)) 41 | dispatch(NavigationActions.navigate({routeName: 'TvShowDetails', 42 | params: { 43 | name: tvShow.name, 44 | id: tvShow.id 45 | }})) 46 | } 47 | }) 48 | 49 | export default connect(mapStateToProps, mapDispatchToProps)(AllTvShows) 50 | -------------------------------------------------------------------------------- /src/components/tv/EpisodeItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {View, Text, Image} from 'react-native' 3 | import {connect} from 'react-redux' 4 | import {styles} from 'react-native-theme'; 5 | 6 | const EpisodeItem = ({data, config}) => { 7 | const {name, overview, still_path, episode_number} = data 8 | const {secureBaseUrl, stillSize} = config.image 9 | const episodeImg = `${secureBaseUrl}${stillSize}/${still_path}` 10 | 11 | return ( 12 | <View style={styles.episodeItem}> 13 | <View style={styles.episodePosterContainer}> 14 | <Image 15 | style={styles.episodePoster} 16 | source={{uri: episodeImg}} 17 | /> 18 | </View> 19 | <View style={styles.episodeDesc}> 20 | <Text style={[styles.text, styles.subHeadingText]}>{name}</Text> 21 | <Text style={[styles.secondaryText, styles.normalText]}>Episode #{episode_number}</Text> 22 | <Text style={[styles.text, styles.normalText]}>{overview}</Text> 23 | </View> 24 | </View> 25 | ) 26 | } 27 | 28 | const mapStateToProps = state => ({ 29 | config: state.configuration 30 | }) 31 | 32 | export default connect(mapStateToProps)(EpisodeItem) 33 | -------------------------------------------------------------------------------- /src/components/tv/EpisodeList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {ScrollView} from 'react-native' 3 | 4 | import EpisodeItem from './EpisodeItem' 5 | 6 | const EpisodeList = (props) => { 7 | return ( 8 | <ScrollView> 9 | {props.episodes.map((episode, index) => ( 10 | <EpisodeItem data={episode} key={index} /> 11 | ))} 12 | </ScrollView> 13 | ) 14 | } 15 | 16 | export default EpisodeList 17 | -------------------------------------------------------------------------------- /src/components/tv/SeasonDetails.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {View, Text, ScrollView } from 'react-native' 3 | import {connect} from 'react-redux' 4 | import axios from 'axios' 5 | import {styles} from 'react-native-theme' 6 | 7 | import { fetchSeasonDetails } from './../../Actions' 8 | import BackgroundImage from '../common/BackgroundImage' 9 | import ShowOverview from '../common/ShowOverview' 10 | import EpisodeList from './EpisodeList' 11 | import Constant from './../../utilities/constants' 12 | 13 | class SeasonDetails extends Component { 14 | // TODO: Use redux store 15 | constructor (props) { 16 | super(props) 17 | this.state = { 18 | error: '' 19 | } 20 | } 21 | 22 | componentDidMount () { 23 | const {tvShowId, season: {season_number}} = this.props.navigation.state.params 24 | this.props.fetchSeasonDetails(tvShowId, season_number, null, (err) => { 25 | this.setState({ 26 | error: err.response.data.message 27 | }) 28 | }) 29 | } 30 | 31 | render () { 32 | const season = this.props.navigation.state.params.season; 33 | const { poster_path, air_date, episode_count, season_number, episodes = [] } = this.props.season || season 34 | const { secureBaseUrl, posterSizeForBackground } = this.props.config.image 35 | const bgImage = `${secureBaseUrl}${posterSizeForBackground}/${poster_path}` 36 | 37 | return ( 38 | <View style={[{ flex: 1 }, styles.screenBackgroundColor]}> 39 | <BackgroundImage uri={bgImage} /> 40 | <ScrollView> 41 | <View style={styles.detailsContainer}> 42 | <Text style={[styles.text, styles.titleText]}> 43 | Season {season_number} 44 | </Text> 45 | <ShowOverview 46 | date={air_date || 'Unknown'} 47 | episodes={episode_count || episodes.length} 48 | /> 49 | <EpisodeList episodes={episodes} /> 50 | </View> 51 | </ScrollView> 52 | </View> 53 | ) 54 | } 55 | } 56 | 57 | const mapStateToProps = ({configuration, season}) => ({ 58 | config: configuration, 59 | season 60 | }) 61 | 62 | export default connect(mapStateToProps, {fetchSeasonDetails})(SeasonDetails) 63 | -------------------------------------------------------------------------------- /src/components/tv/TvShowDetails.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import * as _ from 'lodash' 3 | import { connect } from 'react-redux' 4 | import { NavigationActions } from 'react-navigation' 5 | 6 | import Details from '../base/Details' 7 | import HorizontalImageList from '../common/ImageList' 8 | import { getUriPopulated } from '../../utilities/utils' 9 | import { 10 | castSelected, 11 | searchItemDetailsFetched, 12 | tvShowDetailsFetched 13 | } from '../../Actions' 14 | 15 | class TvShowDetails extends Details { 16 | componentDidMount () { 17 | this.fetchDetails('/tv/', this.props.details.id || this.props.navigation.state.params.id) 18 | } 19 | 20 | showSeasonDetails (season) { 21 | this.props.navigation.navigate('SeasonDetails', {season: season, 22 | title: this.props.details.name, 23 | tvShowId: this.props.details.id 24 | }) 25 | } 26 | 27 | getSpecialComponent () { 28 | const seasons = _.get(this, 'props.details.seasons', []) 29 | const { config } = this.props 30 | 31 | return <HorizontalImageList 32 | isTouchableImage 33 | title='Seasons' 34 | style={config.style.posterSize} 35 | onPress={this.showSeasonDetails.bind(this)} 36 | images={getUriPopulated(seasons.sort((a, b) => b.season_number - a.season_number), config, 'posterSizeForImageList')} 37 | /> 38 | } 39 | } 40 | 41 | const mapStateToProps = ({tabNavHelper, search, tvShows, configuration}) => ({ 42 | details: tabNavHelper.currentTab === 'Search' ? search.details : tvShows.details, 43 | currentTab: tabNavHelper.currentTab, 44 | config: configuration 45 | }) 46 | 47 | const mapDispatchToProps = dispatch => ({ 48 | onDetailsFetched: (details, category, currentTab) => { 49 | if (currentTab === 'Search') { 50 | dispatch(searchItemDetailsFetched(details, category)) 51 | } else { 52 | dispatch(tvShowDetailsFetched(details, category)) 53 | } 54 | }, 55 | onCastSelected: (cast, currentTab) => { 56 | dispatch(castSelected(cast, currentTab)) 57 | dispatch(NavigationActions.navigate({routeName: 'CastDetails', 58 | params: { 59 | name: cast.name, 60 | id: cast.id 61 | }})) 62 | } 63 | }) 64 | 65 | export default connect(mapStateToProps, mapDispatchToProps)(TvShowDetails) 66 | -------------------------------------------------------------------------------- /src/components/tv/TvShows.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {connect} from 'react-redux' 3 | import {NavigationActions} from 'react-navigation' 4 | import * as _ from 'lodash' 5 | 6 | import Shows from '../base/Shows' 7 | import {selectedTvShow, fetchingTvShows, tvShowsFetched} from '../../Actions' 8 | 9 | 10 | class TvShows extends Shows { 11 | constructor (props) { 12 | super(props) 13 | this.carouselCategory = 'showingToday' 14 | } 15 | 16 | /** 17 | * @overrides 18 | */ 19 | componentDidMount () { 20 | // calls base class functions 21 | if (_.isEmpty(this.props.categories.showingToday)) { 22 | this.props.onFetching() 23 | } 24 | this.fetch('showingToday', '/tv/airing_today') 25 | this.fetch('topRated', '/tv/top_rated') 26 | this.fetch('popular', '/tv/popular') 27 | } 28 | 29 | /** 30 | * @overrides 31 | */ 32 | showAll (category) { 33 | this 34 | .props 35 | .navigation 36 | .navigate('AllTvShows', {category: category}) 37 | } 38 | } 39 | 40 | const mapStateToProps = state => ({ 41 | ...state.tvShows, 42 | config: state.configuration, 43 | settings: state.settings 44 | }) 45 | 46 | const mapDispatchToProps = dispatch => ({ 47 | onFetching: () => { 48 | dispatch(fetchingTvShows()) 49 | }, 50 | onFetchCompleted: (category, tvShows) => { 51 | dispatch(tvShowsFetched(category, tvShows)) 52 | }, 53 | onShowDetails: (tvShow) => { 54 | dispatch(selectedTvShow(tvShow)) 55 | dispatch(NavigationActions.navigate({routeName: 'TvShowDetails', 56 | params: { 57 | id: tvShow.id, 58 | name: tvShow.name 59 | }})) 60 | } 61 | }) 62 | 63 | export default connect(mapStateToProps, mapDispatchToProps)(TvShows) 64 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native' 2 | import App from './App' 3 | 4 | AppRegistry.registerComponent('MovieDB', () => App) 5 | -------------------------------------------------------------------------------- /src/reducers/casts.js: -------------------------------------------------------------------------------- 1 | import {FETCH_CAST_DETAILS} from '../Actions' 2 | 3 | export default function CastReducer (state = {}, action) { 4 | switch (action.type) { 5 | case FETCH_CAST_DETAILS: 6 | return action.payload.data 7 | 8 | default: 9 | return state 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/reducers/common.js: -------------------------------------------------------------------------------- 1 | export const populateCastDetails = (newState, action) => { 2 | // We can choose to cache the movies. is that necessary? 3 | if (action.category === 'bio') { 4 | newState.cast.details = Object.assign({}, newState.cast.details, action.details) 5 | } else if (action.category === 'movies') { 6 | newState.cast.details['movies'] = action.details 7 | } 8 | return newState 9 | } 10 | 11 | export const populateDetails = (newState, action) => { 12 | if (action.category === 'imagesAndVideos') { 13 | newState.details = Object.assign({}, newState.details, action.details) 14 | } else if (action.category === 'directorsAndCast') { 15 | newState.details['directors'] = action.details['directors'] 16 | newState.details['casts'] = action.details['casts'] 17 | } 18 | return newState 19 | } 20 | -------------------------------------------------------------------------------- /src/reducers/configuration.js: -------------------------------------------------------------------------------- 1 | import {Dimensions} from 'react-native' 2 | 3 | import initialState from '../State' 4 | import * as _ from 'lodash' 5 | import * as A from '../Actions' 6 | 7 | const margin = 5 8 | 9 | const getCarouselStyle = (width, height) => { 10 | // landscape orientation or portrait 11 | let aspectRatioForCarousel = (width > height) ? 0.45 : 0.55 12 | 13 | return { 14 | flexGrow: 1, 15 | height: width * aspectRatioForCarousel, 16 | width: width 17 | } 18 | } 19 | 20 | const getPosterSizeStyle = (width, numColumns) => { 21 | const aspectRatioForPosters = 1.5 22 | const posterWidth = (width - ((numColumns - 1) * margin)) / numColumns 23 | 24 | return { 25 | margin: margin, 26 | width: posterWidth, 27 | height: posterWidth * aspectRatioForPosters 28 | } 29 | } 30 | 31 | const getBackdropSizeStyle = (width) => { 32 | const aspectRatioForBackdrops = 0.6 33 | const screenImageRatio = 0.8 34 | const backdropWidth = width * screenImageRatio 35 | 36 | return { 37 | margin: margin, 38 | width: backdropWidth, 39 | height: backdropWidth * aspectRatioForBackdrops 40 | } 41 | } 42 | 43 | const populateStylesAndCol = (newState) => { 44 | const {width, height} = Dimensions.get('window') 45 | let numColumns = 3 // default 46 | 47 | if (_.inRange(width, 300, 500)) { 48 | numColumns = 3 49 | } else if (_.inRange(width, 500, 700)) { 50 | numColumns = 4 51 | } else { 52 | numColumns = 5 53 | } 54 | 55 | newState.image.numColumns = numColumns 56 | newState.style.posterSize = getPosterSizeStyle(width, numColumns) 57 | newState.style.backdropSize = getBackdropSizeStyle(width) 58 | newState.style.carousel = getCarouselStyle(width, height) 59 | } 60 | 61 | function configuration (state = initialState['config'], action) { 62 | let newState = JSON.parse(JSON.stringify(state)) 63 | if (action.type === A.CONFIG_FETCHED) { 64 | const images = action.config.images 65 | newState.image.secureBaseUrl = images.secure_base_url 66 | // TODO add more conditions here. Refer 67 | // http://mediag.com/news/popular-screen-resolutions-designing-for-all/ or 68 | // similar documentation. 69 | newState.image.backdropSize = images.backdrop_sizes[2] 70 | newState.image.posterSizeForImageList = images.poster_sizes[2] 71 | newState.image.posterSizeForBackground = images.poster_sizes[5] 72 | newState.image.profileSize = images.profile_sizes[2] 73 | newState.image.stillSize = images.still_sizes[2] 74 | 75 | populateStylesAndCol(newState) 76 | return newState 77 | } else if (action.type == A.LAYOUT_CHANGED) { 78 | populateStylesAndCol(newState) 79 | return newState 80 | } 81 | 82 | return state 83 | } 84 | 85 | export default configuration 86 | -------------------------------------------------------------------------------- /src/reducers/movies.js: -------------------------------------------------------------------------------- 1 | import initialState from '../State' 2 | import * as A from '../Actions' 3 | import { populateDetails, populateCastDetails } from './common' 4 | 5 | function movies (state = initialState['movies'], action) { 6 | let newState = JSON.parse(JSON.stringify(state)) 7 | switch (action.type) { 8 | case A.LANGUAGE_CHANGED: break 9 | case A.REGION_CHANGED: break 10 | case A.FETCHING_MOVIES: 11 | newState.isFetching = true 12 | return newState 13 | case A.NOW_SHOWING_MOVIES_FETCHED: 14 | newState.isFetching = false 15 | newState.categories['nowShowing'] = action.movies 16 | return newState 17 | case A.COMING_SOON_MOVIES_FETCHED: 18 | newState.categories['comingSoon'] = action.movies 19 | return newState 20 | case A.POPULAR_MOVIES_FETCHED: 21 | newState.categories['popular'] = action.movies 22 | return newState 23 | case A.MOVIE_SELECTED: 24 | // We can choose to cache the movies. is that necessary? 25 | // Clear the earlier data 26 | newState.details = {} 27 | newState.details = Object.assign({}, newState.details, action.movie) 28 | return newState 29 | case A.MOVIE_DETAILS_FETCHED: 30 | return populateDetails(newState, action) 31 | case A.MOVIES_CAST_SELECTED: 32 | // Clear the earlier data 33 | newState.cast.details = {} 34 | newState.cast.details = Object.assign({}, newState.cast.details, action.cast) 35 | return newState 36 | case A.FETCHING_MOVIES_CAST_DETAILS: 37 | newState.cast.isFetching = true 38 | return newState 39 | case A.MOVIES_CAST_DETAILS_FETCHED: 40 | newState.cast.isFetching = false 41 | return populateCastDetails(newState, action) 42 | default: return state 43 | } 44 | } 45 | 46 | export default movies 47 | -------------------------------------------------------------------------------- /src/reducers/navigation.js: -------------------------------------------------------------------------------- 1 | import AppNavigator from '../router' 2 | 3 | // Refer https://github.com/react-navigation/react-navigation/issues/2332 4 | // to understand why we keep initialState as undefined 5 | const initialState = undefined 6 | const navigationReducer = (state = initialState, action) => { 7 | const newState = AppNavigator.router.getStateForAction(action, state) 8 | return newState || state 9 | } 10 | 11 | export default navigationReducer 12 | -------------------------------------------------------------------------------- /src/reducers/root.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux' 2 | import movies from './movies' 3 | import tvShows from './tvShows' 4 | import configuration from './configuration' 5 | import settings from './settings' 6 | import navigation from './navigation' 7 | import search from './search' 8 | import tabNavHelper from './tabNavHelper' 9 | import CastsReducer from './casts' 10 | import SeasonReducer from './seasons'; 11 | 12 | const laLune = combineReducers({ 13 | movies, 14 | tvShows, 15 | configuration, 16 | settings, 17 | navigation, 18 | search, 19 | tabNavHelper, 20 | cast: CastsReducer, 21 | season: SeasonReducer 22 | }) 23 | 24 | export default laLune 25 | -------------------------------------------------------------------------------- /src/reducers/search.js: -------------------------------------------------------------------------------- 1 | import initialState from '../State' 2 | import * as A from '../Actions' 3 | import { populateDetails, populateCastDetails } from './common' 4 | 5 | const search = (state = initialState['search'], action) => { 6 | // TODO : optimise 7 | let newState = JSON.parse(JSON.stringify(state)) 8 | switch (action.type) { 9 | case A.SEARCHING_MOVIES_ETC: 10 | newState.isSearching = true 11 | return newState 12 | case A.DONE_SEARCHING_MOVIES_ETC: 13 | newState.isSearching = false 14 | newState.results = action.results 15 | return newState 16 | case A.SEARCH_FILTER_CHANGED: 17 | newState.selectedIndex = action.index 18 | return newState 19 | case A.FETCHING_SEARCH_CAST_DETAILS: 20 | newState.cast.isFetching = true 21 | return newState 22 | case A.SEARCH_CAST_SELECTED: 23 | // Clear the earlier data 24 | newState.cast.details = {} 25 | newState.cast.details = Object.assign({}, newState.cast.details, action.cast) 26 | return newState 27 | case A.SEARCH_RESULT_SELECTED: 28 | // We can choose to cache the movies. is that necessary think? 29 | if (action.mediaType === 'person') { 30 | // Clear the earlier data 31 | newState.cast.details = {} 32 | newState.cast.details = Object.assign({}, newState.cast.details, action.result) 33 | } else { 34 | // Clear the earlier data 35 | newState.details = {} 36 | newState.details = Object.assign({}, newState.details, action.result) 37 | } 38 | return newState 39 | case A.SEARCH_CAST_DETAILS_FETCHED: 40 | case A.SEARCH_ITEM_DETAILS_FETCHED: 41 | if (action.isFetching !== undefined) { newState.cast.isFetching = false } 42 | newState = populateDetails(newState, action) 43 | return populateCastDetails(newState, action) 44 | default: 45 | return state 46 | } 47 | } 48 | 49 | export default search 50 | -------------------------------------------------------------------------------- /src/reducers/seasons.js: -------------------------------------------------------------------------------- 1 | import {FETCH_SEASON_DETAILS} from "../Actions"; 2 | 3 | export default function SeasonReducer(state = [], action) { 4 | switch (action.type) { 5 | case FETCH_SEASON_DETAILS: 6 | return action.payload.data; 7 | 8 | default: 9 | return state; 10 | } 11 | } -------------------------------------------------------------------------------- /src/reducers/settings.js: -------------------------------------------------------------------------------- 1 | import initialState from '../State' 2 | import { 3 | FETCH_SETTINGS, 4 | SAVE_SETTINGS 5 | } from './../Actions' 6 | 7 | export default function settings (state = initialState['settings'], action) { 8 | switch (action.type) { 9 | case FETCH_SETTINGS: 10 | return action.payload 11 | 12 | case SAVE_SETTINGS: 13 | return action.payload ? action.payload : state 14 | 15 | default: 16 | return state 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/reducers/tabNavHelper.js: -------------------------------------------------------------------------------- 1 | import initialState from '../State' 2 | 3 | const acceptedRoutes = ['Movies', 'TvShows', 'Search'] 4 | function tabNavHelper (state = initialState['helper'], action) { 5 | if (action.type === 'Navigation/NAVIGATE' && 6 | acceptedRoutes.indexOf(action.routeName) !== -1) { 7 | return { 8 | 'currentTab': action.routeName 9 | } 10 | } 11 | return state 12 | } 13 | 14 | export default tabNavHelper 15 | -------------------------------------------------------------------------------- /src/reducers/tvShows.js: -------------------------------------------------------------------------------- 1 | import initialState from '../State' 2 | import * as A from '../Actions' 3 | import { populateDetails, populateCastDetails } from './common' 4 | 5 | function tvShows (state = initialState['tvShows'], action) { 6 | let newState = JSON.parse(JSON.stringify(state)) 7 | switch (action.type) { 8 | case A.LANGUAGE_CHANGED: break 9 | case A.REGION_CHANGED: break 10 | case A.FETCHING_TV_SHOWS: 11 | newState.isFetching = true 12 | return newState 13 | case A.SHOWING_TODAY_TV_SHOWS_FETCHED: 14 | newState.isFetching = false 15 | newState.categories['showingToday'] = action.tvShows 16 | return newState 17 | case A.TOP_RATED_TV_SHOWS_FETCHED: 18 | newState.categories['topRated'] = action.tvShows 19 | return newState 20 | case A.POPULAR_TV_SHOWS_FETCHED: 21 | newState.categories['popular'] = action.tvShows 22 | return newState 23 | case A.TV_SHOW_SELECTED: 24 | // We can choose to cache the movies. is that necessary? 25 | // Clear the earlier data 26 | newState.details = {} 27 | newState.details = Object.assign({}, newState.details, action.tvShow) 28 | return newState 29 | case A.TV_SHOW_DETAILS_FETCHED: 30 | return populateDetails(newState, action) 31 | case A.TVSHOWS_CAST_SELECTED: 32 | // Clear the earlier data 33 | newState.cast.details = {} 34 | newState.cast.details = Object.assign({}, newState.cast.details, action.cast) 35 | return newState 36 | case A.FETCHING_TVSHOWS_CAST_DETAILS: 37 | newState.cast.isFetching = true 38 | return newState 39 | case A.TVSHOWS_CAST_DETAILS_FETCHED: 40 | newState.cast.isFetching = false 41 | return populateCastDetails(newState, action) 42 | default: return state 43 | } 44 | } 45 | 46 | export default tvShows 47 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TabNavigator, TabBarBottom } from 'react-navigation' 3 | import { Icon } from 'react-native-elements' 4 | import theme from 'react-native-theme' 5 | 6 | import MoviesStack from './routes/Movies' 7 | import TvShowStack from './routes/TvShows' 8 | import SearchStack from './routes/Search' 9 | import SettingsStack from './routes/Settings' 10 | 11 | import { 12 | primaryColor, 13 | headerBackgroundColor_Dark, 14 | headerBorderColor_Dark, 15 | headerBackgroundColor_Light, 16 | headerBorderColor_Light 17 | } from './styles/styles' 18 | 19 | // Theme name 20 | const themeName = theme.name 21 | 22 | const headerBackgroundColor = 23 | themeName === 'Dark' || themeName === 'default' 24 | ? headerBackgroundColor_Dark 25 | : headerBackgroundColor_Light 26 | 27 | const headerBorderColor = 28 | themeName === 'Dark' || themeName === 'default' 29 | ? headerBorderColor_Dark 30 | : headerBorderColor_Light 31 | 32 | // Application router 33 | const MainScreen = TabNavigator( 34 | { 35 | Movies: { 36 | screen: MoviesStack, 37 | navigationOptions: { 38 | tabBarLabel: 'Movies', 39 | tabBarIcon: ({ tintColor }) => ( 40 | <Icon name="movie" size={30} color={tintColor} /> 41 | ) 42 | } 43 | }, 44 | TvShows: { 45 | screen: TvShowStack, 46 | navigationOptions: { 47 | tabBarLabel: 'TV Shows', 48 | tabBarIcon: ({ tintColor }) => ( 49 | <Icon name="tv" size={30} color={tintColor} /> 50 | ) 51 | } 52 | }, 53 | Search: { 54 | screen: SearchStack, 55 | navigationOptions: { 56 | tabBarLabel: 'Search', 57 | tabBarIcon: ({ tintColor }) => ( 58 | <Icon name="search" size={30} color={tintColor} /> 59 | ) 60 | } 61 | }, 62 | Settings: { 63 | screen: SettingsStack, 64 | navigationOptions: { 65 | tabBarLabel: 'Settings', 66 | tabBarIcon: ({ tintColor }) => ( 67 | <Icon name="settings" size={30} color={tintColor} /> 68 | ) 69 | } 70 | } 71 | }, 72 | { 73 | initialRouteName: 'Movies', 74 | tabBarOptions: { 75 | activeTintColor: primaryColor, 76 | inactiveTintColor: '#a9a9a9', 77 | style: { 78 | backgroundColor: headerBackgroundColor, 79 | borderTopColor: headerBorderColor, 80 | borderTopWidth: 1 81 | } 82 | }, 83 | tabBarComponent: TabBarBottom, 84 | tabBarPosition: 'bottom', 85 | animationEnabled: false, 86 | swipeEnabled: false 87 | } 88 | ) 89 | 90 | export default MainScreen 91 | -------------------------------------------------------------------------------- /src/routes/Common.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import theme from 'react-native-theme' 3 | 4 | import CastDetails from '../components/common/CastDetails' 5 | import VideoPlayer from '../components/common/VideoPlayer' 6 | import ShareButton from '../components/common/ShareButton' 7 | import { 8 | StackNavHeaderStyles_Light, 9 | StackNavHeaderStyles_Dark 10 | } from '../styles/styles' 11 | 12 | const StackNavHeaderStyles = 13 | theme.name === 'Dark' || theme.name === 'default' 14 | ? StackNavHeaderStyles_Dark 15 | : StackNavHeaderStyles_Light 16 | 17 | const CommonRoutes = { 18 | CastDetails: { 19 | screen: CastDetails, 20 | navigationOptions: ({ navigation: { state: { params } } }) => ({ 21 | title: params.name, 22 | ...StackNavHeaderStyles, 23 | headerRight: ( 24 | <ShareButton name={params.name} type="person" id={params.id} /> 25 | ) 26 | }) 27 | }, 28 | VideoPlayer: { 29 | screen: VideoPlayer, 30 | navigationOptions: ({ navigation }) => ({ 31 | tabBarVisible: false, 32 | headerVisible: false, 33 | ...StackNavHeaderStyles 34 | }) 35 | } 36 | } 37 | 38 | export default CommonRoutes 39 | -------------------------------------------------------------------------------- /src/routes/Movies.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StackNavigator } from 'react-navigation' 3 | import theme from 'react-native-theme' 4 | 5 | import Movies from '../components/movies/Movies' 6 | import MovieDetails from '../components/movies/MovieDetails' 7 | import AllMovies from '../components/movies/AllMovies' 8 | import ShareButton from './../components/common/ShareButton' 9 | import CommonRoutes from './Common' 10 | 11 | import { 12 | StackNavHeaderStyles_Light, 13 | StackNavHeaderStyles_Dark, 14 | headerBackgroundColor_dark, 15 | headerBackgroundColor_light 16 | } from '../styles/styles' 17 | 18 | const StackNavHeaderStyles = 19 | theme.name === 'Dark' || theme.name === 'default' 20 | ? StackNavHeaderStyles_Dark 21 | : StackNavHeaderStyles_Light 22 | 23 | const MovieDetailsRoutes = { 24 | MovieDetails: { 25 | screen: MovieDetails, 26 | navigationOptions: ({ navigation: { state: { params } } }) => ({ 27 | title: params.name, 28 | ...StackNavHeaderStyles, 29 | headerRight: ( 30 | <ShareButton name={params.name} type="movie" id={params.id} /> 31 | ) 32 | }) 33 | }, 34 | ...CommonRoutes 35 | } 36 | 37 | const MoviesStack = StackNavigator( 38 | { 39 | Movies: { 40 | screen: Movies, 41 | navigationOptions: { 42 | title: 'Movies', 43 | ...StackNavHeaderStyles 44 | } 45 | }, 46 | AllMovies: { 47 | screen: AllMovies, 48 | navigationOptions: ({ navigation }) => ({ 49 | title: navigation.state.params.category, 50 | ...StackNavHeaderStyles 51 | }) 52 | }, 53 | ...MovieDetailsRoutes 54 | }, 55 | { 56 | headerMode: 'float', 57 | cardStyle: { 58 | backgroundColor: 59 | theme.name === 'Dark' || theme.name === 'default' 60 | ? headerBackgroundColor_dark 61 | : headerBackgroundColor_light 62 | } 63 | } 64 | ) 65 | 66 | export { MovieDetailsRoutes } 67 | export default MoviesStack 68 | -------------------------------------------------------------------------------- /src/routes/Search.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {StackNavigator} from 'react-navigation' 3 | import theme from 'react-native-theme' 4 | 5 | import Search from '../components/search/Search' 6 | import {MovieDetailsRoutes} from './Movies' 7 | import {TvShowDetailsRoutes} from './TvShows' 8 | import ShareButton from '../components/common/ShareButton' 9 | 10 | import {StackNavHeaderStyles_Light, StackNavHeaderStyles_Dark, headerBackgroundColor_dark, headerBackgroundColor_light} from '../styles/styles' 11 | 12 | const StackNavHeaderStyles = (theme.name === 'Dark' || theme.name === 'default') 13 | ? StackNavHeaderStyles_Dark 14 | : StackNavHeaderStyles_Light; 15 | 16 | const SearchStack = StackNavigator({ 17 | Search: { 18 | screen: Search, 19 | navigationOptions: { 20 | title: 'Search', 21 | ...StackNavHeaderStyles 22 | } 23 | }, 24 | ...MovieDetailsRoutes, 25 | ...TvShowDetailsRoutes 26 | }, { 27 | headerMode: 'float', 28 | cardStyle: { 29 | backgroundColor: (theme.name === 'Dark' || theme.name === 'default') 30 | ? headerBackgroundColor_dark 31 | : headerBackgroundColor_light 32 | } 33 | }) 34 | 35 | export default SearchStack 36 | -------------------------------------------------------------------------------- /src/routes/Settings.js: -------------------------------------------------------------------------------- 1 | import {StackNavigator} from 'react-navigation' 2 | import theme from 'react-native-theme' 3 | 4 | import Settings from './../components/settings/Settings' 5 | import SettingDetails from './../components/settings/SettingsDetail' 6 | import {StackNavHeaderStyles_Light, StackNavHeaderStyles_Dark, headerBackgroundColor_light, headerBackgroundColor_dark} from '../styles/styles' 7 | 8 | const StackNavHeaderStyles = (theme.name === 'Dark' || theme.name === 'default') 9 | ? StackNavHeaderStyles_Dark 10 | : StackNavHeaderStyles_Light; 11 | 12 | const SettingsStack = StackNavigator({ 13 | Settings: { 14 | screen: Settings, 15 | navigationOptions: { 16 | title: 'Settings', 17 | ...StackNavHeaderStyles 18 | } 19 | }, 20 | SettingDetails: { 21 | screen: SettingDetails, 22 | navigationOptions: ({ 23 | navigation: { 24 | state: { 25 | params 26 | } 27 | } 28 | }) => ({ 29 | title: `Choose ${params.name}`, 30 | ...StackNavHeaderStyles 31 | }) 32 | } 33 | }, { 34 | headerMode: 'float', 35 | cardStyle: { 36 | backgroundColor: (theme.name === 'Dark' || theme.name === 'default') 37 | ? headerBackgroundColor_dark 38 | : headerBackgroundColor_light 39 | } 40 | }) 41 | 42 | export default SettingsStack 43 | -------------------------------------------------------------------------------- /src/routes/TvShows.js: -------------------------------------------------------------------------------- 1 | // To add TvShows. Think if this is really required? we can have this for 2 | // readability purpose 3 | import React from 'react' 4 | import {StackNavigator} from 'react-navigation' 5 | import theme from 'react-native-theme' 6 | 7 | import TvShow from '../components/tv/TvShows' 8 | import TvShowDetails from '../components/tv/TvShowDetails' 9 | import AllTvShows from '../components/tv/AllTvShows' 10 | import SeasonDetails from '../components/tv/SeasonDetails' 11 | import CommonRoutes from './Common' 12 | import ShareButton from './../components/common/ShareButton' 13 | 14 | import {StackNavHeaderStyles_Light, StackNavHeaderStyles_Dark, headerBackgroundColor_dark, headerBackgroundColor_light} from '../styles/styles' 15 | 16 | const StackNavHeaderStyles = (theme.name === 'Dark' || theme.name === 'default') 17 | ? StackNavHeaderStyles_Dark 18 | : StackNavHeaderStyles_Light; 19 | 20 | const TvShowDetailsRoutes = { 21 | TvShowDetails: { 22 | screen: TvShowDetails, 23 | navigationOptions: ({ 24 | navigation: { 25 | state: { 26 | params 27 | } 28 | } 29 | }) => ({ 30 | title: params.name, 31 | ...StackNavHeaderStyles, 32 | headerRight: <ShareButton name={params.name} type='tv' id={params.id}/> 33 | }) 34 | }, 35 | SeasonDetails: { 36 | screen: SeasonDetails, 37 | navigationOptions: ({navigation}) => ({ 38 | title: navigation.state.params.title, 39 | ...StackNavHeaderStyles 40 | }) 41 | }, 42 | ...CommonRoutes 43 | } 44 | 45 | const TvShowStack = StackNavigator({ 46 | TvShow: { 47 | screen: TvShow, 48 | navigationOptions: { 49 | title: 'TvShows', 50 | ...StackNavHeaderStyles 51 | } 52 | }, 53 | AllTvShows: { 54 | screen: AllTvShows, 55 | navigationOptions: ({navigation}) => ({ 56 | title: navigation.state.params.category, 57 | ...StackNavHeaderStyles 58 | }) 59 | }, 60 | ...TvShowDetailsRoutes 61 | }, { 62 | headerMode: 'float', 63 | cardStyle: { 64 | backgroundColor: (theme.name === 'Dark' || theme.name === 'default') 65 | ? headerBackgroundColor_dark 66 | : headerBackgroundColor_light 67 | } 68 | }) 69 | 70 | export {TvShowDetailsRoutes} 71 | export default TvShowStack 72 | -------------------------------------------------------------------------------- /src/services/index.js: -------------------------------------------------------------------------------- 1 | import * as axios from 'axios'; 2 | 3 | import Constant from './../utilities/constants'; 4 | 5 | axios.defaults.baseURL = Constant.api_base_url;; 6 | axios.defaults.headers.post['Content-Type'] = 'application/json'; 7 | 8 | // Axios interceptor for handling common HTTP errors 9 | // Need to use it with reducers 10 | axios.interceptors.response.use(res => res, err => Promise.reject(error)); 11 | 12 | /** 13 | * HTTP request to search item in The MovieDB 14 | * 15 | * @returns {object | Promise} 16 | */ 17 | const getConfiguration = () => { 18 | return axios.get(`/configuration?${Constant.api_key}`) 19 | } 20 | 21 | export { getConfiguration } 22 | -------------------------------------------------------------------------------- /src/services/person.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import * as index from './index'; 4 | import Constant from './../utilities/constants'; 5 | 6 | const apiKey = Constant.api_key; 7 | 8 | /** 9 | * HTTP request to get cast details 10 | * 11 | * @param {number} castId 12 | * @returns {object | promise} 13 | */ 14 | const getCastDetails = (castId) => { 15 | return axios.get(`/person/${castId}?${apiKey}`) 16 | } 17 | 18 | const getCastKnowFor = (castId) => { 19 | return axios.get(`/person/${castId}/movie_credits?${apiKey}`) 20 | } 21 | 22 | export { 23 | getCastDetails, 24 | getCastKnowFor 25 | } 26 | -------------------------------------------------------------------------------- /src/services/search.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import * as index from './index'; 4 | import Constant from './../utilities/constants'; 5 | 6 | const searchUrl = '/search/multi'; 7 | 8 | 9 | /** 10 | * HTTP request to search item in The MovieDB 11 | * 12 | * @param {string} searchQuery 13 | * @param {string} language 14 | * @param {string} region 15 | * @returns {object | Promise} 16 | */ 17 | const searchItem = (searchQuery, language, region) => { 18 | const attributes = `&language=${language}®ion=${region}&page=1&query=${encodeURIComponent(searchQuery)}` 19 | return axios.get(`${searchUrl}?${Constant.api_key}${attributes}`) 20 | } 21 | 22 | export { 23 | searchItem 24 | } 25 | -------------------------------------------------------------------------------- /src/services/seasons.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import * as index from './index'; 4 | import Constant from './../utilities/constants'; 5 | 6 | const apiKey = Constant.api_key 7 | 8 | /** 9 | * HTTP request to get season details 10 | * 11 | * @param {number} tvShowId 12 | * @param {number} season_number 13 | * @returns {object|promise} 14 | */ 15 | const getSeasonDetails = (tvShowId, season_number) => { 16 | const seasonAPI = `/tv/${tvShowId}/season/${season_number}` 17 | const seasonUrl = `${seasonAPI}?${apiKey}` 18 | 19 | return axios.get(seasonUrl); 20 | } 21 | 22 | export { 23 | getSeasonDetails 24 | } 25 | -------------------------------------------------------------------------------- /src/services/shows.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import * as index from './index'; 4 | import Constant from './../utilities/constants'; 5 | 6 | const apiKey = Constant.api_key; 7 | 8 | /** 9 | * HTTP request to fetch shows specified by route argument 10 | * 11 | * @param {string} route - url route for now showing movies 12 | * @param {string} language 13 | * @param {string} region 14 | * @returns {object | promise} 15 | */ 16 | const getShows = (route, language, region) => { 17 | const uri = `${route}?${apiKey}&language=${language}®ion=${region}&page=1` 18 | return axios.get(uri); 19 | } 20 | 21 | /** 22 | * HTTP request to fetch show (movie tvShow) details for passed value 23 | * 24 | * @param {string} route 25 | * @param {string} showId 26 | * 27 | * @return {object | Promise} 28 | */ 29 | const getShowDetails = (route, showId) => { 30 | const appendResponse = 'append_to_response=videos,images'; 31 | return axios.get(`${route}${showId}?${apiKey}&${appendResponse}`); 32 | } 33 | 34 | /** 35 | * HTTP request to fetch show cast and crew info 36 | * 37 | * @param {string} route 38 | * @param {string} showId 39 | * 40 | * @return {object | Promise} 41 | */ 42 | const getShowCredits = (route, showId) => { 43 | return axios.get(`${route}${showId}/credits?${apiKey}`); 44 | } 45 | 46 | export { 47 | getShows, 48 | getShowDetails, 49 | getShowCredits 50 | } 51 | -------------------------------------------------------------------------------- /src/styles/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Platform } from "react-native"; 2 | import theme from "react-native-theme"; 3 | 4 | import APP_CONSTANT from "./../utilities/constants.js"; 5 | 6 | const marginTop = APP_CONSTANT.height / APP_CONSTANT.goldenRatio; 7 | const primaryColor = "#32CD32"; 8 | const headerBackgroundColor = "#222222"; 9 | const headerBorderColor = "#181818"; 10 | const backgroundColor = "rgba(32, 32, 32, 0.9)"; 11 | const primaryTextColor = "#FFFFFF"; 12 | const secondaryTextColor = "#E1E1E1"; 13 | const castBackground = "#595959"; 14 | const imagePlaceholder = "#545454"; 15 | const screenBackground = "#181818"; 16 | const splashScreenBackground = "#040404"; 17 | const trailerBackground = "#020202"; 18 | const searchItemBackground = "#393939"; 19 | 20 | const headerBackgroundColor_dark = "#222222"; 21 | const headerBorderColor_dark = "#181818"; 22 | const backgroundColor_dark = "rgba(32, 32, 32, 0.9)"; 23 | const headerTextColor_dark = "#CFCFCF"; 24 | const primaryTextColor_dark = "#FFFFFF"; 25 | const secondaryTextColor_dark = "#E1E1E1"; 26 | const castBackground_dark = "#595959"; 27 | const imagePlaceholder_dark = "#545454"; 28 | const screenBackground_dark = "#181818"; 29 | const splashScreenBackground_dark = "#040404"; 30 | const trailerBackground_dark = "#020202"; 31 | const searchItemBackground_dark = "#393939"; 32 | 33 | const headerBackgroundColor_light = "#F1F1F1"; 34 | const headerBorderColor_light = "#E1E1E1"; 35 | const backgroundColor_light = "#FFFFFF"; 36 | const headerTextColor_light = "#333333"; 37 | const primaryTextColor_light = "#444444"; 38 | const secondaryTextColor_light = "#616161"; 39 | const castBackground_light = "#B5B5B5"; 40 | const imagePlaceholder_light = "#C4C4C4"; 41 | const screenBackground_light = "#FFFFFF"; 42 | const splashScreenBackground_light = "#FFFFFF"; 43 | const trailerBackground_light = "#FCFCFC"; 44 | const searchItemBackground_light = "#C9C9C9"; 45 | 46 | const defaultStyle = { 47 | container: { 48 | marginTop: 10, 49 | marginBottom: 10 50 | }, 51 | detailsContainer: { 52 | marginTop, 53 | paddingTop: 20, 54 | paddingBottom: 30, 55 | paddingLeft: 10, 56 | paddingRight: 10, 57 | backgroundColor: backgroundColor 58 | }, 59 | flatListContainer: { 60 | flex: 1, 61 | justifyContent: "space-between" 62 | }, 63 | headerBackground: { 64 | backgroundColor: headerBackgroundColor 65 | }, 66 | headerTextColor: { 67 | color: headerTextColor_dark 68 | }, 69 | text: { 70 | color: primaryTextColor 71 | }, 72 | titleText: { 73 | fontSize: 24, 74 | fontWeight: "700", 75 | marginBottom: 10 76 | }, 77 | headingText: { 78 | fontSize: 18, 79 | fontWeight: "300", 80 | paddingTop: 6, 81 | paddingBottom: 6 82 | }, 83 | subHeadingText: { 84 | fontSize: 14, 85 | fontWeight: "700", 86 | paddingTop: 6, 87 | paddingBottom: 6 88 | }, 89 | normalText: { 90 | fontSize: 12 91 | }, 92 | secondaryText: { 93 | color: secondaryTextColor, 94 | paddingBottom: 4 95 | }, 96 | textStickToBottom: { 97 | display: "flex", 98 | flexDirection: "column", 99 | justifyContent: "center" 100 | }, 101 | avatarContainer: { 102 | width: 100, 103 | height: 60 104 | }, 105 | castBackground: { 106 | flex: 1, 107 | height: 0.618 * (APP_CONSTANT.height - 104), 108 | backgroundColor: castBackground, 109 | alignItems: "center", 110 | justifyContent: "center" 111 | }, 112 | castBiography: { 113 | padding: 20, 114 | backgroundColor: backgroundColor 115 | }, 116 | avatarSize: { 117 | width: 60, 118 | height: 60, 119 | borderRadius: 30, 120 | margin: 10, 121 | marginRight: 20, 122 | marginLeft: 20 123 | }, 124 | avatarBigSize: { 125 | width: 80, 126 | height: 80, 127 | borderRadius: 40 128 | }, 129 | avatarText: { 130 | maxWidth: 90, 131 | textAlign: "center" 132 | }, 133 | imagePlaceholder: { 134 | backgroundColor: imagePlaceholder 135 | }, 136 | screenBackgroundColor: { 137 | backgroundColor: screenBackground 138 | }, 139 | splashScreenBackground: { 140 | backgroundColor: splashScreenBackground 141 | }, 142 | trailerContainer: { 143 | borderWidth: 1, 144 | borderStyle: "solid", 145 | borderColor: "#616161", 146 | backgroundColor: "#020202" 147 | }, 148 | trailerPlayIcon: {}, 149 | trailerTitle: { 150 | textAlign: "left", 151 | paddingLeft: 4 152 | }, 153 | centerContentContainer: { 154 | flex: 1, 155 | justifyContent: "center", 156 | alignItems: "center" 157 | }, 158 | appName: { 159 | marginTop: 15, 160 | fontSize: 48, 161 | fontWeight: "500" 162 | }, 163 | startupScreenTextProps: { 164 | color: primaryColor, 165 | textAlign: "center" 166 | }, 167 | flexContainer: { 168 | flex: 1 169 | }, 170 | detailHeadings: { 171 | marginTop: 15, 172 | marginBottom: 4 173 | }, 174 | searchItem: { 175 | flex: 1, 176 | height: 90, 177 | flexDirection: "row", 178 | padding: 2, 179 | margin: 6, 180 | backgroundColor: "#393939", 181 | shadowRadius: 0, 182 | shadowColor: "#191919" 183 | }, 184 | searchItemImage: { 185 | width: 64, 186 | height: 86 187 | }, 188 | searchItemData: { 189 | width: 220, 190 | justifyContent: "center", 191 | alignItems: "flex-start", 192 | paddingLeft: 25 193 | }, 194 | searchResult: { 195 | marginBottom: 120 196 | }, 197 | rightArrow: { 198 | flexGrow: 1, 199 | justifyContent: "center", 200 | alignItems: "center", 201 | paddingRight: 5 202 | }, 203 | episodeItem: { 204 | flexDirection: "row", 205 | paddingVertical: 10, 206 | paddingHorizontal: 10, 207 | borderBottomColor: "#e1e1e1", 208 | borderBottomWidth: 1 209 | }, 210 | episodePosterContainer: { 211 | flex: 1, 212 | justifyContent: "center", 213 | alignItems: "center" 214 | }, 215 | episodePoster: { 216 | width: 92, 217 | height: 52 218 | }, 219 | episodeDesc: { 220 | flex: 3, 221 | justifyContent: "center", 222 | alignItems: "flex-start", 223 | paddingLeft: 15 224 | }, 225 | listContainer: { 226 | flexGrow: 1, 227 | flexDirection: "row", 228 | height: 54, 229 | backgroundColor: "rgba(255, 255, 255, 0.05)", 230 | justifyContent: "space-between", 231 | alignItems: "center", 232 | marginBottom: 4 233 | }, 234 | listTitle: { 235 | flexGrow: 1, 236 | paddingLeft: 5 237 | }, 238 | listValue: { 239 | flexGrow: 3, 240 | paddingRight: 5 241 | }, 242 | popularSearchContainer: { 243 | flex: 1, 244 | justifyContent: "center", 245 | alignItems: "center" 246 | }, 247 | popularSearch: { 248 | marginTop: 4 249 | }, 250 | settingDetailsTitle: { 251 | marginTop: 6, 252 | marginBottom: 8, 253 | marginLeft: 6 254 | } 255 | }; 256 | 257 | // Style for light theme 258 | const lightTheme = { 259 | detailsContainer: { 260 | marginTop, 261 | paddingTop: 20, 262 | paddingBottom: 30, 263 | paddingLeft: 10, 264 | paddingRight: 10, 265 | backgroundColor: backgroundColor_light 266 | }, 267 | castBiography: { 268 | padding: 20, 269 | backgroundColor: backgroundColor_light 270 | }, 271 | text: { 272 | color: primaryTextColor_light 273 | }, 274 | secondaryText: { 275 | color: secondaryTextColor_light, 276 | paddingBottom: 4 277 | }, 278 | headerBackground: { 279 | backgroundColor: headerBackgroundColor_light 280 | }, 281 | headerTextColor: { 282 | color: headerTextColor_light 283 | }, 284 | castBackground: { 285 | flex: 1, 286 | height: 0.618 * (APP_CONSTANT.height - 104), 287 | backgroundColor: castBackground_light, 288 | alignItems: "center", 289 | justifyContent: "center" 290 | }, 291 | castBiography: { 292 | padding: 20, 293 | backgroundColor: backgroundColor_light 294 | }, 295 | imagePlaceholder: { 296 | backgroundColor: imagePlaceholder_light 297 | }, 298 | screenBackgroundColor: { 299 | backgroundColor: screenBackground_light 300 | }, 301 | splashScreenBackground: { 302 | backgroundColor: splashScreenBackground_light 303 | }, 304 | trailerContainer: { 305 | borderWidth: 1, 306 | borderStyle: "solid", 307 | borderColor: "#616161", 308 | backgroundColor: trailerBackground_light 309 | }, 310 | searchItem: { 311 | flex: 1, 312 | height: 90, 313 | flexDirection: "row", 314 | padding: 2, 315 | margin: 6, 316 | backgroundColor: searchItemBackground_light, 317 | shadowRadius: 0, 318 | shadowColor: "#191919" 319 | }, 320 | listContainer: { 321 | flexGrow: 1, 322 | flexDirection: "row", 323 | height: 54, 324 | backgroundColor: "rgba(0, 0, 0, 0.05)", 325 | justifyContent: "space-between", 326 | alignItems: "center", 327 | marginBottom: 4 328 | } 329 | }; 330 | 331 | // Style for dark theme 332 | const darkTheme = { 333 | detailsContainer: { 334 | marginTop, 335 | paddingTop: 20, 336 | paddingBottom: 30, 337 | paddingLeft: 10, 338 | paddingRight: 10, 339 | backgroundColor: backgroundColor_dark 340 | }, 341 | castBiography: { 342 | padding: 20, 343 | backgroundColor: backgroundColor_dark 344 | }, 345 | text: { 346 | color: primaryTextColor_dark 347 | }, 348 | secondaryText: { 349 | color: secondaryTextColor_dark, 350 | paddingBottom: 4 351 | }, 352 | headerBackground: { 353 | backgroundColor: headerBackgroundColor_dark 354 | }, 355 | headerTextColor: { 356 | color: headerTextColor_dark 357 | }, 358 | castBackground: { 359 | flex: 1, 360 | height: 0.618 * (APP_CONSTANT.height - 104), 361 | backgroundColor: castBackground_dark, 362 | alignItems: "center", 363 | justifyContent: "center" 364 | }, 365 | castBiography: { 366 | padding: 20, 367 | backgroundColor: backgroundColor_dark 368 | }, 369 | imagePlaceholder: { 370 | backgroundColor: imagePlaceholder_dark 371 | }, 372 | screenBackgroundColor: { 373 | backgroundColor: screenBackground_dark 374 | }, 375 | splashScreenBackground: { 376 | backgroundColor: splashScreenBackground_dark 377 | }, 378 | trailerContainer: { 379 | borderWidth: 1, 380 | borderStyle: "solid", 381 | borderColor: "#616161", 382 | backgroundColor: trailerBackground_dark 383 | }, 384 | searchItem: { 385 | flex: 1, 386 | height: 90, 387 | flexDirection: "row", 388 | padding: 2, 389 | margin: 6, 390 | backgroundColor: searchItemBackground_dark, 391 | shadowRadius: 0, 392 | shadowColor: "#191919" 393 | }, 394 | listContainer: { 395 | flexGrow: 1, 396 | flexDirection: "row", 397 | height: 54, 398 | backgroundColor: "rgba(255, 255, 255, 0.05)", 399 | justifyContent: "space-between", 400 | alignItems: "center", 401 | marginBottom: 4 402 | } 403 | }; 404 | 405 | const StackNavHeaderStyles_Dark = { 406 | headerTitleStyle: { 407 | // color: headerTextColor_dark 408 | color: "#CFCFCF" 409 | }, 410 | headerStyle: { 411 | // backgroundColor: headerBackgroundColor_dark 412 | backgroundColor: "#222222" 413 | }, 414 | headerTintColor: primaryColor 415 | }; 416 | 417 | const StackNavHeaderStyles_Light = { 418 | headerTitleStyle: { 419 | // color: headerTextColor_light 420 | color: "#333333" 421 | }, 422 | headerStyle: { 423 | // backgroundColor: headerBackgroundColor_light 424 | backgroundColor: "#F1F1F1" 425 | }, 426 | headerTintColor: primaryColor 427 | }; 428 | 429 | export { 430 | StackNavHeaderStyles_Dark, 431 | StackNavHeaderStyles_Light, 432 | primaryColor, 433 | marginTop, 434 | headerBackgroundColor, 435 | headerBorderColor, 436 | defaultStyle, 437 | lightTheme, 438 | darkTheme, 439 | headerBackgroundColor_dark, 440 | headerBackgroundColor_light 441 | }; 442 | -------------------------------------------------------------------------------- /src/utilities/constants.js: -------------------------------------------------------------------------------- 1 | import { Dimensions } from 'react-native' 2 | import Config from 'react-native-config' 3 | 4 | const { height, width } = Dimensions.get('screen') 5 | const tabBarHeight = 54 6 | 7 | const APP_CONSTANT = { 8 | // api_base_url: Config.API_ENDPOINT, 9 | api_base_url: 'https://api.themoviedb.org/3', 10 | // api_key: `api_key=${Config.API_KEY}`, 11 | api_key: `api_key=2490cc7c5bae166cde5e69c074cf83ea`, 12 | goldenRatio: 1.618, 13 | width, 14 | height: height - tabBarHeight * 2 15 | } 16 | 17 | export default APP_CONSTANT 18 | -------------------------------------------------------------------------------- /src/utilities/utils.js: -------------------------------------------------------------------------------- 1 | export const getUriPopulated = (shows, config, key) => { 2 | const {image} = config 3 | // decipher imageType from key 4 | // ex: posterSizeForImageList, extract poster from string 5 | const imageType = key.substring(0, key.indexOf('S')) 6 | 7 | return shows.map((show) => { 8 | const path = show['file_path'] || show[`${imageType}_path`] 9 | show['uri'] = `${image.secureBaseUrl}${image[key]}${path}` 10 | return show 11 | }) 12 | } 13 | 14 | String.prototype.toCategory = function () { 15 | return this.replace(/ /g, '').replace(/(.)/, c => c.toLowerCase()) 16 | } 17 | 18 | String.prototype.toUnderScore = function () { 19 | return this.replace(/([A-Z])/g, '_$1') 20 | } 21 | --------------------------------------------------------------------------------