diff --git a/react/features/base/participants/functions.js b/react/features/base/participants/functions.js index b859ca479..8739a2d22 100644 --- a/react/features/base/participants/functions.js +++ b/react/features/base/participants/functions.js @@ -261,6 +261,23 @@ export function haveParticipantWithScreenSharingFeature(stateful: Object | Funct return toState(stateful)['features/base/participants'].haveParticipantWithScreenSharingFeature; } +/** + * Selectors for getting all known participant ids, with fake participants filtered + * out. + * + * @param {(Function|Object|Participant[])} stateful - The redux state + * features/base/participants, the (whole) redux state, or redux's + * {@code getState} function to be used to retrieve the state + * features/base/participants. + * @returns {Participant[]} + */ +export function getParticipantsById(stateful: Object | Function) { + const state = toState(stateful)['features/base/participants']; + const noFakeParticipants = state.filter(p => !p.fakeParticipants); + + return noFakeParticipants.map(p => p.id); +} + /** * Selectors for getting all remote participants. * diff --git a/react/features/participants-pane/actions.native.js b/react/features/participants-pane/actions.native.js index 8c8e190a5..422d61b81 100644 --- a/react/features/participants-pane/actions.native.js +++ b/react/features/participants-pane/actions.native.js @@ -20,11 +20,11 @@ export function showContextMenuReject(participant: Object) { /** * Displays the context menu for the selected meeting participant. * - * @param {Object} participant - The selected meeting participant. + * @param {string} participantID - The selected meeting participant id. * @returns {Function} */ -export function showContextMenuDetails(participant: Object) { - return openDialog(ContextMenuMeetingParticipantDetails, { participant }); +export function showContextMenuDetails(participantID: String) { + return openDialog(ContextMenuMeetingParticipantDetails, { participantID }); } /** diff --git a/react/features/participants-pane/components/native/ContextMenuMeetingParticipantDetails.js b/react/features/participants-pane/components/native/ContextMenuMeetingParticipantDetails.js index 0d60a1e1f..03aa3c6b1 100644 --- a/react/features/participants-pane/components/native/ContextMenuMeetingParticipantDetails.js +++ b/react/features/participants-pane/components/native/ContextMenuMeetingParticipantDetails.js @@ -4,9 +4,10 @@ import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { TouchableOpacity, View } from 'react-native'; import { Divider, Text } from 'react-native-paper'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { Avatar } from '../../../base/avatar'; +import { isToolbarButtonEnabled } from '../../../base/config'; import { hideDialog, openDialog } from '../../../base/dialog/actions'; import BottomSheet from '../../../base/dialog/components/native/BottomSheet'; import { @@ -15,10 +16,14 @@ import { IconMuteEveryoneElse, IconVideoOff } from '../../../base/icons'; import { - getParticipantsById, + getParticipantByIdOrUndefined, getParticipantDisplayName, isLocalParticipantModerator -} from '../../../base/participants'; -import { getIsParticipantVideoMuted } from '../../../base/tracks'; +} from '../../../base/participants/functions'; +import { connect } from '../../../base/redux'; +import { + isParticipantAudioMuted, + isParticipantVideoMuted +} from '../../../base/tracks/functions'; import { openChat } from '../../../chat/actions.native'; import { KickRemoteParticipantDialog, @@ -32,131 +37,179 @@ import styles from './styles'; type Props = { + /** + * The display name of the participant. + */ + _displayName: string, + + /** + * True if the local participant is moderator and false otherwise. + */ + _isLocalModerator: boolean, + + /** + * True if the chat button is enabled and false otherwise. + */ + _isChatButtonEnabled: boolean, + + /** + * True if the participant is moderator and false otherwise. + */ + _isParticipantModerator: boolean, + + /** + * True if the participant is video muted and false otherwise. + */ + _isParticipantVideoMuted: boolean, + + /** + * True if the participant is audio muted and false otherwise. + */ + _isParticipantAudioMuted: boolean, + /** * Participant reference */ - participant: Object + _participant: Object, + + /** + * The ID of the participant. + */ + participantID: string, }; -export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props) => { +export const ContextMenuMeetingParticipantDetails = ( + { + _displayName, + _isLocalModerator, + _isChatButtonEnabled, + _isParticipantVideoMuted, + _isParticipantAudioMuted, + _participant, + participantID + }: Props) => { const dispatch = useDispatch(); - const participantsIDArr = useSelector(getParticipantsById); - const participantIsAvailable = participantsIDArr.find(partId => partId === p.id); const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]); - const displayName = p.name; - const isLocalModerator = useSelector(isLocalParticipantModerator); - const isParticipantVideoMuted = useSelector(getIsParticipantVideoMuted(p)); const kickRemoteParticipant = useCallback(() => { dispatch(openDialog(KickRemoteParticipantDialog, { - participantID: p.id + participantID })); - }, [ dispatch, p ]); + }, [ dispatch, participantID ]); const muteAudio = useCallback(() => { dispatch(openDialog(MuteRemoteParticipantDialog, { - participantID: p.id + participantID })); - }, [ dispatch, p ]); + }, [ dispatch, participantID ]); const muteEveryoneElse = useCallback(() => { dispatch(openDialog(MuteEveryoneDialog, { - exclude: [ p.id ] + exclude: [ participantID ] })); - }, [ dispatch, p ]); + }, [ dispatch, participantID ]); const muteVideo = useCallback(() => { dispatch(openDialog(MuteRemoteParticipantsVideoDialog, { - participantID: p.id + participantID })); - }, [ dispatch, p ]); + }, [ dispatch, participantID ]); const sendPrivateMessage = useCallback(() => { dispatch(hideDialog()); - dispatch(openChat(p)); - }, [ dispatch, p ]); + dispatch(openChat(_participant)); + }, [ dispatch, _participant ]); const { t } = useTranslation(); return ( - { displayName } + { _displayName } { - isLocalModerator - && - - - { t('participantsPane.actions.mute') } - - - } - { - isLocalModerator - && - - - { t('participantsPane.actions.muteEveryoneElse') } - - + _isLocalModerator && ( + <> + { + !_isParticipantAudioMuted + && + + + { t('participantsPane.actions.mute') } + + + } + + + + + { t('participantsPane.actions.muteEveryoneElse') } + + + + ) } { - isLocalModerator && ( - isParticipantVideoMuted - || + _isLocalModerator && ( + <> + { + _isParticipantVideoMuted + || + + + { t('participantsPane.actions.stopVideo') } + + + } + + + + + { t('videothumbnail.kick') } + + + + ) + } + { + _isChatButtonEnabled && ( + + src = { IconMessage } /> - { t('participantsPane.actions.stopVideo') } + { t('toolbar.accessibilityLabel.privateMessage') } ) } - { - isLocalModerator - && - - - { t('videothumbnail.kick') } - - - } - - - - { t('toolbar.accessibilityLabel.privateMessage') } - - {/* We need design specs for this*/} {/* */} @@ -167,7 +220,36 @@ export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props) {/* { t('participantsPane.actions.networkStats') }*/} {/* */} - + ); }; + + +/** + * Maps (parts of) the redux state to the associated props for this component. + * + * @param {Object} state - The Redux state. + * @param {Object} ownProps - The own props of the component. + * @private + * @returns {Props} + */ +function _mapStateToProps(state, ownProps): Object { + const { participantID } = ownProps; + const participant = getParticipantByIdOrUndefined(state, participantID); + const _isLocalModerator = isLocalParticipantModerator(state); + const _isChatButtonEnabled = isToolbarButtonEnabled('chat', state); + const _isParticipantVideoMuted = isParticipantVideoMuted(participant, state); + const _isParticipantAudioMuted = isParticipantAudioMuted(participant, state); + + return { + _displayName: getParticipantDisplayName(state, participantID), + _isLocalModerator, + _isChatButtonEnabled, + _isParticipantAudioMuted, + _isParticipantVideoMuted, + _participant: participant + }; +} + +export default connect(_mapStateToProps)(ContextMenuMeetingParticipantDetails); diff --git a/react/features/participants-pane/components/native/LobbyParticipantItem.js b/react/features/participants-pane/components/native/LobbyParticipantItem.js index e53ac11f1..198f1247d 100644 --- a/react/features/participants-pane/components/native/LobbyParticipantItem.js +++ b/react/features/participants-pane/components/native/LobbyParticipantItem.js @@ -29,10 +29,13 @@ export const LobbyParticipantItem = ({ participant: p }: Props) => { return (