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 (