diff --git a/lang/main.json b/lang/main.json
index 8eac4ee7ee..7a0b006704 100644
--- a/lang/main.json
+++ b/lang/main.json
@@ -67,7 +67,8 @@
"renameBreakoutRoom": "Rename breakout room",
"sendToBreakoutRoom": "Send participant to:"
},
- "breakoutList": "breakout list",
+ "breakoutList": "Breakout list",
+ "buttonLabel": "Breakout rooms",
"defaultName": "Breakout room #{{index}}",
"hideParticipantList": "Hide participant list",
"mainRoom": "Main room",
@@ -76,7 +77,8 @@
"joinedMainRoom": "Joining the main room",
"joinedTitle": "Breakout Rooms"
},
- "showParticipantList": "Show participant list"
+ "showParticipantList": "Show participant list",
+ "title": "Breakout Rooms"
},
"calendarSync": {
"addMeetingURL": "Add a meeting link",
@@ -811,6 +813,7 @@
"askUnmute": "Ask to unmute",
"audioModeration": "Unmute themselves",
"blockEveryoneMicCamera": "Block everyone's mic and camera",
+ "breakoutRooms": "Breakout rooms",
"invite": "Invite Someone",
"moreModerationActions": "More moderation options",
"moreModerationControls": "More moderation controls",
@@ -1157,7 +1160,7 @@
"audioOnly": "Toggle audio only",
"audioRoute": "Select the sound device",
"boo": "Boo",
- "breakoutRoom": "Join/leave breakout room",
+ "breakoutRooms": "Breakout rooms",
"callQuality": "Manage video quality",
"carmode": "Car Mode",
"cc": "Toggle subtitles",
diff --git a/react/features/base/flags/constants.ts b/react/features/base/flags/constants.ts
index 38b8293328..17673e1681 100644
--- a/react/features/base/flags/constants.ts
+++ b/react/features/base/flags/constants.ts
@@ -23,6 +23,12 @@ export const AUDIO_MUTE_BUTTON_ENABLED = 'audio-mute.enabled';
*/
export const AUDIO_ONLY_BUTTON_ENABLED = 'audio-only.enabled';
+/**
+ * Flag indicating that the Breakout Rooms button in the overflow menu is enabled.
+ * Default: enabled (true).
+ */
+export const BREAKOUT_ROOMS_BUTTON_ENABLED = 'breakout-rooms.enabled';
+
/**
* Flag indicating if calendar integration should be enabled.
* Default: enabled (true) on Android, auto-detected on iOS.
diff --git a/react/features/participants-pane/components/breakout-rooms/components/native/AddBreakoutRoomButton.tsx b/react/features/breakout-rooms/components/native/AddBreakoutRoomButton.tsx
similarity index 76%
rename from react/features/participants-pane/components/breakout-rooms/components/native/AddBreakoutRoomButton.tsx
rename to react/features/breakout-rooms/components/native/AddBreakoutRoomButton.tsx
index c60930ed38..4a3b522a2c 100644
--- a/react/features/participants-pane/components/breakout-rooms/components/native/AddBreakoutRoomButton.tsx
+++ b/react/features/breakout-rooms/components/native/AddBreakoutRoomButton.tsx
@@ -1,9 +1,9 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
-import Button from '../../../../../base/ui/components/native/Button';
-import { BUTTON_TYPES } from '../../../../../base/ui/constants.native';
-import { createBreakoutRoom } from '../../../../../breakout-rooms/actions';
+import Button from '../../../base/ui/components/native/Button';
+import { BUTTON_TYPES } from '../../../base/ui/constants.native';
+import { createBreakoutRoom } from '../../actions';
import styles from './styles';
diff --git a/react/features/participants-pane/components/breakout-rooms/components/native/AutoAssignButton.tsx b/react/features/breakout-rooms/components/native/AutoAssignButton.tsx
similarity index 78%
rename from react/features/participants-pane/components/breakout-rooms/components/native/AutoAssignButton.tsx
rename to react/features/breakout-rooms/components/native/AutoAssignButton.tsx
index 12be92ed0d..085446a447 100644
--- a/react/features/participants-pane/components/breakout-rooms/components/native/AutoAssignButton.tsx
+++ b/react/features/breakout-rooms/components/native/AutoAssignButton.tsx
@@ -1,9 +1,9 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
-import Button from '../../../../../base/ui/components/native/Button';
-import { BUTTON_TYPES } from '../../../../../base/ui/constants.native';
-import { autoAssignToBreakoutRooms } from '../../../../../breakout-rooms/actions';
+import Button from '../../../base/ui/components/native/Button';
+import { BUTTON_TYPES } from '../../../base/ui/constants.native';
+import { autoAssignToBreakoutRooms } from '../../actions';
import styles from './styles';
diff --git a/react/features/participants-pane/components/breakout-rooms/components/native/BreakoutRoomContextMenu.tsx b/react/features/breakout-rooms/components/native/BreakoutRoomContextMenu.tsx
similarity index 80%
rename from react/features/participants-pane/components/breakout-rooms/components/native/BreakoutRoomContextMenu.tsx
rename to react/features/breakout-rooms/components/native/BreakoutRoomContextMenu.tsx
index 06b624951c..47994b1cd3 100644
--- a/react/features/participants-pane/components/breakout-rooms/components/native/BreakoutRoomContextMenu.tsx
+++ b/react/features/breakout-rooms/components/native/BreakoutRoomContextMenu.tsx
@@ -4,22 +4,23 @@ import { TouchableOpacity, ViewStyle } from 'react-native';
import { Text } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux';
-import { createBreakoutRoomsEvent } from '../../../../../analytics/AnalyticsEvents';
-import { sendAnalytics } from '../../../../../analytics/functions';
-import { hideSheet, openDialog } from '../../../../../base/dialog/actions';
-import BottomSheet from '../../../../../base/dialog/components/native/BottomSheet';
-import Icon from '../../../../../base/icons/components/Icon';
-import { IconCloseLarge, IconEdit, IconRingGroup } from '../../../../../base/icons/svg';
-import { isLocalParticipantModerator } from '../../../../../base/participants/functions';
-import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../../../../breakout-rooms/actions';
-import { getBreakoutRoomsConfig } from '../../../../../breakout-rooms/functions';
-import { IRoom } from '../../../../../breakout-rooms/types';
-import { isBreakoutRoomRenameAllowed } from '../../../../functions';
-import { BREAKOUT_CONTEXT_MENU_ACTIONS as ACTIONS } from '../../../../types';
-import styles from '../../../native/styles';
+import { createBreakoutRoomsEvent } from '../../../analytics/AnalyticsEvents';
+import { sendAnalytics } from '../../../analytics/functions';
+import { hideSheet, openDialog } from '../../../base/dialog/actions';
+import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
+import Icon from '../../../base/icons/components/Icon';
+import { IconCloseLarge, IconEdit, IconRingGroup } from '../../../base/icons/svg';
+import { isLocalParticipantModerator } from '../../../base/participants/functions';
+import styles from '../../../participants-pane/components/native/styles';
+import { isBreakoutRoomRenameAllowed } from '../../../participants-pane/functions';
+import { BREAKOUT_CONTEXT_MENU_ACTIONS as ACTIONS } from '../../../participants-pane/types';
+import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../actions';
+import { getBreakoutRoomsConfig } from '../../functions';
+import { IRoom } from '../../types';
import BreakoutRoomNamePrompt from './BreakoutRoomNamePrompt';
+
/**
* An array with all possible breakout rooms actions.
*/
@@ -85,7 +86,8 @@ const BreakoutRoomContextMenu = ({ room, actions = ALL_ACTIONS }: IProps) => {
)
}
- {!room?.isMainRoom && actions.includes(ACTIONS.RENAME) && _isBreakoutRoomRenameAllowed
+ {
+ !room?.isMainRoom && actions.includes(ACTIONS.RENAME) && _isBreakoutRoomRenameAllowed
&&
@@ -95,7 +97,8 @@ const BreakoutRoomContextMenu = ({ room, actions = ALL_ACTIONS }: IProps) => {
{t('breakoutRooms.actions.rename')}
}
- {!room?.isMainRoom && isLocalModerator && actions.includes(ACTIONS.REMOVE)
+ {
+ !room?.isMainRoom && isLocalModerator && actions.includes(ACTIONS.REMOVE)
&& (room?.participants && Object.keys(room.participants).length > 0
? {
+ const currentRoomId = useSelector(getCurrentRoomId);
+ const inBreakoutRoom = useSelector(isInBreakoutRoom);
+ const isBreakoutRoomsSupported = useSelector((state: IReduxState) =>
+ state['features/base/conference'].conference?.getBreakoutRooms()?.isSupported());
+ const isLocalModerator = useSelector(isLocalParticipantModerator);
+ const keyExtractor = useCallback((e: undefined, i: number) => i.toString(), []);
+ const rooms = Object.values(useSelector(getBreakoutRooms, equals))
+ .filter(room => room.id !== currentRoomId)
+ .sort((p1, p2) => (p1?.name || '').localeCompare(p2?.name || ''));
+ const showAddBreakoutRoom = useSelector(isAddBreakoutRoomButtonVisible);
+ const showAutoAssign = useSelector(isAutoAssignParticipantsVisible);
+
+ return (
+
+
+ { /* Fixes warning regarding nested lists */ }
+ (
+ <>
+ { showAutoAssign && }
+ { inBreakoutRoom && }
+ {
+ isBreakoutRoomsSupported
+ && rooms.map(room => ())
+ }
+ >
+ ) }
+ data = { [] as ReadonlyArray }
+ keyExtractor = { keyExtractor }
+ renderItem = { null }
+ windowSize = { 2 } />
+
+ );
+};
+
+export default BreakoutRooms;
diff --git a/react/features/breakout-rooms/components/native/BreakoutRoomsButton.tsx b/react/features/breakout-rooms/components/native/BreakoutRoomsButton.tsx
new file mode 100644
index 0000000000..54363c56ce
--- /dev/null
+++ b/react/features/breakout-rooms/components/native/BreakoutRoomsButton.tsx
@@ -0,0 +1,53 @@
+import { connect } from 'react-redux';
+
+import { IReduxState } from '../../../app/types';
+import {
+ BREAKOUT_ROOMS_BUTTON_ENABLED
+} from '../../../base/flags/constants';
+import { getFeatureFlag } from '../../../base/flags/functions';
+import { translate } from '../../../base/i18n/functions';
+import { IconRingGroup } from '../../../base/icons/svg';
+import AbstractButton,
+{
+ IProps as AbstractButtonProps
+} from '../../../base/toolbox/components/AbstractButton';
+import {
+ navigate
+} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
+import { screen } from '../../../mobile/navigation/routes';
+
+
+/**
+ * Implements an {@link AbstractButton} to open the breakout room screen.
+ */
+class BreakoutRoomsButton extends AbstractButton {
+ accessibilityLabel = 'toolbar.accessibilityLabel.breakoutRooms';
+ icon = IconRingGroup;
+ label = 'breakoutRooms.buttonLabel';
+
+ /**
+ * Handles clicking / pressing the button and opens the breakout rooms screen.
+ *
+ * @private
+ * @returns {void}
+ */
+ _handleClick() {
+ return navigate(screen.conference.breakoutRooms);
+ }
+}
+
+/**
+ * Maps part of the redux state to the component's props.
+ *
+ * @param {IReduxState} state - The Redux state.
+ * @returns {Object}
+ */
+function _mapStateToProps(state: IReduxState) {
+ const enabled = getFeatureFlag(state, BREAKOUT_ROOMS_BUTTON_ENABLED, true);
+
+ return {
+ visible: enabled
+ };
+}
+
+export default translate(connect(_mapStateToProps)(BreakoutRoomsButton));
diff --git a/react/features/participants-pane/components/breakout-rooms/components/native/CollapsibleRoom.tsx b/react/features/breakout-rooms/components/native/CollapsibleRoom.tsx
similarity index 58%
rename from react/features/participants-pane/components/breakout-rooms/components/native/CollapsibleRoom.tsx
rename to react/features/breakout-rooms/components/native/CollapsibleRoom.tsx
index f71f787b9f..d379ab4406 100644
--- a/react/features/participants-pane/components/breakout-rooms/components/native/CollapsibleRoom.tsx
+++ b/react/features/breakout-rooms/components/native/CollapsibleRoom.tsx
@@ -3,11 +3,9 @@ import { useTranslation } from 'react-i18next';
import { FlatList } from 'react-native';
import { useDispatch } from 'react-redux';
-import { openSheet } from '../../../../../base/dialog/actions';
-import { IRoom } from '../../../../../breakout-rooms/types';
-import { participantMatchesSearch } from '../../../../functions';
-import CollapsibleList from '../../../native/CollapsibleList';
-import styles from '../../../native/styles';
+import { openSheet } from '../../../base/dialog/actions';
+import CollapsibleList from '../../../participants-pane/components/native/CollapsibleList';
+import { IRoom } from '../../types';
import BreakoutRoomContextMenu from './BreakoutRoomContextMenu';
import BreakoutRoomParticipantItem from './BreakoutRoomParticipantItem';
@@ -19,10 +17,7 @@ interface IProps {
*/
room: IRoom;
- /**
- * Participants search string.
- */
- searchString: string;
+ roomId: string;
}
/**
@@ -35,7 +30,7 @@ function _keyExtractor(item: any) {
return item.jid;
}
-export const CollapsibleRoom = ({ room, searchString }: IProps) => {
+export const CollapsibleRoom = ({ room, roomId }: IProps) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const _openContextMenu = useCallback(() => {
@@ -46,29 +41,22 @@ export const CollapsibleRoom = ({ room, searchString }: IProps) => {
= `${room.name
|| t('breakoutRooms.mainRoom')} (${roomParticipantsNr})`;
- // Regarding the fact that we have 3 sections, we apply
- // a certain height percentage for every section in order for all to fit
- // inside the participants pane container
- const containerStyle
- = roomParticipantsNr > 2 ? styles.collapsibleRoomContainer : undefined;
-
return (
participantMatchesSearch(participant, searchString)
- ? (
+
- : null }
- scrollEnabled = { true }
+ ) }
+ scrollEnabled = { false }
showsHorizontalScrollIndicator = { false }
windowSize = { 2 } />
diff --git a/react/features/participants-pane/components/breakout-rooms/components/native/LeaveBreakoutRoomButton.tsx b/react/features/breakout-rooms/components/native/LeaveBreakoutRoomButton.tsx
similarity index 68%
rename from react/features/participants-pane/components/breakout-rooms/components/native/LeaveBreakoutRoomButton.tsx
rename to react/features/breakout-rooms/components/native/LeaveBreakoutRoomButton.tsx
index 0dc5eb09b3..f82472d0ba 100644
--- a/react/features/participants-pane/components/breakout-rooms/components/native/LeaveBreakoutRoomButton.tsx
+++ b/react/features/breakout-rooms/components/native/LeaveBreakoutRoomButton.tsx
@@ -1,11 +1,11 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
-import { createBreakoutRoomsEvent } from '../../../../../analytics/AnalyticsEvents';
-import { sendAnalytics } from '../../../../../analytics/functions';
-import Button from '../../../../../base/ui/components/native/Button';
-import { BUTTON_TYPES } from '../../../../../base/ui/constants.native';
-import { moveToRoom } from '../../../../../breakout-rooms/actions';
+import { createBreakoutRoomsEvent } from '../../../analytics/AnalyticsEvents';
+import { sendAnalytics } from '../../../analytics/functions';
+import Button from '../../../base/ui/components/native/Button';
+import { BUTTON_TYPES } from '../../../base/ui/constants.native';
+import { moveToRoom } from '../../actions';
import styles from './styles';
diff --git a/react/features/participants-pane/components/breakout-rooms/components/native/styles.ts b/react/features/breakout-rooms/components/native/styles.ts
similarity index 63%
rename from react/features/participants-pane/components/breakout-rooms/components/native/styles.ts
rename to react/features/breakout-rooms/components/native/styles.ts
index c38802bc51..250c5147f4 100644
--- a/react/features/participants-pane/components/breakout-rooms/components/native/styles.ts
+++ b/react/features/breakout-rooms/components/native/styles.ts
@@ -1,12 +1,5 @@
-import BaseTheme from '../../../../../base/ui/components/BaseTheme.native';
+import BaseTheme from '../../../base/ui/components/BaseTheme.native';
-const baseButton = {
- borderRadius: BaseTheme.shape.borderRadius,
- height: BaseTheme.spacing[7],
- marginTop: BaseTheme.spacing[3],
- marginLeft: BaseTheme.spacing[3],
- marginRight: BaseTheme.spacing[3]
-};
/**
* The styles of the native components of the feature {@code breakout rooms}.
@@ -14,16 +7,8 @@ const baseButton = {
export default {
button: {
- marginTop: BaseTheme.spacing[3],
- marginLeft: BaseTheme.spacing[3],
- marginRight: BaseTheme.spacing[3]
- },
-
- collapsibleRoom: {
- ...baseButton,
- display: 'flex',
- flexDirection: 'row',
- alignItems: 'center'
+ marginBottom: BaseTheme.spacing[4],
+ marginHorizontal: BaseTheme.spacing[2]
},
collapsibleList: {
@@ -32,7 +17,7 @@ export default {
display: 'flex',
flexDirection: 'row',
height: BaseTheme.spacing[7],
- marginHorizontal: BaseTheme.spacing[3],
+ marginHorizontal: BaseTheme.spacing[2],
marginTop: BaseTheme.spacing[3]
},
@@ -68,5 +53,24 @@ export default {
alignSelf: 'center',
justifyContent: 'center',
marginTop: BaseTheme.spacing[3]
+ },
+
+ breakoutRoomsContainer: {
+ backgroundColor: BaseTheme.palette.ui01,
+ flex: 1,
+ flexDirection: 'column',
+ height: 'auto',
+ paddingHorizontal: BaseTheme.spacing[3]
+ },
+
+ inputContainer: {
+ marginLeft: BaseTheme.spacing[2],
+ marginRight: BaseTheme.spacing[2],
+ marginTop: BaseTheme.spacing[4]
+ },
+
+ centerInput: {
+ paddingRight: BaseTheme.spacing[3],
+ textAlign: 'center'
}
};
diff --git a/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx b/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx
index 3711f49b7b..7c27edbf74 100644
--- a/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx
+++ b/react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx
@@ -6,6 +6,9 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
+import BreakoutRooms
+// @ts-ignore
+ from '../../../../../breakout-rooms/components/native/BreakoutRooms';
// @ts-ignore
import Chat from '../../../../../chat/components/native/Chat';
// @ts-ignore
@@ -43,6 +46,7 @@ import LanguageSelectorDialog
// @ts-ignore
import { screen } from '../../../routes';
import {
+ breakoutRoomsScreenOptions,
carmodeScreenOptions,
chatScreenOptions,
conferenceScreenOptions,
@@ -72,6 +76,7 @@ import {
// @ts-ignore
} from '../ConferenceNavigationContainerRef';
+
const ConferenceStack = createStackNavigator();
@@ -202,6 +207,13 @@ const ConferenceNavigationContainer = () => {
...subtitlesScreenOptions,
title: t('transcribing.subtitles')
}} />
+
);
diff --git a/react/features/mobile/navigation/routes.ts b/react/features/mobile/navigation/routes.ts
index 09f37c4c06..62315e4cfa 100644
--- a/react/features/mobile/navigation/routes.ts
+++ b/react/features/mobile/navigation/routes.ts
@@ -1,17 +1,6 @@
export const screen = {
- welcome: {
- main: 'Welcome',
- tabs: {
- recent: 'Recent',
- calendar: 'Calendar'
- }
- },
- dialInSummary: 'Dial-In Info',
- connecting: 'Connecting',
- preJoin: 'Pre-Join',
conference: {
- root: 'Conference root',
- main: 'Conference',
+ breakoutRooms: 'Breakout Rooms',
carmode: 'Car Mode',
chat: 'Chat',
chatandpolls: {
@@ -22,31 +11,43 @@ export const screen = {
}
},
container: 'Conference container',
- security: 'Security Options',
- recording: 'Recording',
- liveStream: 'Live stream',
- speakerStats: 'Speaker Stats',
- salesforce: 'Link to Salesforce',
- participants: 'Participants',
gifsMenu: 'GIPHY',
invite: 'Invite',
+ liveStream: 'Live stream',
+ main: 'Conference',
+ participants: 'Participants',
+ root: 'Conference root',
+ recording: 'Recording',
+ salesforce: 'Link to Salesforce',
+ security: 'Security Options',
sharedDocument: 'Shared document',
+ speakerStats: 'Speaker Stats',
subtitles: 'Subtitles'
},
+ connecting: 'Connecting',
+ dialInSummary: 'Dial-In Info',
+ preJoin: 'Pre-Join',
lobby: {
- root: 'Lobby root',
+ chat: 'Lobby chat',
main: 'Lobby',
- chat: 'Lobby chat'
+ root: 'Lobby root'
},
settings: {
- main: 'Settings',
+ language: 'Language',
links: {
help: 'Help',
privacy: 'Privacy',
terms: 'Terms'
},
- profile: 'Profile',
- language: 'Language'
+ main: 'Settings',
+ profile: 'Profile'
},
- unsafeRoomWarning: 'Unsafe Room Warning'
+ unsafeRoomWarning: 'Unsafe Room Warning',
+ welcome: {
+ main: 'Welcome',
+ tabs: {
+ calendar: 'Calendar',
+ recent: 'Recent'
+ }
+ }
};
diff --git a/react/features/mobile/navigation/screenOptions.ts b/react/features/mobile/navigation/screenOptions.ts
index 0136c30746..326d4d4df9 100644
--- a/react/features/mobile/navigation/screenOptions.ts
+++ b/react/features/mobile/navigation/screenOptions.ts
@@ -86,6 +86,11 @@ export const presentationScreenOptions = {
}
};
+/**
+ * Screen options for breakout rooms screen.
+ */
+export const breakoutRoomsScreenOptions = presentationScreenOptions;
+
/**
* Screen options for car mode.
*/
@@ -109,6 +114,11 @@ export const dialInSummaryScreenOptions = {
*/
export const inviteScreenOptions = presentationScreenOptions;
+/**
+ * Screen options for live stream modal.
+ */
+export const liveStreamScreenOptions = presentationScreenOptions;
+
/**
* Screen options for participants modal.
*/
@@ -129,11 +139,6 @@ export const securityScreenOptions = presentationScreenOptions;
*/
export const recordingScreenOptions = presentationScreenOptions;
-/**
- * Screen options for live stream modal.
- */
-export const liveStreamScreenOptions = presentationScreenOptions;
-
/**
* Screen options for subtitles modal.
*/
diff --git a/react/features/participants-pane/components/native/CollapsibleList.tsx b/react/features/participants-pane/components/native/CollapsibleList.tsx
index 83d1a204b8..e0adf3f180 100644
--- a/react/features/participants-pane/components/native/CollapsibleList.tsx
+++ b/react/features/participants-pane/components/native/CollapsibleList.tsx
@@ -3,8 +3,7 @@ import { GestureResponderEvent, Text, TextStyle, TouchableOpacity, View, ViewSty
import Icon from '../../../base/icons/components/Icon';
import { IconArrowDown, IconArrowUp } from '../../../base/icons/svg';
-import { StyleType } from '../../../base/styles/functions.native';
-import styles from '../breakout-rooms/components/native/styles';
+import styles from '../../../breakout-rooms/components/native/styles';
interface IProps {
@@ -13,11 +12,6 @@ interface IProps {
*/
children: React.ReactNode;
- /**
- * Additional style to be appended to the CollapsibleList container.
- */
- containerStyle?: StyleType;
-
/**
* Callback to invoke when the {@code CollapsibleList} is long pressed.
*/
@@ -29,14 +23,14 @@ interface IProps {
title: Object;
}
-const CollapsibleList = ({ children, containerStyle, onLongPress, title }: IProps) => {
+const CollapsibleList = ({ children, onLongPress, title }: IProps) => {
const [ collapsed, setCollapsed ] = useState(false);
const _toggleCollapsed = useCallback(() => {
setCollapsed(!collapsed);
}, [ collapsed ]);
return (
-
+
- {
- title
- }
+ { title }
- {
- !collapsed && children
- }
+ { !collapsed && children }
);
};
diff --git a/react/features/participants-pane/components/native/LobbyParticipantList.tsx b/react/features/participants-pane/components/native/LobbyParticipantList.tsx
index d7a1be5a1c..6d2c32a875 100644
--- a/react/features/participants-pane/components/native/LobbyParticipantList.tsx
+++ b/react/features/participants-pane/components/native/LobbyParticipantList.tsx
@@ -1,70 +1,56 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-import { ScrollView, Text, TextStyle, View, ViewStyle } from 'react-native';
+import { Text, TextStyle, View, ViewStyle } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import Button from '../../../base/ui/components/native/Button';
-import { BUTTON_TYPES } from '../../../base/ui/constants.native';
+import { BUTTON_MODES, BUTTON_TYPES } from '../../../base/ui/constants.native';
import { admitMultiple } from '../../../lobby/actions.native';
import { getKnockingParticipants, getLobbyEnabled } from '../../../lobby/functions';
-import CollapsibleList from './CollapsibleList';
import { LobbyParticipantItem } from './LobbyParticipantItem';
import styles from './styles';
const LobbyParticipantList = () => {
+ const dispatch = useDispatch();
const lobbyEnabled = useSelector(getLobbyEnabled);
const participants = useSelector(getKnockingParticipants);
-
- const dispatch = useDispatch();
const admitAll = useCallback(() =>
dispatch(admitMultiple(participants)),
[ dispatch, participants ]);
const { t } = useTranslation();
+ const title = t('participantsPane.headings.waitingLobby',
+ { count: participants.length });
if (!lobbyEnabled || !participants.length) {
return null;
}
- const title = (
-
-
- { t('participantsPane.headings.waitingLobby',
- { count: participants.length }) }
-
- {
- participants.length > 1 && (
-
- )
- }
-
- );
-
- // Regarding the fact that we have 3 sections, we apply
- // a certain height percentage for every section in order for all to fit
- // inside the participants pane container
- const style = participants.length > 1 && styles.lobbyListContent;
-
return (
-
-
+ <>
+
+
+ { title }
+
{
- participants.map(p => (
- )
+ participants.length > 1 && (
+
)
}
-
-
+
+ {
+ participants.map(p => (
+ )
+ )
+ }
+ >
);
};
diff --git a/react/features/participants-pane/components/native/MeetingParticipantList.tsx b/react/features/participants-pane/components/native/MeetingParticipantList.tsx
index b916189414..96d4a5f38f 100644
--- a/react/features/participants-pane/components/native/MeetingParticipantList.tsx
+++ b/react/features/participants-pane/components/native/MeetingParticipantList.tsx
@@ -1,11 +1,9 @@
-import React, { PureComponent } from 'react';
-import { WithTranslation } from 'react-i18next';
-import { FlatList, Text } from 'react-native';
-import { connect } from 'react-redux';
+import React, { useCallback, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { FlatList, Text, TextStyle, View } from 'react-native';
+import { useDispatch, useSelector } from 'react-redux';
-import { IReduxState, IStore } from '../../../app/types';
-import { openSheet } from '../../../base/dialog/actions';
-import { translate } from '../../../base/i18n/functions';
+import { IReduxState } from '../../../app/types';
import Icon from '../../../base/icons/components/Icon';
import { IconAddUser } from '../../../base/icons/svg';
import {
@@ -24,155 +22,33 @@ import {
} from '../../../breakout-rooms/functions';
import { doInvitePeople } from '../../../invite/actions.native';
import { getInviteOthersControl } from '../../../share-room/functions';
-import {
- isCurrentRoomRenamable,
- participantMatchesSearch,
- shouldRenderInviteButton
-} from '../../functions';
-import { BREAKOUT_CONTEXT_MENU_ACTIONS } from '../../types';
-import BreakoutRoomContextMenu from '../breakout-rooms/components/native/BreakoutRoomContextMenu';
+import { participantMatchesSearch, shouldRenderInviteButton } from '../../functions';
-import CollapsibleList from './CollapsibleList';
import MeetingParticipantItem from './MeetingParticipantItem';
import styles from './styles';
-interface IProps extends WithTranslation {
-
- /**
- * Current breakout room, if we are in one.
- */
- _currentRoom: any;
-
- /**
- * Control for invite other button.
- */
- _inviteOthersControl: any;
-
- /**
- * Checks if add-people feature is enabled.
- */
- _isAddPeopleFeatureEnabled: boolean;
-
- /**
- * Indicates whether the room that is currently joined can be renamed.
- */
- _isCurrentRoomRenamable: boolean;
-
- /**
- * The local participant.
- */
- _localParticipant: any;
-
- /**
- * The number of participants in the conference.
- */
- _participantsCount: number;
-
- /**
- * The remote participants.
- */
- _remoteParticipants: Map;
-
- /**
- * Whether or not to show the invite button.
- */
- _showInviteButton: boolean;
-
- /**
- * The remote participants.
- */
- _sortedRemoteParticipants: string[];
-
- /**
- * The current visitors count if any.
- */
- _visitorsCount: number;
-
- /**
- * List of breakout rooms that were created.
- */
- breakoutRooms: ArrayLike;
-
- /**
- * The redux dispatch function.
- */
- dispatch: IStore['dispatch'];
-
- /**
- * Is the local participant moderator?
- */
- isLocalModerator: boolean;
-
- /**
- * List of participants waiting in lobby.
- */
- lobbyParticipants: ArrayLike;
-
- /**
- * Participants search string.
- */
- searchString: string;
-
- /**
- * Function to update the search string.
- */
- setSearchString: Function;
-}
-
-/**
- * The meeting participant list component.
- */
-class MeetingParticipantList extends PureComponent {
-
- /**
- * Creates new MeetingParticipantList instance.
- *
- * @param {IProps} props - The props of the component.
- */
- constructor(props: IProps) {
- super(props);
-
- this._keyExtractor = this._keyExtractor.bind(this);
- this._onInvite = this._onInvite.bind(this);
- this._openContextMenu = this._openContextMenu.bind(this);
- this._renderParticipant = this._renderParticipant.bind(this);
- this._onSearchStringChange = this._onSearchStringChange.bind(this);
- }
-
- /**
- * Returns a key for a passed item of the list.
- *
- * @param {string} item - The user ID.
- * @returns {string} - The user ID.
- */
- _keyExtractor(item: string) {
- return item;
- }
-
- /**
- * Handles ivite button presses.
- *
- * @returns {void}
- */
- _onInvite() {
- const { _isAddPeopleFeatureEnabled, dispatch } = this.props;
-
- setShareDialogVisiblity(_isAddPeopleFeatureEnabled, dispatch);
-
+const MeetingParticipantList = () => {
+ const currentRoomId = useSelector(getCurrentRoomId);
+ const currentRoom = useSelector(getBreakoutRooms)[currentRoomId];
+ const dispatch = useDispatch();
+ const inviteOthersControl = useSelector(getInviteOthersControl);
+ const isAddPeopleFeatureEnabled = useSelector(addPeopleFeatureControl);
+ const keyExtractor
+ = useCallback((e: undefined, i: number) => i.toString(), []);
+ const localParticipant = useSelector(getLocalParticipant);
+ const onInvite = useCallback(() => {
+ setShareDialogVisiblity(isAddPeopleFeatureEnabled, dispatch);
dispatch(doInvitePeople());
- }
-
- /**
- * Renders a participant.
- *
- * @param {Object} flatListItem - Information about the item to be rendered.
- * @param {string} flatListItem.item - The ID of the participant.
- * @returns {ReactElement}
- */
- _renderParticipant({ item/* , index, separators */ }: any) {
- const { _localParticipant, _remoteParticipants, searchString } = this.props;
- const participant = item === _localParticipant?.id ? _localParticipant : _remoteParticipants.get(item);
+ }, [ dispatch ]);
+ const [ searchString, setSearchString ] = useState('');
+ const onSearchStringChange = useCallback((text: string) =>
+ setSearchString(text), []);
+ const participantsCount = useSelector(getParticipantCountWithFake);
+ const remoteParticipants = useSelector(getRemoteParticipants);
+ const renderParticipant = ({ item/* , index, separators */ }: any) => {
+ const participant = item === localParticipant?.id
+ ? localParticipant : remoteParticipants.get(item);
if (participantMatchesSearch(participant, searchString)) {
return (
@@ -183,156 +59,68 @@ class MeetingParticipantList extends PureComponent {
}
return null;
- }
-
- /**
- * Handles search string changes.
- *
- * @param {string} text - New value of the search string.
- * @returns {void}
- */
- _onSearchStringChange(text: string) {
- this.props.setSearchString(text);
- }
-
- /**
- * Opens the context menu to rename the current breakout room.
- *
- * @returns {void}
- */
- _openContextMenu() {
- this.props.dispatch(openSheet(BreakoutRoomContextMenu, {
- room: this.props._currentRoom,
- actions: [ BREAKOUT_CONTEXT_MENU_ACTIONS.RENAME ]
- }));
- }
-
- /**
- * Implements React's {@link Component#render()}.
- *
- * @inheritdoc
- * @returns {ReactElement}
- */
- render() {
- const {
- _currentRoom,
- _isCurrentRoomRenamable,
- _inviteOthersControl,
- _localParticipant,
- _participantsCount,
- _showInviteButton,
- _sortedRemoteParticipants,
- _visitorsCount,
- breakoutRooms,
- isLocalModerator,
- lobbyParticipants,
- t
- } = this.props;
- const title = _currentRoom?.name
- ? `${_currentRoom.name} (${_participantsCount})`
- : t('participantsPane.headings.participantsList',
- { count: _participantsCount });
-
- // Regarding the fact that we have 3 sections, we apply
- // a certain height percentage for every section in order for all to fit
- // inside the participants pane container
- // If there are only meeting participants available,
- // we take the full container height
- const onlyMeetingParticipants
- = breakoutRooms?.length === 0 && lobbyParticipants?.length === 0;
- const containerStyleModerator
- = onlyMeetingParticipants
- ? styles.meetingListFullContainer : styles.meetingListContainer;
- const containerStyle
- = isLocalModerator
- ? containerStyleModerator : styles.notLocalModeratorContainer;
- const finalContainerStyle
- = _participantsCount > 6 ? containerStyle : undefined;
- const { color, shareDialogVisible } = _inviteOthersControl;
- const _visitorsLabelText = _visitorsCount > 0
- ? t('participantsPane.headings.visitors', { count: _visitorsCount })
- : undefined;
- const onLongPress = _isCurrentRoomRenamable ? this._openContextMenu : undefined;
-
- return (
- <>
- { _visitorsCount > 0 && { _visitorsLabelText }
- }
-
- {
- _showInviteButton
- &&
- >
- );
- }
-}
-
-/**
- * Maps (parts of) the redux state to the associated props for this component.
- *
- * @param {Object} state - The Redux state.
- * @private
- * @returns {IProps}
- */
-function _mapStateToProps(state: IReduxState) {
- const _participantsCount = getParticipantCountWithFake(state);
- const { remoteParticipants } = state['features/filmstrip'];
- const { shareDialogVisible } = state['features/share-room'];
- const _inviteOthersControl = getInviteOthersControl(state);
- const _isAddPeopleFeatureEnabled = addPeopleFeatureControl(state);
- const _showInviteButton = shouldRenderInviteButton(state);
- const _remoteParticipants = getRemoteParticipants(state);
- const currentRoomId = getCurrentRoomId(state);
- const _currentRoom = getBreakoutRooms(state)[currentRoomId];
-
- return {
- _currentRoom,
- _isAddPeopleFeatureEnabled,
- _isCurrentRoomRenamable: isCurrentRoomRenamable(state),
- _inviteOthersControl,
- _participantsCount,
- _remoteParticipants,
- _showInviteButton,
- _sortedRemoteParticipants: remoteParticipants,
- _localParticipant: getLocalParticipant(state),
- _shareDialogVisible: shareDialogVisible,
- _visitorsCount: state['features/visitors'].count || 0
};
-}
-
-export default translate(connect(_mapStateToProps)(MeetingParticipantList));
+ const showInviteButton = useSelector(shouldRenderInviteButton);
+ const sortedRemoteParticipants = useSelector(
+ (state: IReduxState) => state['features/filmstrip'].remoteParticipants);
+ const { t } = useTranslation();
+ const title = currentRoom?.name
+ ? `${currentRoom.name} (${participantsCount})`
+ : t('participantsPane.headings.participantsList',
+ { count: participantsCount });
+ const { color, shareDialogVisible } = inviteOthersControl;
+ const visitorsCount = useSelector((state: IReduxState) => state['features/visitors'].count || 0);
+ const visitorsLabelText = visitorsCount > 0
+ ? t('participantsPane.headings.visitors', { count: visitorsCount })
+ : undefined;
+
+ return (
+
+ {
+ visitorsCount > 0
+ &&
+ { visitorsLabelText }
+
+ }
+
+ { title }
+
+ {
+ showInviteButton
+ &&
+ );
+};
+
+export default MeetingParticipantList;
diff --git a/react/features/participants-pane/components/native/ParticipantItem.tsx b/react/features/participants-pane/components/native/ParticipantItem.tsx
index fb09cd26ab..413159b7d0 100644
--- a/react/features/participants-pane/components/native/ParticipantItem.tsx
+++ b/react/features/participants-pane/components/native/ParticipantItem.tsx
@@ -87,6 +87,8 @@ function ParticipantItem({
}: IProps) {
const { t } = useTranslation();
+ const participantNameContainerStyles
+ = isKnockingParticipant ? styles.lobbyParticipantNameContainer : styles.participantNameContainer;
return (
@@ -102,7 +104,7 @@ function ParticipantItem({
styles.participantDetailsContainer,
raisedHand && styles.participantDetailsContainerRaisedHand
] }>
-
+
diff --git a/react/features/participants-pane/components/native/ParticipantsPane.tsx b/react/features/participants-pane/components/native/ParticipantsPane.tsx
index 0e14ceb36d..4db6ce9473 100644
--- a/react/features/participants-pane/components/native/ParticipantsPane.tsx
+++ b/react/features/participants-pane/components/native/ParticipantsPane.tsx
@@ -1,74 +1,45 @@
-import React, { useState } from 'react';
+import React, { useCallback } from 'react';
+import { FlatList } from 'react-native';
import { useSelector } from 'react-redux';
-import { IReduxState } from '../../../app/types';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
-import { equals } from '../../../base/redux/functions';
-import {
- getBreakoutRooms,
- getCurrentRoomId,
- isAddBreakoutRoomButtonVisible,
- isAutoAssignParticipantsVisible,
- isInBreakoutRoom
-} from '../../../breakout-rooms/functions';
-import { getKnockingParticipants } from '../../../lobby/functions';
-import AddBreakoutRoomButton from '../breakout-rooms/components/native/AddBreakoutRoomButton';
-import AutoAssignButton from '../breakout-rooms/components/native/AutoAssignButton';
-import { CollapsibleRoom } from '../breakout-rooms/components/native/CollapsibleRoom';
-import LeaveBreakoutRoomButton from '../breakout-rooms/components/native/LeaveBreakoutRoomButton';
import LobbyParticipantList from './LobbyParticipantList';
import MeetingParticipantList from './MeetingParticipantList';
import ParticipantsPaneFooter from './ParticipantsPaneFooter';
import styles from './styles';
+
/**
* Participants pane.
*
* @returns {React$Element}
*/
const ParticipantsPane = () => {
- const [ searchString, setSearchString ] = useState('');
const isLocalModerator = useSelector(isLocalParticipantModerator);
- const { conference } = useSelector((state: IReduxState) => state['features/base/conference']);
- const _isBreakoutRoomsSupported = conference?.getBreakoutRooms()?.isSupported();
- const currentRoomId = useSelector(getCurrentRoomId);
- const rooms = Object.values(useSelector(getBreakoutRooms, equals))
- .filter(room => room.id !== currentRoomId)
- .sort((p1, p2) => (p1?.name || '').localeCompare(p2?.name || ''));
- const inBreakoutRoom = useSelector(isInBreakoutRoom);
- const showAddBreakoutRoom = useSelector(isAddBreakoutRoomButtonVisible);
- const showAutoAssign = useSelector(isAutoAssignParticipantsVisible);
- const lobbyParticipants = useSelector(getKnockingParticipants);
+ const keyExtractor
+ = useCallback((e: undefined, i: number) => i.toString(), []);
return (
-
-
- {
- showAutoAssign &&
- }
- {
- inBreakoutRoom &&
- }
- {
- _isBreakoutRoomsSupported
- && rooms.map(room => ())
- }
- {
- showAddBreakoutRoom &&
- }
+
+ { /* Fixes warning regarding nested lists */ }
+ (
+ <>
+
+
+ >
+ ) }
+ data = { [] as ReadonlyArray }
+ keyExtractor = { keyExtractor }
+ renderItem = { null }
+ windowSize = { 2 } />
);
};
diff --git a/react/features/participants-pane/components/native/ParticipantsPaneFooter.tsx b/react/features/participants-pane/components/native/ParticipantsPaneFooter.tsx
index 1d98302587..6eaf905c11 100644
--- a/react/features/participants-pane/components/native/ParticipantsPaneFooter.tsx
+++ b/react/features/participants-pane/components/native/ParticipantsPaneFooter.tsx
@@ -1,13 +1,21 @@
+/* eslint-disable lines-around-comment */
+
import React, { useCallback } from 'react';
import { View, ViewStyle } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
+import { IReduxState } from '../../../app/types';
import { openDialog, openSheet } from '../../../base/dialog/actions';
-import { IconDotsHorizontal } from '../../../base/icons/svg';
+import Icon from '../../../base/icons/components/Icon';
+import { IconDotsHorizontal, IconRingGroup } from '../../../base/icons/svg';
+import BaseTheme from '../../../base/ui/components/BaseTheme.native';
import Button from '../../../base/ui/components/native/Button';
import IconButton from '../../../base/ui/components/native/IconButton';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
-// eslint-disable-next-line lines-around-comment
+import {
+ navigate
+} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
+import { screen } from '../../../mobile/navigation/routes';
// @ts-ignore
import MuteEveryoneDialog from '../../../video-menu/components/native/MuteEveryoneDialog';
import { isMoreActionsVisible, isMuteAllVisible } from '../../functions';
@@ -23,6 +31,9 @@ import styles from './styles';
*/
const ParticipantsPaneFooter = (): JSX.Element => {
const dispatch = useDispatch();
+ const isBreakoutRoomsSupported = useSelector((state: IReduxState) =>
+ state['features/base/conference'].conference?.getBreakoutRooms()?.isSupported()
+ );
const openMoreMenu = useCallback(() => dispatch(openSheet(ContextMenuMore)), [ dispatch ]);
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
[ dispatch ]);
@@ -30,25 +41,45 @@ const ParticipantsPaneFooter = (): JSX.Element => {
const showMuteAll = useSelector(isMuteAllVisible);
return (
-
+
{
- showMuteAll && (
-
- )
- }
- {
- showMoreActions && (
-
- )
+ isBreakoutRoomsSupported
+ &&
);
};
diff --git a/react/features/participants-pane/components/native/styles.ts b/react/features/participants-pane/components/native/styles.ts
index b20d56c0df..b68d704e10 100644
--- a/react/features/participants-pane/components/native/styles.ts
+++ b/react/features/participants-pane/components/native/styles.ts
@@ -5,11 +5,13 @@ import BaseTheme from '../../../base/ui/components/BaseTheme.native';
*/
const participantListDescription = {
...BaseTheme.typography.heading6,
- color: BaseTheme.palette.text02,
- paddingBottom: BaseTheme.spacing[3],
- paddingTop: BaseTheme.spacing[3],
+ color: BaseTheme.palette.text01,
+ fontSize: 15,
+ fontWeight: 'bold',
+ marginLeft: BaseTheme.spacing[2],
+ paddingVertical: BaseTheme.spacing[2],
position: 'relative',
- width: '55%'
+ width: '70%'
};
/**
@@ -50,29 +52,18 @@ const contextMenuItem = {
marginLeft: BaseTheme.spacing[3]
};
+const participantNameContainer = {
+ display: 'flex',
+ flexDirection: 'row',
+ overflow: 'hidden',
+ paddingLeft: BaseTheme.spacing[3]
+};
+
/**
* The styles of the native components of the feature {@code participants}.
*/
export default {
- participantActionsButtonAdmit: {
- marginRight: BaseTheme.spacing[3],
- position: 'absolute',
- right: 88
- },
-
- participantActionsButtonReject: {
- marginRight: BaseTheme.spacing[3],
- position: 'absolute',
- right: 0
- },
-
- admitAllButtonLabel: {
- color: BaseTheme.palette.link01,
- marginRight: BaseTheme.spacing[6],
- marginTop: 14
- },
-
participantsBadge: {
backgroundColor: BaseTheme.palette.ui03,
borderRadius: BaseTheme.spacing[2],
@@ -126,16 +117,18 @@ export default {
},
participantNameContainer: {
- display: 'flex',
- flexDirection: 'row',
- overflow: 'hidden',
- paddingLeft: BaseTheme.spacing[3],
+ ...participantNameContainer,
width: '100%'
},
+ lobbyParticipantNameContainer: {
+ ...participantNameContainer,
+ width: '40%'
+ },
+
participantName: {
- overflow: 'hidden',
- color: BaseTheme.palette.text01
+ color: BaseTheme.palette.text01,
+ overflow: 'hidden'
},
moderatorLabel: {
@@ -171,10 +164,6 @@ export default {
color: BaseTheme.palette.uiBackground
},
- lobbyListContent: {
- height: '24%'
- },
-
lobbyButtonAdmit: {
position: 'absolute',
right: 16
@@ -182,76 +171,61 @@ export default {
lobbyButtonReject: {
position: 'absolute',
- right: 104
+ right: 112
},
lobbyListDescription: {
- fontSize: 15,
- color: BaseTheme.palette.text01,
- fontWeight: 'bold',
- marginTop: BaseTheme.spacing[2]
+ ...participantListDescription
},
lobbyListDetails: {
alignItems: 'center',
display: 'flex',
flexDirection: 'row',
- justifyContent: 'space-between',
- overflow: 'hidden',
- position: 'relative',
- width: '100%'
- },
-
- notLocalModeratorContainer: {
- height: '100%'
+ justifyContent: 'space-between'
},
meetingListContainer: {
- height: '56%'
- },
-
- meetingListFullContainer: {
- height: '80%'
+ paddingHorizontal: BaseTheme.spacing[3]
},
meetingListDescription: {
- ...participantListDescription,
- marginLeft: BaseTheme.spacing[3]
- },
-
- collapsibleRoomContainer: {
- height: '32%'
+ ...participantListDescription
},
participantsPaneContainer: {
backgroundColor: BaseTheme.palette.ui01,
flex: 1,
- justifyContent: 'center'
+ flexDirection: 'column',
+ paddingVertical: BaseTheme.spacing[2]
},
- participantsPaneFooter: {
+ participantsPaneFooterContainer: {
alignItems: 'center',
- backgroundColor: BaseTheme.palette.ui01,
bottom: 0,
- flexDirection: 'row',
- height: BaseTheme.spacing[12],
- justifyContent: 'flex-end',
+ height: 128,
left: 0,
- right: 0,
- position: 'absolute',
- paddingBottom: BaseTheme.spacing[2],
- paddingLeft: BaseTheme.spacing[3],
- paddingRight: BaseTheme.spacing[3]
+ paddingHorizontal: BaseTheme.spacing[4],
+ right: 0
},
- headerCloseIcon: {
- marginLeft: 12
+ participantsPaneFooter: {
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'flex-end',
+ paddingBottom: BaseTheme.spacing[3],
+ width: '100%'
},
inviteButton: {
marginLeft: BaseTheme.spacing[3],
marginRight: BaseTheme.spacing[3],
- marginVertical: BaseTheme.spacing[3]
+ marginVertical: BaseTheme.spacing[2]
+ },
+
+ breakoutRoomsButton: {
+ marginBottom: BaseTheme.spacing[2],
+ width: '100%'
},
moreButton: {
diff --git a/react/features/participants-pane/functions.ts b/react/features/participants-pane/functions.ts
index febd9ade53..f295778f60 100644
--- a/react/features/participants-pane/functions.ts
+++ b/react/features/participants-pane/functions.ts
@@ -286,7 +286,7 @@ export const isMuteAllVisible = (state: IReduxState) => {
};
/**
- * Returns true if reanming the currently joined breakout room is allowed and false otherwise.
+ * Returns true if renaming the currently joined breakout room is allowed and false otherwise.
*
* @param {IReduxState} state - The redux state.
* @returns {boolean} - True if reanming the currently joined breakout room is allowed and false otherwise.
diff --git a/react/features/toolbox/components/native/OverflowMenu.tsx b/react/features/toolbox/components/native/OverflowMenu.tsx
index 78c9b5d524..63c32634a6 100644
--- a/react/features/toolbox/components/native/OverflowMenu.tsx
+++ b/react/features/toolbox/components/native/OverflowMenu.tsx
@@ -8,6 +8,8 @@ import { hideSheet } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import { bottomSheetStyles } from '../../../base/dialog/components/native/styles';
import SettingsButton from '../../../base/settings/components/native/SettingsButton';
+import BreakoutRoomsButton
+ from '../../../breakout-rooms/components/native/BreakoutRoomsButton';
import SharedDocumentButton from '../../../etherpad/components/SharedDocumentButton.native';
import ReactionMenu from '../../../reactions/components/native/ReactionMenu';
import { isReactionsEnabled } from '../../../reactions/functions.any';
@@ -29,11 +31,17 @@ import OpenCarmodeButton from './OpenCarmodeButton';
import RaiseHandButton from './RaiseHandButton';
import ScreenSharingButton from './ScreenSharingButton';
+
/**
* The type of the React {@code Component} props of {@link OverflowMenu}.
*/
interface IProps {
+ /**
+ * True if breakout rooms feature is available, false otherwise.
+ */
+ _isBreakoutRoomsSupported?: boolean;
+
/**
* True if the overflow menu is currently visible, false otherwise.
*/
@@ -103,6 +111,7 @@ class OverflowMenu extends PureComponent {
*/
render() {
const {
+ _isBreakoutRoomsSupported,
_isSpeakerStatsDisabled,
_reactionsEnabled,
_width,
@@ -150,6 +159,7 @@ class OverflowMenu extends PureComponent {
{!toolbarButtons.has('screensharing') && }
{!_isSpeakerStatsDisabled && }
{!toolbarButtons.has('tileview') && }
+ {_isBreakoutRoomsSupported && }
{/* @ts-ignore */}
@@ -191,7 +201,10 @@ class OverflowMenu extends PureComponent {
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
+ const { conference } = state['features/base/conference'];
+
return {
+ _isBreakoutRoomsSupported: conference?.getBreakoutRooms()?.isSupported(),
_isSpeakerStatsDisabled: isSpeakerStatsDisabled(state),
_reactionsEnabled: isReactionsEnabled(state),
_width: state['features/base/responsive-ui'].clientWidth