feat(native-participants-pane) implemented review remarks pt. 1

This commit is contained in:
Calin Chitu 2021-06-29 17:05:11 +03:00 committed by Hristo Terezov
parent 36cb896680
commit c6e50ad439
14 changed files with 123 additions and 89 deletions

View File

@ -60,7 +60,15 @@ export function getRecordingSharingUrl(state: Object) {
return state['features/base/config'].recordingSharingUrl;
}
/* eslint-disable max-params, no-shadow */
/**
* Selector used to get if the new participant is starting silent.
*
* @param {Object} state - The global state.
* @returns {string}
*/
export function getIsStartingSilent(state: Object) {
return Boolean(state['features/base/config'].startSilent);
}
/**
* Overrides JSON properties in {@code config} and

View File

@ -56,9 +56,7 @@ class HeaderWithNavigation extends Component<Props> {
const { hideHeaderWithNavigation, onPressBack, onPressForward } = this.props;
return (
<>
{
!hideHeaderWithNavigation
!{ hideHeaderWithNavigation }
&& <Header>
{ onPressBack && <BackButton onPress = { onPressBack } /> }
<HeaderLabel labelKey = { this.props.headerLabelKey } />
@ -67,8 +65,6 @@ class HeaderWithNavigation extends Component<Props> {
labelKey = { this.props.forwardLabelKey }
onPress = { onPressForward } /> }
</Header>
}
</>
);
}
}

View File

@ -75,7 +75,7 @@ type Props = AbstractProps & {
/**
* The indicator which determines if the participants pane is open.
*/
_isOpen: boolean,
_isParticipantsPaneOpen: boolean,
/**
* The ID of the participant currently on stage (if any)
@ -243,7 +243,7 @@ class Conference extends AbstractConference<Props, *> {
_renderContent() {
const {
_connecting,
_isOpen,
_isParticipantsPaneOpen,
_largeVideoParticipantId,
_reducedUI,
_shouldDisplayTileView
@ -309,7 +309,7 @@ class Conference extends AbstractConference<Props, *> {
{_shouldDisplayTileView && <Toolbox />}
{ _isOpen && <ParticipantsPane /> }
{ _isParticipantsPaneOpen && <ParticipantsPane /> }
</>
);
@ -423,7 +423,7 @@ function _mapStateToProps(state) {
_connecting: Boolean(connecting_),
_filmstripVisible: isFilmstripVisible(state),
_fullscreenEnabled: getFeatureFlag(state, FULLSCREEN_ENABLED, true),
_isOpen: isOpen,
_isParticipantsPaneOpen: isOpen,
_largeVideoParticipantId: state['features/large-video'].participantId,
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
_reducedUI: reducedUI,

View File

@ -4,9 +4,10 @@ import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { TouchableOpacity, View } from 'react-native';
import { Divider, Text } from 'react-native-paper';
import { useDispatch, useSelector, useStore } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { Avatar } from '../../../base/avatar';
import { getIsStartingSilent } from '../../../base/config';
import { hideDialog, openDialog } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import {
@ -37,14 +38,14 @@ type Props = {
export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props) => {
const [ volume, setVolume ] = useState(undefined);
const store = useStore();
const startSilent = store.getState['features/base/config'];
const dispatch = useDispatch();
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
const changeVolume = useCallback(() => setVolume(volume), [ volume ]);
const changeVolume = useCallback(() => setVolume(volume), [ setVolume, volume ]);
const displayName = p.name;
const isLocalModerator = useSelector(isLocalParticipantModerator);
const isParticipantVideoMuted = useSelector(getIsParticipantVideoMuted(p));
const isStartingSilent = useSelector(getIsStartingSilent);
const kickRemoteParticipant = useCallback(() => {
dispatch(openDialog(KickRemoteParticipantDialog, {
participantID: p.id
@ -65,7 +66,7 @@ export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props)
participantID: p.id
}));
}, [ dispatch, p ]);
const onVolumeChange = startSilent ? undefined : changeVolume;
const onVolumeChange = isStartingSilent ? undefined : changeVolume;
const sendPrivateMessage = useCallback(() => {
dispatch(hideDialog());
dispatch(openChat(p));

View File

@ -4,7 +4,7 @@ import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { TouchableOpacity } from 'react-native';
import { Text } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { openDialog, hideDialog } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
@ -12,26 +12,22 @@ import {
Icon, IconMicDisabledHollow,
IconVideoOff
} from '../../../base/icons';
import { MEDIA_TYPE } from '../../../base/media';
import { getLocalParticipant } from '../../../base/participants';
import { BlockAudioVideoDialog } from '../../../video-menu';
import {
muteAllParticipants
} from '../../../video-menu/actions.any';
import MuteEveryonesVideoDialog
from '../../../video-menu/components/native/MuteEveryonesVideoDialog';
import styles from './styles';
type Props = {
/**
* Array of participant IDs to not mute
*/
exclude: Array<string>
};
export const ContextMenuMore = ({ exclude }: Props) => {
export const ContextMenuMore = () => {
const dispatch = useDispatch();
const blockAudioVideo = useCallback(() => dispatch(openDialog(BlockAudioVideoDialog)), [ dispatch ]);
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
const muteEveryoneVideo = useCallback(() => dispatch(muteAllParticipants(exclude, MEDIA_TYPE.VIDEO)), [ dispatch ]);
const { id } = useSelector(getLocalParticipant);
const muteAllVideo = useCallback(() =>
dispatch(openDialog(MuteEveryonesVideoDialog,
{ exclude: [ id ] })),
[ dispatch ]);
const { t } = useTranslation();
return (
@ -39,7 +35,7 @@ export const ContextMenuMore = ({ exclude }: Props) => {
onCancel = { cancel }
style = { styles.contextMenuMore }>
<TouchableOpacity
onPress = { muteEveryoneVideo }
onPress = { muteAllVideo }
style = { styles.contextMenuItem }>
<Icon
size = { 20 }

View File

@ -5,9 +5,9 @@ import { useTranslation } from 'react-i18next';
import { Button } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
import { approveKnockingParticipant } from '../../../lobby/actions.native';
import { showContextMenuReject } from '../../actions.native';
import { MediaState } from '../../constants';
import { MEDIA_STATE } from '../../constants';
import ParticipantItem from './ParticipantItem';
import styles from './styles';
@ -22,18 +22,18 @@ type Props = {
export const LobbyParticipantItem = ({ participant: p }: Props) => {
const dispatch = useDispatch();
const admit = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, true), [ dispatch ]));
const admit = useCallback(() => dispatch(approveKnockingParticipant(p.id), [ dispatch ]));
const openContextMenuReject = useCallback(() => dispatch(showContextMenuReject(p), [ dispatch ]));
const { t } = useTranslation();
return (
<ParticipantItem
audioMuteState = { MediaState.Muted }
audioMediaState = { MEDIA_STATE.NONE }
isKnockingParticipant = { true }
name = { p.name }
onPress = { openContextMenuReject }
participant = { p }
videoMuteState = { MediaState.ForceMuted }>
videoMediaState = { MEDIA_STATE.NONE }>
<Button
children = { t('lobby.admit') }
contentStyle = { styles.participantActionsButtonContent }

View File

@ -6,8 +6,8 @@ import { Text, View } from 'react-native';
import { Button } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux';
import { admitMultiple } from '../../../lobby/actions.native';
import { getLobbyState } from '../../../lobby/functions';
import { admitAllKnockingParticipants } from '../../../video-menu/actions.any';
import { LobbyParticipantItem } from './LobbyParticipantItem';
import styles from './styles';
@ -20,7 +20,7 @@ export const LobbyParticipantList = () => {
const dispatch = useDispatch();
const admitAll = useCallback(() =>
dispatch(admitAllKnockingParticipants(participants, lobbyEnabled)),
dispatch(admitMultiple(participants)),
[ dispatch ]);
const { t } = useTranslation();

View File

@ -9,6 +9,7 @@ import {
} from '../../../base/tracks';
import { showContextMenuDetails } from '../../actions.native';
import { MEDIA_STATE } from '../../constants';
import { getParticipantAudioMediaState } from '../../functions';
import ParticipantItem from './ParticipantItem';
@ -25,15 +26,17 @@ export const MeetingParticipantItem = ({ participant: p }: Props) => {
const dispatch = useDispatch();
const isAudioMuted = useSelector(getIsParticipantAudioMuted(p));
const isVideoMuted = useSelector(getIsParticipantVideoMuted(p));
const audioMediaState = useSelector(getParticipantAudioMediaState(p, isAudioMuted));
const openContextMenuDetails = useCallback(() => !p.local && dispatch(showContextMenuDetails(p), [ dispatch ]));
return (
<ParticipantItem
audioMuteState = { isAudioMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED }
audioMediaState = { audioMediaState }
isKnockingParticipant = { false }
name = { p.name }
onPress = { openContextMenuDetails }
participant = { p }
videoMuteState = { isVideoMuted ? MEDIA_STATE.Muted : MEDIA_STATE.Unmuted } />
videoMediaState = { isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED } />
);
};

View File

@ -4,7 +4,7 @@ import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Text, View } from 'react-native';
import { Button } from 'react-native-paper';
import { useDispatch, useSelector, useStore } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { Icon, IconInviteMore } from '../../../base/icons';
import { getParticipants } from '../../../base/participants';
@ -18,9 +18,7 @@ export const MeetingParticipantList = () => {
const dispatch = useDispatch();
const onInvite = useCallback(() => dispatch(doInvitePeople()), [ dispatch ]);
const showInviteButton = useSelector(shouldRenderInviteButton);
const store = useStore();
const state = store.getState();
const participants = getParticipants(state);
const participants = useSelector(getParticipants);
const { t } = useTranslation();
return (

View File

@ -9,11 +9,7 @@ import { useSelector } from 'react-redux';
import { Avatar } from '../../../base/avatar';
import { getParticipantDisplayNameWithId } from '../../../base/participants';
import {
AudioStateIcons,
MediaState,
VideoStateIcons
} from '../../constants';
import { MEDIA_STATE, type MediaState, AudioStateIcons, VideoStateIcons } from '../../constants';
import { RaisedHandIndicator } from './RaisedHandIndicator';
import styles from './styles';
@ -40,11 +36,6 @@ type Props = {
*/
name?: string,
/**
* Callback for when the mouse leaves this component
*/
onLeave?: Function,
/**
* Callback to be invoked on pressing the participant item.
*/
@ -72,8 +63,8 @@ function ParticipantItem({
name,
onPress,
participant: p,
audioMediaState = MediaState.None,
videoMediaState = MediaState.None
audioMediaState = MEDIA_STATE.NONE,
videoMediaState = MEDIA_STATE.NONE
}: Props) {
const displayName = name || useSelector(getParticipantDisplayNameWithId(p.id));
@ -82,7 +73,6 @@ function ParticipantItem({
return (
<View style = { styles.participantContainer } >
<TouchableOpacity
/* eslint-disable-next-line react/jsx-no-bind */
onPress = { onPress }
style = { styles.participantContent }>
<Avatar

View File

@ -10,6 +10,7 @@ import { openDialog } from '../../../base/dialog';
import { Icon, IconClose, IconHorizontalPoints } from '../../../base/icons';
import { JitsiModal } from '../../../base/modal';
import {
getParticipantCount, isEveryoneModerator,
isLocalParticipantModerator
} from '../../../base/participants';
import MuteEveryoneDialog
@ -19,7 +20,7 @@ import { close } from '../../actions.native';
import { ContextMenuMore } from './ContextMenuMore';
import { LobbyParticipantList } from './LobbyParticipantList';
import { MeetingParticipantList } from './MeetingParticipantList';
import styles from './styles';
import styles, { button } from './styles';
/**
* Participant pane.
@ -31,6 +32,9 @@ const ParticipantsPane = () => {
const openMoreMenu = useCallback(() => dispatch(openDialog(ContextMenuMore)), [ dispatch ]);
const closePane = useCallback(() => dispatch(close()), [ dispatch ]);
const isLocalModerator = useSelector(isLocalParticipantModerator);
const participantsCount = useSelector(getParticipantCount);
const everyoneModerator = useSelector(isEveryoneModerator);
const showContextMenu = !everyoneModerator && participantsCount > 2;
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
[ dispatch ]);
const { t } = useTranslation();
@ -65,8 +69,10 @@ const ParticipantsPane = () => {
labelStyle = { styles.muteAllLabel }
mode = 'contained'
onPress = { muteAll }
style = { styles.muteAllButton } />
<Button
style = { showContextMenu ? styles.muteAllButton : button } />
{
showContextMenu
&& <Button
contentStyle = { styles.moreIcon }
/* eslint-disable-next-line react/jsx-no-bind */
icon = { () =>
@ -77,6 +83,7 @@ const ParticipantsPane = () => {
mode = 'contained'
onPress = { openMoreMenu }
style = { styles.moreButton } />
}
</View>
}
</JitsiModal>

View File

@ -25,7 +25,7 @@ const flexContent = {
/**
* The style of the participants pane buttons.
*/
const button = {
export const button = {
alignItems: 'center',
backgroundColor: BaseTheme.palette.action02,
borderRadius: BaseTheme.shape.borderRadius,

View File

@ -1,5 +1,13 @@
// @flow
import React from 'react';
import {
Icon, IconCameraEmpty, IconCameraEmptyDisabled,
IconMicrophoneEmpty,
IconMicrophoneEmptySlash
} from '../base/icons';
/**
* Reducer key for the feature.
*/
@ -46,3 +54,49 @@ export const QUICK_ACTION_BUTTON: {
ASK_TO_UNMUTE: 'AskToUnmute',
NONE: 'None'
};
/**
* Icon mapping for possible participant audio states.
*/
export const AudioStateIcons: {[MediaState]: React$Element<any> | null} = {
[MEDIA_STATE.FORCE_MUTED]: (
<Icon
color = '#E04757'
size = { 16 }
src = { IconMicrophoneEmptySlash } />
),
[MEDIA_STATE.MUTED]: (
<Icon
size = { 16 }
src = { IconMicrophoneEmptySlash } />
),
[MEDIA_STATE.UNMUTED]: (
<Icon
color = '#1EC26A'
size = { 16 }
src = { IconMicrophoneEmpty } />
),
[MEDIA_STATE.NONE]: null
};
/**
* Icon mapping for possible participant video states.
*/
export const VideoStateIcons = {
[MEDIA_STATE.FORCE_MUTED]: (
<Icon
size = { 16 }
src = { IconCameraEmptyDisabled } />
),
[MEDIA_STATE.MUTED]: (
<Icon
size = { 16 }
src = { IconCameraEmptyDisabled } />
),
[MEDIA_STATE.UNMUTED]: (
<Icon
size = { 16 }
src = { IconCameraEmpty } />
),
[MEDIA_STATE.NONE]: null
};

View File

@ -24,7 +24,6 @@ import {
muteRemoteParticipant
} from '../base/participants';
import { getIsParticipantAudioMuted } from '../base/tracks';
import { setKnockingParticipantApproval } from '../lobby/actions';
declare var APP: Object;
@ -109,24 +108,6 @@ export function muteAllParticipants(exclude: Array<string>, mediaType: MEDIA_TYP
};
}
/**
* Admit all knocking participants.
*
* @param {Array<Object>} knockingParticipants - Array of participants waiting in lobby.
* @param {boolean} lobbyEnabled - Is lobby mode enabled.
*
* @returns {Function}
*/
export function admitAllKnockingParticipants(knockingParticipants: Array<Object>, lobbyEnabled: boolean) {
return (dispatch: Dispatch<any>) => {
const knockingParticipantsIds = knockingParticipants.map(participant => participant.id);
knockingParticipantsIds
.map(id => lobbyEnabled && setKnockingParticipantApproval(id, true))
.map(dispatch);
};
}
/**
* Don't allow participants to unmute video/audio.