Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 36 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c6b3c52
feat(toolbox/native): created breakout rooms button
Calinteodor Oct 4, 2023
f908dad
feat(toolbox/native): updated menu to use breakout rooms button
Calinteodor Oct 4, 2023
be2f18b
feat(mobile/navigation): alpha sort and added breakout rooms route
Calinteodor Oct 4, 2023
606fdc8
feat(breakout-rooms/native): updated visibility and navigation for me…
Calinteodor Oct 4, 2023
e636fc9
feat(toolbox/native): fixed button translation
Calinteodor Oct 4, 2023
44c4e22
feat(mobile/navigation): updated route
Calinteodor Oct 4, 2023
8f61668
feat(mobile/navigation): alpha sort and created breakout rooms screen…
Calinteodor Oct 4, 2023
eac37a3
feat(mobile/navigation): added BreakoutRooms to the nav container
Calinteodor Oct 4, 2023
c999a7b
feat(participants-pane/native): moved breakout rooms content to screen
Calinteodor Oct 4, 2023
1101fc3
feat(participants-pane/native): fixed scroll inside scroll and update…
Calinteodor Oct 4, 2023
4d31e78
feat(participants-pane/native): fixed/updated translations
Calinteodor Oct 4, 2023
e951526
feat(participants-pane/native): fixed long lobby part name case
Calinteodor Oct 4, 2023
7686810
feat(participants-pane/native): syntax rework
Calinteodor Oct 4, 2023
c8b158b
feat(participants-pane/native): converted MeetingParticipantList to f…
Calinteodor Oct 5, 2023
a534771
feat(participants-pane/native): syntax rework and removed some empty …
Calinteodor Oct 5, 2023
42eb20e
feat(participants-pane/native): participants footer style fix
Calinteodor Oct 5, 2023
8036b5b
feat(participants-pane/native): fixed ts errors
Calinteodor Oct 5, 2023
8855c53
feat(participants-pane/native): some ts types and comments
Calinteodor Oct 6, 2023
977b336
feat(breakout-rooms/native): moved all related components to feature
Calinteodor Oct 6, 2023
9980d0c
feat(breakout-rooms): alpha sort translations
Calinteodor Oct 9, 2023
ce755ec
feat(breakout-rooms): fixed linter
Calinteodor Oct 9, 2023
82c7ba6
feat(breakout-rooms): fixed linter pt. 2
Calinteodor Oct 9, 2023
0e3320c
feat(breakout-rooms): fixed linter pt. 3
Calinteodor Oct 9, 2023
3b61d89
feat(breakout-rooms): fixed linter pt. 4
Calinteodor Oct 9, 2023
7c8ed6f
feat(breakout-rooms): removed search from breakout rooms
Calinteodor Oct 9, 2023
7bdd7c7
feat(breakout-rooms): removed unused hook
Calinteodor Oct 9, 2023
bf027e1
feat(breakout-rooms): updated translations
Calinteodor Oct 11, 2023
a09ec19
feat(breakout-rooms): fixed and removed unused styles for screen
Calinteodor Oct 11, 2023
a145456
feat(participants-pane/native): fixed styles for meeting and lobby pa…
Calinteodor Oct 11, 2023
584dcb7
feat(participants-pane): fixed linter
Calinteodor Oct 11, 2023
6e7bacd
ios: reverted podfile.lock change
Calinteodor Oct 11, 2023
3baff80
feat(participants-pane): removed breakout rooms related onLongPress e…
Calinteodor Oct 11, 2023
665c776
feat(breakout-rooms): fixed linter
Calinteodor Oct 11, 2023
f94cc68
feat(breakout-rooms): fixed styles
Calinteodor Oct 11, 2023
7401a7c
feat(participants-pane): fixed background color
Calinteodor Oct 11, 2023
293003b
feat(participants-pane): fixed linter
Calinteodor Oct 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions lang/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions react/features/base/flags/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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 }>
Expand All @@ -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 }
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {

Expand Down
68 changes: 68 additions & 0 deletions react/features/breakout-rooms/components/native/BreakoutRooms.tsx
Original file line number Diff line number Diff line change
@@ -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 = { () => (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat!

<>
{ 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;
Original file line number Diff line number Diff line change
@@ -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));
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -19,10 +17,7 @@ interface IProps {
*/
room: IRoom;

/**
* Participants search string.
*/
searchString: string;
roomId: string;
}

/**
Expand All @@ -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(() => {
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
Loading