import React from 'react'; import AboutPage from '../page/about'; import DiscoverPage from '../page/discover'; import DownloadsPage from '../page/downloads'; import FilePage from '../page/file'; import FirstRunScreen from '../page/firstRun'; import RewardsPage from '../page/rewards'; import TrendingPage from '../page/trending'; import SearchPage from '../page/search'; import SettingsPage from '../page/settings'; import SplashScreen from '../page/splash'; import SubscriptionsPage from '../page/subscriptions'; import TransactionHistoryPage from '../page/transactionHistory'; import WalletPage from '../page/wallet'; import SearchInput from '../component/searchInput'; import { createDrawerNavigator, createStackNavigator, NavigationActions } from 'react-navigation'; import { addListener, reduxifyNavigator, createReactNavigationReduxMiddleware, } from 'react-navigation-redux-helpers'; import { connect } from 'react-redux'; import { AppState, AsyncStorage, BackHandler, Linking, NativeModules, TextInput, ToastAndroid } from 'react-native'; import { doPopDrawerStack } from 'redux/actions/drawer'; import { doDeleteCompleteBlobs } from 'redux/actions/file'; import { selectDrawerStack } from 'redux/selectors/drawer'; import { SETTINGS, doDismissToast, doToast, selectToast } from 'lbry-redux'; import { doUserEmailVerify, doUserEmailVerifyFailure, selectEmailToVerify, selectEmailVerifyIsPending, selectEmailVerifyErrorMessage, selectUser } from 'lbryinc'; import { makeSelectClientSetting } from 'redux/selectors/settings'; import { decode as atob } from 'base-64'; import { dispatchNavigateBack, dispatchNavigateToUri } from 'utils/helper'; import Colors from 'styles/colors'; import Constants from 'constants'; import Icon from 'react-native-vector-icons/FontAwesome5'; import NavigationButton from 'component/navigationButton'; import discoverStyle from 'styles/discover'; import searchStyle from 'styles/search'; import SearchRightHeaderIcon from 'component/searchRightHeaderIcon'; const menuNavigationButton = (navigation) => navigation.openDrawer() } /> const discoverStack = createStackNavigator({ Discover: { screen: DiscoverPage, navigationOptions: ({ navigation }) => ({ title: 'Discover', headerLeft: menuNavigationButton(navigation), headerTitleStyle: discoverStyle.titleText }) }, File: { screen: FilePage, navigationOptions: ({ navigation }) => ({ header: null }) }, Search: { screen: SearchPage, navigationOptions: ({ navigation }) => ({ headerTitleStyle: discoverStyle.titleText }) } }, { headerMode: 'screen' }); discoverStack.navigationOptions = ({ navigation }) => { let drawerLockMode = 'unlocked'; if (navigation.state.index > 0) { drawerLockMode = 'locked-closed'; } return { drawerLockMode }; }; const trendingStack = createStackNavigator({ Trending: { screen: TrendingPage, navigationOptions: ({ navigation }) => ({ title: 'Trending', headerLeft: menuNavigationButton(navigation), headerTitleStyle: discoverStyle.titleText }) } }); const myLbryStack = createStackNavigator({ Downloads: { screen: DownloadsPage, navigationOptions: ({ navigation }) => ({ title: 'My LBRY', headerLeft: menuNavigationButton(navigation), headerTitleStyle: discoverStyle.titleText }) } }); const mySubscriptionsStack = createStackNavigator({ Subscriptions: { screen: SubscriptionsPage, navigationOptions: ({ navigation }) => ({ title: 'My Subscriptions', headerLeft: menuNavigationButton(navigation), headerTitleStyle: discoverStyle.titleText }) } }); const rewardsStack = createStackNavigator({ Rewards: { screen: RewardsPage, navigationOptions: ({ navigation }) => ({ title: 'Rewards', headerLeft: menuNavigationButton(navigation), headerTitleStyle: discoverStyle.titleText }) } }); const walletStack = createStackNavigator({ Wallet: { screen: WalletPage, navigationOptions: ({ navigation }) => ({ title: 'Wallet', headerLeft: menuNavigationButton(navigation), headerTitleStyle: discoverStyle.titleText }) }, TransactionHistory: { screen: TransactionHistoryPage, navigationOptions: { title: 'Transaction History', drawerLockMode: 'locked-closed', headerTitleStyle: discoverStyle.titleText } } }, { headerMode: 'screen' }); const drawer = createDrawerNavigator({ DiscoverStack: { screen: discoverStack, navigationOptions: { title: 'Discover', drawerIcon: ({ tintColor }) => }}, TrendingStack: { screen: trendingStack, navigationOptions: { title: 'Trending', drawerIcon: ({ tintColor }) => }}, MySubscriptionsStack: { screen: mySubscriptionsStack, navigationOptions: { title: 'My Subscriptions', drawerIcon: ({ tintColor }) => }}, MyLBRYStack: { screen: myLbryStack, navigationOptions: { title: 'My LBRY', drawerIcon: ({ tintColor }) => }}, Rewards: { screen: rewardsStack, navigationOptions: { drawerIcon: ({ tintColor }) => }}, WalletStack: { screen: walletStack, navigationOptions: { title: 'Wallet', drawerIcon: ({ tintColor }) => }}, Settings: { screen: SettingsPage, navigationOptions: { drawerLockMode: 'locked-closed', drawerIcon: ({ tintColor }) => }}, About: { screen: AboutPage, navigationOptions: { drawerLockMode: 'locked-closed', drawerIcon: ({ tintColor }) => }} }, { drawerWidth: 300, headerMode: 'none', contentOptions: { activeTintColor: Colors.LbryGreen, labelStyle: discoverStyle.menuText } }); export const AppNavigator = new createStackNavigator({ FirstRun: { screen: FirstRunScreen, navigationOptions: { drawerLockMode: 'locked-closed' } }, Splash: { screen: SplashScreen, navigationOptions: { drawerLockMode: 'locked-closed' } }, Main: { screen: drawer } }, { headerMode: 'none' }); export const reactNavigationMiddleware = createReactNavigationReduxMiddleware( "root", state => state.nav, ); const App = reduxifyNavigator(AppNavigator, "root"); const appMapStateToProps = (state) => ({ state: state.nav, }); const ReduxAppNavigator = connect(appMapStateToProps)(App); class AppWithNavigationState extends React.Component { static supportedDisplayTypes = ['toast']; constructor() { super(); this.state = { emailVerifyDone: false }; } componentWillMount() { AppState.addEventListener('change', this._handleAppStateChange); BackHandler.addEventListener('hardwareBackPress', function() { const { dispatch, nav, drawerStack, popDrawerStack } = this.props; // There should be a better way to check this if (nav.routes.length > 0) { if (nav.routes[0].routeName === 'Main') { const mainRoute = nav.routes[0]; if (mainRoute.index > 0 || mainRoute.routes[0].index > 0 /* Discover stack index */ || mainRoute.routes[4].index > 0 /* Wallet stack index */ || mainRoute.index >= 5 /* Settings and About screens */) { dispatchNavigateBack(dispatch, nav, drawerStack, doPopDrawerStack); return true; } } } return false; }.bind(this)); } componentDidMount() { Linking.addEventListener('url', this._handleUrl); } componentWillUnmount() { AppState.removeEventListener('change', this._handleAppStateChange); BackHandler.removeEventListener('hardwareBackPress'); Linking.removeEventListener('url', this._handleUrl); } componentWillUpdate(nextProps) { const { dispatch } = this.props; const { toast, emailToVerify, emailVerifyPending, emailVerifyErrorMessage, user } = nextProps; if (toast) { const { message } = toast; let currentDisplayType; if (!currentDisplayType && message) { // default to toast if no display type set and there is a message specified currentDisplayType = 'toast'; } if ('toast' === currentDisplayType) { ToastAndroid.show(message, ToastAndroid.LONG); } dispatch(doDismissToast()); } if (user && !emailVerifyPending && !this.state.emailVerifyDone && (emailToVerify || emailVerifyErrorMessage)) { AsyncStorage.getItem(Constants.KEY_SHOULD_VERIFY_EMAIL).then(shouldVerify => { if ('true' === shouldVerify) { this.setState({ emailVerifyDone: true }); const message = emailVerifyErrorMessage ? String(emailVerifyErrorMessage) : 'Your email address was successfully verified.'; if (!emailVerifyErrorMessage) { AsyncStorage.removeItem(Constants.KEY_FIRST_RUN_EMAIL); } AsyncStorage.removeItem(Constants.KEY_SHOULD_VERIFY_EMAIL); dispatch(doToast({ message })); } }); } } _handleAppStateChange = (nextAppState) => { const { backgroundPlayEnabled, dispatch } = this.props; // Check if the app was suspended if (AppState.currentState && AppState.currentState.match(/inactive|background/)) { AsyncStorage.getItem('firstLaunchTime').then(start => { if (start !== null && !isNaN(parseInt(start, 10))) { // App suspended during first launch? // If so, this needs to be included as a property when tracking AsyncStorage.setItem('firstLaunchSuspended', 'true'); } // Background media if (backgroundPlayEnabled && NativeModules.BackgroundMedia && window.currentMediaInfo) { const { title, channel, uri } = window.currentMediaInfo; NativeModules.BackgroundMedia.showPlaybackNotification(title, channel, uri, false); } }); } if (AppState.currentState && AppState.currentState.match(/active/)) { // Cleanup blobs for completed files upon app resume to save space dispatch(doDeleteCompleteBlobs()); if (backgroundPlayEnabled || NativeModules.BackgroundMedia) { NativeModules.BackgroundMedia.hidePlaybackNotification(); } } } _handleUrl = (evt) => { const { dispatch, nav } = this.props; if (evt.url) { if (evt.url.startsWith('lbry://?verify=')) { this.setState({ emailVerifyDone: false }); let verification = {}; try { verification = JSON.parse(atob(evt.url.substring(15))); } catch (error) { console.log(error); } if (verification.token && verification.recaptcha) { AsyncStorage.setItem(Constants.KEY_SHOULD_VERIFY_EMAIL, 'true'); try { dispatch(doUserEmailVerify(verification.token, verification.recaptcha)); } catch (error) { const message = 'Invalid Verification Token'; dispatch(doUserEmailVerifyFailure(message)); dispatch(doToast({ message })); } } else { dispatch(doToast({ message: 'Invalid Verification URI', })); } } else { dispatchNavigateToUri(dispatch, nav, evt.url); } } } render() { return ; } } const mapStateToProps = state => ({ backgroundPlayEnabled: makeSelectClientSetting(SETTINGS.BACKGROUND_PLAY_ENABLED)(state), keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state), nav: state.nav, toast: selectToast(state), drawerStack: selectDrawerStack(state), emailToVerify: selectEmailToVerify(state), emailVerifyPending: selectEmailVerifyIsPending(state), emailVerifyErrorMessage: selectEmailVerifyErrorMessage(state), showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state), user: selectUser(state) }); export default connect(mapStateToProps)(AppWithNavigationState);