Integrate Mobile landing in the Redux app

This commit is contained in:
Ilya Daynatovich 2016-12-27 18:03:30 +02:00 committed by Lyubomir Marinov
parent 23ef0c8d9d
commit 8248b14555
9 changed files with 130 additions and 48 deletions

View File

@ -21,3 +21,14 @@ export const APP_WILL_MOUNT = Symbol('APP_WILL_MOUNT');
* } * }
*/ */
export const APP_WILL_UNMOUNT = Symbol('APP_WILL_UNMOUNT'); 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');

View File

@ -1,8 +1,16 @@
import { setRoom } from '../base/conference'; import { setRoom } from '../base/conference';
import { getDomain, setDomain } from '../base/connection'; import { getDomain, setDomain } from '../base/connection';
import { loadConfig, setConfig } from '../base/lib-jitsi-meet'; 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 { import {
_getRoomAndDomainFromUrlString, _getRoomAndDomainFromUrlString,
_getRouteToRender, _getRouteToRender,
@ -31,7 +39,8 @@ export function appInit() {
*/ */
export function appNavigate(urlOrRoom) { export function appNavigate(urlOrRoom) {
return (dispatch, getState) => { return (dispatch, getState) => {
const oldDomain = getDomain(getState()); const state = getState();
const oldDomain = getDomain(state);
const { domain, room } = _getRoomAndDomainFromUrlString(urlOrRoom); 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 // current conference and start a new one with the new room name or
// domain. // 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 // If both domain and room vars became undefined, that means we're
// actually dealing with just room name and not with URL. // actually dealing with just room name and not with URL.
dispatch( dispatch(
@ -87,7 +98,6 @@ export function appNavigate(urlOrRoom) {
// start to not make app re-render conference page for two times. // start to not make app re-render conference page for two times.
dispatch(setRoom(room)); dispatch(setRoom(room));
dispatch(setConfig(config)); 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). * Signals that a specific App will unmount (in the terms of React).
* *
@ -152,9 +193,13 @@ function _setRoomAndNavigate(newRoom) {
dispatch(setRoom(newRoom)); dispatch(setRoom(newRoom));
const state = getState(); 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); _navigate(state);
} }
}; };

View File

@ -1,4 +1,4 @@
import { appInit } from '../actions'; import { appInit, detectPlatform } from '../actions';
import { AbstractApp } from './AbstractApp'; import { AbstractApp } from './AbstractApp';
/** /**
@ -22,6 +22,7 @@ export class App extends AbstractApp {
componentWillMount(...args) { componentWillMount(...args) {
super.componentWillMount(...args); super.componentWillMount(...args);
this.props.store.dispatch(detectPlatform());
this.props.store.dispatch(appInit()); this.props.store.dispatch(appInit());
} }

View File

@ -7,10 +7,45 @@ import settings from '../../../modules/settings/Settings';
import getTokenData from '../../../modules/tokendata/TokenData'; import getTokenData from '../../../modules/tokendata/TokenData';
import JitsiMeetLogStorage from '../../../modules/util/JitsiMeetLogStorage'; 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'); const Logger = require('jitsi-meet-logger');
export * from './functions.native'; 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 * Temporary solution. Later we'll get rid of global APP and set its properties
* in redux store. * in redux store.

View File

@ -1,6 +1,10 @@
import { ReducerRegistry } from '../base/redux'; 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) => { ReducerRegistry.register('features/app', (state = {}, action) => {
switch (action.type) { switch (action.type) {
@ -28,6 +32,13 @@ ReducerRegistry.register('features/app', (state = {}, action) => {
}; };
} }
break; break;
case APP_SET_PLATFORM:
return {
...state,
platform: action.platform
};
} }
return state; return state;

View File

@ -21,23 +21,3 @@ export function detectIOS() {
return false; 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}`;
}, '');
}

View File

@ -5,7 +5,6 @@ import HttpConfigFetch from '../../../modules/config/HttpConfigFetch';
import ConferenceUrl from '../../../modules/URL/ConferenceUrl'; import ConferenceUrl from '../../../modules/URL/ConferenceUrl';
import { RouteRegistry } from '../base/navigator'; import { RouteRegistry } from '../base/navigator';
import { detectIOS, detectAndroid, serializeQuery } from '../base/util';
import { Conference } from './components'; import { Conference } from './components';
const logger = require('jitsi-meet-logger').getLogger(__filename); const logger = require('jitsi-meet-logger').getLogger(__filename);

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Link } from 'react-router';
import { landingIsShown } from '../actions'; import { landingIsShown } from '../actions';
const links = { const links = {
@ -14,6 +14,7 @@ const links = {
* @class Landing * @class Landing
*/ */
class Landing extends Component { class Landing extends Component {
/** /**
* React lifecycle method triggered after * React lifecycle method triggered after
* component is mount. * component is mount.
@ -26,7 +27,8 @@ class Landing extends Component {
static propTypes = { static propTypes = {
dispatch: React.PropTypes.func, 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} * @returns {void}
*/ */
componentWillMount() { componentWillMount() {
const { query } = this.props.location; const { room } = this.props;
const { conferenceName, platform } = query;
let btnText; let btnText;
let link = '/'; let link = '/';
if (conferenceName) { if (room) {
btnText = 'Join the conversation'; btnText = 'Join the conversation';
link += conferenceName; link += room;
} else { } else {
btnText = 'Start a conference'; btnText = 'Start a conference';
} }
this.setState({ this.setState({
btnText, btnText,
link, link
platform
}); });
} }
@ -61,7 +61,8 @@ class Landing extends Component {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const { btnText, link, platform } = this.state; const { platform } = this.props;
const { btnText, link } = this.state;
const primaryButtonClasses = 'landing__button landing__button_primary'; const primaryButtonClasses = 'landing__button landing__button_primary';
return ( 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);

View File

@ -3,13 +3,5 @@ import { Landing } from './components';
RouteRegistry.register({ RouteRegistry.register({
component: Landing, component: Landing,
path: '/mobile-app', path: '/mobile-app'
onEnter: store => () => {
const state = store.getState();
const { landingIsShown } = state;
if (landingIsShown) {
return;
}
}
}); });