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

feat(breakout-rooms/native): separate breakout rooms from participants
pull/13939/head jitsi-meet_9019
Calinteodor 2 years ago committed by GitHub
parent 7e1d10fb4d
commit 9c04ba767c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      lang/main.json
  2. 6
      react/features/base/flags/constants.ts
  3. 6
      react/features/breakout-rooms/components/native/AddBreakoutRoomButton.tsx
  4. 6
      react/features/breakout-rooms/components/native/AutoAssignButton.tsx
  5. 33
      react/features/breakout-rooms/components/native/BreakoutRoomContextMenu.tsx
  6. 7
      react/features/breakout-rooms/components/native/BreakoutRoomNamePrompt.tsx
  7. 10
      react/features/breakout-rooms/components/native/BreakoutRoomParticipantItem.tsx
  8. 68
      react/features/breakout-rooms/components/native/BreakoutRooms.tsx
  9. 53
      react/features/breakout-rooms/components/native/BreakoutRoomsButton.tsx
  10. 34
      react/features/breakout-rooms/components/native/CollapsibleRoom.tsx
  11. 10
      react/features/breakout-rooms/components/native/LeaveBreakoutRoomButton.tsx
  12. 42
      react/features/breakout-rooms/components/native/styles.ts
  13. 12
      react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.tsx
  14. 49
      react/features/mobile/navigation/routes.ts
  15. 15
      react/features/mobile/navigation/screenOptions.ts
  16. 20
      react/features/participants-pane/components/native/CollapsibleList.tsx
  17. 66
      react/features/participants-pane/components/native/LobbyParticipantList.tsx
  18. 392
      react/features/participants-pane/components/native/MeetingParticipantList.tsx
  19. 4
      react/features/participants-pane/components/native/ParticipantItem.tsx
  20. 69
      react/features/participants-pane/components/native/ParticipantsPane.tsx
  21. 69
      react/features/participants-pane/components/native/ParticipantsPaneFooter.tsx
  22. 114
      react/features/participants-pane/components/native/styles.ts
  23. 2
      react/features/participants-pane/functions.ts
  24. 13
      react/features/toolbox/components/native/OverflowMenu.tsx

@ -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",

@ -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.

@ -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';

@ -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';

@ -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) => {
</TouchableOpacity>
)
}
{!room?.isMainRoom && actions.includes(ACTIONS.RENAME) && _isBreakoutRoomRenameAllowed
{
!room?.isMainRoom && actions.includes(ACTIONS.RENAME) && _isBreakoutRoomRenameAllowed
&& <TouchableOpacity
onPress = { onRenameBreakoutRoom }
style = { styles.contextMenuItem as ViewStyle }>
@ -95,7 +97,8 @@ const BreakoutRoomContextMenu = ({ room, actions = ALL_ACTIONS }: IProps) => {
<Text style = { styles.contextMenuItemText }>{t('breakoutRooms.actions.rename')}</Text>
</TouchableOpacity>
}
{!room?.isMainRoom && isLocalModerator && actions.includes(ACTIONS.REMOVE)
{
!room?.isMainRoom && isLocalModerator && actions.includes(ACTIONS.REMOVE)
&& (room?.participants && Object.keys(room.participants).length > 0
? <TouchableOpacity
onPress = { onCloseBreakoutRoom }

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

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

@ -0,0 +1,68 @@
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 '../../functions';
import AddBreakoutRoomButton from './AddBreakoutRoomButton';
import AutoAssignButton from './AutoAssignButton';
import { CollapsibleRoom } from './CollapsibleRoom';
import LeaveBreakoutRoomButton from './LeaveBreakoutRoomButton';
import styles from './styles';
const BreakoutRooms = () => {
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 (
<JitsiScreen
footerComponent = { isLocalModerator && showAddBreakoutRoom
? AddBreakoutRoomButton : undefined }
style = { styles.breakoutRoomsContainer }>
{ /* Fixes warning regarding nested lists */ }
<FlatList
/* eslint-disable react/jsx-no-bind */
ListHeaderComponent = { () => (
<>
{ showAutoAssign && <AutoAssignButton /> }
{ inBreakoutRoom && <LeaveBreakoutRoomButton /> }
{
isBreakoutRoomsSupported
&& rooms.map(room => (<CollapsibleRoom
key = { room.id }
room = { room }
roomId = { room.id } />))
}
</>
) }
data = { [] as ReadonlyArray<undefined> }
keyExtractor = { keyExtractor }
renderItem = { null }
windowSize = { 2 } />
</JitsiScreen>
);
};
export default BreakoutRooms;

@ -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<AbstractButtonProps> {
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));

@ -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 (
<CollapsibleList
containerStyle = { containerStyle }
onLongPress = { _openContextMenu }
title = { title }>
<FlatList
bounces = { false }
data = { Object.values(room.participants || {}) }
horizontal = { false }
keyExtractor = { _keyExtractor }
listKey = { roomId }
// eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow
renderItem = { ({ item: participant }) => participantMatchesSearch(participant, searchString)
? <BreakoutRoomParticipantItem
renderItem = { ({ item: participant }) => (
<BreakoutRoomParticipantItem
item = { participant }
room = { room } />
: null }
scrollEnabled = { true }
) }
scrollEnabled = { false }
showsHorizontalScrollIndicator = { false }
windowSize = { 2 } />
</CollapsibleList>

@ -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';

@ -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'
}
};

@ -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')
}} />
<ConferenceStack.Screen
component = { BreakoutRooms }
name = { screen.conference.breakoutRooms }
options = {{
...breakoutRoomsScreenOptions,
title: t('breakoutRooms.title')
}} />
</ConferenceStack.Navigator>
</NavigationContainer>
);

@ -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'
}
}
};

@ -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.
*/

@ -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 (
<View style = { !collapsed && containerStyle }>
<View>
<TouchableOpacity
onLongPress = { onLongPress }
onPress = { _toggleCollapsed }
@ -49,14 +43,10 @@ const CollapsibleList = ({ children, containerStyle, onLongPress, title }: IProp
src = { collapsed ? IconArrowDown : IconArrowUp } />
</TouchableOpacity>
<Text style = { styles.listTile as TextStyle }>
{
title
}
{ title }
</Text>
</TouchableOpacity>
{
!collapsed && children
}
{ !collapsed && children }
</View>
);
};

@ -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 = (
<View style = { styles.lobbyListDetails as ViewStyle } >
<Text style = { styles.lobbyListDescription as TextStyle }>
{ t('participantsPane.headings.waitingLobby',
{ count: participants.length }) }
</Text>
{
participants.length > 1 && (
<Button
accessibilityLabel = 'lobby.admitAll'
labelKey = 'lobby.admitAll'
labelStyle = { styles.admitAllButtonLabel }
onClick = { admitAll }
type = { BUTTON_TYPES.TERTIARY } />
)
}
</View>
);
// 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 (
<CollapsibleList
title = { title }>
<ScrollView
bounces = { false }
style = { style } >
<>
<View style = { styles.lobbyListDetails as ViewStyle } >
<Text style = { styles.lobbyListDescription as TextStyle }>
{ title }
</Text>
{
participants.map(p => (
<LobbyParticipantItem
key = { p.id }
participant = { p } />)
participants.length > 1 && (
<Button
accessibilityLabel = 'lobby.admitAll'
labelKey = 'lobby.admitAll'
mode = { BUTTON_MODES.TEXT }
onClick = { admitAll }
type = { BUTTON_TYPES.PRIMARY } />
)
}
</ScrollView>
</CollapsibleList>
</View>
{
participants.map(p => (
<LobbyParticipantItem
key = { p.id }
participant = { p } />)
)
}
</>
);
};

@ -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<string, Object>;
/**
* 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<any>;
/**
* The redux dispatch function.
*/
dispatch: IStore['dispatch'];
/**
* Is the local participant moderator?
*/
isLocalModerator: boolean;
/**
* List of participants waiting in lobby.
*/
lobbyParticipants: ArrayLike<any>;
/**
* Participants search string.
*/
searchString: string;
/**
* Function to update the search string.
*/
setSearchString: Function;
}
/**
* The meeting participant list component.
*/
class MeetingParticipantList extends PureComponent<IProps> {
/**
* 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<IProps> {
}
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 && <Text style = { styles.visitorsLabel }>{ _visitorsLabelText }</Text>
}
<CollapsibleList
containerStyle = { finalContainerStyle }
onLongPress = { onLongPress }
title = { title }>
{
_showInviteButton
&& <Button
accessibilityLabel = 'participantsPane.actions.invite'
disabled = { shareDialogVisible }
// eslint-disable-next-line react/jsx-no-bind
icon = { () => (
<Icon
color = { color }
size = { 20 }
src = { IconAddUser } />
) }
labelKey = 'participantsPane.actions.invite'
onClick = { this._onInvite }
style = { styles.inviteButton }
type = { BUTTON_TYPES.PRIMARY } />
}
<Input
clearable = { true }
customStyles = {{
container: styles.inputContainer,
input: styles.centerInput }}
onChange = { this._onSearchStringChange }
placeholder = { t('participantsPane.search') }
value = { this.props.searchString } />
<FlatList
bounces = { false }
data = { [ _localParticipant?.id, ..._sortedRemoteParticipants ] }
horizontal = { false }
keyExtractor = { this._keyExtractor }
renderItem = { this._renderParticipant }
scrollEnabled = { true }
showsHorizontalScrollIndicator = { false }
windowSize = { 2 } />
</CollapsibleList>
</>
);
}
}
/**
* 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 (
<View style = { styles.meetingListContainer }>
{
visitorsCount > 0
&& <Text style = { styles.visitorsLabel }>
{ visitorsLabelText }
</Text>
}
<Text
style = { styles.meetingListDescription as TextStyle }>
{ title }
</Text>
{
showInviteButton
&& <Button
accessibilityLabel = 'participantsPane.actions.invite'
disabled = { shareDialogVisible }
// eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow
icon = { () => (
<Icon
color = { color }
size = { 20 }
src = { IconAddUser } />
) }
labelKey = 'participantsPane.actions.invite'
onClick = { onInvite }
style = { styles.inviteButton }
type = { BUTTON_TYPES.PRIMARY } />
}
<Input
clearable = { true }
customStyles = {{
container: styles.inputContainer,
input: styles.centerInput }}
onChange = { onSearchStringChange }
placeholder = { t('participantsPane.search') }
value = { searchString } />
<FlatList
data = { [ localParticipant?.id, ...sortedRemoteParticipants ] as Array<any> }
keyExtractor = { keyExtractor }
/* eslint-disable react/jsx-no-bind */
renderItem = { renderParticipant }
windowSize = { 2 } />
</View>
);
};
export default MeetingParticipantList;

@ -87,6 +87,8 @@ function ParticipantItem({
}: IProps) {
const { t } = useTranslation();
const participantNameContainerStyles
= isKnockingParticipant ? styles.lobbyParticipantNameContainer : styles.participantNameContainer;
return (
<View style = { styles.participantContainer as ViewStyle } >
@ -102,7 +104,7 @@ function ParticipantItem({
styles.participantDetailsContainer,
raisedHand && styles.participantDetailsContainerRaisedHand
] }>
<View style = { styles.participantNameContainer as ViewStyle }>
<View style = { participantNameContainerStyles as ViewStyle }>
<Text
numberOfLines = { 1 }
style = { styles.participantName as TextStyle }>

@ -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<any>}
*/
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 (
<JitsiScreen
footerComponent = { isLocalModerator ? ParticipantsPaneFooter : undefined }
style = { styles.participantsPaneContainer }>
<LobbyParticipantList />
<MeetingParticipantList
breakoutRooms = { rooms }
isLocalModerator = { isLocalModerator }
lobbyParticipants = { lobbyParticipants }
searchString = { searchString }
setSearchString = { setSearchString } />
{
showAutoAssign && <AutoAssignButton />
}
{
inBreakoutRoom && <LeaveBreakoutRoomButton />
}
{
_isBreakoutRoomsSupported
&& rooms.map(room => (<CollapsibleRoom
key = { room.id }
room = { room }
searchString = { searchString } />))
}
{
showAddBreakoutRoom && <AddBreakoutRoomButton />
}
{ /* Fixes warning regarding nested lists */ }
<FlatList
// eslint-disable-next-line react/jsx-no-bind
ListHeaderComponent = { () => (
<>
<LobbyParticipantList />
<MeetingParticipantList />
</>
) }
data = { [] as ReadonlyArray<undefined> }
keyExtractor = { keyExtractor }
renderItem = { null }
windowSize = { 2 } />
</JitsiScreen>
);
};

@ -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 (
<View style = { styles.participantsPaneFooter as ViewStyle }>
<View style = { styles.participantsPaneFooterContainer as ViewStyle }>
{
showMuteAll && (
<Button
accessibilityLabel = 'participantsPane.actions.muteAll'
labelKey = 'participantsPane.actions.muteAll'
onClick = { muteAll }
type = { BUTTON_TYPES.SECONDARY } />
)
}
{
showMoreActions && (
<IconButton
onPress = { openMoreMenu }
src = { IconDotsHorizontal }
style = { styles.moreButton }
type = { BUTTON_TYPES.SECONDARY } />
)
isBreakoutRoomsSupported
&& <Button
accessibilityLabel = 'participantsPane.actions.breakoutRooms'
// eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow
icon = { () => (
<Icon
color = { BaseTheme.palette.icon04 }
size = { 20 }
src = { IconRingGroup } />
) }
labelKey = 'participantsPane.actions.breakoutRooms'
// eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow
onClick = { () => navigate(screen.conference.breakoutRooms) }
style = { styles.breakoutRoomsButton }
type = { BUTTON_TYPES.SECONDARY } />
}
<View style = { styles.participantsPaneFooter as ViewStyle }>
{
showMuteAll && (
<Button
accessibilityLabel = 'participantsPane.actions.muteAll'
labelKey = 'participantsPane.actions.muteAll'
onClick = { muteAll }
type = { BUTTON_TYPES.SECONDARY } />
)
}
{
showMoreActions && (
<IconButton
onPress = { openMoreMenu }
src = { IconDotsHorizontal }
style = { styles.moreButton }
type = { BUTTON_TYPES.SECONDARY } />
)
}
</View>
</View>
);
};

@ -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: {

@ -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.

@ -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<IProps, IState> {
*/
render() {
const {
_isBreakoutRoomsSupported,
_isSpeakerStatsDisabled,
_reactionsEnabled,
_width,
@ -150,6 +159,7 @@ class OverflowMenu extends PureComponent<IProps, IState> {
{!toolbarButtons.has('screensharing') && <ScreenSharingButton { ...buttonProps } />}
{!_isSpeakerStatsDisabled && <SpeakerStatsButton { ...buttonProps } />}
{!toolbarButtons.has('tileview') && <TileViewButton { ...buttonProps } />}
{_isBreakoutRoomsSupported && <BreakoutRoomsButton { ...buttonProps } />}
{/* @ts-ignore */}
<Divider style = { styles.divider as ViewStyle } />
<ClosedCaptionButton { ...buttonProps } />
@ -191,7 +201,10 @@ class OverflowMenu extends PureComponent<IProps, IState> {
* @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

Loading…
Cancel
Save