From 8248b1455584400d27e7f3845e1523d75de9b986 Mon Sep 17 00:00:00 2001 From: Ilya Daynatovich Date: Tue, 27 Dec 2016 18:03:30 +0200 Subject: [PATCH] Integrate Mobile landing in the Redux app --- react/features/app/actionTypes.js | 11 ++++ react/features/app/actions.js | 57 +++++++++++++++++--- react/features/app/components/App.web.js | 3 +- react/features/app/functions.web.js | 35 ++++++++++++ react/features/app/reducer.js | 13 ++++- react/features/base/util/detectDevices.js | 20 ------- react/features/conference/route.js | 1 - react/features/landing/components/Landing.js | 28 ++++++---- react/features/landing/route.js | 10 +--- 9 files changed, 130 insertions(+), 48 deletions(-) diff --git a/react/features/app/actionTypes.js b/react/features/app/actionTypes.js index 7e5f2da8b..d3670e525 100644 --- a/react/features/app/actionTypes.js +++ b/react/features/app/actionTypes.js @@ -21,3 +21,14 @@ export const APP_WILL_MOUNT = Symbol('APP_WILL_MOUNT'); * } */ export const APP_WILL_UNMOUNT = Symbol('APP_WILL_UNMOUNT'); + +/** + * The type of this action sets the platform of user agent + * in order to decide to show the landing or not. + * + * { + * type: APP_SET_PLATFORM, + * platform: String + * } + */ +export const APP_SET_PLATFORM = Symbol('APP_SET_PLATFORM'); diff --git a/react/features/app/actions.js b/react/features/app/actions.js index efbbe6cad..d1268e865 100644 --- a/react/features/app/actions.js +++ b/react/features/app/actions.js @@ -1,8 +1,16 @@ import { setRoom } from '../base/conference'; import { getDomain, setDomain } from '../base/connection'; import { loadConfig, setConfig } from '../base/lib-jitsi-meet'; +import { + detectAndroid, + detectIOS +} from '../base/util'; -import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes'; +import { + APP_WILL_MOUNT, + APP_WILL_UNMOUNT, + APP_SET_PLATFORM +} from './actionTypes'; import { _getRoomAndDomainFromUrlString, _getRouteToRender, @@ -31,7 +39,8 @@ export function appInit() { */ export function appNavigate(urlOrRoom) { return (dispatch, getState) => { - const oldDomain = getDomain(getState()); + const state = getState(); + const oldDomain = getDomain(state); const { domain, room } = _getRoomAndDomainFromUrlString(urlOrRoom); @@ -40,7 +49,9 @@ export function appNavigate(urlOrRoom) { // current conference and start a new one with the new room name or // domain. - if (typeof domain === 'undefined' || oldDomain === domain) { + if (room === 'mobile-app') { + return; + } else if (typeof domain === 'undefined' || oldDomain === domain) { // If both domain and room vars became undefined, that means we're // actually dealing with just room name and not with URL. dispatch( @@ -87,7 +98,6 @@ export function appNavigate(urlOrRoom) { // start to not make app re-render conference page for two times. dispatch(setRoom(room)); dispatch(setConfig(config)); - _navigate(getState()); } }; } @@ -108,6 +118,37 @@ export function appWillMount(app) { }; } +/** + * Detects the platform of user agent and + * signals that platform detected. + * + * @returns {Function} + */ +export function detectPlatform() { + return dispatch => { + if (detectAndroid()) { + dispatch(_setPlatform('android')); + } else if (detectIOS()) { + dispatch(_setPlatform('ios')); + } + }; +} + +/** + * Signals that user agent platform is mobile and it has + * been already detected. + * + * @param {string} platform - Mobile user agent platform. + * @returns {{type, platform: string}} + * @private + */ +export function _setPlatform(platform) { + return { + type: APP_SET_PLATFORM, + platform + }; +} + /** * Signals that a specific App will unmount (in the terms of React). * @@ -152,9 +193,13 @@ function _setRoomAndNavigate(newRoom) { dispatch(setRoom(newRoom)); const state = getState(); - const room = state['features/base/conference'].room; + const { platform } = state['features/app']; + const { room } = state['features/base/conference']; + const { landingIsShown } = state['features/landing']; - if (room !== oldRoom) { + // If user agent is mobile browser and landing wasn't shown we + // should recheck which component to render. + if ((platform && !landingIsShown) || room !== oldRoom) { _navigate(state); } }; diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js index c75f6afe0..eeb9ee6ea 100644 --- a/react/features/app/components/App.web.js +++ b/react/features/app/components/App.web.js @@ -1,4 +1,4 @@ -import { appInit } from '../actions'; +import { appInit, detectPlatform } from '../actions'; import { AbstractApp } from './AbstractApp'; /** @@ -22,6 +22,7 @@ export class App extends AbstractApp { componentWillMount(...args) { super.componentWillMount(...args); + this.props.store.dispatch(detectPlatform()); this.props.store.dispatch(appInit()); } diff --git a/react/features/app/functions.web.js b/react/features/app/functions.web.js index 22d161843..1ee98fabd 100644 --- a/react/features/app/functions.web.js +++ b/react/features/app/functions.web.js @@ -7,10 +7,45 @@ import settings from '../../../modules/settings/Settings'; import getTokenData from '../../../modules/tokendata/TokenData'; import JitsiMeetLogStorage from '../../../modules/util/JitsiMeetLogStorage'; +import { detectIOS, detectAndroid } from '../base/util'; + +// XXX We should import landing feature here in order to update router registry. +import { Landing } from '../landing'; +import { Conference } from '../conference'; +import { WelcomePage } from '../welcome'; + const Logger = require('jitsi-meet-logger'); export * from './functions.native'; +/** + * Determines which route is to be rendered in order to depict a specific Redux + * store. + * + * @param {(Object|Function)} stateOrGetState - Redux state or Regux getState() + * method. + * @returns {Route} + */ +export function _getRouteToRender(stateOrGetState) { + const state + = typeof stateOrGetState === 'function' + ? stateOrGetState() + : stateOrGetState; + + const { platform } = state['features/app']; + const { room } = state['features/base/conference']; + const { landingIsShown } = state['features/landing']; + + let component = isRoomValid(room) ? Conference : WelcomePage; + + // If landing was shown there is no need to show it again. + if (platform && !landingIsShown) { + component = detectAndroid() || detectIOS() ? Landing : component; + } + + return RouteRegistry.getRouteByComponent(component); +} + /** * Temporary solution. Later we'll get rid of global APP and set its properties * in redux store. diff --git a/react/features/app/reducer.js b/react/features/app/reducer.js index d6922b8c3..b92e8e9f8 100644 --- a/react/features/app/reducer.js +++ b/react/features/app/reducer.js @@ -1,6 +1,10 @@ import { ReducerRegistry } from '../base/redux'; -import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes'; +import { + APP_WILL_MOUNT, + APP_WILL_UNMOUNT, + APP_SET_PLATFORM +} from './actionTypes'; ReducerRegistry.register('features/app', (state = {}, action) => { switch (action.type) { @@ -28,6 +32,13 @@ ReducerRegistry.register('features/app', (state = {}, action) => { }; } break; + + case APP_SET_PLATFORM: + return { + ...state, + platform: action.platform + }; + } return state; diff --git a/react/features/base/util/detectDevices.js b/react/features/base/util/detectDevices.js index 04fd9da3e..4b203e45c 100644 --- a/react/features/base/util/detectDevices.js +++ b/react/features/base/util/detectDevices.js @@ -21,23 +21,3 @@ export function detectIOS() { return false; } - -/** - * Transforms hash map with parameters to query string. - * - * @param {Object} params - Hash map to be processed into query string. - * @returns {string} - */ -export function serializeQuery(params) { - return Object.keys(params).reduce((str, key, index) => { - const encodedKey = encodeURIComponent(key); - const encodedValue = encodeURIComponent(params[key]); - let separator = '&'; - - if (index === 0) { - separator = '?'; - } - - return `${str}${separator}${encodedKey}=${encodedValue}`; - }, ''); -} diff --git a/react/features/conference/route.js b/react/features/conference/route.js index 7c4a68d8d..da55ce662 100644 --- a/react/features/conference/route.js +++ b/react/features/conference/route.js @@ -5,7 +5,6 @@ import HttpConfigFetch from '../../../modules/config/HttpConfigFetch'; import ConferenceUrl from '../../../modules/URL/ConferenceUrl'; import { RouteRegistry } from '../base/navigator'; -import { detectIOS, detectAndroid, serializeQuery } from '../base/util'; import { Conference } from './components'; const logger = require('jitsi-meet-logger').getLogger(__filename); diff --git a/react/features/landing/components/Landing.js b/react/features/landing/components/Landing.js index 67cedef6c..759ad6011 100644 --- a/react/features/landing/components/Landing.js +++ b/react/features/landing/components/Landing.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; -import { Link } from 'react-router'; import { connect } from 'react-redux'; +import { Link } from 'react-router'; import { landingIsShown } from '../actions'; const links = { @@ -14,6 +14,7 @@ const links = { * @class Landing */ class Landing extends Component { + /** * React lifecycle method triggered after * component is mount. @@ -26,7 +27,8 @@ class Landing extends Component { static propTypes = { dispatch: React.PropTypes.func, - location: React.PropTypes.object + platform: React.PropTypes.string, + room: React.PropTypes.string }; /** @@ -36,22 +38,20 @@ class Landing extends Component { * @returns {void} */ componentWillMount() { - const { query } = this.props.location; - const { conferenceName, platform } = query; + const { room } = this.props; let btnText; let link = '/'; - if (conferenceName) { + if (room) { btnText = 'Join the conversation'; - link += conferenceName; + link += room; } else { btnText = 'Start a conference'; } this.setState({ btnText, - link, - platform + link }); } @@ -61,7 +61,8 @@ class Landing extends Component { * @returns {ReactElement} */ render() { - const { btnText, link, platform } = this.state; + const { platform } = this.props; + const { btnText, link } = this.state; const primaryButtonClasses = 'landing__button landing__button_primary'; return ( @@ -94,4 +95,11 @@ class Landing extends Component { } } -export default connect()(Landing); +const mapStateToProps = state => { + return { + platform: state['features/app'].platform, + room: state['features/base/conference'].room + }; +}; + +export default connect(mapStateToProps)(Landing); diff --git a/react/features/landing/route.js b/react/features/landing/route.js index c0d2f3dfd..da1c4bcce 100644 --- a/react/features/landing/route.js +++ b/react/features/landing/route.js @@ -3,13 +3,5 @@ import { Landing } from './components'; RouteRegistry.register({ component: Landing, - path: '/mobile-app', - onEnter: store => () => { - const state = store.getState(); - const { landingIsShown } = state; - - if (landingIsShown) { - return; - } - } + path: '/mobile-app' });