[RN] Add remote video menu

This commit is contained in:
Bettenbuk Zoltan
2018-12-19 19:40:17 +01:00
committed by Zoltan Bettenbuk
parent d4c0840659
commit 6b68fba220
27 changed files with 582 additions and 65 deletions

View File

@@ -0,0 +1,46 @@
// @flow
import { connect } from 'react-redux';
import { openDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { AbstractButton } from '../../../base/toolbox';
import type { AbstractButtonProps } from '../../../base/toolbox';
import KickRemoteParticipantDialog from './KickRemoteParticipantDialog';
type Props = AbstractButtonProps & {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
/**
* The participant object that this button is supposed to kick.
*/
participant: Object
};
/**
* A remote video menu button which kicks the remote participant.
*/
class KickButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.audioRoute';
iconName = 'icon-kick';
label = 'videothumbnail.kick';
/**
* Handles clicking / pressing the button, and kicks the participant.
*
* @private
* @returns {void}
*/
_handleClick() {
const { dispatch, participant } = this.props;
dispatch(openDialog(KickRemoteParticipantDialog, { participant }));
}
}
export default translate(connect()(KickButton));

View File

@@ -0,0 +1,84 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
createRemoteVideoMenuButtonEvent,
sendAnalytics
} from '../../../analytics';
import { ConfirmDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { kickParticipant } from '../../../base/participants';
type Props = {
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The remote participant to be kicked.
*/
participant: Object,
/**
* Function to translate i18n labels.
*/
t: Function
};
/**
* Dialog to confirm a remote participant kick action.
*/
class KickRemoteParticipantDialog extends Component<Props> {
/**
* Initializes a new {@code KickRemoteParticipantDialog} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onSubmit = this._onSubmit.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<ConfirmDialog
contentKey = 'dialog.kickParticipantDialog'
onSubmit = { this._onSubmit } />
);
}
_onSubmit: () => boolean;
/**
* Callback for the confirm button.
*
* @private
* @returns {boolean} - True (to note that the modal should be closed).
*/
_onSubmit() {
const { dispatch, participant } = this.props;
sendAnalytics(createRemoteVideoMenuButtonEvent(
'kick.button',
{
'participant_id': participant.id
}));
dispatch(kickParticipant(participant.id));
return true;
}
}
export default translate(connect()(KickRemoteParticipantDialog));

View File

@@ -0,0 +1,116 @@
// @flow
import { connect } from 'react-redux';
import {
createRemoteVideoMenuButtonEvent,
sendAnalytics
} from '../../../analytics';
import { openDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { MEDIA_TYPE } from '../../../base/media';
import {
AbstractButton,
type AbstractButtonProps
} from '../../../base/toolbox';
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
import MuteRemoteParticipantDialog from './MuteRemoteParticipantDialog';
type Props = AbstractButtonProps & {
/**
* The audio track of the participant.
*/
_audioTrack: ?Object,
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
/**
* The participant object that this button is supposed to mute/unmute.
*/
participant: Object
};
/**
* A remote video menu button which mutes the remote participant.
*/
class MuteButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.audioRoute';
iconName = 'icon-mic-disabled';
label = 'videothumbnail.domute';
toggledLabel = 'videothumbnail.muted';
/**
* Handles clicking / pressing the button, and mutes the participant.
*
* @private
* @returns {void}
*/
_handleClick() {
const { dispatch, participant } = this.props;
sendAnalytics(createRemoteVideoMenuButtonEvent(
'mute.button',
{
'participant_id': participant.id
}));
dispatch(openDialog(MuteRemoteParticipantDialog, { participant }));
}
/**
* Renders the item disabled if the participant is muted.
*
* @inheritdoc
*/
_isDisabled() {
return this._isMuted();
}
/**
* Returns true if the participant is muted, false otherwise.
*
* @returns {boolean}
*/
_isMuted() {
const { _audioTrack } = this.props;
return !_audioTrack || _audioTrack.muted;
}
/**
* Renders the item toggled if the participant is muted.
*
* @inheritdoc
*/
_isToggled() {
return this._isMuted();
}
}
/**
* Function that maps parts of Redux state tree into component props.
*
* @param {Object} state - Redux state.
* @param {Object} ownProps - Properties of component.
* @private
* @returns {{
* _audioTrack: Track
* }}
*/
function _mapStateToProps(state, ownProps) {
const tracks = state['features/base/tracks'];
const audioTrack
= getTrackByMediaTypeAndParticipant(
tracks, MEDIA_TYPE.AUDIO, ownProps.participant.id);
return {
_audioTrack: audioTrack
};
}
export default translate(connect(_mapStateToProps)(MuteButton));

View File

@@ -0,0 +1,80 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
createRemoteMuteConfirmedEvent,
sendAnalytics
} from '../../../analytics';
import { ConfirmDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { muteRemoteParticipant } from '../../../base/participants';
type Props = {
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The remote participant to be muted.
*/
participant: Object,
/**
* Function to translate i18n labels.
*/
t: Function
};
/**
* Dialog to confirm a remote participant mute action.
*/
class MuteRemoteParticipantDialog extends Component<Props> {
/**
* Initializes a new {@code MuteRemoteParticipantDialog} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onSubmit = this._onSubmit.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<ConfirmDialog
contentKey = 'dialog.muteParticipantDialog'
onSubmit = { this._onSubmit } />
);
}
_onSubmit: () => boolean;
/**
* Callback for the confirm button.
*
* @private
* @returns {boolean} - True (to note that the modal should be closed).
*/
_onSubmit() {
const { dispatch, participant } = this.props;
sendAnalytics(createRemoteMuteConfirmedEvent(participant.id));
dispatch(muteRemoteParticipant(participant.id));
return true;
}
}
export default translate(connect()(MuteRemoteParticipantDialog));

View File

@@ -0,0 +1,109 @@
// @flow
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
import {
BottomSheet,
bottomSheetItemStylesCombined
} from '../../../base/dialog';
import { getParticipantDisplayName } from '../../../base/participants';
import { hideRemoteVideoMenu } from '../../actions';
import KickButton from './KickButton';
import MuteButton from './MuteButton';
import styles from './styles';
type Props = {
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The participant for which this menu opened for.
*/
participant: Object,
/**
* Display name of the participant retreived from Redux.
*/
_participantDisplayName: string
}
/**
* Class to implement a popup menu that opens upon long pressing a thumbnail.
*/
class RemoteVideoMenu extends Component<Props> {
/**
* Constructor of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onCancel = this._onCancel.bind(this);
}
/**
* Implements {@code Component#render}.
*
* @inheritdoc
*/
render() {
const buttonProps = {
afterClick: this._onCancel,
showLabel: true,
participant: this.props.participant,
styles: bottomSheetItemStylesCombined
};
return (
<BottomSheet onCancel = { this._onCancel }>
<View style = { styles.participantNameContainer }>
<Text style = { styles.participantNameLabel }>
{ this.props._participantDisplayName }
</Text>
</View>
<MuteButton { ...buttonProps } />
<KickButton { ...buttonProps } />
</BottomSheet>
);
}
_onCancel: () => void;
/**
* Callback to hide the {@code RemoteVideoMenu}.
*
* @private
* @returns {void}
*/
_onCancel() {
this.props.dispatch(hideRemoteVideoMenu());
}
}
/**
* Function that maps parts of Redux state tree into component props.
*
* @param {Object} state - Redux state.
* @param {Object} ownProps - Properties of component.
* @private
* @returns {{
* _participantDisplayName: string
* }}
*/
function _mapStateToProps(state, ownProps) {
const { id } = ownProps.participant;
return {
_participantDisplayName: getParticipantDisplayName(state, id)
};
}
export default connect(_mapStateToProps)(RemoteVideoMenu);

View File

@@ -0,0 +1,3 @@
// @flow
export { default as RemoteVideoMenu } from './RemoteVideoMenu';

View File

@@ -0,0 +1,20 @@
// @flow
import { ColorPalette, createStyleSheet } from '../../../base/styles';
export default createStyleSheet({
participantNameContainer: {
alignItems: 'center',
borderBottomColor: ColorPalette.darkGrey,
borderBottomWidth: 1,
flexDirection: 'row',
height: 48
},
participantNameLabel: {
color: ColorPalette.lightGrey,
flexShrink: 1,
fontSize: 16,
opacity: 0.90
}
});