// @flow import React from 'react'; import { TouchableOpacity, View } from 'react-native'; import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet'; import { connect } from '../../../base/redux'; import { isNarrowAspectRatio, makeAspectRatioAware } from '../../../base/responsive-ui'; import { RecordingExpandedLabel } from '../../../recording'; import { TranscribingExpandedLabel } from '../../../transcribing'; import { VideoQualityExpandedLabel } from '../../../video-quality'; import AbstractLabels, { _abstractMapStateToProps, type Props as AbstractLabelsProps } from '../AbstractLabels'; import { shouldDisplayNotifications } from '../../functions'; import styles from './styles'; /** * The type of the React {@code Component} props of {@link Labels}. */ type Props = AbstractLabelsProps & { /** * Function to translate i18n labels. */ t: Function, /** * The indicator which determines whether the UI is reduced (to accommodate * smaller display areas). * * @private */ _reducedUI: boolean, /** * True if the labels should be visible, false otherwise. */ _visible: boolean }; type State = { /** * Layout object of the outermost container. For stucture please see: * https://facebook.github.io/react-native/docs/view#onlayout */ containerLayout: ?Object, /** * Layout objects of the individual labels. This data type contains the same * structure as the layout is defined here: * https://facebook.github.io/react-native/docs/view#onlayout * but keyed with the ID of the label its layout it contains. E.g. * * { * transcribing: { * { layout: { x, y, width, height } } * }, * ... * } */ labelLayouts: Object, /** * Position of the label to render the {@code ExpandedLabel} to. */ parentPosition: ?number, /** * String to show which {@code ExpandedLabel} to be shown. (Equals to the * label IDs below.) */ visibleExpandedLabel: ?string } const LABEL_ID_QUALITY = 'quality'; const LABEL_ID_RECORDING = 'recording'; const LABEL_ID_STREAMING = 'streaming'; const LABEL_ID_TRANSCRIBING = 'transcribing'; /** * The {@code ExpandedLabel} components to be rendered for the individual * {@code Label}s. */ const EXPANDED_LABELS = { quality: VideoQualityExpandedLabel, recording: { component: RecordingExpandedLabel, props: { mode: JitsiRecordingConstants.mode.FILE } }, streaming: { component: RecordingExpandedLabel, props: { mode: JitsiRecordingConstants.mode.STREAM } }, transcribing: TranscribingExpandedLabel }; /** * Timeout to hide the {@ExpandedLabel}. */ const EXPANDED_LABEL_TIMEOUT = 5000; /** * A container that renders the conference indicators, if any. */ class Labels extends AbstractLabels { /** * Timeout for the expanded labels to disappear. */ expandedLabelTimeout: TimeoutID; /** * Instantiates a new instance of {@code Labels}. * * @inheritdoc */ constructor(props: Props) { super(props); this.state = { containerLayout: undefined, labelLayouts: {}, parentPosition: undefined, visibleExpandedLabel: undefined }; this._onTopViewLayout = this._onTopViewLayout.bind(this); } /** * Implements React {@code Component}'s componentWillUnmount. * * @inheritdoc */ componentWillUnmount() { clearTimeout(this.expandedLabelTimeout); } /** * Implements React {@code Component}'s render. * * @inheritdoc */ render() { if (!this.props._visible) { return null; } const wide = !isNarrowAspectRatio(this); const { _filmstripVisible, _reducedUI } = this.props; return ( { this._renderRecordingLabel( JitsiRecordingConstants.mode.FILE) } { this._renderRecordingLabel( JitsiRecordingConstants.mode.STREAM) } { this._renderTranscribingLabel() } {/* * Emil, Lyubomir, Nichole, and Zoli said that the Labels * should not be rendered in Picture-in-Picture. Saul * argued that the recording Labels should be rendered. As * a temporary compromise, don't render the * VideoQualityLabel at least because it's not that * important. */ _reducedUI || ( { this._renderVideoQualityLabel() } ) } { this._renderExpandedLabel() } ); } /** * Creates a function to be invoked when the onLayout of the touchables are * triggered. * * @param {string} label - The identifier of the label that's onLayout is * triggered. * @returns {Function} */ _createOnLayout(label) { return ({ nativeEvent: { layout } }) => { const { labelLayouts } = this.state; const updatedLayout = {}; updatedLayout[label] = layout; this.setState({ labelLayouts: { ...labelLayouts, ...updatedLayout } }); }; } /** * Creates a function to be invoked when the onPress of the touchables are * triggered. * * @param {string} label - The identifier of the label that's onLayout is * triggered. * @returns {Function} */ _createOnPress(label) { return () => { const { containerLayout, labelLayouts } = this.state; let { visibleExpandedLabel } = this.state; if (containerLayout) { const labelLayout = labelLayouts[label]; // This calculation has to be changed if the labels are not // positioned right anymore. const right = containerLayout.width - labelLayout.x; visibleExpandedLabel = visibleExpandedLabel === label ? undefined : label; clearTimeout(this.expandedLabelTimeout); this.setState({ parentPosition: right, visibleExpandedLabel }); if (visibleExpandedLabel) { this.expandedLabelTimeout = setTimeout(() => { this.setState({ visibleExpandedLabel: undefined }); }, EXPANDED_LABEL_TIMEOUT); } } }; } _onTopViewLayout: Object => void /** * Invoked when the View containing the {@code Label}s is laid out. * * @param {Object} layout - The native layout object. * @returns {void} */ _onTopViewLayout({ nativeEvent: { layout } }) { this.setState({ containerLayout: layout }); } /** * Rendes the expanded (explaining) label for the label that was touched. * * @returns {React$Element} */ _renderExpandedLabel() { const { parentPosition, visibleExpandedLabel } = this.state; if (visibleExpandedLabel) { const expandedLabel = EXPANDED_LABELS[visibleExpandedLabel]; if (expandedLabel) { const component = expandedLabel.component || expandedLabel; const expandedLabelProps = expandedLabel.props || {}; return React.createElement(component, { ...expandedLabelProps, parentPosition }); } } return null; } _renderRecordingLabel: string => React$Element<*>; _renderTranscribingLabel: () => React$Element<*> _renderVideoQualityLabel: () => React$Element<*>; } /** * Maps (parts of) the redux state to the associated * {@code Labels}'s props. * * @param {Object} state - The redux state. * @private * @returns {{ * _filmstripVisible: boolean, * _reducedUI: boolean, * _visible: boolean * }} */ function _mapStateToProps(state) { return { ..._abstractMapStateToProps(state), _reducedUI: state['features/base/responsive-ui'].reducedUI, _visible: !shouldDisplayNotifications(state) }; } export default connect(_mapStateToProps)(makeAspectRatioAware(Labels));