| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 | // @flow
import React, { PureComponent } from 'react';
import {
    KeyboardAvoidingView,
    Platform,
    Text,
    TextInput,
    View
} from 'react-native';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { ColorSchemeRegistry } from '../../../../base/color-scheme';
import {
    FIELD_UNDERLINE,
    CustomSubmitDialog
} from '../../../../base/dialog';
import { getFeatureFlag, MEETING_PASSWORD_ENABLED } from '../../../../base/flags';
import { translate } from '../../../../base/i18n';
import { isLocalParticipantModerator } from '../../../../base/participants';
import { StyleType } from '../../../../base/styles';
import { toggleLobbyMode } from '../../../../lobby/actions.any';
import LobbyModeSwitch
    from '../../../../lobby/components/native/LobbyModeSwitch';
import { LOCKED_LOCALLY } from '../../../../room-lock';
import {
    endRoomLockRequest,
    unlockRoom
} from '../../../../room-lock/actions';
import RoomLockSwitch from '../../../../room-lock/components/RoomLockSwitch';
/**
 * The style of the {@link TextInput} rendered by {@code SecurityDialog}. As it
 * requests the entry of a password, {@code TextInput} automatically correcting
 * the entry of the password is a pain to deal with as a user.
 */
const _TEXT_INPUT_PROPS = {
    autoCapitalize: 'none',
    autoCorrect: false
};
/**
 * The type of the React {@code Component} props of {@link SecurityDialog}.
 */
type Props = {
    /**
     * The JitsiConference which requires a password.
     */
    _conference: Object,
    /**
     * The color-schemed stylesheet of the feature.
     */
    _dialogStyles: StyleType,
    /**
     * Whether the local user is the moderator.
     */
    _isModerator: boolean,
    /**
     * State of the lobby mode.
     */
    _lobbyEnabled: boolean,
    /**
     * Whether the lobby mode switch is available or not.
     */
    _lobbyModeSwitchVisible: boolean,
    /**
     * The value for how the conference is locked (or undefined if not locked)
     * as defined by room-lock constants.
     */
    _locked: string,
    /**
     * Checks if the conference room is locked or not.
     */
    _lockedConference: boolean,
    /**
     * The current known password for the JitsiConference.
     */
    _password: string,
    /**
     * Number of digits used in the room-lock password.
     */
    _passwordNumberOfDigits: number,
    /**
     * Whether the room lock switch is available or not.
     */
    _roomLockSwitchVisible: boolean,
    /**
     * The color-schemed stylesheet of the security dialog feature.
     */
    _securityDialogStyles: StyleType,
    /**
     * Redux store dispatch function.
     */
    dispatch: Dispatch<any>,
    /**
     * Invoked to obtain translated strings.
     */
    t: Function
};
/**
 * The type of the React {@code Component} state of {@link SecurityDialog}.
 */
type State = {
    /**
     * Password added by the participant for room lock.
     */
    passwordInputValue: string,
    /**
     * Shows an input or a message.
     */
    showElement: boolean
};
/**
 * Component that renders the security options dialog.
 *
 * @returns {React$Element<any>}
 */
class SecurityDialog extends PureComponent<Props, State> {
    /**
     * Instantiates a new {@code SecurityDialog}.
     *
     * @inheritdoc
     */
    constructor(props: Props) {
        super(props);
        this.state = {
            passwordInputValue: '',
            showElement: props._locked === LOCKED_LOCALLY || false
        };
        this._onChangeText = this._onChangeText.bind(this);
        this._onSubmit = this._onSubmit.bind(this);
        this._onToggleLobbyMode = this._onToggleLobbyMode.bind(this);
        this._onToggleRoomLock = this._onToggleRoomLock.bind(this);
    }
    /**
     * Implements {@code SecurityDialog.render}.
     *
     * @inheritdoc
     */
    render() {
        return (
            <CustomSubmitDialog
                onSubmit = { this._onSubmit }>
                <KeyboardAvoidingView
                    behavior =
                        {
                            Platform.OS === 'ios'
                                ? 'padding' : 'height'
                        }
                    enabled = { true }>
                    { this._renderLobbyMode() }
                    { this._renderRoomLock() }
                </KeyboardAvoidingView>
            </CustomSubmitDialog>
        );
    }
    /**
     * Renders lobby mode.
     *
     * @returns {ReactElement}
     * @private
     */
    _renderLobbyMode() {
        const {
            _lobbyEnabled,
            _lobbyModeSwitchVisible,
            _securityDialogStyles,
            t
        } = this.props;
        if (!_lobbyModeSwitchVisible) {
            return null;
        }
        return (
            <View>
                <Text style = { _securityDialogStyles.title } >
                    { t('lobby.dialogTitle') }
                </Text>
                <Text style = { _securityDialogStyles.text } >
                    { t('lobby.enableDialogText') }
                </Text>
                <LobbyModeSwitch
                    lobbyEnabled = { _lobbyEnabled }
                    onToggleLobbyMode = { this._onToggleLobbyMode } />
            </View>
        );
    }
    /**
     * Renders room lock.
     *
     * @returns {ReactElement}
     * @private
     */
    _renderRoomLock() {
        const {
            _isModerator,
            _locked,
            _lockedConference,
            _roomLockSwitchVisible,
            _securityDialogStyles,
            t
        } = this.props;
        const { showElement } = this.state;
        if (!_roomLockSwitchVisible) {
            return null;
        }
        return (
            <View>
                <Text style = { _securityDialogStyles.title } >
                    { t('dialog.lockRoom') }
                </Text>
                <Text style = { _securityDialogStyles.text } >
                    { t('security.about') }
                </Text>
                <RoomLockSwitch
                    disabled = { !_isModerator }
                    locked = { _locked }
                    onToggleRoomLock = { this._onToggleRoomLock }
                    toggleRoomLock = { showElement || _lockedConference } />
                { this._renderRoomLockMessage() }
            </View>
        );
    }
    /**
     * Renders room lock text input/message.
     *
     * @returns {ReactElement}
     * @private
     */
    _renderRoomLockMessage() {
        let textInputProps = _TEXT_INPUT_PROPS;
        const {
            _isModerator,
            _locked,
            _password,
            _passwordNumberOfDigits,
            _securityDialogStyles,
            t
        } = this.props;
        const { passwordInputValue, showElement } = this.state;
        if (_passwordNumberOfDigits) {
            textInputProps = {
                ...textInputProps,
                keyboardType: 'numeric',
                maxLength: _passwordNumberOfDigits
            };
        }
        if (!_isModerator) {
            return null;
        }
        if (showElement) {
            if (typeof _locked === 'undefined') {
                return (
                    <TextInput
                        onChangeText = { this._onChangeText }
                        placeholder = { t('lobby.passwordField') }
                        style = { _securityDialogStyles.field }
                        underlineColorAndroid = { FIELD_UNDERLINE }
                        value = { passwordInputValue }
                        { ...textInputProps } />
                );
            } else if (_locked) {
                if (_locked === LOCKED_LOCALLY && typeof _password !== 'undefined') {
                    return (
                        <TextInput
                            onChangeText = { this._onChangeText }
                            placeholder = { _password }
                            style = { _securityDialogStyles.field }
                            underlineColorAndroid = { FIELD_UNDERLINE }
                            value = { passwordInputValue }
                            { ...textInputProps } />
                    );
                }
            }
        }
    }
    _onToggleLobbyMode: () => void;
    /**
     * Handles the enable-disable lobby mode switch.
     *
     * @private
     * @returns {void}
     */
    _onToggleLobbyMode() {
        const { _lobbyEnabled, dispatch } = this.props;
        if (_lobbyEnabled) {
            dispatch(toggleLobbyMode(false));
        } else {
            dispatch(toggleLobbyMode(true));
        }
    }
    _onToggleRoomLock: () => void;
    /**
     * Callback to be invoked when room lock button is pressed.
     *
     * @returns {void}
     */
    _onToggleRoomLock() {
        const { _isModerator, _locked, dispatch } = this.props;
        const { showElement } = this.state;
        this.setState({
            showElement: !showElement
        });
        if (_locked && _isModerator) {
            dispatch(unlockRoom());
            this.setState({
                showElement: false
            });
        }
    }
    /**
     * Verifies input in case only digits are required.
     *
     * @param {string} passwordInputValue - The value of the password
     * text input.
     * @private
     * @returns {boolean} False when the value is not valid and True otherwise.
     */
    _validateInputValue(passwordInputValue: string) {
        const { _passwordNumberOfDigits } = this.props;
        // we want only digits,
        // but both number-pad and numeric add ',' and '.' as symbols
        if (_passwordNumberOfDigits
            && passwordInputValue.length > 0
            && !/^\d+$/.test(passwordInputValue)) {
            return false;
        }
        return true;
    }
    _onChangeText: string => void;
    /**
     * Callback to be invoked when the text in the field changes.
     *
     * @param {string} passwordInputValue - The value of password input.
     * @returns {void}
     */
    _onChangeText(passwordInputValue) {
        if (!this._validateInputValue(passwordInputValue)) {
            return;
        }
        this.setState({
            passwordInputValue
        });
    }
    _onSubmit: () => boolean;
    /**
     * Submits value typed in text input.
     *
     * @returns {boolean} False because we do not want to hide this
     * dialog/prompt as the hiding will be handled inside endRoomLockRequest
     * after setting the password is resolved.
     */
    _onSubmit() {
        const {
            _conference,
            dispatch
        } = this.props;
        const { passwordInputValue } = this.state;
        dispatch(endRoomLockRequest(_conference, passwordInputValue));
        return false;
    }
}
/**
 * Maps part of the Redux state to the props of this component.
 *
 * @param {Object} state - The Redux state.
 * @returns {Props}
 */
function _mapStateToProps(state: Object): Object {
    const { conference, locked, password } = state['features/base/conference'];
    const { hideLobbyButton } = state['features/base/config'];
    const { lobbyEnabled } = state['features/lobby'];
    const { roomPasswordNumberOfDigits } = state['features/base/config'];
    const lobbySupported = conference && conference.isLobbySupported();
    const visible = getFeatureFlag(state, MEETING_PASSWORD_ENABLED, true);
    return {
        _conference: conference,
        _dialogStyles: ColorSchemeRegistry.get(state, 'Dialog'),
        _isModerator: isLocalParticipantModerator(state),
        _lobbyEnabled: lobbyEnabled,
        _lobbyModeSwitchVisible:
            lobbySupported && isLocalParticipantModerator(state) && !hideLobbyButton,
        _locked: locked,
        _lockedConference: Boolean(conference && locked),
        _password: password,
        _passwordNumberOfDigits: roomPasswordNumberOfDigits,
        _roomLockSwitchVisible: visible,
        _securityDialogStyles: ColorSchemeRegistry.get(state, 'SecurityDialog')
    };
}
export default translate(connect(_mapStateToProps)(SecurityDialog));
 |