feat: display filmstrip on the short side
Adds the ability to detect app area's aspect ratio on react-native through the features/base/aspect-ratio. Makes conference, filmstrip and toolbox react to the aspect ratio changes and display filmstrip on the shorter side of the screen.
This commit is contained in:
parent
2b46c37077
commit
c0a7d6144a
|
@ -1,10 +1,12 @@
|
|||
/* global __DEV__ */
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Linking } from 'react-native';
|
||||
|
||||
import '../../analytics';
|
||||
import '../../authentication';
|
||||
import { AspectRatioDetector } from '../../base/aspect-ratio';
|
||||
import { Platform } from '../../base/react';
|
||||
import '../../mobile/audio-mode';
|
||||
import '../../mobile/background';
|
||||
|
@ -86,6 +88,19 @@ export class App extends AbstractApp {
|
|||
super.componentWillUnmount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the super method to inject {@link AspectRatioDetector} as
|
||||
* the top most component.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_createElement(component, props) {
|
||||
return (
|
||||
<AspectRatioDetector>
|
||||
{super._createElement(component, props)}
|
||||
</AspectRatioDetector>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to disable the use of React Native
|
||||
* {@link ExceptionsManager#handleException} on platforms and in
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* The type of (redux) action which signals that a new aspect ratio has been
|
||||
* detected by the app.
|
||||
* {
|
||||
* type: SET_ASPECT_RATIO,
|
||||
* aspectRatio: Symbol
|
||||
* }
|
||||
*/
|
||||
export const SET_ASPECT_RATIO = Symbol('SET_ASPECT_RATIO');
|
|
@ -0,0 +1,22 @@
|
|||
/* @flow */
|
||||
|
||||
import { SET_ASPECT_RATIO } from './actionTypes';
|
||||
import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from './constants';
|
||||
|
||||
/**
|
||||
* Calculates new aspect ratio for the app based on provided width and height
|
||||
* values.
|
||||
*
|
||||
* @param {number} width - The width of the app's area used on the screen.
|
||||
* @param {number} height - The height of the app's area used on the screen.
|
||||
* @returns {{
|
||||
* type: SET_ASPECT_RATIO,
|
||||
* aspectRatio: Symbol
|
||||
* }}
|
||||
*/
|
||||
export function calculateNewAspectRatio(width: number, height: number): Object {
|
||||
return {
|
||||
type: SET_ASPECT_RATIO,
|
||||
aspectRatio: width > height ? ASPECT_RATIO_WIDE : ASPECT_RATIO_NARROW
|
||||
};
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// @flow
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ASPECT_RATIO_NARROW } from '../constants';
|
||||
|
||||
/**
|
||||
* Decorates given React component class into {@link AspectRatioAwareWrapper}
|
||||
* which provides the <tt>aspectRatio</tt> property updated on each Redux state
|
||||
* change.
|
||||
*
|
||||
* @param {ReactClass} WrapperComponent - A React component class to be wrapped.
|
||||
* @returns {AspectRatioAwareWrapper}
|
||||
*/
|
||||
export function AspectRatioAware(
|
||||
WrapperComponent: ReactClass<*>): ReactClass<*> {
|
||||
return connect(_mapStateToProps)(
|
||||
class AspectRatioAwareWrapper extends Component {
|
||||
/**
|
||||
* Properties of the aspect ratio aware wrapper.
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Either {@link ASPECT_RATIO_NARROW} or
|
||||
* {@link ASPECT_RATIO_WIDE}.
|
||||
*/
|
||||
aspectRatio: PropTypes.symbol
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement's React render method to wrap the nested component.
|
||||
*
|
||||
* @returns {XML}
|
||||
*/
|
||||
render(): React$Element<*> {
|
||||
return <WrapperComponent { ...this.props } />;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps Redux state to {@link AspectRatioAwareWrapper} properties.
|
||||
*
|
||||
* @param {Object} state - The Redux whole state.
|
||||
* @returns {{
|
||||
* aspectRatio: Symbol
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
aspectRatio: state['features/base/aspect-ratio'].aspectRatio
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given React component decorated in {@link AspectRatioAwareWrapper}
|
||||
* has currently the {@link ASPECT_RATIO_NARROW} set in the aspect ratio
|
||||
* property.
|
||||
*
|
||||
* @param {AspectRatioAwareWrapper} component - A
|
||||
* {@link AspectRatioAwareWrapper} which has <tt>aspectRation</tt> property.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isNarrowAspectRatio(component: ReactClass<*>) {
|
||||
return component.props.aspectRatio === ASPECT_RATIO_NARROW;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { calculateNewAspectRatio } from '../actions';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* A root {@link View} which captures the 'onLayout' event and figures out
|
||||
* the aspect ratio of the app.
|
||||
*/
|
||||
class AspectRatioDetector extends Component {
|
||||
/**
|
||||
* AspectRatioDetector component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* The "onLayout" handler.
|
||||
*/
|
||||
_onLayout: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Any nested components.
|
||||
*/
|
||||
children: PropTypes.object
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the root view and it's children.
|
||||
*
|
||||
* @returns {Component}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<View
|
||||
onLayout = { this.props._onLayout }
|
||||
style = { styles.aspectRatioDetectorStyle } >
|
||||
{this.props.children}
|
||||
</View>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps dispatching of the aspect ratio actions to React component props.
|
||||
*
|
||||
* @param {Function} dispatch - Redux action dispatcher.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _onLayout: Function
|
||||
* }}
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
/**
|
||||
* Handles the "on layout" View's event and dispatches aspect ratio
|
||||
* changed action.
|
||||
*
|
||||
* @param {{ width: number, height: number }} event - The "on layout"
|
||||
* event structure passed by react-native.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
_onLayout(event) {
|
||||
const { width, height } = event.nativeEvent.layout;
|
||||
|
||||
dispatch(calculateNewAspectRatio(width, height));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(undefined, _mapDispatchToProps)(AspectRatioDetector);
|
|
@ -0,0 +1,2 @@
|
|||
export * from './AspectRatioAware';
|
||||
export { default as AspectRatioDetector } from './AspectRatioDetector';
|
|
@ -0,0 +1,14 @@
|
|||
import { createStyleSheet, fixAndroidViewClipping } from '../../styles/index';
|
||||
|
||||
/**
|
||||
* The styles of the feature app.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
/**
|
||||
* The style for {@link AspectRatioDetector} root view used on react-native.
|
||||
*/
|
||||
aspectRatioDetectorStyle: fixAndroidViewClipping({
|
||||
alignSelf: 'stretch',
|
||||
flex: 1
|
||||
})
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* The aspect ratio constant indicates that the app area's width is smaller than
|
||||
* the height.
|
||||
*
|
||||
* @type {Symbol}
|
||||
*/
|
||||
export const ASPECT_RATIO_NARROW = Symbol('ASPECT_RATIO_NARROW');
|
||||
|
||||
/**
|
||||
* Aspect ratio constant indicates that the app area's width is larger than
|
||||
* the height.
|
||||
*
|
||||
* @type {Symbol}
|
||||
*/
|
||||
export const ASPECT_RATIO_WIDE = Symbol('ASPECT_RATIO_WIDE');
|
|
@ -0,0 +1,6 @@
|
|||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './constants';
|
||||
|
||||
import './reducer';
|
|
@ -0,0 +1,19 @@
|
|||
import { ReducerRegistry, set } from '../redux';
|
||||
|
||||
import { SET_ASPECT_RATIO } from './actionTypes';
|
||||
import { ASPECT_RATIO_NARROW } from './constants';
|
||||
|
||||
const INITIAL_STATE = {
|
||||
aspectRatio: ASPECT_RATIO_NARROW
|
||||
};
|
||||
|
||||
ReducerRegistry.register(
|
||||
'features/base/aspect-ratio',
|
||||
(state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case SET_ASPECT_RATIO:
|
||||
return set(state, 'aspectRatio', action.aspectRatio);
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
|
@ -184,15 +184,6 @@ class Conference extends Component {
|
|||
*/}
|
||||
<LargeVideo />
|
||||
|
||||
{/*
|
||||
* The Filmstrip is in a stacking layer above the LargeVideo.
|
||||
* The LargeVideo and the Filmstrip form what the Web/React app
|
||||
* calls "videospace". Presumably, the name and grouping stem
|
||||
* from the fact that these two React Components depict the
|
||||
* videos of the conference's participants.
|
||||
*/}
|
||||
<Filmstrip />
|
||||
|
||||
{/*
|
||||
* The overlays need to be bellow the Toolbox so that the user
|
||||
* may tap the ToolbarButtons.
|
||||
|
@ -209,10 +200,22 @@ class Conference extends Component {
|
|||
</View>
|
||||
}
|
||||
|
||||
{/*
|
||||
* The Toolbox is in a stacking layer above the Filmstrip.
|
||||
*/}
|
||||
<Toolbox />
|
||||
<View style = { styles.toolboxAndFilmstripContainer } >
|
||||
{/*
|
||||
* The Toolbox is in a stacking layer above the Filmstrip.
|
||||
*/}
|
||||
<Toolbox />
|
||||
{/*
|
||||
* The Filmstrip is in a stacking layer above
|
||||
* the LargeVideo.
|
||||
* The LargeVideo and the Filmstrip form what the Web/React
|
||||
* app calls "videospace". Presumably, the name and
|
||||
* grouping stem from the fact that these two React
|
||||
* Components depict the videos of the conference's
|
||||
* participants.
|
||||
*/}
|
||||
<Filmstrip />
|
||||
</View>
|
||||
|
||||
{/*
|
||||
* The dialogs are in the topmost stacking layers.
|
||||
|
|
|
@ -38,5 +38,19 @@ export default createStyleSheet({
|
|||
// contrast and translucency.
|
||||
backgroundColor: ColorPalette.appBackground,
|
||||
opacity: 0.5
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the view which expands over the whole conference area and
|
||||
* splits it between both the filmstrip and the toolbox.
|
||||
*/
|
||||
toolboxAndFilmstripContainer: {
|
||||
bottom: 0,
|
||||
flexDirection: 'column',
|
||||
left: 0,
|
||||
justifyContent: 'flex-end',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0
|
||||
}
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ import React, { Component } from 'react';
|
|||
import { ScrollView } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { AspectRatioAware, isNarrowAspectRatio } from '../../base/aspect-ratio';
|
||||
import { Container } from '../../base/react';
|
||||
|
||||
import Thumbnail from './Thumbnail';
|
||||
|
@ -47,15 +48,16 @@ class Filmstrip extends Component<*> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const filmstripStyle
|
||||
= isNarrowAspectRatio(this)
|
||||
? styles.filmstripNarrow : styles.filmstripWide;
|
||||
|
||||
return (
|
||||
<Container
|
||||
style = { styles.filmstrip }
|
||||
visible = { this.props._visible }>
|
||||
style = { filmstripStyle }
|
||||
visible = { this.props._visible } >
|
||||
<ScrollView
|
||||
|
||||
contentContainerStyle
|
||||
= { styles.filmstripScrollViewContentContainer }
|
||||
horizontal = { true }
|
||||
horizontal = { isNarrowAspectRatio(this) }
|
||||
showsHorizontalScrollIndicator = { false }
|
||||
showsVerticalScrollIndicator = { false }>
|
||||
{
|
||||
|
@ -121,6 +123,8 @@ class Filmstrip extends Component<*> {
|
|||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const participants = state['features/base/participants'];
|
||||
|
||||
return {
|
||||
/**
|
||||
* The participants in the conference.
|
||||
|
@ -128,20 +132,20 @@ function _mapStateToProps(state) {
|
|||
* @private
|
||||
* @type {Participant[]}
|
||||
*/
|
||||
_participants: state['features/base/participants'],
|
||||
_participants: participants,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the filmstrip is visible.
|
||||
*
|
||||
* XXX The React Component Filmstrip is used on mobile only at the time
|
||||
* of this writing and on mobile the filmstrip is visible when the
|
||||
* toolbar is not.
|
||||
* of this writing and on mobile the filmstrip is when there are at
|
||||
* least 2 participants in the conference (including the local one).
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
_visible: !state['features/toolbox'].visible
|
||||
_visible: participants.length > 1
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(Filmstrip);
|
||||
export default connect(_mapStateToProps)(AspectRatioAware(Filmstrip));
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
import { Platform } from '../../base/react';
|
||||
import { BoxModel, ColorPalette } from '../../base/styles';
|
||||
|
||||
/**
|
||||
* The base filmstrip style shared between narrow and wide versions.
|
||||
*/
|
||||
const filmstripBaseStyle = {
|
||||
flexGrow: 0,
|
||||
flexDirection: 'column'
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the feature filmstrip common to both Web and native.
|
||||
*/
|
||||
|
@ -40,26 +48,28 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* The style of the Container which represents the very filmstrip.
|
||||
* The style of the narrow filmstrip version which displays thumbnails
|
||||
* in a row at the bottom of the screen.
|
||||
*/
|
||||
filmstrip: {
|
||||
filmstripNarrow: {
|
||||
...filmstripBaseStyle,
|
||||
alignItems: 'flex-end',
|
||||
alignSelf: 'stretch',
|
||||
bottom: BoxModel.margin,
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0
|
||||
height: 90,
|
||||
marginBottom: BoxModel.margin,
|
||||
marginLeft: BoxModel.margin,
|
||||
marginRight: BoxModel.margin
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the content container of the ScrollView which is placed
|
||||
* inside filmstrip and which contains the participants' thumbnails in order
|
||||
* to allow scrolling through them if they do not fit within the display.
|
||||
* The style of the wide version of the filmstrip which appears as a column
|
||||
* on the short side of the screen.
|
||||
*/
|
||||
filmstripScrollViewContentContainer: {
|
||||
paddingHorizontal: BoxModel.padding
|
||||
filmstripWide: {
|
||||
...filmstripBaseStyle,
|
||||
bottom: BoxModel.margin,
|
||||
left: BoxModel.margin,
|
||||
position: 'absolute',
|
||||
top: BoxModel.margin
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -86,8 +96,7 @@ export default {
|
|||
borderWidth: 1,
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
marginLeft: 2,
|
||||
marginRight: 2,
|
||||
margin: 2,
|
||||
overflow: 'hidden',
|
||||
position: 'relative'
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@ import { View } from 'react-native';
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { sendAnalyticsEvent } from '../../analytics';
|
||||
import { AspectRatioAware, isNarrowAspectRatio } from '../../base/aspect-ratio';
|
||||
import { toggleAudioOnly } from '../../base/conference';
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
|
@ -119,15 +120,25 @@ class Toolbox extends Component {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container
|
||||
style = { styles.toolbarContainer }
|
||||
visible = { this.props._visible }>
|
||||
style = {
|
||||
isNarrowAspectRatio(this)
|
||||
? styles.toolbarContainerNarrow
|
||||
: styles.toolbarContainerWide } >
|
||||
{
|
||||
this._renderPrimaryToolbar()
|
||||
isNarrowAspectRatio(this)
|
||||
? this._renderSecondaryToolbar()
|
||||
: this._renderPrimaryToolbar()
|
||||
}
|
||||
{
|
||||
this._renderSecondaryToolbar()
|
||||
isNarrowAspectRatio(this)
|
||||
? this._renderPrimaryToolbar()
|
||||
: this._renderSecondaryToolbar()
|
||||
}
|
||||
</Container>
|
||||
);
|
||||
|
@ -420,4 +431,5 @@ function _mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(Toolbox);
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(
|
||||
AspectRatioAware(Toolbox));
|
||||
|
|
|
@ -6,7 +6,7 @@ import { BoxModel, ColorPalette, createStyleSheet } from '../../base/styles';
|
|||
* @type {Object}
|
||||
*/
|
||||
const _toolbar = {
|
||||
flex: 1,
|
||||
flex: 0,
|
||||
position: 'absolute'
|
||||
};
|
||||
|
||||
|
@ -86,7 +86,7 @@ export default createStyleSheet({
|
|||
*/
|
||||
primaryToolbar: {
|
||||
..._toolbar,
|
||||
bottom: 3 * BoxModel.margin,
|
||||
bottom: 0,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
left: 0,
|
||||
|
@ -135,9 +135,23 @@ export default createStyleSheet({
|
|||
|
||||
/**
|
||||
* The style of the root/top-level {@link Container} of {@link Toolbox}
|
||||
* which contains {@link Toolbar}s.
|
||||
* which contains {@link Toolbar}s. This is narrow layout version which
|
||||
* spans from the top of the screen to the top of the filmstrip located at
|
||||
* the bottom of the screen.
|
||||
*/
|
||||
toolbarContainer: {
|
||||
toolbarContainerNarrow: {
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the root/top-level {@link Container} of {@link Toolbox}
|
||||
* which contains {@link Toolbar}s. This is wide layout version which
|
||||
* spans from the top to the bottom of the screen and is located to
|
||||
* the right of the filmstrip which is displayed as a column on the left
|
||||
* side of the screen.
|
||||
*/
|
||||
toolbarContainerWide: {
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
|
|
Loading…
Reference in New Issue