├── .watchmanconfig ├── .gitattributes ├── app.json ├── .env.development ├── PUXA-R10!.png ├── assets └── fonts │ ├── CircularStd-Bold.otf │ ├── CircularStd-Book.otf │ ├── Modesta-Script.ttf │ ├── CircularStd-Black.otf │ └── CircularStd-Medium.otf ├── android ├── app │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── drawable │ │ │ │ ├── icon.png │ │ │ │ ├── splash_screen_icon.png │ │ │ │ └── background_splash.xml │ │ │ ├── drawable-hdpi │ │ │ │ └── icon.png │ │ │ ├── drawable-ldpi │ │ │ │ └── icon.png │ │ │ ├── drawable-mdpi │ │ │ │ └── icon.png │ │ │ ├── drawable-xhdpi │ │ │ │ └── icon.png │ │ │ ├── drawable-xxhdpi │ │ │ │ └── icon.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ └── icon.png │ │ │ └── layout │ │ │ │ └── launch_screen.xml │ │ │ ├── assets │ │ │ └── fonts │ │ │ │ ├── Entypo.ttf │ │ │ │ ├── Feather.ttf │ │ │ │ ├── Zocial.ttf │ │ │ │ ├── AntDesign.ttf │ │ │ │ ├── EvilIcons.ttf │ │ │ │ ├── Foundation.ttf │ │ │ │ ├── Ionicons.ttf │ │ │ │ ├── Octicons.ttf │ │ │ │ ├── FontAwesome.ttf │ │ │ │ ├── MaterialIcons.ttf │ │ │ │ ├── Modesta-Script.ttf │ │ │ │ ├── SimpleLineIcons.ttf │ │ │ │ ├── CircularStd-Black.otf │ │ │ │ ├── CircularStd-Bold.otf │ │ │ │ ├── CircularStd-Book.otf │ │ │ │ ├── CircularStd-Medium.otf │ │ │ │ ├── FontAwesome5_Brands.ttf │ │ │ │ ├── FontAwesome5_Regular.ttf │ │ │ │ ├── FontAwesome5_Solid.ttf │ │ │ │ └── MaterialCommunityIcons.ttf │ │ │ ├── java │ │ │ └── com │ │ │ │ └── mindcast │ │ │ │ ├── SplashActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── AndroidManifest.xml │ ├── build_defs.bzl │ ├── proguard-rules.pro │ └── BUCK ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── keystores │ ├── debug.keystore.properties │ └── BUCK ├── gradle.properties ├── build.gradle ├── settings.gradle └── gradlew.bat ├── ios ├── mindCast │ ├── Images.xcassets │ │ ├── Contents.json │ │ ├── logo.imageset │ │ │ ├── icon.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-20x20@2x.png │ │ │ ├── Icon-20x20@3x.png │ │ │ ├── Icon-29x29@2x.png │ │ │ ├── Icon-29x29@3x.png │ │ │ ├── Icon-40x40@2x.png │ │ │ ├── Icon-40x40@3x.png │ │ │ ├── Icon-60x60@2x.png │ │ │ ├── Icon-60x60@3x.png │ │ │ ├── Icon-marketing-1024x1024.png │ │ │ └── Contents.json │ ├── AppDelegate.h │ ├── main.m │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ └── Info.plist ├── mindCastTests │ ├── Info.plist │ └── mindCastTests.m ├── mindCast-tvOSTests │ └── Info.plist └── mindCast-tvOS │ └── Info.plist ├── flow-typed └── npm │ ├── react-native_vx.x.x.js │ ├── react-native-fs_vx.x.x.js │ ├── flow-bin_v0.x.x.js │ ├── babel-jest_vx.x.x.js │ ├── babel-core_vx.x.x.js │ ├── reactotron-redux_vx.x.x.js │ ├── reactotron-redux-saga_vx.x.x.js │ ├── reactotron-react-native_vx.x.x.js │ ├── prettier-eslint_vx.x.x.js │ ├── eslint-import-resolver-babel-plugin-root-import_vx.x.x.js │ ├── babel-plugin-root-import_vx.x.x.js │ ├── flow_vx.x.x.js │ ├── react-native-side-menu_vx.x.x.js │ ├── metro-react-native-babel-preset_vx.x.x.js │ └── react-test-renderer_v16.x.x.js ├── jsconfig.json ├── .buckconfig ├── .editorconfig ├── src ├── services │ └── api.js ├── config │ └── ReactotronConfig.js ├── store │ ├── ducks │ │ ├── index.js │ │ ├── home.js │ │ └── subject.js │ ├── sagas │ │ ├── subject.js │ │ ├── utils │ │ │ └── parseParams.js │ │ ├── author.js │ │ └── home.js │ └── index.js ├── components │ ├── screens │ │ ├── settings │ │ │ ├── about │ │ │ │ ├── SOCIAL_BUTTONS.js │ │ │ │ └── HeartBeating.js │ │ │ └── routes.js │ │ ├── interests │ │ │ └── Interests.js │ │ ├── login │ │ │ └── components │ │ │ │ ├── DefaultText.js │ │ │ │ ├── BackgroundImage.js │ │ │ │ ├── Header.js │ │ │ │ ├── ChangeAction.js │ │ │ │ ├── LoginComponent.js │ │ │ │ ├── RegisterComponent.js │ │ │ │ └── Input.js │ │ ├── library │ │ │ ├── Library.js │ │ │ └── components │ │ │ │ ├── sections │ │ │ │ └── SectionItem.js │ │ │ │ ├── playlists │ │ │ │ └── PlaylistOperationModal.js │ │ │ │ ├── playlist-detail │ │ │ │ └── components │ │ │ │ │ ├── PodcastListItem.js │ │ │ │ │ └── PlaylistDetailComponent.js │ │ │ │ └── PodcastsDownloaded.js │ │ ├── home │ │ │ ├── Home.js │ │ │ └── components │ │ │ │ ├── trending-authors │ │ │ │ ├── TrendingAuthorsSeeAll.js │ │ │ │ └── trending-authors-discover │ │ │ │ │ └── TrendingAuthorsDiscover.js │ │ │ │ ├── new-releases │ │ │ │ ├── NewReleasesSeeAll.js │ │ │ │ └── new-releases-discover │ │ │ │ │ └── NewReleasesDiscover.js │ │ │ │ └── hottest-podcasts │ │ │ │ ├── HottestPodcastsDiscover.js │ │ │ │ └── HottestPodcastsSeeAll.js │ │ ├── search │ │ │ ├── components │ │ │ │ ├── search-author │ │ │ │ │ └── SearchAuthorListContainer.js │ │ │ │ ├── subject-detail │ │ │ │ │ ├── SubjectDetailContainer.js │ │ │ │ │ └── components │ │ │ │ │ │ └── trending │ │ │ │ │ │ └── Trending.js │ │ │ │ ├── subjects-list │ │ │ │ │ └── SubjectListItem.js │ │ │ │ ├── SearchComponent.js │ │ │ │ └── SearchAuthorTextInput.js │ │ │ └── SearchContainer.js │ │ └── oboarding-intro │ │ │ └── components │ │ │ └── MiddleContent.js │ └── common │ │ ├── Icon.js │ │ ├── author-detail │ │ ├── components │ │ │ ├── SectionWrapper.js │ │ │ ├── AuthorName.js │ │ │ ├── AboutSection.js │ │ │ ├── SubjectsSection.js │ │ │ ├── featured-section │ │ │ │ └── FeaturedSection.js │ │ │ ├── related-authors │ │ │ │ ├── RelatedAuthorsListItem.js │ │ │ │ └── RelatedAuthors.js │ │ │ └── new-releases-section │ │ │ │ └── NewReleasesSection.js │ │ └── AuthorDetailContainer.js │ │ ├── Loading.js │ │ ├── player │ │ └── components │ │ │ ├── bottom-player-options │ │ │ ├── components │ │ │ │ ├── AddPodcastPlaylist.js │ │ │ │ ├── Button.js │ │ │ │ └── ShufflePlaylist.js │ │ │ └── BottomPlayerOptions.js │ │ │ ├── BackgroundImage.js │ │ │ ├── PodcastImage.js │ │ │ └── PodcastTextContent.js │ │ ├── ScreenTitle.js │ │ ├── SwipeOutButton.js │ │ ├── SectionTitle.js │ │ ├── Switch.js │ │ ├── SectionWithButton.js │ │ ├── HeaderActionButton.js │ │ ├── HeaderButton.js │ │ ├── navigation │ │ ├── components │ │ │ ├── navigation-bar │ │ │ │ ├── NavigationBar.js │ │ │ │ └── NavigationBarItem.js │ │ │ └── ProgressTimeLine.js │ │ └── Navigation.js │ │ ├── AuthorInfo.js │ │ ├── DefaultButton.js │ │ ├── ErrorMessage.js │ │ ├── interests │ │ └── InterestsListItem.js │ │ ├── ProgressiveImage.js │ │ ├── PlaylistCompositionImages.js │ │ ├── podcast-detail │ │ └── components │ │ │ ├── BottomContent.js │ │ │ └── PodcastInfo.js │ │ ├── playlists-list │ │ └── components │ │ │ └── CreatePlaylistButton.js │ │ └── Alert.js ├── styles │ ├── index.js │ ├── colors.js │ └── metrics.js ├── index.js ├── utils │ ├── isEqualsOrLargestThanIphoneX.js │ ├── AsyncStorageManager.js │ └── CONSTANTS.js └── routes │ ├── mainStack.js │ └── index.js ├── index.js ├── babel.config.js ├── .gitignore ├── LICENSE ├── .eslintrc.json ├── package.json └── .flowconfig /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mindCast", 3 | "displayName": "mindCast" 4 | } -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | SERVER_URL=http://192.168.25.27:3001/mind-cast/api/v1 2 | -------------------------------------------------------------------------------- /PUXA-R10!.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/PUXA-R10!.png -------------------------------------------------------------------------------- /assets/fonts/CircularStd-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/assets/fonts/CircularStd-Bold.otf -------------------------------------------------------------------------------- /assets/fonts/CircularStd-Book.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/assets/fonts/CircularStd-Book.otf -------------------------------------------------------------------------------- /assets/fonts/Modesta-Script.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/assets/fonts/Modesta-Script.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MindCast 3 | 4 | -------------------------------------------------------------------------------- /assets/fonts/CircularStd-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/assets/fonts/CircularStd-Black.otf -------------------------------------------------------------------------------- /assets/fonts/CircularStd-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/assets/fonts/CircularStd-Medium.otf -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /flow-typed/npm/react-native_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare module 'react-native' { 4 | declare module.exports: any; 5 | } 6 | -------------------------------------------------------------------------------- /flow-typed/npm/react-native-fs_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare module 'react-native-fs' { 4 | declare module.exports: any; 5 | } 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/res/drawable/icon.png -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "~/*": ["src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/Entypo.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Feather.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/Feather.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Zocial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/Zocial.ttf -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/AntDesign.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/AntDesign.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/EvilIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/EvilIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Foundation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/Foundation.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/Ionicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/Octicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/res/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/res/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/res/drawable-xxxhdpi/icon.png -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/MaterialIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Modesta-Script.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/Modesta-Script.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/SimpleLineIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/SimpleLineIcons.ttf -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/logo.imageset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/ios/mindCast/Images.xcassets/logo.imageset/icon.png -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/CircularStd-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/CircularStd-Black.otf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/CircularStd-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/CircularStd-Bold.otf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/CircularStd-Book.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/CircularStd-Book.otf -------------------------------------------------------------------------------- /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/app/src/main/assets/fonts/CircularStd-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/CircularStd-Medium.otf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/splash_screen_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/res/drawable/splash_screen_icon.png -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-20x20@2x.png -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-20x20@3x.png -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-29x29@2x.png -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-29x29@3x.png -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-40x40@2x.png -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-40x40@3x.png -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-60x60@2x.png -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-60x60@3x.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #fff 4 | #000 5 | 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = "debug", 3 | properties = "debug.keystore.properties", 4 | store = "debug.keystore", 5 | visibility = [ 6 | "PUBLIC", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /src/services/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { SERVER_URL } from 'react-native-dotenv'; 3 | 4 | const api = axios.create({ 5 | baseURL: SERVER_URL, 6 | }); 7 | 8 | export default api; 9 | -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-marketing-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/mindCast/HEAD/ios/mindCast/Images.xcassets/AppIcon.appiconset/Icon-marketing-1024x1024.png -------------------------------------------------------------------------------- /flow-typed/npm/flow-bin_v0.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 6a5610678d4b01e13bbfbbc62bdaf583 2 | // flow-typed version: 3817bc6980/flow-bin_v0.x.x/flow_>=v0.25.x 3 | 4 | declare module 'flow-bin' { 5 | declare module.exports: string; 6 | } 7 | -------------------------------------------------------------------------------- /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-4.7-all.zip 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | * @lint-ignore-every XPLATJSCOPYRIGHT1 4 | */ 5 | 6 | import { AppRegistry } from 'react-native'; 7 | import App from './src'; 8 | import { name as appName } from './app.json'; 9 | 10 | AppRegistry.registerComponent(appName, () => App); 11 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /ios/mindCast/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | @property (nonatomic, strong) UIWindow *window; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /src/config/ReactotronConfig.js: -------------------------------------------------------------------------------- 1 | import Reactotron from 'reactotron-react-native'; 2 | import { reactotronRedux } from 'reactotron-redux'; 3 | import sagaPlugin from 'reactotron-redux-saga'; 4 | 5 | if (__DEV__) { 6 | const tron = Reactotron.configure({ host: '192.168.25.27' }) 7 | .useReactNative() 8 | .use(reactotronRedux()) 9 | .use(sagaPlugin()) 10 | .connect(); 11 | 12 | tron.clear(); 13 | 14 | console.tron = tron; 15 | } 16 | -------------------------------------------------------------------------------- /src/store/ducks/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import localPodcastsManager from './localPodcastsManager'; 4 | import playlist from './playlist'; 5 | import subject from './subject'; 6 | import author from './author'; 7 | import player from './player'; 8 | import home from './home'; 9 | 10 | export default combineReducers({ 11 | localPodcastsManager, 12 | playlist, 13 | subject, 14 | author, 15 | player, 16 | home, 17 | }); 18 | -------------------------------------------------------------------------------- /ios/mindCast/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/mindcast/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.mindcast; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | public class SplashActivity extends AppCompatActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstance) { 10 | super.onCreate(savedInstance); 11 | 12 | Intent intent = new Intent(this, MainActivity.class); 13 | startActivity(intent); 14 | finish(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/launch_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/screens/settings/about/SOCIAL_BUTTONS.js: -------------------------------------------------------------------------------- 1 | export default (SOCIAL_BUTTONS = [ 2 | { 3 | color: '#0077B5', 4 | url: 'https://www.linkedin.com/in/steniowagner', 5 | iconName: 'linkedin', 6 | withPadingTop: false, 7 | }, 8 | { 9 | color: '#333333', 10 | url: 'https://github.com/steniowagner', 11 | iconName: 'github-circle', 12 | withPadingTop: true, 13 | }, 14 | { 15 | color: '#EA4C89', 16 | url: 'https://dribbble.com/steniowagner', 17 | iconName: 'dribbble', 18 | withPadingTop: true, 19 | }, 20 | ]); 21 | -------------------------------------------------------------------------------- /src/components/common/Icon.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; 6 | import { withTheme } from 'styled-components'; 7 | 8 | type Props = { 9 | color: ?string, 10 | theme: Object, 11 | name: string, 12 | size: number, 13 | }; 14 | 15 | const Icon = ({ 16 | theme, color, name, size, 17 | }: Props) => ( 18 | 23 | ); 24 | 25 | export default withTheme(Icon); 26 | -------------------------------------------------------------------------------- /src/store/sagas/subject.js: -------------------------------------------------------------------------------- 1 | import { call, select, put } from 'redux-saga/effects'; 2 | 3 | import { SERVER_URL } from 'react-native-dotenv'; 4 | import api from '~/services/api'; 5 | 6 | import { Creators as SubjectCreators } from '../ducks/subject'; 7 | 8 | export function* getSubjectDetail({ payload }) { 9 | try { 10 | const { id } = payload; 11 | 12 | const { data } = yield call(api.get, `/categories/${id}`); 13 | 14 | yield put(SubjectCreators.getSubjectDetailSuccess(data)); 15 | } catch (err) { 16 | yield put(SubjectCreators.getSubjectDetailFailure()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/common/author-detail/components/SectionWrapper.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | const Wrapper = styled(View)` 8 | width: 100%; 9 | margin-vertical: ${({ theme }) => theme.metrics.extraLargeSize}px; 10 | padding-horizontal: ${({ theme }) => theme.metrics.largeSize}px; 11 | `; 12 | 13 | type Props = { 14 | children: Object, 15 | }; 16 | 17 | const SectionWrapper = ({ children }: Props): Object => ( 18 | {children} 19 | ); 20 | 21 | export default SectionWrapper; 22 | -------------------------------------------------------------------------------- /src/styles/index.js: -------------------------------------------------------------------------------- 1 | import metrics from './metrics'; 2 | import colors from './colors'; 3 | 4 | export default { metrics, colors }; 5 | 6 | export const darkTheme = { 7 | secondaryColor: '#111', 8 | backgroundColor: '#222', 9 | textColor: '#FFF', 10 | subTextColor: 'rgba(255, 255, 255, 0.5)', 11 | androidToolbarColor: '#222', 12 | trendingAuthorsCard: '#222', 13 | }; 14 | 15 | export const lightTheme = { 16 | secondaryColor: '#FFF', 17 | backgroundColor: '#F2F2F2', 18 | textColor: 'rgba(0, 0, 0, 0.8)', 19 | subTextColor: 'rgba(0, 0, 0, 0.4)', 20 | androidToolbarColor: '#F2F2F2', 21 | trendingAuthorsCard: '#FFF', 22 | }; 23 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Fragment } from 'react'; 4 | import { Provider } from 'react-redux'; 5 | 6 | import SoundComponent from './components/common/SoundComponent'; 7 | import { ThemeContextProvider } from './ThemeContextProvider'; 8 | import ApplicationNavigator from './routes'; 9 | import store from './store'; 10 | 11 | import './config/ReactotronConfig'; 12 | 13 | const App = (): Object => ( 14 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | export default App; 24 | -------------------------------------------------------------------------------- /src/components/common/Loading.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { ActivityIndicator, View } from 'react-native'; 5 | 6 | import styled from 'styled-components'; 7 | import appStyles from '~/styles'; 8 | 9 | const LoadingWrapper = styled(View)` 10 | flex: 1; 11 | justify-content: center; 12 | align-items: center; 13 | `; 14 | 15 | type Props = { 16 | size: ?string, 17 | }; 18 | 19 | const Loading = ({ size }: Props): Object => ( 20 | 21 | 25 | 26 | ); 27 | 28 | export default Loading; 29 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import createSagaMiddleware from 'redux-saga'; 3 | 4 | import reducers from './ducks'; 5 | import sagas from './sagas'; 6 | 7 | import '~/config/ReactotronConfig'; 8 | 9 | const sagaMonitor = __DEV__ ? console.tron.createSagaMonitor() : null; 10 | const sagaMiddleware = createSagaMiddleware({ sagaMonitor }); 11 | 12 | const middleware = [sagaMiddleware]; 13 | 14 | const createAppropriateStore = __DEV__ ? console.tron.createStore : createStore; 15 | const store = createAppropriateStore(reducers, applyMiddleware(...middleware)); 16 | 17 | sagaMiddleware.run(sagas); 18 | 19 | export default store; 20 | -------------------------------------------------------------------------------- /src/components/screens/interests/Interests.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | 5 | import InterestsComponent from '~/components/common/interests/Interests'; 6 | import CONSTANTS from '~/utils/CONSTANTS'; 7 | 8 | type Props = { 9 | navigation: Object, 10 | }; 11 | 12 | class Interests extends Component { 13 | componentDidMount() { 14 | const { navigation } = this.props; 15 | 16 | navigation.setParams({ 17 | [CONSTANTS.PARAMS.HEADER_ACTION]: () => navigation.navigate(CONSTANTS.ROUTES.MAIN_STACK), 18 | }); 19 | } 20 | 21 | render() { 22 | return ; 23 | } 24 | } 25 | 26 | export default Interests; 27 | -------------------------------------------------------------------------------- /src/styles/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | darkText: 'rgba(0, 0, 0, 0.8)', 3 | subText: 'rgba(0, 0, 0, 0.5)', 4 | subTextWhite: '#bbb', 5 | darkLayer: 'rgba(0, 0, 0, 0.6)', 6 | lighDarkLayer: 'rgba(0, 0, 0, 0.45)', 7 | progressiveImageForeground: 'rgba(242, 242, 242, 0.5)', 8 | primaryColor: '#EF010B', 9 | primaryColorAlpha: 'rgba(239, 1, 11, 0.2)', 10 | interestSelectedColor: 'rgba(239, 1, 11, 0.6)', 11 | yellow: '#F8C330', 12 | white: '#FFF', 13 | lightSecondaryColor: '#222', 14 | dark: '#111', 15 | facebook: '#3B5998', 16 | googlePlus: '#DD4B39', 17 | black: '#000', 18 | lightDark: 'rgba(0, 0, 0, 0.4)', 19 | transparentGray: 'rgba(218, 218, 218, 0.8)', 20 | }; 21 | -------------------------------------------------------------------------------- /android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 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 | -------------------------------------------------------------------------------- /src/components/common/player/components/bottom-player-options/components/AddPodcastPlaylist.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import Icon from '~/components/common/Icon'; 6 | import Button from './Button'; 7 | import appStyles from '~/styles'; 8 | 9 | type Props = { 10 | onToggleAddPlaylistModal: Function, 11 | }; 12 | 13 | const AddPodcastPlaylist = ({ onToggleAddPlaylistModal }: Props): Object => ( 14 | 23 | ); 24 | 25 | export default AddPodcastPlaylist; 26 | -------------------------------------------------------------------------------- /src/components/common/player/components/bottom-player-options/components/Button.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TouchableOpacity } from 'react-native'; 5 | 6 | import appStyles from '~/styles'; 7 | 8 | type Props = { 9 | onPress: Function, 10 | children: Object, 11 | }; 12 | 13 | const Button = ({ onPress, children }: Props): Object => ( 14 | 23 | {children} 24 | 25 | ); 26 | 27 | export default Button; 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | 'module:metro-react-native-babel-preset', 4 | 'module:react-native-dotenv', 5 | ], 6 | plugins: [ 7 | ['@babel/plugin-transform-flow-strip-types'], 8 | ['@babel/plugin-proposal-class-properties', { loose: true }], 9 | [ 10 | 'babel-plugin-root-import', 11 | { 12 | rootPathSuffix: 'src', 13 | }, 14 | ], 15 | ], 16 | env: { 17 | production: { 18 | plugins: [ 19 | ['@babel/plugin-transform-flow-strip-types'], 20 | ['@babel/plugin-proposal-class-properties', { loose: true }], 21 | [ 22 | 'babel-plugin-root-import', 23 | { 24 | rootPathSuffix: 'src', 25 | }, 26 | ], 27 | ], 28 | }, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/common/ScreenTitle.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { Platform, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | const Title = styled(Text)` 8 | width: 100%; 9 | margin-top: ${({ theme }) => { 10 | const percentage = Platform.OS === 'android' ? '8%' : '15%'; 11 | return theme.metrics.getWidthFromDP(percentage); 12 | }}px; 13 | margin-left: ${({ theme }) => theme.metrics.largeSize}px; 14 | font-size: ${({ theme }) => theme.metrics.extraLargeSize * 1.7}px; 15 | font-family: CircularStd-Black; 16 | color: ${({ theme }) => theme.colors.textColor}; 17 | `; 18 | 19 | type Props = { 20 | title: string, 21 | }; 22 | 23 | const ScreenTitle = ({ title }: Props) => {title}; 24 | 25 | export default ScreenTitle; 26 | -------------------------------------------------------------------------------- /src/components/common/SwipeOutButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import Icon from '~/components/common/Icon'; 8 | import appStyles from '~/styles'; 9 | 10 | const Wrapper = styled(View)` 11 | width: 100%; 12 | height: 100%; 13 | justify-content: center; 14 | align-items: center; 15 | background-color: ${({ color }) => color}; 16 | `; 17 | 18 | type Props = { 19 | color: string, 20 | icon: string, 21 | }; 22 | 23 | const SwipeOutButton = ({ color, icon }: Props): Object => ( 24 | 27 | 32 | 33 | ); 34 | 35 | export default SwipeOutButton; 36 | -------------------------------------------------------------------------------- /src/store/sagas/utils/parseParams.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | const parseParams = (params: Object): string => { 4 | const keys = Object.keys(params); 5 | let options = ''; 6 | 7 | keys.forEach((key) => { 8 | const isParamTypeObject = typeof params[key] === 'object'; 9 | const isParamTypeArray = isParamTypeObject && params[key].length >= 0; 10 | 11 | if (!isParamTypeObject) { 12 | options += `${key}=${params[key]}&`; 13 | } 14 | 15 | if (isParamTypeObject && isParamTypeArray) { 16 | if (params[key].length === 0) { 17 | options += `${key}=all&`; 18 | } 19 | 20 | params[key].forEach((element) => { 21 | options += `${key}=${element}&`; 22 | }); 23 | } 24 | }); 25 | 26 | return options ? options.slice(0, -1) : options; 27 | }; 28 | 29 | export default parseParams; 30 | -------------------------------------------------------------------------------- /src/utils/isEqualsOrLargestThanIphoneX.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { Dimensions, Platform } from 'react-native'; 4 | 5 | const screenWidth = Dimensions.get('window').width; 6 | const screenHeight = Dimensions.get('window').height; 7 | 8 | const IPHONEX_WIDTH = 375; 9 | const IPHONEX_HEIGHT = 812; 10 | 11 | const isEqualsOrLargestThanIphoneX = (): boolean => { 12 | const isEqualsOrLargestThanIphoneXInPortraitMode = screenHeight >= IPHONEX_HEIGHT && screenWidth >= IPHONEX_WIDTH; 13 | const isEqualsOrLargestThanIphoneXInLandscapeMode = screenHeight >= IPHONEX_WIDTH && screenWidth >= IPHONEX_HEIGHT; 14 | 15 | return ( 16 | Platform.OS === 'ios' 17 | && (isEqualsOrLargestThanIphoneXInPortraitMode 18 | || isEqualsOrLargestThanIphoneXInLandscapeMode) 19 | ); 20 | }; 21 | 22 | export default isEqualsOrLargestThanIphoneX; 23 | -------------------------------------------------------------------------------- /ios/mindCastTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 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 | -------------------------------------------------------------------------------- /src/components/common/author-detail/components/AuthorName.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | const Wrapper = styled(View)` 8 | width: 100%; 9 | height: ${({ theme }) => theme.metrics.getHeightFromDP('30%')}; 10 | justify-content: flex-end; 11 | padding-right: ${({ theme }) => theme.metrics.getWidthFromDP('35%')}px; 12 | `; 13 | 14 | const Name = styled(Text)` 15 | font-family: CircularStd-Black; 16 | font-size: ${({ theme }) => theme.metrics.getWidthFromDP('10%')}px; 17 | color: ${({ theme }) => theme.colors.textColor}; 18 | `; 19 | 20 | type Props = { 21 | name: string, 22 | }; 23 | 24 | const AuthorName = ({ name }: Props) => ( 25 | 26 | {name} 27 | 28 | ); 29 | 30 | export default AuthorName; 31 | -------------------------------------------------------------------------------- /src/components/common/player/components/bottom-player-options/components/ShufflePlaylist.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import Icon from '~/components/common/Icon'; 6 | import appStyles from '~/styles'; 7 | import Button from './Button'; 8 | 9 | type Props = { 10 | shouldShufflePlaylist: boolean, 11 | shufflePlaylist: Function, 12 | }; 13 | 14 | const ShufflePlaylist = ({ 15 | shouldShufflePlaylist, 16 | shufflePlaylist, 17 | }: Props): Object => ( 18 | 31 | ); 32 | 33 | export default ShufflePlaylist; 34 | -------------------------------------------------------------------------------- /src/components/screens/login/components/DefaultText.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | const TextStyle = styled(Text)` 8 | margin-right: ${({ withMarginRight, theme }) => (withMarginRight ? theme.metrics.smallSize : 0)}px; 9 | font-size: ${({ theme }) => theme.metrics.getWidthFromDP('4.5%')}px; 10 | font-family: CircularStd-Bold; 11 | color: ${({ color }) => color}; 12 | text-align: center; 13 | `; 14 | 15 | type Props = { 16 | withMarginRight: ?boolean, 17 | color: string, 18 | text: string, 19 | }; 20 | 21 | const DefaultText = ({ withMarginRight, color, text }: Props): Object => ( 22 | 26 | {text} 27 | 28 | ); 29 | 30 | export default DefaultText; 31 | -------------------------------------------------------------------------------- /ios/mindCast-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 | -------------------------------------------------------------------------------- /src/components/common/author-detail/components/AboutSection.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import SectionTitle from '~/components/common/SectionTitle'; 8 | 9 | const Wrapper = styled(View)` 10 | width: 100%; 11 | `; 12 | 13 | const About = styled(Text)` 14 | margin-top: ${({ theme }) => theme.metrics.largeSize}px; 15 | font-size: ${({ theme }) => theme.metrics.largeSize * 1.2}px; 16 | font-family: CircularStd-Medium; 17 | color: ${({ theme }) => theme.colors.textColor}; 18 | `; 19 | 20 | type Props = { 21 | about: string, 22 | }; 23 | 24 | const AboutSection = ({ about }: Props): Object => ( 25 | 26 | 29 | {about} 30 | 31 | ); 32 | 33 | export default AboutSection; 34 | -------------------------------------------------------------------------------- /src/components/common/SectionTitle.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | const Title = styled(Text)` 8 | width: 100%; 9 | margin-bottom: ${({ theme }) => theme.metrics.mediumSize}px; 10 | font-size: ${({ theme }) => theme.metrics.extraLargeSize * 1.2}px; 11 | font-family: CircularStd-Bold; 12 | color: ${({ theme }) => theme.colors.textColor}; 13 | `; 14 | 15 | const Line = styled(View)` 16 | height: ${({ theme }) => theme.metrics.getHeightFromDP('0.5%')}px; 17 | width: ${({ theme }) => theme.metrics.extraLargeSize * 2}px; 18 | background-color: ${({ theme }) => theme.colors.primaryColor}; 19 | `; 20 | 21 | type Props = { 22 | title: string, 23 | }; 24 | 25 | const SectionTitle = ({ title }: Props): Object => ( 26 | 27 | {title} 28 | 29 | 30 | ); 31 | 32 | export default SectionTitle; 33 | -------------------------------------------------------------------------------- /src/components/common/Switch.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { Platform, Switch } from 'react-native'; 5 | import appStyles from '~/styles'; 6 | 7 | type Props = { 8 | onToggle: Function, 9 | value: boolean, 10 | }; 11 | 12 | const CustomSwitch = ({ onToggle, value }: Props): Object => { 13 | const thumbTintColor = value 14 | ? appStyles.colors.primaryColor 15 | : appStyles.colors.white; 16 | 17 | const trackColor = { 18 | true: 19 | Platform.OS === 'android' 20 | ? appStyles.colors.primaryColorAlpha 21 | : appStyles.colors.primaryColor, 22 | false: Platform.OS === 'android' ? appStyles.colors.subTextWhite : '', 23 | }; 24 | 25 | return ( 26 | 32 | ); 33 | }; 34 | 35 | export default CustomSwitch; 36 | -------------------------------------------------------------------------------- /src/components/screens/login/components/BackgroundImage.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View } from 'react-native'; 5 | import FastImage from 'react-native-fast-image'; 6 | import styled from 'styled-components'; 7 | 8 | const Wrapper = styled(View)` 9 | width: 100%; 10 | height: 100%; 11 | position: absolute; 12 | `; 13 | 14 | const DarkLayer = styled(View)` 15 | width: 100%; 16 | height: 100%; 17 | position: absolute; 18 | background-color: ${({ theme }) => theme.colors.darkLayer}; 19 | `; 20 | 21 | const Image = styled(FastImage).attrs({ 22 | source: { 23 | uri: 24 | 'https://s3-sa-east-1.amazonaws.com/mind-cast/images/background-image.jpg', 25 | }, 26 | })` 27 | position: absolute; 28 | width: 100%; 29 | height: 100%; 30 | `; 31 | 32 | const BackgroundImage = (): Object => ( 33 | 34 | 35 | 36 | 37 | ); 38 | 39 | export default BackgroundImage; 40 | -------------------------------------------------------------------------------- /src/components/screens/library/Library.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import ScreenTitle from '~/components/common/ScreenTitle'; 8 | import Playlists from './components/playlists/Playlists'; 9 | import Sections from './components/sections/Sections'; 10 | import CONSTANTS from '~/utils/CONSTANTS'; 11 | 12 | const Wrapper = styled(View)` 13 | width: 100%; 14 | height: 100%; 15 | background-color: ${({ theme }) => theme.colors.secondaryColor}; 16 | `; 17 | 18 | type Props = { 19 | LOCAL_STACK_ROUTES: Object, 20 | navigation: Object, 21 | }; 22 | 23 | const Library = ({ LOCAL_STACK_ROUTES, navigation }: Props): Object => ( 24 | 25 | 28 | 32 | 33 | ); 34 | 35 | export default Library; 36 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-jest_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: d962e1f20379146a7bcaf03c5b32068b 2 | // flow-typed version: <>/babel-jest_v24.1.0/flow_v0.92.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-jest' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-jest' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-jest/build/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'babel-jest/build/index.js' { 31 | declare module.exports: $Exports<'babel-jest/build/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-core_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 3852c7fdc189c8b0f361ad03d4368c5b 2 | // flow-typed version: <>/babel-core_v^7.0.0-bridge.0/flow_v0.92.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-core' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-core' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | 26 | // Filename aliases 27 | declare module 'babel-core/index' { 28 | declare module.exports: $Exports<'babel-core'>; 29 | } 30 | declare module 'babel-core/index.js' { 31 | declare module.exports: $Exports<'babel-core'>; 32 | } 33 | -------------------------------------------------------------------------------- /flow-typed/npm/reactotron-redux_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 2396aaa22bd56cb09a6a49089d29dee3 2 | // flow-typed version: <>/reactotron-redux_v^2.1.3/flow_v0.92.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'reactotron-redux' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'reactotron-redux' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'reactotron-redux/dist/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'reactotron-redux/dist/index.js' { 31 | declare module.exports: $Exports<'reactotron-redux/dist/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /src/store/sagas/author.js: -------------------------------------------------------------------------------- 1 | import { 2 | call, select, put, delay, 3 | } from 'redux-saga/effects'; 4 | 5 | import { Creators as AuthorCreators } from '../ducks/author'; 6 | import parseParams from './utils/parseParams'; 7 | import api from '~/services/api'; 8 | 9 | export function* searchAuthorByName({ payload }) { 10 | try { 11 | const { name } = payload; 12 | 13 | const { data } = yield call(api.get, '/authors/filter', { 14 | paramsSerializer: params => parseParams(params), 15 | params: { name }, 16 | }); 17 | 18 | yield put(AuthorCreators.searchAuthorByNameSuccess(data.authors)); 19 | } catch (err) { 20 | yield put(AuthorCreators.searchAuthorByNameFailure()); 21 | } 22 | } 23 | 24 | export function* getAuthorById({ payload }) { 25 | try { 26 | const { id } = payload; 27 | 28 | const { data } = yield call(api.get, `/authors/${id}`); 29 | 30 | yield put(AuthorCreators.getAuthorByIdSuccess(data.author)); 31 | } catch (err) { 32 | yield put(AuthorCreators.getAuthorByIdFailure()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/common/player/components/BackgroundImage.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View, Image } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | const Container = styled(View)` 8 | width: 100%; 9 | height: 100%; 10 | position: absolute; 11 | background-color: ${({ theme }) => theme.colors.darkLayer}; 12 | `; 13 | 14 | const BlurredImage = styled(Image).attrs(({ imageURL }) => ({ 15 | source: { uri: imageURL }, 16 | resize: 'cover', 17 | blurRadius: 1, 18 | }))` 19 | width: 100%;' 20 | height: 100%; 21 | `; 22 | 23 | const BlackLayer = styled(View)` 24 | width: 100%; 25 | height: 100%; 26 | position: absolute; 27 | background-color: ${({ theme }) => theme.colors.darkLayer}; 28 | `; 29 | 30 | type Props = { 31 | imageURL: string, 32 | }; 33 | 34 | const BackgroundImage = ({ imageURL }: Props): Object => ( 35 | 36 | 39 | 40 | 41 | ); 42 | 43 | export default BackgroundImage; 44 | -------------------------------------------------------------------------------- /src/components/common/player/components/PodcastImage.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import ProgressiveImage from '~/components/common/ProgressiveImage'; 8 | 9 | const Wrapper = styled(View)` 10 | width: 100%; 11 | justify-content: center; 12 | align-content: center; 13 | `; 14 | 15 | const ImageContainer = styled(View)` 16 | width: 100%; 17 | height: ${({ theme }) => theme.metrics.getHeightFromDP('35%')}px; 18 | padding-horizontal: ${({ theme }) => theme.metrics.getWidthFromDP('15%')}px; 19 | align-self: center; 20 | `; 21 | 22 | type Props = { 23 | thumbnailImageURL: string, 24 | imageURL: string, 25 | }; 26 | 27 | const PodcastImage = ({ thumbnailImageURL, imageURL }: Props): Object => ( 28 | 29 | 30 | 34 | 35 | 36 | ); 37 | 38 | export default PodcastImage; 39 | -------------------------------------------------------------------------------- /flow-typed/npm/reactotron-redux-saga_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: dd74c53eaa36b17213ee6d79bb810107 2 | // flow-typed version: <>/reactotron-redux-saga_v^4.0.0/flow_v0.92.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'reactotron-redux-saga' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'reactotron-redux-saga' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'reactotron-redux-saga/dist/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'reactotron-redux-saga/dist/index.js' { 31 | declare module.exports: $Exports<'reactotron-redux-saga/dist/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # dotenv production 45 | .env.production 46 | 47 | # fastlane 48 | # 49 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 50 | # screenshots whenever they are needed. 51 | # For more information about the recommended setup visit: 52 | # https://docs.fastlane.tools/best-practices/source-control/ 53 | 54 | */fastlane/report.xml 55 | */fastlane/Preview.html 56 | */fastlane/screenshots 57 | 58 | # Bundle artifact 59 | *.jsbundle 60 | -------------------------------------------------------------------------------- /flow-typed/npm/reactotron-react-native_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: b29dac161cf068a52b547e4ec86e3b00 2 | // flow-typed version: <>/reactotron-react-native_v^2.1.5/flow_v0.92.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'reactotron-react-native' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'reactotron-react-native' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'reactotron-react-native/dist/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'reactotron-react-native/dist/index.js' { 31 | declare module.exports: $Exports<'reactotron-react-native/dist/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/common/SectionWithButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import DefaultButton from './DefaultButton'; 8 | import SectionTitle from './SectionTitle'; 9 | 10 | const ContentWrapper = styled(View)` 11 | width: 100%; 12 | flex-direction: row; 13 | justify-content: space-between; 14 | align-items: center; 15 | padding-horizontal: ${({ theme }) => theme.metrics.largeSize}px; 16 | `; 17 | 18 | type Props = { 19 | sectionTitle: string, 20 | buttonSize: string, 21 | buttonText: string, 22 | onPress: Function, 23 | }; 24 | 25 | const SectionWithButton = ({ 26 | sectionTitle, 27 | buttonText, 28 | buttonSize, 29 | onPress, 30 | }: Props): Object => ( 31 | 32 | 35 | 41 | 42 | ); 43 | 44 | export default SectionWithButton; 45 | -------------------------------------------------------------------------------- /src/utils/AsyncStorageManager.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { AsyncStorage } from 'react-native'; 4 | import CONSTANTS from '~/utils/CONSTANTS'; 5 | 6 | export const getItemFromStorage = async ( 7 | key: string, 8 | defaultValue: any, 9 | ): any => { 10 | try { 11 | const valueFromStorage = await AsyncStorage.getItem( 12 | `${CONSTANTS.KEYS.APP_STORAGE_KEY}:${key}`, 13 | ); 14 | 15 | return valueFromStorage || defaultValue; 16 | } catch (error) { 17 | console.log(error); 18 | } 19 | 20 | return defaultValue; 21 | }; 22 | 23 | export const persistItemInStorage = async ( 24 | key: string, 25 | value: any, 26 | ): Promise => { 27 | try { 28 | await AsyncStorage.setItem( 29 | `${CONSTANTS.KEYS.APP_STORAGE_KEY}:${key}`, 30 | JSON.stringify(value), 31 | ); 32 | } catch (err) { 33 | console.log(err); 34 | } 35 | }; 36 | 37 | export const removeItemFromStorage = async (key: string) => { 38 | try { 39 | await AsyncStorage.removeItem(`${CONSTANTS.KEYS.APP_STORAGE_KEY}:${key}`); 40 | } catch (err) { 41 | console.log(err); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /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 | MYAPP_RELEASE_STORE_FILE=my-release-key.keystore 21 | MYAPP_RELEASE_KEY_ALIAS=my-key-alias 22 | MYAPP_RELEASE_STORE_PASSWORD=qwpo10xl 23 | MYAPP_RELEASE_KEY_PASSWORD=qwpo10xl 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Stenio Wagner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "28.0.2" 6 | minSdkVersion = 16 7 | compileSdkVersion = 28 8 | targetSdkVersion = 27 9 | supportLibVersion = "28.0.0" 10 | } 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | dependencies { 16 | classpath 'com.android.tools.build:gradle:3.2.1' 17 | 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | mavenLocal() 26 | google() 27 | jcenter() 28 | maven { 29 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 30 | url "$rootDir/../node_modules/react-native/android" 31 | } 32 | } 33 | } 34 | 35 | 36 | task wrapper(type: Wrapper) { 37 | gradleVersion = '4.7' 38 | distributionUrl = distributionUrl.replace("bin", "all") 39 | } 40 | -------------------------------------------------------------------------------- /src/components/common/HeaderActionButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TouchableOpacity, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import Icon from '~/components/common/Icon'; 8 | import appStyles from '~/styles'; 9 | 10 | const Wrapper = styled(View)` 11 | justify-content: center; 12 | align-items: center; 13 | padding-top: 3px; 14 | margin-right: ${({ theme }) => theme.metrics.largeSize}px; 15 | `; 16 | 17 | type Props = { 18 | onPress: Function, 19 | color: string, 20 | icon: string, 21 | }; 22 | 23 | const HeaderActionButton = ({ onPress, color, icon }: Props): Object => ( 24 | 25 | 34 | 39 | 40 | 41 | ); 42 | 43 | export default HeaderActionButton; 44 | -------------------------------------------------------------------------------- /src/store/ducks/home.js: -------------------------------------------------------------------------------- 1 | export const Types = { 2 | GET_HOME_REQUEST: 'subject/GET_HOME_REQUEST', 3 | GET_HOME_SUCCESS: 'subject/GET_HOME_SUCCESS', 4 | GET_HOME_ERROR: 'subject/GET_HOME_ERROR', 5 | }; 6 | 7 | const INITIAL_STATE = { 8 | loading: true, 9 | error: false, 10 | data: null, 11 | }; 12 | 13 | export const Creators = { 14 | getHome: () => ({ 15 | type: Types.GET_HOME_REQUEST, 16 | }), 17 | 18 | getHomeSuccess: data => ({ 19 | type: Types.GET_HOME_SUCCESS, 20 | payload: { data }, 21 | }), 22 | 23 | getHomeFailure: () => ({ 24 | type: Types.GET_HOME_ERROR, 25 | }), 26 | }; 27 | 28 | const subject = (state = INITIAL_STATE, { type, payload }) => { 29 | switch (type) { 30 | case Types.GET_HOME_REQUEST: 31 | return { 32 | ...INITIAL_STATE, 33 | }; 34 | 35 | case Types.GET_HOME_SUCCESS: 36 | return { 37 | ...state, 38 | data: payload.data, 39 | loading: false, 40 | }; 41 | 42 | case Types.GET_HOME_ERROR: 43 | return { 44 | ...state, 45 | loading: false, 46 | error: true, 47 | }; 48 | 49 | default: 50 | return state; 51 | } 52 | }; 53 | 54 | export default subject; 55 | -------------------------------------------------------------------------------- /src/store/sagas/home.js: -------------------------------------------------------------------------------- 1 | import { 2 | call, select, delay, put, 3 | } from 'redux-saga/effects'; 4 | 5 | import api from '~/services/api'; 6 | import { SERVER_URL } from 'react-native-dotenv'; 7 | 8 | import { getItemFromStorage } from '../../utils/AsyncStorageManager'; 9 | import { Creators as HomeCreators } from '../ducks/home'; 10 | import CONSTANTS from '../../utils/CONSTANTS'; 11 | import parseParams from './utils/parseParams'; 12 | 13 | export function* getHome() { 14 | try { 15 | const rawInterests = yield call( 16 | getItemFromStorage, 17 | CONSTANTS.KEYS.INTERESTS_STORAGE_KEY, 18 | [], 19 | ); 20 | 21 | const interests = typeof rawInterests === 'string' 22 | ? JSON.parse(rawInterests) 23 | : rawInterests; 24 | 25 | const interestsSelected = interests 26 | .filter(interest => interest.isSelected) 27 | .map(interest => interest.title.toLowerCase()); 28 | 29 | const { data } = yield call(api.get, '/home', { 30 | paramsSerializer: params => parseParams(params), 31 | params: { categories: interestsSelected }, 32 | }); 33 | 34 | yield put(HomeCreators.getHomeSuccess(data)); 35 | } catch (err) { 36 | yield put(HomeCreators.getHomeFailure()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/mindcast/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mindcast; 2 | 3 | import android.os.Bundle; 4 | import com.facebook.react.ReactActivity; 5 | import com.facebook.react.ReactActivityDelegate; 6 | import com.facebook.react.ReactRootView; 7 | import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; 8 | import org.devio.rn.splashscreen.SplashScreen; 9 | 10 | public class MainActivity extends ReactActivity { 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | SplashScreen.show(this); 14 | super.onCreate(savedInstanceState); 15 | } 16 | 17 | /** 18 | * Returns the name of the main component registered from JavaScript. 19 | * This is used to schedule rendering of the component. 20 | */ 21 | @Override 22 | protected String getMainComponentName() { 23 | return "mindCast"; 24 | } 25 | 26 | @Override 27 | protected ReactActivityDelegate createReactActivityDelegate() { 28 | return new ReactActivityDelegate(this, getMainComponentName()) { 29 | @Override 30 | protected ReactRootView createRootView() { 31 | return new RNGestureHandlerEnabledRootView(MainActivity.this); 32 | } 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/styles/metrics.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { Dimensions, Platform, PixelRatio } from 'react-native'; 4 | 5 | const { width, height } = Dimensions.get('window'); 6 | 7 | const getWidthFromDP = (widthPercentage: string): number => { 8 | const percentageDesired = parseFloat(widthPercentage); 9 | const widthPercentageToDP = PixelRatio.roundToNearestPixel( 10 | (width * percentageDesired) / 100, 11 | ); 12 | 13 | return widthPercentageToDP; 14 | }; 15 | 16 | const getHeightFromDP = (heightPercentage: string): number => { 17 | const percentageDesired = parseFloat(heightPercentage); 18 | const heightPercentageToDP = PixelRatio.roundToNearestPixel( 19 | (height * percentageDesired) / 100, 20 | ); 21 | 22 | return heightPercentageToDP; 23 | }; 24 | 25 | export default { 26 | navigationHeaderFontSize: Platform.OS === 'ios' ? 17 : 19, 27 | navigationHeaderHeight: Platform.OS === 'ios' ? 64 : 54, 28 | statusBarHeight: Platform.OS === 'ios' ? 20 : 0, 29 | extraSmallSize: getWidthFromDP('1%'), 30 | smallSize: getWidthFromDP('2%'), 31 | mediumSize: getWidthFromDP('3%'), 32 | largeSize: getWidthFromDP('4%'), 33 | extraLargeSize: getWidthFromDP('5%'), 34 | getWidthFromDP, 35 | getHeightFromDP, 36 | width, 37 | height, 38 | }; 39 | -------------------------------------------------------------------------------- /flow-typed/npm/prettier-eslint_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 16b8620cf8cc56c172f40336bb734c42 2 | // flow-typed version: <>/prettier-eslint_v^8.8.2/flow_v0.92.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'prettier-eslint' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'prettier-eslint' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'prettier-eslint/dist/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'prettier-eslint/dist/utils' { 30 | declare module.exports: any; 31 | } 32 | 33 | // Filename aliases 34 | declare module 'prettier-eslint/dist/index.js' { 35 | declare module.exports: $Exports<'prettier-eslint/dist/index'>; 36 | } 37 | declare module 'prettier-eslint/dist/utils.js' { 38 | declare module.exports: $Exports<'prettier-eslint/dist/utils'>; 39 | } 40 | -------------------------------------------------------------------------------- /flow-typed/npm/eslint-import-resolver-babel-plugin-root-import_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 3512ef1e89d0b84ddb2a2c277f1e6ca6 2 | // flow-typed version: <>/eslint-import-resolver-babel-plugin-root-import_v^1.1.1/flow_v0.92.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'eslint-import-resolver-babel-plugin-root-import' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'eslint-import-resolver-babel-plugin-root-import' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'eslint-import-resolver-babel-plugin-root-import/src/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'eslint-import-resolver-babel-plugin-root-import/src/index.js' { 31 | declare module.exports: $Exports< 32 | 'eslint-import-resolver-babel-plugin-root-import/src/index', 33 | >; 34 | } 35 | -------------------------------------------------------------------------------- /ios/mindCast/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "AppDelegate.h" 9 | 10 | #import 11 | #import 12 | 13 | @implementation AppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | NSURL *jsCodeLocation; 18 | 19 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 20 | 21 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 22 | moduleName:@"mindCast" 23 | initialProperties:nil 24 | launchOptions:launchOptions]; 25 | rootView.backgroundColor = [UIColor blackColor]; 26 | 27 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 28 | UIViewController *rootViewController = [UIViewController new]; 29 | rootViewController.view = rootView; 30 | self.window.rootViewController = rootViewController; 31 | [self.window makeKeyAndVisible]; 32 | return YES; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "jest": true 5 | }, 6 | "plugins": ["react-native", "jsx-a11y", "import"], 7 | "extends": ["airbnb", "plugin:react-native/all"], 8 | "rules": { 9 | "no-plusplus": 0, 10 | "max-len": 0, 11 | "lines-between-class-members": 0, 12 | "no-underscore-dangle": 0, 13 | "import/no-cycle": 0, 14 | "no-alert": 0, 15 | "react-native/split-platform-components": 0, 16 | "import/no-extraneous-dependencies": 0, 17 | "import/no-dynamic-require": 0, 18 | "react/jsx-max-props-per-line": [1, { "maximum": 1, "when": "always" }], 19 | "react/jsx-first-prop-new-line": [1, "always"], 20 | "react-native/no-inline-styles": 0, 21 | "react/jsx-wrap-multilines": 0, 22 | "react-native/no-raw-text": 0, 23 | "consistent-return": 0, 24 | "react/jsx-filename-extension": [ 25 | "error", 26 | { 27 | "extensions": [".js", ".jsx"] 28 | } 29 | ], 30 | "global-require": "off", 31 | "no-console": "off", 32 | "import/prefer-default-export": "off", 33 | "no-unused-vars": [ 34 | "error", 35 | { 36 | "argsIgnorePattern": "^_" 37 | } 38 | ] 39 | }, 40 | "settings": { 41 | "import/resolver": { 42 | "babel-plugin-root-import": {} 43 | } 44 | }, 45 | "globals": { 46 | "__DEV__": true 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/store/ducks/subject.js: -------------------------------------------------------------------------------- 1 | export const Types = { 2 | GET_SUBJECT_DETAIL_REQUEST: 'subject/GET_SUBJECT_DETAIL_REQUEST', 3 | GET_SUBJECT_DETAIL_SUCCESS: 'subject/GET_SUBJECT_DETAIL_SUCCESS', 4 | GET_SUBJECT_DETAIL_ERROR: 'subject/GET_SUBJECT_DETAIL_ERROR', 5 | }; 6 | 7 | const INITIAL_STATE = { 8 | loading: true, 9 | error: false, 10 | data: null, 11 | }; 12 | 13 | export const Creators = { 14 | getSubjectDetail: id => ({ 15 | type: Types.GET_SUBJECT_DETAIL_REQUEST, 16 | payload: { id }, 17 | }), 18 | 19 | getSubjectDetailSuccess: data => ({ 20 | type: Types.GET_SUBJECT_DETAIL_SUCCESS, 21 | payload: { data }, 22 | }), 23 | 24 | getSubjectDetailFailure: () => ({ 25 | type: Types.GET_SUBJECT_DETAIL_ERROR, 26 | }), 27 | }; 28 | 29 | const subject = (state = INITIAL_STATE, { type, payload }) => { 30 | switch (type) { 31 | case Types.GET_SUBJECT_DETAIL_REQUEST: 32 | return { 33 | ...INITIAL_STATE, 34 | }; 35 | 36 | case Types.GET_SUBJECT_DETAIL_SUCCESS: 37 | return { 38 | ...state, 39 | data: payload.data, 40 | loading: false, 41 | }; 42 | 43 | case Types.GET_SUBJECT_DETAIL_ERROR: 44 | return { 45 | ...state, 46 | loading: false, 47 | error: true, 48 | }; 49 | 50 | default: 51 | return state; 52 | } 53 | }; 54 | 55 | export default subject; 56 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'mindCast' 2 | include ':react-native-splash-screen' 3 | project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android') 4 | include ':react-native-linear-gradient' 5 | project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android') 6 | include ':react-native-gesture-handler' 7 | project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android') 8 | include ':react-native-vector-icons' 9 | project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') 10 | include ':react-native-fs' 11 | project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android') 12 | include ':react-native-video' 13 | project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android') 14 | 15 | include ':app' 16 | 17 | include ':react-native-fs' 18 | project(':react-native-fs').projectDir = new File(settingsDir, '../node_modules/react-native-fs/android') 19 | 20 | include ':react-native-fast-image' 21 | project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android') 22 | -------------------------------------------------------------------------------- /src/components/common/HeaderButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { Platform, TouchableOpacity } from 'react-native'; 5 | 6 | import Icon from '~/components/common/Icon'; 7 | import appStyles from '~/styles'; 8 | 9 | type Props = { 10 | onPress: Function, 11 | iconName: string, 12 | position: string, 13 | }; 14 | 15 | export const POSITIONS = { 16 | RIGHT: 'RIGHT', 17 | LEFT: 'LEFT', 18 | }; 19 | 20 | export const HeaderButton = ({ 21 | onPress, 22 | position, 23 | iconName, 24 | }: Props): Object => { 25 | const isRightPositioned = position === POSITIONS.RIGHT; 26 | const isLeftPositioned = position === POSITIONS.LEFT; 27 | const marginValue = Platform.OS === 'android' ? 16 : 14; 28 | 29 | const marginRight = isRightPositioned ? marginValue : 0; 30 | const marginLeft = isLeftPositioned ? marginValue : 0; 31 | 32 | return ( 33 | 47 | 52 | 53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /src/components/common/navigation/components/navigation-bar/NavigationBar.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import isEqualsOrLargestThanIphoneX from '~/utils/isEqualsOrLargestThanIphoneX'; 8 | import NavigationBarItem from './NavigationBarItem'; 9 | 10 | const Wrapper = styled(View).attrs(({ theme }) => ({ 11 | borderTopColor: theme.colors.secondaryColor, 12 | borderTopRightRadius: 1, 13 | borderTopWidth: 1, 14 | }))` 15 | width: 100%; 16 | height: ${({ theme }) => theme.metrics.getWidthFromDP('18%') 17 | + (isEqualsOrLargestThanIphoneX() ? 30 : 0)}px; 18 | flex-direction: row; 19 | align-items: center; 20 | justify-content: space-evenly; 21 | padding-bottom: ${isEqualsOrLargestThanIphoneX() ? 30 : 0}px; 22 | background-color: ${({ theme }) => theme.colors.backgroundColor}; 23 | `; 24 | 25 | type Props = { 26 | onSelectStackRoute: Function, 27 | stackRouteSelected: number, 28 | items: Array, 29 | }; 30 | 31 | const NavigationBar = ({ 32 | onSelectStackRoute, 33 | stackRouteSelected, 34 | items, 35 | }: Props): Object => ( 36 | 37 | {items.map((item, index) => ( 38 | onSelectStackRoute(item.route)} 41 | isSelected={stackRouteSelected === index} 42 | key={item.label} 43 | /> 44 | ))} 45 | 46 | ); 47 | export default NavigationBar; 48 | -------------------------------------------------------------------------------- /src/components/common/player/components/PodcastTextContent.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | const Container = styled(View)` 8 | width: 100%; 9 | height: ${({ theme }) => theme.metrics.getHeightFromDP('15%')}px; 10 | justify-content: center; 11 | align-items: center; 12 | margin-vertical: ${({ theme }) => theme.metrics.mediumSize}px; 13 | `; 14 | 15 | const Wrapper = styled(View)` 16 | width: 75%; 17 | justify-content: center; 18 | align-items: center; 19 | `; 20 | 21 | const AuthorText = styled(Text).attrs({ 22 | numberOfLines: 2, 23 | })` 24 | font-family: CircularStd-Bold; 25 | color: ${({ theme }) => theme.colors.primaryColor}; 26 | padding-bottom: ${({ theme }) => theme.metrics.smallSize}px; 27 | font-size: ${({ theme }) => theme.metrics.extraLargeSize}px; 28 | text-align: center; 29 | `; 30 | 31 | const TitleText = styled(Text)` 32 | font-family: CircularStd-Black; 33 | color: ${({ theme }) => theme.colors.white}; 34 | font-size: ${({ theme }) => theme.metrics.extraLargeSize * 1.1}px; 35 | text-align: center; 36 | `; 37 | 38 | type Props = { 39 | author: string, 40 | title: string, 41 | }; 42 | 43 | const PodcastTextContent = ({ author, title }: Props): Object => ( 44 | 45 | 46 | {author} 47 | {title} 48 | 49 | 50 | ); 51 | 52 | export default PodcastTextContent; 53 | -------------------------------------------------------------------------------- /ios/mindCast/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-29x29@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-29x29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-40x40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-40x40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-60x60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-60x60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "1024x1024", 53 | "idiom" : "ios-marketing", 54 | "filename" : "Icon-marketing-1024x1024.png", 55 | "scale" : "1x" 56 | } 57 | ], 58 | "info" : { 59 | "version" : 1, 60 | "author" : "xcode" 61 | } 62 | } -------------------------------------------------------------------------------- /src/components/screens/home/Home.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | 5 | import { bindActionCreators } from 'redux'; 6 | import { connect } from 'react-redux'; 7 | import { Creators as HomeCreators } from '~/store/ducks/home'; 8 | 9 | import HomeComponent from './components/HomeComponent'; 10 | import CONSTANTS from '~/utils/CONSTANTS'; 11 | 12 | type HomeStoreData = { 13 | loading: boolean, 14 | error: boolean, 15 | data: Object, 16 | }; 17 | 18 | type Props = { 19 | LOCAL_STACK_ROUTES: Object, 20 | home: HomeStoreData, 21 | navigation: Object, 22 | getHome: Function, 23 | }; 24 | 25 | class HomeContainer extends Component { 26 | componentDidMount() { 27 | const { LOCAL_STACK_ROUTES, getHome, navigation } = this.props; 28 | 29 | getHome(); 30 | 31 | navigation.setParams({ 32 | LOCAL_STACK_ROUTES, 33 | }); 34 | } 35 | 36 | render() { 37 | const { navigation, getHome, home } = this.props; 38 | const { loading, error, data } = home; 39 | 40 | return ( 41 | 48 | ); 49 | } 50 | } 51 | 52 | const mapDispatchToProps = dispatch => bindActionCreators(HomeCreators, dispatch); 53 | 54 | const mapStateToProps = state => ({ 55 | home: state.home, 56 | }); 57 | 58 | export default connect( 59 | mapStateToProps, 60 | mapDispatchToProps, 61 | )(HomeContainer); 62 | -------------------------------------------------------------------------------- /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 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.mindcast", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.mindcast", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/common/AuthorInfo.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | import FastImage from 'react-native-fast-image'; 7 | 8 | const Wrapper = styled(View)` 9 | flex-direction: row; 10 | align-items: center; 11 | `; 12 | 13 | const AuthorImage = styled(FastImage).attrs(({ uri }) => ({ 14 | source: { uri }, 15 | }))` 16 | width: ${({ theme }) => theme.metrics.getWidthFromDP('8%')}px; 17 | height: ${({ theme }) => theme.metrics.getWidthFromDP('8%')}px; 18 | border-radius: ${({ theme }) => theme.metrics.getWidthFromDP('4%')}px; 19 | margin-right: ${({ theme }) => theme.metrics.mediumSize}px; 20 | `; 21 | 22 | const AuthorName = styled(Text).attrs(({ numberOfLines }) => ({ 23 | ellipsizeMode: 'tail', 24 | numberOfLines, 25 | }))` 26 | color: ${({ theme, textColor }) => theme.colors[textColor]}; 27 | font-size: ${({ theme }) => theme.metrics.largeSize}; 28 | font-family: CircularStd-Bold; 29 | `; 30 | 31 | type Props = { 32 | numberOfLines: number, 33 | textColor: string, 34 | imageURL: string, 35 | name: string, 36 | }; 37 | 38 | const AuthorInfo = ({ 39 | numberOfLines, 40 | imageURL, 41 | textColor, 42 | name, 43 | }: Props): Object => ( 44 | 45 | 48 | 52 | {name} 53 | 54 | 55 | ); 56 | 57 | export default AuthorInfo; 58 | -------------------------------------------------------------------------------- /src/components/screens/login/components/Header.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import DefaultText from './DefaultText'; 8 | import appStyles from '~/styles'; 9 | 10 | const Wrapper = styled(View)` 11 | width: 100%; 12 | height: ${({ theme }) => theme.metrics.getHeightFromDP('30%')}px; 13 | justify-content: center; 14 | align-items: center; 15 | `; 16 | 17 | const AppTitle = styled(Text)` 18 | font-size: ${({ theme }) => theme.metrics.getWidthFromDP('8%')}px; 19 | font-family: CircularStd-Black; 20 | color: ${({ theme }) => theme.colors.white}; 21 | text-align: center; 22 | `; 23 | 24 | const SloganWrapper = styled(View)` 25 | width: 100%; 26 | flex-direction: row; 27 | justify-content: center; 28 | margin-top: ${({ theme }) => theme.metrics.mediumSize}px; 29 | `; 30 | 31 | const SloganText = styled(Text)` 32 | font-size: ${({ theme }) => theme.metrics.getWidthFromDP('4.5%')}px; 33 | font-family: CircularStd-Bold; 34 | color: ${({ color }) => color}; 35 | text-align: center; 36 | `; 37 | 38 | const Header = (): Object => ( 39 | 40 | MINDCAST 41 | 42 | 46 | 50 | 51 | 52 | ); 53 | 54 | export default Header; 55 | -------------------------------------------------------------------------------- /src/components/screens/login/components/ChangeAction.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TouchableOpacity, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import DefaultButton from '~/components/common/DefaultButton'; 8 | import DefaultText from './DefaultText'; 9 | import appStyles from '~/styles'; 10 | 11 | const Wrapper = styled(View)` 12 | flex-direction: row; 13 | justify-content: space-between; 14 | align-items: center; 15 | `; 16 | 17 | const Row = styled(View)` 18 | flex-direction: row; 19 | align-items: center; 20 | `; 21 | 22 | type Props = { 23 | onNavigateToMainStack: Function, 24 | onPressActionButton: Function, 25 | changeActionText: string, 26 | questionText: string, 27 | buttonText: string, 28 | }; 29 | 30 | const ChangeAction = ({ 31 | onNavigateToMainStack, 32 | onPressActionButton, 33 | changeActionText, 34 | questionText, 35 | buttonText, 36 | }: Props): Object => ( 37 | 38 | 39 | 43 | 46 | 50 | 51 | 52 | 57 | 58 | ); 59 | 60 | export default ChangeAction; 61 | -------------------------------------------------------------------------------- /src/components/common/DefaultButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TouchableOpacity, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | const ButtonWrapper = styled(TouchableOpacity)` 8 | justify-content: center; 9 | align-items: center; 10 | padding-vertical: ${({ translucent, theme }) => theme.metrics.mediumSize}px; 11 | padding-horizontal: ${({ theme }) => theme.metrics.mediumSize}px; 12 | background-color: ${({ translucent, theme }) => (translucent ? 'transparent' : theme.colors.primaryColor)}; 13 | border: ${({ translucent, theme }) => (translucent ? theme.colors.textColor : 'transparent')} 14 | solid 1.5px; 15 | border-radius: 4.5px; 16 | `; 17 | 18 | const Title = styled(Text)` 19 | color: ${({ translucent, theme }) => (translucent ? theme.colors.textColor : theme.colors.white)}; 20 | font-size: ${({ theme, size }) => (size === 'large' 21 | ? theme.metrics.largeSize 22 | : theme.metrics.mediumSize * 1.2)}px; 23 | font-family: CircularStd-Black; 24 | `; 25 | 26 | type Props = { 27 | translucent: ?boolean, 28 | onPress: Function, 29 | size: string, 30 | text: string, 31 | }; 32 | 33 | const ListenNowButton = ({ 34 | translucent, 35 | onPress, 36 | size, 37 | text, 38 | }: Props): Object => ( 39 | 43 | 47 | {text} 48 | 49 | 50 | ); 51 | 52 | export default ListenNowButton; 53 | -------------------------------------------------------------------------------- /src/components/common/author-detail/AuthorDetailContainer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { View } from 'react-native'; 5 | import { bindActionCreators } from 'redux'; 6 | import { connect } from 'react-redux'; 7 | import { Creators as AuthorCreators } from '~/store/ducks/author'; 8 | 9 | import AuthorDetailComponent from './components/AuthorDetailComponent'; 10 | import CONSTANTS from '~/utils/CONSTANTS'; 11 | 12 | type Props = { 13 | getAuthorById: Function, 14 | navigation: Object, 15 | loading: boolean, 16 | error: boolean, 17 | author: Object, 18 | }; 19 | 20 | class AuthorDetailContainer extends Component { 21 | componentDidMount() { 22 | const { getAuthorById, navigation } = this.props; 23 | const { params } = navigation.state; 24 | const { id } = params[CONSTANTS.PARAMS.AUTHOR_DETAIL]; 25 | 26 | getAuthorById(id); 27 | } 28 | 29 | render() { 30 | const { 31 | navigation, loading, author, error, 32 | } = this.props; 33 | 34 | return ( 35 | 41 | ); 42 | } 43 | } 44 | 45 | const mapStateToProps = ({ author }) => ({ 46 | loading: author.loadingSearchAuthorById, 47 | author: author.author, 48 | error: author.error, 49 | }); 50 | 51 | const mapDispatchToProps = dispatch => bindActionCreators(AuthorCreators, dispatch); 52 | 53 | export default connect( 54 | mapStateToProps, 55 | mapDispatchToProps, 56 | )(AuthorDetailContainer); 57 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-plugin-root-import_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: e3752636fadc94f31a83e833c12b151d 2 | // flow-typed version: <>/babel-plugin-root-import_v^6.1.0/flow_v0.92.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-plugin-root-import' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-plugin-root-import' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-plugin-root-import/build/helper' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'babel-plugin-root-import/build/index' { 30 | declare module.exports: any; 31 | } 32 | 33 | // Filename aliases 34 | declare module 'babel-plugin-root-import/build/helper.js' { 35 | declare module.exports: $Exports<'babel-plugin-root-import/build/helper'>; 36 | } 37 | declare module 'babel-plugin-root-import/build/index.js' { 38 | declare module.exports: $Exports<'babel-plugin-root-import/build/index'>; 39 | } 40 | declare module 'babel-plugin-root-import/index' { 41 | declare module.exports: $Exports<'babel-plugin-root-import'>; 42 | } 43 | declare module 'babel-plugin-root-import/index.js' { 44 | declare module.exports: $Exports<'babel-plugin-root-import'>; 45 | } 46 | -------------------------------------------------------------------------------- /src/components/common/navigation/components/navigation-bar/NavigationBarItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { 5 | TouchableOpacity, Platform, Text, View, 6 | } from 'react-native'; 7 | import styled from 'styled-components'; 8 | 9 | import Icon from '~/components/common/Icon'; 10 | 11 | const Wrapper = styled(TouchableOpacity)` 12 | width: ${({ theme }) => theme.metrics.getWidthFromDP('25%')}px; 13 | height: 100%; 14 | justify-content: center; 15 | align-items: center; 16 | `; 17 | 18 | const ItemText = styled(Text)` 19 | margin-top: ${({ theme }) => (Platform.OS === 'android' ? theme.metrics.extraSmallSize : 0)}px; 20 | font-family: CircularStd-Book; 21 | color: ${({ isSelected, theme }) => (isSelected ? theme.colors.primaryColor : theme.colors.subTextWhite)}; 22 | font-size: ${({ theme }) => theme.metrics.mediumSize}; 23 | `; 24 | 25 | const ItemIcon = styled(Icon).attrs(({ isSelected, theme, icon }) => ({ 26 | color: isSelected ? theme.colors.primaryColor : theme.colors.subTextWhite, 27 | name: icon, 28 | size: 24, 29 | }))``; 30 | 31 | type Props = { 32 | onPressItem: Function, 33 | isSelected: boolean, 34 | label: string, 35 | icon: string, 36 | }; 37 | 38 | const NavigationBarItem = ({ 39 | onPressItem, 40 | isSelected, 41 | label, 42 | icon, 43 | }: Props): Object => ( 44 | 47 | 52 | 55 | {label} 56 | 57 | 58 | ); 59 | 60 | export default NavigationBarItem; 61 | -------------------------------------------------------------------------------- /src/components/screens/library/components/sections/SectionItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TouchableOpacity, View, Text } from 'react-native'; 5 | import styled, { withTheme } from 'styled-components'; 6 | 7 | import Icon from '~/components/common/Icon'; 8 | import appStyles from '~/styles'; 9 | 10 | const Container = styled(TouchableOpacity)` 11 | width: 100%; 12 | flex-direction: row; 13 | justify-content: space-between; 14 | margin-bottom: ${({ theme }) => theme.metrics.extraLargeSize}px; 15 | `; 16 | 17 | const LeftContentWrapper = styled(View)` 18 | flex-direction: row; 19 | justify-content: center; 20 | align-items: center; 21 | `; 22 | 23 | const OptionTitle = styled(Text)` 24 | margin-left: ${({ theme }) => theme.metrics.largeSize}px; 25 | font-family: CircularStd-Medium; 26 | font-size: ${({ theme }) => theme.metrics.extraLargeSize}px; 27 | color: ${({ theme }) => theme.colors.textColor}; 28 | `; 29 | 30 | type Props = { 31 | onPressItem: Function, 32 | iconName: string, 33 | title: string, 34 | theme: Object, 35 | }; 36 | 37 | const SectionItem = ({ 38 | onPressItem, 39 | iconName, 40 | title, 41 | theme, 42 | }: Props): Object => ( 43 | 46 | 47 | 52 | {title} 53 | 54 | 59 | 60 | ); 61 | 62 | export default withTheme(SectionItem); 63 | -------------------------------------------------------------------------------- /src/components/common/ErrorMessage.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import Icon from '~/components/common/Icon'; 8 | 9 | const Wrapper = styled(View)` 10 | width: 100%; 11 | height: 100%; 12 | justify-content: center; 13 | align-items: center; 14 | padding-horizontal: ${({ theme }) => theme.metrics.largeSize}px; 15 | `; 16 | 17 | const ContentWrapper = styled(View)` 18 | justify-content: center; 19 | align-items: center; 20 | margin-top: -${({ theme }) => theme.metrics.extraLargeSize * 1.2}px; 21 | `; 22 | 23 | const Title = styled(Text).attrs({ 24 | numberOfLines: 1, 25 | })` 26 | font-size: ${({ theme }) => theme.metrics.extraLargeSize * 1.4}px; 27 | color: ${({ theme }) => theme.colors.textColor}; 28 | font-family: CircularStd-Black; 29 | text-align: center; 30 | `; 31 | 32 | const Message = styled(Text).attrs({ 33 | numberOfLines: 3, 34 | })` 35 | margin-top: ${({ theme }) => theme.metrics.mediumSize}px; 36 | font-size: ${({ theme }) => theme.metrics.extraLargeSize * 1.1}px; 37 | color: ${({ theme }) => theme.colors.subTextColor}; 38 | font-family: CircularStd-Medium; 39 | text-align: center; 40 | `; 41 | 42 | type Props = { 43 | message: string, 44 | title: string, 45 | icon: string, 46 | }; 47 | 48 | const ErrorMessage = ({ message, title, icon }: Props): Object => ( 49 | 50 | 51 | 55 | {title} 56 | {message} 57 | 58 | 59 | ); 60 | 61 | export default ErrorMessage; 62 | -------------------------------------------------------------------------------- /src/components/screens/home/components/trending-authors/TrendingAuthorsSeeAll.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { FlatList, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import AuthorsListItem from '~/components/common/AuthorListItemWithSubjects'; 8 | import CONSTANTS from '~/utils/CONSTANTS'; 9 | 10 | const Wrapper = styled(View)` 11 | width: 100%; 12 | height: 100%; 13 | flex: 1; 14 | background-color: ${({ theme }) => theme.colors.backgroundColor}; 15 | `; 16 | 17 | const TrendingAuthorsList = styled(FlatList)` 18 | width: 100%; 19 | height: 100%; 20 | `; 21 | 22 | type Props = { 23 | navigation: Object, 24 | }; 25 | 26 | const TrendingAuthorsSeeAll = ({ navigation }: Props): Object => { 27 | const { params } = navigation.state; 28 | const trendingAuthors = params[CONSTANTS.PARAMS.TRENDING_AUTHORS]; 29 | 30 | return ( 31 | 32 | `${author.id}`} 34 | showsVerticalScrollIndicator={false} 35 | data={trendingAuthors} 36 | renderItem={({ item, index }) => ( 37 | navigation.navigate(CONSTANTS.ROUTES.AUTHOR_DETAIL, { 39 | [CONSTANTS.PARAMS.AUTHOR_DETAIL]: { 40 | id: item.id, 41 | }, 42 | }) 43 | } 44 | numberPodcasts={item.podcasts.length} 45 | profileImage={item.profileImageURL} 46 | subjects={item.categories} 47 | name={item.name} 48 | id={item.id} 49 | /> 50 | )} 51 | /> 52 | 53 | ); 54 | }; 55 | 56 | export default TrendingAuthorsSeeAll; 57 | -------------------------------------------------------------------------------- /src/components/screens/settings/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createStackNavigator } from 'react-navigation'; 3 | import { Platform } from 'react-native'; 4 | 5 | import { 6 | getPlayerNavigationOption, 7 | getDefaultHeaderWithTitle, 8 | } from '~/routes/utils/navigationOptions'; 9 | import Player from '~/components/common/player/PlayerContainer'; 10 | import CONSTANTS from '~/utils/CONSTANTS'; 11 | 12 | import Settings from './Settings'; 13 | import About from './about/About'; 14 | 15 | const LOCAL_STACK_ROUTES = { 16 | ABOUT: 'ABOUT', 17 | }; 18 | 19 | const RootStack = createStackNavigator( 20 | { 21 | [CONSTANTS.ROUTES.SETTINGS]: { 22 | screen: props => ( 23 | 27 | ), 28 | navigationOptions: () => ({ 29 | headerBackTitle: null, 30 | header: null, 31 | }), 32 | }, 33 | 34 | [CONSTANTS.ROUTES.PLAYER]: { 35 | screen: Player, 36 | navigationOptions: ({ navigation }) => getPlayerNavigationOption(navigation), 37 | }, 38 | 39 | [LOCAL_STACK_ROUTES.ABOUT]: { 40 | screen: About, 41 | navigationOptions: ({ navigation, screenProps }) => getDefaultHeaderWithTitle('About', navigation, screenProps), 42 | }, 43 | }, 44 | { 45 | initialRouteName: CONSTANTS.ROUTES.SETTINGS, 46 | mode: Platform.OS === 'ios' ? 'card' : 'modal', 47 | headerLayoutPreset: 'center', 48 | headerMode: 'screen', 49 | }, 50 | ); 51 | 52 | RootStack.navigationOptions = ({ navigation }) => { 53 | const tabBarVisible = navigation.state.index <= 0; 54 | 55 | return { 56 | tabBarVisible, 57 | }; 58 | }; 59 | 60 | export default RootStack; 61 | -------------------------------------------------------------------------------- /src/routes/mainStack.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | createMaterialTopTabNavigator, 4 | createAppContainer, 5 | } from 'react-navigation'; 6 | import { withTheme } from 'styled-components'; 7 | 8 | import Navigation from '~/components/common/navigation/Navigation'; 9 | import HomeRoutes from '~/components/screens/home/routes'; 10 | import SearchRoutes from '~/components/screens/search/routes'; 11 | import LibraryRoutes from '~/components/screens/library/routes'; 12 | import SettingsRoutes from '~/components/screens/settings/routes'; 13 | import appStyles from '~/styles'; 14 | 15 | export const ROUTE_NAMES = { 16 | HOME: 'HOME', 17 | SEARCH: 'SEARCH', 18 | LIBRARY: 'LIBRARY', 19 | PROFILE: 'PROFILE', 20 | SETTINGS: 'SETTINGS', 21 | }; 22 | 23 | const ApplicationTabs = createMaterialTopTabNavigator( 24 | { 25 | [ROUTE_NAMES.HOME]: { 26 | screen: HomeRoutes, 27 | header: null, 28 | }, 29 | 30 | [ROUTE_NAMES.SEARCH]: { 31 | screen: SearchRoutes, 32 | header: null, 33 | }, 34 | 35 | [ROUTE_NAMES.LIBRARY]: { 36 | screen: LibraryRoutes, 37 | header: null, 38 | }, 39 | 40 | [ROUTE_NAMES.SETTINGS]: { 41 | screen: SettingsRoutes, 42 | header: null, 43 | }, 44 | }, 45 | { 46 | tabBarComponent: ({ navigationState, navigation }) => ( 47 | 51 | ), 52 | initialRouteName: ROUTE_NAMES.HOME, 53 | tabBarPosition: 'bottom', 54 | animationEnabled: true, 55 | swipeEnabled: false, 56 | lazy: false, 57 | }, 58 | ); 59 | 60 | const AppContainer = createAppContainer(ApplicationTabs); 61 | 62 | export default AppContainer; 63 | -------------------------------------------------------------------------------- /src/components/screens/settings/about/HeartBeating.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { View, Animated } from 'react-native'; 5 | 6 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 7 | import styled from 'styled-components'; 8 | import appStyles from '~/styles'; 9 | 10 | const Wrapper = styled(View)` 11 | align-items: center; 12 | margin: 0 ${({ theme }) => theme.metrics.extraSmallSize}px; 13 | width: 30px; 14 | `; 15 | 16 | const HeartIcon = Animated.createAnimatedComponent(Icon); 17 | 18 | class HeartBeating extends Component { 19 | _heartSize = new Animated.Value(20); 20 | 21 | componentDidMount() { 22 | this.beatHeart(); 23 | } 24 | 25 | beatHeart = () => { 26 | Animated.loop( 27 | Animated.sequence([ 28 | Animated.timing(this._heartSize, { 29 | toValue: 30, 30 | duration: 100, 31 | }), 32 | 33 | Animated.timing(this._heartSize, { 34 | toValue: 25, 35 | duration: 100, 36 | }), 37 | 38 | Animated.timing(this._heartSize, { 39 | toValue: 30, 40 | duration: 100, 41 | }), 42 | 43 | Animated.timing(this._heartSize, { 44 | toValue: 25, 45 | duration: 200, 46 | }), 47 | 48 | Animated.delay(700), 49 | ]), 50 | ).start(); 51 | }; 52 | 53 | render() { 54 | return ( 55 | 56 | 64 | 65 | ); 66 | } 67 | } 68 | 69 | export default HeartBeating; 70 | -------------------------------------------------------------------------------- /src/components/common/author-detail/components/SubjectsSection.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import SectionTitle from '~/components/common/SectionTitle'; 8 | 9 | const Wrapper = styled(View)` 10 | width: 100%; 11 | `; 12 | 13 | const SubjectItemWrapper = styled(View)` 14 | justify-content: center; 15 | align-items: center; 16 | margin-right: ${({ theme }) => theme.metrics.mediumSize}px; 17 | margin-bottom: ${({ theme }) => theme.metrics.mediumSize}px; 18 | padding-vertical: ${({ theme }) => theme.metrics.smallSize * 1.2}px; 19 | padding-horizontal: ${({ theme }) => theme.metrics.largeSize}px; 20 | border-radius: 3px; 21 | background-color: ${({ theme }) => theme.colors.black}; 22 | `; 23 | 24 | const SubjectItemText = styled(Text)` 25 | font-size: ${({ theme }) => theme.metrics.largeSize * 1.1}px; 26 | font-family: CircularStd-Bold; 27 | color: ${({ theme }) => theme.colors.white}; 28 | `; 29 | 30 | const SubjectsWrapper = styled(View)` 31 | width: 100%; 32 | flex-wrap: wrap; 33 | flex-direction: row; 34 | margin-top: ${({ theme }) => theme.metrics.largeSize}px; 35 | `; 36 | 37 | type Props = { 38 | subjects: Array, 39 | }; 40 | 41 | const SubjectsSection = ({ subjects }: Props): Object => ( 42 | 43 | 46 | 47 | {subjects.map(subject => ( 48 | 51 | {`#${subject}`} 52 | 53 | ))} 54 | 55 | 56 | ); 57 | 58 | export default SubjectsSection; 59 | -------------------------------------------------------------------------------- /src/components/common/author-detail/components/featured-section/FeaturedSection.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import SectionWithButton from '~/components/common/SectionWithButton'; 8 | import FeaturedListItem from './FeaturedListItem'; 9 | import CONSTANTS from '~/utils/CONSTANTS'; 10 | 11 | const Wrapper = styled(View)` 12 | width: 100%; 13 | margin-vertical: ${({ theme }) => theme.metrics.extraLargeSize}px; 14 | `; 15 | 16 | const ItemsWrapper = styled(View)` 17 | margin-left: ${({ theme }) => theme.metrics.extraLargeSize}px; 18 | margin-top: ${({ theme }) => theme.metrics.extraLargeSize}px; 19 | `; 20 | 21 | type Props = { 22 | featured: Array, 23 | onPressItem: Function, 24 | navigation: Object, 25 | }; 26 | 27 | const Featured = ({ onPressItem, navigation, featured }: Props): Object => ( 28 | 29 | navigation.navigate(CONSTANTS.ROUTES.PLAYER, { 31 | [CONSTANTS.PARAMS.PLAYER]: { 32 | [CONSTANTS.KEYS.PLAYLIST]: featured, 33 | }, 34 | }) 35 | } 36 | buttonSize="small" 37 | buttonText="LISTEN NOW" 38 | sectionTitle="Featured" 39 | /> 40 | 41 | {featured.map((podcast, index) => ( 42 | onPressItem(podcast)} 44 | imageURL={podcast.imageURL} 45 | fileName={podcast.fileName} 46 | subject={podcast.category} 47 | title={podcast.title} 48 | index={index + 1} 49 | key={podcast.id} 50 | /> 51 | ))} 52 | 53 | 54 | ); 55 | 56 | export default Featured; 57 | -------------------------------------------------------------------------------- /src/components/common/author-detail/components/related-authors/RelatedAuthorsListItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TouchableOpacity, View, Text } from 'react-native'; 5 | import FastImage from 'react-native-fast-image'; 6 | import styled from 'styled-components'; 7 | 8 | import { bindActionCreators } from 'redux'; 9 | import { connect } from 'react-redux'; 10 | import { Creators as AuthorCreators } from '~/store/ducks/author'; 11 | 12 | const Wrapper = styled(TouchableOpacity)` 13 | width: ${({ theme }) => theme.metrics.getWidthFromDP('35%')}px; 14 | margin-right: ${({ theme }) => theme.metrics.mediumSize}px; 15 | `; 16 | 17 | const Image = styled(FastImage).attrs(({ uri }) => ({ 18 | source: { uri }, 19 | }))` 20 | width: 100%; 21 | height: ${({ theme }) => theme.metrics.getHeightFromDP('30%')}px; 22 | border-radius: 5px; 23 | `; 24 | 25 | const Name = styled(Text).attrs({ 26 | numberOfLines: 2, 27 | })` 28 | margin-top: ${({ theme }) => theme.metrics.smallSize}px; 29 | font-size: ${({ theme }) => theme.metrics.largeSize}px; 30 | font-family: CircularStd-Bold; 31 | color: ${({ theme }) => theme.colors.textColor}; 32 | `; 33 | 34 | type Props = { 35 | getAuthorById: Function, 36 | profileImage: string, 37 | name: string, 38 | id: string, 39 | }; 40 | 41 | const RelatedAuthorsListItem = ({ 42 | getAuthorById, 43 | profileImage, 44 | name, 45 | id, 46 | }: Props): Object => ( 47 | getAuthorById(id)} 49 | > 50 | 53 | {name} 54 | 55 | ); 56 | 57 | const mapDispatchToProps = dispatch => bindActionCreators(AuthorCreators, dispatch); 58 | 59 | export default connect( 60 | null, 61 | mapDispatchToProps, 62 | )(RelatedAuthorsListItem); 63 | -------------------------------------------------------------------------------- /src/components/common/interests/InterestsListItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TouchableOpacity, View, Text } from 'react-native'; 5 | import FastImage from 'react-native-fast-image'; 6 | import styled from 'styled-components'; 7 | 8 | const Wrapper = styled(TouchableOpacity)` 9 | width: 100%; 10 | height: ${({ theme }) => theme.metrics.getHeightFromDP('20%')}px; 11 | margin-bottom: ${({ theme }) => theme.metrics.mediumSize}px; 12 | `; 13 | 14 | const PodcastImage = styled(FastImage).attrs(({ uri }) => ({ 15 | source: { 16 | priority: FastImage.priority.high, 17 | uri, 18 | }, 19 | }))` 20 | width: 100%; 21 | height: 100%; 22 | position: absolute; 23 | border-radius: 4px; 24 | `; 25 | 26 | const DarkLayer = styled(View)` 27 | width: 100%; 28 | height: 100%; 29 | justify-content: center; 30 | align-items: center; 31 | background-color: ${({ isSelected, theme }) => (isSelected ? theme.colors.interestSelectedColor : theme.colors.darkLayer)}; 32 | border-radius: 4px; 33 | `; 34 | 35 | const InterestTitle = styled(Text)` 36 | font-family: CircularStd-Black; 37 | font-size: ${({ theme }) => theme.metrics.extraLargeSize}px; 38 | color: ${({ theme }) => theme.colors.white}; 39 | `; 40 | 41 | type Props = { 42 | onPressItem: Function, 43 | isSelected: boolean, 44 | imageURL: string, 45 | title: string, 46 | }; 47 | 48 | const InterestsListItem = ({ 49 | onPressItem, 50 | isSelected, 51 | imageURL, 52 | title, 53 | }: Props): Object => ( 54 | 57 | 60 | 63 | {title} 64 | 65 | 66 | ); 67 | 68 | export default InterestsListItem; 69 | -------------------------------------------------------------------------------- /ios/mindCast-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 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | createSwitchNavigator, 5 | createAppContainer, 6 | createStackNavigator, 7 | } from 'react-navigation'; 8 | import { withTheme } from 'styled-components'; 9 | 10 | import OboardingIntro from '~/components/screens/oboarding-intro/OnboardingIntro'; 11 | import { getDefaultHeaderWithButton } from './utils/navigationOptions'; 12 | import Interests from '~/components/screens/interests/Interests'; 13 | import StarterScreen from '~/components/screens/StaterScreen'; 14 | import Login from '~/components/screens/login/Login'; 15 | import CONSTANTS from '../utils/CONSTANTS'; 16 | import MainStack from './mainStack'; 17 | 18 | const InterestsScreen = createStackNavigator( 19 | { 20 | [CONSTANTS.ROUTES.INTERESTS]: { 21 | screen: Interests, 22 | navigationOptions: ({ navigation, screenProps }) => getDefaultHeaderWithButton( 23 | navigation, 24 | screenProps, 25 | 'Your Interests', 26 | 'check-all', 27 | ), 28 | }, 29 | }, 30 | { 31 | headerLayoutPreset: 'center', 32 | }, 33 | ); 34 | 35 | const InitialStack = createSwitchNavigator( 36 | { 37 | [CONSTANTS.ROUTES.STARTER_SCREEN]: { 38 | screen: StarterScreen, 39 | }, 40 | 41 | [CONSTANTS.ROUTES.ONBOARDING_INTRO]: { 42 | screen: OboardingIntro, 43 | }, 44 | 45 | [CONSTANTS.ROUTES.LOGIN]: { 46 | screen: Login, 47 | }, 48 | 49 | [CONSTANTS.ROUTES.INTERESTS]: InterestsScreen, 50 | 51 | [CONSTANTS.ROUTES.MAIN_STACK]: { 52 | screen: MainStack, 53 | }, 54 | }, 55 | { 56 | initialRouteName: CONSTANTS.ROUTES.STARTER_SCREEN, 57 | }, 58 | ); 59 | 60 | const AppContainer = createAppContainer(InitialStack); 61 | 62 | export default withTheme(({ theme }) => ( 63 | 66 | )); 67 | -------------------------------------------------------------------------------- /src/components/screens/login/components/LoginComponent.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TouchableOpacity, Text, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import BottomContent from './BottomContent'; 8 | import ChangeAction from './ChangeAction'; 9 | import Input from './Input'; 10 | 11 | const Wrapper = styled(View)` 12 | width: 100%; 13 | height: ${({ theme }) => theme.metrics.getHeightFromDP('70%')}px; 14 | justify-content: space-between; 15 | `; 16 | 17 | const Row = styled(View)` 18 | flex-direction: row; 19 | justify-content: space-between; 20 | align-items: center; 21 | `; 22 | 23 | const DefaultText = styled(Text)` 24 | font-size: ${({ theme }) => theme.metrics.getWidthFromDP('4.5%')}px; 25 | font-family: CircularStd-Bold; 26 | color: ${({ color }) => color}; 27 | text-align: center; 28 | `; 29 | 30 | type Props = { 31 | onNavigateToMainStack: Function, 32 | onChangeListIndex: Function, 33 | }; 34 | 35 | const LoginComponent = ({ 36 | onNavigateToMainStack, 37 | onChangeListIndex, 38 | }: Props): Object => ( 39 | 40 | 41 | 46 | 51 | onChangeListIndex(1)} 53 | onNavigateToMainStack={onNavigateToMainStack} 54 | changeActionText="Register now" 55 | questionText="Not account?" 56 | buttonText="LOGIN" 57 | /> 58 | 59 | 63 | 64 | ); 65 | 66 | export default LoginComponent; 67 | -------------------------------------------------------------------------------- /src/components/screens/library/components/playlists/PlaylistOperationModal.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { AlertIOS, Platform } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import AndroidOperationModal from './AndroidOperationModal'; 8 | 9 | const iOSModalAlert = ( 10 | mainAction: Function, 11 | title: string, 12 | error, 13 | defaultText: string, 14 | toggleModal: Function, 15 | mode: string, 16 | ): void => { 17 | AlertIOS.prompt( 18 | title, 19 | error, 20 | [ 21 | { 22 | text: 'Cancel', 23 | onPress: toggleModal, 24 | style: 'cancel', 25 | }, 26 | { 27 | text: mode.toUpperCase(), 28 | onPress: playlistTitle => mainAction(playlistTitle), 29 | }, 30 | ], 31 | 'plain-text', 32 | defaultText, 33 | ); 34 | }; 35 | 36 | type Props = { 37 | onTypePlaylistTitle: Function, 38 | toggleModal: Function, 39 | mainAction: Function, 40 | playlistTitle: string, 41 | hasError: boolean, 42 | mode: string, 43 | }; 44 | 45 | const PlaylistOperationModal = ({ 46 | onTypePlaylistTitle, 47 | playlistTitle, 48 | toggleModal, 49 | mainAction, 50 | hasError, 51 | mode, 52 | }: Props): Object => { 53 | const title = `${mode} Playlist`; 54 | const error = hasError 55 | ? 'This title is already in use by other Playlist.' 56 | : ''; 57 | 58 | if (Platform.OS === 'android') { 59 | return ( 60 | 68 | ); 69 | } 70 | 71 | iOSModalAlert(mainAction, title, error, playlistTitle, toggleModal, mode); 72 | 73 | return null; 74 | }; 75 | 76 | export default PlaylistOperationModal; 77 | -------------------------------------------------------------------------------- /src/utils/CONSTANTS.js: -------------------------------------------------------------------------------- 1 | const ROUTES = { 2 | ONBOARDING_INTRO: 'ONBOARDING_INTRO', 3 | MAIN_STACK: 'MAIN_STACK', 4 | LOGIN: 'LOGIN', 5 | STARTER_SCREEN: 'STARTER_SCREEN', 6 | SUBJECT_DETAIL: 'SUBJECT_DETAIL', 7 | PODCAST_DETAIL: 'PODCAST_DETAIL', 8 | AUTHOR_DETAIL: 'AUTHOR_DETAIL', 9 | INTERESTS: 'INTERESTS', 10 | PLAYER: 'PLAYER', 11 | HOME: 'HOME', 12 | SEARCH: 'SEARCH', 13 | LIBRARY: 'LIBRARY', 14 | SETTINGS: 'SETTINGS', 15 | }; 16 | 17 | const PARAMS = { 18 | HEADER_BUTTON_RIGHT_PLAYER_ACTION: 'HEADER_BUTTON_RIGHT_PLAYER_ACTION', 19 | HEADER_PLAY_FUNCTION_PARAM: 'HEADER_PLAY_FUNCTION_PARAM', 20 | PODCASTS_HOTTEST_PODCASTS: 'PODCASTS_HOTTEST_PODCASTS', 21 | SEARCH_AUTHOR_BY_NAME: 'SEARCH_AUTHOR_BY_NAME', 22 | PODCASTS_NEW_RELEASES: 'PODCASTS_NEW_RELEASES', 23 | PODCASTS_DOWNLOADED: 'PODCASTS_DOWNLOADED', 24 | TRENDING_AUTHORS: 'TRENDING_AUTHORS', 25 | HEADER_ACTION: 'HEADER_ACTION', 26 | PLAYLIST_TITLE: 'PLAYLIST_TITLE', 27 | SUBJECT_DETAIL: 'SUBJECT_DETAIL', 28 | PODCAST_DETAIL: 'PODCAST_DETAIL', 29 | AUTHOR_DETAIL: 'AUTHOR_DETAIL', 30 | PLAYER_TITLE: 'PLAYER_TITLE', 31 | YOUR_PODCASTS: 'YOUR_PODCASTS', 32 | HAS_ERROR: 'HAS_ERROR', 33 | PLAYER: 'PLAYER', 34 | }; 35 | 36 | const KEYS = { 37 | PODCASTS_PLAYED_RECENTLY: 'PODCASTS_PLAYED_RECENTLY', 38 | IS_PLAYER_RIGHT_MENU_OPEN: 'IS_PLAYER_RIGHT_MENU_OPEN', 39 | SHOULD_SHUFFLE_PLAYLIST: 'SHOULD_SHUFFLE_PLAYLIST', 40 | INTERESTS_STORAGE_KEY: 'INTERESTS_STORAGE_KEY', 41 | PLAYLIST_STORAGE_KEY: 'PLAYLIST_STORAGE_KEY', 42 | PODCASTS_SAVED: 'PODCASTS_SAVED', 43 | APP_STORAGE_KEY: '@MIND_CAST', 44 | PLAYLIST: 'PLAYLIST', 45 | PODCAST_DETAIL_SHOULD_SHOW_AUTHOR_SECTION: 46 | 'PODCAST_DETAIL_SHOULD_SHOW_AUTHOR_SECTION', 47 | LOOKUP_PLAYER: 'LOOKUP_PLAYER', 48 | APP_THEME: 'APP_THEME', 49 | FIRST_TIME_RUNNING_APP: 'FIRST_TIME_RUNNING_APP', 50 | }; 51 | 52 | export default { 53 | ROUTES, 54 | PARAMS, 55 | KEYS, 56 | }; 57 | -------------------------------------------------------------------------------- /src/components/common/navigation/Navigation.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Fragment } from 'react'; 4 | import { TouchableOpacity, Text, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import NavigationBar from './components/navigation-bar/NavigationBar'; 8 | import PlayerTracker from './components/PlayerTracker'; 9 | 10 | import CONSTANTS from '~/utils/CONSTANTS'; 11 | 12 | const navigationBarItems = [ 13 | { 14 | label: 'Discover', 15 | icon: 'compass', 16 | route: CONSTANTS.ROUTES.HOME, 17 | }, 18 | { 19 | label: 'Search', 20 | icon: 'magnify', 21 | route: CONSTANTS.ROUTES.SEARCH, 22 | }, 23 | { 24 | label: 'Library', 25 | icon: 'library-music', 26 | route: CONSTANTS.ROUTES.LIBRARY, 27 | }, 28 | { 29 | label: 'Settings', 30 | icon: 'settings', 31 | route: CONSTANTS.ROUTES.SETTINGS, 32 | }, 33 | ]; 34 | 35 | type Props = { 36 | navigationState: Object, 37 | navigation: Object, 38 | }; 39 | 40 | const onSelectStackRoute = (navigation: Object, route: string): void => navigation.navigate(route); 41 | 42 | const Navigation = ({ navigationState, navigation }: Props): Object => { 43 | const { index, routes } = navigationState; 44 | const routeSelected = routes[index]; 45 | 46 | const nameRouteSelected = routeSelected.routes[routeSelected.index].routeName; 47 | const isShowingPlayerScreen = nameRouteSelected === CONSTANTS.ROUTES.PLAYER; 48 | 49 | return ( 50 | 51 | {!isShowingPlayerScreen && ( 52 | 53 | 56 | onSelectStackRoute(navigation, route)} 58 | stackRouteSelected={index} 59 | items={navigationBarItems} 60 | /> 61 | 62 | )} 63 | 64 | ); 65 | }; 66 | 67 | export default Navigation; 68 | -------------------------------------------------------------------------------- /src/components/screens/search/components/search-author/SearchAuthorListContainer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | 5 | import { bindActionCreators } from 'redux'; 6 | import { connect } from 'react-redux'; 7 | import { Creators as AuthorCreators } from '~/store/ducks/author'; 8 | 9 | import SearchAuthorListComponent from './SearchAuthorListComponent'; 10 | import CONSTANTS from '~/utils/CONSTANTS'; 11 | import appStyles from '~/styles'; 12 | 13 | type AuthorProps = { 14 | loadingSearchAuthorByName: boolean, 15 | authors: Array, 16 | }; 17 | 18 | type Props = { 19 | searchAuthorByName: Function, 20 | author: AuthorProps, 21 | navigation: Object, 22 | }; 23 | 24 | class SearchAuthorListContainer extends Component { 25 | componentDidMount() { 26 | const { searchAuthorByName } = this.props; 27 | 28 | const authorName = this.getAuthorNameParam(); 29 | 30 | searchAuthorByName(authorName); 31 | } 32 | 33 | getAuthorNameParam = (): string => { 34 | const { navigation } = this.props; 35 | const { params } = navigation.state; 36 | 37 | const authorName = params[CONSTANTS.PARAMS.SEARCH_AUTHOR_BY_NAME]; 38 | 39 | return authorName; 40 | }; 41 | 42 | render() { 43 | const { navigation, author } = this.props; 44 | const { loadingSearchAuthorByName, authors } = author; 45 | const authorName = this.getAuthorNameParam(); 46 | 47 | return ( 48 | 54 | ); 55 | } 56 | } 57 | 58 | const mapStateToProps = state => ({ 59 | author: state.author, 60 | }); 61 | 62 | const mapDispatchToProps = dispatch => bindActionCreators(AuthorCreators, dispatch); 63 | 64 | export default connect( 65 | mapStateToProps, 66 | mapDispatchToProps, 67 | )(SearchAuthorListContainer); 68 | -------------------------------------------------------------------------------- /flow-typed/npm/flow_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 447b0a67dee1a37fe9805d763719df9f 2 | // flow-typed version: <>/flow_v^0.2.3/flow_v0.92.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'flow' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'flow' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'flow/examples/keystore' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'flow/examples/multi' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'flow/examples/serialForEach' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'flow/examples/simple' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'flow/flow' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'flow/tests' { 46 | declare module.exports: any; 47 | } 48 | 49 | // Filename aliases 50 | declare module 'flow/examples/keystore.js' { 51 | declare module.exports: $Exports<'flow/examples/keystore'>; 52 | } 53 | declare module 'flow/examples/multi.js' { 54 | declare module.exports: $Exports<'flow/examples/multi'>; 55 | } 56 | declare module 'flow/examples/serialForEach.js' { 57 | declare module.exports: $Exports<'flow/examples/serialForEach'>; 58 | } 59 | declare module 'flow/examples/simple.js' { 60 | declare module.exports: $Exports<'flow/examples/simple'>; 61 | } 62 | declare module 'flow/flow.js' { 63 | declare module.exports: $Exports<'flow/flow'>; 64 | } 65 | declare module 'flow/tests.js' { 66 | declare module.exports: $Exports<'flow/tests'>; 67 | } 68 | -------------------------------------------------------------------------------- /src/components/common/author-detail/components/new-releases-section/NewReleasesSection.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { FlatList, View } from 'react-native'; 5 | import { StackActions } from 'react-navigation'; 6 | import styled from 'styled-components'; 7 | 8 | import NewReleasesSectionItemList from './NewReleasesSectionItemList'; 9 | import SectionWithButton from '~/components/common/SectionWithButton'; 10 | import CONSTANTS from '~/utils/CONSTANTS'; 11 | 12 | const Wrapper = styled(View)` 13 | width: 100%; 14 | margin-vertical: ${({ theme }) => theme.metrics.extraLargeSize}px; 15 | `; 16 | 17 | const NewReleasesList = styled(FlatList)` 18 | width: 100%; 19 | margin-top: ${({ theme }) => theme.metrics.extraLargeSize}px; 20 | `; 21 | 22 | type Props = { 23 | newReleases: Array, 24 | onPressItem: Function, 25 | navigation: Object, 26 | }; 27 | 28 | const NewReleasesSection = ({ 29 | onPressItem, 30 | newReleases, 31 | navigation, 32 | }: Props): Object => ( 33 | 34 | navigation.navigate(CONSTANTS.ROUTES.PLAYER, { 36 | [CONSTANTS.PARAMS.PLAYER]: { 37 | [CONSTANTS.KEYS.PLAYLIST]: newReleases, 38 | }, 39 | }) 40 | } 41 | sectionTitle="New Releases" 42 | buttonText="LISTEN NOW" 43 | buttonSize="small" 44 | /> 45 | `${podcast.id}`} 48 | data={newReleases} 49 | horizontal 50 | renderItem={({ item, index }) => ( 51 | onPressItem(item)} 54 | imageURL={item.imageURL} 55 | subject={item.category} 56 | title={item.title} 57 | stars={item.stars} 58 | /> 59 | )} 60 | /> 61 | 62 | ); 63 | 64 | export default NewReleasesSection; 65 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/mindcast/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.mindcast; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.react.ReactApplication; 6 | import org.devio.rn.splashscreen.SplashScreenReactPackage; 7 | import com.BV.LinearGradient.LinearGradientPackage; 8 | import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; 9 | import com.oblador.vectoricons.VectorIconsPackage; 10 | import com.rnfs.RNFSPackage; 11 | import com.brentvatne.react.ReactVideoPackage; 12 | import com.facebook.react.ReactNativeHost; 13 | import com.facebook.react.ReactPackage; 14 | import com.facebook.react.shell.MainReactPackage; 15 | import com.facebook.soloader.SoLoader; 16 | import com.rnfs.RNFSPackage; 17 | import com.dylanvann.fastimage.FastImageViewPackage; 18 | 19 | import java.util.Arrays; 20 | import java.util.List; 21 | 22 | public class MainApplication extends Application implements ReactApplication { 23 | 24 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 25 | @Override 26 | public boolean getUseDeveloperSupport() { 27 | return BuildConfig.DEBUG; 28 | } 29 | 30 | @Override 31 | protected List getPackages() { 32 | return Arrays.asList( 33 | new MainReactPackage(), 34 | new SplashScreenReactPackage(), 35 | new LinearGradientPackage(), 36 | new RNGestureHandlerPackage(), 37 | new VectorIconsPackage(), 38 | new RNFSPackage(), 39 | new ReactVideoPackage(), 40 | new FastImageViewPackage() 41 | ); 42 | } 43 | 44 | @Override 45 | protected String getJSMainModuleName() { 46 | return "index"; 47 | } 48 | }; 49 | 50 | @Override 51 | public ReactNativeHost getReactNativeHost() { 52 | return mReactNativeHost; 53 | } 54 | 55 | @Override 56 | public void onCreate() { 57 | super.onCreate(); 58 | SoLoader.init(this, /* native exopackage */ false); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/screens/search/SearchContainer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | 5 | import { bindActionCreators } from 'redux'; 6 | import { connect } from 'react-redux'; 7 | import { Creators as AuthorCreators } from '~/store/ducks/author'; 8 | 9 | import SearchComponent from './components/SearchComponent'; 10 | import CONSTANTS from '~/utils/CONSTANTS'; 11 | 12 | type Props = { 13 | LOCAL_STACK_ROUTES: Object, 14 | navigation: Object, 15 | }; 16 | 17 | type State = { 18 | isTextInputFocused: boolean, 19 | authorName: string, 20 | }; 21 | 22 | class SearchContainer extends Component { 23 | state = { 24 | isTextInputFocused: false, 25 | authorName: '', 26 | }; 27 | 28 | onTypeAuthorName = (authorName: string): void => { 29 | this.setState({ 30 | authorName, 31 | }); 32 | }; 33 | 34 | onSearchForAuthor = (): void => { 35 | const { navigation, LOCAL_STACK_ROUTES } = this.props; 36 | const { authorName } = this.state; 37 | 38 | if (authorName.length) { 39 | navigation.navigate(LOCAL_STACK_ROUTES.SEARCH_AUTHORS_RESULT, { 40 | [CONSTANTS.PARAMS.SEARCH_AUTHOR_BY_NAME]: authorName, 41 | }); 42 | } 43 | }; 44 | 45 | onToggleDarkLayer = (isTextInputFocused: boolean) => { 46 | this.setState({ 47 | isTextInputFocused, 48 | }); 49 | }; 50 | 51 | render() { 52 | const { navigation } = this.props; 53 | const { isTextInputFocused } = this.state; 54 | 55 | return ( 56 | 63 | ); 64 | } 65 | } 66 | 67 | const mapDispatchToProps = dispatch => bindActionCreators(AuthorCreators, dispatch); 68 | 69 | export default connect( 70 | null, 71 | mapDispatchToProps, 72 | )(SearchContainer); 73 | -------------------------------------------------------------------------------- /src/components/screens/login/components/RegisterComponent.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TouchableOpacity, Text, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import BottomContent from './BottomContent'; 8 | import ChangeAction from './ChangeAction'; 9 | import Input from './Input'; 10 | 11 | const Wrapper = styled(View)` 12 | width: 100%; 13 | height: ${({ theme }) => theme.metrics.getHeightFromDP('70%')}px; 14 | justify-content: space-between; 15 | `; 16 | 17 | const Row = styled(View)` 18 | flex-direction: row; 19 | justify-content: space-between; 20 | align-items: center; 21 | `; 22 | 23 | const DefaultText = styled(Text)` 24 | font-size: ${({ theme }) => theme.metrics.getWidthFromDP('4.5%')}px; 25 | font-family: CircularStd-Bold; 26 | color: ${({ color }) => color}; 27 | text-align: center; 28 | `; 29 | 30 | type Props = { 31 | onNavigateToMainStack: Function, 32 | onChangeListIndex: Function, 33 | }; 34 | 35 | const LoginComponent = ({ 36 | onNavigateToMainStack, 37 | onChangeListIndex, 38 | }: Props): Object => ( 39 | 40 | 41 | 46 | 51 | 56 | onChangeListIndex(0)} 58 | onNavigateToMainStack={onNavigateToMainStack} 59 | questionText="Has account?" 60 | changeActionText="Log-in" 61 | buttonText="REGISTER" 62 | /> 63 | 64 | 68 | 69 | ); 70 | 71 | export default LoginComponent; 72 | -------------------------------------------------------------------------------- /src/components/screens/search/components/subject-detail/SubjectDetailContainer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { Animated } from 'react-native'; 5 | 6 | import { bindActionCreators } from 'redux'; 7 | import { connect } from 'react-redux'; 8 | import { Creators as SubjectCreators } from '~/store/ducks/subject'; 9 | 10 | import SubjectDetailComponent from './components/SubjectDetailComponent'; 11 | import CONSTANTS from '~/utils/CONSTANTS'; 12 | import appStyles from '~/styles'; 13 | 14 | type SubjectProps = { 15 | loading: boolean, 16 | error: boolean, 17 | data: Object, 18 | }; 19 | 20 | type Props = { 21 | getSubjectDetail: Function, 22 | subject: SubjectProps, 23 | navigation: Object, 24 | }; 25 | 26 | const HEADER_HEIGHT = appStyles.metrics.getHeightFromDP('20%'); 27 | 28 | class SubjectDetailContainer extends Component { 29 | componentDidMount() { 30 | const { getSubjectDetail, navigation } = this.props; 31 | 32 | const { params } = navigation.state; 33 | const { id } = params[CONSTANTS.PARAMS.SUBJECT_DETAIL]; 34 | 35 | getSubjectDetail(id); 36 | } 37 | 38 | componentWillReceiveProps(nextProps: Props) { 39 | const { subject, navigation } = nextProps; 40 | 41 | if (subject.error && !this.props.subject.error) { 42 | navigation.setParams({ [CONSTANTS.PARAMS.HAS_ERROR]: true }); 43 | } 44 | } 45 | 46 | render() { 47 | const { subject, navigation } = this.props; 48 | const { loading, error, data } = subject; 49 | 50 | return ( 51 | 57 | ); 58 | } 59 | } 60 | 61 | const mapStateToProps = state => ({ 62 | subject: state.subject, 63 | }); 64 | 65 | const mapDispatchToProps = dispatch => bindActionCreators(SubjectCreators, dispatch); 66 | 67 | export default connect( 68 | mapStateToProps, 69 | mapDispatchToProps, 70 | )(SubjectDetailContainer); 71 | -------------------------------------------------------------------------------- /src/components/screens/search/components/subjects-list/SubjectListItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TouchableOpacity, View, Text } from 'react-native'; 5 | import FastImage from 'react-native-fast-image'; 6 | import styled from 'styled-components'; 7 | 8 | const Container = styled(TouchableOpacity)` 9 | width: ${({ theme }) => theme.metrics.getWidthFromDP('44%')}px; 10 | height: ${({ theme }) => theme.metrics.getWidthFromDP('44%')}px; 11 | margin-bottom: ${({ theme }) => theme.metrics.mediumSize}px; 12 | margin-right: ${({ theme, index }) => (index % 2 !== 0 ? theme.metrics.largeSize : 0)}px; 13 | margin-left: ${({ theme, index }) => (index % 2 === 0 ? 0 : theme.metrics.largeSize)}px; 14 | border-radius: 4px; 15 | `; 16 | 17 | const SubjectImage = styled(FastImage).attrs(({ uri }) => ({ 18 | source: { uri, priority: FastImage.priority.high }, 19 | }))` 20 | width: 100%; 21 | height: 100%; 22 | position: absolute; 23 | border-radius: 4px; 24 | `; 25 | 26 | const DarkLayer = styled(View)` 27 | width: 100%; 28 | height: 100%; 29 | justify-content: center; 30 | align-items: center; 31 | border-radius: 4px; 32 | background-color: ${({ theme }) => theme.colors.darkLayer}; 33 | `; 34 | 35 | const Title = styled(Text)` 36 | font-size: ${({ theme }) => theme.metrics.largeSize}px; 37 | font-family: CircularStd-Black; 38 | color: ${({ theme }) => theme.colors.white}; 39 | `; 40 | 41 | type Props = { 42 | isTextInputFocused: boolean, 43 | onPress: Function, 44 | imageURL: string, 45 | title: string, 46 | index: number, 47 | }; 48 | 49 | const SubjectListItem = ({ 50 | isTextInputFocused, 51 | imageURL, 52 | onPress, 53 | title, 54 | index, 55 | }: Props): Object => ( 56 | 61 | 64 | 65 | {title} 66 | 67 | 68 | ); 69 | 70 | export default SubjectListItem; 71 | -------------------------------------------------------------------------------- /flow-typed/npm/react-native-side-menu_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: eae9f9fa1492118916b90662c831e13d 2 | // flow-typed version: <>/react-native-side-menu_v^1.1.3/flow_v0.92.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'react-native-side-menu' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'react-native-side-menu' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'react-native-side-menu/build/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'react-native-side-menu/build/styles' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'react-native-side-menu/libdefs' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'react-native-side-menu/styles' { 38 | declare module.exports: any; 39 | } 40 | 41 | // Filename aliases 42 | declare module 'react-native-side-menu/build/index.js' { 43 | declare module.exports: $Exports<'react-native-side-menu/build/index'>; 44 | } 45 | declare module 'react-native-side-menu/build/styles.js' { 46 | declare module.exports: $Exports<'react-native-side-menu/build/styles'>; 47 | } 48 | declare module 'react-native-side-menu/index' { 49 | declare module.exports: $Exports<'react-native-side-menu'>; 50 | } 51 | declare module 'react-native-side-menu/index.js' { 52 | declare module.exports: $Exports<'react-native-side-menu'>; 53 | } 54 | declare module 'react-native-side-menu/libdefs.js' { 55 | declare module.exports: $Exports<'react-native-side-menu/libdefs'>; 56 | } 57 | declare module 'react-native-side-menu/styles.js' { 58 | declare module.exports: $Exports<'react-native-side-menu/styles'>; 59 | } 60 | -------------------------------------------------------------------------------- /src/components/screens/search/components/subject-detail/components/trending/Trending.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { ScrollView, View, FlatList } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import TrendingListItem from './TrendingListItem'; 8 | import appStyles from '~/styles'; 9 | 10 | const ListsWrapper = styled(View)` 11 | width: ${({ theme }) => theme.metrics.width}px; 12 | height: 100%; 13 | flex-direction: row; 14 | flex-wrap: wrap; 15 | justify-content: space-around; 16 | padding-left: 4px; 17 | `; 18 | 19 | type Props = { 20 | podcasts: Array, 21 | onPress: Function, 22 | }; 23 | 24 | const renderList = ( 25 | podcasts: Array, 26 | onPress: Function, 27 | side: string, 28 | ): Object => ( 29 | `${item.id}`} 32 | data={podcasts} 33 | style={{ 34 | flex: 1, 35 | width: appStyles.metrics.getWidthFromDP('50%'), 36 | }} 37 | contentContainerStyle={{ 38 | alignItems: 'center', 39 | }} 40 | renderItem={({ item, index }) => ( 41 | onPress(item)} 43 | datasetLength={podcasts.length} 44 | podcastImage={item.imageURL} 45 | author={item.author} 46 | title={item.title} 47 | index={index} 48 | side={side} 49 | /> 50 | )} 51 | /> 52 | ); 53 | 54 | const TrendingPodcastsList = ({ podcasts, onPress }: Props): Object => { 55 | const middleIndex = Math.floor(podcasts.length / 2); 56 | const rightDataset = podcasts.slice(0, middleIndex); 57 | const leftDataset = podcasts.slice(middleIndex, podcasts.length); 58 | 59 | return ( 60 | 63 | 64 | {renderList(leftDataset, onPress, 'left')} 65 | {renderList(rightDataset, onPress, 'right')} 66 | 67 | 68 | ); 69 | }; 70 | 71 | export default TrendingPodcastsList; 72 | -------------------------------------------------------------------------------- /src/components/screens/login/components/Input.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TextInput, View } from 'react-native'; 5 | 6 | import Icon from '~/components/common/Icon'; 7 | import styled from 'styled-components'; 8 | 9 | import appStyles from '~/styles'; 10 | 11 | const InputWrapper = styled(View)` 12 | width: 100%; 13 | height: 100%; 14 | flex-direction: row; 15 | align-items: center; 16 | padding-horizontal: ${({ theme }) => theme.metrics.largeSize}px; 17 | `; 18 | 19 | const ContentContainer = styled(View)` 20 | width: 100%; 21 | height: ${({ theme }) => theme.metrics.getHeightFromDP('8%')}px; 22 | justify-content: center; 23 | align-items: center; 24 | margin-bottom: ${({ theme }) => theme.metrics.largeSize}px; 25 | background-color: ${({ color }) => color}; 26 | border-radius: 4px; 27 | `; 28 | 29 | const CustomInput = styled(TextInput).attrs(({ placeholder, type, theme }) => ({ 30 | placeholderTextColor: theme.colors.subTextWhite, 31 | selectionColor: theme.colors.darkText, 32 | underlineColorAndroid: 'transparent', 33 | secureTextEntry: type === 'password', 34 | autoCapitalize: 'none', 35 | textContentType: type, 36 | autoCorrect: false, 37 | placeholder, 38 | }))` 39 | width: 90%; 40 | height: 100%; 41 | margin-left: ${({ theme }) => theme.metrics.mediumSize}px; 42 | font-size: ${({ theme }) => 1.1 * theme.metrics.largeSize}px; 43 | font-family: CircularStd-Book; 44 | color: ${({ theme }) => theme.colors.darkText}; 45 | `; 46 | 47 | type InputProps = { 48 | placeholder: string, 49 | iconName: string, 50 | type: string, 51 | }; 52 | 53 | const Input = ({ placeholder, iconName, type }: InputProps): Object => ( 54 | 57 | 58 | 63 | 67 | 68 | 69 | ); 70 | 71 | export default Input; 72 | -------------------------------------------------------------------------------- /src/components/screens/library/components/playlist-detail/components/PodcastListItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View } from 'react-native'; 5 | import FastImage from 'react-native-fast-image'; 6 | import Swipeout from 'react-native-swipeout'; 7 | import styled from 'styled-components'; 8 | 9 | import PodcastsDownloadedListItem from '~/components/common/PodcastItemLIst'; 10 | import SwipeOutButton from '~/components/common/SwipeOutButton'; 11 | import Loading from '~/components/common/Loading'; 12 | import Icon from '~/components/common/Icon'; 13 | import appStyles from '~/styles'; 14 | 15 | const Wrapper = styled(View)` 16 | width: 100%; 17 | padding-horizontal: ${({ theme }) => theme.metrics.mediumSize}px; 18 | `; 19 | 20 | const SwipeDeleteButton = styled(View)` 21 | width: 100%; 22 | height: 100%; 23 | justify-content: center; 24 | align-items: center; 25 | background-color: ${({ theme }) => theme.colors.primaryColor}; 26 | `; 27 | 28 | type Props = { 29 | onRemovePodcastFromPlaylist: Function, 30 | onPressPodcastsListItem: Function, 31 | isDownloading: boolean, 32 | podcast: Object, 33 | index: number, 34 | }; 35 | 36 | const PodcastListItem = ({ 37 | onRemovePodcastFromPlaylist, 38 | onPressPodcastsListItem, 39 | isDownloading, 40 | podcast, 41 | index, 42 | }: Props): Object => ( 43 | 44 | 54 | ), 55 | onPress: onRemovePodcastFromPlaylist, 56 | type: 'delete', 57 | }, 58 | ]} 59 | > 60 | 67 | 68 | 69 | ); 70 | 71 | export default PodcastListItem; 72 | -------------------------------------------------------------------------------- /src/components/common/author-detail/components/related-authors/RelatedAuthors.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { FlatList, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import RelatedAuthorsListItem from './RelatedAuthorsListItem'; 8 | import SectionTitle from '~/components/common/SectionTitle'; 9 | 10 | const Wrapper = styled(View)` 11 | width: 100%; 12 | margin-bottom: ${({ theme }) => theme.metrics.extraLargeSize * 2}px; 13 | margin-vertical: ${({ theme }) => theme.metrics.extraLargeSize}px; 14 | `; 15 | 16 | const UpperContent = styled(View)` 17 | width: 100%; 18 | padding-left: ${({ theme }) => theme.metrics.largeSize}px; 19 | `; 20 | 21 | const RelatedAuthorsList = styled(FlatList)` 22 | width: 100%; 23 | margin-top: ${({ theme }) => theme.metrics.extraLargeSize}px; 24 | padding-left: ${({ theme }) => theme.metrics.extraLargeSize}px; 25 | `; 26 | 27 | const ListFooterComponent = styled(View)` 28 | width: ${({ theme }) => theme.metrics.extraLargeSize}px; 29 | height: 1px; 30 | `; 31 | 32 | const TitleWrapper = styled(View)` 33 | width: 100%; 34 | padding-left: ${({ theme }) => theme.metrics.largeSize}px; 35 | `; 36 | 37 | type RelatedAuthorProps = { 38 | profileImage: string, 39 | name: string, 40 | id: string, 41 | }; 42 | 43 | type Props = { 44 | relatedAuthors: Array, 45 | }; 46 | 47 | const RelatedAuthors = ({ relatedAuthors }: Props): Object => ( 48 | 49 | 50 | 53 | 54 | `${podcast.id}`} 60 | renderItem={({ item }) => ( 61 | 66 | )} 67 | /> 68 | 69 | ); 70 | 71 | export default RelatedAuthors; 72 | -------------------------------------------------------------------------------- /flow-typed/npm/metro-react-native-babel-preset_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 148d0aed2c97d30bb6ede2e59e07ec42 2 | // flow-typed version: <>/metro-react-native-babel-preset_v0.51.1/flow_v0.92.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'metro-react-native-babel-preset' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'metro-react-native-babel-preset' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'metro-react-native-babel-preset/src/configs/hmr' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'metro-react-native-babel-preset/src/configs/main' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'metro-react-native-babel-preset/src/index' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'metro-react-native-babel-preset/src/transforms/transform-symbol-member' { 38 | declare module.exports: any; 39 | } 40 | 41 | // Filename aliases 42 | declare module 'metro-react-native-babel-preset/src/configs/hmr.js' { 43 | declare module.exports: $Exports< 44 | 'metro-react-native-babel-preset/src/configs/hmr', 45 | >; 46 | } 47 | declare module 'metro-react-native-babel-preset/src/configs/main.js' { 48 | declare module.exports: $Exports< 49 | 'metro-react-native-babel-preset/src/configs/main', 50 | >; 51 | } 52 | declare module 'metro-react-native-babel-preset/src/index.js' { 53 | declare module.exports: $Exports<'metro-react-native-babel-preset/src/index'>; 54 | } 55 | declare module 'metro-react-native-babel-preset/src/transforms/transform-symbol-member.js' { 56 | declare module.exports: $Exports< 57 | 'metro-react-native-babel-preset/src/transforms/transform-symbol-member', 58 | >; 59 | } 60 | -------------------------------------------------------------------------------- /src/components/common/ProgressiveImage.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { View, StyleSheet, Animated } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | const ForegroundLayer = styled(View)` 8 | height: 100%; 9 | width: 100%; 10 | background-color: ${({ theme }) => theme.colors.progressiveImageForeground}; 11 | `; 12 | 13 | const styles = StyleSheet.create({ 14 | container: { 15 | height: '100%', 16 | width: '100%', 17 | }, 18 | 19 | imageOverlay: { 20 | bottom: 0, 21 | left: 0, 22 | position: 'absolute', 23 | right: 0, 24 | top: 0, 25 | }, 26 | }); 27 | 28 | type Props = { 29 | thumbnailImageURL: string, 30 | imageURL: string, 31 | }; 32 | 33 | class ProgressiveImage extends Component { 34 | _thumbnailOpacity = new Animated.Value(0); 35 | _imageOpacity = new Animated.Value(0); 36 | 37 | onThumbnailLoaded = (): void => { 38 | Animated.timing(this._thumbnailOpacity, { 39 | toValue: 1, 40 | useNativeDriver: true, 41 | }).start(); 42 | }; 43 | 44 | onImageLoaded = (): void => { 45 | Animated.timing(this._imageOpacity, { 46 | toValue: 1, 47 | useNativeDriver: true, 48 | }).start(); 49 | }; 50 | 51 | render() { 52 | const { thumbnailImageURL, imageURL } = this.props; 53 | 54 | return ( 55 | 56 | 68 | 80 | 81 | ); 82 | } 83 | } 84 | 85 | export default ProgressiveImage; 86 | -------------------------------------------------------------------------------- /src/components/screens/search/components/SearchComponent.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import ScreenTitle from '~/components/common/ScreenTitle'; 8 | import SearchAuthorTextInput from './SearchAuthorTextInput'; 9 | import SubjectsList from './subjects-list/SubjectList'; 10 | 11 | const Container = styled(View)` 12 | width: 100%; 13 | height: 100%; 14 | background-color: ${({ theme }) => theme.colors.secondaryColor}; 15 | `; 16 | 17 | const DarkLayer = styled(View)` 18 | width: 100%; 19 | height: 100%; 20 | margin-top: ${({ theme }) => theme.metrics.getHeightFromDP('8.5%')}; 21 | background-color: ${({ theme }) => theme.colors.lightDark}; 22 | position: absolute; 23 | `; 24 | 25 | const SubjectsListWrapper = styled(View)` 26 | flex: 1; 27 | padding-horizontal: ${({ theme }) => theme.metrics.largeSize}px; 28 | opacity: ${({ isTextInputFocused }) => (isTextInputFocused ? 0.35 : 1)}; 29 | `; 30 | 31 | const SearchAuthorTextInputWrapper = styled(View)` 32 | width: 100%; 33 | padding-horizontal: ${({ theme }) => theme.metrics.largeSize}px; 34 | `; 35 | 36 | type Props = { 37 | onSearchForAuthor: Function, 38 | onToggleDarkLayer: Function, 39 | isTextInputFocused: boolean, 40 | onTypeAuthorName: Function, 41 | navigation: Object, 42 | }; 43 | 44 | const SearchComponent = ({ 45 | onSearchForAuthor, 46 | onToggleDarkLayer, 47 | isTextInputFocused, 48 | onTypeAuthorName, 49 | navigation, 50 | }: Props): Object => ( 51 | 52 | 55 | 56 | 61 | 62 | 65 | 69 | 70 | 71 | ); 72 | 73 | export default SearchComponent; 74 | -------------------------------------------------------------------------------- /ios/mindCastTests/mindCastTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | #import 12 | #import 13 | 14 | #define TIMEOUT_SECONDS 600 15 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 16 | 17 | @interface mindCastTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation mindCastTests 22 | 23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 24 | { 25 | if (test(view)) { 26 | return YES; 27 | } 28 | for (UIView *subview in [view subviews]) { 29 | if ([self findSubviewInView:subview matching:test]) { 30 | return YES; 31 | } 32 | } 33 | return NO; 34 | } 35 | 36 | - (void)testRendersWelcomeScreen 37 | { 38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 40 | BOOL foundElement = NO; 41 | 42 | __block NSString *redboxError = nil; 43 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 44 | if (level >= RCTLogLevelError) { 45 | redboxError = message; 46 | } 47 | }); 48 | 49 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 50 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 51 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 52 | 53 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 54 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 55 | return YES; 56 | } 57 | return NO; 58 | }]; 59 | } 60 | 61 | RCTSetLogFunction(RCTDefaultLogFunction); 62 | 63 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 64 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 65 | } 66 | 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /src/components/screens/home/components/trending-authors/trending-authors-discover/TrendingAuthorsDiscover.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { FlatList, Platform, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import TrendingAuthorsDiscoverListItem from './TrendingAuthorsDiscoverListItem'; 8 | import SectionWithButton from '~/components/common/SectionWithButton'; 9 | import CONSTANTS from '~/utils/CONSTANTS'; 10 | 11 | const Wrapper = styled(View)` 12 | width: 100%; 13 | flex: 1; 14 | margin-top: ${({ theme }) => theme.metrics.extraLargeSize * 1.5}px; 15 | margin-bottom: ${({ theme }) => theme.metrics.extraLargeSize}px; 16 | `; 17 | 18 | const TrendingAuthorsList = styled(FlatList)` 19 | width: 100%; 20 | flex: 1; 21 | margin-top: ${({ theme }) => theme.metrics.extraLargeSize}px; 22 | `; 23 | 24 | type Props = { 25 | data: Array, 26 | navigation: Object, 27 | }; 28 | 29 | const TrendingAuthorsDiscover = ({ navigation, data }: Props): Object => ( 30 | 31 | { 33 | const { params } = navigation.state; 34 | 35 | navigation.navigate( 36 | params.LOCAL_STACK_ROUTES.TRENDING_AUTHORS_SEE_ALL, 37 | { [CONSTANTS.PARAMS.TRENDING_AUTHORS]: data }, 38 | ); 39 | }} 40 | sectionTitle="Trending Authors" 41 | buttonText="SEE ALL" 42 | buttonSize="small" 43 | /> 44 | ( 46 | navigation.navigate(CONSTANTS.ROUTES.AUTHOR_DETAIL, { 51 | [CONSTANTS.PARAMS.AUTHOR_DETAIL]: { 52 | id: item.id, 53 | }, 54 | }) 55 | } 56 | /> 57 | )} 58 | keyExtractor={podcast => `${podcast.id}`} 59 | showsHorizontalScrollIndicator={false} 60 | contentContainerStyle={{ 61 | alignItems: 'center', 62 | }} 63 | data={data.slice(0, 5)} 64 | horizontal 65 | /> 66 | 67 | ); 68 | 69 | export default TrendingAuthorsDiscover; 70 | -------------------------------------------------------------------------------- /src/components/common/navigation/components/ProgressTimeLine.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import { connect } from 'react-redux'; 8 | import { Creators as PlayerCreators } from '~/store/ducks/player'; 9 | 10 | import appStyles from '~/styles'; 11 | 12 | const TotalDurationLine = styled(View)` 13 | width: 100%; 14 | height: 2px; 15 | background-color: ${({ theme }) => theme.colors.subTextWhite}; 16 | position: absolute; 17 | `; 18 | 19 | const CurrentTimeLine = styled(View)` 20 | width: ${({ width }) => width}; 21 | height: 2px; 22 | background-color: ${({ theme }) => theme.colors.primaryColor}; 23 | `; 24 | 25 | type Props = { 26 | durationInSeconds: number, 27 | currentTime: string, 28 | }; 29 | 30 | const getTotalSecondsFromCurrentTime = (currentTime: string): number => { 31 | const rawMinutes = currentTime.split(':')[0]; 32 | const rawSeconds = currentTime.split(':')[1]; 33 | 34 | const minutes = parseInt(rawMinutes, 10); 35 | const seconds = parseInt(rawSeconds, 10); 36 | 37 | const currentTimeInSeconds = minutes * 60 + seconds; 38 | 39 | return currentTimeInSeconds; 40 | }; 41 | 42 | const getCurrentTimeLineWidth = ( 43 | durationInSeconds: number, 44 | currentTime: string, 45 | ): number => { 46 | const currentTimeInSeconds = getTotalSecondsFromCurrentTime(currentTime); 47 | const screenWidth = appStyles.metrics.width; 48 | 49 | const currentTimeLineWidth = (currentTimeInSeconds * screenWidth) / durationInSeconds; 50 | 51 | return currentTimeLineWidth; 52 | }; 53 | 54 | const ProgressTimeLine = ({ 55 | durationInSeconds, 56 | currentTime, 57 | }: Props): Object => { 58 | const currentTimeLineWidth = getCurrentTimeLineWidth( 59 | durationInSeconds, 60 | currentTime, 61 | ); 62 | 63 | return ( 64 | 65 | 66 | 69 | 70 | ); 71 | }; 72 | 73 | const mapStateToProps = state => ({ 74 | durationInSeconds: state.player.currentPodcast.durationInSeconds, 75 | currentTime: state.player.currentTime, 76 | }); 77 | 78 | export default connect(mapStateToProps)(ProgressTimeLine); 79 | -------------------------------------------------------------------------------- /src/components/screens/library/components/PodcastsDownloaded.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { PureComponent } from 'react'; 4 | import { FlatList, View, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import { connect } from 'react-redux'; 8 | import { Creators as PlaylistsCreators } from '~/store/ducks/playlist'; 9 | 10 | import { setHeaderPlayButtonPress } from '~/routes/utils/navigationOptions'; 11 | import PodcastsDownloadedListItem from '~/components/common/PodcastItemLIst'; 12 | import CONSTANTS from '~/utils/CONSTANTS'; 13 | 14 | const Wrapper = styled(View)` 15 | width: 100%; 16 | height: 100%; 17 | flex: 1; 18 | background-color: ${({ theme }) => theme.colors.backgroundColor}; 19 | `; 20 | 21 | const PodcastsDownloadedList = styled(FlatList)` 22 | width: 100%; 23 | height: 100%; 24 | padding-horizontal: ${({ theme }) => theme.metrics.mediumSize}px; 25 | `; 26 | 27 | type Props = { 28 | podcastsDownloaded: Array, 29 | navigation: Object, 30 | }; 31 | 32 | class PodcastsDownloaded extends PureComponent { 33 | componentDidMount() { 34 | const { podcastsDownloaded, navigation } = this.props; 35 | const { params } = navigation.state; 36 | 37 | setHeaderPlayButtonPress(podcastsDownloaded, navigation); 38 | } 39 | 40 | render() { 41 | const { podcastsDownloaded, navigation } = this.props; 42 | 43 | return ( 44 | 45 | ( 47 | navigation.navigate(CONSTANTS.ROUTES.PLAYER, { 49 | [CONSTANTS.PARAMS.PLAYER]: { 50 | [CONSTANTS.KEYS.PLAYLIST]: [item], 51 | }, 52 | }) 53 | } 54 | index={index + 1} 55 | podcast={item} 56 | /> 57 | )} 58 | showsVerticalScrollIndicator={false} 59 | keyExtractor={item => `${item.id}`} 60 | data={podcastsDownloaded} 61 | /> 62 | 63 | ); 64 | } 65 | } 66 | 67 | const mapStateToProps = state => ({ 68 | podcastsDownloaded: state.localPodcastsManager.podcastsDownloaded, 69 | }); 70 | 71 | export default connect(mapStateToProps)(PodcastsDownloaded); 72 | -------------------------------------------------------------------------------- /ios/mindCast/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/components/common/PlaylistCompositionImages.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View } from 'react-native'; 5 | import FastImage from 'react-native-fast-image'; 6 | import styled from 'styled-components'; 7 | 8 | import Icon from '~/components/common/Icon'; 9 | import appStyles from '~/styles'; 10 | 11 | const LARGE_SIZE = appStyles.metrics.getWidthFromDP('36.05%'); 12 | const SMALL_SIZE = appStyles.metrics.getWidthFromDP('18.05%'); 13 | 14 | const Wrapper = styled(View)` 15 | width: ${({ theme, size }) => (size === 'large' ? LARGE_SIZE : SMALL_SIZE)}px; 16 | height: ${({ theme, size }) => (size === 'large' ? LARGE_SIZE : SMALL_SIZE)}px; 17 | background-color: ${({ theme }) => theme.colors.textColor}; 18 | border-radius: 5px; 19 | flex-wrap: wrap; 20 | `; 21 | 22 | const PodcastImage = styled(FastImage).attrs(({ uri }) => ({ 23 | source: { uri }, 24 | }))` 25 | width: ${({ theme, size }) => (size === 'large' ? LARGE_SIZE / 2 : SMALL_SIZE / 2)}px; 26 | height: ${({ theme, size }) => (size === 'large' ? LARGE_SIZE / 2 : SMALL_SIZE / 2)}px; 27 | border-top-left-radius: ${({ index, theme }) => (index === 0 ? 5 : 0)}px; 28 | border-top-right-radius: ${({ index, theme }) => (index === 2 ? 5 : 0)}px; 29 | border-bottom-left-radius: ${({ index, theme }) => (index === 1 ? 5 : 0)}px; 30 | border-bottom-right-radius: ${({ index, theme }) => (index === 3 ? 5 : 0)}px; 31 | `; 32 | 33 | const PodcastIconWrapper = styled(View)` 34 | width: 100%; 35 | height: 100%; 36 | justify-content: center; 37 | align-items: center; 38 | `; 39 | 40 | type Props = { 41 | images: Array, 42 | size: string, 43 | }; 44 | 45 | const PlaylistCompositionImages = ({ images, size }: Props): Object => ( 46 | 49 | {images.length === 0 && ( 50 | 51 | 56 | 57 | )} 58 | {images.length > 0 59 | && images.map((url, index) => ( 60 | 66 | ))} 67 | 68 | ); 69 | 70 | export default PlaylistCompositionImages; 71 | -------------------------------------------------------------------------------- /src/components/screens/home/components/new-releases/NewReleasesSeeAll.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { FlatList, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import { setHeaderPlayButtonPress } from '~/routes/utils/navigationOptions'; 8 | import NewReleasesSeeAllListItem from '~/components/common/PodcastItemLIst'; 9 | import CONSTANTS from '~/utils/CONSTANTS'; 10 | 11 | const Wrapper = styled(View)` 12 | width: 100%; 13 | height: 100%; 14 | flex: 1; 15 | padding-horizontal: ${({ theme }) => theme.metrics.mediumSize}px; 16 | background-color: ${({ theme }) => theme.colors.backgroundColor}; 17 | `; 18 | 19 | const NewReleasesSeeAllList = styled(FlatList)` 20 | width: 100%; 21 | height: 100%; 22 | `; 23 | 24 | type Props = { 25 | data: Array, 26 | navigation: Object, 27 | }; 28 | 29 | class NewReleasesSeeAll extends Component { 30 | componentDidMount() { 31 | const newReleases = this.getNewReleases(); 32 | const { navigation } = this.props; 33 | 34 | setHeaderPlayButtonPress(newReleases, navigation); 35 | } 36 | 37 | getNewReleases = (): Array => { 38 | const { navigation } = this.props; 39 | const { params } = navigation.state; 40 | 41 | return params[CONSTANTS.PARAMS.PODCASTS_NEW_RELEASES]; 42 | }; 43 | 44 | render() { 45 | const { navigation } = this.props; 46 | 47 | const newReleases = this.getNewReleases(); 48 | 49 | return ( 50 | 51 | `${podcast.id}`} 53 | showsVerticalScrollIndicator={false} 54 | data={newReleases} 55 | renderItem={({ item, index }) => ( 56 | navigation.navigate(CONSTANTS.ROUTES.PODCAST_DETAIL, { 58 | [CONSTANTS.KEYS 59 | .PODCAST_DETAIL_SHOULD_SHOW_AUTHOR_SECTION]: true, 60 | [CONSTANTS.PARAMS.PODCAST_DETAIL]: item, 61 | }) 62 | } 63 | shouldShowDownloadStatus={false} 64 | index={index + 1} 65 | podcast={item} 66 | /> 67 | )} 68 | /> 69 | 70 | ); 71 | } 72 | } 73 | 74 | export default NewReleasesSeeAll; 75 | -------------------------------------------------------------------------------- /src/components/screens/home/components/new-releases/new-releases-discover/NewReleasesDiscover.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { FlatList, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import NewReleasesDiscoverListItem from './NewReleasesDiscoverListItem'; 8 | import SectionWithButton from '~/components/common/SectionWithButton'; 9 | import CONSTANTS from '~/utils/CONSTANTS'; 10 | 11 | const Wrapper = styled(View)` 12 | width: 100%; 13 | flex: 1; 14 | margin-top: ${({ theme }) => theme.metrics.extraLargeSize * 1.5}px; 15 | margin-bottom: ${({ theme }) => theme.metrics.extraLargeSize}px; 16 | `; 17 | 18 | const NewReleasesDiscoverList = styled(FlatList)` 19 | width: 100%; 20 | flex: 1; 21 | margin-top: ${({ theme }) => theme.metrics.extraLargeSize}px; 22 | `; 23 | 24 | type Props = { 25 | data: Array, 26 | navigation: Object, 27 | }; 28 | 29 | const NewReleasesDiscover = ({ navigation, data }: Props): Object => ( 30 | 31 | { 33 | const { params } = navigation.state; 34 | navigation.navigate(params.LOCAL_STACK_ROUTES.NEW_RELEASES_SEE_ALL, { 35 | [CONSTANTS.PARAMS.PODCASTS_NEW_RELEASES]: data, 36 | }); 37 | }} 38 | sectionTitle="New Releases" 39 | buttonText="SEE ALL" 40 | buttonSize="small" 41 | /> 42 | `${podcast.id}`} 44 | showsHorizontalScrollIndicator={false} 45 | horizontal 46 | data={data.slice(0, 9)} 47 | renderItem={({ item, index }) => ( 48 | navigation.navigate(CONSTANTS.ROUTES.PODCAST_DETAIL, { 50 | [CONSTANTS.KEYS.PODCAST_DETAIL_SHOULD_SHOW_AUTHOR_SECTION]: true, 51 | [CONSTANTS.PARAMS.PODCAST_DETAIL]: item, 52 | }) 53 | } 54 | authorImage={item.author.thumbnailProfileImageURL} 55 | authorName={item.author.name} 56 | podcastImage={item.imageURL} 57 | isLastIndex={index === data.length - 1} 58 | navigation={navigation} 59 | subject={item.category} 60 | title={item.title} 61 | /> 62 | )} 63 | /> 64 | 65 | ); 66 | 67 | export default NewReleasesDiscover; 68 | -------------------------------------------------------------------------------- /src/components/screens/home/components/hottest-podcasts/HottestPodcastsDiscover.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { FlatList, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import NewReleasesSectionItemList from '~/components/common/author-detail/components/new-releases-section/NewReleasesSectionItemList'; 8 | import SectionWithButton from '~/components/common/SectionWithButton'; 9 | import CONSTANTS from '~/utils/CONSTANTS'; 10 | 11 | const Wrapper = styled(View)` 12 | width: 100%; 13 | flex: 1; 14 | margin-top: ${({ theme }) => theme.metrics.extraLargeSize * 1.5}px; 15 | margin-bottom: ${({ theme }) => theme.metrics.extraLargeSize}px; 16 | `; 17 | 18 | const HottestPodcastsDiscoverList = styled(FlatList)` 19 | width: 100%; 20 | flex: 1; 21 | margin-top: ${({ theme }) => theme.metrics.extraLargeSize}px; 22 | `; 23 | 24 | type Props = { 25 | data: Array, 26 | navigation: Object, 27 | }; 28 | 29 | const HottestPodcastsDiscover = ({ navigation, data }: Props): Object => ( 30 | 31 | { 33 | const { params } = navigation.state; 34 | navigation.navigate( 35 | params.LOCAL_STACK_ROUTES.HOTTEST_PODCASTS_SEE_ALL, 36 | { 37 | [CONSTANTS.PARAMS.PODCASTS_HOTTEST_PODCASTS]: data, 38 | }, 39 | ); 40 | }} 41 | sectionTitle="Hottest Podcasts" 42 | buttonText="SEE ALL" 43 | buttonSize="small" 44 | /> 45 | `${podcast.id}`} 47 | showsHorizontalScrollIndicator={false} 48 | data={data.slice(0, 9)} 49 | horizontal 50 | renderItem={({ item, index }) => ( 51 | navigation.navigate(CONSTANTS.ROUTES.PODCAST_DETAIL, { 53 | [CONSTANTS.KEYS.PODCAST_DETAIL_SHOULD_SHOW_AUTHOR_SECTION]: true, 54 | [CONSTANTS.PARAMS.PODCAST_DETAIL]: item, 55 | }) 56 | } 57 | isLastIndex={index === data.length - 1} 58 | imageURL={item.imageURL} 59 | subject={item.category} 60 | title={item.title} 61 | stars={item.stars} 62 | /> 63 | )} 64 | /> 65 | 66 | ); 67 | 68 | export default HottestPodcastsDiscover; 69 | -------------------------------------------------------------------------------- /src/components/common/player/components/bottom-player-options/BottomPlayerOptions.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import PlaylistList from '~/components/common/playlists-list/PlaylistsListContainer'; 8 | import AddPodcastPlaylist from './components/AddPodcastPlaylist'; 9 | import ShufflePlaylist from './components/ShufflePlaylist'; 10 | import Download from './components/Download'; 11 | import Repeat from './components/Repeat'; 12 | import appStyles from '~/styles'; 13 | 14 | const Wrapper = styled(View)` 15 | width: 100%; 16 | height: 30px; 17 | justify-content: space-between; 18 | align-items: center; 19 | flex-direction: row; 20 | padding-horizontal: ${({ theme }) => theme.metrics.getWidthFromDP('8%')}px; 21 | `; 22 | 23 | type Props = { 24 | isCurrentPodcastDownloaded: boolean, 25 | onToggleAddPlaylistModal: Function, 26 | shouldShufflePlaylist: boolean, 27 | shouldRepeatPlaylist: boolean, 28 | shouldRepeatCurrent: boolean, 29 | disableRepetition: Function, 30 | setRepeatPlaylist: Function, 31 | setRepeatCurrent: Function, 32 | shufflePlaylist: Function, 33 | playlist: Array, 34 | currentPodcast: Object, 35 | playlistIndex: number, 36 | }; 37 | 38 | const BottomPlayerOptions = ({ 39 | isCurrentPodcastDownloaded, 40 | onToggleAddPlaylistModal, 41 | shouldShufflePlaylist, 42 | shouldRepeatPlaylist, 43 | shouldRepeatCurrent, 44 | disableRepetition, 45 | setRepeatPlaylist, 46 | setRepeatCurrent, 47 | shufflePlaylist, 48 | currentPodcast, 49 | playlistIndex, 50 | playlist, 51 | }: Props): Object => ( 52 | 53 | 57 | 64 | 68 | 71 | 72 | ); 73 | 74 | export default BottomPlayerOptions; 75 | -------------------------------------------------------------------------------- /src/components/common/podcast-detail/components/BottomContent.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Fragment } from 'react'; 4 | import { View, Text } from 'react-native'; 5 | import FastImage from 'react-native-fast-image'; 6 | import styled from 'styled-components'; 7 | 8 | import AuthorsListItem from '~/components/common/AuthorListItem'; 9 | import DefaultButton from '~/components/common/DefaultButton'; 10 | import SectionTitle from '~/components/common/SectionTitle'; 11 | 12 | const Wrapper = styled(View)` 13 | margin-bottom: ${({ theme }) => theme.metrics.largeSize}px; 14 | `; 15 | 16 | const Section = styled(View)` 17 | margin-vertical: ${({ theme }) => theme.metrics.largeSize}px; 18 | `; 19 | 20 | const SectionContentWrapper = styled(View)` 21 | margin-top: ${({ theme }) => theme.metrics.largeSize}px; 22 | padding: ${({ theme }) => theme.metrics.mediumSize}px; 23 | background-color: ${({ theme }) => theme.colors.secondaryColor}; 24 | border-radius: 4px; 25 | `; 26 | 27 | const AuthorDetailWrapper = styled(View)` 28 | width: 100%; 29 | margin-top: ${({ theme }) => theme.metrics.largeSize}px; 30 | background-color: ${({ theme }) => theme.colors.secondaryColor}; 31 | border-radius: 4px; 32 | `; 33 | 34 | const PodcastDescriptionText = styled(Text)` 35 | color: ${({ theme }) => theme.colors.textColor}; 36 | font-family: CircularStd-Medium; 37 | font-size: ${({ theme }) => theme.metrics.largeSize * 1.1}px; 38 | `; 39 | 40 | type Props = { 41 | shouldShowAuthorSection: boolean, 42 | onPressDetail: Function, 43 | description: string, 44 | author: Object, 45 | }; 46 | 47 | const BottomContent = ({ 48 | shouldShowAuthorSection, 49 | onPressDetail, 50 | description, 51 | author, 52 | }: Props): Object => ( 53 | 54 |
55 | 58 | 59 | {description} 60 | 61 |
62 | {shouldShowAuthorSection && ( 63 |
64 | 67 | 68 | 72 | 73 |
74 | )} 75 |
76 | ); 77 | 78 | export default BottomContent; 79 | -------------------------------------------------------------------------------- /src/components/screens/search/components/SearchAuthorTextInput.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { TextInput, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import Icon from '~/components/common/Icon'; 8 | import appStyles from '~/styles'; 9 | 10 | const Wrapper = styled(View)` 11 | width: 100%; 12 | flex-direction: row; 13 | align-items: center; 14 | margin-vertical: ${({ theme }) => theme.metrics.largeSize}px; 15 | padding-horizontal: ${({ theme }) => theme.metrics.smallSize}px; 16 | border-radius: 4px; 17 | background-color: ${({ theme }) => theme.colors.white}; 18 | `; 19 | 20 | const Input = styled(TextInput).attrs(({ theme }) => ({ 21 | placeholderTextColor: theme.colors.subText, 22 | selectionColor: theme.colors.subText, 23 | underlineColorAndroid: 'transparent', 24 | autoCapitalize: 'none', 25 | autoCorrect: false, 26 | placeholder: 'Search for a specific Author', 27 | returnKeyLabel: 'search', 28 | returnKeyType: 'search', 29 | }))` 30 | width: 90%; 31 | height: 100%; 32 | margin-left: ${({ theme }) => theme.metrics.smallSize}px; 33 | font-family: CircularStd-Book; 34 | color: ${({ theme }) => theme.colors.darkText}; 35 | `; 36 | 37 | const IconWrapper = styled(View)` 38 | padding-top: ${({ theme }) => theme.metrics.mediumSize}; 39 | padding-bottom: ${({ theme }) => theme.metrics.getWidthFromDP('2.5%')}; 40 | `; 41 | 42 | type Props = { 43 | onSearchForAuthor: Function, 44 | onToggleDarkLayer: Function, 45 | onTypeAuthorName: Function, 46 | }; 47 | 48 | const SearchAuthorTextInput = ({ 49 | onSearchForAuthor, 50 | onToggleDarkLayer, 51 | onTypeAuthorName, 52 | }: Props): Object => ( 53 | 65 | 66 | 71 | 72 | onTypeAuthorName(text)} 74 | onFocus={() => onToggleDarkLayer(true)} 75 | onBlur={() => onToggleDarkLayer(false)} 76 | onSubmitEditing={onSearchForAuthor} 77 | /> 78 | 79 | ); 80 | 81 | export default SearchAuthorTextInput; 82 | -------------------------------------------------------------------------------- /src/components/screens/home/components/hottest-podcasts/HottestPodcastsSeeAll.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { FlatList, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import HottestPodcastsSeeAllListItem from '~/components/common/PodcastItemLIst'; 8 | 9 | import { setHeaderPlayButtonPress } from '~/routes/utils/navigationOptions'; 10 | import AuthorsListItem from '~/components/common/AuthorListItem'; 11 | import CONSTANTS from '~/utils/CONSTANTS'; 12 | 13 | const Wrapper = styled(View)` 14 | width: 100%; 15 | height: 100%; 16 | flex: 1; 17 | padding-horizontal: ${({ theme }) => theme.metrics.mediumSize}px; 18 | background-color: ${({ theme }) => theme.colors.backgroundColor}; 19 | `; 20 | 21 | const HottestPodcastsSeeAllList = styled(FlatList)` 22 | width: 100%; 23 | height: 100%; 24 | `; 25 | 26 | type Props = { 27 | navigation: Object, 28 | }; 29 | 30 | class HottestPodcastsSeeAll extends Component { 31 | componentDidMount() { 32 | const { navigation } = this.props; 33 | const hottestPodcasts = this.getHottestPodcasts(); 34 | 35 | setHeaderPlayButtonPress(hottestPodcasts, navigation); 36 | } 37 | 38 | getHottestPodcasts = (): Array => { 39 | const { navigation } = this.props; 40 | const { params } = navigation.state; 41 | 42 | return params[CONSTANTS.PARAMS.PODCASTS_HOTTEST_PODCASTS]; 43 | }; 44 | 45 | render() { 46 | const { navigation } = this.props; 47 | 48 | const hottestPodcasts = this.getHottestPodcasts(); 49 | 50 | return ( 51 | 52 | `${podcast.id}`} 54 | showsVerticalScrollIndicator={false} 55 | data={hottestPodcasts} 56 | renderItem={({ item, index }) => ( 57 | navigation.navigate(CONSTANTS.ROUTES.PODCAST_DETAIL, { 59 | [CONSTANTS.KEYS 60 | .PODCAST_DETAIL_SHOULD_SHOW_AUTHOR_SECTION]: true, 61 | [CONSTANTS.PARAMS.PODCAST_DETAIL]: item, 62 | }) 63 | } 64 | shouldShowDownloadStatus={false} 65 | roundedImage={false} 66 | podcast={item} 67 | index={index + 1} 68 | /> 69 | )} 70 | /> 71 | 72 | ); 73 | } 74 | } 75 | 76 | export default HottestPodcastsSeeAll; 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mind-cast", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "build-android": "cd android && ./gradlew assembleRelease && cd .. && react-native run-android --variant=release", 8 | "lint": "eslint '**/*.js'", 9 | "lint:fix": "prettier-eslint '**/*.js' --write", 10 | "start:clean": "react-native start --reset-cache", 11 | "test": "jest --watch", 12 | "flow": "flow" 13 | }, 14 | "rnpm": { 15 | "assets": [ 16 | "./assets/fonts/" 17 | ] 18 | }, 19 | "husky": { 20 | "hooks": { 21 | "pre-commit": "lint-staged" 22 | } 23 | }, 24 | "lint-staged": { 25 | "*.js": [ 26 | "npm run lint:fix", 27 | "git add" 28 | ] 29 | }, 30 | "dependencies": { 31 | "axios": "^0.18.0", 32 | "react": "16.6.3", 33 | "react-native": "0.58.4", 34 | "react-native-dotenv": "^0.2.0", 35 | "react-native-fast-image": "^5.1.2", 36 | "react-native-fs": "2.13.2", 37 | "react-native-gesture-handler": "^1.0.16", 38 | "react-native-linear-gradient": "^2.5.3", 39 | "react-native-side-menu": "^1.1.3", 40 | "react-native-splash-screen": "^3.2.0", 41 | "react-native-swipeout": "^2.3.6", 42 | "react-native-vector-icons": "^6.3.0", 43 | "react-native-video": "^4.4.0", 44 | "react-navigation": "^3.3.0", 45 | "react-redux": "^6.0.0", 46 | "redux": "^4.0.1", 47 | "redux-saga": "^1.0.1", 48 | "styled-components": "^4.1.3" 49 | }, 50 | "devDependencies": { 51 | "@babel/plugin-proposal-class-properties": "^7.3.3", 52 | "@babel/plugin-transform-flow-strip-types": "^7.2.3", 53 | "babel-core": "^7.0.0-bridge.0", 54 | "babel-eslint": "^10.0.1", 55 | "babel-plugin-root-import": "^6.1.0", 56 | "eslint": "^5.13.0", 57 | "eslint-config-airbnb": "^17.1.0", 58 | "eslint-import-resolver-babel-plugin-root-import": "^1.1.1", 59 | "eslint-plugin-import": "^2.16.0", 60 | "eslint-plugin-jsx-a11y": "^6.2.1", 61 | "eslint-plugin-react": "^7.12.4", 62 | "eslint-plugin-react-native": "^3.6.0", 63 | "flow": "^0.2.3", 64 | "flow-bin": "^0.92.1", 65 | "husky": "^1.3.1", 66 | "lint-staged": "^8.1.3", 67 | "metro-react-native-babel-preset": "0.51.1", 68 | "prettier-eslint": "^8.8.2", 69 | "prettier-eslint-cli": "^4.7.1", 70 | "reactotron-react-native": "^2.1.5", 71 | "reactotron-redux": "^2.1.3", 72 | "reactotron-redux-saga": "^4.0.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/screens/library/components/playlist-detail/components/PlaylistDetailComponent.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { FlatList, Animated, View } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import PodcastListItem from './PodcastListItem'; 8 | import Header from './Header'; 9 | 10 | import CONSTANTS from '~/utils/CONSTANTS'; 11 | import appStyles from '~/styles'; 12 | 13 | const HEADER_HEIGHT = appStyles.metrics.getHeightFromDP('50%'); 14 | 15 | const Container = styled(View)` 16 | width: 100%; 17 | height: 100%; 18 | position: absolute; 19 | background-color: ${({ theme }) => theme.colors.secondaryColor}; 20 | `; 21 | 22 | type Props = { 23 | onTogglePlaylistDownloadedSwitch: Function, 24 | onRemovePodcastFromPlaylist: Function, 25 | isPlaylistAvailableOffline: boolean, 26 | onPressPlayAllButton: Function, 27 | onPressShuffleButton: Function, 28 | podcastsImages: Array, 29 | podcasts: Array, 30 | navigation: Object, 31 | title: string, 32 | }; 33 | 34 | const PlaylistDetailComponent = ({ 35 | onTogglePlaylistDownloadedSwitch, 36 | onRemovePodcastFromPlaylist, 37 | isPlaylistAvailableOffline, 38 | onPressPlayAllButton, 39 | onPressShuffleButton, 40 | podcastsImages, 41 | navigation, 42 | podcasts, 43 | title, 44 | }: Props): Object => ( 45 | 46 |
54 | ( 56 | onRemovePodcastFromPlaylist(index)} 58 | onPressPodcastsListItem={() => { 59 | navigation.navigate(CONSTANTS.ROUTES.PODCAST_DETAIL, { 60 | [CONSTANTS.KEYS.PODCAST_DETAIL_SHOULD_SHOW_AUTHOR_SECTION]: true, 61 | [CONSTANTS.PARAMS.PODCAST_DETAIL]: item, 62 | }); 63 | }} 64 | isDownloading={item.isDownloading} 65 | podcast={item} 66 | index={index} 67 | /> 68 | )} 69 | keyExtractor={(item, index) => `${item.title}-${index}`} 70 | showsVerticalScrollIndicator={false} 71 | data={podcasts} 72 | /> 73 | 74 | ); 75 | 76 | export default PlaylistDetailComponent; 77 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | 16 | ; Ignore polyfills 17 | .*/Libraries/polyfills/.* 18 | 19 | ; Ignore metro 20 | .*/node_modules/.* 21 | 22 | [include] 23 | 24 | [libs] 25 | node_modules/react-native/Libraries/react-native/react-native-interface.js 26 | node_modules/react-native/flow/ 27 | node_modules/react-native/flow-github/ 28 | 29 | [options] 30 | module.name_mapper='^{~}/\(.*\)$' -> '/{src}/\1' 31 | emoji=true 32 | 33 | esproposal.optional_chaining=enable 34 | esproposal.nullish_coalescing=enable 35 | 36 | module.system=haste 37 | module.system.haste.use_name_reducers=true 38 | # get basename 39 | module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' 40 | # strip .js or .js.flow suffix 41 | module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' 42 | # strip .ios suffix 43 | module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' 44 | module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' 45 | module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' 46 | module.system.haste.paths.blacklist=.*/__tests__/.* 47 | module.system.haste.paths.blacklist=.*/__mocks__/.* 48 | module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* 49 | module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* 50 | 51 | munge_underscores=true 52 | 53 | 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' 54 | 55 | module.file_ext=.js 56 | module.file_ext=.jsx 57 | module.file_ext=.json 58 | module.file_ext=.native.js 59 | 60 | suppress_type=$FlowIssue 61 | suppress_type=$FlowFixMe 62 | suppress_type=$FlowFixMeProps 63 | suppress_type=$FlowFixMeState 64 | 65 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 66 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 67 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 68 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 69 | 70 | [version] 71 | ^0.92.1 72 | -------------------------------------------------------------------------------- /src/components/common/podcast-detail/components/PodcastInfo.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { View, Text } from 'react-native'; 5 | import FastImage from 'react-native-fast-image'; 6 | import styled from 'styled-components'; 7 | 8 | import ReviewStars from '~/components/common/ReviewStars'; 9 | 10 | const Wrapper = styled(View)` 11 | width: 100%; 12 | flex-direction: row; 13 | align-items: center; 14 | `; 15 | 16 | const TextContentWrapper = styled(View)` 17 | width: ${({ theme }) => theme.metrics.getWidthFromDP('65%')}px; 18 | padding-horizontal: ${({ theme }) => theme.metrics.mediumSize}px; 19 | justify-content: center; 20 | `; 21 | 22 | const PodcastImage = styled(FastImage).attrs(({ uri }) => ({ 23 | source: { uri }, 24 | }))` 25 | width: ${({ theme }) => theme.metrics.getWidthFromDP('30%')}px; 26 | height: ${({ theme }) => theme.metrics.getHeightFromDP('20%')}px; 27 | border-radius: 2px; 28 | `; 29 | 30 | const PodcastTitleText = styled(Text).attrs({ 31 | numberOfLines: 4, 32 | })` 33 | margin-bottom: ${({ theme }) => theme.metrics.extraSmallSize}px; 34 | font-size: ${({ theme }) => theme.metrics.largeSize * 1.15}px; 35 | color: ${({ theme }) => theme.colors.textColor}; 36 | font-family: CircularStd-Bold; 37 | `; 38 | 39 | const PodcastSubjectText = styled(Text)` 40 | color: ${({ theme }) => theme.colors.white}; 41 | font-size: ${({ theme }) => theme.metrics.largeSize * 1.1}px; 42 | font-family: CircularStd-Bold; 43 | `; 44 | 45 | const SubjectWrapper = styled(View)` 46 | align-items: flex-start; 47 | margin-top: ${({ theme }) => theme.metrics.smallSize}px; 48 | `; 49 | 50 | const SubjectTextWrapper = styled(View)` 51 | padding-vertical: ${({ theme }) => theme.metrics.smallSize}px; 52 | padding-horizontal: ${({ theme }) => theme.metrics.mediumSize}px; 53 | border-radius: 3px; 54 | background-color: ${({ theme }) => theme.colors.black}; 55 | `; 56 | 57 | type Props = { 58 | imageURL: string, 59 | subject: string, 60 | title: string, 61 | stars: number, 62 | }; 63 | 64 | const PodcastInfo = ({ 65 | imageURL, subject, title, stars, 66 | }: Props): Object => ( 67 | 68 | 71 | 72 | {title} 73 | 76 | 77 | 78 | {`#${subject}`} 79 | 80 | 81 | 82 | 83 | ); 84 | 85 | export default PodcastInfo; 86 | -------------------------------------------------------------------------------- /flow-typed/npm/react-test-renderer_v16.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 9b9f4128694a7f68659d945b81fb78ff 2 | // flow-typed version: 46dfe79a54/react-test-renderer_v16.x.x/flow_>=v0.47.x 3 | 4 | // Type definitions for react-test-renderer 16.x.x 5 | // Ported from: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-test-renderer 6 | 7 | type ReactComponentInstance = React$Component; 8 | 9 | type ReactTestRendererJSON = { 10 | type: string, 11 | props: { [propName: string]: any }, 12 | children: null | ReactTestRendererJSON[], 13 | }; 14 | 15 | type ReactTestRendererTree = ReactTestRendererJSON & { 16 | nodeType: 'component' | 'host', 17 | instance: ?ReactComponentInstance, 18 | rendered: null | ReactTestRendererTree, 19 | }; 20 | 21 | type ReactTestInstance = { 22 | instance: ?ReactComponentInstance, 23 | type: string, 24 | props: { [propName: string]: any }, 25 | parent: null | ReactTestInstance, 26 | children: Array, 27 | 28 | find(predicate: (node: ReactTestInstance) => boolean): ReactTestInstance, 29 | findByType(type: React$ElementType): ReactTestInstance, 30 | findByProps(props: { [propName: string]: any }): ReactTestInstance, 31 | 32 | findAll( 33 | predicate: (node: ReactTestInstance) => boolean, 34 | options?: { deep: boolean }, 35 | ): ReactTestInstance[], 36 | findAllByType( 37 | type: React$ElementType, 38 | options?: { deep: boolean }, 39 | ): ReactTestInstance[], 40 | findAllByProps( 41 | props: { [propName: string]: any }, 42 | options?: { deep: boolean }, 43 | ): ReactTestInstance[], 44 | }; 45 | 46 | type TestRendererOptions = { 47 | createNodeMock(element: React$Element): any, 48 | }; 49 | 50 | declare module 'react-test-renderer' { 51 | declare export type ReactTestRenderer = { 52 | toJSON(): null | ReactTestRendererJSON, 53 | toTree(): null | ReactTestRendererTree, 54 | unmount(nextElement?: React$Element): void, 55 | update(nextElement: React$Element): void, 56 | getInstance(): ?ReactComponentInstance, 57 | root: ReactTestInstance, 58 | }; 59 | 60 | declare function create( 61 | nextElement: React$Element, 62 | options?: TestRendererOptions, 63 | ): ReactTestRenderer; 64 | } 65 | 66 | declare module 'react-test-renderer/shallow' { 67 | declare export default class ShallowRenderer { 68 | static createRenderer(): ShallowRenderer; 69 | getMountedInstance(): ReactTestInstance; 70 | getRenderOutput>(): E; 71 | getRenderOutput(): React$Element; 72 | render(element: React$Element, context?: any): void; 73 | unmount(): void; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/screens/oboarding-intro/components/MiddleContent.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | import { Platform, View, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import Icon from '~/components/common/Icon'; 8 | import appStyles from '~/styles'; 9 | 10 | const Wrapper = styled(View)` 11 | width: 100%; 12 | justify-content: center; 13 | align-items: center; 14 | padding-horizontal: ${({ theme }) => theme.metrics.extraLargeSize}px; 15 | `; 16 | 17 | const IconWrapper = styled(View)` 18 | width: ${({ theme }) => theme.metrics.getWidthFromDP('50%')}px; 19 | height: ${({ theme }) => theme.metrics.getWidthFromDP('50%')}px; 20 | justify-content: center; 21 | align-items: center; 22 | padding-top: ${({ theme }) => (Platform.OS === 'ios' ? theme.metrics.mediumSize : 0)}px; 23 | border-radius: ${({ theme }) => theme.metrics.getWidthFromDP('25%')}px; 24 | background-color: ${({ theme }) => theme.colors.primaryColor}; 25 | `; 26 | 27 | const Title = styled(Text)` 28 | margin-top: ${({ theme }) => 1.2 * theme.metrics.extraLargeSize}px; 29 | margin-bottom: ${({ theme }) => theme.metrics.mediumSize}px; 30 | color: ${({ theme }) => theme.colors.darkText}; 31 | font-size: ${({ theme }) => 1.3 * theme.metrics.extraLargeSize}; 32 | font-family: CircularStd-Black; 33 | `; 34 | 35 | const Description = styled(Text)` 36 | font-size: ${({ theme }) => 1.2 * theme.metrics.largeSize}px; 37 | font-family: CircularStd-Medium; 38 | color: ${({ theme }) => theme.colors.subTextWhite}; 39 | text-align: center; 40 | `; 41 | 42 | const ITEMS = [ 43 | { 44 | title: 'DISCOVER', 45 | description: 'Find a new way to sharp your knowledge about the world.', 46 | icon: 'compass', 47 | }, 48 | { 49 | title: 'LEARN', 50 | description: 51 | 'Learn about a new subject everyday and start to see the world with a new perspective.', 52 | icon: 'brain', 53 | }, 54 | { 55 | title: 'LISTEN ANY TIME', 56 | description: 57 | 'Download your favorite podcasts and playlists to listen offline.', 58 | icon: 'headphones', 59 | }, 60 | ]; 61 | 62 | type Props = { 63 | currentIndex: number, 64 | }; 65 | 66 | const MiddleContent = ({ currentIndex }: Props): Object => { 67 | const { title, description, icon } = ITEMS[currentIndex]; 68 | 69 | return ( 70 | 71 | 72 | 77 | 78 | {title} 79 | {description} 80 | 81 | ); 82 | }; 83 | 84 | export default MiddleContent; 85 | -------------------------------------------------------------------------------- /ios/mindCast/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | MindCast 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 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 | LSApplicationCategoryType 26 | 27 | 28 | 29 | LSRequiresIPhoneOS 30 | 31 | NSAppTransportSecurity 32 | 33 | NSAllowsArbitraryLoads 34 | 35 | NSExceptionDomains 36 | 37 | localhost 38 | 39 | NSExceptionAllowsInsecureHTTPLoads 40 | 41 | 42 | 43 | 44 | NSLocationWhenInUseUsageDescription 45 | 46 | UIAppFonts 47 | 48 | MaterialCommunityIcons.ttf 49 | CircularStd-Black.otf 50 | CircularStd-Bold.otf 51 | CircularStd-Book.otf 52 | CircularStd-Medium.otf 53 | Modesta-Script.ttf 54 | AntDesign.ttf 55 | Entypo.ttf 56 | EvilIcons.ttf 57 | Feather.ttf 58 | FontAwesome.ttf 59 | FontAwesome5_Brands.ttf 60 | FontAwesome5_Regular.ttf 61 | FontAwesome5_Solid.ttf 62 | Foundation.ttf 63 | Ionicons.ttf 64 | MaterialIcons.ttf 65 | Octicons.ttf 66 | SimpleLineIcons.ttf 67 | Zocial.ttf 68 | 69 | UIBackgroundModes 70 | 71 | audio 72 | 73 | UILaunchStoryboardName 74 | LaunchScreen 75 | UIRequiredDeviceCapabilities 76 | 77 | armv7 78 | 79 | UISupportedInterfaceOrientations 80 | 81 | UIInterfaceOrientationPortrait 82 | 83 | UIViewControllerBasedStatusBarAppearance 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/components/common/playlists-list/components/CreatePlaylistButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | import { TouchableOpacity, View, Text } from 'react-native'; 5 | import styled from 'styled-components'; 6 | 7 | import CreatePlaylistAlert from '~/components/screens/library/components/playlists/PlaylistOperationModal'; 8 | import CreatePlaylist from '~/components/common/DefaultButton'; 9 | 10 | const Wrapper = styled(View)` 11 | width: 100%; 12 | justify-content: center; 13 | align-items: center 14 | align-self: center; 15 | margin-top: ${({ theme }) => theme.metrics.getHeightFromDP('30%')}px; 16 | `; 17 | 18 | const DefaultText = styled(Text)` 19 | margin-bottom: ${({ theme }) => theme.metrics.extraLargeSize}px; 20 | color: ${({ theme }) => theme.colors.textColor}; 21 | font-family: CircularStd-Medium; 22 | font-size: ${({ theme }) => theme.metrics.extraLargeSize}px; 23 | `; 24 | 25 | type Props = { 26 | createPlaylist: Function, 27 | }; 28 | 29 | type State = { 30 | playlistTitle: string, 31 | isModalOpen: boolean, 32 | }; 33 | 34 | class CreatePlaylistButton extends Component { 35 | state = { 36 | isModalOpen: false, 37 | playlistTitle: '', 38 | }; 39 | 40 | onCreatePlaylist = (iosTitle: ?string): void => { 41 | const { createPlaylist } = this.props; 42 | const { playlistTitle } = this.state; 43 | 44 | const title = iosTitle || playlistTitle; 45 | 46 | if (title) { 47 | createPlaylist(title); 48 | } 49 | 50 | this.setState({ 51 | isModalOpen: false, 52 | }); 53 | }; 54 | 55 | onToggleModal = (): void => { 56 | const { isModalOpen } = this.state; 57 | 58 | this.setState({ 59 | isModalOpen: !isModalOpen, 60 | }); 61 | }; 62 | 63 | onTypePlaylistTitle = (playlistTitle: string): void => { 64 | this.setState({ 65 | playlistTitle, 66 | }); 67 | }; 68 | 69 | render() { 70 | const { playlistTitle, isModalOpen } = this.state; 71 | 72 | return ( 73 | 74 | There's no Playlists created. 75 | 76 | 81 | 82 | {isModalOpen && ( 83 | 91 | )} 92 | 93 | ); 94 | } 95 | } 96 | 97 | export default CreatePlaylistButton; 98 | -------------------------------------------------------------------------------- /src/components/common/Alert.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { Alert } from 'react-native'; 4 | 5 | export const TYPES = { 6 | REMOVE_DOWNLOADED_PODCAST_BY_PLAYLIST: 7 | 'REMOVE_DOWNLOADED_PODCAST_BY_PLAYLIST', 8 | ADD_UNDOWNLOADED_PODCAST_PLAYLIST_AVAILABLE_OFFLINE: 9 | 'ADD_UNDOWNLOADED_PODCAST_PLAYLIST_AVAILABLE_OFFLINE', 10 | REMOVE_PODCAST_FROM_PLAYLIST: 'REMOVE_PODCAST_FROM_PLAYLIST', 11 | ADD_REPEATED_PODCAS_PLAYLIST: 'ADD_REPEATED_PODCAS_PLAYLIST', 12 | REMOVE_DOWNLOADED_PODCAST: 'REMOVE_DOWNLOADED_PODCAST', 13 | DOWNLOAD_PODCAST: 'DOWNLOAD_PODCAST', 14 | REMOVE_PLAYLIST: 'REMOVE_PLAYLIST', 15 | }; 16 | 17 | const configs = { 18 | [TYPES.REMOVE_DOWNLOADED_PODCAST_BY_PLAYLIST]: { 19 | title: 'Remove Downloaded Podcast', 20 | description: 21 | "This Podcast belongs to some of your Playlists that are available offline. If you remove the download of this podcast, it won't be available offline anymore on these playlists.", 22 | positiveText: 'Ok', 23 | }, 24 | 25 | [TYPES.REMOVE_DOWNLOADED_PODCAST]: { 26 | title: 'Remove Downloaded Podcast', 27 | description: 28 | 'Are you sure to remove this Podcast permanently from your device?', 29 | positiveText: 'Yes', 30 | }, 31 | 32 | [TYPES.DOWNLOAD_PODCAST]: { 33 | title: 'Download Podcast', 34 | description: 35 | 'Are you sure you want to Download this podcast? It can take a while.', 36 | positiveText: 'Yes', 37 | }, 38 | 39 | [TYPES.REMOVE_PODCAST_FROM_PLAYLIST]: { 40 | title: 'Remove Podcast', 41 | description: 42 | 'Are you sure you want to remove this Podcast from this Playlist?', 43 | positiveText: 'Yes', 44 | }, 45 | 46 | [TYPES.ADD_REPEATED_PODCAS_PLAYLIST]: { 47 | title: 'Duplicated Podcast', 48 | description: 49 | 'This Podcast has already been added to this Playlist. Do you want add it again?', 50 | positiveText: 'Yes', 51 | }, 52 | 53 | [TYPES.ADD_UNDOWNLOADED_PODCAST_PLAYLIST_AVAILABLE_OFFLINE]: { 54 | title: 'Availability Offline', 55 | description: 56 | 'This Playlist is Available Offline. When you add this podcast to this playlist, it will be downloaded automatically.', 57 | positiveText: 'OK', 58 | }, 59 | 60 | [TYPES.REMOVE_PLAYLIST]: { 61 | title: 'Remove Playlist', 62 | description: 'Are you sure you want to remove this Playlist?', 63 | positiveText: 'Yes', 64 | }, 65 | }; 66 | 67 | export const CustomAlert = (type: string, action: Function): void => { 68 | const { title, description, positiveText } = configs[type]; 69 | 70 | Alert.alert( 71 | title, 72 | description, 73 | [ 74 | { 75 | text: 'Cancel', 76 | style: 'cancel', 77 | }, 78 | { text: positiveText, onPress: () => action() }, 79 | ], 80 | { cancelable: false }, 81 | ); 82 | }; 83 | --------------------------------------------------------------------------------