feat(native-participants-pane) context menu for meeting participant
This commit is contained in:
parent
47be509d17
commit
0b3991d9e1
|
@ -1,13 +1,24 @@
|
||||||
import { openDialog } from '../base/dialog';
|
import { openDialog } from '../base/dialog';
|
||||||
|
|
||||||
import { ContextMenuReject } from './components/native/ContextMenuReject';
|
import { ContextMenuLobbyParticipantReject, ContextMenuMeetingParticipantDetails } from './components/native';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the context menu for the selected lobby participant.
|
* Displays the context menu for the selected lobby participant.
|
||||||
*
|
*
|
||||||
* @param {string} participant - The selected participant's id.
|
* @param {Object} participant - The selected lobby participant.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function showContextMenuReject(participant) {
|
export function showContextMenuReject(participant) {
|
||||||
return openDialog(ContextMenuReject, { participant });
|
return openDialog(ContextMenuLobbyParticipantReject, { participant });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the context menu for the selected meeting participant.
|
||||||
|
*
|
||||||
|
* @param {Object} participant - The selected meeting participant.
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export function showContextMenuDetails(participant) {
|
||||||
|
return openDialog(ContextMenuMeetingParticipantDetails, { participant });
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ type Props = {
|
||||||
participant: Object
|
participant: Object
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ContextMenuReject = ({ participant: p }: Props) => {
|
export const ContextMenuLobbyParticipantReject = ({ participant: p }: Props) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
|
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
|
||||||
const displayName = p.name;
|
const displayName = p.name;
|
||||||
|
@ -35,23 +35,24 @@ export const ContextMenuReject = ({ participant: p }: Props) => {
|
||||||
onCancel = { cancel }
|
onCancel = { cancel }
|
||||||
style = { styles.contextMenuMore }>
|
style = { styles.contextMenuMore }>
|
||||||
<View
|
<View
|
||||||
style = { styles.contextMenuItemDetails }>
|
style = { styles.contextMenuItemSection }>
|
||||||
<Avatar
|
<Avatar
|
||||||
className = 'participant-avatar'
|
className = 'participant-avatar'
|
||||||
participantId = { p.id }
|
participantId = { p.id }
|
||||||
size = { 32 } />
|
size = { 32 } />
|
||||||
<View style = { styles.contextMenuItemText }>
|
<View style = { styles.contextMenuItemText }>
|
||||||
<Text style = { styles.contextMenuItemParticipantName }>
|
<Text style = { styles.contextMenuItemName }>
|
||||||
{ displayName }
|
{ displayName }
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress = { reject }
|
onPress = { reject }
|
||||||
style = { styles.contextMenuItemReject }>
|
style = { styles.contextMenuItem }>
|
||||||
<Icon
|
<Icon
|
||||||
size = { 24 }
|
size = { 24 }
|
||||||
src = { IconClose } />
|
src = { IconClose }
|
||||||
|
style = { styles.contextMenuItemIcon } />
|
||||||
<Text style = { styles.contextMenuItemText }>{ t('lobby.reject') }</Text>
|
<Text style = { styles.contextMenuItemText }>{ t('lobby.reject') }</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</BottomSheet>
|
</BottomSheet>
|
|
@ -0,0 +1,105 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { TouchableOpacity, View } from 'react-native';
|
||||||
|
import { Text } from 'react-native-paper';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
import { Avatar } from '../../../base/avatar';
|
||||||
|
import { hideDialog } from '../../../base/dialog';
|
||||||
|
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
|
||||||
|
import {
|
||||||
|
Icon, IconCloseCircle, IconConnectionActive, IconMessage,
|
||||||
|
IconMicrophoneEmptySlash,
|
||||||
|
IconMuteEveryoneElse, IconVideoOff
|
||||||
|
} from '../../../base/icons';
|
||||||
|
import { MEDIA_TYPE } from '../../../base/media';
|
||||||
|
import { muteRemote } from '../../../video-menu/actions.any';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Participant reference
|
||||||
|
*/
|
||||||
|
participant: Object
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
|
||||||
|
const displayName = p.name;
|
||||||
|
const muteAudio = useCallback(() => dispatch(muteRemote(p.id, MEDIA_TYPE.AUDIO)), [ dispatch ]);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BottomSheet
|
||||||
|
onCancel = { cancel }
|
||||||
|
style = { styles.contextMenuMore }>
|
||||||
|
<View
|
||||||
|
style = { styles.contextMenuItemSection }>
|
||||||
|
<Avatar
|
||||||
|
className = 'participant-avatar'
|
||||||
|
participantId = { p.id }
|
||||||
|
size = { 32 } />
|
||||||
|
<View style = { styles.contextMenuItemText }>
|
||||||
|
<Text style = { styles.contextMenuItemName }>
|
||||||
|
{ displayName }
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress = { muteAudio }
|
||||||
|
style = { styles.contextMenuItem }>
|
||||||
|
<Icon
|
||||||
|
size = { 24 }
|
||||||
|
src = { IconMicrophoneEmptySlash }
|
||||||
|
style = { styles.contextMenuItemIcon } />
|
||||||
|
<Text style = { styles.contextMenuItemText }>{ t('participantsPane.actions.mute') }</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress = { muteAudio }
|
||||||
|
style = { styles.contextMenuItem }>
|
||||||
|
<Icon
|
||||||
|
size = { 24 }
|
||||||
|
src = { IconMuteEveryoneElse }
|
||||||
|
style = { styles.contextMenuItemIcon } />
|
||||||
|
<Text style = { styles.contextMenuItemText }>{ t('participantsPane.actions.muteEveryoneElse') }</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style = { styles.contextMenuItemSection }>
|
||||||
|
<Icon
|
||||||
|
size = { 24 }
|
||||||
|
src = { IconVideoOff }
|
||||||
|
style = { styles.contextMenuItemIcon } />
|
||||||
|
<Text style = { styles.contextMenuItemText }>{ t('participantsPane.actions.stopVideo') }</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style = { styles.contextMenuItem }>
|
||||||
|
<Icon
|
||||||
|
size = { 24 }
|
||||||
|
src = { IconCloseCircle }
|
||||||
|
style = { styles.contextMenuItemIcon } />
|
||||||
|
<Text style = { styles.contextMenuItemText }>{ t('videothumbnail.kick') }</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style = { styles.contextMenuItem }>
|
||||||
|
<Icon
|
||||||
|
size = { 24 }
|
||||||
|
src = { IconMessage }
|
||||||
|
style = { styles.contextMenuItemIcon } />
|
||||||
|
<Text style = { styles.contextMenuItemText }>{ t('toolbar.accessibilityLabel.privateMessage') }</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style = { styles.contextMenuItemSection }>
|
||||||
|
<Icon
|
||||||
|
size = { 24 }
|
||||||
|
src = { IconConnectionActive }
|
||||||
|
style = { styles.contextMenuItemIcon } />
|
||||||
|
<Text style = { styles.contextMenuItemText }>{ t('participantsPane.actions.networkStats') }</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</BottomSheet>
|
||||||
|
);
|
||||||
|
};
|
|
@ -41,14 +41,14 @@ export const ContextMenuMore = ({ exclude }: Props) => {
|
||||||
style = { styles.contextMenuMore }>
|
style = { styles.contextMenuMore }>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress = { muteEveryoneVideo }
|
onPress = { muteEveryoneVideo }
|
||||||
style = { styles.contextMenuItemMuteVideo }>
|
style = { styles.contextMenuItem }>
|
||||||
<Icon
|
<Icon
|
||||||
size = { 24 }
|
size = { 24 }
|
||||||
src = { IconVideoOff } />
|
src = { IconVideoOff } />
|
||||||
<Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
|
<Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style = { styles.contextMenuItemDontAllowUnmute }>
|
style = { styles.contextMenuItem }>
|
||||||
<Icon
|
<Icon
|
||||||
size = { 24 }
|
size = { 24 }
|
||||||
src = { IconMicDisabledHollow }
|
src = { IconMicDisabledHollow }
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getIsParticipantAudioMuted,
|
getIsParticipantAudioMuted,
|
||||||
getIsParticipantVideoMuted
|
getIsParticipantVideoMuted
|
||||||
} from '../../../base/tracks';
|
} from '../../../base/tracks';
|
||||||
|
import { showContextMenuDetails } from '../../actions.native';
|
||||||
import { MediaState } from '../../constants';
|
import { MediaState } from '../../constants';
|
||||||
|
|
||||||
import ParticipantItem from './ParticipantItem';
|
import ParticipantItem from './ParticipantItem';
|
||||||
|
@ -21,13 +22,16 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MeetingParticipantItem = ({ participant: p }: Props) => {
|
export const MeetingParticipantItem = ({ participant: p }: Props) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
const isAudioMuted = useSelector(getIsParticipantAudioMuted(p));
|
const isAudioMuted = useSelector(getIsParticipantAudioMuted(p));
|
||||||
const isVideoMuted = useSelector(getIsParticipantVideoMuted(p));
|
const isVideoMuted = useSelector(getIsParticipantVideoMuted(p));
|
||||||
|
const openContextMenuDetails = useCallback(() => dispatch(showContextMenuDetails(p), [ dispatch ]));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ParticipantItem
|
<ParticipantItem
|
||||||
audioMuteState = { isAudioMuted ? MediaState.Muted : MediaState.Unmuted }
|
audioMuteState = { isAudioMuted ? MediaState.Muted : MediaState.Unmuted }
|
||||||
name = { p.name }
|
name = { p.name }
|
||||||
|
onPress = { openContextMenuDetails }
|
||||||
participant = { p }
|
participant = { p }
|
||||||
videoMuteState = { isVideoMuted ? MediaState.Muted : MediaState.Unmuted } />
|
videoMuteState = { isVideoMuted ? MediaState.Muted : MediaState.Unmuted } />
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,3 +2,5 @@
|
||||||
|
|
||||||
export { default as ParticipantsPane } from './ParticipantsPane';
|
export { default as ParticipantsPane } from './ParticipantsPane';
|
||||||
export { default as ParticipantsPaneButton } from './ParticipantsPaneButton';
|
export { default as ParticipantsPaneButton } from './ParticipantsPaneButton';
|
||||||
|
export { ContextMenuLobbyParticipantReject } from './ContextMenuLobbyParticipantReject';
|
||||||
|
export { ContextMenuMeetingParticipantDetails } from './ContextMenuMeetingParticipantDetails';
|
||||||
|
|
|
@ -287,33 +287,29 @@ export default {
|
||||||
textTransform: 'capitalize'
|
textTransform: 'capitalize'
|
||||||
},
|
},
|
||||||
|
|
||||||
contextMenuItemMuteVideo: {
|
contextMenuItem: {
|
||||||
...contextMenuItem
|
...contextMenuItem
|
||||||
},
|
},
|
||||||
|
|
||||||
contextMenuItemDontAllowUnmute: {
|
contextMenuItemSection: {
|
||||||
...contextMenuItem
|
|
||||||
},
|
|
||||||
|
|
||||||
contextMenuItemDetails: {
|
|
||||||
...contextMenuItem,
|
...contextMenuItem,
|
||||||
borderBottomColor: BaseTheme.palette.section01,
|
borderBottomColor: BaseTheme.palette.section01,
|
||||||
borderBottomWidth: 1
|
borderBottomWidth: 1
|
||||||
},
|
},
|
||||||
|
|
||||||
contextMenuItemReject: {
|
|
||||||
...contextMenuItem
|
|
||||||
},
|
|
||||||
|
|
||||||
contextMenuItemText: {
|
contextMenuItemText: {
|
||||||
...BaseTheme.typography.bodyShortRegularLarge,
|
...BaseTheme.typography.bodyShortRegularLarge,
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
color: BaseTheme.palette.text01,
|
color: BaseTheme.palette.text01,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
marginLeft: 8
|
marginLeft: BaseTheme.spacing[3]
|
||||||
},
|
},
|
||||||
|
|
||||||
contextMenuItemParticipantName: {
|
contextMenuItemIcon: {
|
||||||
|
marginLeft: BaseTheme.spacing[1]
|
||||||
|
},
|
||||||
|
|
||||||
|
contextMenuItemName: {
|
||||||
...BaseTheme.typography.bodyShortRegularLarge,
|
...BaseTheme.typography.bodyShortRegularLarge,
|
||||||
color: BaseTheme.palette.text01
|
color: BaseTheme.palette.text01
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue