[RN] Room lock button
This commit is contained in:
parent
8c3fb54d3d
commit
9a49a01713
|
@ -45,6 +45,18 @@ export const CONFERENCE_LEFT = Symbol('CONFERENCE_LEFT');
|
|||
*/
|
||||
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
|
||||
* specific JitsiConference.
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
LOCK_STATE_CHANGED,
|
||||
SET_PASSWORD,
|
||||
SET_ROOM
|
||||
} from './actionTypes';
|
||||
|
@ -46,6 +47,10 @@ function _addConferenceListeners(conference, dispatch) {
|
|||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
(...args) => dispatch(dominantSpeakerChanged(...args)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.LOCK_STATE_CHANGED,
|
||||
(...args) => dispatch(_lockStateChanged(conference, ...args)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.TRACK_ADDED,
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
LOCK_STATE_CHANGED,
|
||||
SET_PASSWORD,
|
||||
SET_ROOM
|
||||
} from './actionTypes';
|
||||
|
@ -33,6 +34,9 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
|
|||
case CONFERENCE_WILL_LEAVE:
|
||||
return _conferenceWillLeave(state, action);
|
||||
|
||||
case LOCK_STATE_CHANGED:
|
||||
return _lockStateChanged(state, action);
|
||||
|
||||
case SET_PASSWORD:
|
||||
return _setPassword(state, action);
|
||||
|
||||
|
@ -70,6 +74,7 @@ function _conferenceFailed(state, action) {
|
|||
setStateProperties(state, {
|
||||
conference: undefined,
|
||||
leaving: undefined,
|
||||
locked: undefined,
|
||||
password: undefined,
|
||||
|
||||
/**
|
||||
|
@ -92,6 +97,14 @@ function _conferenceFailed(state, action) {
|
|||
* reduction of the specified 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 (
|
||||
setStateProperties(state, {
|
||||
/**
|
||||
|
@ -100,8 +113,15 @@ function _conferenceJoined(state, action) {
|
|||
*
|
||||
* @type {JitsiConference}
|
||||
*/
|
||||
conference: action.conference,
|
||||
conference,
|
||||
leaving: undefined,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the conference is locked.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
locked,
|
||||
passwordRequired: undefined
|
||||
}));
|
||||
}
|
||||
|
@ -127,6 +147,7 @@ function _conferenceLeft(state, action) {
|
|||
setStateProperties(state, {
|
||||
conference: undefined,
|
||||
leaving: undefined,
|
||||
locked: undefined,
|
||||
password: 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.
|
||||
*
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
// FIXME React Native does not polyfill Symbol at versions 0.39.2 or earlier.
|
||||
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
|
||||
// prevent collisions in Redux action types. Consequently, the Symbol
|
||||
// implementation provided bellow is minimal and specific to our
|
||||
// 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
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
import { appNavigate } from '../../app';
|
||||
import {
|
||||
toggleAudioMuted,
|
||||
toggleVideoMuted
|
||||
} from '../../base/media';
|
||||
import { toggleAudioMuted, toggleVideoMuted } from '../../base/media';
|
||||
import { ColorPalette } from '../../base/styles';
|
||||
|
||||
import { styles } from './styles';
|
||||
|
@ -23,6 +20,12 @@ export class AbstractToolbar extends Component {
|
|||
static propTypes = {
|
||||
audioMuted: React.PropTypes.bool,
|
||||
dispatch: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the conference is
|
||||
* locked/password-protected.
|
||||
*/
|
||||
locked: React.PropTypes.bool,
|
||||
videoMuted: React.PropTypes.bool,
|
||||
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.
|
||||
this._onHangup = this._onHangup.bind(this);
|
||||
this._toggleAudio = this._toggleAudio.bind(this);
|
||||
this._toggleLock = this._toggleLock.bind(this);
|
||||
this._toggleVideo = this._toggleVideo.bind(this);
|
||||
}
|
||||
|
||||
|
@ -50,33 +54,32 @@ export class AbstractToolbar extends Component {
|
|||
* button to get styles for.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* buttonStyle: Object,
|
||||
* iconName: string,
|
||||
* iconStyle: Object
|
||||
* iconStyle: Object,
|
||||
* style: Object
|
||||
* }}
|
||||
*/
|
||||
_getMuteButtonStyles(mediaType) {
|
||||
let buttonStyle;
|
||||
let iconName;
|
||||
let iconStyle;
|
||||
let style = styles.primaryToolbarButton;
|
||||
|
||||
if (this.props[`${mediaType}Muted`]) {
|
||||
buttonStyle = {
|
||||
...styles.toolbarButton,
|
||||
backgroundColor: ColorPalette.buttonUnderlay
|
||||
};
|
||||
iconName = this[`${mediaType}MutedIcon`];
|
||||
iconStyle = styles.whiteIcon;
|
||||
style = {
|
||||
...style,
|
||||
backgroundColor: ColorPalette.buttonUnderlay
|
||||
};
|
||||
} else {
|
||||
buttonStyle = styles.toolbarButton;
|
||||
iconName = this[`${mediaType}Icon`];
|
||||
iconStyle = styles.icon;
|
||||
}
|
||||
|
||||
return {
|
||||
buttonStyle,
|
||||
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
|
||||
* @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
|
||||
* @returns {void}
|
||||
|
@ -123,10 +137,19 @@ export class AbstractToolbar extends Component {
|
|||
* @returns {{ audioMuted: boolean, videoMuted: boolean }}
|
||||
*/
|
||||
export function mapStateToProps(state) {
|
||||
const conference = state['features/base/conference'];
|
||||
const media = state['features/base/media'];
|
||||
|
||||
return {
|
||||
audioMuted: media.audio.muted,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the conference is
|
||||
* locked/password-protected.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
locked: conference.locked,
|
||||
videoMuted: media.video.muted
|
||||
};
|
||||
}
|
||||
|
|
|
@ -77,13 +77,13 @@ class Toolbar extends AbstractToolbar {
|
|||
iconName = { audioButtonStyles.iconName }
|
||||
iconStyle = { audioButtonStyles.iconStyle }
|
||||
onClick = { this._toggleAudio }
|
||||
style = { audioButtonStyles.buttonStyle } />
|
||||
style = { audioButtonStyles.style } />
|
||||
<ToolbarButton
|
||||
iconName = 'hangup'
|
||||
iconStyle = { styles.whiteIcon }
|
||||
onClick = { this._onHangup }
|
||||
style = {{
|
||||
...styles.toolbarButton,
|
||||
...styles.primaryToolbarButton,
|
||||
backgroundColor: ColorPalette.red
|
||||
}}
|
||||
underlayColor = { ColorPalette.buttonUnderlay } />
|
||||
|
@ -91,7 +91,7 @@ class Toolbar extends AbstractToolbar {
|
|||
iconName = { videoButtonStyles.iconName }
|
||||
iconStyle = { videoButtonStyles.iconStyle }
|
||||
onClick = { this._toggleVideo }
|
||||
style = { videoButtonStyles.buttonStyle } />
|
||||
style = { videoButtonStyles.style } />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
@ -106,21 +106,33 @@ class Toolbar extends AbstractToolbar {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
_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.
|
||||
return (
|
||||
<View style = { styles.secondaryToolbar }>
|
||||
<ToolbarButton
|
||||
iconName = 'reload'
|
||||
iconStyle = { styles.whiteIcon }
|
||||
iconStyle = { iconStyle }
|
||||
onClick = { this._toggleCameraFacingMode }
|
||||
style = { styles.toggleCameraFacingModeButton }
|
||||
underlayColor = 'transparent' />
|
||||
style = { style }
|
||||
underlayColor = { underlayColor } />
|
||||
<ToolbarButton
|
||||
iconName = {
|
||||
this.props.locked ? 'security-locked' : 'security'
|
||||
}
|
||||
iconStyle = { iconStyle }
|
||||
onClick = { this._toggleLock }
|
||||
style = { style }
|
||||
underlayColor = { underlayColor } />
|
||||
</View>
|
||||
);
|
||||
|
||||
/* eslint-enable react/jsx-handler-names */
|
||||
/* eslint-enable react/jsx-curly-spacing,react/jsx-handler-names */
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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}
|
||||
*/
|
||||
const button = {
|
||||
alignSelf: 'center',
|
||||
borderRadius: 35,
|
||||
borderWidth: 0,
|
||||
flex: 0,
|
||||
flexDirection: 'row',
|
||||
height: 60,
|
||||
justifyContent: 'center',
|
||||
margin: BoxModel.margin,
|
||||
width: 60
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic container for buttons.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const container = {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic styles for an icon.
|
||||
* The base style for icons.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
@ -39,28 +27,45 @@ const icon = {
|
|||
fontSize: 24
|
||||
};
|
||||
|
||||
/**
|
||||
* The base style for toolbars.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const toolbar = {
|
||||
flex: 1,
|
||||
position: 'absolute'
|
||||
};
|
||||
|
||||
/**
|
||||
* The (conference) toolbar related styles.
|
||||
* TODO Make styles more generic and reusable. Use ColorPalette for all colors.
|
||||
*/
|
||||
export const styles = createStyleSheet({
|
||||
/**
|
||||
* The toolbar button icon style.
|
||||
*/
|
||||
icon: {
|
||||
...icon,
|
||||
color: ColorPalette.darkGrey
|
||||
},
|
||||
icon,
|
||||
|
||||
/**
|
||||
* The style of the toolbar which contains the primary buttons such as
|
||||
* hangup, audio and video mute.
|
||||
*/
|
||||
primaryToolbar: {
|
||||
...container,
|
||||
bottom: 30,
|
||||
height: 60,
|
||||
justifyContent: 'center'
|
||||
...toolbar,
|
||||
bottom: 3 * BoxModel.margin,
|
||||
flexDirection: 'row',
|
||||
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.
|
||||
*/
|
||||
secondaryToolbar: {
|
||||
...container,
|
||||
height: 60,
|
||||
justifyContent: 'flex-end'
|
||||
...toolbar,
|
||||
bottom: 0,
|
||||
flexDirection: 'column',
|
||||
right: 0,
|
||||
top: 0
|
||||
},
|
||||
|
||||
/**
|
||||
* The toggle camera facing mode button style.
|
||||
* The style of button in secondaryToolbar.
|
||||
*/
|
||||
toggleCameraFacingModeButton: {
|
||||
secondaryToolbarButton: {
|
||||
...button,
|
||||
backgroundColor: 'transparent'
|
||||
},
|
||||
|
||||
/**
|
||||
* The toolbar button style.
|
||||
*/
|
||||
toolbarButton: {
|
||||
...button,
|
||||
backgroundColor: ColorPalette.white,
|
||||
marginLeft: 20,
|
||||
marginRight: 20,
|
||||
opacity: 0.8
|
||||
},
|
||||
|
||||
/**
|
||||
* The toolbar container style.
|
||||
* The style of the root/top-level Container of Toolbar.
|
||||
*/
|
||||
toolbarContainer: {
|
||||
...container,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue