[RN] Room lock button

This commit is contained in:
Lyubomir Marinov 2016-12-12 13:49:23 -06:00
parent 8c3fb54d3d
commit 9a49a01713
7 changed files with 193 additions and 73 deletions

View File

@ -45,6 +45,18 @@ export const CONFERENCE_LEFT = Symbol('CONFERENCE_LEFT');
*/ */
export const CONFERENCE_WILL_LEAVE = Symbol('CONFERENCE_WILL_LEAVE'); export const CONFERENCE_WILL_LEAVE = Symbol('CONFERENCE_WILL_LEAVE');
/**
* The type of the Redux action which signals that the lock state of a specific
* <tt>JitsiConference</tt> changed.
*
* {
* type: LOCK_STATE_CHANGED,
* conference: JitsiConference,
* locked: boolean
* }
*/
export const LOCK_STATE_CHANGED = Symbol('LOCK_STATE_CHANGED');
/** /**
* The type of the Redux action which sets the password to join or lock a * The type of the Redux action which sets the password to join or lock a
* specific JitsiConference. * specific JitsiConference.

View File

@ -13,6 +13,7 @@ import {
CONFERENCE_JOINED, CONFERENCE_JOINED,
CONFERENCE_LEFT, CONFERENCE_LEFT,
CONFERENCE_WILL_LEAVE, CONFERENCE_WILL_LEAVE,
LOCK_STATE_CHANGED,
SET_PASSWORD, SET_PASSWORD,
SET_ROOM SET_ROOM
} from './actionTypes'; } from './actionTypes';
@ -46,6 +47,10 @@ function _addConferenceListeners(conference, dispatch) {
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
(...args) => dispatch(dominantSpeakerChanged(...args))); (...args) => dispatch(dominantSpeakerChanged(...args)));
conference.on(
JitsiConferenceEvents.LOCK_STATE_CHANGED,
(...args) => dispatch(_lockStateChanged(conference, ...args)));
conference.on( conference.on(
JitsiConferenceEvents.TRACK_ADDED, JitsiConferenceEvents.TRACK_ADDED,
t => t && !t.isLocal() && dispatch(trackAdded(t))); t => t && !t.isLocal() && dispatch(trackAdded(t)));
@ -185,6 +190,27 @@ export function createConference() {
}; };
} }
/**
* Signals that the lock state of a specific JitsiConference changed.
*
* @param {JitsiConference} conference - The JitsiConference which had its lock
* state changed.
* @param {boolean} locked - If the specified conference became locked, true;
* otherwise, false.
* @returns {{
* type: LOCK_STATE_CHANGED,
* conference: JitsiConference,
* locked: boolean
* }}
*/
function _lockStateChanged(conference, locked) {
return {
type: LOCK_STATE_CHANGED,
conference,
locked
};
}
/** /**
* Sets the password to join or lock a specific JitsiConference. * Sets the password to join or lock a specific JitsiConference.
* *

View File

@ -10,6 +10,7 @@ import {
CONFERENCE_JOINED, CONFERENCE_JOINED,
CONFERENCE_LEFT, CONFERENCE_LEFT,
CONFERENCE_WILL_LEAVE, CONFERENCE_WILL_LEAVE,
LOCK_STATE_CHANGED,
SET_PASSWORD, SET_PASSWORD,
SET_ROOM SET_ROOM
} from './actionTypes'; } from './actionTypes';
@ -33,6 +34,9 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
case CONFERENCE_WILL_LEAVE: case CONFERENCE_WILL_LEAVE:
return _conferenceWillLeave(state, action); return _conferenceWillLeave(state, action);
case LOCK_STATE_CHANGED:
return _lockStateChanged(state, action);
case SET_PASSWORD: case SET_PASSWORD:
return _setPassword(state, action); return _setPassword(state, action);
@ -70,6 +74,7 @@ function _conferenceFailed(state, action) {
setStateProperties(state, { setStateProperties(state, {
conference: undefined, conference: undefined,
leaving: undefined, leaving: undefined,
locked: undefined,
password: undefined, password: undefined,
/** /**
@ -92,6 +97,14 @@ function _conferenceFailed(state, action) {
* reduction of the specified action. * reduction of the specified action.
*/ */
function _conferenceJoined(state, action) { function _conferenceJoined(state, action) {
const conference = action.conference;
// FIXME The indicator which determines whether a JitsiConference is locked
// i.e. password-protected is private to lib-jitsi-meet. However, the
// library does not fire LOCK_STATE_CHANGED upon joining a JitsiConference
// with a password.
const locked = conference.room.locked || undefined;
return ( return (
setStateProperties(state, { setStateProperties(state, {
/** /**
@ -100,8 +113,15 @@ function _conferenceJoined(state, action) {
* *
* @type {JitsiConference} * @type {JitsiConference}
*/ */
conference: action.conference, conference,
leaving: undefined, leaving: undefined,
/**
* The indicator which determines whether the conference is locked.
*
* @type {boolean}
*/
locked,
passwordRequired: undefined passwordRequired: undefined
})); }));
} }
@ -127,6 +147,7 @@ function _conferenceLeft(state, action) {
setStateProperties(state, { setStateProperties(state, {
conference: undefined, conference: undefined,
leaving: undefined, leaving: undefined,
locked: undefined,
password: undefined, password: undefined,
passwordRequired: undefined passwordRequired: undefined
})); }));
@ -162,6 +183,24 @@ function _conferenceWillLeave(state, action) {
})); }));
} }
/**
* Reduces a specific Redux action LOCK_STATE_CHANGED of the feature
* base/conference.
*
* @param {Object} state - The Redux state of the feature base/conference.
* @param {Action} action - The Redux action LOCK_STATE_CHANGED to reduce.
* @private
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _lockStateChanged(state, action) {
if (state.conference !== action.conference) {
return state;
}
return setStateProperty(state, 'locked', action.locked || undefined);
}
/** /**
* Reduces a specific Redux action SET_PASSWORD of the feature base/conference. * Reduces a specific Redux action SET_PASSWORD of the feature base/conference.
* *

View File

@ -1,14 +1,24 @@
// FIXME React Native does not polyfill Symbol at versions 0.39.2 or earlier. // FIXME React Native does not polyfill Symbol at versions 0.39.2 or earlier.
export default (global => { export default (global => {
let s = global.Symbol; let clazz = global.Symbol;
if (typeof s === 'undefined') { if (typeof clazz === 'undefined') {
// XXX At the time of this writing we use Symbol only as a way to // XXX At the time of this writing we use Symbol only as a way to
// prevent collisions in Redux action types. Consequently, the Symbol // prevent collisions in Redux action types. Consequently, the Symbol
// implementation provided bellow is minimal and specific to our // implementation provided bellow is minimal and specific to our
// purpose. // purpose.
s = description => (description || '').split(''); const toString = function() {
return this.join(''); // eslint-disable-line no-invalid-this
};
clazz = description => {
const thiz = (description || '').split('');
thiz.toString = toString;
return thiz;
};
} }
return s; return clazz;
})(global || window || this); // eslint-disable-line no-invalid-this })(global || window || this); // eslint-disable-line no-invalid-this

View File

@ -1,10 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { appNavigate } from '../../app'; import { appNavigate } from '../../app';
import { import { toggleAudioMuted, toggleVideoMuted } from '../../base/media';
toggleAudioMuted,
toggleVideoMuted
} from '../../base/media';
import { ColorPalette } from '../../base/styles'; import { ColorPalette } from '../../base/styles';
import { styles } from './styles'; import { styles } from './styles';
@ -23,6 +20,12 @@ export class AbstractToolbar extends Component {
static propTypes = { static propTypes = {
audioMuted: React.PropTypes.bool, audioMuted: React.PropTypes.bool,
dispatch: React.PropTypes.func, dispatch: React.PropTypes.func,
/**
* The indicator which determines whether the conference is
* locked/password-protected.
*/
locked: React.PropTypes.bool,
videoMuted: React.PropTypes.bool, videoMuted: React.PropTypes.bool,
visible: React.PropTypes.bool.isRequired visible: React.PropTypes.bool.isRequired
} }
@ -39,6 +42,7 @@ export class AbstractToolbar extends Component {
// Bind event handlers so they are only bound once for every instance. // Bind event handlers so they are only bound once for every instance.
this._onHangup = this._onHangup.bind(this); this._onHangup = this._onHangup.bind(this);
this._toggleAudio = this._toggleAudio.bind(this); this._toggleAudio = this._toggleAudio.bind(this);
this._toggleLock = this._toggleLock.bind(this);
this._toggleVideo = this._toggleVideo.bind(this); this._toggleVideo = this._toggleVideo.bind(this);
} }
@ -50,33 +54,32 @@ export class AbstractToolbar extends Component {
* button to get styles for. * button to get styles for.
* @protected * @protected
* @returns {{ * @returns {{
* buttonStyle: Object,
* iconName: string, * iconName: string,
* iconStyle: Object * iconStyle: Object,
* style: Object
* }} * }}
*/ */
_getMuteButtonStyles(mediaType) { _getMuteButtonStyles(mediaType) {
let buttonStyle;
let iconName; let iconName;
let iconStyle; let iconStyle;
let style = styles.primaryToolbarButton;
if (this.props[`${mediaType}Muted`]) { if (this.props[`${mediaType}Muted`]) {
buttonStyle = {
...styles.toolbarButton,
backgroundColor: ColorPalette.buttonUnderlay
};
iconName = this[`${mediaType}MutedIcon`]; iconName = this[`${mediaType}MutedIcon`];
iconStyle = styles.whiteIcon; iconStyle = styles.whiteIcon;
style = {
...style,
backgroundColor: ColorPalette.buttonUnderlay
};
} else { } else {
buttonStyle = styles.toolbarButton;
iconName = this[`${mediaType}Icon`]; iconName = this[`${mediaType}Icon`];
iconStyle = styles.icon; iconStyle = styles.icon;
} }
return { return {
buttonStyle,
iconName, iconName,
iconStyle iconStyle,
style
}; };
} }
@ -96,7 +99,7 @@ export class AbstractToolbar extends Component {
} }
/** /**
* Dispatches action to toggle the mute state of the audio/microphone. * Dispatches an action to toggle the mute state of the audio/microphone.
* *
* @protected * @protected
* @returns {void} * @returns {void}
@ -106,7 +109,18 @@ export class AbstractToolbar extends Component {
} }
/** /**
* Dispatches action to toggle the mute state of the video/camera. * 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.
* *
* @protected * @protected
* @returns {void} * @returns {void}
@ -123,10 +137,19 @@ export class AbstractToolbar extends Component {
* @returns {{ audioMuted: boolean, videoMuted: boolean }} * @returns {{ audioMuted: boolean, videoMuted: boolean }}
*/ */
export function mapStateToProps(state) { export function mapStateToProps(state) {
const conference = state['features/base/conference'];
const media = state['features/base/media']; const media = state['features/base/media'];
return { return {
audioMuted: media.audio.muted, audioMuted: media.audio.muted,
/**
* The indicator which determines whether the conference is
* locked/password-protected.
*
* @type {boolean}
*/
locked: conference.locked,
videoMuted: media.video.muted videoMuted: media.video.muted
}; };
} }

View File

@ -77,13 +77,13 @@ class Toolbar extends AbstractToolbar {
iconName = { audioButtonStyles.iconName } iconName = { audioButtonStyles.iconName }
iconStyle = { audioButtonStyles.iconStyle } iconStyle = { audioButtonStyles.iconStyle }
onClick = { this._toggleAudio } onClick = { this._toggleAudio }
style = { audioButtonStyles.buttonStyle } /> style = { audioButtonStyles.style } />
<ToolbarButton <ToolbarButton
iconName = 'hangup' iconName = 'hangup'
iconStyle = { styles.whiteIcon } iconStyle = { styles.whiteIcon }
onClick = { this._onHangup } onClick = { this._onHangup }
style = {{ style = {{
...styles.toolbarButton, ...styles.primaryToolbarButton,
backgroundColor: ColorPalette.red backgroundColor: ColorPalette.red
}} }}
underlayColor = { ColorPalette.buttonUnderlay } /> underlayColor = { ColorPalette.buttonUnderlay } />
@ -91,7 +91,7 @@ class Toolbar extends AbstractToolbar {
iconName = { videoButtonStyles.iconName } iconName = { videoButtonStyles.iconName }
iconStyle = { videoButtonStyles.iconStyle } iconStyle = { videoButtonStyles.iconStyle }
onClick = { this._toggleVideo } onClick = { this._toggleVideo }
style = { videoButtonStyles.buttonStyle } /> style = { videoButtonStyles.style } />
</View> </View>
); );
@ -106,21 +106,33 @@ class Toolbar extends AbstractToolbar {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderSecondaryToolbar() { _renderSecondaryToolbar() {
/* eslint-disable react/jsx-handler-names */ const iconStyle = styles.whiteIcon;
const style = styles.secondaryToolbarButton;
const underlayColor = 'transparent';
/* eslint-disable react/jsx-curly-spacing,react/jsx-handler-names */
// TODO Use an appropriate icon for toggle camera facing mode. // TODO Use an appropriate icon for toggle camera facing mode.
return ( return (
<View style = { styles.secondaryToolbar }> <View style = { styles.secondaryToolbar }>
<ToolbarButton <ToolbarButton
iconName = 'reload' iconName = 'reload'
iconStyle = { styles.whiteIcon } iconStyle = { iconStyle }
onClick = { this._toggleCameraFacingMode } onClick = { this._toggleCameraFacingMode }
style = { styles.toggleCameraFacingModeButton } style = { style }
underlayColor = 'transparent' /> underlayColor = { underlayColor } />
<ToolbarButton
iconName = {
this.props.locked ? 'security-locked' : 'security'
}
iconStyle = { iconStyle }
onClick = { this._toggleLock }
style = { style }
underlayColor = { underlayColor } />
</View> </View>
); );
/* eslint-enable react/jsx-handler-names */ /* eslint-enable react/jsx-curly-spacing,react/jsx-handler-names */
} }
/** /**

View File

@ -1,35 +1,23 @@
import { ColorPalette, createStyleSheet } from '../../base/styles'; import { BoxModel, ColorPalette, createStyleSheet } from '../../base/styles';
/** /**
* Generic styles for a button. * The base style for (toolbar) buttons.
* *
* @type {Object} * @type {Object}
*/ */
const button = { const button = {
alignSelf: 'center',
borderRadius: 35, borderRadius: 35,
borderWidth: 0, borderWidth: 0,
flex: 0,
flexDirection: 'row', flexDirection: 'row',
height: 60, height: 60,
justifyContent: 'center', justifyContent: 'center',
margin: BoxModel.margin,
width: 60 width: 60
}; };
/** /**
* Generic container for buttons. * The base style for icons.
*
* @type {Object}
*/
const container = {
flex: 1,
flexDirection: 'row',
left: 0,
position: 'absolute',
right: 0
};
/**
* Generic styles for an icon.
* *
* @type {Object} * @type {Object}
*/ */
@ -39,28 +27,45 @@ const icon = {
fontSize: 24 fontSize: 24
}; };
/**
* The base style for toolbars.
*
* @type {Object}
*/
const toolbar = {
flex: 1,
position: 'absolute'
};
/** /**
* The (conference) toolbar related styles. * The (conference) toolbar related styles.
* TODO Make styles more generic and reusable. Use ColorPalette for all colors.
*/ */
export const styles = createStyleSheet({ export const styles = createStyleSheet({
/** /**
* The toolbar button icon style. * The toolbar button icon style.
*/ */
icon: { icon,
...icon,
color: ColorPalette.darkGrey
},
/** /**
* The style of the toolbar which contains the primary buttons such as * The style of the toolbar which contains the primary buttons such as
* hangup, audio and video mute. * hangup, audio and video mute.
*/ */
primaryToolbar: { primaryToolbar: {
...container, ...toolbar,
bottom: 30, bottom: 3 * BoxModel.margin,
height: 60, flexDirection: 'row',
justifyContent: 'center' justifyContent: 'center',
left: 0,
right: 0
},
/**
* The style of button in primaryToolbar.
*/
primaryToolbarButton: {
...button,
backgroundColor: ColorPalette.white,
opacity: 0.8
}, },
/** /**
@ -68,36 +73,29 @@ export const styles = createStyleSheet({
* toggle camera facing mode. * toggle camera facing mode.
*/ */
secondaryToolbar: { secondaryToolbar: {
...container, ...toolbar,
height: 60, bottom: 0,
justifyContent: 'flex-end' flexDirection: 'column',
right: 0,
top: 0
}, },
/** /**
* The toggle camera facing mode button style. * The style of button in secondaryToolbar.
*/ */
toggleCameraFacingModeButton: { secondaryToolbarButton: {
...button, ...button,
backgroundColor: 'transparent' backgroundColor: 'transparent'
}, },
/** /**
* The toolbar button style. * The style of the root/top-level Container of Toolbar.
*/
toolbarButton: {
...button,
backgroundColor: ColorPalette.white,
marginLeft: 20,
marginRight: 20,
opacity: 0.8
},
/**
* The toolbar container style.
*/ */
toolbarContainer: { toolbarContainer: {
...container,
bottom: 0, bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0 top: 0
}, },