[RN] Prepare to display BlankPage more

For example, while config.js and other files are being loaded before the
navigation to Conference is feasible.
This commit is contained in:
Lyubo Marinov 2017-08-31 23:13:59 -05:00
parent 6545a7a1bb
commit 45a1ae26ca
11 changed files with 133 additions and 187 deletions

View File

@ -46,22 +46,22 @@ function _appNavigateToMandatoryLocation(
.then(() => dispatch(setRoom(newLocation.room))); .then(() => dispatch(setRoom(newLocation.room)));
/** /**
* Notifies that an attempt to load a config(uration) has completed. Due to * Notifies that an attempt to load a configuration has completed. Due to
* the asynchronous native of the loading, the specified <tt>config</tt> may * the asynchronous nature of the loading, the specified <tt>config</tt> may
* or may not be required by the time the notification arrives. * or may not be required by the time the notification arrives.
* *
* @param {string|undefined} err - If the loading has failed, the error * @param {string|undefined} error - If the loading has failed, the error
* detailing the cause of the failure. * detailing the cause of the failure.
* @param {Object|undefined} config - If the loading has succeeded, the * @param {Object|undefined} config - If the loading has succeeded, the
* loaded config(uration). * loaded configuration.
* @returns {void} * @returns {void}
*/ */
function configLoaded(err, config) { function configLoaded(error, config) {
// FIXME Due to the asynchronous native of the loading, the specified // FIXME Due to the asynchronous nature of the loading, the specified
// config may or may not be required by the time the notification // config may or may not be required by the time the notification
// arrives. // arrives.
if (err) { if (error) {
// XXX The failure could be, for example, because of a // XXX The failure could be, for example, because of a
// certificate-related error. In which case the connection will // certificate-related error. In which case the connection will
// fail later in Strophe anyway even if we use the default // fail later in Strophe anyway even if we use the default

View File

@ -13,6 +13,7 @@ import {
import { RouteRegistry } from '../../base/react'; import { RouteRegistry } from '../../base/react';
import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux'; import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
import { toURLString } from '../../base/util'; import { toURLString } from '../../base/util';
import { BlankPage } from '../../welcome';
import { appNavigate, appWillMount, appWillUnmount } from '../actions'; import { appNavigate, appWillMount, appWillUnmount } from '../actions';
@ -194,13 +195,14 @@ export class AbstractApp extends Component {
*/ */
render() { render() {
const { route } = this.state; const { route } = this.state;
const component = (route && route.component) || BlankPage;
if (route) { if (component) {
return ( return (
<I18nextProvider i18n = { i18next }> <I18nextProvider i18n = { i18next }>
<Provider store = { this._getStore() }> <Provider store = { this._getStore() }>
{ {
this._createElement(route.component) this._createElement(component)
} }
</Provider> </Provider>
</I18nextProvider> </I18nextProvider>
@ -372,15 +374,20 @@ export class AbstractApp extends Component {
// onEnter. During the removal of react-router, modifications were // onEnter. During the removal of react-router, modifications were
// minimized by preserving the onEnter interface: // minimized by preserving the onEnter interface:
// (1) Router would provide its nextState to the Route's onEnter. As the // (1) Router would provide its nextState to the Route's onEnter. As the
// role of Router is now this AbstractApp, provide its nextState. // role of Router is now this AbstractApp and we use redux, provide the
// redux store instead.
// (2) A replace function would be provided to the Route in case it // (2) A replace function would be provided to the Route in case it
// chose to redirect to another path. // chose to redirect to another path.
route && this._onRouteEnter(route, nextState, pathname => { route && this._onRouteEnter(route, this._getStore(), pathname => {
if (pathname) {
this._openURL(pathname); this._openURL(pathname);
// Do not proceed with the route because it chose to redirect to // Do not proceed with the route because it chose to redirect to
// another path. // another path.
nextState = undefined; nextState = undefined;
} else {
nextState.route = undefined;
}
}); });
// XXX React's setState is asynchronous which means that the value of // XXX React's setState is asynchronous which means that the value of

View File

@ -4,7 +4,7 @@ import { isRoomValid } from '../base/conference';
import { RouteRegistry } from '../base/react'; import { RouteRegistry } from '../base/react';
import { toState } from '../base/redux'; import { toState } from '../base/redux';
import { Conference } from '../conference'; import { Conference } from '../conference';
import { Entryway } from '../welcome'; import { WelcomePage } from '../welcome';
/** /**
* Determines which route is to be rendered in order to depict a specific Redux * Determines which route is to be rendered in order to depict a specific Redux

View File

@ -1,43 +0,0 @@
import PropTypes from 'prop-types';
import { Component } from 'react';
import { destroyLocalTracks } from '../../base/tracks';
/**
* A React <tt>Component</tt> which represents a blank page. Destroys the local
* tracks upon mounting since no media is desired when this component utilized.
* Renders nothing.
*/
export default class AbstractBlankPage extends Component {
/**
* <tt>AbstractBlankPage</tt> React <tt>Component</tt>'s prop types.
*
* @static
*/
static propTypes = {
dispatch: PropTypes.func
};
/**
* Destroys the local tracks (if any) since no media is desired when this
* component is rendered.
*
* @inheritdoc
* @returns {void}
*/
componentWillMount() {
this.props.dispatch(destroyLocalTracks());
}
/**
* Implements React's {@link Component#render()}. Returns null because the
* purpose of this component is to destroy the local tracks and render
* nothing.
*
* @inheritdoc
* @returns {null}
*/
render() {
return null;
}
}

View File

@ -1,39 +1,58 @@
/* @flow */ /* @flow */
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React, { Component } from 'react';
import { ActivityIndicator, View } from 'react-native'; import { ActivityIndicator, View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import AbstractBlankPage from './AbstractBlankPage'; import { destroyLocalTracks } from '../../base/tracks';
import { isWelcomePageAppEnabled } from '../functions';
import styles from './styles'; import styles from './styles';
/** /**
* Mobile/React Native implementation of <tt>AbstractBlankPage</tt>. Since this * The React <tt>Component</tt> displayed by <tt>AbstractApp</tt> when it has no
* is the <tt>Component</tt> rendered when there is no <tt>WelcomePage</tt>, * <tt>Route</tt> to render. Renders a progress indicator when there are ongoing
* it will show a progress indicator when there are ongoing network requests * network requests.
* (notably, the loading of config.js before joining a conference). The use case
* which prompted the introduction of this <tt>Component</tt> is mobile where
* SDK users probably disable the <tt>WelcomePage</tt>.
*/ */
class BlankPage extends AbstractBlankPage { class BlankPage extends Component {
/** /**
* <tt>BlankPage</tt> React <tt>Component</tt>'s prop types. * <tt>BlankPage</tt> React <tt>Component</tt>'s prop types.
* *
* @static * @static
*/ */
static propTypes = { static propTypes = {
...AbstractBlankPage.propTypes,
/** /**
* Indicates whether there is network activity i.e. ongoing network * Indicates whether there is network activity i.e. ongoing network
* requests. * requests.
* *
* @private * @private
*/ */
_networkActivity: PropTypes.bool _networkActivity: PropTypes.bool,
/**
* The indicator which determines whether <tt>WelcomePage</tt> is (to
* be) rendered.
*
* @private
*/
_welcomePageEnabled: PropTypes.bool,
dispatch: PropTypes.func
}; };
/**
* Destroys the local tracks (if any) since no media is desired when this
* component is rendered.
*
* @inheritdoc
* @returns {void}
*/
componentWillMount() {
this.props._welcomePageEnabled
|| this.props.dispatch(destroyLocalTracks());
}
/** /**
* Implements React's {@link Component#render()}. * Implements React's {@link Component#render()}.
* *
@ -59,7 +78,8 @@ class BlankPage extends AbstractBlankPage {
* @param {Object} state - The redux state. * @param {Object} state - The redux state.
* @private * @private
* @returns {{ * @returns {{
* networkActivity: boolean * _networkActivity: boolean,
* _welcomePageEnabled: boolean
* }} * }}
*/ */
function _mapStateToProps(state) { function _mapStateToProps(state) {
@ -67,7 +87,8 @@ function _mapStateToProps(state) {
return { return {
_networkActivity: _networkActivity:
Boolean(requests && (requests.length || requests.size)) Boolean(requests && (requests.length || requests.size)),
_welcomePageEnabled: isWelcomePageAppEnabled(state)
}; };
} }

View File

@ -1,10 +1 @@
import AbstractBlankPage from './AbstractBlankPage'; export default undefined;
/**
* Default <tt>BlankPage</tt> implementation for Web/React. It's not currently
* in use but it's here for symmetry with mobile/React Native should we choose
* to take advantage of it in the future. Destroys the local tracks and renders
* nothing.
*/
export default class BlankPage extends AbstractBlankPage {
}

View File

@ -1,74 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import BlankPage from './BlankPage';
import WelcomePage from './WelcomePage';
/**
* A React <tt>Component</tt> which is rendered when there is no (valid) room
* (name) i.e. it is the opposite of <tt>Conference</tt>. Generally and
* historically, it is <tt>WelcomePage</tt>. However, Jitsi Meet SDK for Android
* and iOS allows the use of the (JavaScript) app without <tt>WelcomePage</tt>
* and it needs to display something between conferences.
*/
class Entryway extends Component {
/**
* <tt>Entryway</tt>'s React <tt>Component</tt> prop types.
*/
static propTypes = {
/**
* The indicator which determines whether <tt>WelcomePage</tt> is (to
* be) rendered.
*
* @private
*/
_welcomePageEnabled: PropTypes.bool
};
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
this.props._welcomePageEnabled ? <WelcomePage /> : <BlankPage />
);
}
}
/**
* Maps (parts of) the redux state to the associated Entryway's props.
*
* @param {Object} state - The redux state.
* @private
* @returns {{
* _welcomePageEnabled: boolean
* }}
*/
function _mapStateToProps(state) {
let welcomePageEnabled;
if (navigator.product === 'ReactNative') {
// We introduced the welcomePageEnabled prop on App in Jitsi Meet SDK
// for Android and iOS. There isn't a strong reason not to introduce it
// on Web but there're a few considerations to be taken before I go
// there among which:
// - Enabling/disabling the Welcome page on Web historically
// automatically redirects to a random room and that does not make sense
// on mobile (right now).
const { app } = state['features/app'];
welcomePageEnabled = Boolean(app && app.props.welcomePageEnabled);
} else {
welcomePageEnabled = true;
}
return {
_welcomePageEnabled: welcomePageEnabled
};
}
export default connect(_mapStateToProps)(Entryway);

View File

@ -1,3 +1,2 @@
export { default as BlankPage } from './BlankPage'; export { default as BlankPage } from './BlankPage';
export { default as Entryway } from './Entryway';
export { default as WelcomePage } from './WelcomePage'; export { default as WelcomePage } from './WelcomePage';

View File

@ -0,0 +1,57 @@
/* @flow */
import { toState } from '../base/redux';
declare var APP: Object;
declare var config: Object;
export * from './roomnameGenerator';
/**
* Determines whether the <tt>WelcomePage</tt> is enabled by the app itself
* (e.g. programmatically via the Jitsi Meet SDK for Android and iOS). Not to be
* confused with {@link isWelcomePageUserEnabled}.
*
* @param {Object|Function} stateOrGetState - The redux state or
* {@link getState} function.
* @returns {boolean} If the <tt>WelcomePage</tt> is enabled by the app, then
* <tt>true</tt>; otherwise, <tt>false</tt>.
*/
export function isWelcomePageAppEnabled(stateOrGetState: Object | Function) {
let b;
if (navigator.product === 'ReactNative') {
// We introduced the welcomePageEnabled prop on App in Jitsi Meet SDK
// for Android and iOS. There isn't a strong reason not to introduce it
// on Web but there're a few considerations to be taken before I go
// there among which:
// - Enabling/disabling the Welcome page on Web historically
// automatically redirects to a random room and that does not make sense
// on mobile (right now).
const { app } = toState(stateOrGetState)['features/app'];
b = Boolean(app && app.props.welcomePageEnabled);
} else {
b = true;
}
return b;
}
/**
* Determines whether the <tt>WelcomePage</tt> is enabled by the user either
* herself or through her deployment config(uration). Not to be confused with
* {@link isWelcomePageAppEnabled}.
*
* @param {Object|Function} stateOrGetState - The redux state or
* {@link getState} function.
* @returns {boolean} If the <tt>WelcomePage</tt> is enabled by the user, then
* <tt>true</tt>; otherwise, <tt>false</tt>.
*/
export function isWelcomePageUserEnabled(stateOrGetState: Object | Function) {
return (
typeof APP === 'undefined'
? true
: toState(stateOrGetState)['features/base/config'].enableWelcomePage
&& APP.settings.isWelcomePageEnabled());
}

View File

@ -1,3 +1,4 @@
import './route'; import './route';
export * from './components'; export * from './components';
export * from './functions';

View File

@ -2,47 +2,34 @@
import { RouteRegistry } from '../base/react'; import { RouteRegistry } from '../base/react';
import { Entryway } from './components'; import { WelcomePage } from './components';
import { generateRoomWithoutSeparator } from './roomnameGenerator'; import {
generateRoomWithoutSeparator,
declare var APP: Object; isWelcomePageAppEnabled,
declare var config: Object; isWelcomePageUserEnabled
} from './functions';
/** /**
* Register route for Entryway. * Register route for <tt>WelcomePage</tt>.
*/ */
RouteRegistry.register({ RouteRegistry.register({
component: Entryway, component: WelcomePage,
onEnter, onEnter,
path: '/' path: '/'
}); });
/** /**
* If the Welcome page/screen is disabled, generates a (random) room (name) so * Skips the <tt>WelcomePage</tt> if it is disabled (by the app or the user).
* that the Welcome page/screen is skipped and the Conference page/screen is
* presented instead.
* *
* @param {Object} nextState - The next Router state. * @param {Object} store - The redux store.
* @param {Function} replace - The function to redirect to another path. * @param {Function} replace - The function to redirect to another path.
* @returns {void} * @returns {void}
*/ */
function onEnter(nextState, replace) { function onEnter({ getState }, replace) {
// The disabling of the Welcome page by redirecting to a random room name is if (isWelcomePageAppEnabled(getState)) {
// a feature (1) we have on Web/React and (2) we do not want on mobile/React isWelcomePageUserEnabled(getState)
// Native (at the time of this writing). || replace(`/${generateRoomWithoutSeparator()}`);
if (typeof APP === 'object' } else {
replace(undefined);
// TODO Technically, there is features/base/config now so it is
// preferable to read config(uration) values from there and not rely
// on a global variable. However, the redux store is not available
// here at the time of this writing. Given the current (1) Web
// exclusivity of the feature and (2) the reliance on other global
// variables (e.g. APP), go with the global variable for now in
// order to minimize the effort involved.
&& !(config.enableWelcomePage
&& APP.settings.isWelcomePageEnabled())) {
const room = generateRoomWithoutSeparator();
replace(`/${room}`);
} }
} }