浏览代码

feat(rn,av-moderation) updated advanced moderation on Native

Updated participants list to:
- show Moderator label
- show correct status icons (red for force muted)
- show participants in the right order

Updated moderation to:
- show moderation menu at all times
- make moderation options functional

Updated notifications:
- fixed raise hand to show name
- display moderator rights granted

Updated mute/ stop video for all dialogs to include moderation toggles

Added ask to unmute button

Fix comments on ask to unmute

Co-authored-by: robertpin <robert.pin9@gmail.com>
master
robertpin 4 年前
父节点
当前提交
c3dae1f6e9
没有帐户链接到提交者的电子邮件

+ 1
- 0
react/features/app/middlewares.any.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
 import '../analytics/middleware';
3
 import '../analytics/middleware';
4
+import '../av-moderation/middleware';
4
 import '../base/conference/middleware';
5
 import '../base/conference/middleware';
5
 import '../base/config/middleware';
6
 import '../base/config/middleware';
6
 import '../base/jwt/middleware';
7
 import '../base/jwt/middleware';

+ 0
- 1
react/features/app/middlewares.web.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
 import '../authentication/middleware';
3
 import '../authentication/middleware';
4
-import '../av-moderation/middleware';
5
 import '../base/devices/middleware';
4
 import '../base/devices/middleware';
6
 import '../e2ee/middleware';
5
 import '../e2ee/middleware';
7
 import '../external-api/middleware';
6
 import '../external-api/middleware';

+ 1
- 0
react/features/app/reducers.any.js 查看文件

2
 
2
 
3
 import '../analytics/reducer';
3
 import '../analytics/reducer';
4
 import '../authentication/reducer';
4
 import '../authentication/reducer';
5
+import '../av-moderation/reducer';
5
 import '../base/app/reducer';
6
 import '../base/app/reducer';
6
 import '../base/audio-only/reducer';
7
 import '../base/audio-only/reducer';
7
 import '../base/color-scheme/reducer';
8
 import '../base/color-scheme/reducer';

+ 0
- 1
react/features/app/reducers.web.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
-import '../av-moderation/reducer';
4
 import '../base/devices/reducer';
3
 import '../base/devices/reducer';
5
 import '../e2ee/reducer';
4
 import '../e2ee/reducer';
6
 import '../feedback/reducer';
5
 import '../feedback/reducer';

+ 1
- 0
react/features/base/participants/middleware.js 查看文件

546
             title: getParticipantDisplayName(state, participantId),
546
             title: getParticipantDisplayName(state, participantId),
547
             descriptionKey: 'notify.raisedHand',
547
             descriptionKey: 'notify.raisedHand',
548
             raiseHandNotification: true,
548
             raiseHandNotification: true,
549
+            concatText: true,
549
             ...action
550
             ...action
550
         }, NOTIFICATION_TIMEOUT * (shouldDisplayAllowAction ? 2 : 1)));
551
         }, NOTIFICATION_TIMEOUT * (shouldDisplayAllowAction ? 2 : 1)));
551
         dispatch(playSound(RAISE_HAND_SOUND_ID));
552
         dispatch(playSound(RAISE_HAND_SOUND_ID));

+ 3
- 1
react/features/filmstrip/components/native/Thumbnail.js 查看文件

217
         const indicators = [];
217
         const indicators = [];
218
 
218
 
219
         if (renderModeratorIndicator) {
219
         if (renderModeratorIndicator) {
220
-            indicators.push(<View style = { styles.moderatorIndicatorContainer }>
220
+            indicators.push(<View
221
+                key = 'moderator-indicator'
222
+                style = { styles.moderatorIndicatorContainer }>
221
                 <ModeratorIndicator />
223
                 <ModeratorIndicator />
222
             </View>);
224
             </View>);
223
         }
225
         }

+ 5
- 0
react/features/notifications/components/AbstractNotification.js 查看文件

12
      */
12
      */
13
     appearance: string,
13
     appearance: string,
14
 
14
 
15
+    /**
16
+     * Whether or not the title and description should be concatenated.
17
+     */
18
+    concatText?: boolean,
19
+
15
     /**
20
     /**
16
      * Callback invoked when the custom button is clicked.
21
      * Callback invoked when the custom button is clicked.
17
      */
22
      */

+ 7
- 2
react/features/notifications/components/native/Notification.js 查看文件

66
      * @private
66
      * @private
67
      */
67
      */
68
     _renderContent() {
68
     _renderContent() {
69
-        const { maxLines = DEFAULT_MAX_LINES, t, title, titleArguments, titleKey } = this.props;
69
+        const { maxLines = DEFAULT_MAX_LINES, t, title, titleArguments, titleKey, concatText } = this.props;
70
         const titleText = title || (titleKey && t(titleKey, titleArguments));
70
         const titleText = title || (titleKey && t(titleKey, titleArguments));
71
         const description = this._getDescription();
71
         const description = this._getDescription();
72
+        const titleConcat = [];
73
+
74
+        if (concatText) {
75
+            titleConcat.push(titleText);
76
+        }
72
 
77
 
73
         if (description && description.length) {
78
         if (description && description.length) {
74
-            return description.map((line, index) => (
79
+            return [ ...titleConcat, ...description ].map((line, index) => (
75
                 <Text
80
                 <Text
76
                     key = { index }
81
                     key = { index }
77
                     numberOfLines = { maxLines }
82
                     numberOfLines = { maxLines }

+ 72
- 13
react/features/participants-pane/components/native/ContextMenuMore.js 查看文件

2
 
2
 
3
 import React, { useCallback } from 'react';
3
 import React, { useCallback } from 'react';
4
 import { useTranslation } from 'react-i18next';
4
 import { useTranslation } from 'react-i18next';
5
-import { TouchableOpacity } from 'react-native';
6
-import { Text } from 'react-native-paper';
5
+import { TouchableOpacity, View } from 'react-native';
6
+import { Divider, Text } from 'react-native-paper';
7
 import { useDispatch, useSelector } from 'react-redux';
7
 import { useDispatch, useSelector } from 'react-redux';
8
 
8
 
9
+import {
10
+    requestDisableAudioModeration,
11
+    requestDisableVideoModeration,
12
+    requestEnableAudioModeration,
13
+    requestEnableVideoModeration
14
+} from '../../../av-moderation/actions';
15
+import {
16
+    isSupported as isAvModerationSupported,
17
+    isEnabled as isAvModerationEnabled
18
+} from '../../../av-moderation/functions';
9
 import { openDialog, hideDialog } from '../../../base/dialog/actions';
19
 import { openDialog, hideDialog } from '../../../base/dialog/actions';
10
 import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
20
 import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
11
 import {
21
 import {
12
     Icon,
22
     Icon,
23
+    IconCheck,
13
     IconVideoOff
24
     IconVideoOff
14
 } from '../../../base/icons';
25
 } from '../../../base/icons';
15
-import {
16
-    getLocalParticipant,
17
-    getParticipantCount
18
-} from '../../../base/participants';
26
+import { MEDIA_TYPE } from '../../../base/media';
27
+import { getParticipantCount, isEveryoneModerator } from '../../../base/participants';
19
 import MuteEveryonesVideoDialog
28
 import MuteEveryonesVideoDialog
20
     from '../../../video-menu/components/native/MuteEveryonesVideoDialog';
29
     from '../../../video-menu/components/native/MuteEveryonesVideoDialog';
21
 
30
 
24
 export const ContextMenuMore = () => {
33
 export const ContextMenuMore = () => {
25
     const dispatch = useDispatch();
34
     const dispatch = useDispatch();
26
     const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
35
     const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
27
-    const { id } = useSelector(getLocalParticipant);
28
-    const participantsCount = useSelector(getParticipantCount);
29
-    const showSlidingView = participantsCount > 2;
30
     const muteAllVideo = useCallback(() =>
36
     const muteAllVideo = useCallback(() =>
31
-        dispatch(openDialog(MuteEveryonesVideoDialog,
32
-            { exclude: [ id ] })),
37
+        dispatch(openDialog(MuteEveryonesVideoDialog)),
33
         [ dispatch ]);
38
         [ dispatch ]);
34
     const { t } = useTranslation();
39
     const { t } = useTranslation();
35
 
40
 
41
+    const isModerationSupported = useSelector(isAvModerationSupported());
42
+    const allModerators = useSelector(isEveryoneModerator);
43
+    const participantCount = useSelector(getParticipantCount);
44
+
45
+    const isAudioModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.AUDIO));
46
+    const isVideoModerationEnabled = useSelector(isAvModerationEnabled(MEDIA_TYPE.VIDEO));
47
+
48
+    const disableAudioModeration = useCallback(() => dispatch(requestDisableAudioModeration()), [ dispatch ]);
49
+    const disableVideoModeration = useCallback(() => dispatch(requestDisableVideoModeration()), [ dispatch ]);
50
+
51
+    const enableAudioModeration = useCallback(() => dispatch(requestEnableAudioModeration()), [ dispatch ]);
52
+    const enableVideoModeration = useCallback(() => dispatch(requestEnableVideoModeration()), [ dispatch ]);
53
+
36
     return (
54
     return (
37
         <BottomSheet
55
         <BottomSheet
38
             addScrollViewPadding = { false }
56
             addScrollViewPadding = { false }
39
             onCancel = { cancel }
57
             onCancel = { cancel }
40
-            showSlidingView = { showSlidingView }
41
-            style = { styles.contextMenuMore }>
58
+            showSlidingView = { true }>
42
             <TouchableOpacity
59
             <TouchableOpacity
43
                 onPress = { muteAllVideo }
60
                 onPress = { muteAllVideo }
44
                 style = { styles.contextMenuItem }>
61
                 style = { styles.contextMenuItem }>
47
                     src = { IconVideoOff } />
64
                     src = { IconVideoOff } />
48
                 <Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
65
                 <Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
49
             </TouchableOpacity>
66
             </TouchableOpacity>
67
+            {isModerationSupported && ((participantCount === 1 || !allModerators)) && <>
68
+                <Divider style = { styles.divider } />
69
+                <View style = { styles.contextMenuItem }>
70
+                    <Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.allow')}</Text>
71
+                </View>
72
+                {isAudioModerationEnabled
73
+                    ? <TouchableOpacity
74
+                        onPress = { disableAudioModeration }
75
+                        style = { styles.contextMenuItem }>
76
+                        <Text style = { styles.contextMenuItemTextNoIcon }>
77
+                            {t('participantsPane.actions.audioModeration')}
78
+                        </Text>
79
+                    </TouchableOpacity>
80
+                    : <TouchableOpacity
81
+                        onPress = { enableAudioModeration }
82
+                        style = { styles.contextMenuItem }>
83
+                        <Icon
84
+                            size = { 24 }
85
+                            src = { IconCheck } />
86
+                        <Text style = { styles.contextMenuItemText }>
87
+                            {t('participantsPane.actions.audioModeration')}
88
+                        </Text>
89
+                    </TouchableOpacity> }
90
+                {isVideoModerationEnabled
91
+                    ? <TouchableOpacity
92
+                        onPress = { disableVideoModeration }
93
+                        style = { styles.contextMenuItem }>
94
+                        <Text style = { styles.contextMenuItemTextNoIcon }>
95
+                            {t('participantsPane.actions.videoModeration')}
96
+                        </Text>
97
+                    </TouchableOpacity>
98
+                    : <TouchableOpacity
99
+                        onPress = { enableVideoModeration }
100
+                        style = { styles.contextMenuItem }>
101
+                        <Icon
102
+                            size = { 24 }
103
+                            src = { IconCheck } />
104
+                        <Text style = { styles.contextMenuItemText }>
105
+                            {t('participantsPane.actions.videoModeration')}
106
+                        </Text>
107
+                    </TouchableOpacity>}
108
+            </>}
50
         </BottomSheet>
109
         </BottomSheet>
51
     );
110
     );
52
 };
111
 };

+ 10
- 0
react/features/participants-pane/components/native/HorizontalDotsIcon.js 查看文件

1
+// @flow
2
+import React from 'react';
3
+
4
+import { Icon, IconHorizontalPoints } from '../../../base/icons';
5
+
6
+const HorizontalDotsIcon = () => (<Icon
7
+    size = { 20 }
8
+    src = { IconHorizontalPoints } />);
9
+
10
+export default HorizontalDotsIcon;

+ 20
- 11
react/features/participants-pane/components/native/MeetingParticipantItem.js 查看文件

6
 import {
6
 import {
7
     getLocalParticipant,
7
     getLocalParticipant,
8
     getParticipantByIdOrUndefined,
8
     getParticipantByIdOrUndefined,
9
-    getParticipantDisplayName
9
+    getParticipantDisplayName,
10
+    isParticipantModerator
10
 } from '../../../base/participants';
11
 } from '../../../base/participants';
11
 import { connect } from '../../../base/redux';
12
 import { connect } from '../../../base/redux';
12
 import {
13
 import {
14
     isParticipantVideoMuted
15
     isParticipantVideoMuted
15
 } from '../../../base/tracks';
16
 } from '../../../base/tracks';
16
 import { showConnectionStatus, showContextMenuDetails, showSharedVideoMenu } from '../../actions.native';
17
 import { showConnectionStatus, showContextMenuDetails, showSharedVideoMenu } from '../../actions.native';
17
-import { MEDIA_STATE } from '../../constants';
18
 import type { MediaState } from '../../constants';
18
 import type { MediaState } from '../../constants';
19
-import { getParticipantAudioMediaState } from '../../functions';
19
+import { getParticipantAudioMediaState, getParticipantVideoMediaState } from '../../functions';
20
 
20
 
21
 import ParticipantItem from './ParticipantItem';
21
 import ParticipantItem from './ParticipantItem';
22
 
22
 
39
     _isFakeParticipant: boolean,
39
     _isFakeParticipant: boolean,
40
 
40
 
41
     /**
41
     /**
42
-     * True if the participant is video muted.
42
+     * Whether or not the user is a moderator.
43
      */
43
      */
44
-    _isVideoMuted: boolean,
44
+    _isModerator: boolean,
45
 
45
 
46
     /**
46
     /**
47
      * True if the participant is the local participant.
47
      * True if the participant is the local participant.
63
      */
63
      */
64
     _raisedHand: boolean,
64
     _raisedHand: boolean,
65
 
65
 
66
+    /**
67
+     * Media state for video.
68
+     */
69
+    _videoMediaState: MediaState,
70
+
66
     /**
71
     /**
67
      * The redux dispatch function.
72
      * The redux dispatch function.
68
      */
73
      */
127
         const {
132
         const {
128
             _audioMediaState,
133
             _audioMediaState,
129
             _displayName,
134
             _displayName,
130
-            _isVideoMuted,
135
+            _isModerator,
131
             _local,
136
             _local,
132
             _participantID,
137
             _participantID,
133
-            _raisedHand
138
+            _raisedHand,
139
+            _videoMediaState
134
         } = this.props;
140
         } = this.props;
135
 
141
 
136
         return (
142
         return (
138
                 audioMediaState = { _audioMediaState }
144
                 audioMediaState = { _audioMediaState }
139
                 displayName = { _displayName }
145
                 displayName = { _displayName }
140
                 isKnockingParticipant = { false }
146
                 isKnockingParticipant = { false }
147
+                isModerator = { _isModerator }
141
                 local = { _local }
148
                 local = { _local }
142
                 onPress = { this._onPress }
149
                 onPress = { this._onPress }
143
                 participantID = { _participantID }
150
                 participantID = { _participantID }
144
                 raisedHand = { _raisedHand }
151
                 raisedHand = { _raisedHand }
145
-                videoMediaState = { _isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED } />
152
+                videoMediaState = { _videoMediaState } />
146
         );
153
         );
147
     }
154
     }
148
 }
155
 }
161
     const localParticipantId = getLocalParticipant(state).id;
168
     const localParticipantId = getLocalParticipant(state).id;
162
     const participant = getParticipantByIdOrUndefined(state, participantID);
169
     const participant = getParticipantByIdOrUndefined(state, participantID);
163
     const _isAudioMuted = isParticipantAudioMuted(participant, state);
170
     const _isAudioMuted = isParticipantAudioMuted(participant, state);
164
-    const isVideoMuted = isParticipantVideoMuted(participant, state);
171
+    const _isVideoMuted = isParticipantVideoMuted(participant, state);
165
     const audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state);
172
     const audioMediaState = getParticipantAudioMediaState(participant, _isAudioMuted, state);
173
+    const videoMediaState = getParticipantVideoMediaState(participant, _isVideoMuted, state);
166
 
174
 
167
     return {
175
     return {
168
         _audioMediaState: audioMediaState,
176
         _audioMediaState: audioMediaState,
169
         _displayName: getParticipantDisplayName(state, participant?.id),
177
         _displayName: getParticipantDisplayName(state, participant?.id),
170
         _isAudioMuted,
178
         _isAudioMuted,
171
         _isFakeParticipant: Boolean(participant?.isFakeParticipant),
179
         _isFakeParticipant: Boolean(participant?.isFakeParticipant),
172
-        _isVideoMuted: isVideoMuted,
180
+        _isModerator: isParticipantModerator(participant),
173
         _local: Boolean(participant?.local),
181
         _local: Boolean(participant?.local),
174
         _localVideoOwner: Boolean(ownerId === localParticipantId),
182
         _localVideoOwner: Boolean(ownerId === localParticipantId),
175
         _participantID: participant?.id,
183
         _participantID: participant?.id,
176
-        _raisedHand: Boolean(participant?.raisedHand)
184
+        _raisedHand: Boolean(participant?.raisedHand),
185
+        _videoMediaState: videoMediaState
177
     };
186
     };
178
 }
187
 }
179
 
188
 

+ 14
- 5
react/features/participants-pane/components/native/ParticipantItem.js 查看文件

34
      */
34
      */
35
     isKnockingParticipant: boolean,
35
     isKnockingParticipant: boolean,
36
 
36
 
37
+    /**
38
+     * Whether or not the user is a moderator.
39
+     */
40
+    isModerator?: boolean,
41
+
37
     /**
42
     /**
38
      * True if the participant is local.
43
      * True if the participant is local.
39
      */
44
      */
69
     children,
74
     children,
70
     displayName,
75
     displayName,
71
     isKnockingParticipant,
76
     isKnockingParticipant,
77
+    isModerator,
72
     local,
78
     local,
73
     onPress,
79
     onPress,
74
     participantID,
80
     participantID,
88
                     className = 'participant-avatar'
94
                     className = 'participant-avatar'
89
                     participantId = { participantID }
95
                     participantId = { participantID }
90
                     size = { 32 } />
96
                     size = { 32 } />
91
-                <View style = { styles.participantNameContainer }>
92
-                    <Text style = { styles.participantName }>
93
-                        { displayName }
94
-                    </Text>
95
-                    { local ? <Text style = { styles.isLocal }>({t('chat.you')})</Text> : null }
97
+                <View style = { styles.participantDetailsContainer }>
98
+                    <View style = { styles.participantNameContainer }>
99
+                        <Text style = { styles.participantName }>
100
+                            { displayName }
101
+                        </Text>
102
+                        { local ? <Text style = { styles.isLocal }>({t('chat.you')})</Text> : null }
103
+                    </View>
104
+                    {isModerator && <Text style = { styles.moderatorLabel }>{t('videothumbnail.moderator')}</Text>}
96
                 </View>
105
                 </View>
97
                 {
106
                 {
98
                     !isKnockingParticipant
107
                     !isKnockingParticipant

+ 8
- 19
react/features/participants-pane/components/native/ParticipantsPane.js 查看文件

7
 import { useDispatch, useSelector } from 'react-redux';
7
 import { useDispatch, useSelector } from 'react-redux';
8
 
8
 
9
 import { openDialog } from '../../../base/dialog';
9
 import { openDialog } from '../../../base/dialog';
10
-import { Icon, IconHorizontalPoints } from '../../../base/icons';
11
 import { JitsiModal } from '../../../base/modal';
10
 import { JitsiModal } from '../../../base/modal';
12
 import {
11
 import {
13
-    getParticipantCount,
14
     isLocalParticipantModerator
12
     isLocalParticipantModerator
15
 } from '../../../base/participants';
13
 } from '../../../base/participants';
16
 import MuteEveryoneDialog
14
 import MuteEveryoneDialog
18
 import { close } from '../../actions.native';
16
 import { close } from '../../actions.native';
19
 
17
 
20
 import { ContextMenuMore } from './ContextMenuMore';
18
 import { ContextMenuMore } from './ContextMenuMore';
19
+import HorizontalDotsIcon from './HorizontalDotsIcon';
21
 import LobbyParticipantList from './LobbyParticipantList';
20
 import LobbyParticipantList from './LobbyParticipantList';
22
 import MeetingParticipantList from './MeetingParticipantList';
21
 import MeetingParticipantList from './MeetingParticipantList';
23
 import styles from './styles';
22
 import styles from './styles';
32
     const openMoreMenu = useCallback(() => dispatch(openDialog(ContextMenuMore)), [ dispatch ]);
31
     const openMoreMenu = useCallback(() => dispatch(openDialog(ContextMenuMore)), [ dispatch ]);
33
     const closePane = useCallback(() => dispatch(close()), [ dispatch ]);
32
     const closePane = useCallback(() => dispatch(close()), [ dispatch ]);
34
     const isLocalModerator = useSelector(isLocalParticipantModerator);
33
     const isLocalModerator = useSelector(isLocalParticipantModerator);
35
-    const participantsCount = useSelector(getParticipantCount);
36
-    const showContextMenu = participantsCount > 2;
37
     const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
34
     const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
38
         [ dispatch ]);
35
         [ dispatch ]);
39
     const { t } = useTranslation();
36
     const { t } = useTranslation();
55
                         labelStyle = { styles.muteAllLabel }
52
                         labelStyle = { styles.muteAllLabel }
56
                         mode = 'contained'
53
                         mode = 'contained'
57
                         onPress = { muteAll }
54
                         onPress = { muteAll }
58
-                        style = { showContextMenu ? styles.muteAllMoreButton : styles.muteAllButton } />
59
-                    {
60
-                        showContextMenu
61
-                        && <Button
62
-                            /* eslint-disable-next-line react/jsx-no-bind */
63
-                            icon = { () =>
64
-                                (<Icon
65
-                                    size = { 20 }
66
-                                    src = { IconHorizontalPoints } />)
67
-                            }
68
-                            labelStyle = { styles.moreIcon }
69
-                            mode = 'contained'
70
-                            onPress = { openMoreMenu }
71
-                            style = { styles.moreButton } />
72
-                    }
55
+                        style = { styles.muteAllMoreButton } />
56
+                    <Button
57
+                        icon = { HorizontalDotsIcon }
58
+                        labelStyle = { styles.moreIcon }
59
+                        mode = 'contained'
60
+                        onPress = { openMoreMenu }
61
+                        style = { styles.moreButton } />
73
                 </View>
62
                 </View>
74
             }
63
             }
75
         </JitsiModal>
64
         </JitsiModal>

+ 20
- 8
react/features/participants-pane/components/native/styles.js 查看文件

78
     display: 'flex',
78
     display: 'flex',
79
     flexDirection: 'row',
79
     flexDirection: 'row',
80
     height: BaseTheme.spacing[7],
80
     height: BaseTheme.spacing[7],
81
-    marginLeft: BaseTheme.spacing[3],
82
-    marginTop: BaseTheme.spacing[2]
81
+    marginLeft: BaseTheme.spacing[3]
83
 };
82
 };
84
 
83
 
85
 /**
84
 /**
137
         width: '100%'
136
         width: '100%'
138
     },
137
     },
139
 
138
 
139
+    participantDetailsContainer: {
140
+        display: 'flex',
141
+        flexDirection: 'column',
142
+        width: '100%'
143
+    },
144
+
140
     participantNameContainer: {
145
     participantNameContainer: {
141
         display: 'flex',
146
         display: 'flex',
142
         flexDirection: 'row',
147
         flexDirection: 'row',
143
         overflow: 'hidden',
148
         overflow: 'hidden',
144
         paddingLeft: BaseTheme.spacing[3],
149
         paddingLeft: BaseTheme.spacing[3],
145
-        width: '63%'
150
+        width: '100%'
146
     },
151
     },
147
 
152
 
148
     participantName: {
153
     participantName: {
150
         color: BaseTheme.palette.text01
155
         color: BaseTheme.palette.text01
151
     },
156
     },
152
 
157
 
158
+    moderatorLabel: {
159
+        color: BaseTheme.palette.text03,
160
+        alignSelf: 'flex-start',
161
+        paddingLeft: BaseTheme.spacing[3],
162
+        paddingTop: BaseTheme.spacing[1]
163
+    },
164
+
153
     isLocal: {
165
     isLocal: {
154
         alignSelf: 'center',
166
         alignSelf: 'center',
155
         color: BaseTheme.palette.text01,
167
         color: BaseTheme.palette.text01,
249
         marginLeft: 'auto'
261
         marginLeft: 'auto'
250
     },
262
     },
251
 
263
 
252
-    contextMenuMore: {
253
-        backgroundColor: BaseTheme.palette.bottomSheet,
254
-        borderRadius: BaseTheme.shape.borderRadius
255
-    },
256
-
257
     muteAllButton: {
264
     muteAllButton: {
258
         ...muteAllButton
265
         ...muteAllButton
259
     },
266
     },
300
         marginLeft: BaseTheme.spacing[3]
307
         marginLeft: BaseTheme.spacing[3]
301
     },
308
     },
302
 
309
 
310
+    contextMenuItemTextNoIcon: {
311
+        ...contextMenuItemText,
312
+        marginLeft: BaseTheme.spacing[6]
313
+    },
314
+
303
     contextMenuItemName: {
315
     contextMenuItemName: {
304
         color: BaseTheme.palette.text04,
316
         color: BaseTheme.palette.text04,
305
         flexShrink: 1,
317
         flexShrink: 1,

+ 0
- 2
react/features/participants-pane/functions.js 查看文件

71
  * @param {Object} participant - The participant.
71
  * @param {Object} participant - The participant.
72
  * @param {boolean} muted - The mute state of the participant.
72
  * @param {boolean} muted - The mute state of the participant.
73
  * @param {Object} state - The redux state.
73
  * @param {Object} state - The redux state.
74
- * @param {boolean} ignoreDominantSpeaker - Whether to ignore the dominant speaker state.
75
  * @returns {MediaState}
74
  * @returns {MediaState}
76
  */
75
  */
77
 export function getParticipantAudioMediaState(participant: Object, muted: Boolean, state: Object) {
76
 export function getParticipantAudioMediaState(participant: Object, muted: Boolean, state: Object) {
98
  * @param {Object} participant - The participant.
97
  * @param {Object} participant - The participant.
99
  * @param {boolean} muted - The mute state of the participant.
98
  * @param {boolean} muted - The mute state of the participant.
100
  * @param {Object} state - The redux state.
99
  * @param {Object} state - The redux state.
101
- * @param {boolean} ignoreDominantSpeaker - Whether to ignore the dominant speaker state.
102
  * @returns {MediaState}
100
  * @returns {MediaState}
103
  */
101
  */
104
 export function getParticipantVideoMediaState(participant: Object, muted: Boolean, state: Object) {
102
 export function getParticipantVideoMediaState(participant: Object, muted: Boolean, state: Object) {

+ 65
- 0
react/features/video-menu/components/native/AskUnmuteButton.js 查看文件

1
+// @flow
2
+
3
+import { approveParticipant } from '../../../av-moderation/actions';
4
+import { translate } from '../../../base/i18n';
5
+import { IconMicrophone } from '../../../base/icons';
6
+import { MEDIA_TYPE } from '../../../base/media';
7
+import { getParticipantById, isLocalParticipantModerator } from '../../../base/participants';
8
+import { connect } from '../../../base/redux';
9
+import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
10
+import { isForceMuted } from '../../../participants-pane/functions';
11
+
12
+export type Props = AbstractButtonProps & {
13
+
14
+    /**
15
+     * The redux {@code dispatch} function.
16
+     */
17
+    dispatch: Function,
18
+
19
+    /**
20
+     * The ID of the participant object that this button is supposed to
21
+     * ask to unmute.
22
+     */
23
+    participantID: string,
24
+};
25
+
26
+/**
27
+ * An abstract remote video menu button which asks the remote participant to unmute.
28
+ */
29
+class AskUnmuteButton extends AbstractButton<Props, *> {
30
+    accessibilityLabel = 'participantsPane.actions.askUnmute';
31
+    icon = IconMicrophone;
32
+    label = 'participantsPane.actions.askUnmute';
33
+
34
+    /**
35
+     * Handles clicking / pressing the button, and asks the participant to unmute.
36
+     *
37
+     * @private
38
+     * @returns {void}
39
+     */
40
+    _handleClick() {
41
+        const { dispatch, participantID } = this.props;
42
+
43
+        dispatch(approveParticipant(participantID));
44
+    }
45
+}
46
+
47
+/**
48
+ * Maps part of the Redux state to the props of this component.
49
+ *
50
+ * @param {Object} state - The Redux state.
51
+ * @param {Object} ownProps - Properties of component.
52
+ * @returns {Props}
53
+ */
54
+function mapStateToProps(state, ownProps) {
55
+    const { participantID } = ownProps;
56
+    const participant = getParticipantById(state, participantID);
57
+
58
+    return {
59
+        visible: isLocalParticipantModerator(state)
60
+            && (isForceMuted(participant, MEDIA_TYPE.AUDIO, state)
61
+            || isForceMuted(participant, MEDIA_TYPE.VIDEO, state))
62
+    };
63
+}
64
+
65
+export default translate(connect(mapStateToProps)(AskUnmuteButton));

+ 8
- 3
react/features/video-menu/components/native/GrantModeratorDialog.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
+import { Text } from 'react-native';
4
 
5
 
5
 import { ConfirmDialog } from '../../../base/dialog';
6
 import { ConfirmDialog } from '../../../base/dialog';
6
 import { translate } from '../../../base/i18n';
7
 import { translate } from '../../../base/i18n';
7
 import { connect } from '../../../base/redux';
8
 import { connect } from '../../../base/redux';
8
-import AbstractGrantModeratorDialog
9
+import AbstractGrantModeratorDialog, { abstractMapStateToProps }
9
     from '../AbstractGrantModeratorDialog';
10
     from '../AbstractGrantModeratorDialog';
10
 
11
 
11
 /**
12
 /**
22
         return (
23
         return (
23
             <ConfirmDialog
24
             <ConfirmDialog
24
                 contentKey = 'dialog.grantModeratorDialog'
25
                 contentKey = 'dialog.grantModeratorDialog'
25
-                onSubmit = { this._onSubmit } />
26
+                onSubmit = { this._onSubmit }>
27
+                <Text>
28
+                    {`${this.props.t('dialog.grantModeratorDialog', { participantName: this.props.participantName })}`}
29
+                </Text>
30
+            </ConfirmDialog>
26
         );
31
         );
27
     }
32
     }
28
 
33
 
29
     _onSubmit: () => boolean;
34
     _onSubmit: () => boolean;
30
 }
35
 }
31
 
36
 
32
-export default translate(connect()(GrantModeratorDialog));
37
+export default translate(connect(abstractMapStateToProps)(GrantModeratorDialog));

+ 36
- 2
react/features/video-menu/components/native/MuteEveryoneDialog.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
-import { Text } from 'react-native';
4
+import { Text, View, Switch } from 'react-native';
5
+import { Divider } from 'react-native-paper';
5
 
6
 
6
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
 import { ConfirmDialog } from '../../../base/dialog';
8
 import { ConfirmDialog } from '../../../base/dialog';
12
     abstractMapStateToProps,
13
     abstractMapStateToProps,
13
     type Props as AbstractProps } from '../AbstractMuteEveryoneDialog';
14
     type Props as AbstractProps } from '../AbstractMuteEveryoneDialog';
14
 
15
 
16
+import styles from './styles';
17
+
15
 type Props = AbstractProps & {
18
 type Props = AbstractProps & {
16
 
19
 
17
     /**
20
     /**
28
  */
31
  */
29
 class MuteEveryoneDialog extends AbstractMuteEveryoneDialog<Props> {
32
 class MuteEveryoneDialog extends AbstractMuteEveryoneDialog<Props> {
30
 
33
 
34
+    /**
35
+     * Toggles advanced moderation switch.
36
+     *
37
+     * @returns {void}
38
+     */
39
+    _onToggleModeration() {
40
+        this.setState(state => {
41
+            return {
42
+                audioModerationEnabled: !state.audioModerationEnabled,
43
+                content: this.props.t(state.audioModerationEnabled
44
+                    ? 'dialog.muteEveryoneDialog' : 'dialog.muteEveryoneDialogModerationOn'
45
+                )
46
+            };
47
+        });
48
+    }
49
+
31
     /**
50
     /**
32
      * Implements {@code Component#render}.
51
      * Implements {@code Component#render}.
33
      *
52
      *
39
                 okKey = 'dialog.muteParticipantButton'
58
                 okKey = 'dialog.muteParticipantButton'
40
                 onSubmit = { this._onSubmit } >
59
                 onSubmit = { this._onSubmit } >
41
                 <Text style = { this.props._dialogStyles.text }>
60
                 <Text style = { this.props._dialogStyles.text }>
42
-                    { `${this.props.title} \n\n ${this.props.content}` }
61
+                    { `${this.props.title} \n\n ${this.state.content}` }
43
                 </Text>
62
                 </Text>
63
+                {this.props.exclude.length === 0 && <>
64
+                    <Divider style = { styles.dividerWithSpacing } />
65
+                    <View style = { styles.toggleContainer }>
66
+                        <Text
67
+                            style = {{
68
+                                ...this.props._dialogStyles.text,
69
+                                ...styles.toggleLabel
70
+                            }}>
71
+                            {this.props.t('dialog.moderationAudioLabel')}
72
+                        </Text>
73
+                        <Switch
74
+                            onValueChange = { this._onToggleModeration }
75
+                            value = { !this.state.audioModerationEnabled } />
76
+                    </View>
77
+                </>}
44
             </ConfirmDialog>
78
             </ConfirmDialog>
45
         );
79
         );
46
     }
80
     }

+ 35
- 3
react/features/video-menu/components/native/MuteEveryonesVideoDialog.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
-import { Text } from 'react-native';
4
+import { Switch, Text, View } from 'react-native';
5
+import { Divider } from 'react-native-paper';
5
 
6
 
6
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
 import { ConfirmDialog } from '../../../base/dialog';
8
 import { ConfirmDialog } from '../../../base/dialog';
12
     abstractMapStateToProps,
13
     abstractMapStateToProps,
13
     type Props as AbstractProps } from '../AbstractMuteEveryonesVideoDialog';
14
     type Props as AbstractProps } from '../AbstractMuteEveryonesVideoDialog';
14
 
15
 
16
+import styles from './styles';
17
+
15
 type Props = AbstractProps & {
18
 type Props = AbstractProps & {
16
 
19
 
17
     /**
20
     /**
24
  * A React Component with the contents for a dialog that asks for confirmation
27
  * A React Component with the contents for a dialog that asks for confirmation
25
  * from the user before muting all remote participants.
28
  * from the user before muting all remote participants.
26
  *
29
  *
27
- * @extends AbstractMuteEveryoneDialog
30
+ * @extends AbstractMuteEveryonesVideoDialog
28
  */
31
  */
29
 class MuteEveryonesVideoDialog extends AbstractMuteEveryonesVideoDialog<Props> {
32
 class MuteEveryonesVideoDialog extends AbstractMuteEveryonesVideoDialog<Props> {
30
 
33
 
34
+    /**
35
+     * Toggles advanced moderation switch.
36
+     *
37
+     * @returns {void}
38
+     */
39
+    _onToggleModeration() {
40
+        this.setState(state => {
41
+            return {
42
+                moderationEnabled: !state.moderationEnabled,
43
+                content: this.props.t(state.moderationEnabled
44
+                    ? 'dialog.muteEveryonesVideoDialog' : 'dialog.muteEveryonesVideoDialogModerationOn'
45
+                )
46
+            };
47
+        });
48
+    }
49
+
31
     /**
50
     /**
32
      * Implements {@code Component#render}.
51
      * Implements {@code Component#render}.
33
      *
52
      *
39
                 okKey = 'dialog.muteEveryonesVideoDialogOk'
58
                 okKey = 'dialog.muteEveryonesVideoDialogOk'
40
                 onSubmit = { this._onSubmit } >
59
                 onSubmit = { this._onSubmit } >
41
                 <Text style = { this.props._dialogStyles.text }>
60
                 <Text style = { this.props._dialogStyles.text }>
42
-                    { `${this.props.title} \n\n ${this.props.content}` }
61
+                    { `${this.props.title} \n\n ${this.state.content}` }
43
                 </Text>
62
                 </Text>
63
+                {this.props.exclude.length === 0 && <>
64
+                    <Divider style = { styles.dividerWithSpacing } />
65
+                    <View style = { styles.toggleContainer }>
66
+                        <Text
67
+                            style = {{ ...this.props._dialogStyles.text,
68
+                                ...styles.toggleLabel }}>
69
+                            {this.props.t('dialog.moderationVideoLabel')}
70
+                        </Text>
71
+                        <Switch
72
+                            onValueChange = { this._onToggleModeration }
73
+                            value = { !this.state.moderationEnabled } />
74
+                    </View>
75
+                </>}
44
             </ConfirmDialog>
76
             </ConfirmDialog>
45
         );
77
         );
46
     }
78
     }

+ 2
- 0
react/features/video-menu/components/native/RemoteVideoMenu.js 查看文件

18
 import { hideRemoteVideoMenu } from '../../actions.native';
18
 import { hideRemoteVideoMenu } from '../../actions.native';
19
 import ConnectionStatusButton from '../native/ConnectionStatusButton';
19
 import ConnectionStatusButton from '../native/ConnectionStatusButton';
20
 
20
 
21
+import AskUnmuteButton from './AskUnmuteButton';
21
 import GrantModeratorButton from './GrantModeratorButton';
22
 import GrantModeratorButton from './GrantModeratorButton';
22
 import KickButton from './KickButton';
23
 import KickButton from './KickButton';
23
 import MuteButton from './MuteButton';
24
 import MuteButton from './MuteButton';
126
                 onCancel = { this._onCancel }
127
                 onCancel = { this._onCancel }
127
                 renderHeader = { this._renderMenuHeader }
128
                 renderHeader = { this._renderMenuHeader }
128
                 showSlidingView = { _isParticipantAvailable }>
129
                 showSlidingView = { _isParticipantAvailable }>
130
+                <AskUnmuteButton { ...buttonProps } />
129
                 { !_disableRemoteMute && <MuteButton { ...buttonProps } /> }
131
                 { !_disableRemoteMute && <MuteButton { ...buttonProps } /> }
130
                 <MuteEveryoneElseButton { ...buttonProps } />
132
                 <MuteEveryoneElseButton { ...buttonProps } />
131
                 { !_disableRemoteMute && <MuteVideoButton { ...buttonProps } /> }
133
                 { !_disableRemoteMute && <MuteVideoButton { ...buttonProps } /> }

+ 19
- 0
react/features/video-menu/components/native/styles.js 查看文件

67
 
67
 
68
     divider: {
68
     divider: {
69
         backgroundColor: BaseTheme.palette.dividerColor
69
         backgroundColor: BaseTheme.palette.dividerColor
70
+    },
71
+
72
+    dividerWithSpacing: {
73
+        backgroundColor: BaseTheme.palette.dividerColor,
74
+        marginVertical: BaseTheme.spacing[3]
75
+    },
76
+
77
+    toggleContainer: {
78
+        display: 'flex',
79
+        alignItems: 'center',
80
+        flexDirection: 'row',
81
+        justifyContent: 'center',
82
+        width: '100%',
83
+        overflow: 'hidden'
84
+    },
85
+
86
+    toggleLabel: {
87
+        marginRight: BaseTheme.spacing[3],
88
+        maxWidth: '70%'
70
     }
89
     }
71
 });
90
 });

正在加载...
取消
保存