feat(native-participants-pane) implemented review remarks pt. 1
This commit is contained in:
parent
36cb896680
commit
c6e50ad439
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 } />
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue