You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TileView.js 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. // @flow
  2. import React, { Component } from 'react';
  3. import {
  4. ScrollView,
  5. TouchableWithoutFeedback,
  6. View
  7. } from 'react-native';
  8. import type { Dispatch } from 'redux';
  9. import {
  10. getNearestReceiverVideoQualityLevel,
  11. setMaxReceiverVideoQuality
  12. } from '../../../base/conference';
  13. import { connect } from '../../../base/redux';
  14. import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
  15. import Thumbnail from './Thumbnail';
  16. import styles from './styles';
  17. /**
  18. * The type of the React {@link Component} props of {@link TileView}.
  19. */
  20. type Props = {
  21. /**
  22. * Application's aspect ratio.
  23. */
  24. _aspectRatio: Symbol,
  25. /**
  26. * Application's viewport height.
  27. */
  28. _height: number,
  29. /**
  30. * The participants in the conference.
  31. */
  32. _participants: Array<Object>,
  33. /**
  34. * Application's viewport height.
  35. */
  36. _width: number,
  37. /**
  38. * Invoked to update the receiver video quality.
  39. */
  40. dispatch: Dispatch<any>,
  41. /**
  42. * Callback to invoke when tile view is tapped.
  43. */
  44. onClick: Function
  45. };
  46. /**
  47. * The margin for each side of the tile view. Taken away from the available
  48. * height and width for the tile container to display in.
  49. *
  50. * @private
  51. * @type {number}
  52. */
  53. const MARGIN = 10;
  54. /**
  55. * The aspect ratio the tiles should display in.
  56. *
  57. * @private
  58. * @type {number}
  59. */
  60. const TILE_ASPECT_RATIO = 1;
  61. /**
  62. * Implements a React {@link Component} which displays thumbnails in a two
  63. * dimensional grid.
  64. *
  65. * @extends Component
  66. */
  67. class TileView extends Component<Props> {
  68. /**
  69. * Implements React's {@link Component#componentDidMount}.
  70. *
  71. * @inheritdoc
  72. */
  73. componentDidMount() {
  74. this._updateReceiverQuality();
  75. }
  76. /**
  77. * Implements React's {@link Component#componentDidUpdate}.
  78. *
  79. * @inheritdoc
  80. */
  81. componentDidUpdate() {
  82. this._updateReceiverQuality();
  83. }
  84. /**
  85. * Implements React's {@link Component#render()}.
  86. *
  87. * @inheritdoc
  88. * @returns {ReactElement}
  89. */
  90. render() {
  91. const { _height, _width, onClick } = this.props;
  92. const rowElements = this._groupIntoRows(this._renderThumbnails(), this._getColumnCount());
  93. return (
  94. <ScrollView
  95. style = {{
  96. ...styles.tileView,
  97. height: _height,
  98. width: _width
  99. }}>
  100. <TouchableWithoutFeedback onPress = { onClick }>
  101. <View
  102. style = {{
  103. ...styles.tileViewRows,
  104. minHeight: _height,
  105. minWidth: _width
  106. }}>
  107. { rowElements }
  108. </View>
  109. </TouchableWithoutFeedback>
  110. </ScrollView>
  111. );
  112. }
  113. /**
  114. * Returns how many columns should be displayed for tile view.
  115. *
  116. * @returns {number}
  117. * @private
  118. */
  119. _getColumnCount() {
  120. const participantCount = this.props._participants.length;
  121. // For narrow view, tiles should stack on top of each other for a lonely
  122. // call and a 1:1 call. Otherwise tiles should be grouped into rows of
  123. // two.
  124. if (this.props._aspectRatio === ASPECT_RATIO_NARROW) {
  125. return participantCount >= 3 ? 2 : 1;
  126. }
  127. if (participantCount === 4) {
  128. // In wide view, a four person call should display as a 2x2 grid.
  129. return 2;
  130. }
  131. return Math.min(3, participantCount);
  132. }
  133. /**
  134. * Returns all participants with the local participant at the end.
  135. *
  136. * @private
  137. * @returns {Participant[]}
  138. */
  139. _getSortedParticipants() {
  140. const participants = [];
  141. let localParticipant;
  142. for (const participant of this.props._participants) {
  143. if (participant.local) {
  144. localParticipant = participant;
  145. } else {
  146. participants.push(participant);
  147. }
  148. }
  149. localParticipant && participants.push(localParticipant);
  150. return participants;
  151. }
  152. /**
  153. * Calculate the height and width for the tiles.
  154. *
  155. * @private
  156. * @returns {Object}
  157. */
  158. _getTileDimensions() {
  159. const { _height, _participants, _width } = this.props;
  160. const columns = this._getColumnCount();
  161. const participantCount = _participants.length;
  162. const heightToUse = _height - (MARGIN * 2);
  163. const widthToUse = _width - (MARGIN * 2);
  164. let tileWidth;
  165. // If there is going to be at least two rows, ensure that at least two
  166. // rows display fully on screen.
  167. if (participantCount / columns > 1) {
  168. tileWidth = Math.min(widthToUse / columns, heightToUse / 2);
  169. } else {
  170. tileWidth = Math.min(widthToUse / columns, heightToUse);
  171. }
  172. return {
  173. height: tileWidth / TILE_ASPECT_RATIO,
  174. width: tileWidth
  175. };
  176. }
  177. /**
  178. * Splits a list of thumbnails into React Elements with a maximum of
  179. * {@link rowLength} thumbnails in each.
  180. *
  181. * @param {Array} thumbnails - The list of thumbnails that should be split
  182. * into separate row groupings.
  183. * @param {number} rowLength - How many thumbnails should be in each row.
  184. * @private
  185. * @returns {ReactElement[]}
  186. */
  187. _groupIntoRows(thumbnails, rowLength) {
  188. const rowElements = [];
  189. for (let i = 0; i < thumbnails.length; i++) {
  190. if (i % rowLength === 0) {
  191. const thumbnailsInRow = thumbnails.slice(i, i + rowLength);
  192. rowElements.push(
  193. <View
  194. key = { rowElements.length }
  195. style = { styles.tileViewRow }>
  196. { thumbnailsInRow }
  197. </View>
  198. );
  199. }
  200. }
  201. return rowElements;
  202. }
  203. /**
  204. * Creates React Elements to display each participant in a thumbnail. Each
  205. * tile will be.
  206. *
  207. * @private
  208. * @returns {ReactElement[]}
  209. */
  210. _renderThumbnails() {
  211. const styleOverrides = {
  212. aspectRatio: TILE_ASPECT_RATIO,
  213. flex: 0,
  214. height: this._getTileDimensions().height,
  215. width: null
  216. };
  217. return this._getSortedParticipants()
  218. .map(participant => (
  219. <Thumbnail
  220. disableTint = { true }
  221. key = { participant.id }
  222. participant = { participant }
  223. renderDisplayName = { true }
  224. styleOverrides = { styleOverrides }
  225. tileView = { true } />));
  226. }
  227. /**
  228. * Sets the receiver video quality based on the dimensions of the thumbnails
  229. * that are displayed.
  230. *
  231. * @private
  232. * @returns {void}
  233. */
  234. _updateReceiverQuality() {
  235. const { height } = this._getTileDimensions();
  236. const qualityLevel = getNearestReceiverVideoQualityLevel(height);
  237. this.props.dispatch(setMaxReceiverVideoQuality(qualityLevel));
  238. }
  239. }
  240. /**
  241. * Maps (parts of) the redux state to the associated {@code TileView}'s props.
  242. *
  243. * @param {Object} state - The redux state.
  244. * @private
  245. * @returns {Props}
  246. */
  247. function _mapStateToProps(state) {
  248. const responsiveUi = state['features/base/responsive-ui'];
  249. return {
  250. _aspectRatio: responsiveUi.aspectRatio,
  251. _height: responsiveUi.clientHeight,
  252. _participants: state['features/base/participants'],
  253. _width: responsiveUi.clientWidth
  254. };
  255. }
  256. export default connect(_mapStateToProps)(TileView);