Просмотр исходного кода

feat(breakout-rooms/native): separate breakout rooms from participants (#13920)

feat(breakout-rooms/native): separate breakout rooms from participants
factor2
Calinteodor 2 лет назад
Родитель
Сommit
9c04ba767c
Аккаунт пользователя с таким Email не найден
24 измененных файлов: 504 добавлений и 605 удалений
  1. 6
    3
      lang/main.json
  2. 6
    0
      react/features/base/flags/constants.ts
  3. 3
    3
      react/features/breakout-rooms/components/native/AddBreakoutRoomButton.tsx
  4. 3
    3
      react/features/breakout-rooms/components/native/AutoAssignButton.tsx
  5. 18
    15
      react/features/breakout-rooms/components/native/BreakoutRoomContextMenu.tsx
  6. 4
    3
      react/features/breakout-rooms/components/native/BreakoutRoomNamePrompt.tsx
  7. 5
    5
      react/features/breakout-rooms/components/native/BreakoutRoomParticipantItem.tsx
  8. 68
    0
      react/features/breakout-rooms/components/native/BreakoutRooms.tsx
  9. 53
    0
      react/features/breakout-rooms/components/native/BreakoutRoomsButton.tsx
  10. 11
    23
      react/features/breakout-rooms/components/native/CollapsibleRoom.tsx
  11. 5
    5
      react/features/breakout-rooms/components/native/LeaveBreakoutRoomButton.tsx
  12. 23
    19
      react/features/breakout-rooms/components/native/styles.ts
  13. 12
    0
      react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx
  14. 25
    24
      react/features/mobile/navigation/routes.ts
  15. 10
    5
      react/features/mobile/navigation/screenOptions.ts
  16. 5
    15
      react/features/participants-pane/components/native/CollapsibleList.tsx
  17. 26
    40
      react/features/participants-pane/components/native/LobbyParticipantList.tsx
  18. 90
    302
      react/features/participants-pane/components/native/MeetingParticipantList.tsx
  19. 3
    1
      react/features/participants-pane/components/native/ParticipantItem.tsx
  20. 20
    49
      react/features/participants-pane/components/native/ParticipantsPane.tsx
  21. 50
    19
      react/features/participants-pane/components/native/ParticipantsPaneFooter.tsx
  22. 44
    70
      react/features/participants-pane/components/native/styles.ts
  23. 1
    1
      react/features/participants-pane/functions.ts
  24. 13
    0
      react/features/toolbox/components/native/OverflowMenu.tsx

+ 6
- 3
lang/main.json Просмотреть файл

@@ -67,7 +67,8 @@
67 67
             "renameBreakoutRoom": "Rename breakout room",
68 68
             "sendToBreakoutRoom": "Send participant to:"
69 69
         },
70
-        "breakoutList": "breakout list",
70
+        "breakoutList": "Breakout list",
71
+        "buttonLabel": "Breakout rooms",
71 72
         "defaultName": "Breakout room #{{index}}",
72 73
         "hideParticipantList": "Hide participant list",
73 74
         "mainRoom": "Main room",
@@ -76,7 +77,8 @@
76 77
             "joinedMainRoom": "Joining the main room",
77 78
             "joinedTitle": "Breakout Rooms"
78 79
         },
79
-        "showParticipantList": "Show participant list"
80
+        "showParticipantList": "Show participant list",
81
+        "title": "Breakout Rooms"
80 82
     },
81 83
     "calendarSync": {
82 84
         "addMeetingURL": "Add a meeting link",
@@ -811,6 +813,7 @@
811 813
             "askUnmute": "Ask to unmute",
812 814
             "audioModeration": "Unmute themselves",
813 815
             "blockEveryoneMicCamera": "Block everyone's mic and camera",
816
+            "breakoutRooms": "Breakout rooms",
814 817
             "invite": "Invite Someone",
815 818
             "moreModerationActions": "More moderation options",
816 819
             "moreModerationControls": "More moderation controls",
@@ -1157,7 +1160,7 @@
1157 1160
             "audioOnly": "Toggle audio only",
1158 1161
             "audioRoute": "Select the sound device",
1159 1162
             "boo": "Boo",
1160
-            "breakoutRoom": "Join/leave breakout room",
1163
+            "breakoutRooms": "Breakout rooms",
1161 1164
             "callQuality": "Manage video quality",
1162 1165
             "carmode": "Car Mode",
1163 1166
             "cc": "Toggle subtitles",

+ 6
- 0
react/features/base/flags/constants.ts Просмотреть файл

@@ -23,6 +23,12 @@ export const AUDIO_MUTE_BUTTON_ENABLED = 'audio-mute.enabled';
23 23
  */
24 24
 export const AUDIO_ONLY_BUTTON_ENABLED = 'audio-only.enabled';
25 25
 
26
+/**
27
+ * Flag indicating that the Breakout Rooms button in the overflow menu is enabled.
28
+ * Default: enabled (true).
29
+ */
30
+export const BREAKOUT_ROOMS_BUTTON_ENABLED = 'breakout-rooms.enabled';
31
+
26 32
 /**
27 33
  * Flag indicating if calendar integration should be enabled.
28 34
  * Default: enabled (true) on Android, auto-detected on iOS.

react/features/participants-pane/components/breakout-rooms/components/native/AddBreakoutRoomButton.tsx → react/features/breakout-rooms/components/native/AddBreakoutRoomButton.tsx Просмотреть файл

@@ -1,9 +1,9 @@
1 1
 import React, { useCallback } from 'react';
2 2
 import { useDispatch } from 'react-redux';
3 3
 
4
-import Button from '../../../../../base/ui/components/native/Button';
5
-import { BUTTON_TYPES } from '../../../../../base/ui/constants.native';
6
-import { createBreakoutRoom } from '../../../../../breakout-rooms/actions';
4
+import Button from '../../../base/ui/components/native/Button';
5
+import { BUTTON_TYPES } from '../../../base/ui/constants.native';
6
+import { createBreakoutRoom } from '../../actions';
7 7
 
8 8
 import styles from './styles';
9 9
 

react/features/participants-pane/components/breakout-rooms/components/native/AutoAssignButton.tsx → react/features/breakout-rooms/components/native/AutoAssignButton.tsx Просмотреть файл

@@ -1,9 +1,9 @@
1 1
 import React, { useCallback } from 'react';
2 2
 import { useDispatch } from 'react-redux';
3 3
 
4
-import Button from '../../../../../base/ui/components/native/Button';
5
-import { BUTTON_TYPES } from '../../../../../base/ui/constants.native';
6
-import { autoAssignToBreakoutRooms } from '../../../../../breakout-rooms/actions';
4
+import Button from '../../../base/ui/components/native/Button';
5
+import { BUTTON_TYPES } from '../../../base/ui/constants.native';
6
+import { autoAssignToBreakoutRooms } from '../../actions';
7 7
 
8 8
 import styles from './styles';
9 9
 

react/features/participants-pane/components/breakout-rooms/components/native/BreakoutRoomContextMenu.tsx → react/features/breakout-rooms/components/native/BreakoutRoomContextMenu.tsx Просмотреть файл

@@ -4,22 +4,23 @@ import { TouchableOpacity, ViewStyle } from 'react-native';
4 4
 import { Text } from 'react-native-paper';
5 5
 import { useDispatch, useSelector } from 'react-redux';
6 6
 
7
-import { createBreakoutRoomsEvent } from '../../../../../analytics/AnalyticsEvents';
8
-import { sendAnalytics } from '../../../../../analytics/functions';
9
-import { hideSheet, openDialog } from '../../../../../base/dialog/actions';
10
-import BottomSheet from '../../../../../base/dialog/components/native/BottomSheet';
11
-import Icon from '../../../../../base/icons/components/Icon';
12
-import { IconCloseLarge, IconEdit, IconRingGroup } from '../../../../../base/icons/svg';
13
-import { isLocalParticipantModerator } from '../../../../../base/participants/functions';
14
-import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../../../../breakout-rooms/actions';
15
-import { getBreakoutRoomsConfig } from '../../../../../breakout-rooms/functions';
16
-import { IRoom } from '../../../../../breakout-rooms/types';
17
-import { isBreakoutRoomRenameAllowed } from '../../../../functions';
18
-import { BREAKOUT_CONTEXT_MENU_ACTIONS as ACTIONS } from '../../../../types';
19
-import styles from '../../../native/styles';
7
+import { createBreakoutRoomsEvent } from '../../../analytics/AnalyticsEvents';
8
+import { sendAnalytics } from '../../../analytics/functions';
9
+import { hideSheet, openDialog } from '../../../base/dialog/actions';
10
+import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
11
+import Icon from '../../../base/icons/components/Icon';
12
+import { IconCloseLarge, IconEdit, IconRingGroup } from '../../../base/icons/svg';
13
+import { isLocalParticipantModerator } from '../../../base/participants/functions';
14
+import styles from '../../../participants-pane/components/native/styles';
15
+import { isBreakoutRoomRenameAllowed } from '../../../participants-pane/functions';
16
+import { BREAKOUT_CONTEXT_MENU_ACTIONS as ACTIONS } from '../../../participants-pane/types';
17
+import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../actions';
18
+import { getBreakoutRoomsConfig } from '../../functions';
19
+import { IRoom } from '../../types';
20 20
 
21 21
 import BreakoutRoomNamePrompt from './BreakoutRoomNamePrompt';
22 22
 
23
+
23 24
 /**
24 25
  * An array with all possible breakout rooms actions.
25 26
  */
@@ -85,7 +86,8 @@ const BreakoutRoomContextMenu = ({ room, actions = ALL_ACTIONS }: IProps) => {
85 86
                     </TouchableOpacity>
86 87
                 )
87 88
             }
88
-            {!room?.isMainRoom && actions.includes(ACTIONS.RENAME) && _isBreakoutRoomRenameAllowed
89
+            {
90
+                !room?.isMainRoom && actions.includes(ACTIONS.RENAME) && _isBreakoutRoomRenameAllowed
89 91
                 && <TouchableOpacity
90 92
                     onPress = { onRenameBreakoutRoom }
91 93
                     style = { styles.contextMenuItem as ViewStyle }>
@@ -95,7 +97,8 @@ const BreakoutRoomContextMenu = ({ room, actions = ALL_ACTIONS }: IProps) => {
95 97
                     <Text style = { styles.contextMenuItemText }>{t('breakoutRooms.actions.rename')}</Text>
96 98
                 </TouchableOpacity>
97 99
             }
98
-            {!room?.isMainRoom && isLocalModerator && actions.includes(ACTIONS.REMOVE)
100
+            {
101
+                !room?.isMainRoom && isLocalModerator && actions.includes(ACTIONS.REMOVE)
99 102
                 && (room?.participants && Object.keys(room.participants).length > 0
100 103
                     ? <TouchableOpacity
101 104
                         onPress = { onCloseBreakoutRoom }

react/features/participants-pane/components/breakout-rooms/components/native/BreakoutRoomNamePrompt.tsx → react/features/breakout-rooms/components/native/BreakoutRoomNamePrompt.tsx Просмотреть файл

@@ -1,9 +1,10 @@
1 1
 import React, { useCallback } from 'react';
2 2
 import { useDispatch } from 'react-redux';
3 3
 
4
-import InputDialog from '../../../../../base/dialog/components/native/InputDialog';
5
-import { renameBreakoutRoom } from '../../../../../breakout-rooms/actions';
6
-import { IBreakoutRoomNamePromptProps as IProps } from '../../../../types';
4
+import InputDialog from '../../../base/dialog/components/native/InputDialog';
5
+import { IBreakoutRoomNamePromptProps as IProps } from '../../../participants-pane/types';
6
+import { renameBreakoutRoom } from '../../actions';
7
+
7 8
 
8 9
 /**
9 10
  * Implements a component to render a breakout room name prompt.

react/features/participants-pane/components/breakout-rooms/components/native/BreakoutRoomParticipantItem.tsx → react/features/breakout-rooms/components/native/BreakoutRoomParticipantItem.tsx Просмотреть файл

@@ -1,11 +1,11 @@
1 1
 import React, { useCallback } from 'react';
2 2
 import { useDispatch, useSelector } from 'react-redux';
3 3
 
4
-import { IReduxState } from '../../../../../app/types';
5
-import { isLocalParticipantModerator, isParticipantModerator } from '../../../../../base/participants/functions';
6
-import { IRoom } from '../../../../../breakout-rooms/types';
7
-import { showRoomParticipantMenu } from '../../../../actions.native';
8
-import ParticipantItem from '../../../native/ParticipantItem';
4
+import { IReduxState } from '../../../app/types';
5
+import { isLocalParticipantModerator, isParticipantModerator } from '../../../base/participants/functions';
6
+import { showRoomParticipantMenu } from '../../../participants-pane/actions.native';
7
+import ParticipantItem from '../../../participants-pane/components/native/ParticipantItem';
8
+import { IRoom } from '../../types';
9 9
 
10 10
 interface IProps {
11 11
 

+ 68
- 0
react/features/breakout-rooms/components/native/BreakoutRooms.tsx Просмотреть файл

@@ -0,0 +1,68 @@
1
+import React, { useCallback } from 'react';
2
+import { FlatList } from 'react-native';
3
+import { useSelector } from 'react-redux';
4
+
5
+import { IReduxState } from '../../../app/types';
6
+import JitsiScreen from '../../../base/modal/components/JitsiScreen';
7
+import { isLocalParticipantModerator } from '../../../base/participants/functions';
8
+import { equals } from '../../../base/redux/functions';
9
+import {
10
+    getBreakoutRooms,
11
+    getCurrentRoomId,
12
+    isAddBreakoutRoomButtonVisible,
13
+    isAutoAssignParticipantsVisible,
14
+    isInBreakoutRoom
15
+} from '../../functions';
16
+
17
+import AddBreakoutRoomButton from './AddBreakoutRoomButton';
18
+import AutoAssignButton from './AutoAssignButton';
19
+import { CollapsibleRoom } from './CollapsibleRoom';
20
+import LeaveBreakoutRoomButton from './LeaveBreakoutRoomButton';
21
+import styles from './styles';
22
+
23
+
24
+const BreakoutRooms = () => {
25
+    const currentRoomId = useSelector(getCurrentRoomId);
26
+    const inBreakoutRoom = useSelector(isInBreakoutRoom);
27
+    const isBreakoutRoomsSupported = useSelector((state: IReduxState) =>
28
+        state['features/base/conference'].conference?.getBreakoutRooms()?.isSupported());
29
+    const isLocalModerator = useSelector(isLocalParticipantModerator);
30
+    const keyExtractor = useCallback((e: undefined, i: number) => i.toString(), []);
31
+    const rooms = Object.values(useSelector(getBreakoutRooms, equals))
32
+        .filter(room => room.id !== currentRoomId)
33
+        .sort((p1, p2) => (p1?.name || '').localeCompare(p2?.name || ''));
34
+    const showAddBreakoutRoom = useSelector(isAddBreakoutRoomButtonVisible);
35
+    const showAutoAssign = useSelector(isAutoAssignParticipantsVisible);
36
+
37
+    return (
38
+        <JitsiScreen
39
+            footerComponent = { isLocalModerator && showAddBreakoutRoom
40
+                ? AddBreakoutRoomButton : undefined }
41
+            style = { styles.breakoutRoomsContainer }>
42
+
43
+            { /* Fixes warning regarding nested lists */ }
44
+            <FlatList
45
+
46
+                /* eslint-disable react/jsx-no-bind */
47
+                ListHeaderComponent = { () => (
48
+                    <>
49
+                        { showAutoAssign && <AutoAssignButton /> }
50
+                        { inBreakoutRoom && <LeaveBreakoutRoomButton /> }
51
+                        {
52
+                            isBreakoutRoomsSupported
53
+                            && rooms.map(room => (<CollapsibleRoom
54
+                                key = { room.id }
55
+                                room = { room }
56
+                                roomId = { room.id } />))
57
+                        }
58
+                    </>
59
+                ) }
60
+                data = { [] as ReadonlyArray<undefined> }
61
+                keyExtractor = { keyExtractor }
62
+                renderItem = { null }
63
+                windowSize = { 2 } />
64
+        </JitsiScreen>
65
+    );
66
+};
67
+
68
+export default BreakoutRooms;

+ 53
- 0
react/features/breakout-rooms/components/native/BreakoutRoomsButton.tsx Просмотреть файл

@@ -0,0 +1,53 @@
1
+import { connect } from 'react-redux';
2
+
3
+import { IReduxState } from '../../../app/types';
4
+import {
5
+    BREAKOUT_ROOMS_BUTTON_ENABLED
6
+} from '../../../base/flags/constants';
7
+import { getFeatureFlag } from '../../../base/flags/functions';
8
+import { translate } from '../../../base/i18n/functions';
9
+import { IconRingGroup } from '../../../base/icons/svg';
10
+import AbstractButton,
11
+{
12
+    IProps as AbstractButtonProps
13
+} from '../../../base/toolbox/components/AbstractButton';
14
+import {
15
+    navigate
16
+} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
17
+import { screen } from '../../../mobile/navigation/routes';
18
+
19
+
20
+/**
21
+ * Implements an {@link AbstractButton} to open the breakout room screen.
22
+ */
23
+class BreakoutRoomsButton extends AbstractButton<AbstractButtonProps> {
24
+    accessibilityLabel = 'toolbar.accessibilityLabel.breakoutRooms';
25
+    icon = IconRingGroup;
26
+    label = 'breakoutRooms.buttonLabel';
27
+
28
+    /**
29
+     * Handles clicking / pressing the button and opens the breakout rooms screen.
30
+     *
31
+     * @private
32
+     * @returns {void}
33
+     */
34
+    _handleClick() {
35
+        return navigate(screen.conference.breakoutRooms);
36
+    }
37
+}
38
+
39
+/**
40
+ * Maps part of the redux state to the component's props.
41
+ *
42
+ * @param {IReduxState} state - The Redux state.
43
+ * @returns {Object}
44
+ */
45
+function _mapStateToProps(state: IReduxState) {
46
+    const enabled = getFeatureFlag(state, BREAKOUT_ROOMS_BUTTON_ENABLED, true);
47
+
48
+    return {
49
+        visible: enabled
50
+    };
51
+}
52
+
53
+export default translate(connect(_mapStateToProps)(BreakoutRoomsButton));

react/features/participants-pane/components/breakout-rooms/components/native/CollapsibleRoom.tsx → react/features/breakout-rooms/components/native/CollapsibleRoom.tsx Просмотреть файл

@@ -3,11 +3,9 @@ import { useTranslation } from 'react-i18next';
3 3
 import { FlatList } from 'react-native';
4 4
 import { useDispatch } from 'react-redux';
5 5
 
6
-import { openSheet } from '../../../../../base/dialog/actions';
7
-import { IRoom } from '../../../../../breakout-rooms/types';
8
-import { participantMatchesSearch } from '../../../../functions';
9
-import CollapsibleList from '../../../native/CollapsibleList';
10
-import styles from '../../../native/styles';
6
+import { openSheet } from '../../../base/dialog/actions';
7
+import CollapsibleList from '../../../participants-pane/components/native/CollapsibleList';
8
+import { IRoom } from '../../types';
11 9
 
12 10
 import BreakoutRoomContextMenu from './BreakoutRoomContextMenu';
13 11
 import BreakoutRoomParticipantItem from './BreakoutRoomParticipantItem';
@@ -19,10 +17,7 @@ interface IProps {
19 17
      */
20 18
     room: IRoom;
21 19
 
22
-    /**
23
-     * Participants search string.
24
-     */
25
-    searchString: string;
20
+    roomId: string;
26 21
 }
27 22
 
28 23
 /**
@@ -35,7 +30,7 @@ function _keyExtractor(item: any) {
35 30
     return item.jid;
36 31
 }
37 32
 
38
-export const CollapsibleRoom = ({ room, searchString }: IProps) => {
33
+export const CollapsibleRoom = ({ room, roomId }: IProps) => {
39 34
     const dispatch = useDispatch();
40 35
     const { t } = useTranslation();
41 36
     const _openContextMenu = useCallback(() => {
@@ -46,29 +41,22 @@ export const CollapsibleRoom = ({ room, searchString }: IProps) => {
46 41
         = `${room.name
47 42
     || t('breakoutRooms.mainRoom')} (${roomParticipantsNr})`;
48 43
 
49
-    // Regarding the fact that we have 3 sections, we apply
50
-    // a certain height percentage for every section in order for all to fit
51
-    // inside the participants pane container
52
-    const containerStyle
53
-        = roomParticipantsNr > 2 ? styles.collapsibleRoomContainer : undefined;
54
-
55 44
     return (
56 45
         <CollapsibleList
57
-            containerStyle = { containerStyle }
58 46
             onLongPress = { _openContextMenu }
59 47
             title = { title }>
60 48
             <FlatList
61
-                bounces = { false }
62 49
                 data = { Object.values(room.participants || {}) }
63
-                horizontal = { false }
64 50
                 keyExtractor = { _keyExtractor }
51
+                listKey = { roomId }
52
+
65 53
                 // eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow
66
-                renderItem = { ({ item: participant }) => participantMatchesSearch(participant, searchString)
67
-                    ? <BreakoutRoomParticipantItem
54
+                renderItem = { ({ item: participant }) => (
55
+                    <BreakoutRoomParticipantItem
68 56
                         item = { participant }
69 57
                         room = { room } />
70
-                    : null }
71
-                scrollEnabled = { true }
58
+                ) }
59
+                scrollEnabled = { false }
72 60
                 showsHorizontalScrollIndicator = { false }
73 61
                 windowSize = { 2 } />
74 62
         </CollapsibleList>

react/features/participants-pane/components/breakout-rooms/components/native/LeaveBreakoutRoomButton.tsx → react/features/breakout-rooms/components/native/LeaveBreakoutRoomButton.tsx Просмотреть файл

@@ -1,11 +1,11 @@
1 1
 import React, { useCallback } from 'react';
2 2
 import { useDispatch } from 'react-redux';
3 3
 
4
-import { createBreakoutRoomsEvent } from '../../../../../analytics/AnalyticsEvents';
5
-import { sendAnalytics } from '../../../../../analytics/functions';
6
-import Button from '../../../../../base/ui/components/native/Button';
7
-import { BUTTON_TYPES } from '../../../../../base/ui/constants.native';
8
-import { moveToRoom } from '../../../../../breakout-rooms/actions';
4
+import { createBreakoutRoomsEvent } from '../../../analytics/AnalyticsEvents';
5
+import { sendAnalytics } from '../../../analytics/functions';
6
+import Button from '../../../base/ui/components/native/Button';
7
+import { BUTTON_TYPES } from '../../../base/ui/constants.native';
8
+import { moveToRoom } from '../../actions';
9 9
 
10 10
 import styles from './styles';
11 11
 

react/features/participants-pane/components/breakout-rooms/components/native/styles.ts → react/features/breakout-rooms/components/native/styles.ts Просмотреть файл

@@ -1,12 +1,5 @@
1
-import BaseTheme from '../../../../../base/ui/components/BaseTheme.native';
1
+import BaseTheme from '../../../base/ui/components/BaseTheme.native';
2 2
 
3
-const baseButton = {
4
-    borderRadius: BaseTheme.shape.borderRadius,
5
-    height: BaseTheme.spacing[7],
6
-    marginTop: BaseTheme.spacing[3],
7
-    marginLeft: BaseTheme.spacing[3],
8
-    marginRight: BaseTheme.spacing[3]
9
-};
10 3
 
11 4
 /**
12 5
  * The styles of the native components of the feature {@code breakout rooms}.
@@ -14,16 +7,8 @@ const baseButton = {
14 7
 export default {
15 8
 
16 9
     button: {
17
-        marginTop: BaseTheme.spacing[3],
18
-        marginLeft: BaseTheme.spacing[3],
19
-        marginRight: BaseTheme.spacing[3]
20
-    },
21
-
22
-    collapsibleRoom: {
23
-        ...baseButton,
24
-        display: 'flex',
25
-        flexDirection: 'row',
26
-        alignItems: 'center'
10
+        marginBottom: BaseTheme.spacing[4],
11
+        marginHorizontal: BaseTheme.spacing[2]
27 12
     },
28 13
 
29 14
     collapsibleList: {
@@ -32,7 +17,7 @@ export default {
32 17
         display: 'flex',
33 18
         flexDirection: 'row',
34 19
         height: BaseTheme.spacing[7],
35
-        marginHorizontal: BaseTheme.spacing[3],
20
+        marginHorizontal: BaseTheme.spacing[2],
36 21
         marginTop: BaseTheme.spacing[3]
37 22
     },
38 23
 
@@ -68,5 +53,24 @@ export default {
68 53
         alignSelf: 'center',
69 54
         justifyContent: 'center',
70 55
         marginTop: BaseTheme.spacing[3]
56
+    },
57
+
58
+    breakoutRoomsContainer: {
59
+        backgroundColor: BaseTheme.palette.ui01,
60
+        flex: 1,
61
+        flexDirection: 'column',
62
+        height: 'auto',
63
+        paddingHorizontal: BaseTheme.spacing[3]
64
+    },
65
+
66
+    inputContainer: {
67
+        marginLeft: BaseTheme.spacing[2],
68
+        marginRight: BaseTheme.spacing[2],
69
+        marginTop: BaseTheme.spacing[4]
70
+    },
71
+
72
+    centerInput: {
73
+        paddingRight: BaseTheme.spacing[3],
74
+        textAlign: 'center'
71 75
     }
72 76
 };

+ 12
- 0
react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx Просмотреть файл

@@ -6,6 +6,9 @@ import React from 'react';
6 6
 import { useTranslation } from 'react-i18next';
7 7
 import { useSelector } from 'react-redux';
8 8
 
9
+import BreakoutRooms
10
+// @ts-ignore
11
+    from '../../../../../breakout-rooms/components/native/BreakoutRooms';
9 12
 // @ts-ignore
10 13
 import Chat from '../../../../../chat/components/native/Chat';
11 14
 // @ts-ignore
@@ -43,6 +46,7 @@ import LanguageSelectorDialog
43 46
 // @ts-ignore
44 47
 import { screen } from '../../../routes';
45 48
 import {
49
+    breakoutRoomsScreenOptions,
46 50
     carmodeScreenOptions,
47 51
     chatScreenOptions,
48 52
     conferenceScreenOptions,
@@ -72,6 +76,7 @@ import {
72 76
     // @ts-ignore
73 77
 } from '../ConferenceNavigationContainerRef';
74 78
 
79
+
75 80
 const ConferenceStack = createStackNavigator();
76 81
 
77 82
 
@@ -202,6 +207,13 @@ const ConferenceNavigationContainer = () => {
202 207
                         ...subtitlesScreenOptions,
203 208
                         title: t('transcribing.subtitles')
204 209
                     }} />
210
+                <ConferenceStack.Screen
211
+                    component = { BreakoutRooms }
212
+                    name = { screen.conference.breakoutRooms }
213
+                    options = {{
214
+                        ...breakoutRoomsScreenOptions,
215
+                        title: t('breakoutRooms.title')
216
+                    }} />
205 217
             </ConferenceStack.Navigator>
206 218
         </NavigationContainer>
207 219
     );

+ 25
- 24
react/features/mobile/navigation/routes.ts Просмотреть файл

@@ -1,17 +1,6 @@
1 1
 export const screen = {
2
-    welcome: {
3
-        main: 'Welcome',
4
-        tabs: {
5
-            recent: 'Recent',
6
-            calendar: 'Calendar'
7
-        }
8
-    },
9
-    dialInSummary: 'Dial-In Info',
10
-    connecting: 'Connecting',
11
-    preJoin: 'Pre-Join',
12 2
     conference: {
13
-        root: 'Conference root',
14
-        main: 'Conference',
3
+        breakoutRooms: 'Breakout Rooms',
15 4
         carmode: 'Car Mode',
16 5
         chat: 'Chat',
17 6
         chatandpolls: {
@@ -22,31 +11,43 @@ export const screen = {
22 11
             }
23 12
         },
24 13
         container: 'Conference container',
25
-        security: 'Security Options',
26
-        recording: 'Recording',
27
-        liveStream: 'Live stream',
28
-        speakerStats: 'Speaker Stats',
29
-        salesforce: 'Link to Salesforce',
30
-        participants: 'Participants',
31 14
         gifsMenu: 'GIPHY',
32 15
         invite: 'Invite',
16
+        liveStream: 'Live stream',
17
+        main: 'Conference',
18
+        participants: 'Participants',
19
+        root: 'Conference root',
20
+        recording: 'Recording',
21
+        salesforce: 'Link to Salesforce',
22
+        security: 'Security Options',
33 23
         sharedDocument: 'Shared document',
24
+        speakerStats: 'Speaker Stats',
34 25
         subtitles: 'Subtitles'
35 26
     },
27
+    connecting: 'Connecting',
28
+    dialInSummary: 'Dial-In Info',
29
+    preJoin: 'Pre-Join',
36 30
     lobby: {
37
-        root: 'Lobby root',
31
+        chat: 'Lobby chat',
38 32
         main: 'Lobby',
39
-        chat: 'Lobby chat'
33
+        root: 'Lobby root'
40 34
     },
41 35
     settings: {
42
-        main: 'Settings',
36
+        language: 'Language',
43 37
         links: {
44 38
             help: 'Help',
45 39
             privacy: 'Privacy',
46 40
             terms: 'Terms'
47 41
         },
48
-        profile: 'Profile',
49
-        language: 'Language'
42
+        main: 'Settings',
43
+        profile: 'Profile'
50 44
     },
51
-    unsafeRoomWarning: 'Unsafe Room Warning'
45
+    unsafeRoomWarning: 'Unsafe Room Warning',
46
+    welcome: {
47
+        main: 'Welcome',
48
+        tabs: {
49
+            calendar: 'Calendar',
50
+            recent: 'Recent'
51
+        }
52
+    }
52 53
 };

+ 10
- 5
react/features/mobile/navigation/screenOptions.ts Просмотреть файл

@@ -86,6 +86,11 @@ export const presentationScreenOptions = {
86 86
     }
87 87
 };
88 88
 
89
+/**
90
+ * Screen options for breakout rooms screen.
91
+ */
92
+export const breakoutRoomsScreenOptions = presentationScreenOptions;
93
+
89 94
 /**
90 95
  * Screen options for car mode.
91 96
  */
@@ -109,6 +114,11 @@ export const dialInSummaryScreenOptions = {
109 114
  */
110 115
 export const inviteScreenOptions = presentationScreenOptions;
111 116
 
117
+/**
118
+ * Screen options for live stream modal.
119
+ */
120
+export const liveStreamScreenOptions = presentationScreenOptions;
121
+
112 122
 /**
113 123
  * Screen options for participants modal.
114 124
  */
@@ -129,11 +139,6 @@ export const securityScreenOptions = presentationScreenOptions;
129 139
  */
130 140
 export const recordingScreenOptions = presentationScreenOptions;
131 141
 
132
-/**
133
- * Screen options for live stream modal.
134
- */
135
-export const liveStreamScreenOptions = presentationScreenOptions;
136
-
137 142
 /**
138 143
  * Screen options for subtitles modal.
139 144
  */

+ 5
- 15
react/features/participants-pane/components/native/CollapsibleList.tsx Просмотреть файл

@@ -3,8 +3,7 @@ import { GestureResponderEvent, Text, TextStyle, TouchableOpacity, View, ViewSty
3 3
 
4 4
 import Icon from '../../../base/icons/components/Icon';
5 5
 import { IconArrowDown, IconArrowUp } from '../../../base/icons/svg';
6
-import { StyleType } from '../../../base/styles/functions.native';
7
-import styles from '../breakout-rooms/components/native/styles';
6
+import styles from '../../../breakout-rooms/components/native/styles';
8 7
 
9 8
 interface IProps {
10 9
 
@@ -13,11 +12,6 @@ interface IProps {
13 12
      */
14 13
     children: React.ReactNode;
15 14
 
16
-    /**
17
-     * Additional style to be appended to the CollapsibleList container.
18
-     */
19
-    containerStyle?: StyleType;
20
-
21 15
     /**
22 16
      * Callback to invoke when the {@code CollapsibleList} is long pressed.
23 17
      */
@@ -29,14 +23,14 @@ interface IProps {
29 23
     title: Object;
30 24
 }
31 25
 
32
-const CollapsibleList = ({ children, containerStyle, onLongPress, title }: IProps) => {
26
+const CollapsibleList = ({ children, onLongPress, title }: IProps) => {
33 27
     const [ collapsed, setCollapsed ] = useState(false);
34 28
     const _toggleCollapsed = useCallback(() => {
35 29
         setCollapsed(!collapsed);
36 30
     }, [ collapsed ]);
37 31
 
38 32
     return (
39
-        <View style = { !collapsed && containerStyle }>
33
+        <View>
40 34
             <TouchableOpacity
41 35
                 onLongPress = { onLongPress }
42 36
                 onPress = { _toggleCollapsed }
@@ -49,14 +43,10 @@ const CollapsibleList = ({ children, containerStyle, onLongPress, title }: IProp
49 43
                         src = { collapsed ? IconArrowDown : IconArrowUp } />
50 44
                 </TouchableOpacity>
51 45
                 <Text style = { styles.listTile as TextStyle }>
52
-                    {
53
-                        title
54
-                    }
46
+                    { title }
55 47
                 </Text>
56 48
             </TouchableOpacity>
57
-            {
58
-                !collapsed && children
59
-            }
49
+            { !collapsed && children }
60 50
         </View>
61 51
     );
62 52
 };

+ 26
- 40
react/features/participants-pane/components/native/LobbyParticipantList.tsx Просмотреть файл

@@ -1,70 +1,56 @@
1 1
 import React, { useCallback } from 'react';
2 2
 import { useTranslation } from 'react-i18next';
3
-import { ScrollView, Text, TextStyle, View, ViewStyle } from 'react-native';
3
+import { Text, TextStyle, View, ViewStyle } from 'react-native';
4 4
 import { useDispatch, useSelector } from 'react-redux';
5 5
 
6 6
 import Button from '../../../base/ui/components/native/Button';
7
-import { BUTTON_TYPES } from '../../../base/ui/constants.native';
7
+import { BUTTON_MODES, BUTTON_TYPES } from '../../../base/ui/constants.native';
8 8
 import { admitMultiple } from '../../../lobby/actions.native';
9 9
 import { getKnockingParticipants, getLobbyEnabled } from '../../../lobby/functions';
10 10
 
11
-import CollapsibleList from './CollapsibleList';
12 11
 import { LobbyParticipantItem } from './LobbyParticipantItem';
13 12
 import styles from './styles';
14 13
 
15 14
 const LobbyParticipantList = () => {
15
+    const dispatch = useDispatch();
16 16
     const lobbyEnabled = useSelector(getLobbyEnabled);
17 17
     const participants = useSelector(getKnockingParticipants);
18
-
19
-    const dispatch = useDispatch();
20 18
     const admitAll = useCallback(() =>
21 19
         dispatch(admitMultiple(participants)),
22 20
         [ dispatch, participants ]);
23 21
     const { t } = useTranslation();
22
+    const title = t('participantsPane.headings.waitingLobby',
23
+        { count: participants.length });
24 24
 
25 25
     if (!lobbyEnabled || !participants.length) {
26 26
         return null;
27 27
     }
28 28
 
29
-    const title = (
30
-        <View style = { styles.lobbyListDetails as ViewStyle } >
31
-            <Text style = { styles.lobbyListDescription as TextStyle }>
32
-                { t('participantsPane.headings.waitingLobby',
33
-                        { count: participants.length }) }
34
-            </Text>
35
-            {
36
-                participants.length > 1 && (
37
-                    <Button
38
-                        accessibilityLabel = 'lobby.admitAll'
39
-                        labelKey = 'lobby.admitAll'
40
-                        labelStyle = { styles.admitAllButtonLabel }
41
-                        onClick = { admitAll }
42
-                        type = { BUTTON_TYPES.TERTIARY } />
43
-                )
44
-            }
45
-        </View>
46
-    );
47
-
48
-    // Regarding the fact that we have 3 sections, we apply
49
-    // a certain height percentage for every section in order for all to fit
50
-    // inside the participants pane container
51
-    const style = participants.length > 1 && styles.lobbyListContent;
52
-
53 29
     return (
54
-        <CollapsibleList
55
-            title = { title }>
56
-            <ScrollView
57
-                bounces = { false }
58
-                style = { style } >
30
+        <>
31
+            <View style = { styles.lobbyListDetails as ViewStyle } >
32
+                <Text style = { styles.lobbyListDescription as TextStyle }>
33
+                    { title }
34
+                </Text>
59 35
                 {
60
-                    participants.map(p => (
61
-                        <LobbyParticipantItem
62
-                            key = { p.id }
63
-                            participant = { p } />)
36
+                    participants.length > 1 && (
37
+                        <Button
38
+                            accessibilityLabel = 'lobby.admitAll'
39
+                            labelKey = 'lobby.admitAll'
40
+                            mode = { BUTTON_MODES.TEXT }
41
+                            onClick = { admitAll }
42
+                            type = { BUTTON_TYPES.PRIMARY } />
64 43
                     )
65 44
                 }
66
-            </ScrollView>
67
-        </CollapsibleList>
45
+            </View>
46
+            {
47
+                participants.map(p => (
48
+                    <LobbyParticipantItem
49
+                        key = { p.id }
50
+                        participant = { p } />)
51
+                )
52
+            }
53
+        </>
68 54
     );
69 55
 };
70 56
 

+ 90
- 302
react/features/participants-pane/components/native/MeetingParticipantList.tsx Просмотреть файл

@@ -1,11 +1,9 @@
1
-import React, { PureComponent } from 'react';
2
-import { WithTranslation } from 'react-i18next';
3
-import { FlatList, Text } from 'react-native';
4
-import { connect } from 'react-redux';
1
+import React, { useCallback, useState } from 'react';
2
+import { useTranslation } from 'react-i18next';
3
+import { FlatList, Text, TextStyle, View } from 'react-native';
4
+import { useDispatch, useSelector } from 'react-redux';
5 5
 
6
-import { IReduxState, IStore } from '../../../app/types';
7
-import { openSheet } from '../../../base/dialog/actions';
8
-import { translate } from '../../../base/i18n/functions';
6
+import { IReduxState } from '../../../app/types';
9 7
 import Icon from '../../../base/icons/components/Icon';
10 8
 import { IconAddUser } from '../../../base/icons/svg';
11 9
 import {
@@ -24,155 +22,33 @@ import {
24 22
 } from '../../../breakout-rooms/functions';
25 23
 import { doInvitePeople } from '../../../invite/actions.native';
26 24
 import { getInviteOthersControl } from '../../../share-room/functions';
27
-import {
28
-    isCurrentRoomRenamable,
29
-    participantMatchesSearch,
30
-    shouldRenderInviteButton
31
-} from '../../functions';
32
-import { BREAKOUT_CONTEXT_MENU_ACTIONS } from '../../types';
33
-import BreakoutRoomContextMenu from '../breakout-rooms/components/native/BreakoutRoomContextMenu';
25
+import { participantMatchesSearch, shouldRenderInviteButton } from '../../functions';
34 26
 
35
-import CollapsibleList from './CollapsibleList';
36 27
 import MeetingParticipantItem from './MeetingParticipantItem';
37 28
 import styles from './styles';
38 29
 
39 30
 
40
-interface IProps extends WithTranslation {
41
-
42
-    /**
43
-     * Current breakout room, if we are in one.
44
-     */
45
-    _currentRoom: any;
46
-
47
-    /**
48
-     * Control for invite other button.
49
-     */
50
-    _inviteOthersControl: any;
51
-
52
-    /**
53
-     * Checks if add-people feature is enabled.
54
-     */
55
-    _isAddPeopleFeatureEnabled: boolean;
56
-
57
-    /**
58
-     * Indicates whether the room that is currently joined can be renamed.
59
-     */
60
-    _isCurrentRoomRenamable: boolean;
61
-
62
-    /**
63
-     * The local participant.
64
-     */
65
-    _localParticipant: any;
66
-
67
-    /**
68
-     * The number of participants in the conference.
69
-     */
70
-    _participantsCount: number;
71
-
72
-    /**
73
-     * The remote participants.
74
-     */
75
-    _remoteParticipants: Map<string, Object>;
76
-
77
-    /**
78
-     * Whether or not to show the invite button.
79
-     */
80
-    _showInviteButton: boolean;
81
-
82
-    /**
83
-     * The remote participants.
84
-     */
85
-    _sortedRemoteParticipants: string[];
86
-
87
-    /**
88
-     * The current visitors count if any.
89
-     */
90
-    _visitorsCount: number;
91
-
92
-    /**
93
-     * List of breakout rooms that were created.
94
-     */
95
-    breakoutRooms: ArrayLike<any>;
96
-
97
-    /**
98
-     * The redux dispatch function.
99
-     */
100
-    dispatch: IStore['dispatch'];
101
-
102
-    /**
103
-     * Is the local participant moderator?
104
-     */
105
-    isLocalModerator: boolean;
106
-
107
-    /**
108
-     * List of participants waiting in lobby.
109
-     */
110
-    lobbyParticipants: ArrayLike<any>;
111
-
112
-    /**
113
-     * Participants search string.
114
-     */
115
-    searchString: string;
116
-
117
-    /**
118
-     * Function to update the search string.
119
-     */
120
-    setSearchString: Function;
121
-}
122
-
123
-/**
124
- *  The meeting participant list component.
125
- */
126
-class MeetingParticipantList extends PureComponent<IProps> {
127
-
128
-    /**
129
-     * Creates new MeetingParticipantList instance.
130
-     *
131
-     * @param {IProps} props - The props of the component.
132
-     */
133
-    constructor(props: IProps) {
134
-        super(props);
135
-
136
-        this._keyExtractor = this._keyExtractor.bind(this);
137
-        this._onInvite = this._onInvite.bind(this);
138
-        this._openContextMenu = this._openContextMenu.bind(this);
139
-        this._renderParticipant = this._renderParticipant.bind(this);
140
-        this._onSearchStringChange = this._onSearchStringChange.bind(this);
141
-    }
142
-
143
-    /**
144
-     * Returns a key for a passed item of the list.
145
-     *
146
-     * @param {string} item - The user ID.
147
-     * @returns {string} - The user ID.
148
-     */
149
-    _keyExtractor(item: string) {
150
-        return item;
151
-    }
152
-
153
-    /**
154
-     * Handles ivite button presses.
155
-     *
156
-     * @returns {void}
157
-     */
158
-    _onInvite() {
159
-        const { _isAddPeopleFeatureEnabled, dispatch } = this.props;
160
-
161
-        setShareDialogVisiblity(_isAddPeopleFeatureEnabled, dispatch);
162
-
31
+const MeetingParticipantList = () => {
32
+    const currentRoomId = useSelector(getCurrentRoomId);
33
+    const currentRoom = useSelector(getBreakoutRooms)[currentRoomId];
34
+    const dispatch = useDispatch();
35
+    const inviteOthersControl = useSelector(getInviteOthersControl);
36
+    const isAddPeopleFeatureEnabled = useSelector(addPeopleFeatureControl);
37
+    const keyExtractor
38
+        = useCallback((e: undefined, i: number) => i.toString(), []);
39
+    const localParticipant = useSelector(getLocalParticipant);
40
+    const onInvite = useCallback(() => {
41
+        setShareDialogVisiblity(isAddPeopleFeatureEnabled, dispatch);
163 42
         dispatch(doInvitePeople());
164
-    }
165
-
166
-    /**
167
-     * Renders a participant.
168
-     *
169
-     * @param {Object} flatListItem - Information about the item to be rendered.
170
-     * @param {string} flatListItem.item - The ID of the participant.
171
-     * @returns {ReactElement}
172
-     */
173
-    _renderParticipant({ item/* , index, separators */ }: any) {
174
-        const { _localParticipant, _remoteParticipants, searchString } = this.props;
175
-        const participant = item === _localParticipant?.id ? _localParticipant : _remoteParticipants.get(item);
43
+    }, [ dispatch ]);
44
+    const [ searchString, setSearchString ] = useState('');
45
+    const onSearchStringChange = useCallback((text: string) =>
46
+        setSearchString(text), []);
47
+    const participantsCount = useSelector(getParticipantCountWithFake);
48
+    const remoteParticipants = useSelector(getRemoteParticipants);
49
+    const renderParticipant = ({ item/* , index, separators */ }: any) => {
50
+        const participant = item === localParticipant?.id
51
+            ? localParticipant : remoteParticipants.get(item);
176 52
 
177 53
         if (participantMatchesSearch(participant, searchString)) {
178 54
             return (
@@ -183,156 +59,68 @@ class MeetingParticipantList extends PureComponent<IProps> {
183 59
         }
184 60
 
185 61
         return null;
186
-    }
187
-
188
-    /**
189
-     * Handles search string changes.
190
-     *
191
-     * @param {string} text - New value of the search string.
192
-     * @returns {void}
193
-     */
194
-    _onSearchStringChange(text: string) {
195
-        this.props.setSearchString(text);
196
-    }
197
-
198
-    /**
199
-     * Opens the context menu to rename the current breakout room.
200
-     *
201
-     * @returns {void}
202
-     */
203
-    _openContextMenu() {
204
-        this.props.dispatch(openSheet(BreakoutRoomContextMenu, {
205
-            room: this.props._currentRoom,
206
-            actions: [ BREAKOUT_CONTEXT_MENU_ACTIONS.RENAME ]
207
-        }));
208
-    }
209
-
210
-    /**
211
-     * Implements React's {@link Component#render()}.
212
-     *
213
-     * @inheritdoc
214
-     * @returns {ReactElement}
215
-     */
216
-    render() {
217
-        const {
218
-            _currentRoom,
219
-            _isCurrentRoomRenamable,
220
-            _inviteOthersControl,
221
-            _localParticipant,
222
-            _participantsCount,
223
-            _showInviteButton,
224
-            _sortedRemoteParticipants,
225
-            _visitorsCount,
226
-            breakoutRooms,
227
-            isLocalModerator,
228
-            lobbyParticipants,
229
-            t
230
-        } = this.props;
231
-        const title = _currentRoom?.name
232
-            ? `${_currentRoom.name} (${_participantsCount})`
233
-            : t('participantsPane.headings.participantsList',
234
-                { count: _participantsCount });
235
-
236
-        // Regarding the fact that we have 3 sections, we apply
237
-        // a certain height percentage for every section in order for all to fit
238
-        // inside the participants pane container
239
-        // If there are only meeting participants available,
240
-        // we take the full container height
241
-        const onlyMeetingParticipants
242
-            = breakoutRooms?.length === 0 && lobbyParticipants?.length === 0;
243
-        const containerStyleModerator
244
-            = onlyMeetingParticipants
245
-                ? styles.meetingListFullContainer : styles.meetingListContainer;
246
-        const containerStyle
247
-            = isLocalModerator
248
-                ? containerStyleModerator : styles.notLocalModeratorContainer;
249
-        const finalContainerStyle
250
-            = _participantsCount > 6 ? containerStyle : undefined;
251
-        const { color, shareDialogVisible } = _inviteOthersControl;
252
-        const _visitorsLabelText = _visitorsCount > 0
253
-            ? t('participantsPane.headings.visitors', { count: _visitorsCount })
254
-            : undefined;
255
-        const onLongPress = _isCurrentRoomRenamable ? this._openContextMenu : undefined;
256
-
257
-        return (
258
-            <>
259
-                { _visitorsCount > 0 && <Text style = { styles.visitorsLabel }>{ _visitorsLabelText }</Text>
260
-                }
261
-                <CollapsibleList
262
-                    containerStyle = { finalContainerStyle }
263
-                    onLongPress = { onLongPress }
264
-                    title = { title }>
265
-                    {
266
-                        _showInviteButton
267
-                        && <Button
268
-                            accessibilityLabel = 'participantsPane.actions.invite'
269
-                            disabled = { shareDialogVisible }
270
-                            // eslint-disable-next-line react/jsx-no-bind
271
-                            icon = { () => (
272
-                                <Icon
273
-                                    color = { color }
274
-                                    size = { 20 }
275
-                                    src = { IconAddUser } />
276
-                            ) }
277
-                            labelKey = 'participantsPane.actions.invite'
278
-                            onClick = { this._onInvite }
279
-                            style = { styles.inviteButton }
280
-                            type = { BUTTON_TYPES.PRIMARY } />
281
-                    }
282
-                    <Input
283
-                        clearable = { true }
284
-                        customStyles = {{
285
-                            container: styles.inputContainer,
286
-                            input: styles.centerInput }}
287
-                        onChange = { this._onSearchStringChange }
288
-                        placeholder = { t('participantsPane.search') }
289
-                        value = { this.props.searchString } />
290
-                    <FlatList
291
-                        bounces = { false }
292
-                        data = { [ _localParticipant?.id, ..._sortedRemoteParticipants ] }
293
-                        horizontal = { false }
294
-                        keyExtractor = { this._keyExtractor }
295
-                        renderItem = { this._renderParticipant }
296
-                        scrollEnabled = { true }
297
-                        showsHorizontalScrollIndicator = { false }
298
-                        windowSize = { 2 } />
299
-                </CollapsibleList>
300
-            </>
301
-        );
302
-    }
303
-}
304
-
305
-/**
306
- * Maps (parts of) the redux state to the associated props for this component.
307
- *
308
- * @param {Object} state - The Redux state.
309
- * @private
310
- * @returns {IProps}
311
- */
312
-function _mapStateToProps(state: IReduxState) {
313
-    const _participantsCount = getParticipantCountWithFake(state);
314
-    const { remoteParticipants } = state['features/filmstrip'];
315
-    const { shareDialogVisible } = state['features/share-room'];
316
-    const _inviteOthersControl = getInviteOthersControl(state);
317
-    const _isAddPeopleFeatureEnabled = addPeopleFeatureControl(state);
318
-    const _showInviteButton = shouldRenderInviteButton(state);
319
-    const _remoteParticipants = getRemoteParticipants(state);
320
-    const currentRoomId = getCurrentRoomId(state);
321
-    const _currentRoom = getBreakoutRooms(state)[currentRoomId];
322
-
323
-    return {
324
-        _currentRoom,
325
-        _isAddPeopleFeatureEnabled,
326
-        _isCurrentRoomRenamable: isCurrentRoomRenamable(state),
327
-        _inviteOthersControl,
328
-        _participantsCount,
329
-        _remoteParticipants,
330
-        _showInviteButton,
331
-        _sortedRemoteParticipants: remoteParticipants,
332
-        _localParticipant: getLocalParticipant(state),
333
-        _shareDialogVisible: shareDialogVisible,
334
-        _visitorsCount: state['features/visitors'].count || 0
335 62
     };
336
-}
337
-
338
-export default translate(connect(_mapStateToProps)(MeetingParticipantList));
63
+    const showInviteButton = useSelector(shouldRenderInviteButton);
64
+    const sortedRemoteParticipants = useSelector(
65
+        (state: IReduxState) => state['features/filmstrip'].remoteParticipants);
66
+    const { t } = useTranslation();
67
+    const title = currentRoom?.name
68
+        ? `${currentRoom.name} (${participantsCount})`
69
+        : t('participantsPane.headings.participantsList',
70
+            { count: participantsCount });
71
+    const { color, shareDialogVisible } = inviteOthersControl;
72
+    const visitorsCount = useSelector((state: IReduxState) => state['features/visitors'].count || 0);
73
+    const visitorsLabelText = visitorsCount > 0
74
+        ? t('participantsPane.headings.visitors', { count: visitorsCount })
75
+        : undefined;
76
+
77
+    return (
78
+        <View style = { styles.meetingListContainer }>
79
+            {
80
+                visitorsCount > 0
81
+                && <Text style = { styles.visitorsLabel }>
82
+                    { visitorsLabelText }
83
+                </Text>
84
+            }
85
+            <Text
86
+                style = { styles.meetingListDescription as TextStyle }>
87
+                { title }
88
+            </Text>
89
+            {
90
+                showInviteButton
91
+                && <Button
92
+                    accessibilityLabel = 'participantsPane.actions.invite'
93
+                    disabled = { shareDialogVisible }
94
+
95
+                    // eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow
96
+                    icon = { () => (
97
+                        <Icon
98
+                            color = { color }
99
+                            size = { 20 }
100
+                            src = { IconAddUser } />
101
+                    ) }
102
+                    labelKey = 'participantsPane.actions.invite'
103
+                    onClick = { onInvite }
104
+                    style = { styles.inviteButton }
105
+                    type = { BUTTON_TYPES.PRIMARY } />
106
+            }
107
+            <Input
108
+                clearable = { true }
109
+                customStyles = {{
110
+                    container: styles.inputContainer,
111
+                    input: styles.centerInput }}
112
+                onChange = { onSearchStringChange }
113
+                placeholder = { t('participantsPane.search') }
114
+                value = { searchString } />
115
+            <FlatList
116
+                data = { [ localParticipant?.id, ...sortedRemoteParticipants ] as Array<any> }
117
+                keyExtractor = { keyExtractor }
118
+
119
+                /* eslint-disable react/jsx-no-bind */
120
+                renderItem = { renderParticipant }
121
+                windowSize = { 2 } />
122
+        </View>
123
+    );
124
+};
125
+
126
+export default MeetingParticipantList;

+ 3
- 1
react/features/participants-pane/components/native/ParticipantItem.tsx Просмотреть файл

@@ -87,6 +87,8 @@ function ParticipantItem({
87 87
 }: IProps) {
88 88
 
89 89
     const { t } = useTranslation();
90
+    const participantNameContainerStyles
91
+        = isKnockingParticipant ? styles.lobbyParticipantNameContainer : styles.participantNameContainer;
90 92
 
91 93
     return (
92 94
         <View style = { styles.participantContainer as ViewStyle } >
@@ -102,7 +104,7 @@ function ParticipantItem({
102 104
                         styles.participantDetailsContainer,
103 105
                         raisedHand && styles.participantDetailsContainerRaisedHand
104 106
                     ] }>
105
-                    <View style = { styles.participantNameContainer as ViewStyle }>
107
+                    <View style = { participantNameContainerStyles as ViewStyle }>
106 108
                         <Text
107 109
                             numberOfLines = { 1 }
108 110
                             style = { styles.participantName as TextStyle }>

+ 20
- 49
react/features/participants-pane/components/native/ParticipantsPane.tsx Просмотреть файл

@@ -1,74 +1,45 @@
1
-import React, { useState } from 'react';
1
+import React, { useCallback } from 'react';
2
+import { FlatList } from 'react-native';
2 3
 import { useSelector } from 'react-redux';
3 4
 
4
-import { IReduxState } from '../../../app/types';
5 5
 import JitsiScreen from '../../../base/modal/components/JitsiScreen';
6 6
 import { isLocalParticipantModerator } from '../../../base/participants/functions';
7
-import { equals } from '../../../base/redux/functions';
8
-import {
9
-    getBreakoutRooms,
10
-    getCurrentRoomId,
11
-    isAddBreakoutRoomButtonVisible,
12
-    isAutoAssignParticipantsVisible,
13
-    isInBreakoutRoom
14
-} from '../../../breakout-rooms/functions';
15
-import { getKnockingParticipants } from '../../../lobby/functions';
16
-import AddBreakoutRoomButton from '../breakout-rooms/components/native/AddBreakoutRoomButton';
17
-import AutoAssignButton from '../breakout-rooms/components/native/AutoAssignButton';
18
-import { CollapsibleRoom } from '../breakout-rooms/components/native/CollapsibleRoom';
19
-import LeaveBreakoutRoomButton from '../breakout-rooms/components/native/LeaveBreakoutRoomButton';
20 7
 
21 8
 import LobbyParticipantList from './LobbyParticipantList';
22 9
 import MeetingParticipantList from './MeetingParticipantList';
23 10
 import ParticipantsPaneFooter from './ParticipantsPaneFooter';
24 11
 import styles from './styles';
25 12
 
13
+
26 14
 /**
27 15
  * Participants pane.
28 16
  *
29 17
  * @returns {React$Element<any>}
30 18
  */
31 19
 const ParticipantsPane = () => {
32
-    const [ searchString, setSearchString ] = useState('');
33 20
     const isLocalModerator = useSelector(isLocalParticipantModerator);
34
-    const { conference } = useSelector((state: IReduxState) => state['features/base/conference']);
35
-    const _isBreakoutRoomsSupported = conference?.getBreakoutRooms()?.isSupported();
36
-    const currentRoomId = useSelector(getCurrentRoomId);
37
-    const rooms = Object.values(useSelector(getBreakoutRooms, equals))
38
-        .filter(room => room.id !== currentRoomId)
39
-        .sort((p1, p2) => (p1?.name || '').localeCompare(p2?.name || ''));
40
-    const inBreakoutRoom = useSelector(isInBreakoutRoom);
41
-    const showAddBreakoutRoom = useSelector(isAddBreakoutRoomButtonVisible);
42
-    const showAutoAssign = useSelector(isAutoAssignParticipantsVisible);
43
-    const lobbyParticipants = useSelector(getKnockingParticipants);
21
+    const keyExtractor
22
+        = useCallback((e: undefined, i: number) => i.toString(), []);
44 23
 
45 24
     return (
46 25
         <JitsiScreen
47 26
             footerComponent = { isLocalModerator ? ParticipantsPaneFooter : undefined }
48 27
             style = { styles.participantsPaneContainer }>
49
-            <LobbyParticipantList />
50
-            <MeetingParticipantList
51
-                breakoutRooms = { rooms }
52
-                isLocalModerator = { isLocalModerator }
53
-                lobbyParticipants = { lobbyParticipants }
54
-                searchString = { searchString }
55
-                setSearchString = { setSearchString } />
56
-            {
57
-                showAutoAssign && <AutoAssignButton />
58
-            }
59
-            {
60
-                inBreakoutRoom && <LeaveBreakoutRoomButton />
61
-            }
62
-            {
63
-                _isBreakoutRoomsSupported
64
-                && rooms.map(room => (<CollapsibleRoom
65
-                    key = { room.id }
66
-                    room = { room }
67
-                    searchString = { searchString } />))
68
-            }
69
-            {
70
-                showAddBreakoutRoom && <AddBreakoutRoomButton />
71
-            }
28
+
29
+            { /* Fixes warning regarding nested lists */ }
30
+            <FlatList
31
+
32
+                // eslint-disable-next-line react/jsx-no-bind
33
+                ListHeaderComponent = { () => (
34
+                    <>
35
+                        <LobbyParticipantList />
36
+                        <MeetingParticipantList />
37
+                    </>
38
+                ) }
39
+                data = { [] as ReadonlyArray<undefined> }
40
+                keyExtractor = { keyExtractor }
41
+                renderItem = { null }
42
+                windowSize = { 2 } />
72 43
         </JitsiScreen>
73 44
     );
74 45
 };

+ 50
- 19
react/features/participants-pane/components/native/ParticipantsPaneFooter.tsx Просмотреть файл

@@ -1,13 +1,21 @@
1
+/* eslint-disable lines-around-comment */
2
+
1 3
 import React, { useCallback } from 'react';
2 4
 import { View, ViewStyle } from 'react-native';
3 5
 import { useDispatch, useSelector } from 'react-redux';
4 6
 
7
+import { IReduxState } from '../../../app/types';
5 8
 import { openDialog, openSheet } from '../../../base/dialog/actions';
6
-import { IconDotsHorizontal } from '../../../base/icons/svg';
9
+import Icon from '../../../base/icons/components/Icon';
10
+import { IconDotsHorizontal, IconRingGroup } from '../../../base/icons/svg';
11
+import BaseTheme from '../../../base/ui/components/BaseTheme.native';
7 12
 import Button from '../../../base/ui/components/native/Button';
8 13
 import IconButton from '../../../base/ui/components/native/IconButton';
9 14
 import { BUTTON_TYPES } from '../../../base/ui/constants.native';
10
-// eslint-disable-next-line lines-around-comment
15
+import {
16
+    navigate
17
+} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
18
+import { screen } from '../../../mobile/navigation/routes';
11 19
 // @ts-ignore
12 20
 import MuteEveryoneDialog from '../../../video-menu/components/native/MuteEveryoneDialog';
13 21
 import { isMoreActionsVisible, isMuteAllVisible } from '../../functions';
@@ -23,6 +31,9 @@ import styles from './styles';
23 31
  */
24 32
 const ParticipantsPaneFooter = (): JSX.Element => {
25 33
     const dispatch = useDispatch();
34
+    const isBreakoutRoomsSupported = useSelector((state: IReduxState) =>
35
+        state['features/base/conference'].conference?.getBreakoutRooms()?.isSupported()
36
+    );
26 37
     const openMoreMenu = useCallback(() => dispatch(openSheet(ContextMenuMore)), [ dispatch ]);
27 38
     const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
28 39
         [ dispatch ]);
@@ -30,25 +41,45 @@ const ParticipantsPaneFooter = (): JSX.Element => {
30 41
     const showMuteAll = useSelector(isMuteAllVisible);
31 42
 
32 43
     return (
33
-        <View style = { styles.participantsPaneFooter as ViewStyle }>
44
+        <View style = { styles.participantsPaneFooterContainer as ViewStyle }>
34 45
             {
35
-                showMuteAll && (
36
-                    <Button
37
-                        accessibilityLabel = 'participantsPane.actions.muteAll'
38
-                        labelKey = 'participantsPane.actions.muteAll'
39
-                        onClick = { muteAll }
40
-                        type = { BUTTON_TYPES.SECONDARY } />
41
-                )
42
-            }
43
-            {
44
-                showMoreActions && (
45
-                    <IconButton
46
-                        onPress = { openMoreMenu }
47
-                        src = { IconDotsHorizontal }
48
-                        style = { styles.moreButton }
49
-                        type = { BUTTON_TYPES.SECONDARY } />
50
-                )
46
+                isBreakoutRoomsSupported
47
+                && <Button
48
+                    accessibilityLabel = 'participantsPane.actions.breakoutRooms'
49
+                    // eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow
50
+                    icon = { () => (
51
+                        <Icon
52
+                            color = { BaseTheme.palette.icon04 }
53
+                            size = { 20 }
54
+                            src = { IconRingGroup } />
55
+                    ) }
56
+                    labelKey = 'participantsPane.actions.breakoutRooms'
57
+                    // eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow
58
+                    onClick = { () => navigate(screen.conference.breakoutRooms) }
59
+                    style = { styles.breakoutRoomsButton }
60
+                    type = { BUTTON_TYPES.SECONDARY } />
51 61
             }
62
+
63
+            <View style = { styles.participantsPaneFooter as ViewStyle }>
64
+                {
65
+                    showMuteAll && (
66
+                        <Button
67
+                            accessibilityLabel = 'participantsPane.actions.muteAll'
68
+                            labelKey = 'participantsPane.actions.muteAll'
69
+                            onClick = { muteAll }
70
+                            type = { BUTTON_TYPES.SECONDARY } />
71
+                    )
72
+                }
73
+                {
74
+                    showMoreActions && (
75
+                        <IconButton
76
+                            onPress = { openMoreMenu }
77
+                            src = { IconDotsHorizontal }
78
+                            style = { styles.moreButton }
79
+                            type = { BUTTON_TYPES.SECONDARY } />
80
+                    )
81
+                }
82
+            </View>
52 83
         </View>
53 84
     );
54 85
 };

+ 44
- 70
react/features/participants-pane/components/native/styles.ts Просмотреть файл

@@ -5,11 +5,13 @@ import BaseTheme from '../../../base/ui/components/BaseTheme.native';
5 5
  */
6 6
 const participantListDescription = {
7 7
     ...BaseTheme.typography.heading6,
8
-    color: BaseTheme.palette.text02,
9
-    paddingBottom: BaseTheme.spacing[3],
10
-    paddingTop: BaseTheme.spacing[3],
8
+    color: BaseTheme.palette.text01,
9
+    fontSize: 15,
10
+    fontWeight: 'bold',
11
+    marginLeft: BaseTheme.spacing[2],
12
+    paddingVertical: BaseTheme.spacing[2],
11 13
     position: 'relative',
12
-    width: '55%'
14
+    width: '70%'
13 15
 };
14 16
 
15 17
 /**
@@ -50,29 +52,18 @@ const contextMenuItem = {
50 52
     marginLeft: BaseTheme.spacing[3]
51 53
 };
52 54
 
55
+const participantNameContainer = {
56
+    display: 'flex',
57
+    flexDirection: 'row',
58
+    overflow: 'hidden',
59
+    paddingLeft: BaseTheme.spacing[3]
60
+};
61
+
53 62
 /**
54 63
  * The styles of the native components of the feature {@code participants}.
55 64
  */
56 65
 export default {
57 66
 
58
-    participantActionsButtonAdmit: {
59
-        marginRight: BaseTheme.spacing[3],
60
-        position: 'absolute',
61
-        right: 88
62
-    },
63
-
64
-    participantActionsButtonReject: {
65
-        marginRight: BaseTheme.spacing[3],
66
-        position: 'absolute',
67
-        right: 0
68
-    },
69
-
70
-    admitAllButtonLabel: {
71
-        color: BaseTheme.palette.link01,
72
-        marginRight: BaseTheme.spacing[6],
73
-        marginTop: 14
74
-    },
75
-
76 67
     participantsBadge: {
77 68
         backgroundColor: BaseTheme.palette.ui03,
78 69
         borderRadius: BaseTheme.spacing[2],
@@ -126,16 +117,18 @@ export default {
126 117
     },
127 118
 
128 119
     participantNameContainer: {
129
-        display: 'flex',
130
-        flexDirection: 'row',
131
-        overflow: 'hidden',
132
-        paddingLeft: BaseTheme.spacing[3],
120
+        ...participantNameContainer,
133 121
         width: '100%'
134 122
     },
135 123
 
124
+    lobbyParticipantNameContainer: {
125
+        ...participantNameContainer,
126
+        width: '40%'
127
+    },
128
+
136 129
     participantName: {
137
-        overflow: 'hidden',
138
-        color: BaseTheme.palette.text01
130
+        color: BaseTheme.palette.text01,
131
+        overflow: 'hidden'
139 132
     },
140 133
 
141 134
     moderatorLabel: {
@@ -171,10 +164,6 @@ export default {
171 164
         color: BaseTheme.palette.uiBackground
172 165
     },
173 166
 
174
-    lobbyListContent: {
175
-        height: '24%'
176
-    },
177
-
178 167
     lobbyButtonAdmit: {
179 168
         position: 'absolute',
180 169
         right: 16
@@ -182,76 +171,61 @@ export default {
182 171
 
183 172
     lobbyButtonReject: {
184 173
         position: 'absolute',
185
-        right: 104
174
+        right: 112
186 175
     },
187 176
 
188 177
     lobbyListDescription: {
189
-        fontSize: 15,
190
-        color: BaseTheme.palette.text01,
191
-        fontWeight: 'bold',
192
-        marginTop: BaseTheme.spacing[2]
178
+        ...participantListDescription
193 179
     },
194 180
 
195 181
     lobbyListDetails: {
196 182
         alignItems: 'center',
197 183
         display: 'flex',
198 184
         flexDirection: 'row',
199
-        justifyContent: 'space-between',
200
-        overflow: 'hidden',
201
-        position: 'relative',
202
-        width: '100%'
203
-    },
204
-
205
-    notLocalModeratorContainer: {
206
-        height: '100%'
185
+        justifyContent: 'space-between'
207 186
     },
208 187
 
209 188
     meetingListContainer: {
210
-        height: '56%'
211
-    },
212
-
213
-    meetingListFullContainer: {
214
-        height: '80%'
189
+        paddingHorizontal: BaseTheme.spacing[3]
215 190
     },
216 191
 
217 192
     meetingListDescription: {
218
-        ...participantListDescription,
219
-        marginLeft: BaseTheme.spacing[3]
220
-    },
221
-
222
-    collapsibleRoomContainer: {
223
-        height: '32%'
193
+        ...participantListDescription
224 194
     },
225 195
 
226 196
     participantsPaneContainer: {
227 197
         backgroundColor: BaseTheme.palette.ui01,
228 198
         flex: 1,
229
-        justifyContent: 'center'
199
+        flexDirection: 'column',
200
+        paddingVertical: BaseTheme.spacing[2]
230 201
     },
231 202
 
232
-    participantsPaneFooter: {
203
+    participantsPaneFooterContainer: {
233 204
         alignItems: 'center',
234
-        backgroundColor: BaseTheme.palette.ui01,
235 205
         bottom: 0,
236
-        flexDirection: 'row',
237
-        height: BaseTheme.spacing[12],
238
-        justifyContent: 'flex-end',
206
+        height: 128,
239 207
         left: 0,
240
-        right: 0,
241
-        position: 'absolute',
242
-        paddingBottom: BaseTheme.spacing[2],
243
-        paddingLeft: BaseTheme.spacing[3],
244
-        paddingRight: BaseTheme.spacing[3]
208
+        paddingHorizontal: BaseTheme.spacing[4],
209
+        right: 0
245 210
     },
246 211
 
247
-    headerCloseIcon: {
248
-        marginLeft: 12
212
+    participantsPaneFooter: {
213
+        display: 'flex',
214
+        flexDirection: 'row',
215
+        justifyContent: 'flex-end',
216
+        paddingBottom: BaseTheme.spacing[3],
217
+        width: '100%'
249 218
     },
250 219
 
251 220
     inviteButton: {
252 221
         marginLeft: BaseTheme.spacing[3],
253 222
         marginRight: BaseTheme.spacing[3],
254
-        marginVertical: BaseTheme.spacing[3]
223
+        marginVertical: BaseTheme.spacing[2]
224
+    },
225
+
226
+    breakoutRoomsButton: {
227
+        marginBottom: BaseTheme.spacing[2],
228
+        width: '100%'
255 229
     },
256 230
 
257 231
     moreButton: {

+ 1
- 1
react/features/participants-pane/functions.ts Просмотреть файл

@@ -286,7 +286,7 @@ export const isMuteAllVisible = (state: IReduxState) => {
286 286
 };
287 287
 
288 288
 /**
289
- * Returns true if reanming the currently joined breakout room is allowed and false otherwise.
289
+ * Returns true if renaming the currently joined breakout room is allowed and false otherwise.
290 290
  *
291 291
  * @param {IReduxState} state - The redux state.
292 292
  * @returns {boolean} - True if reanming the currently joined breakout room is allowed and false otherwise.

+ 13
- 0
react/features/toolbox/components/native/OverflowMenu.tsx Просмотреть файл

@@ -8,6 +8,8 @@ import { hideSheet } from '../../../base/dialog/actions';
8 8
 import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
9 9
 import { bottomSheetStyles } from '../../../base/dialog/components/native/styles';
10 10
 import SettingsButton from '../../../base/settings/components/native/SettingsButton';
11
+import BreakoutRoomsButton
12
+    from '../../../breakout-rooms/components/native/BreakoutRoomsButton';
11 13
 import SharedDocumentButton from '../../../etherpad/components/SharedDocumentButton.native';
12 14
 import ReactionMenu from '../../../reactions/components/native/ReactionMenu';
13 15
 import { isReactionsEnabled } from '../../../reactions/functions.any';
@@ -29,11 +31,17 @@ import OpenCarmodeButton from './OpenCarmodeButton';
29 31
 import RaiseHandButton from './RaiseHandButton';
30 32
 import ScreenSharingButton from './ScreenSharingButton';
31 33
 
34
+
32 35
 /**
33 36
  * The type of the React {@code Component} props of {@link OverflowMenu}.
34 37
  */
35 38
 interface IProps {
36 39
 
40
+    /**
41
+     * True if breakout rooms feature is available, false otherwise.
42
+     */
43
+    _isBreakoutRoomsSupported?: boolean;
44
+
37 45
     /**
38 46
      * True if the overflow menu is currently visible, false otherwise.
39 47
      */
@@ -103,6 +111,7 @@ class OverflowMenu extends PureComponent<IProps, IState> {
103 111
      */
104 112
     render() {
105 113
         const {
114
+            _isBreakoutRoomsSupported,
106 115
             _isSpeakerStatsDisabled,
107 116
             _reactionsEnabled,
108 117
             _width,
@@ -150,6 +159,7 @@ class OverflowMenu extends PureComponent<IProps, IState> {
150 159
                 {!toolbarButtons.has('screensharing') && <ScreenSharingButton { ...buttonProps } />}
151 160
                 {!_isSpeakerStatsDisabled && <SpeakerStatsButton { ...buttonProps } />}
152 161
                 {!toolbarButtons.has('tileview') && <TileViewButton { ...buttonProps } />}
162
+                {_isBreakoutRoomsSupported && <BreakoutRoomsButton { ...buttonProps } />}
153 163
                 {/* @ts-ignore */}
154 164
                 <Divider style = { styles.divider as ViewStyle } />
155 165
                 <ClosedCaptionButton { ...buttonProps } />
@@ -191,7 +201,10 @@ class OverflowMenu extends PureComponent<IProps, IState> {
191 201
  * @returns {IProps}
192 202
  */
193 203
 function _mapStateToProps(state: IReduxState) {
204
+    const { conference } = state['features/base/conference'];
205
+
194 206
     return {
207
+        _isBreakoutRoomsSupported: conference?.getBreakoutRooms()?.isSupported(),
195 208
         _isSpeakerStatsDisabled: isSpeakerStatsDisabled(state),
196 209
         _reactionsEnabled: isReactionsEnabled(state),
197 210
         _width: state['features/base/responsive-ui'].clientWidth

Загрузка…
Отмена
Сохранить