| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 | // @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<Props, State> {
    /**
     * 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 (
            <View
                pointerEvents = 'box-none'
                style = { styles.labelWrapper }>
                <View
                    onLayout = { this._onTopViewLayout }
                    pointerEvents = 'box-none'
                    style = { [
                        styles.indicatorContainer,
                        wide && _filmstripVisible
                            && styles.indicatorContainerWide
                    ] }>
                    <TouchableOpacity
                        onLayout = { this._createOnLayout(LABEL_ID_RECORDING) }
                        onPress = { this._createOnPress(LABEL_ID_RECORDING) } >
                        {
                            this._renderRecordingLabel(
                                JitsiRecordingConstants.mode.FILE)
                        }
                    </TouchableOpacity>
                    <TouchableOpacity
                        onLayout = { this._createOnLayout(LABEL_ID_STREAMING) }
                        onPress = { this._createOnPress(LABEL_ID_STREAMING) } >
                        {
                            this._renderRecordingLabel(
                                JitsiRecordingConstants.mode.STREAM)
                        }
                    </TouchableOpacity>
                    <TouchableOpacity
                        onLayout = {
                            this._createOnLayout(LABEL_ID_TRANSCRIBING)
                        }
                        onPress = {
                            this._createOnPress(LABEL_ID_TRANSCRIBING)
                        } >
                        {
                            this._renderTranscribingLabel()
                        }
                    </TouchableOpacity>
                    {/*
                      * 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 || (
                            <TouchableOpacity
                                onLayout = {
                                    this._createOnLayout(LABEL_ID_QUALITY) }
                                onPress = {
                                    this._createOnPress(LABEL_ID_QUALITY) } >
                                { this._renderVideoQualityLabel() }
                            </TouchableOpacity>
                        )
                    }
                </View>
                <View
                    style = { [
                        styles.indicatorContainer,
                        wide && _filmstripVisible
                            && styles.indicatorContainerWide
                    ] }>
                    {
                        this._renderExpandedLabel()
                    }
                </View>
            </View>
        );
    }
    /**
     * 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));
 |