diff --git a/template/customization-api/typeDefinition.ts b/template/customization-api/typeDefinition.ts index 50745ca57..3750f3f43 100644 --- a/template/customization-api/typeDefinition.ts +++ b/template/customization-api/typeDefinition.ts @@ -86,6 +86,7 @@ export interface VideoCallInterface extends BeforeAndAfterInterface { captionPanel?: React.ComponentType; transcriptPanel?: React.ComponentType; virtualBackgroundPanel?: React.ComponentType; + breakoutRoomPanel?: React.ComponentType; customLayout?: (layouts: LayoutItem[]) => LayoutItem[]; wrapper?: React.ComponentType; customAgentInterface?: React.ComponentType; diff --git a/template/defaultConfig.js b/template/defaultConfig.js index 0098d55f2..67546b855 100644 --- a/template/defaultConfig.js +++ b/template/defaultConfig.js @@ -91,6 +91,7 @@ const DefaultConfig = { ENABLE_WAITING_ROOM_AUTO_APPROVAL: false, ENABLE_WAITING_ROOM_AUTO_REQUEST: false, ENABLE_TEXT_TRACKS: false, + ENABLE_BREAKOUT_ROOM: false, }; module.exports = DefaultConfig; diff --git a/template/global.d.ts b/template/global.d.ts index cf136cf05..4bc494e7c 100644 --- a/template/global.d.ts +++ b/template/global.d.ts @@ -178,6 +178,7 @@ interface ConfigInterface { ENABLE_WAITING_ROOM_AUTO_APPROVAL: boolean; ENABLE_WAITING_ROOM_AUTO_REQUEST: boolean; ENABLE_TEXT_TRACKS: boolean; + ENABLE_BREAKOUT_ROOM: boolean; } declare var $config: ConfigInterface; declare module 'customization' { diff --git a/template/src/components/Controls.tsx b/template/src/components/Controls.tsx index 0828af9e2..e04c1b622 100644 --- a/template/src/components/Controls.tsx +++ b/template/src/components/Controls.tsx @@ -104,6 +104,7 @@ import { toolbarItemVirtualBackgroundText, toolbarItemWhiteboardText, toolbarItemManageTextTracksText, + toolbarItemBreakoutRoomText, } from '../language/default-labels/videoCallScreenLabels'; import {LogSource, logger} from '../logger/AppBuilderLogger'; import {useModal} from '../utils/useModal'; @@ -285,6 +286,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => { const virtualBackgroundLabel = useString(toolbarItemVirtualBackgroundText)(); const chatLabel = useString(toolbarItemChatText)(); const inviteLabel = useString(toolbarItemInviteText)(); + const breakoutRoomLabel = useString(toolbarItemBreakoutRoomText)(); const peopleLabel = useString(toolbarItemPeopleText)(); const layoutLabel = useString(toolbarItemLayoutText)(); const {dispatch} = useContext(DispatchContext); @@ -834,6 +836,23 @@ const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => { }); } + // 14. Breakout Room + const canAccessBreakoutRoom = useControlPermissionMatrix('breakoutRoom'); + if (canAccessBreakoutRoom) { + actionMenuitems.push({ + componentName: 'breakoutRoom', + order: 14, + icon: 'participants', + iconColor: $config.SECONDARY_ACTION_COLOR, + textColor: $config.FONT_COLOR, + title: breakoutRoomLabel, + onPress: () => { + setActionMenuVisible(false); + setSidePanel(SidePanelType.BreakoutRoom); + }, + }); + } + useEffect(() => { if (isHovered) { setActionMenuVisible(true); diff --git a/template/src/components/breakout-room/BreakoutRoomView.tsx b/template/src/components/breakout-room/BreakoutRoomView.tsx new file mode 100644 index 000000000..cbff9bfb6 --- /dev/null +++ b/template/src/components/breakout-room/BreakoutRoomView.tsx @@ -0,0 +1,253 @@ +/* +******************************************** + Copyright © 2021 Agora Lab, Inc., all rights reserved. + AppBuilder and all associated components, source code, APIs, services, and documentation + (the “Materials”) are owned by Agora Lab, Inc. and its licensors. The Materials may not be + accessed, used, modified, or distributed for any purpose without a license from Agora Lab, Inc. + Use without a license or in violation of any license terms and conditions (including use for + any purpose competitive to Agora Lab, Inc.’s business) is strictly prohibited. For more + information visit https://appbuilder.agora.io. +********************************************* +*/ +import React, {useContext, useState} from 'react'; +import { + View, + StyleSheet, + ScrollView, + TouchableOpacity, + Text, +} from 'react-native'; +import ParticipantSectionTitle from '../participants/ParticipantSectionTitle'; +import AllHostParticipants from '../participants/AllHostParticipants'; +import {useString} from '../../utils/useString'; +import {isMobileUA, isWebInternal, useIsSmall} from '../../utils/common'; +import ChatContext from '../ChatContext'; +import CommonStyles from '../CommonStyles'; +import {useLayout, useContent, TertiaryButton, Spacer} from 'customization-api'; +import {getGridLayoutName} from '../../pages/video-call/DefaultLayouts'; +import {BreakoutRoomHeader} from '../../pages/video-call/SidePanelHeader'; +import useCaptionWidth from '../../../src/subComponents/caption/useCaptionWidth'; +import { + peoplePanelInThisMeetingLabel, + peoplePanelNoUsersJoinedContent, +} from '../../../src/language/default-labels/videoCallScreenLabels'; +import {useRoomInfo} from '../room-info/useRoomInfo'; +import {BreakoutRoomInfo, useBreakoutRoomInfo} from './useBreakoutRoomInfo'; + +const BreakoutRoomGroupCard = ({name, participants}: BreakoutRoomInfo) => { + const {defaultContent} = useContent(); + return ( + + + + {name} + + + {participants?.hosts?.length ? ( + <> + + Hosts + + {participants?.hosts?.map(uid => { + return ( + + {defaultContent[uid].name} + + ); + })} + + ) : ( + <> + )} + + {participants?.attendees?.length ? ( + <> + + Attendees + + {participants?.attendees?.map(uid => { + return ( + + {defaultContent[uid].name} + + ); + })} + + ) : ( + <> + )} + + + + Members{' - '} + {participants?.hosts?.length + participants?.attendees?.length} + + + + + ); +}; + +const BreakoutRoomView = props => { + const {activeUids, customContent} = useContent(); + const {onlineUsersCount} = useContext(ChatContext); + const {showHeader = true} = props; + const meetingParticpantsLabel = useString(peoplePanelInThisMeetingLabel)(); + const noUsersJoinedYet = useString(peoplePanelNoUsersJoinedContent)(); + const isSmall = useIsSmall(); + const [showMeetingParticipants, setShowMeetingParticipants] = useState(true); + const {currentLayout} = useLayout(); + const {transcriptHeight} = useCaptionWidth(); + const { + data: {isHost}, + } = useRoomInfo(); + + const {createBreakoutRoomGroup, breakoutRoomInfo, startBreakoutRoom} = + useBreakoutRoomInfo(); + + return ( + + {showHeader && } + + <> + setShowMeetingParticipants(!showMeetingParticipants)} + /> + {showMeetingParticipants ? ( + !customContent[i])} + isMobile={isSmall()} + updateActionSheet={props.updateActionSheet} + handleClose={props.handleClose} + hideControls={true} + showBreakoutRoomMenu={true} + from="breakout-room" + /> + ) : ( + <> + )} + + + createBreakoutRoomGroup()}> + + + Create Group + + + + {breakoutRoomInfo.map(props => { + return ; + })} + + {isHost && ( + + + {}} text={'CANCEL'} /> + + + + { + startBreakoutRoom(); + }} + text={'START'} + /> + + + )} + + ); +}; + +const style = StyleSheet.create({ + footer: { + width: '100%', + padding: 12, + height: 'auto', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: $config.CARD_LAYER_2_COLOR, + }, + bodyContainer: { + flex: 1, + }, +}); + +export default BreakoutRoomView; diff --git a/template/src/components/breakout-room/useBreakoutRoomInfo.tsx b/template/src/components/breakout-room/useBreakoutRoomInfo.tsx new file mode 100644 index 000000000..275d3f348 --- /dev/null +++ b/template/src/components/breakout-room/useBreakoutRoomInfo.tsx @@ -0,0 +1,180 @@ +/* +******************************************** + Copyright © 2021 Agora Lab, Inc., all rights reserved. + AppBuilder and all associated components, source code, APIs, services, and documentation + (the “Materials”) are owned by Agora Lab, Inc. and its licensors. The Materials may not be + accessed, used, modified, or distributed for any purpose without a license from Agora Lab, Inc. + Use without a license or in violation of any license terms and conditions (including use for + any purpose competitive to Agora Lab, Inc.’s business) is strictly prohibited. For more + information visit https://appbuilder.agora.io. +********************************************* +*/ +import {UidType} from '../../../agora-rn-uikit'; +import {createHook} from 'customization-implementation'; +import React, {useState, SetStateAction, useEffect, useContext} from 'react'; +import {randomNameGenerator} from '../../utils'; +import StorageContext from '../StorageContext'; +import getUniqueID from '../../utils/getUniqueID'; +import {logger, LogSource} from '../../logger/AppBuilderLogger'; +import {useRoomInfo} from 'customization-api'; + +export interface BreakoutRoomInfo { + internalGroupId: string; + name: string; + participants: { + hosts: UidType[]; + attendees: UidType[]; + }; +} + +export interface BreakoutRoomContextInterface { + breakoutRoomInfo: BreakoutRoomInfo[]; + setBreakoutRoomInfo: React.Dispatch>; + createBreakoutRoomGroup: (name?: string) => void; + addUserIntoGroup: ( + uid: UidType, + selectGroupId: string, + isHost: boolean, + ) => void; + startBreakoutRoom: () => void; +} + +const BreakoutRoomContext = React.createContext({ + breakoutRoomInfo: [], + setBreakoutRoomInfo: () => {}, + createBreakoutRoomGroup: () => {}, + addUserIntoGroup: () => {}, + startBreakoutRoom: () => {}, +}); + +const BreakoutRoomProvider = (props: {children: React.ReactNode}) => { + const {store} = useContext(StorageContext); + const { + data: {roomId}, + } = useRoomInfo(); + const [breakoutRoomInfo, setBreakoutRoomInfo] = useState( + [], + ); + + useEffect(() => { + console.log('debugging breakoutRoomInfo', breakoutRoomInfo); + }, [breakoutRoomInfo]); + + const startBreakoutRoom = () => { + const startReqTs = Date.now(); + const requestId = getUniqueID(); + + fetch(`${$config.BACKEND_ENDPOINT}/v1/breakout-room`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + authorization: store.token ? `Bearer ${store.token}` : '', + 'X-Request-Id': requestId, + 'X-Session-Id': logger.getSessionId(), + }, + body: JSON.stringify({ + passphrase: roomId.host, + session_id: randomNameGenerator(6), + breakout_room: breakoutRoomInfo, + }), + }) + .then((res: any) => { + console.log('debugging res', res); + debugger; + const endRequestTs = Date.now(); + const latency = endRequestTs - startReqTs; + if (res.status === 200) { + // logger.debug( + // LogSource.NetworkRest, + // 'breakout-room', + // 'start breakout successfully', + // { + // responseData: res, + // startReqTs, + // endRequestTs, + // latency, + // requestId, + // }, + // ); + } else { + // throw Error(`Internal server error ${res.status}`); + } + }) + .catch(err => { + console.log('debugging err', err); + debugger; + const endRequestTs = Date.now(); + const latency = endRequestTs - startReqTs; + // logger.error( + // LogSource.NetworkRest, + // 'recording_start', + // 'Error while start recording', + // JSON.stringify(err || {}), + // { + // startReqTs, + // endRequestTs, + // latency, + // requestId, + // }, + // ); + }); + }; + + const createBreakoutRoomGroup = name => { + let groupName = name ? name : `Group ${breakoutRoomInfo?.length + 1}`; + + setBreakoutRoomInfo(prevState => { + return [ + ...prevState, + { + name: groupName, + internalGroupId: randomNameGenerator(6), + participants: {hosts: [], attendees: []}, + }, + ]; + }); + }; + + const addUserIntoGroup = (uid, selectGroupId, isHost) => { + setBreakoutRoomInfo(prevState => { + const temp = []; + prevState.forEach((item, index) => { + const {internalGroupId} = item; + if (internalGroupId === selectGroupId) { + const data = { + ...prevState[index], + participants: { + hosts: isHost + ? prevState[index]?.participants?.hosts?.concat([uid]) + : prevState[index]?.participants?.hosts, + attendees: !isHost + ? prevState[index]?.participants?.attendees.concat([uid]) + : prevState[index]?.participants?.attendees, + }, + }; + temp.push(data); + } else { + temp.push(item); + } + }); + return temp; + }); + }; + + return ( + + {props.children} + + ); +}; + +const useBreakoutRoomInfo = createHook(BreakoutRoomContext); + +export {useBreakoutRoomInfo, BreakoutRoomProvider}; diff --git a/template/src/components/controls/useControlPermissionMatrix.tsx b/template/src/components/controls/useControlPermissionMatrix.tsx index 2daa52cf0..01a8dca14 100644 --- a/template/src/components/controls/useControlPermissionMatrix.tsx +++ b/template/src/components/controls/useControlPermissionMatrix.tsx @@ -15,7 +15,8 @@ export type ControlPermissionKey = | 'participantControl' | 'screenshareControl' | 'settingsControl' - | 'viewAllTextTracks'; + | 'viewAllTextTracks' + | 'breakoutRoom'; /** * ControlPermissionRule defines the properties used to evaluate permission rules. @@ -42,6 +43,7 @@ export const controlPermissionMatrix: Record< $config.ENABLE_MEETING_TRANSCRIPT && $config.ENABLE_TEXT_TRACKS && isWeb(), + breakoutRoom: ({isHost}) => isHost && $config.ENABLE_BREAKOUT_ROOM, }; export const useControlPermissionMatrix = ( diff --git a/template/src/components/participants/AllHostParticipants.tsx b/template/src/components/participants/AllHostParticipants.tsx index 4d537d7d5..73d992d02 100644 --- a/template/src/components/participants/AllHostParticipants.tsx +++ b/template/src/components/participants/AllHostParticipants.tsx @@ -73,12 +73,14 @@ export default function AllHostParticipants(props: any) { isAudienceUser={false} name={getParticipantName(localUid)} user={defaultContent[localUid]} - showControls={true} + showControls={props?.hideControls ? false : true} isHostUser={hostUids.indexOf(localUid) !== -1} key={localUid} isMobile={isMobile} handleClose={handleClose} updateActionSheet={updateActionSheet} + showBreakoutRoomMenu={props?.showBreakoutRoomMenu} + from={props?.from} /> {renderScreenShare(defaultContent[localUid])} @@ -104,12 +106,18 @@ export default function AllHostParticipants(props: any) { isAudienceUser={false} name={getParticipantName(uid)} user={defaultContent[uid]} - showControls={defaultContent[uid]?.type === 'rtc' && isHost} + showControls={ + defaultContent[uid]?.type === 'rtc' && + isHost && + (props?.hideControls ? false : true) + } isHostUser={hostUids.indexOf(uid) !== -1} key={uid} isMobile={isMobile} handleClose={handleClose} updateActionSheet={updateActionSheet} + showBreakoutRoomMenu={props?.showBreakoutRoomMenu} + from={props?.from} /> {renderScreenShare(defaultContent[uid])} diff --git a/template/src/components/participants/Participant.tsx b/template/src/components/participants/Participant.tsx index 3414521da..1cf416880 100644 --- a/template/src/components/participants/Participant.tsx +++ b/template/src/components/participants/Participant.tsx @@ -66,6 +66,12 @@ interface ParticipantInterface { updateActionSheet: (screenName: 'chat' | 'participants' | 'settings') => {}; uid?: UidType; screenUid?: UidType; + showBreakoutRoomMenu?: boolean; + from?: + | 'breakout-room' + | 'partcipant' + | 'screenshare-participant' + | 'video-tile'; } const Participant = (props: ParticipantInterface) => { @@ -106,7 +112,7 @@ const Participant = (props: ParticipantInterface) => { setActionMenuVisible={setActionMenuVisible} user={props.user} btnRef={moreIconRef} - from={'partcipant'} + from={props?.from || 'partcipant'} spotlightUid={spotlightUid} setSpotlightUid={setSpotlightUid} /> diff --git a/template/src/components/participants/UserActionMenuOptions.tsx b/template/src/components/participants/UserActionMenuOptions.tsx index 1e3343661..ff18a8654 100644 --- a/template/src/components/participants/UserActionMenuOptions.tsx +++ b/template/src/components/participants/UserActionMenuOptions.tsx @@ -77,13 +77,18 @@ import { DEFAULT_ACTION_KEYS, UserActionMenuItemsConfig, } from '../../atoms/UserActionMenuPreset'; +import {useBreakoutRoomInfo} from '../breakout-room/useBreakoutRoomInfo'; interface UserActionMenuOptionsOptionsProps { user: ContentInterface; actionMenuVisible: boolean; setActionMenuVisible: (actionMenuVisible: boolean) => void; btnRef: any; - from: 'partcipant' | 'screenshare-participant' | 'video-tile'; + from: + | 'partcipant' + | 'screenshare-participant' + | 'video-tile' + | 'breakout-room'; spotlightUid?: UidType; setSpotlightUid?: (uid: UidType) => void; items?: UserActionMenuItemsConfig; @@ -102,7 +107,8 @@ export default function UserActionMenuOptionsOptions( useState(false); const [actionMenuitems, setActionMenuitems] = useState([]); const {setSidePanel} = useSidePanel(); - const {user, actionMenuVisible, setActionMenuVisible, spotlightUid} = props; + const {user, actionMenuVisible, setActionMenuVisible, spotlightUid, from} = + props; const {currentLayout} = useLayout(); const {pinnedUid, activeUids, customContent, secondaryPinnedUid} = useContent(); @@ -146,7 +152,7 @@ export default function UserActionMenuOptionsOptions( const moreBtnSpotlightLabel = useString(moreBtnSpotlight); const {chatConnectionStatus} = useChatUIControls(); const chatErrNotConnectedText = useString(chatErrorNotConnected)(); - + const {breakoutRoomInfo, addUserIntoGroup} = useBreakoutRoomInfo(); useEffect(() => { customEvents.on('DisableChat', data => { // for other users @@ -166,6 +172,27 @@ export default function UserActionMenuOptionsOptions( useEffect(() => { const items: ActionMenuItem[] = []; + if (from === 'breakout-room' && $config.ENABLE_BREAKOUT_ROOM) { + if (breakoutRoomInfo && breakoutRoomInfo?.length) { + breakoutRoomInfo.map(({name, internalGroupId}, index) => { + items.push({ + order: index + 1, + icon: 'add', + onHoverIcon: 'add', + iconColor: $config.SECONDARY_ACTION_COLOR, + textColor: $config.SECONDARY_ACTION_COLOR, + title: `Move to ${name}`, + onPress: () => { + setActionMenuVisible(false); + addUserIntoGroup(user.uid, internalGroupId, false); + }, + }); + }); + setActionMenuitems(items); + } + return; + } + //Context of current user role const isSelf = user.uid === localuid; const isRemote = !isSelf; @@ -729,6 +756,8 @@ export default function UserActionMenuOptionsOptions( secondaryPinnedUid, currentLayout, spotlightUid, + from, + breakoutRoomInfo, ]); const {width: globalWidth, height: globalHeight} = useWindowDimensions(); diff --git a/template/src/language/default-labels/videoCallScreenLabels.ts b/template/src/language/default-labels/videoCallScreenLabels.ts index fe1e96211..3e8112bd6 100644 --- a/template/src/language/default-labels/videoCallScreenLabels.ts +++ b/template/src/language/default-labels/videoCallScreenLabels.ts @@ -92,6 +92,7 @@ export const toolbarItemLayoutOptionGridText = export const toolbarItemLayoutOptionSidebarText = 'toolbarItemLayoutOptionSidebarText'; export const toolbarItemInviteText = 'toolbarItemInviteText'; +export const toolbarItemBreakoutRoomText = 'toolbarItemBreakoutRoomText'; export const toolbarItemMicrophoneText = 'toolbarItemMicrophoneText'; export const toolbarItemMicrophoneTooltipText = @@ -213,6 +214,7 @@ export const chatPanelPrivateTabText = 'chatPanelPrivateTabText'; export const groupChatWelcomeContent = 'groupChatWelcomeContent'; export const peoplePanelHeaderText = 'peoplePanelHeaderText'; +export const breakoutRoomPanelHeaderText = 'breakoutRoomPanelHeaderText'; export const groupChatMeetingInputPlaceHolderText = 'groupChatMeetingInputPlaceHolderText'; @@ -557,6 +559,8 @@ export interface I18nVideoCallScreenLabelsInterface { [toolbarItemLayoutText]?: I18nBaseType; [toolbarItemInviteText]?: I18nBaseType; + [toolbarItemBreakoutRoomText]?: I18nBaseType; + [toolbarItemMicrophoneText]?: I18nBaseType; [toolbarItemMicrophoneTooltipText]?: I18nBaseType; [toolbarItemCameraText]?: I18nBaseType; @@ -644,6 +648,7 @@ export interface I18nVideoCallScreenLabelsInterface { [sttLanguageChangeInProgress]?: I18nBaseType; [peoplePanelHeaderText]?: I18nBaseType; + [breakoutRoomPanelHeaderText]?: I18nBaseType; [chatPanelGroupTabText]?: I18nBaseType; [chatPanelPrivateTabText]?: I18nBaseType; @@ -879,6 +884,7 @@ export const VideoCallScreenLabels: I18nVideoCallScreenLabelsInterface = { [toolbarItemSettingText]: 'Settings', [toolbarItemLayoutText]: 'Layout', [toolbarItemInviteText]: 'Invite', + [toolbarItemBreakoutRoomText]: 'Breakout Room', [toolbarItemMicrophoneText]: deviceStatus => { switch (deviceStatus) { @@ -1044,6 +1050,7 @@ export const VideoCallScreenLabels: I18nVideoCallScreenLabelsInterface = { [sttLanguageChangeInProgress]: 'Language Change is in progress...', [peoplePanelHeaderText]: 'People', + [breakoutRoomPanelHeaderText]: 'Breakout Room', [chatPanelGroupTabText]: 'Public', [chatPanelPrivateTabText]: 'Private', diff --git a/template/src/logger/AppBuilderLogger.tsx b/template/src/logger/AppBuilderLogger.tsx index 98161371b..1f47bcaa0 100644 --- a/template/src/logger/AppBuilderLogger.tsx +++ b/template/src/logger/AppBuilderLogger.tsx @@ -105,7 +105,8 @@ type LogType = { | 'recording_stop' | 'recordings_get' | 'recording_delete' - | 'ban_user'; + | 'ban_user' + | 'breakout-room'; [LogSource.Events]: 'CUSTOM_EVENTS' | 'RTM_EVENTS'; [LogSource.CustomizationAPI]: | 'Log' diff --git a/template/src/pages/VideoCall.tsx b/template/src/pages/VideoCall.tsx index 39bee5268..177e8970d 100644 --- a/template/src/pages/VideoCall.tsx +++ b/template/src/pages/VideoCall.tsx @@ -81,6 +81,7 @@ import {BeautyEffectProvider} from '../components/beauty-effect/useBeautyEffects import {UserActionMenuProvider} from '../components/useUserActionMenu'; import Toast from '../../react-native-toast-message'; import {AuthErrorCodes} from '../utils/common'; +import {BreakoutRoomProvider} from '../components/breakout-room/useBreakoutRoomInfo'; enum RnEncryptionEnum { /** @@ -534,27 +535,29 @@ const VideoCall: React.FC = () => { setCallActive={ setCallActive }> - - {callActive ? ( - - - - - - - - ) : $config.PRECALL ? ( - - - - ) : ( - <> - )} - + + + {callActive ? ( + + + + + + + + ) : $config.PRECALL ? ( + + + + ) : ( + <> + )} + + diff --git a/template/src/pages/video-call/SidePanelHeader.tsx b/template/src/pages/video-call/SidePanelHeader.tsx index a173cdeac..d7f8a1626 100644 --- a/template/src/pages/video-call/SidePanelHeader.tsx +++ b/template/src/pages/video-call/SidePanelHeader.tsx @@ -38,6 +38,7 @@ import { vbPanelHeading, } from '../../language/default-labels/precallScreenLabels'; import { + breakoutRoomPanelHeaderText, chatPanelGroupTabText, chatPanelPrivateTabText, peoplePanelHeaderText, @@ -93,6 +94,22 @@ export const PeopleHeader = () => { ); }; +export const BreakoutRoomHeader = () => { + const headerText = useString(breakoutRoomPanelHeaderText)(); + const {setSidePanel} = useSidePanel(); + return ( + {headerText} + } + trailingIconName="close" + trailingIconOnPress={() => { + setSidePanel(SidePanelType.None); + }} + /> + ); +}; + export const ChatHeader = () => { const { unreadGroupMessageCount, diff --git a/template/src/pages/video-call/VideoCallScreen.tsx b/template/src/pages/video-call/VideoCallScreen.tsx index 4093cc44e..c007079cf 100644 --- a/template/src/pages/video-call/VideoCallScreen.tsx +++ b/template/src/pages/video-call/VideoCallScreen.tsx @@ -55,6 +55,7 @@ import {useIsRecordingBot} from '../../subComponents/recording/useIsRecordingBot import {ToolbarPresetProps} from '../../atoms/ToolbarPreset'; import CustomSidePanelView from '../../components/CustomSidePanel'; import {useControlPermissionMatrix} from '../../components/controls/useControlPermissionMatrix'; +import BreakoutRoomView from '../../components/breakout-room/BreakoutRoomView'; const VideoCallScreen = () => { useFindActiveSpeaker(); @@ -73,6 +74,7 @@ const VideoCallScreen = () => { VideocallComponent, BottombarComponent, ParticipantsComponent, + BreakoutRoomComponent, TranscriptComponent, CaptionComponent, VirtualBackgroundComponent, @@ -99,6 +101,7 @@ const VideoCallScreen = () => { CaptionComponent: React.ComponentType; VirtualBackgroundComponent: React.ComponentType; SettingsComponent: React.ComponentType; + BreakoutRoomComponent: React.ComponentType; TopbarComponent: React.ComponentType; VideocallBeforeView: React.ComponentType; VideocallAfterView: React.ComponentType; @@ -118,6 +121,7 @@ const VideoCallScreen = () => { CaptionComponent: CaptionContainer, VirtualBackgroundComponent: VBPanel, SettingsComponent: SettingsView, + BreakoutRoomComponent: BreakoutRoomView, VideocallAfterView: React.Fragment, VideocallBeforeView: React.Fragment, VideocallWrapper: React.Fragment, @@ -236,6 +240,15 @@ const VideoCallScreen = () => { data?.components?.videoCall.participantsPanel; } + if ( + data?.components?.videoCall.breakoutRoomPanel && + typeof data?.components?.videoCall.breakoutRoomPanel !== 'object' && + isValidReactComponent(data?.components?.videoCall.breakoutRoomPanel) + ) { + components.BreakoutRoomComponent = + data?.components?.videoCall.breakoutRoomPanel; + } + if ( data?.components?.videoCall.transcriptPanel && typeof data?.components?.videoCall.transcriptPanel !== 'object' && @@ -428,6 +441,11 @@ const VideoCallScreen = () => { ) : ( <> )} + {sidePanel === SidePanelType.BreakoutRoom ? ( + + ) : ( + <> + )} {sidePanel === SidePanelType.Transcript ? ( $config.ENABLE_MEETING_TRANSCRIPT ? ( diff --git a/template/src/subComponents/SidePanelEnum.tsx b/template/src/subComponents/SidePanelEnum.tsx index 0ac6cec07..f784930cc 100644 --- a/template/src/subComponents/SidePanelEnum.tsx +++ b/template/src/subComponents/SidePanelEnum.tsx @@ -16,4 +16,5 @@ export enum SidePanelType { Settings = 'Settings', Transcript = 'Transcript', VirtualBackground = 'VirtualBackground', + BreakoutRoom = 'BreakoutRoom', }