2018-09-13 15:20:22 +00:00
|
|
|
// @flow
|
|
|
|
|
|
|
|
import React, { Component } from 'react';
|
|
|
|
import {
|
|
|
|
ScrollView,
|
|
|
|
TouchableWithoutFeedback,
|
|
|
|
View
|
|
|
|
} from 'react-native';
|
2019-03-19 15:42:25 +00:00
|
|
|
import type { Dispatch } from 'redux';
|
2018-09-13 15:20:22 +00:00
|
|
|
|
2019-03-21 16:38:29 +00:00
|
|
|
import { connect } from '../../../base/redux';
|
2020-06-02 09:03:17 +00:00
|
|
|
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
2020-03-09 11:54:54 +00:00
|
|
|
import { setTileViewDimensions } from '../../actions.native';
|
2018-09-13 15:20:22 +00:00
|
|
|
|
|
|
|
import Thumbnail from './Thumbnail';
|
|
|
|
import styles from './styles';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The type of the React {@link Component} props of {@link TileView}.
|
|
|
|
*/
|
|
|
|
type Props = {
|
|
|
|
|
|
|
|
/**
|
2020-06-02 09:03:17 +00:00
|
|
|
* Application's aspect ratio.
|
2018-09-13 15:20:22 +00:00
|
|
|
*/
|
2020-06-02 09:03:17 +00:00
|
|
|
_aspectRatio: Symbol,
|
2018-09-13 15:20:22 +00:00
|
|
|
|
|
|
|
/**
|
2020-06-02 09:03:17 +00:00
|
|
|
* Application's viewport height.
|
2018-09-13 15:20:22 +00:00
|
|
|
*/
|
2020-06-02 09:03:17 +00:00
|
|
|
_height: number,
|
2018-09-13 15:20:22 +00:00
|
|
|
|
|
|
|
/**
|
2020-06-02 09:03:17 +00:00
|
|
|
* The participants in the conference.
|
2018-09-13 15:20:22 +00:00
|
|
|
*/
|
2020-06-02 09:03:17 +00:00
|
|
|
_participants: Array<Object>,
|
2018-09-13 15:20:22 +00:00
|
|
|
|
2020-06-02 09:03:17 +00:00
|
|
|
/**
|
|
|
|
* Application's viewport height.
|
|
|
|
*/
|
|
|
|
_width: number,
|
2018-09-13 15:20:22 +00:00
|
|
|
|
|
|
|
/**
|
2020-06-02 09:03:17 +00:00
|
|
|
* Invoked to update the receiver video quality.
|
2018-09-13 15:20:22 +00:00
|
|
|
*/
|
2020-06-02 09:03:17 +00:00
|
|
|
dispatch: Dispatch<any>,
|
2018-09-13 15:20:22 +00:00
|
|
|
|
|
|
|
/**
|
2020-06-02 09:03:17 +00:00
|
|
|
* Callback to invoke when tile view is tapped.
|
2018-09-13 15:20:22 +00:00
|
|
|
*/
|
2020-06-02 09:03:17 +00:00
|
|
|
onClick: Function
|
2018-09-13 15:20:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The margin for each side of the tile view. Taken away from the available
|
|
|
|
* height and width for the tile container to display in.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @type {number}
|
|
|
|
*/
|
|
|
|
const MARGIN = 10;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The aspect ratio the tiles should display in.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @type {number}
|
|
|
|
*/
|
|
|
|
const TILE_ASPECT_RATIO = 1;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements a React {@link Component} which displays thumbnails in a two
|
|
|
|
* dimensional grid.
|
|
|
|
*
|
|
|
|
* @extends Component
|
|
|
|
*/
|
2020-06-02 09:03:17 +00:00
|
|
|
class TileView extends Component<Props> {
|
2018-09-13 15:20:22 +00:00
|
|
|
/**
|
|
|
|
* Implements React's {@link Component#componentDidMount}.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
componentDidMount() {
|
|
|
|
this._updateReceiverQuality();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements React's {@link Component#componentDidUpdate}.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
componentDidUpdate() {
|
|
|
|
this._updateReceiverQuality();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements React's {@link Component#render()}.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
* @returns {ReactElement}
|
|
|
|
*/
|
|
|
|
render() {
|
2020-06-02 09:03:17 +00:00
|
|
|
const { _height, _width, onClick } = this.props;
|
|
|
|
const rowElements = this._groupIntoRows(this._renderThumbnails(), this._getColumnCount());
|
2018-09-13 15:20:22 +00:00
|
|
|
|
|
|
|
return (
|
2020-06-02 09:03:17 +00:00
|
|
|
<ScrollView
|
|
|
|
style = {{
|
|
|
|
...styles.tileView,
|
|
|
|
height: _height,
|
|
|
|
width: _width
|
|
|
|
}}>
|
|
|
|
<TouchableWithoutFeedback onPress = { onClick }>
|
|
|
|
<View
|
|
|
|
style = {{
|
|
|
|
...styles.tileViewRows,
|
|
|
|
minHeight: _height,
|
|
|
|
minWidth: _width
|
|
|
|
}}>
|
|
|
|
{ rowElements }
|
|
|
|
</View>
|
|
|
|
</TouchableWithoutFeedback>
|
|
|
|
</ScrollView>
|
2018-09-13 15:20:22 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns how many columns should be displayed for tile view.
|
|
|
|
*
|
|
|
|
* @returns {number}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_getColumnCount() {
|
|
|
|
const participantCount = this.props._participants.length;
|
|
|
|
|
|
|
|
// 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.
|
2020-06-02 09:03:17 +00:00
|
|
|
if (this.props._aspectRatio === ASPECT_RATIO_NARROW) {
|
2018-09-13 15:20:22 +00:00
|
|
|
return participantCount >= 3 ? 2 : 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (participantCount === 4) {
|
|
|
|
// In wide view, a four person call should display as a 2x2 grid.
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Math.min(3, participantCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns all participants with the local participant at the end.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {Participant[]}
|
|
|
|
*/
|
|
|
|
_getSortedParticipants() {
|
|
|
|
const participants = [];
|
|
|
|
let localParticipant;
|
|
|
|
|
|
|
|
for (const participant of this.props._participants) {
|
|
|
|
if (participant.local) {
|
|
|
|
localParticipant = participant;
|
|
|
|
} else {
|
|
|
|
participants.push(participant);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
localParticipant && participants.push(localParticipant);
|
|
|
|
|
|
|
|
return participants;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the height and width for the tiles.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
|
|
|
_getTileDimensions() {
|
2020-06-02 09:03:17 +00:00
|
|
|
const { _height, _participants, _width } = this.props;
|
2018-09-13 15:20:22 +00:00
|
|
|
const columns = this._getColumnCount();
|
|
|
|
const participantCount = _participants.length;
|
2020-06-02 09:03:17 +00:00
|
|
|
const heightToUse = _height - (MARGIN * 2);
|
|
|
|
const widthToUse = _width - (MARGIN * 2);
|
2018-09-13 15:20:22 +00:00
|
|
|
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) {
|
2020-06-02 09:03:17 +00:00
|
|
|
tileWidth = Math.min(widthToUse / columns, heightToUse / 2);
|
2018-09-13 15:20:22 +00:00
|
|
|
} else {
|
|
|
|
tileWidth = Math.min(widthToUse / columns, heightToUse);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
height: tileWidth / TILE_ASPECT_RATIO,
|
|
|
|
width: tileWidth
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Splits a list of thumbnails into React Elements with a maximum of
|
|
|
|
* {@link rowLength} thumbnails in each.
|
|
|
|
*
|
|
|
|
* @param {Array} thumbnails - The list of thumbnails that should be split
|
|
|
|
* into separate row groupings.
|
|
|
|
* @param {number} rowLength - How many thumbnails should be in each row.
|
|
|
|
* @private
|
|
|
|
* @returns {ReactElement[]}
|
|
|
|
*/
|
|
|
|
_groupIntoRows(thumbnails, rowLength) {
|
|
|
|
const rowElements = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < thumbnails.length; i++) {
|
|
|
|
if (i % rowLength === 0) {
|
2020-06-02 09:03:17 +00:00
|
|
|
const thumbnailsInRow = thumbnails.slice(i, i + rowLength);
|
2018-09-13 15:20:22 +00:00
|
|
|
|
|
|
|
rowElements.push(
|
|
|
|
<View
|
|
|
|
key = { rowElements.length }
|
|
|
|
style = { styles.tileViewRow }>
|
|
|
|
{ thumbnailsInRow }
|
|
|
|
</View>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rowElements;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates React Elements to display each participant in a thumbnail. Each
|
|
|
|
* tile will be.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {ReactElement[]}
|
|
|
|
*/
|
|
|
|
_renderThumbnails() {
|
|
|
|
const styleOverrides = {
|
|
|
|
aspectRatio: TILE_ASPECT_RATIO,
|
|
|
|
flex: 0,
|
|
|
|
height: this._getTileDimensions().height,
|
|
|
|
width: null
|
|
|
|
};
|
|
|
|
|
|
|
|
return this._getSortedParticipants()
|
|
|
|
.map(participant => (
|
|
|
|
<Thumbnail
|
|
|
|
disableTint = { true }
|
|
|
|
key = { participant.id }
|
|
|
|
participant = { participant }
|
2019-04-11 10:04:50 +00:00
|
|
|
renderDisplayName = { true }
|
2019-06-19 12:44:39 +00:00
|
|
|
styleOverrides = { styleOverrides }
|
|
|
|
tileView = { true } />));
|
2018-09-13 15:20:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the receiver video quality based on the dimensions of the thumbnails
|
|
|
|
* that are displayed.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
_updateReceiverQuality() {
|
2020-07-15 20:30:44 +00:00
|
|
|
const { height, width } = this._getTileDimensions();
|
2018-09-13 15:20:22 +00:00
|
|
|
|
2020-07-15 20:30:44 +00:00
|
|
|
this.props.dispatch(setTileViewDimensions({
|
|
|
|
thumbnailSize: {
|
|
|
|
height,
|
|
|
|
width
|
|
|
|
}
|
|
|
|
}));
|
2018-09-13 15:20:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Maps (parts of) the redux state to the associated {@code TileView}'s props.
|
|
|
|
*
|
|
|
|
* @param {Object} state - The redux state.
|
|
|
|
* @private
|
2020-06-02 09:03:17 +00:00
|
|
|
* @returns {Props}
|
2018-09-13 15:20:22 +00:00
|
|
|
*/
|
|
|
|
function _mapStateToProps(state) {
|
2020-06-02 09:03:17 +00:00
|
|
|
const responsiveUi = state['features/base/responsive-ui'];
|
|
|
|
|
2018-09-13 15:20:22 +00:00
|
|
|
return {
|
2020-06-02 09:03:17 +00:00
|
|
|
_aspectRatio: responsiveUi.aspectRatio,
|
|
|
|
_height: responsiveUi.clientHeight,
|
|
|
|
_participants: state['features/base/participants'],
|
|
|
|
_width: responsiveUi.clientWidth
|
2018-09-13 15:20:22 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-06-02 09:03:17 +00:00
|
|
|
export default connect(_mapStateToProps)(TileView);
|