Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. // @flow
  2. import React, { PureComponent } from 'react';
  3. import {
  4. FlatList,
  5. SafeAreaView,
  6. TouchableWithoutFeedback
  7. } from 'react-native';
  8. import { withSafeAreaInsets } from 'react-native-safe-area-context';
  9. import type { Dispatch } from 'redux';
  10. import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants';
  11. import { connect } from '../../../base/redux';
  12. import { getHideSelfView } from '../../../base/settings/functions.any';
  13. import { setVisibleRemoteParticipants } from '../../actions.web';
  14. import Thumbnail from './Thumbnail';
  15. import styles from './styles';
  16. /**
  17. * The type of the React {@link Component} props of {@link TileView}.
  18. */
  19. type Props = {
  20. /**
  21. * Application's aspect ratio.
  22. */
  23. _aspectRatio: Symbol,
  24. /**
  25. * The number of columns.
  26. */
  27. _columns: number,
  28. /**
  29. * Whether or not to hide the self view.
  30. */
  31. _disableSelfView: boolean,
  32. /**
  33. * Application's viewport height.
  34. */
  35. _height: number,
  36. /**
  37. * The local participant.
  38. */
  39. _localParticipant: Object,
  40. /**
  41. * The number of participants in the conference.
  42. */
  43. _participantCount: number,
  44. /**
  45. * An array with the IDs of the remote participants in the conference.
  46. */
  47. _remoteParticipants: Array<string>,
  48. /**
  49. * The thumbnail height.
  50. */
  51. _thumbnailHeight: number,
  52. /**
  53. * Application's viewport height.
  54. */
  55. _width: number,
  56. /**
  57. * Invoked to update the receiver video quality.
  58. */
  59. dispatch: Dispatch<any>,
  60. /**
  61. * Object containing the safe area insets.
  62. */
  63. insets: Object,
  64. /**
  65. * Callback to invoke when tile view is tapped.
  66. */
  67. onClick: Function
  68. };
  69. /**
  70. * An empty array. The purpose of the constant is to use the same reference every time we need an empty array.
  71. * This will prevent unnecessary re-renders.
  72. */
  73. const EMPTY_ARRAY = [];
  74. /**
  75. * Implements a React {@link PureComponent} which displays thumbnails in a two
  76. * dimensional grid.
  77. *
  78. * @augments PureComponent
  79. */
  80. class TileView extends PureComponent<Props> {
  81. /**
  82. * The styles for the content container of the FlatList.
  83. */
  84. _contentContainerStyles: Object;
  85. /**
  86. * The styles for the FlatList.
  87. */
  88. _flatListStyles: Object;
  89. /**
  90. * The FlatList's viewabilityConfig.
  91. */
  92. _viewabilityConfig: Object;
  93. /**
  94. * Creates new TileView component.
  95. *
  96. * @param {Props} props - The props of the component.
  97. */
  98. constructor(props: Props) {
  99. super(props);
  100. this._keyExtractor = this._keyExtractor.bind(this);
  101. this._onViewableItemsChanged = this._onViewableItemsChanged.bind(this);
  102. this._renderThumbnail = this._renderThumbnail.bind(this);
  103. this._viewabilityConfig = {
  104. itemVisiblePercentThreshold: 30,
  105. minimumViewTime: 500
  106. };
  107. this._flatListStyles = {
  108. ...styles.flatListTileView
  109. };
  110. this._contentContainerStyles = {
  111. ...styles.contentContainer,
  112. paddingBottom: this.props.insets?.bottom || 0
  113. };
  114. }
  115. _keyExtractor: string => string;
  116. /**
  117. * Returns a key for a passed item of the list.
  118. *
  119. * @param {string} item - The user ID.
  120. * @returns {string} - The user ID.
  121. */
  122. _keyExtractor(item) {
  123. return item;
  124. }
  125. _onViewableItemsChanged: Object => void;
  126. /**
  127. * A handler for visible items changes.
  128. *
  129. * @param {Object} data - The visible items data.
  130. * @param {Array<Object>} data.viewableItems - The visible items array.
  131. * @returns {void}
  132. */
  133. _onViewableItemsChanged({ viewableItems = [] }: { viewableItems: Array<Object> }) {
  134. const { _disableSelfView } = this.props;
  135. if (viewableItems[0]?.index === 0 && !_disableSelfView) {
  136. // Skip the local thumbnail.
  137. viewableItems.shift();
  138. }
  139. if (viewableItems.length === 0) {
  140. // User might be fast-scrolling, it will stabilize.
  141. return;
  142. }
  143. // We are off by one in the remote participants array.
  144. const startIndex = viewableItems[0].index - (_disableSelfView ? 0 : 1);
  145. const endIndex = viewableItems[viewableItems.length - 1].index - (_disableSelfView ? 0 : 1);
  146. this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
  147. }
  148. /**
  149. * Implements React's {@link Component#render()}.
  150. *
  151. * @inheritdoc
  152. * @returns {ReactElement}
  153. */
  154. render() {
  155. const { _columns, _height, _thumbnailHeight, _width, onClick } = this.props;
  156. const participants = this._getSortedParticipants();
  157. const initialRowsToRender = Math.ceil(_height / (_thumbnailHeight + (2 * styles.thumbnail.margin)));
  158. if (this._flatListStyles.minHeight !== _height || this._flatListStyles.minWidth !== _width) {
  159. this._flatListStyles = {
  160. ...styles.flatListTileView,
  161. minHeight: _height,
  162. minWidth: _width
  163. };
  164. }
  165. if (this._contentContainerStyles.minHeight !== _height || this._contentContainerStyles.minWidth !== _width) {
  166. this._contentContainerStyles = {
  167. ...styles.contentContainer,
  168. minHeight: _height,
  169. minWidth: _width,
  170. paddingBottom: this.props.insets?.bottom || 0
  171. };
  172. }
  173. return (
  174. <TouchableWithoutFeedback onPress = { onClick }>
  175. <SafeAreaView style = { styles.flatListContainer }>
  176. <FlatList
  177. bounces = { false }
  178. contentContainerStyle = { this._contentContainerStyles }
  179. data = { participants }
  180. horizontal = { false }
  181. initialNumToRender = { initialRowsToRender }
  182. key = { _columns }
  183. keyExtractor = { this._keyExtractor }
  184. numColumns = { _columns }
  185. onViewableItemsChanged = { this._onViewableItemsChanged }
  186. renderItem = { this._renderThumbnail }
  187. showsHorizontalScrollIndicator = { false }
  188. showsVerticalScrollIndicator = { false }
  189. style = { this._flatListStyles }
  190. viewabilityConfig = { this._viewabilityConfig }
  191. windowSize = { 2 } />
  192. </SafeAreaView>
  193. </TouchableWithoutFeedback>
  194. );
  195. }
  196. /**
  197. * Returns all participants with the local participant at the end.
  198. *
  199. * @private
  200. * @returns {Participant[]}
  201. */
  202. _getSortedParticipants() {
  203. const { _localParticipant, _remoteParticipants, _disableSelfView } = this.props;
  204. if (!_localParticipant) {
  205. return EMPTY_ARRAY;
  206. }
  207. if (_disableSelfView) {
  208. return _remoteParticipants;
  209. }
  210. return [ _localParticipant?.id, ..._remoteParticipants ];
  211. }
  212. _renderThumbnail: Object => Object;
  213. /**
  214. * Creates React Element to display each participant in a thumbnail.
  215. *
  216. * @private
  217. * @returns {ReactElement}
  218. */
  219. _renderThumbnail({ item/* , index , separators */ }) {
  220. const { _thumbnailHeight } = this.props;
  221. return (
  222. <Thumbnail
  223. height = { _thumbnailHeight }
  224. key = { item }
  225. participantID = { item }
  226. renderDisplayName = { true }
  227. tileView = { true } />)
  228. ;
  229. }
  230. }
  231. /**
  232. * Maps (parts of) the redux state to the associated {@code TileView}'s props.
  233. *
  234. * @param {Object} state - The redux state.
  235. * @param {Object} ownProps - Component props.
  236. * @private
  237. * @returns {Props}
  238. */
  239. function _mapStateToProps(state, ownProps) {
  240. const responsiveUi = state['features/base/responsive-ui'];
  241. const { remoteParticipants, tileViewDimensions } = state['features/filmstrip'];
  242. const disableSelfView = getHideSelfView(state);
  243. const { height } = tileViewDimensions.thumbnailSize;
  244. const { columns } = tileViewDimensions;
  245. return {
  246. _aspectRatio: responsiveUi.aspectRatio,
  247. _columns: columns,
  248. _disableSelfView: disableSelfView,
  249. _height: responsiveUi.clientHeight - (ownProps.insets?.top || 0),
  250. _insets: ownProps.insets,
  251. _localParticipant: getLocalParticipant(state),
  252. _participantCount: getParticipantCountWithFake(state),
  253. _remoteParticipants: remoteParticipants,
  254. _thumbnailHeight: height,
  255. _width: responsiveUi.clientWidth - (ownProps.insets?.right || 0) - (ownProps.insets?.left || 0)
  256. };
  257. }
  258. export default withSafeAreaInsets(connect(_mapStateToProps)(TileView));