Browse Source

feat(native-participants-pane) context menu for more btn and reject lobby participant

master
Calin Chitu 4 years ago
parent
commit
ba64d3e0c8

+ 40
- 6
react/features/base/dialog/components/native/BottomSheet.js View File

@@ -25,11 +25,21 @@ const GESTURE_SPEED_THRESHOLD = 0.2;
25 25
  */
26 26
 type Props = {
27 27
 
28
+    /**
29
+     * The height of the screen.
30
+     */
31
+    _height: number,
32
+
28 33
     /**
29 34
      * The color-schemed stylesheet of the feature.
30 35
      */
31 36
     _styles: StyleType,
32 37
 
38
+    /**
39
+     * Whether to add padding to scroll view.
40
+     */
41
+    addScrollViewPadding?: boolean,
42
+
33 43
     /**
34 44
      * The children to be displayed within this component.
35 45
      */
@@ -57,9 +67,14 @@ type Props = {
57 67
     renderFooter: ?Function,
58 68
 
59 69
     /**
60
-    * The height of the screen.
61
-    */
62
-    _height: number
70
+     * Whether to show sliding view or not.
71
+     */
72
+    showSlidingView?: boolean,
73
+
74
+    /**
75
+     * The component's external style
76
+     */
77
+    style: Object
63 78
 };
64 79
 
65 80
 /**
@@ -68,6 +83,16 @@ type Props = {
68 83
 class BottomSheet extends PureComponent<Props> {
69 84
     panResponder: Object;
70 85
 
86
+    /**
87
+     * Default values for {@code BottomSheet} component's properties.
88
+     *
89
+     * @static
90
+     */
91
+    static defaultProps = {
92
+        addScrollViewPadding: true,
93
+        showSlidingView: true
94
+    };
95
+
71 96
     /**
72 97
      * Instantiates a new component.
73 98
      *
@@ -90,7 +115,15 @@ class BottomSheet extends PureComponent<Props> {
90 115
      * @returns {ReactElement}
91 116
      */
92 117
     render() {
93
-        const { _styles, renderHeader, renderFooter, _height } = this.props;
118
+        const {
119
+            _height,
120
+            _styles,
121
+            addScrollViewPadding,
122
+            renderHeader,
123
+            renderFooter,
124
+            showSlidingView,
125
+            style
126
+        } = this.props;
94 127
 
95 128
         return (
96 129
             <SlidingView
@@ -98,7 +131,7 @@ class BottomSheet extends PureComponent<Props> {
98 131
                 accessibilityViewIsModal = { true }
99 132
                 onHide = { this.props.onCancel }
100 133
                 position = 'bottom'
101
-                show = { true }>
134
+                show = { showSlidingView }>
102 135
                 <View
103 136
                     pointerEvents = 'box-none'
104 137
                     style = { styles.sheetContainer }>
@@ -110,6 +143,7 @@ class BottomSheet extends PureComponent<Props> {
110 143
                         style = { [
111 144
                             styles.sheetItemContainer,
112 145
                             _styles.sheet,
146
+                            style,
113 147
                             {
114 148
                                 maxHeight: _height - 100
115 149
                             }
@@ -118,7 +152,7 @@ class BottomSheet extends PureComponent<Props> {
118 152
                         <ScrollView
119 153
                             bounces = { false }
120 154
                             showsVerticalScrollIndicator = { false }
121
-                            style = { styles.scrollView } >
155
+                            style = { addScrollViewPadding && styles.scrollView } >
122 156
                             { this.props.children }
123 157
                         </ScrollView>
124 158
                         { renderFooter && renderFooter() }

+ 13
- 0
react/features/participants-pane/actions.native.js View File

@@ -0,0 +1,13 @@
1
+import { openDialog } from '../base/dialog';
2
+
3
+import { ContextMenuReject } from './components/native/ContextMenuReject';
4
+
5
+/**
6
+ * Displays the context menu for the selected lobby participant.
7
+ *
8
+ * @param {string} participant - The selected participant's id.
9
+ * @returns {Function}
10
+ */
11
+export function showContextMenuReject(participant) {
12
+    return openDialog(ContextMenuReject, { participant });
13
+}

+ 60
- 0
react/features/participants-pane/components/native/ContextMenuMore.js View File

@@ -0,0 +1,60 @@
1
+// @flow
2
+
3
+import React, { useCallback } from 'react';
4
+import { useTranslation } from 'react-i18next';
5
+import { TouchableOpacity } from 'react-native';
6
+import { Text } from 'react-native-paper';
7
+import { useDispatch } from 'react-redux';
8
+
9
+import { hideDialog } from '../../../base/dialog';
10
+import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
11
+import {
12
+    Icon, IconMicDisabledHollow,
13
+    IconVideoOff
14
+} from '../../../base/icons';
15
+import { MEDIA_TYPE } from '../../../base/media';
16
+import { muteAllParticipants } from '../../../video-menu/actions.any';
17
+
18
+import styles from './styles';
19
+type Props = {
20
+
21
+    /**
22
+     * Array of participant IDs to not mute
23
+     */
24
+    exclude: Array<string>,
25
+
26
+    /**
27
+     * Participant reference
28
+     */
29
+    participant: Object
30
+};
31
+
32
+export const ContextMenuMore = ({ exclude }: Props) => {
33
+    const dispatch = useDispatch();
34
+    const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
35
+    const muteEveryoneVideo = useCallback(() => dispatch(muteAllParticipants(exclude, MEDIA_TYPE.VIDEO)), [ dispatch ]);
36
+    const { t } = useTranslation();
37
+
38
+    return (
39
+        <BottomSheet
40
+            onCancel = { cancel }
41
+            style = { styles.contextMenuMore }>
42
+            <TouchableOpacity
43
+                onPress = { muteEveryoneVideo }
44
+                style = { styles.contextMenuItemMuteVideo }>
45
+                <Icon
46
+                    size = { 24 }
47
+                    src = { IconVideoOff } />
48
+                <Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
49
+            </TouchableOpacity>
50
+            <TouchableOpacity
51
+                style = { styles.contextMenuItemDontAllowUnmute }>
52
+                <Icon
53
+                    size = { 24 }
54
+                    src = { IconMicDisabledHollow }
55
+                    style = { styles.contextMenuIcon } />
56
+                <Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.dontAllowUnmute')}</Text>
57
+            </TouchableOpacity>
58
+        </BottomSheet>
59
+    );
60
+};

+ 59
- 0
react/features/participants-pane/components/native/ContextMenuReject.js View File

@@ -0,0 +1,59 @@
1
+// @flow
2
+
3
+import React, { useCallback } from 'react';
4
+import { useTranslation } from 'react-i18next';
5
+import { TouchableOpacity, View } from 'react-native';
6
+import { Text } from 'react-native-paper';
7
+import { useDispatch } from 'react-redux';
8
+
9
+import { Avatar } from '../../../base/avatar';
10
+import { hideDialog } from '../../../base/dialog';
11
+import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
12
+import {
13
+    Icon, IconClose
14
+} from '../../../base/icons';
15
+import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
16
+
17
+import styles from './styles';
18
+type Props = {
19
+
20
+    /**
21
+     * Participant reference
22
+     */
23
+    participant: Object
24
+};
25
+
26
+export const ContextMenuReject = ({ participant: p }: Props) => {
27
+    const dispatch = useDispatch();
28
+    const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
29
+    const displayName = p.name;
30
+    const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false), [ dispatch ]));
31
+    const { t } = useTranslation();
32
+
33
+    return (
34
+        <BottomSheet
35
+            onCancel = { cancel }
36
+            style = { styles.contextMenuMore }>
37
+            <View
38
+                style = { styles.contextMenuItemDetails }>
39
+                <Avatar
40
+                    className = 'participant-avatar'
41
+                    participantId = { p.id }
42
+                    size = { 32 } />
43
+                <View style = { styles.contextMenuItemText }>
44
+                    <Text style = { styles.contextMenuItemParticipantName }>
45
+                        { displayName }
46
+                    </Text>
47
+                </View>
48
+            </View>
49
+            <TouchableOpacity
50
+                onPress = { reject }
51
+                style = { styles.contextMenuItemReject }>
52
+                <Icon
53
+                    size = { 24 }
54
+                    src = { IconClose } />
55
+                <Text style = { styles.contextMenuItemText }>{ t('lobby.reject') }</Text>
56
+            </TouchableOpacity>
57
+        </BottomSheet>
58
+    );
59
+};

+ 10
- 18
react/features/participants-pane/components/native/LobbyParticipantItem.js View File

@@ -2,11 +2,11 @@
2 2
 
3 3
 import React, { useCallback } from 'react';
4 4
 import { useTranslation } from 'react-i18next';
5
-import { View } from 'react-native';
6 5
 import { Button } from 'react-native-paper';
7 6
 import { useDispatch } from 'react-redux';
8 7
 
9 8
 import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
9
+import { showContextMenuReject } from '../../actions.native';
10 10
 import { MediaState } from '../../constants';
11 11
 
12 12
 import ParticipantItem from './ParticipantItem';
@@ -23,31 +23,23 @@ type Props = {
23 23
 export const LobbyParticipantItem = ({ participant: p }: Props) => {
24 24
     const dispatch = useDispatch();
25 25
     const admit = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, true), [ dispatch ]));
26
-    const reject = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, false), [ dispatch ]));
26
+    const openContextMenuReject = useCallback(() => dispatch(showContextMenuReject(p), [ dispatch ]));
27 27
     const { t } = useTranslation();
28 28
 
29 29
     return (
30 30
         <ParticipantItem
31 31
             audioMuteState = { MediaState.Muted }
32 32
             name = { p.name }
33
+            onPress = { openContextMenuReject }
33 34
             participant = { p }
34 35
             videoMuteState = { MediaState.ForceMuted }>
35
-            <View style = { styles.lobbyParticipantItem }>
36
-                <Button
37
-                    children = { t('lobby.admit') }
38
-                    contentStyle = { styles.participantActionsButtonContent }
39
-                    labelStyle = { styles.participantActionsButtonText }
40
-                    mode = 'contained'
41
-                    onPress = { admit }
42
-                    style = { styles.participantActionsButtonAdmit } />
43
-                <Button
44
-                    children = { t('lobby.reject') }
45
-                    contentStyle = { styles.participantActionsButtonContent }
46
-                    labelStyle = { styles.participantActionsButtonText }
47
-                    mode = 'contained'
48
-                    onPress = { reject }
49
-                    style = { styles.participantActionsButtonReject } />
50
-            </View>
36
+            <Button
37
+                children = { t('lobby.admit') }
38
+                contentStyle = { styles.participantActionsButtonContent }
39
+                labelStyle = { styles.participantActionsButtonText }
40
+                mode = 'contained'
41
+                onPress = { admit }
42
+                style = { styles.participantActionsButtonAdmit } />
51 43
         </ParticipantItem>
52 44
     );
53 45
 };

+ 5
- 12
react/features/participants-pane/components/native/LobbyParticipantList.js View File

@@ -19,18 +19,11 @@ export const LobbyParticipantList = () => {
19 19
                     {t('participantsPane.headings.lobby',
20 20
                         { count: participants.length })}
21 21
                 </Text>
22
-                <View style = { styles.lobbyListActions }>
23
-                    <Button
24
-                        labelStyle = { styles.allParticipantActionsButton }
25
-                        mode = 'text'>
26
-                        {t('lobby.admitAll')}
27
-                    </Button>
28
-                    <Button
29
-                        labelStyle = { styles.allParticipantActionsButton }
30
-                        mode = 'text'>
31
-                        {t('lobby.rejectAll')}
32
-                    </Button>
33
-                </View>
22
+                <Button
23
+                    labelStyle = { styles.allParticipantActionsButton }
24
+                    mode = 'text'>
25
+                    {t('lobby.admitAll')}
26
+                </Button>
34 27
             </View>
35 28
             { participants.map(p => (
36 29
                 <LobbyParticipantItem

+ 17
- 7
react/features/participants-pane/components/native/ParticipantItem.js View File

@@ -3,7 +3,7 @@
3 3
 import React from 'react';
4 4
 import type { Node } from 'react';
5 5
 import { useTranslation } from 'react-i18next';
6
-import { View } from 'react-native';
6
+import { TouchableOpacity, View } from 'react-native';
7 7
 import { Text } from 'react-native-paper';
8 8
 import { useSelector } from 'react-redux';
9 9
 
@@ -40,6 +40,11 @@ type Props = {
40 40
      */
41 41
     onLeave?: Function,
42 42
 
43
+    /**
44
+     * Callback to be invoked on pressing the participant item.
45
+     */
46
+    onPress?: Function,
47
+
43 48
     /**
44 49
      * Participant reference
45 50
      */
@@ -60,19 +65,24 @@ function ParticipantItem({
60 65
     audioMuteState = MediaState.None,
61 66
     children,
62 67
     name,
68
+    onPress,
63 69
     participant: p,
64 70
     videoMuteState = MediaState.None
65 71
 }: Props) {
72
+
66 73
     const displayName = name || useSelector(getParticipantDisplayNameWithId(p.id));
67 74
     const { t } = useTranslation();
68 75
 
69 76
     return (
70 77
         <View style = { styles.participantContainer } >
71
-            <Avatar
72
-                className = 'participant-avatar'
73
-                participantId = { p.id }
74
-                size = { 32 } />
75
-            <View style = { styles.participantContent }>
78
+            <TouchableOpacity
79
+                /* eslint-disable-next-line react/jsx-no-bind */
80
+                onPress = { onPress }
81
+                style = { styles.participantContent }>
82
+                <Avatar
83
+                    className = 'participant-avatar'
84
+                    participantId = { p.id }
85
+                    size = { 32 } />
76 86
                 <View style = { styles.participantNameContainer }>
77 87
                     <Text style = { styles.participantName }>
78 88
                         { displayName }
@@ -84,7 +94,7 @@ function ParticipantItem({
84 94
                     <View style = { styles.participantStateVideo }>{VideoStateIcons[videoMuteState]}</View>
85 95
                     <View style = { styles.participantStateAudio }>{AudioStateIcons[audioMuteState]}</View>
86 96
                 </View>
87
-            </View>
97
+            </TouchableOpacity>
88 98
             { children }
89 99
         </View>
90 100
     );

+ 3
- 0
react/features/participants-pane/components/native/ParticipantsPane.js View File

@@ -14,6 +14,7 @@ import MuteEveryoneDialog
14 14
 
15 15
 import { LobbyParticipantList } from './LobbyParticipantList';
16 16
 import { MeetingParticipantList } from './MeetingParticipantList';
17
+import { ContextMenuMore } from './ContextMenuMore';
17 18
 import styles from './styles';
18 19
 
19 20
 /**
@@ -23,6 +24,7 @@ import styles from './styles';
23 24
  */
24 25
 export function ParticipantsPane() {
25 26
     const dispatch = useDispatch();
27
+    const openMoreMenu = useCallback(() => dispatch(openDialog(ContextMenuMore)));
26 28
     const closePane = useCallback(
27 29
         () => dispatch(hideDialog()),
28 30
         [ dispatch ]);
@@ -68,6 +70,7 @@ export function ParticipantsPane() {
68 70
                             src = { IconHorizontalPoints } />)
69 71
                     }
70 72
                     mode = 'contained'
73
+                    onPress = { openMoreMenu }
71 74
                     style = { styles.moreButton } />
72 75
             </View>
73 76
         </JitsiModal>

+ 1
- 1
react/features/participants-pane/components/native/participants.js View File

@@ -93,7 +93,7 @@ export const participants = [
93 93
         isJigasi: undefined,
94 94
         loadableAvatarUrl: undefined,
95 95
         local: true,
96
-        name: 'Carlin',
96
+        name: 'Carlin Teodor',
97 97
         pinned: false,
98 98
         presence: undefined,
99 99
         raisedHand: true,

+ 66
- 20
react/features/participants-pane/components/native/styles.js View File

@@ -69,24 +69,30 @@ const buttonContent = {
69 69
     justifyContent: 'center'
70 70
 };
71 71
 
72
+/**
73
+ * The style of the context menu pane items.
74
+ */
75
+const contextMenuItem = {
76
+    flexDirection: 'row',
77
+    paddingBottom: 16,
78
+    paddingTop: 16
79
+};
80
+
72 81
 /**
73 82
  * The styles of the native components of the feature {@code participants}.
74 83
  */
75 84
 export default {
76 85
 
77
-    lobbyParticipantItem: {
86
+    participantActionsButtonAdmit: {
87
+        backgroundColor: BaseTheme.palette.action01,
88
+        borderRadius: BaseTheme.shape.borderRadius,
78 89
         flexDirection: 'row',
90
+        height: BaseTheme.spacing[5],
79 91
         position: 'absolute',
80 92
         right: 0,
81 93
         zIndex: 1
82 94
     },
83 95
 
84
-    participantActionsButtonAdmit: {
85
-        backgroundColor: BaseTheme.palette.action01,
86
-        borderRadius: BaseTheme.shape.borderRadius,
87
-        height: BaseTheme.spacing[5]
88
-    },
89
-
90 96
     participantActionsButtonReject: {
91 97
         backgroundColor: BaseTheme.palette.action01,
92 98
         borderRadius: BaseTheme.shape.borderRadius,
@@ -105,10 +111,10 @@ export default {
105 111
     },
106 112
 
107 113
     allParticipantActionsButton: {
108
-        ...BaseTheme.typography.labelRegular,
109
-        color: BaseTheme.palette.action01,
114
+        ...BaseTheme.typography.heading6,
115
+        color: BaseTheme.palette.link01,
116
+        marginRight: 'auto',
110 117
         textTransform: 'capitalize'
111
-
112 118
     },
113 119
 
114 120
     participantContainer: {
@@ -144,7 +150,9 @@ export default {
144 150
     },
145 151
 
146 152
     isLocal: {
147
-        color: BaseTheme.palette.text01
153
+        alignSelf: 'center',
154
+        color: BaseTheme.palette.text01,
155
+        marginLeft: 4
148 156
     },
149 157
 
150 158
     participantsPane: {
@@ -154,7 +162,8 @@ export default {
154 162
     participantStatesContainer: {
155 163
         display: 'flex',
156 164
         flexDirection: 'row',
157
-        marginLeft: BaseTheme.spacing[3]
165
+        marginLeft: 'auto',
166
+        width: 72
158 167
     },
159 168
 
160 169
     participantStateAudio: {
@@ -191,6 +200,7 @@ export default {
191 200
         alignItems: 'center',
192 201
         display: 'flex',
193 202
         flexDirection: 'row',
203
+        justifyContent: 'space-between',
194 204
         overflow: 'hidden',
195 205
         position: 'relative',
196 206
         width: '100%'
@@ -204,10 +214,6 @@ export default {
204 214
         ...participantListDescription
205 215
     },
206 216
 
207
-    lobbyListActions: {
208
-        flexDirection: 'row'
209
-    },
210
-
211 217
     header: {
212 218
         alignItems: 'center',
213 219
         backgroundColor: BaseTheme.palette.ui01,
@@ -243,10 +249,6 @@ export default {
243 249
         left: BaseTheme.spacing[2]
244 250
     },
245 251
 
246
-    moreButton: {
247
-        ...smallButton
248
-    },
249
-
250 252
     inviteButton: {
251 253
         backgroundColor: BaseTheme.palette.action01,
252 254
         marginTop: BaseTheme.spacing[2]
@@ -257,11 +259,20 @@ export default {
257 259
         textTransform: 'capitalize'
258 260
     },
259 261
 
262
+    moreButton: {
263
+        ...smallButton
264
+    },
265
+
260 266
     moreIcon: {
261 267
         ...buttonContent,
262 268
         left: BaseTheme.spacing[2]
263 269
     },
264 270
 
271
+    contextMenuMore: {
272
+        backgroundColor: BaseTheme.palette.action02,
273
+        borderRadius: BaseTheme.shape.borderRadius
274
+    },
275
+
265 276
     muteAllButton: {
266 277
         ...button,
267 278
         left: BaseTheme.spacing[10] + BaseTheme.spacing[2]
@@ -274,5 +285,40 @@ export default {
274 285
     muteAllLabel: {
275 286
         color: BaseTheme.palette.text01,
276 287
         textTransform: 'capitalize'
288
+    },
289
+
290
+    contextMenuItemMuteVideo: {
291
+        ...contextMenuItem
292
+    },
293
+
294
+    contextMenuItemDontAllowUnmute: {
295
+        ...contextMenuItem
296
+    },
297
+
298
+    contextMenuItemDetails: {
299
+        ...contextMenuItem,
300
+        borderBottomColor: BaseTheme.palette.section01,
301
+        borderBottomWidth: 1
302
+    },
303
+
304
+    contextMenuItemReject: {
305
+        ...contextMenuItem
306
+    },
307
+
308
+    contextMenuItemText: {
309
+        ...BaseTheme.typography.bodyShortRegularLarge,
310
+        alignSelf: 'center',
311
+        color: BaseTheme.palette.text01,
312
+        flexDirection: 'row',
313
+        marginLeft: 8
314
+    },
315
+
316
+    contextMenuItemParticipantName: {
317
+        ...BaseTheme.typography.bodyShortRegularLarge,
318
+        color: BaseTheme.palette.text01
319
+    },
320
+
321
+    contextMenuIcon: {
322
+        color: BaseTheme.palette.actionDanger
277 323
     }
278 324
 };

Loading…
Cancel
Save