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.5KB

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