rn,responsive-ui: refactor dimensions detection

Use a dimensions detecting root component. The Dimensions module does not
measure the app's view size, but the Window, which may not be the same, for
example on iOS when PiP is used.

Also refactor the aspect ratio wrap component since it can be taken directly
from the store.

Last, remove the use of DimensionsDetector on LargeVideo and TileView since they
occupy the full-screen anyway.

Fixes PiP mode on iOS.
This commit is contained in:
Saúl Ibarra Corretgé 2020-06-02 11:03:17 +02:00 committed by Saúl Ibarra Corretgé
parent d93b219c7f
commit d740752522
13 changed files with 185 additions and 442 deletions

View File

@ -9,7 +9,7 @@ import { DialogContainer } from '../../base/dialog';
import { CALL_INTEGRATION_ENABLED, updateFlags } from '../../base/flags';
import '../../base/jwt';
import { Platform } from '../../base/react';
import '../../base/responsive-ui';
import { DimensionsDetector, clientResized } from '../../base/responsive-ui';
import { updateSettings } from '../../base/settings';
import '../../google-api';
import '../../mobile/audio-mode';
@ -78,6 +78,9 @@ export class App extends AbstractApp {
// This will effectively kill the app. In accord with the Web, do not
// kill the app.
this._maybeDisableExceptionsManager();
// Bind event handler so it is only bound once per instance.
this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
}
/**
@ -107,6 +110,21 @@ export class App extends AbstractApp {
});
}
/**
* Overrides the parent method to inject {@link DimensionsDetector} as
* the top most component.
*
* @override
*/
_createMainElement(component, props) {
return (
<DimensionsDetector
onDimensionsChanged = { this._onDimensionsChanged }>
{ super._createMainElement(component, props) }
</DimensionsDetector>
);
}
/**
* Attempts to disable the use of React Native
* {@link ExceptionsManager#handleException} on platforms and in
@ -144,6 +162,22 @@ export class App extends AbstractApp {
}
}
_onDimensionsChanged: (width: number, height: number) => void;
/**
* Updates the known available size for the app to occupy.
*
* @param {number} width - The component's current width.
* @param {number} height - The component's current height.
* @private
* @returns {void}
*/
_onDimensionsChanged(width: number, height: number) {
const { dispatch } = this.state.store;
dispatch(clientResized(width, height));
}
/**
* Renders the platform specific dialog container.
*

View File

@ -1,70 +0,0 @@
// @flow
import React, { Component } from 'react';
import { connect } from '../../redux';
import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from '../constants';
/**
* The type of the React {@code Component} props of {@link AspectRatioAware}.
*/
type Props = {
aspectRatio: ASPECT_RATIO_NARROW | ASPECT_RATIO_WIDE
};
/**
* Determines whether a specific React {@code Component} decorated into an
* {@link AspectRatioAware} has {@link ASPECT_RATIO_NARROW} as the value of its
* {@code aspectRatio} React prop.
*
* @param {AspectRatioAware} component - An {@link AspectRatioAware} which may
* have an {@code aspectRatio} React prop.
* @returns {boolean}
*/
export function isNarrowAspectRatio(component: React$Component<*>) {
return component.props.aspectRatio === ASPECT_RATIO_NARROW;
}
/**
* Decorates a specific React {@code Component} class into an
* {@link AspectRatioAware} which provides the React prop {@code aspectRatio}
* updated on each redux state change.
*
* @param {Class<React$Component>} WrappedComponent - A React {@code Component}
* class to be wrapped.
* @returns {AspectRatioAwareWrapper}
*/
export function makeAspectRatioAware(
WrappedComponent: Class<React$Component<*>>
): Class<React$Component<*>> {
/**
* Renders {@code WrappedComponent} with the React prop {@code aspectRatio}.
*/
class AspectRatioAware extends Component<Props> {
/**
* Implement's React render method to wrap the nested component.
*
* @returns {React$Element}
*/
render(): React$Element<*> {
return <WrappedComponent { ...this.props } />;
}
}
return connect(_mapStateToProps)(AspectRatioAware);
}
/**
* Maps (parts of) the redux state to {@link AspectRatioAware} props.
*
* @param {Object} state - The whole redux state.
* @private
* @returns {{
* aspectRatio: Symbol
* }}
*/
function _mapStateToProps(state) {
return {
aspectRatio: state['features/base/responsive-ui'].aspectRatio
};
}

View File

@ -1,13 +1,9 @@
// @flow
import React, { PureComponent } from 'react';
import { View } from 'react-native';
import { StyleSheet, View } from 'react-native';
import styles from './styles';
/**
* AspectRatioDetector component's property types.
*/
type Props = {
/**
@ -64,7 +60,7 @@ export default class DimensionsDetector extends PureComponent<Props> {
return (
<View
onLayout = { this._onLayout }
style = { styles.dimensionsDetector } >
style = { StyleSheet.absoluteFillObject } >
{ this.props.children }
</View>
);

View File

@ -1,2 +1 @@
export * from './AspectRatioAware';
export { default as DimensionsDetector } from './DimensionsDetector';

View File

@ -1,14 +0,0 @@
import { createStyleSheet } from '../../styles';
/**
* The styles of the feature base/responsive-ui.
*/
export default createStyleSheet({
/**
* The style of {@link DimensionsDetector} used on react-native.
*/
dimensionsDetector: {
alignSelf: 'stretch',
flex: 1
}
});

View File

@ -1,16 +1,10 @@
// @flow
import { Dimensions } from 'react-native';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
import { MiddlewareRegistry } from '../../base/redux';
import { CLIENT_RESIZED } from './actionTypes';
import { setAspectRatio, setReducedUI } from './actions';
/**
* Dimensions change handler.
*/
let handler;
/**
* Middleware that handles widnow dimension changes and updates the aspect ratio and
@ -19,65 +13,19 @@ let handler;
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
MiddlewareRegistry.register(({ dispatch }) => next => action => {
const result = next(action);
switch (action.type) {
case APP_WILL_UNMOUNT: {
_appWillUnmount();
case CLIENT_RESIZED: {
const { clientWidth: width, clientHeight: height } = action;
dispatch(setAspectRatio(width, height));
dispatch(setReducedUI(width, height));
break;
}
case APP_WILL_MOUNT:
_appWillMount(store);
break;
}
return result;
});
/**
* Notifies this feature that the action {@link APP_WILL_MOUNT} is being
* dispatched within a specific redux {@code store}.
*
* @param {Store} store - The redux store in which the specified {@code action}
* is being dispatched.
* @private
* @returns {void}
*/
function _appWillMount(store) {
handler = dim => {
_onDimensionsChange(dim, store);
};
Dimensions.addEventListener('change', handler);
}
/**
* Notifies this feature that the action {@link APP_WILL_UNMOUNT} is being
* dispatched within a specific redux {@code store}.
*
* @private
* @returns {void}
*/
function _appWillUnmount() {
Dimensions.removeEventListener('change', handler);
handler = undefined;
}
/**
* Handles window dimension changes.
*
* @param {Object} dimensions - The new dimensions.
* @param {Store} store - The redux store.
* @private
* @returns {void}
*/
function _onDimensionsChange(dimensions, store) {
const { width, height } = dimensions.window;
const { dispatch } = store;
dispatch(setAspectRatio(width, height));
dispatch(setReducedUI(width, height));
}

View File

@ -23,7 +23,6 @@ const DEFAULT_STATE = {
ReducerRegistry.register('features/base/responsive-ui', (state = DEFAULT_STATE, action) => {
switch (action.type) {
case CLIENT_RESIZED: {
return {
...state,
clientWidth: action.clientWidth,

View File

@ -8,10 +8,7 @@ import { appNavigate } from '../../../app';
import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
import { connect } from '../../../base/redux';
import {
isNarrowAspectRatio,
makeAspectRatioAware
} from '../../../base/responsive-ui';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import { TestConnectionInfo } from '../../../base/testing';
import { ConferenceNotification, isCalendarEnabled } from '../../../calendar-sync';
import { Chat } from '../../../chat';
@ -45,10 +42,13 @@ import styles, { NAVBAR_GRADIENT_COLORS } from './styles';
*/
type Props = AbstractProps & {
/**
* Application's aspect ratio.
*/
_aspectRatio: Symbol,
/**
* Wherther the calendar feature is enabled or not.
*
* @private
*/
_calendarEnabled: boolean,
@ -57,15 +57,11 @@ type Props = AbstractProps & {
* conference which includes establishing the XMPP connection and then
* joining the room. If truthy, then an activity/loading indicator will be
* rendered.
*
* @private
*/
_connecting: boolean,
/**
* Set to {@code true} when the filmstrip is currently visible.
*
* @private
*/
_filmstripVisible: boolean,
@ -76,34 +72,17 @@ type Props = AbstractProps & {
/**
* Whether Picture-in-Picture is enabled.
*
* @private
*/
_pictureInPictureEnabled: boolean,
/**
* The indicator which determines whether the UI is reduced (to accommodate
* smaller display areas).
*
* @private
*/
_reducedUI: boolean,
/**
* The handler which dispatches the (redux) action {@link setToolboxVisible}
* to show/hide the {@link Toolbox}.
*
* @param {boolean} visible - {@code true} to show the {@code Toolbox} or
* {@code false} to hide it.
* @private
* @returns {void}
*/
_setToolboxVisible: Function,
/**
* The indicator which determines whether the Toolbox is visible.
*
* @private
*/
_toolboxVisible: boolean,
@ -249,6 +228,7 @@ class Conference extends AbstractConference<Props, *> {
*/
_renderContent() {
const {
_aspectRatio,
_connecting,
_filmstripVisible,
_largeVideoParticipantId,
@ -257,7 +237,8 @@ class Conference extends AbstractConference<Props, *> {
_toolboxVisible
} = this.props;
const showGradient = _toolboxVisible;
const applyGradientStretching = _filmstripVisible && isNarrowAspectRatio(this) && !_shouldDisplayTileView;
const applyGradientStretching
= _filmstripVisible && _aspectRatio === ASPECT_RATIO_NARROW && !_shouldDisplayTileView;
if (_reducedUI) {
return this._renderContentForReducedUi();
@ -393,7 +374,9 @@ class Conference extends AbstractConference<Props, *> {
// flex layout. The only option that seemed to limit the notification's
// size was explicit 'width' value which is not better than the margin
// added here.
if (this.props._filmstripVisible && !isNarrowAspectRatio(this)) {
const { _aspectRatio, _filmstripVisible } = this.props;
if (_filmstripVisible && _aspectRatio !== ASPECT_RATIO_NARROW) {
notificationsStyle.marginRight = FILMSTRIP_SIZE;
}
@ -433,7 +416,7 @@ function _mapStateToProps(state) {
joining,
leaving
} = state['features/base/conference'];
const { reducedUI } = state['features/base/responsive-ui'];
const { aspectRatio, reducedUI } = state['features/base/responsive-ui'];
// XXX There is a window of time between the successful establishment of the
// XMPP connection and the subsequent commencement of joining the MUC during
@ -449,61 +432,15 @@ function _mapStateToProps(state) {
return {
...abstractMapStateToProps(state),
/**
* Wherther the calendar feature is enabled or not.
*
* @private
* @type {boolean}
*/
_aspectRatio: aspectRatio,
_calendarEnabled: isCalendarEnabled(state),
/**
* The indicator which determines that we are still connecting to the
* conference which includes establishing the XMPP connection and then
* joining the room. If truthy, then an activity/loading indicator will
* be rendered.
*
* @private
* @type {boolean}
*/
_connecting: Boolean(connecting_),
/**
* Is {@code true} when the filmstrip is currently visible.
*/
_filmstripVisible: isFilmstripVisible(state),
/**
* The ID of the participant currently on stage.
*/
_largeVideoParticipantId: state['features/large-video'].participantId,
/**
* Whether Picture-in-Picture is enabled.
*
* @private
* @type {boolean}
*/
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
/**
* The indicator which determines whether the UI is reduced (to
* accommodate smaller display areas).
*
* @private
* @type {boolean}
*/
_reducedUI: reducedUI,
/**
* The indicator which determines whether the Toolbox is visible.
*
* @private
* @type {boolean}
*/
_toolboxVisible: isToolboxVisible(state)
};
}
export default connect(_mapStateToProps)(makeAspectRatioAware(Conference));
export default connect(_mapStateToProps)(Conference);

View File

@ -5,10 +5,7 @@ import { TouchableOpacity, View } from 'react-native';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { connect } from '../../../base/redux';
import {
isNarrowAspectRatio,
makeAspectRatioAware
} from '../../../base/responsive-ui';
import { ASPECT_RATIO_WIDE } from '../../../base/responsive-ui/constants';
import {
RecordingExpandedLabel
} from '../../../recording';
@ -29,14 +26,19 @@ import styles from './styles';
type Props = AbstractLabelsProps & {
/**
* Function to translate i18n labels.
* Application's aspect ratio.
*/
t: Function,
_aspectRatio: Symbol,
/**
* True if the labels should be visible, false otherwise.
*/
_visible: boolean
_visible: boolean,
/**
* Function to translate i18n labels.
*/
t: Function
};
type State = {
@ -149,12 +151,13 @@ class Labels extends AbstractLabels<Props, State> {
* @inheritdoc
*/
render() {
if (!this.props._visible) {
const { _aspectRatio, _filmstripVisible, _visible } = this.props;
if (!_visible) {
return null;
}
const wide = !isNarrowAspectRatio(this);
const { _filmstripVisible } = this.props;
const wide = _aspectRatio === ASPECT_RATIO_WIDE;
return (
<View
@ -354,8 +357,9 @@ class Labels extends AbstractLabels<Props, State> {
function _mapStateToProps(state) {
return {
..._abstractMapStateToProps(state),
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
_visible: !shouldDisplayNotifications(state)
};
}
export default connect(_mapStateToProps)(makeAspectRatioAware(Labels));
export default connect(_mapStateToProps)(Labels);

View File

@ -41,8 +41,7 @@ class Labels extends AbstractLabels<Props, State> {
*/
static getDerivedStateFromProps(props: Props, prevState: State) {
return {
filmstripBecomingVisible: !prevState.filmstripBecomingVisible
&& props._filmstripVisible
filmstripBecomingVisible: !prevState.filmstripBecomingVisible && props._filmstripVisible
};
}

View File

@ -5,10 +5,7 @@ import { ScrollView } from 'react-native';
import { Container, Platform } from '../../../base/react';
import { connect } from '../../../base/redux';
import {
isNarrowAspectRatio,
makeAspectRatioAware
} from '../../../base/responsive-ui';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import { isFilmstripVisible } from '../../functions';
import LocalThumbnail from './LocalThumbnail';
@ -20,24 +17,23 @@ import styles from './styles';
*/
type Props = {
/**
* Application's aspect ratio.
*/
_aspectRatio: Symbol,
/**
* The indicator which determines whether the filmstrip is enabled.
*
* @private
*/
_enabled: boolean,
/**
* The participants in the conference.
*
* @private
*/
_participants: Array<any>,
/**
* The indicator which determines whether the filmstrip is visible.
*
* @private
*/
_visible: boolean
};
@ -90,40 +86,36 @@ class Filmstrip extends Component<Props> {
* @returns {ReactElement}
*/
render() {
if (!this.props._enabled) {
const { _aspectRatio, _enabled, _participants, _visible } = this.props;
if (!_enabled) {
return null;
}
const isNarrowAspectRatio_ = isNarrowAspectRatio(this);
const filmstripStyle
= isNarrowAspectRatio_
? styles.filmstripNarrow
: styles.filmstripWide;
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
const filmstripStyle = isNarrowAspectRatio ? styles.filmstripNarrow : styles.filmstripWide;
return (
<Container
style = { filmstripStyle }
visible = { this.props._visible }>
visible = { _visible }>
{
this._separateLocalThumbnail
&& !isNarrowAspectRatio_
&& !isNarrowAspectRatio
&& <LocalThumbnail />
}
<ScrollView
horizontal = { isNarrowAspectRatio_ }
horizontal = { isNarrowAspectRatio }
showsHorizontalScrollIndicator = { false }
showsVerticalScrollIndicator = { false }
style = { styles.scrollView } >
{
!this._separateLocalThumbnail
&& !isNarrowAspectRatio_
!this._separateLocalThumbnail && !isNarrowAspectRatio
&& <LocalThumbnail />
}
{
this._sort(
this.props._participants,
isNarrowAspectRatio_)
this._sort(_participants, isNarrowAspectRatio)
.map(p => (
<Thumbnail
key = { p.id }
@ -131,14 +123,12 @@ class Filmstrip extends Component<Props> {
}
{
!this._separateLocalThumbnail
&& isNarrowAspectRatio_
!this._separateLocalThumbnail && isNarrowAspectRatio
&& <LocalThumbnail />
}
</ScrollView>
{
this._separateLocalThumbnail
&& isNarrowAspectRatio_
this._separateLocalThumbnail && isNarrowAspectRatio
&& <LocalThumbnail />
}
</Container>
@ -150,13 +140,13 @@ class Filmstrip extends Component<Props> {
*
* @param {Participant[]} participants - The array of {@code Participant}s
* to sort in display order.
* @param {boolean} isNarrowAspectRatio_ - Indicates if the aspect ratio is
* @param {boolean} isNarrowAspectRatio - Indicates if the aspect ratio is
* wide or narrow.
* @private
* @returns {Participant[]} A new array containing the elements of the
* specified {@code participants} array sorted in display order.
*/
_sort(participants, isNarrowAspectRatio_) {
_sort(participants, isNarrowAspectRatio) {
// XXX Array.prototype.sort() is not appropriate because (1) it operates
// in place and (2) it is not necessarily stable.
@ -164,7 +154,7 @@ class Filmstrip extends Component<Props> {
...participants
];
if (isNarrowAspectRatio_) {
if (isNarrowAspectRatio) {
// When the narrow aspect ratio is used, we want to have the remote
// participants from right to left with the newest added/joined to
// the leftmost side. The local participant is the leftmost item.
@ -180,42 +170,18 @@ class Filmstrip extends Component<Props> {
*
* @param {Object} state - The redux state.
* @private
* @returns {{
* _participants: Participant[],
* _visible: boolean
* }}
* @returns {Props}
*/
function _mapStateToProps(state) {
const participants = state['features/base/participants'];
const { enabled } = state['features/filmstrip'];
return {
/**
* The indicator which determines whether the filmstrip is enabled.
*
* @private
* @type {boolean}
*/
_aspectRatio: state['features/base/responsive-ui'].aspectRatio,
_enabled: enabled,
/**
* The remote participants in the conference.
*
* @private
* @type {Participant[]}
*/
_participants: participants.filter(p => !p.local),
/**
* The indicator which determines whether the filmstrip is visible. The
* mobile/react-native Filmstrip is visible when there are at least 2
* participants in the conference (including the local one).
*
* @private
* @type {boolean}
*/
_visible: isFilmstripVisible(state)
};
}
export default connect(_mapStateToProps)(makeAspectRatioAware(Filmstrip));
export default connect(_mapStateToProps)(Filmstrip);

View File

@ -13,11 +13,7 @@ import {
setMaxReceiverVideoQuality
} from '../../../base/conference';
import { connect } from '../../../base/redux';
import {
DimensionsDetector,
isNarrowAspectRatio,
makeAspectRatioAware
} from '../../../base/responsive-ui';
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
import Thumbnail from './Thumbnail';
import styles from './styles';
@ -27,11 +23,26 @@ import styles from './styles';
*/
type Props = {
/**
* Application's aspect ratio.
*/
_aspectRatio: Symbol,
/**
* Application's viewport height.
*/
_height: number,
/**
* The participants in the conference.
*/
_participants: Array<Object>,
/**
* Application's viewport height.
*/
_width: number,
/**
* Invoked to update the receiver video quality.
*/
@ -43,22 +54,6 @@ type Props = {
onClick: Function
};
/**
* The type of the React {@link Component} state of {@link TileView}.
*/
type State = {
/**
* The available width for {@link TileView} to occupy.
*/
height: number,
/**
* The available height for {@link TileView} to occupy.
*/
width: number
};
/**
* The margin for each side of the tile view. Taken away from the available
* height and width for the tile container to display in.
@ -82,25 +77,7 @@ const TILE_ASPECT_RATIO = 1;
*
* @extends Component
*/
class TileView extends Component<Props, State> {
state = {
height: 0,
width: 0
};
/**
* Initializes a new {@code TileView} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Props) {
super(props);
// Bind event handler so it is only bound once per instance.
this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
}
class TileView extends Component<Props> {
/**
* Implements React's {@link Component#componentDidMount}.
*
@ -126,32 +103,27 @@ class TileView extends Component<Props, State> {
* @returns {ReactElement}
*/
render() {
const { onClick } = this.props;
const { height, width } = this.state;
const rowElements = this._groupIntoRows(
this._renderThumbnails(), this._getColumnCount());
const { _height, _width, onClick } = this.props;
const rowElements = this._groupIntoRows(this._renderThumbnails(), this._getColumnCount());
return (
<DimensionsDetector
onDimensionsChanged = { this._onDimensionsChanged }>
<ScrollView
style = {{
...styles.tileView,
height,
width
}}>
<TouchableWithoutFeedback onPress = { onClick }>
<View
style = {{
...styles.tileViewRows,
minHeight: height,
minWidth: width
}}>
{ rowElements }
</View>
</TouchableWithoutFeedback>
</ScrollView>
</DimensionsDetector>
<ScrollView
style = {{
...styles.tileView,
height: _height,
width: _width
}}>
<TouchableWithoutFeedback onPress = { onClick }>
<View
style = {{
...styles.tileViewRows,
minHeight: _height,
minWidth: _width
}}>
{ rowElements }
</View>
</TouchableWithoutFeedback>
</ScrollView>
);
}
@ -167,7 +139,7 @@ class TileView extends Component<Props, State> {
// For narrow view, tiles should stack on top of each other for a lonely
// call and a 1:1 call. Otherwise tiles should be grouped into rows of
// two.
if (isNarrowAspectRatio(this)) {
if (this.props._aspectRatio === ASPECT_RATIO_NARROW) {
return participantCount >= 3 ? 2 : 1;
}
@ -209,19 +181,17 @@ class TileView extends Component<Props, State> {
* @returns {Object}
*/
_getTileDimensions() {
const { _participants } = this.props;
const { height, width } = this.state;
const { _height, _participants, _width } = this.props;
const columns = this._getColumnCount();
const participantCount = _participants.length;
const heightToUse = height - (MARGIN * 2);
const widthToUse = width - (MARGIN * 2);
const heightToUse = _height - (MARGIN * 2);
const widthToUse = _width - (MARGIN * 2);
let tileWidth;
// If there is going to be at least two rows, ensure that at least two
// rows display fully on screen.
if (participantCount / columns > 1) {
tileWidth
= Math.min(widthToUse / columns, heightToUse / 2);
tileWidth = Math.min(widthToUse / columns, heightToUse / 2);
} else {
tileWidth = Math.min(widthToUse / columns, heightToUse);
}
@ -247,8 +217,7 @@ class TileView extends Component<Props, State> {
for (let i = 0; i < thumbnails.length; i++) {
if (i % rowLength === 0) {
const thumbnailsInRow
= thumbnails.slice(i, i + rowLength);
const thumbnailsInRow = thumbnails.slice(i, i + rowLength);
rowElements.push(
<View
@ -263,23 +232,6 @@ class TileView extends Component<Props, State> {
return rowElements;
}
_onDimensionsChanged: (width: number, height: number) => void;
/**
* Updates the known available state for {@link TileView} to occupy.
*
* @param {number} width - The component's current width.
* @param {number} height - The component's current height.
* @private
* @returns {void}
*/
_onDimensionsChanged(width: number, height: number) {
this.setState({
height,
width
});
}
/**
* Creates React Elements to display each participant in a thumbnail. Each
* tile will be.
@ -326,14 +278,17 @@ class TileView extends Component<Props, State> {
*
* @param {Object} state - The redux state.
* @private
* @returns {{
* _participants: Participant[]
* }}
* @returns {Props}
*/
function _mapStateToProps(state) {
const responsiveUi = state['features/base/responsive-ui'];
return {
_participants: state['features/base/participants']
_aspectRatio: responsiveUi.aspectRatio,
_height: responsiveUi.clientHeight,
_participants: state['features/base/participants'],
_width: responsiveUi.clientWidth
};
}
export default connect(_mapStateToProps)(makeAspectRatioAware(TileView));
export default connect(_mapStateToProps)(TileView);

View File

@ -1,11 +1,10 @@
// @flow
import React, { Component } from 'react';
import React, { PureComponent } from 'react';
import { ColorSchemeRegistry } from '../../base/color-scheme';
import { ParticipantView } from '../../base/participants';
import { connect } from '../../base/redux';
import { DimensionsDetector } from '../../base/responsive-ui';
import { StyleType } from '../../base/styles';
import { AVATAR_SIZE } from './styles';
@ -15,6 +14,11 @@ import { AVATAR_SIZE } from './styles';
*/
type Props = {
/**
* Application's viewport height.
*/
_height: number,
/**
* The ID of the participant (to be) depicted by LargeVideo.
*
@ -27,6 +31,11 @@ type Props = {
*/
_styles: StyleType,
/**
* Application's viewport height.
*/
_width: number,
/**
* Callback to invoke when the {@code LargeVideo} is clicked/pressed.
*/
@ -62,50 +71,33 @@ const DEFAULT_STATE = {
*
* @extends Component
*/
class LargeVideo extends Component<Props, State> {
class LargeVideo extends PureComponent<Props, State> {
state = {
...DEFAULT_STATE
};
/** Initializes a new {@code LargeVideo} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onDimensionsChanged = this._onDimensionsChanged.bind(this);
}
_onDimensionsChanged: (width: number, height: number) => void;
/**
* Handle this component's dimension changes. In case we deem it's too
* Handles dimension changes. In case we deem it's too
* small, the connectivity indicator won't be rendered and the avatar
* will occupy the entirety of the available screen state.
*
* @param {number} width - The component's current width.
* @param {number} height - The component's current height.
* @private
* @returns {void}
* @inheritdoc
*/
_onDimensionsChanged(width: number, height: number) {
static getDerivedStateFromProps(props: Props) {
const { _height, _width } = props;
// Get the size, rounded to the nearest even number.
const size = 2 * Math.round(Math.min(height, width) / 2);
let nextState;
const size = 2 * Math.round(Math.min(_height, _width) / 2);
if (size < AVATAR_SIZE * 1.5) {
nextState = {
return {
avatarSize: size - 15, // Leave some margin.
useConnectivityInfoLabel: false
};
} else {
nextState = DEFAULT_STATE;
}
this.setState(nextState);
return DEFAULT_STATE;
}
/**
@ -126,18 +118,15 @@ class LargeVideo extends Component<Props, State> {
} = this.props;
return (
<DimensionsDetector
onDimensionsChanged = { this._onDimensionsChanged }>
<ParticipantView
avatarSize = { avatarSize }
onPress = { onClick }
participantId = { _participantId }
style = { _styles.largeVideo }
testHintId = 'org.jitsi.meet.LargeVideo'
useConnectivityInfoLabel = { useConnectivityInfoLabel }
zOrder = { 0 }
zoomEnabled = { true } />
</DimensionsDetector>
<ParticipantView
avatarSize = { avatarSize }
onPress = { onClick }
participantId = { _participantId }
style = { _styles.largeVideo }
testHintId = 'org.jitsi.meet.LargeVideo'
useConnectivityInfoLabel = { useConnectivityInfoLabel }
zOrder = { 0 }
zoomEnabled = { true } />
);
}
}
@ -147,15 +136,16 @@ class LargeVideo extends Component<Props, State> {
*
* @param {Object} state - Redux state.
* @private
* @returns {{
* _participantId: string,
* _styles: StyleType
* }}
* @returns {Props}
*/
function _mapStateToProps(state) {
const { clientHeight: height, clientWidth: width } = state['features/base/responsive-ui'];
return {
_height: height,
_participantId: state['features/large-video'].participantId,
_styles: ColorSchemeRegistry.get(state, 'LargeVideo')
_styles: ColorSchemeRegistry.get(state, 'LargeVideo'),
_width: width
};
}