您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

ContextMenuMeetingParticipantDetails.js 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. // @flow
  2. import React, { useCallback } from 'react';
  3. import { useTranslation } from 'react-i18next';
  4. import { TouchableOpacity, View } from 'react-native';
  5. import { Divider, Text } from 'react-native-paper';
  6. import { useDispatch } from 'react-redux';
  7. import { Avatar } from '../../../base/avatar';
  8. import { hideDialog, openDialog } from '../../../base/dialog/actions';
  9. import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
  10. import {
  11. Icon, IconCloseCircle, IconMessage,
  12. IconMicrophoneEmptySlash,
  13. IconMuteEveryoneElse, IconVideoOff
  14. } from '../../../base/icons';
  15. import {
  16. getLocalParticipant,
  17. getParticipantByIdOrUndefined,
  18. getParticipantDisplayName, getRemoteParticipants,
  19. isLocalParticipantModerator
  20. } from '../../../base/participants/functions';
  21. import { connect } from '../../../base/redux';
  22. import {
  23. isParticipantAudioMuted,
  24. isParticipantVideoMuted
  25. } from '../../../base/tracks/functions';
  26. import { openChat } from '../../../chat/actions.native';
  27. import {
  28. KickRemoteParticipantDialog,
  29. MuteEveryoneDialog,
  30. MuteRemoteParticipantDialog,
  31. MuteRemoteParticipantsVideoDialog
  32. } from '../../../video-menu';
  33. import VolumeSlider from '../../../video-menu/components/native/VolumeSlider';
  34. import styles from './styles';
  35. type Props = {
  36. /**
  37. * The display name of the participant.
  38. */
  39. _displayName: string,
  40. /**
  41. * True if the local participant is moderator and false otherwise.
  42. */
  43. _isLocalModerator: boolean,
  44. /**
  45. * True if the participant is moderator and false otherwise.
  46. */
  47. _isParticipantModerator: boolean,
  48. /**
  49. * True if the participant is video muted and false otherwise.
  50. */
  51. _isParticipantVideoMuted: boolean,
  52. /**
  53. * True if the participant is audio muted and false otherwise.
  54. */
  55. _isParticipantAudioMuted: boolean,
  56. /**
  57. * Whether the participant is present in the room or not.
  58. */
  59. _isParticipantIDAvailable?: boolean,
  60. /**
  61. * Participant reference
  62. */
  63. _participant: Object,
  64. /**
  65. * The ID of the participant.
  66. */
  67. participantID: string,
  68. };
  69. const ContextMenuMeetingParticipantDetails = (
  70. {
  71. _displayName,
  72. _isLocalModerator,
  73. _isParticipantVideoMuted,
  74. _isParticipantAudioMuted,
  75. _participant,
  76. _isParticipantIDAvailable,
  77. participantID
  78. }: Props) => {
  79. const dispatch = useDispatch();
  80. const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
  81. const kickRemoteParticipant = useCallback(() => {
  82. dispatch(openDialog(KickRemoteParticipantDialog, {
  83. participantID
  84. }));
  85. }, [ dispatch, participantID ]);
  86. const muteAudio = useCallback(() => {
  87. dispatch(openDialog(MuteRemoteParticipantDialog, {
  88. participantID
  89. }));
  90. }, [ dispatch, participantID ]);
  91. const muteEveryoneElse = useCallback(() => {
  92. dispatch(openDialog(MuteEveryoneDialog, {
  93. exclude: [ participantID ]
  94. }));
  95. }, [ dispatch, participantID ]);
  96. const muteVideo = useCallback(() => {
  97. dispatch(openDialog(MuteRemoteParticipantsVideoDialog, {
  98. participantID
  99. }));
  100. }, [ dispatch, participantID ]);
  101. const sendPrivateMessage = useCallback(() => {
  102. dispatch(hideDialog());
  103. dispatch(openChat(_participant));
  104. }, [ dispatch, _participant ]);
  105. const { t } = useTranslation();
  106. return (
  107. <BottomSheet
  108. addScrollViewPadding = { false }
  109. onCancel = { cancel }
  110. showSlidingView = { _isParticipantIDAvailable }
  111. style = { styles.contextMenuMeetingParticipantDetails }>
  112. <View
  113. style = { styles.contextMenuItemSectionAvatar }>
  114. <Avatar
  115. className = 'participant-avatar'
  116. participantId = { participantID }
  117. size = { 20 } />
  118. <View style = { styles.contextMenuItemAvatarText }>
  119. <Text style = { styles.contextMenuItemName }>
  120. { _displayName }
  121. </Text>
  122. </View>
  123. </View>
  124. <Divider style = { styles.divider } />
  125. {
  126. _isLocalModerator && (
  127. <>
  128. {
  129. !_isParticipantAudioMuted
  130. && <TouchableOpacity
  131. onPress = { muteAudio }
  132. style = { styles.contextMenuItem }>
  133. <Icon
  134. size = { 20 }
  135. src = { IconMicrophoneEmptySlash } />
  136. <Text style = { styles.contextMenuItemText }>
  137. { t('participantsPane.actions.mute') }
  138. </Text>
  139. </TouchableOpacity>
  140. }
  141. <TouchableOpacity
  142. onPress = { muteEveryoneElse }
  143. style = { styles.contextMenuItem }>
  144. <Icon
  145. size = { 20 }
  146. src = { IconMuteEveryoneElse } />
  147. <Text style = { styles.contextMenuItemText }>
  148. { t('participantsPane.actions.muteEveryoneElse') }
  149. </Text>
  150. </TouchableOpacity>
  151. </>
  152. )
  153. }
  154. <Divider style = { styles.divider } />
  155. {
  156. _isLocalModerator && (
  157. <>
  158. {
  159. !_isParticipantVideoMuted
  160. && <TouchableOpacity
  161. onPress = { muteVideo }
  162. style = { styles.contextMenuItemSection }>
  163. <Icon
  164. size = { 20 }
  165. src = { IconVideoOff } />
  166. <Text style = { styles.contextMenuItemText }>
  167. { t('participantsPane.actions.stopVideo') }
  168. </Text>
  169. </TouchableOpacity>
  170. }
  171. <TouchableOpacity
  172. onPress = { kickRemoteParticipant }
  173. style = { styles.contextMenuItem }>
  174. <Icon
  175. size = { 20 }
  176. src = { IconCloseCircle } />
  177. <Text style = { styles.contextMenuItemText }>
  178. { t('videothumbnail.kick') }
  179. </Text>
  180. </TouchableOpacity>
  181. </>
  182. )
  183. }
  184. <TouchableOpacity
  185. onPress = { sendPrivateMessage }
  186. style = { styles.contextMenuItem }>
  187. <Icon
  188. size = { 20 }
  189. src = { IconMessage } />
  190. <Text style = { styles.contextMenuItemText }>
  191. { t('toolbar.accessibilityLabel.privateMessage') }
  192. </Text>
  193. </TouchableOpacity>
  194. {/* We need design specs for this*/}
  195. {/* <TouchableOpacity*/}
  196. {/* style = { styles.contextMenuItemSection }>*/}
  197. {/* <Icon*/}
  198. {/* size = { 20 }*/}
  199. {/* src = { IconConnectionActive }*/}
  200. {/* style = { styles.contextMenuItemIcon } />*/}
  201. {/* <Text style = { styles.contextMenuItemText }>{ t('participantsPane.actions.networkStats') }</Text>*/}
  202. {/* </TouchableOpacity>*/}
  203. <Divider style = { styles.divider } />
  204. <VolumeSlider participantID = { participantID } />
  205. </BottomSheet>
  206. );
  207. };
  208. /**
  209. * Maps (parts of) the redux state to the associated props for this component.
  210. *
  211. * @param {Object} state - The Redux state.
  212. * @param {Object} ownProps - The own props of the component.
  213. * @private
  214. * @returns {Props}
  215. */
  216. function _mapStateToProps(state, ownProps): Object {
  217. const { participantID } = ownProps;
  218. const participantIDS = [];
  219. const participant = getParticipantByIdOrUndefined(state, participantID);
  220. const _isLocalModerator = isLocalParticipantModerator(state);
  221. const _isParticipantVideoMuted = isParticipantVideoMuted(participant, state);
  222. const _isParticipantAudioMuted = isParticipantAudioMuted(participant, state);
  223. const localParticipant = getLocalParticipant(state);
  224. const remoteParticipants = getRemoteParticipants(state);
  225. localParticipant && participantIDS.push(localParticipant?.id);
  226. remoteParticipants.forEach(p => {
  227. participantIDS.push(p?.id);
  228. });
  229. const isParticipantIDAvailable = participantIDS.find(partID => partID === participantID);
  230. return {
  231. _displayName: getParticipantDisplayName(state, participantID),
  232. _isLocalModerator,
  233. _isParticipantAudioMuted,
  234. _isParticipantIDAvailable: Boolean(isParticipantIDAvailable),
  235. _isParticipantVideoMuted,
  236. _participant: participant
  237. };
  238. }
  239. export default connect(_mapStateToProps)(ContextMenuMeetingParticipantDetails);