[RN] LocalVideoTrackUnderlay

Implement a React Component which displays children as an overlay of
local video. The WelcomePage implemented such a component inside of it
among other WelcomePage-specific logic so I split
LocalVideoTrackUnderlay out of it. The new Component is used on the
BlankPage which may be displayed in the future not only when the
WelcomePage is disabled but also when there are long running network
requests, for example.
This commit is contained in:
Lyubo Marinov 2017-09-05 17:45:20 -05:00
parent b304ad5808
commit a7ee632f43
6 changed files with 186 additions and 110 deletions

View File

@ -1,12 +1,10 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import { Component } from 'react';
import { appNavigate } from '../../app'; import { appNavigate } from '../../app';
import { isRoomValid } from '../../base/conference'; import { isRoomValid } from '../../base/conference';
import { VideoTrack } from '../../base/media';
import { getLocalVideoTrack } from '../../base/tracks';
import { generateRoomWithoutSeparator } from '../roomnameGenerator'; import { generateRoomWithoutSeparator } from '../functions';
/** /**
* Base (abstract) class for container component rendering the welcome page. * Base (abstract) class for container component rendering the welcome page.
@ -15,12 +13,11 @@ import { generateRoomWithoutSeparator } from '../roomnameGenerator';
*/ */
export class AbstractWelcomePage extends Component { export class AbstractWelcomePage extends Component {
/** /**
* {@code AbstractWelcomePage} component's property types. * {@code AbstractWelcomePage}'s React {@code Component} prop types.
* *
* @static * @static
*/ */
static propTypes = { static propTypes = {
_localVideoTrack: PropTypes.object,
_room: PropTypes.string, _room: PropTypes.string,
dispatch: PropTypes.func dispatch: PropTypes.func
}; };
@ -159,18 +156,6 @@ export class AbstractWelcomePage extends Component {
this.setState({ room: value }); this.setState({ room: value });
} }
/**
* Renders a local video if any.
*
* @protected
* @returns {(ReactElement|null)}
*/
_renderLocalVideo() {
return (
<VideoTrack videoTrack = { this.props._localVideoTrack } />
);
}
/** /**
* Triggers the generation of a new room name and initiates an animation of * Triggers the generation of a new room name and initiates an animation of
* its changing. * its changing.
@ -195,24 +180,17 @@ export class AbstractWelcomePage extends Component {
} }
/** /**
* Selects local video track from tracks in state, local participant and room * Maps (parts of) the redux state to the React {@code Component} props of
* and maps them to component props. It seems it's not possible to 'connect' * {@code AbstractWelcomePage}.
* base component and then extend from it. So we export this function in order
* to be used in child classes for 'connect'.
* *
* @param {Object} state - Redux state. * @param {Object} state - The redux state.
* @protected * @protected
* @returns {{ * @returns {{
* _localVideoTrack: (Track|undefined),
* _room: string * _room: string
* }} * }}
*/ */
export function _mapStateToProps(state) { export function _mapStateToProps(state) {
const conference = state['features/base/conference'];
const tracks = state['features/base/tracks'];
return { return {
_localVideoTrack: getLocalVideoTrack(tracks), _room: state['features/base/conference'].room
_room: conference.room
}; };
} }

View File

@ -2,12 +2,13 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { ActivityIndicator, View } from 'react-native'; import { ActivityIndicator } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { destroyLocalTracks } from '../../base/tracks'; import { destroyLocalTracks } from '../../base/tracks';
import { isWelcomePageAppEnabled } from '../functions'; import { isWelcomePageAppEnabled } from '../functions';
import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
import styles from './styles'; import styles from './styles';
/** /**
@ -62,11 +63,11 @@ class BlankPage extends Component {
*/ */
render() { render() {
return ( return (
<View style = { styles.blankPage }> <LocalVideoTrackUnderlay style = { styles.blankPage }>
<ActivityIndicator <ActivityIndicator
animating = { this.props._networkActivity } animating = { this.props._networkActivity }
size = { 'large' } /> size = { 'large' } />
</View> </LocalVideoTrackUnderlay>
); );
} }
} }

View File

@ -0,0 +1,117 @@
/* @flow */
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { View } from 'react-native';
import { connect } from 'react-redux';
import { VideoTrack } from '../../base/media';
import { getLocalVideoTrack } from '../../base/tracks';
import styles from './styles';
/**
* Implements a React {@code Component} which underlays the local video track,
* if any, underneath its children.
*/
class LocalVideoTrackUnderlay extends Component {
state: {
/**
* The style of <tt>LocalVideoTrackUnderlay</tt> which is a combination
* of its default style and the consumer-specified style.
*/
style: Object
};
/**
* {@code LocalVideoTrackUnderlay}'s React {@code Component} prop types.
*
* @static
*/
static propTypes = {
_localVideoTrack: PropTypes.object,
children: PropTypes.node,
style: PropTypes.object
};
/**
* Initializes a new {@code LocalVideoTrackUnderlay} instance.
*
* @param {Object} props - The read-only React {@code Component} props with
* which the new instance is to be initialized.
*/
constructor(props) {
super(props);
this.componentWillReceiveProps(props);
}
/**
* Notifies this mounted React {@code Component} that it will receive new
* props. Forks (in Facebook/React speak) the prop {@code style} because its
* value is to be combined with the default style.
*
* @inheritdoc
* @param {Object} nextProps - The read-only React {@code Component} props
* that this instance will receive.
* @returns {void}
*/
componentWillReceiveProps(nextProps) {
// style
const prevStyle = this.props && this.props.style;
const nextStyle = nextProps && nextProps.style;
const assignState = !this.state;
if (prevStyle !== nextStyle || assignState) {
const nextState = {
style: {
...styles.localVideoTrackUnderlay,
...nextStyle
}
};
if (assignState) {
this.state = nextState;
} else {
this.setState(nextState);
}
}
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @override
* @returns {ReactElement}
*/
render() {
return (
<View style = { this.state.style }>
<VideoTrack videoTrack = { this.props._localVideoTrack } />
<View style = { styles.localVideoTrackOverlay }>
{ this.props.children }
</View>
</View>
);
}
}
/**
* Maps (parts of) the redux state to the React {@code Component} props of
* {@code LocalVideoTrackUnderlay}.
*
* @param {Object} state - The redux state.
* @private
* @returns {{
* _localVideoTrack: (Track|undefined)
* }}
*/
function _mapStateToProps(state) {
return {
_localVideoTrack: getLocalVideoTrack(state['features/base/tracks'])
};
}
export default connect(_mapStateToProps)(LocalVideoTrackUnderlay);

View File

@ -9,6 +9,7 @@ import { ColorPalette } from '../../base/styles';
import { createDesiredLocalTracks } from '../../base/tracks'; import { createDesiredLocalTracks } from '../../base/tracks';
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage'; import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
import styles from './styles'; import styles from './styles';
/** /**
@ -55,15 +56,40 @@ class WelcomePage extends AbstractWelcomePage {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const { t } = this.props;
return ( return (
<View style = { styles.container }> <LocalVideoTrackUnderlay style = { styles.welcomePage }>
{ <View style = { styles.roomContainer }>
this._renderLocalVideo() <Text style = { styles.title }>
} { t('welcomepage.roomname') }
{ </Text>
this._renderLocalVideoOverlay() <TextInput
} accessibilityLabel = { 'Input room name.' }
autoCapitalize = 'none'
autoComplete = { false }
autoCorrect = { false }
autoFocus = { false }
onChangeText = { this._onRoomChange }
placeholder = { t('welcomepage.roomnamePlaceHolder') }
style = { styles.textInput }
underlineColorAndroid = 'transparent'
value = { this.state.room } />
<TouchableHighlight
accessibilityLabel = { 'Tap to Join.' }
disabled = { this._isJoinDisabled() }
onPress = { this._onJoin }
style = { styles.button }
underlayColor = { ColorPalette.white }>
<Text style = { styles.buttonText }>
{ t('welcomepage.join') }
</Text>
</TouchableHighlight>
</View> </View>
{
this._renderLegalese()
}
</LocalVideoTrackUnderlay>
); );
} }
@ -97,53 +123,6 @@ class WelcomePage extends AbstractWelcomePage {
</View> </View>
); );
} }
/**
* Renders a View over the local video. The latter is thought of as the
* background (content) of this WelcomePage. The former is thought of as the
* foreground (content) of this WelcomePage such as the room name input, the
* button to initiate joining the specified room, etc.
*
* @private
* @returns {ReactElement}
*/
_renderLocalVideoOverlay() {
const { t } = this.props;
return (
<View style = { styles.localVideoOverlay }>
<View style = { styles.roomContainer }>
<Text style = { styles.title }>
{ t('welcomepage.roomname') }
</Text>
<TextInput
accessibilityLabel = { 'Input room name.' }
autoCapitalize = 'none'
autoComplete = { false }
autoCorrect = { false }
autoFocus = { false }
onChangeText = { this._onRoomChange }
placeholder = { t('welcomepage.roomnamePlaceHolder') }
style = { styles.textInput }
underlineColorAndroid = 'transparent'
value = { this.state.room } />
<TouchableHighlight
accessibilityLabel = { 'Tap to Join.' }
disabled = { this._isJoinDisabled() }
onPress = { this._onJoin }
style = { styles.button }
underlayColor = { ColorPalette.white }>
<Text style = { styles.buttonText }>
{ t('welcomepage.join') }
</Text>
</TouchableHighlight>
</View>
{
this._renderLegalese()
}
</View>
);
}
} }
export default translate(connect(_mapStateToProps)(WelcomePage)); export default translate(connect(_mapStateToProps)(WelcomePage));

View File

@ -16,14 +16,9 @@ const TEXT_COLOR = ColorPalette.white;
*/ */
export default createStyleSheet({ export default createStyleSheet({
/** /**
* The style of <tt>BlankPage</tt>. * The style of the top-level container of <tt>BlankPage</tt>.
*/ */
blankPage: { blankPage: {
alignItems: 'center',
backgroundColor: 'transparent',
flex: 1,
flexDirection: 'column',
justifyContent: 'center'
}, },
/** /**
@ -49,15 +44,6 @@ export default createStyleSheet({
fontSize: 18 fontSize: 18
}, },
/**
* The style of the top-level container of WelcomePage.
*/
container: fixAndroidViewClipping({
alignSelf: 'stretch',
backgroundColor: ColorPalette.blue,
flex: 1
}),
/** /**
* The style of the legal-related content such as (hyper)links to Privacy * The style of the legal-related content such as (hyper)links to Privacy
* Policy and Terms of Service displayed on the WelcomePage. * Policy and Terms of Service displayed on the WelcomePage.
@ -80,27 +66,35 @@ export default createStyleSheet({
}, },
/** /**
* The style of the View displayed over the local video. The latter is * The style of the <tt>View</tt> displayed over the local video by
* thought of as the background (content) of WelcomePage. The former is * <tt>LocalVideoTrackUnderlay</tt>. The latter is thought of as the
* thought of as the foreground (content) of WelcomePage. * background (content). The former is thought of as the foreground
* (content).
*/ */
localVideoOverlay: { localVideoTrackOverlay: {
// Since (1) the top-level container of WelcomePage is not transparent
// and, more importantly, (2) this View is displayed over the local
// video, this View would better not have a background color.
// Otherwise, Views within this View will inherit its background color
// and Text, for example, will display non-transparent rectangles over
// the local video.
backgroundColor: 'transparent', backgroundColor: 'transparent',
bottom: 0, bottom: 0,
flex: 1, flex: 1,
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'center',
left: 0, left: 0,
position: 'absolute', position: 'absolute',
right: 0, right: 0,
top: 0 top: 0
}, },
/**
* The style of the top-level container/<tt>View</tt> of
* <tt>LocalVideoTrackUnderlay</tt>.
*/
localVideoTrackUnderlay: fixAndroidViewClipping({
alignItems: 'center',
alignSelf: 'stretch',
backgroundColor: 'transparent',
flex: 1,
justifyContent: 'center'
}),
/** /**
* Container for room name input box and 'join' button. * Container for room name input box and 'join' button.
*/ */
@ -134,5 +128,12 @@ export default createStyleSheet({
fontSize: 25, fontSize: 25,
marginBottom: 2 * BoxModel.margin, marginBottom: 2 * BoxModel.margin,
textAlign: 'center' textAlign: 'center'
},
/**
* The style of the top-level container of <tt>WelcomePage</tt>.
*/
welcomePage: {
backgroundColor: ColorPalette.blue
} }
}); });