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.

Labels.native.js 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. // @flow
  2. import React from 'react';
  3. import { TouchableOpacity, View } from 'react-native';
  4. import { connect } from 'react-redux';
  5. import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
  6. import {
  7. RecordingExpandedLabel
  8. } from '../../recording';
  9. import {
  10. isNarrowAspectRatio,
  11. makeAspectRatioAware
  12. } from '../../base/responsive-ui';
  13. import { TranscribingExpandedLabel } from '../../transcribing';
  14. import { VideoQualityExpandedLabel } from '../../video-quality';
  15. import AbstractLabels, {
  16. _abstractMapStateToProps,
  17. type Props as AbstractLabelsProps
  18. } from './AbstractLabels';
  19. import styles from './styles';
  20. /**
  21. * The type of the React {@code Component} props of {@link Labels}.
  22. */
  23. type Props = AbstractLabelsProps & {
  24. /**
  25. * Function to translate i18n labels.
  26. */
  27. t: Function,
  28. /**
  29. * The indicator which determines whether the UI is reduced (to accommodate
  30. * smaller display areas).
  31. *
  32. * @private
  33. */
  34. _reducedUI: boolean
  35. };
  36. type State = {
  37. /**
  38. * Layout object of the outermost container. For stucture please see:
  39. * https://facebook.github.io/react-native/docs/view#onlayout
  40. */
  41. containerLayout: ?Object,
  42. /**
  43. * Layout objects of the individual labels. This data type contains the same
  44. * structure as the layout is defined here:
  45. * https://facebook.github.io/react-native/docs/view#onlayout
  46. * but keyed with the ID of the label its layout it contains. E.g.
  47. *
  48. * {
  49. * transcribing: {
  50. * { layout: { x, y, width, height } }
  51. * },
  52. * ...
  53. * }
  54. */
  55. labelLayouts: Object,
  56. /**
  57. * Position of the label to render the {@code ExpandedLabel} to.
  58. */
  59. parentPosition: ?number,
  60. /**
  61. * String to show which {@code ExpandedLabel} to be shown. (Equals to the
  62. * label IDs below.)
  63. */
  64. visibleExpandedLabel: ?string
  65. }
  66. const LABEL_ID_QUALITY = 'quality';
  67. const LABEL_ID_RECORDING = 'recording';
  68. const LABEL_ID_STREAMING = 'streaming';
  69. const LABEL_ID_TRANSCRIBING = 'transcribing';
  70. /**
  71. * The {@code ExpandedLabel} components to be rendered for the individual
  72. * {@code Label}s.
  73. */
  74. const EXPANDED_LABELS = {
  75. quality: VideoQualityExpandedLabel,
  76. recording: {
  77. component: RecordingExpandedLabel,
  78. props: {
  79. mode: JitsiRecordingConstants.mode.FILE
  80. }
  81. },
  82. streaming: {
  83. component: RecordingExpandedLabel,
  84. props: {
  85. mode: JitsiRecordingConstants.mode.STREAM
  86. }
  87. },
  88. transcribing: TranscribingExpandedLabel
  89. };
  90. /**
  91. * Timeout to hide the {@ExpandedLabel}.
  92. */
  93. const EXPANDED_LABEL_TIMEOUT = 5000;
  94. /**
  95. * A container that renders the conference indicators, if any.
  96. */
  97. class Labels extends AbstractLabels<Props, State> {
  98. /**
  99. * Timeout for the expanded labels to disappear.
  100. */
  101. expandedLabelTimeout: TimeoutID;
  102. /**
  103. * Instantiates a new instance of {@code Labels}.
  104. *
  105. * @inheritdoc
  106. */
  107. constructor(props: Props) {
  108. super(props);
  109. this.state = {
  110. containerLayout: undefined,
  111. labelLayouts: {},
  112. parentPosition: undefined,
  113. visibleExpandedLabel: undefined
  114. };
  115. this._onTopViewLayout = this._onTopViewLayout.bind(this);
  116. }
  117. /**
  118. * Implements React {@code Component}'s componentWillUnmount.
  119. *
  120. * @inheritdoc
  121. */
  122. componentWillUnmount() {
  123. clearTimeout(this.expandedLabelTimeout);
  124. }
  125. /**
  126. * Implements React {@code Component}'s render.
  127. *
  128. * @inheritdoc
  129. */
  130. render() {
  131. const wide = !isNarrowAspectRatio(this);
  132. const { _filmstripVisible, _reducedUI } = this.props;
  133. return (
  134. <View
  135. pointerEvents = 'box-none'
  136. style = { styles.labelWrapper }>
  137. <View
  138. onLayout = { this._onTopViewLayout }
  139. pointerEvents = 'box-none'
  140. style = { [
  141. styles.indicatorContainer,
  142. wide && _filmstripVisible
  143. && styles.indicatorContainerWide
  144. ] }>
  145. <TouchableOpacity
  146. onLayout = { this._createOnLayout(LABEL_ID_RECORDING) }
  147. onPress = { this._createOnPress(LABEL_ID_RECORDING) } >
  148. {
  149. this._renderRecordingLabel(
  150. JitsiRecordingConstants.mode.FILE)
  151. }
  152. </TouchableOpacity>
  153. <TouchableOpacity
  154. onLayout = { this._createOnLayout(LABEL_ID_STREAMING) }
  155. onPress = { this._createOnPress(LABEL_ID_STREAMING) } >
  156. {
  157. this._renderRecordingLabel(
  158. JitsiRecordingConstants.mode.STREAM)
  159. }
  160. </TouchableOpacity>
  161. <TouchableOpacity
  162. onLayout = {
  163. this._createOnLayout(LABEL_ID_TRANSCRIBING)
  164. }
  165. onPress = {
  166. this._createOnPress(LABEL_ID_TRANSCRIBING)
  167. } >
  168. {
  169. this._renderTranscribingLabel()
  170. }
  171. </TouchableOpacity>
  172. {/*
  173. * Emil, Lyubomir, Nichole, and Zoli said that the Labels
  174. * should not be rendered in Picture-in-Picture. Saul
  175. * argued that the recording Labels should be rendered. As
  176. * a temporary compromise, don't render the
  177. * VideoQualityLabel at least because it's not that
  178. * important.
  179. */
  180. _reducedUI || (
  181. <TouchableOpacity
  182. onLayout = {
  183. this._createOnLayout(LABEL_ID_QUALITY) }
  184. onPress = {
  185. this._createOnPress(LABEL_ID_QUALITY) } >
  186. { this._renderVideoQualityLabel() }
  187. </TouchableOpacity>
  188. )
  189. }
  190. </View>
  191. <View
  192. style = { [
  193. styles.indicatorContainer,
  194. wide && _filmstripVisible
  195. && styles.indicatorContainerWide
  196. ] }>
  197. {
  198. this._renderExpandedLabel()
  199. }
  200. </View>
  201. </View>
  202. );
  203. }
  204. /**
  205. * Creates a function to be invoked when the onLayout of the touchables are
  206. * triggered.
  207. *
  208. * @param {string} label - The identifier of the label that's onLayout is
  209. * triggered.
  210. * @returns {Function}
  211. */
  212. _createOnLayout(label) {
  213. return ({ nativeEvent: { layout } }) => {
  214. const { labelLayouts } = this.state;
  215. const updatedLayout = {};
  216. updatedLayout[label] = layout;
  217. this.setState({
  218. labelLayouts: {
  219. ...labelLayouts,
  220. ...updatedLayout
  221. }
  222. });
  223. };
  224. }
  225. /**
  226. * Creates a function to be invoked when the onPress of the touchables are
  227. * triggered.
  228. *
  229. * @param {string} label - The identifier of the label that's onLayout is
  230. * triggered.
  231. * @returns {Function}
  232. */
  233. _createOnPress(label) {
  234. return () => {
  235. const {
  236. containerLayout,
  237. labelLayouts
  238. } = this.state;
  239. let { visibleExpandedLabel } = this.state;
  240. if (containerLayout) {
  241. const labelLayout = labelLayouts[label];
  242. // This calculation has to be changed if the labels are not
  243. // positioned right anymore.
  244. const right = containerLayout.width - labelLayout.x;
  245. visibleExpandedLabel
  246. = visibleExpandedLabel === label ? undefined : label;
  247. clearTimeout(this.expandedLabelTimeout);
  248. this.setState({
  249. parentPosition: right,
  250. visibleExpandedLabel
  251. });
  252. if (visibleExpandedLabel) {
  253. this.expandedLabelTimeout = setTimeout(() => {
  254. this.setState({
  255. visibleExpandedLabel: undefined
  256. });
  257. }, EXPANDED_LABEL_TIMEOUT);
  258. }
  259. }
  260. };
  261. }
  262. _onTopViewLayout: Object => void
  263. /**
  264. * Invoked when the View containing the {@code Label}s is laid out.
  265. *
  266. * @param {Object} layout - The native layout object.
  267. * @returns {void}
  268. */
  269. _onTopViewLayout({ nativeEvent: { layout } }) {
  270. this.setState({
  271. containerLayout: layout
  272. });
  273. }
  274. /**
  275. * Rendes the expanded (explaining) label for the label that was touched.
  276. *
  277. * @returns {React$Element}
  278. */
  279. _renderExpandedLabel() {
  280. const { parentPosition, visibleExpandedLabel } = this.state;
  281. if (visibleExpandedLabel) {
  282. const expandedLabel = EXPANDED_LABELS[visibleExpandedLabel];
  283. if (expandedLabel) {
  284. const component = expandedLabel.component || expandedLabel;
  285. const expandedLabelProps = expandedLabel.props || {};
  286. return React.createElement(component, {
  287. ...expandedLabelProps,
  288. parentPosition
  289. });
  290. }
  291. }
  292. return null;
  293. }
  294. _renderRecordingLabel: string => React$Element<*>;
  295. _renderTranscribingLabel: () => React$Element<*>
  296. _renderVideoQualityLabel: () => React$Element<*>;
  297. }
  298. /**
  299. * Maps (parts of) the redux state to the associated
  300. * {@code Labels}'s props.
  301. *
  302. * @param {Object} state - The redux state.
  303. * @private
  304. * @returns {{
  305. * _filmstripVisible: boolean,
  306. * _reducedUI: boolean
  307. * }}
  308. */
  309. function _mapStateToProps(state) {
  310. return {
  311. ..._abstractMapStateToProps(state),
  312. _reducedUI: state['features/base/responsive-ui'].reducedUI
  313. };
  314. }
  315. export default connect(_mapStateToProps)(makeAspectRatioAware(Labels));