From 80685395ed07297887e21f2740e05b3748cae49e Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Tue, 13 Dec 2016 03:14:04 -0600 Subject: [PATCH] [RN] Room lock --- react/features/base/conference/actions.js | 60 +++++++++++--- react/features/base/conference/middleware.js | 54 ------------ .../components/Conference.native.js | 60 ++++++++++++-- react/features/room-lock/actionTypes.js | 24 ++++++ react/features/room-lock/actions.js | 56 +++++++++++++ .../components/RoomLockPrompt.native.js | 83 +++++++++++++++++++ react/features/room-lock/components/index.js | 1 + react/features/room-lock/index.js | 2 + react/features/room-lock/reducer.js | 33 ++++++++ .../toolbar/components/AbstractToolbar.js | 25 +++--- .../toolbar/components/Toolbar.native.js | 2 +- 11 files changed, 317 insertions(+), 83 deletions(-) create mode 100644 react/features/room-lock/actionTypes.js create mode 100644 react/features/room-lock/actions.js create mode 100644 react/features/room-lock/components/RoomLockPrompt.native.js create mode 100644 react/features/room-lock/components/index.js create mode 100644 react/features/room-lock/index.js create mode 100644 react/features/room-lock/reducer.js diff --git a/react/features/base/conference/actions.js b/react/features/base/conference/actions.js index ece614e1f..c26ef4b52 100644 --- a/react/features/base/conference/actions.js +++ b/react/features/base/conference/actions.js @@ -220,19 +220,57 @@ function _lockStateChanged(conference, locked) { * such as join or lock. * @param {string} password - The password with which the specified conference * is to be joined or locked. - * @returns {{ - * type: SET_PASSWORD, - * conference: JitsiConference, - * method: Function, - * password: string - * }} + * @returns {Function} */ export function setPassword(conference, method, password) { - return { - type: SET_PASSWORD, - conference, - method, - password + return (dispatch, getState) => { + switch (method) { + case conference.join: { + let state = getState()['features/base/conference']; + + // Make sure that the action will set a password for a conference + // that the application wants joined. + if (state.passwordRequired === conference) { + dispatch({ + type: SET_PASSWORD, + conference, + method, + password + }); + + // Join the conference with the newly-set password. + + // Make sure that the action did set the password. + state = getState()['features/base/conference']; + if (state.password === password + && !state.passwordRequired + + // Make sure that the application still wants the + // conference joined. + && !state.conference) { + method.call(conference, password); + } + } + break; + } + + case conference.lock: { + const state = getState()['features/base/conference']; + + if (state.conference === conference) { + return ( + method.call(conference, password) + .then(() => dispatch({ + type: SET_PASSWORD, + conference, + method, + password + }))); + } + + return Promise.reject(); + } + } }; } diff --git a/react/features/base/conference/middleware.js b/react/features/base/conference/middleware.js index e335bdf40..eb7c538b8 100644 --- a/react/features/base/conference/middleware.js +++ b/react/features/base/conference/middleware.js @@ -8,7 +8,6 @@ import { MiddlewareRegistry } from '../redux'; import { TRACK_ADDED, TRACK_REMOVED } from '../tracks'; import { createConference } from './actions'; -import { SET_PASSWORD } from './actionTypes'; import { _addLocalTracksToConference, _handleParticipantError, @@ -29,9 +28,6 @@ MiddlewareRegistry.register(store => next => action => { case PIN_PARTICIPANT: return _pinParticipant(store, next, action); - case SET_PASSWORD: - return _setPassword(store, next, action); - case TRACK_ADDED: case TRACK_REMOVED: return _trackAddedOrRemoved(store, next, action); @@ -111,56 +107,6 @@ function _pinParticipant(store, next, action) { return next(action); } -/** - * Notifies the feature base/conference that the action SET_PASSWORD is - * being dispatched within a specific Redux store. Joins or locks a specific - * JitsiConference with a specific password. - * - * @param {Store} store - The Redux store in which the specified action is being - * dispatched. - * @param {Dispatch} next - The Redux dispatch function to dispatch the - * specified action to the specified store. - * @param {Action} action - The Redux action SET_PASSWORD which is - * being dispatched in the specified store. - * @private - * @returns {Object} The new state that is the result of the reduction of the - * specified action. - */ -function _setPassword(store, next, action) { - const { conference, method } = action; - - switch (method) { - case conference.join: { - let state = store.getState()['features/base/conference']; - - // Make sure that the action will set a password for a conference that - // the application wants joined. - if (state.passwordRequired === conference) { - const result = next(action); - - // Join the conference with the newly-set password. - const password = action.password; - - // Make sure that the action did set the password. - state = store.getState()['features/base/conference']; - if (state.password === password - && !state.passwordRequired - - // Make sure that the application still wants the conference - // joined. - && !state.conference) { - method.call(conference, password); - } - - return result; - } - break; - } - } - - return next(action); -} - /** * Synchronizes local tracks from state with local tracks in JitsiConference * instance. diff --git a/react/features/conference/components/Conference.native.js b/react/features/conference/components/Conference.native.js index e783f90e6..77815a10c 100644 --- a/react/features/conference/components/Conference.native.js +++ b/react/features/conference/components/Conference.native.js @@ -5,6 +5,7 @@ import { connect, disconnect } from '../../base/connection'; import { Container } from '../../base/react'; import { FilmStrip } from '../../filmStrip'; import { LargeVideo } from '../../largeVideo'; +import { RoomLockPrompt } from '../../room-lock'; import { Toolbar } from '../../toolbar'; import PasswordRequiredPrompt from './PasswordRequiredPrompt'; @@ -33,6 +34,15 @@ class Conference extends Component { * @type {JitsiConference} */ _passwordRequired: React.PropTypes.object, + + /** + * The indicator which determines whether the user has requested to lock + * the conference/room. + * + * @private + * @type {JitsiConference} + */ + _roomLockRequested: React.PropTypes.object, dispatch: React.PropTypes.func } @@ -142,19 +152,50 @@ class Conference extends Component { } } + /** + * Renders a prompt if a password is required to join the conference. + * + * @private + * @returns {ReactElement} + */ + _renderPasswordRequiredPrompt() { + const required = this.props._passwordRequired; + + if (required) { + return ( + + ); + } + + return null; + } + /** * Renders a prompt if necessary such as when a password is required to join - * the conference. + * the conference or the user has requested to lock the conference/room. * * @private * @returns {ReactElement} */ _renderPrompt() { - const passwordRequired = this.props._passwordRequired; + return ( + this._renderPasswordRequiredPrompt() + || this._renderRoomLockPrompt() + ); + } - if (passwordRequired) { + /** + * Renders a prompt if the user has requested to lock the conference/room. + * + * @private + * @returns {ReactElement} + */ + _renderRoomLockPrompt() { + const requested = this.props._roomLockRequested; + + if (requested) { return ( - + ); } @@ -179,7 +220,16 @@ function mapStateToProps(state) { * @private * @type {JitsiConference} */ - _passwordRequired: state['features/base/conference'].passwordRequired + _passwordRequired: state['features/base/conference'].passwordRequired, + + /** + * The indicator which determines whether the user has requested to lock + * the conference/room. + * + * @private + * @type {JitsiConference} + */ + _roomLockRequested: state['features/room-lock'].requested }; } diff --git a/react/features/room-lock/actionTypes.js b/react/features/room-lock/actionTypes.js new file mode 100644 index 000000000..cecc53f51 --- /dev/null +++ b/react/features/room-lock/actionTypes.js @@ -0,0 +1,24 @@ +import { Symbol } from '../base/react'; + +/** + * The type of Redux action which begins a (user) request to lock a specific + * JitsiConference. + * + * { + * type: BEGIN_ROOM_LOCK_REQUEST, + * conference: JitsiConference + * } + */ +export const BEGIN_ROOM_LOCK_REQUEST = Symbol('BEGIN_ROOM_LOCK_REQUEST'); + +/** + * The type of Redux action which end a (user) request to lock a specific + * JitsiConference. + * + * { + * type: END_ROOM_LOCK_REQUEST, + * conference: JitsiConference, + * password: string + * } + */ +export const END_ROOM_LOCK_REQUEST = Symbol('END_ROOM_LOCK_REQUEST'); diff --git a/react/features/room-lock/actions.js b/react/features/room-lock/actions.js new file mode 100644 index 000000000..562a5f92b --- /dev/null +++ b/react/features/room-lock/actions.js @@ -0,0 +1,56 @@ +import { setPassword } from '../base/conference'; + +import { BEGIN_ROOM_LOCK_REQUEST, END_ROOM_LOCK_REQUEST } from './actionTypes'; +import './reducer'; + +/** + * Begins a (user) request to lock a specific conference/room. + * + * @param {JitsiConference|undefined} conference - The JitsiConference to lock + * if specified or undefined if the current JitsiConference is to be locked. + * @returns {Function} + */ +export function beginRoomLockRequest(conference) { + return (dispatch, getState) => { + if (typeof conference === 'undefined') { + const state = getState(); + + // eslint-disable-next-line no-param-reassign + conference = state['features/base/conference'].conference; + } + + if (conference) { + dispatch({ + type: BEGIN_ROOM_LOCK_REQUEST, + conference + }); + } + }; +} + +/** + * Ends a (user) request to lock a specific conference/room. + * + * @param {JitsiConference} conference - The JitsiConference to lock. + * @param {string|undefined} password - The password with which the specified + * conference is to be locked or undefined to cancel the (user) request to lock + * the specified conference. + * @returns {Function} + */ +export function endRoomLockRequest(conference, password) { + return dispatch => { + const setPassword_ + = password + ? dispatch(setPassword(conference, conference.lock, password)) + : Promise.resolve(); + const endRoomLockRequest_ = () => { + dispatch({ + type: END_ROOM_LOCK_REQUEST, + conference, + password + }); + }; + + setPassword_.then(endRoomLockRequest_, endRoomLockRequest_); + }; +} diff --git a/react/features/room-lock/components/RoomLockPrompt.native.js b/react/features/room-lock/components/RoomLockPrompt.native.js new file mode 100644 index 000000000..b2c5e73d9 --- /dev/null +++ b/react/features/room-lock/components/RoomLockPrompt.native.js @@ -0,0 +1,83 @@ +import React, { Component } from 'react'; +import Prompt from 'react-native-prompt'; +import { connect } from 'react-redux'; + +import { endRoomLockRequest } from '../actions'; + +/** + * Implements a React Component which prompts the user for a password to lock a + * conference/room. + */ +class RoomLockPrompt extends Component { + /** + * RoomLockPrompt component's property types. + * + * @static + */ + static propTypes = { + /** + * The JitsiConference which requires a password. + * + * @type {JitsiConference} + */ + conference: React.PropTypes.object, + dispatch: React.PropTypes.func + } + + /** + * Initializes a new RoomLockPrompt instance. + * + * @param {Object} props - The read-only properties with which the new + * instance is to be initialized. + */ + constructor(props) { + super(props); + + // Bind event handlers so they are only bound once for every instance. + this._onCancel = this._onCancel.bind(this); + this._onSubmit = this._onSubmit.bind(this); + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + return ( + + ); + } + + /** + * Notifies this prompt that it has been dismissed by cancel. + * + * @private + * @returns {void} + */ + _onCancel() { + // An undefined password is understood to cancel the request to lock the + // conference/room. + this._onSubmit(undefined); + } + + /** + * Notifies this prompt that it has been dismissed by submitting a specific + * value. + * + * @param {string} value - The submitted value. + * @private + * @returns {void} + */ + _onSubmit(value) { + this.props.dispatch(endRoomLockRequest(this.props.conference, value)); + } +} + +export default connect()(RoomLockPrompt); diff --git a/react/features/room-lock/components/index.js b/react/features/room-lock/components/index.js new file mode 100644 index 000000000..a67f1c4db --- /dev/null +++ b/react/features/room-lock/components/index.js @@ -0,0 +1 @@ +export { default as RoomLockPrompt } from './RoomLockPrompt'; diff --git a/react/features/room-lock/index.js b/react/features/room-lock/index.js new file mode 100644 index 000000000..3c46ed49d --- /dev/null +++ b/react/features/room-lock/index.js @@ -0,0 +1,2 @@ +export * from './actions'; +export * from './components'; diff --git a/react/features/room-lock/reducer.js b/react/features/room-lock/reducer.js new file mode 100644 index 000000000..80025f788 --- /dev/null +++ b/react/features/room-lock/reducer.js @@ -0,0 +1,33 @@ +import { + CONFERENCE_FAILED, + CONFERENCE_JOINED, + CONFERENCE_LEFT +} from '../base/conference'; +import { ReducerRegistry, setStateProperty } from '../base/redux'; + +import { BEGIN_ROOM_LOCK_REQUEST, END_ROOM_LOCK_REQUEST } from './actionTypes'; + +ReducerRegistry.register('features/room-lock', (state = {}, action) => { + switch (action.type) { + case BEGIN_ROOM_LOCK_REQUEST: + return setStateProperty(state, 'requested', action.conference); + + case CONFERENCE_FAILED: + case CONFERENCE_LEFT: + case END_ROOM_LOCK_REQUEST: { + if (state.requested === action.conference) { + return setStateProperty(state, 'requested', undefined); + } + break; + } + + case CONFERENCE_JOINED: { + if (state.requested !== action.conference) { + return setStateProperty(state, 'requested', undefined); + } + break; + } + } + + return state; +}); diff --git a/react/features/toolbar/components/AbstractToolbar.js b/react/features/toolbar/components/AbstractToolbar.js index c43eaba81..71c6b47c8 100644 --- a/react/features/toolbar/components/AbstractToolbar.js +++ b/react/features/toolbar/components/AbstractToolbar.js @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import { appNavigate } from '../../app'; import { toggleAudioMuted, toggleVideoMuted } from '../../base/media'; import { ColorPalette } from '../../base/styles'; +import { beginRoomLockRequest } from '../../room-lock'; import { styles } from './styles'; @@ -41,8 +42,8 @@ export class AbstractToolbar extends Component { // Bind event handlers so they are only bound once for every instance. this._onHangup = this._onHangup.bind(this); + this._onRoomLock = this._onRoomLock.bind(this); this._toggleAudio = this._toggleAudio.bind(this); - this._toggleLock = this._toggleLock.bind(this); this._toggleVideo = this._toggleVideo.bind(this); } @@ -98,6 +99,17 @@ export class AbstractToolbar extends Component { this.props.dispatch(appNavigate(undefined)); } + /** + * Dispatches an action to set the lock i.e. password protection of the + * conference/room. + * + * @protected + * @returns {void} + */ + _onRoomLock() { + this.props.dispatch(beginRoomLockRequest()); + } + /** * Dispatches an action to toggle the mute state of the audio/microphone. * @@ -108,17 +120,6 @@ export class AbstractToolbar extends Component { this.props.dispatch(toggleAudioMuted()); } - /** - * Dispatches an action to toggle the lock i.e. password protection of the - * conference. - * - * @protected - * @returns {void} - */ - _toggleLock() { - // TODO Auto-generated method stub - } - /** * Dispatches an action to toggle the mute state of the video/camera. * diff --git a/react/features/toolbar/components/Toolbar.native.js b/react/features/toolbar/components/Toolbar.native.js index 537c6dbf0..02b5629c1 100644 --- a/react/features/toolbar/components/Toolbar.native.js +++ b/react/features/toolbar/components/Toolbar.native.js @@ -126,7 +126,7 @@ class Toolbar extends AbstractToolbar { this.props.locked ? 'security-locked' : 'security' } iconStyle = { iconStyle } - onClick = { this._toggleLock } + onClick = { this._onRoomLock } style = { style } underlayColor = { underlayColor } />