From 1fe896fe2207878afc2e795f4707cc0a046fd51c Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 16 Aug 2023 15:47:27 +0530 Subject: [PATCH] feat(react-native): add CallContent component with customization of inner components (#933) This PR primarily focuses on the introduction of the CallContent component which is also the root component of the customization of inner components. This PR also removes the `fullscreen` mode from `LocalParticipantView`. --- .../components/CallParticipantsGrid.test.tsx | 55 +++---- .../CallParticipantsSpotlight.test.tsx | 34 ++++- .../Call/CallContent/CallContent.tsx | 135 ++++++++++++++++++ .../src/components/Call/CallContent/index.ts | 1 + .../Call/CallLayout/CallParticipantsGrid.tsx | 70 +++++---- .../CallLayout/CallParticipantsSpotlight.tsx | 81 +++++------ .../Call/CallTopView/CallTopView.tsx | 7 +- .../src/components/Call/index.ts | 1 + .../LocalParticipantView/index.tsx | 67 +-------- .../ParticipantView/ParticipantLabel.tsx | 66 ++++----- .../ParticipantView/ParticipantReaction.tsx | 6 +- .../react-native-sdk/src/constants/TestIds.ts | 1 - .../dogfood/src/components/ActiveCall.tsx | 117 ++++----------- .../src/components/CallControlsComponent.tsx | 58 ++++++++ .../dogfood/src/components/MeetingUI.tsx | 14 +- 15 files changed, 395 insertions(+), 318 deletions(-) create mode 100644 packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx create mode 100644 packages/react-native-sdk/src/components/Call/CallContent/index.ts create mode 100644 sample-apps/react-native/dogfood/src/components/CallControlsComponent.tsx diff --git a/packages/react-native-sdk/__tests__/components/CallParticipantsGrid.test.tsx b/packages/react-native-sdk/__tests__/components/CallParticipantsGrid.test.tsx index 7e3a72abbe..00436ef8be 100644 --- a/packages/react-native-sdk/__tests__/components/CallParticipantsGrid.test.tsx +++ b/packages/react-native-sdk/__tests__/components/CallParticipantsGrid.test.tsx @@ -5,7 +5,13 @@ import mockParticipant from '../mocks/participant'; import { ComponentTestIds } from '../../src/constants/TestIds'; import { mockCall } from '../mocks/call'; import { act, render, screen, within } from '../utils/RNTLTools'; -import { CallParticipantsGrid } from '../../src/components'; +import { + CallParticipantsGrid, + CallParticipantsList, + LocalParticipantView, + ParticipantView, + VideoRenderer, +} from '../../src/components'; import { ViewToken } from 'react-native'; console.warn = jest.fn(); @@ -19,26 +25,6 @@ enum P_IDS { } describe('CallParticipantsGrid', () => { - it('should render an local video when only 1 participant present in the call', async () => { - const call = mockCall(mockClientWithUser(), [ - mockParticipant({ - isLocalParticipant: true, - sessionId: P_IDS.LOCAL_1, - userId: P_IDS.LOCAL_1, - publishedTracks: [SfuModels.TrackType.AUDIO], - videoStream: null, - }), - ]); - - render(, { - call, - }); - - expect( - await screen.findByTestId(ComponentTestIds.LOCAL_PARTICIPANT_FULLSCREEN), - ).toBeVisible(); - }); - it('should render an call participants with grid mode with 2 participants when no screen shared', async () => { const call = mockCall(mockClientWithUser(), [ mockParticipant({ @@ -53,9 +39,17 @@ describe('CallParticipantsGrid', () => { }), ]); - render(, { - call, - }); + render( + , + { + call, + }, + ); expect( await screen.findByTestId(ComponentTestIds.CALL_PARTICIPANTS_GRID), @@ -108,9 +102,16 @@ describe('CallParticipantsGrid', () => { }), ]); - render(, { - call, - }); + render( + , + { + call, + }, + ); const visibleParticipantsItems = call.state.participants.map((p) => ({ key: p.sessionId, diff --git a/packages/react-native-sdk/__tests__/components/CallParticipantsSpotlight.test.tsx b/packages/react-native-sdk/__tests__/components/CallParticipantsSpotlight.test.tsx index 6b134bc37b..458a66c806 100644 --- a/packages/react-native-sdk/__tests__/components/CallParticipantsSpotlight.test.tsx +++ b/packages/react-native-sdk/__tests__/components/CallParticipantsSpotlight.test.tsx @@ -5,7 +5,13 @@ import mockParticipant from '../mocks/participant'; import { ComponentTestIds } from '../../src/constants/TestIds'; import { mockCall } from '../mocks/call'; import { render, screen } from '../utils/RNTLTools'; -import { CallParticipantsSpotlight } from '../../src/components'; +import { + CallParticipantsList, + CallParticipantsSpotlight, + ParticipantLabel, + ParticipantView, + VideoRenderer, +} from '../../src/components'; console.warn = jest.fn(); jest.useFakeTimers(); @@ -38,9 +44,17 @@ describe('CallParticipantsSpotlight', () => { }), ]); - render(, { - call, - }); + render( + , + { + call, + }, + ); expect( await screen.findByTestId(ComponentTestIds.PARTICIPANT_SCREEN_SHARING), @@ -68,9 +82,15 @@ describe('CallParticipantsSpotlight', () => { }), ]); - render(, { - call, - }); + render( + , + { + call, + }, + ); expect( await screen.findByTestId(ComponentTestIds.CALL_PARTICIPANTS_SPOTLIGHT), diff --git a/packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx b/packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx new file mode 100644 index 0000000000..194f4aff5b --- /dev/null +++ b/packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx @@ -0,0 +1,135 @@ +import React, { useEffect, useRef } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { + CallTopView as DefaultCallTopView, + ParticipantsInfoBadge as DefaultParticipantsInfoBadge, + CallTopViewProps, +} from '../CallTopView'; +import { + CallParticipantsGrid, + CallParticipantsGridProps, + CallParticipantsSpotlight, +} from '../CallLayout'; +import { + CallControls as DefaultCallControls, + CallControlProps, +} from '../CallControls'; +import { CallParticipantsList as DefaultCallParticipantsList } from '../CallParticipantsList'; +import { + useCall, + useHasOngoingScreenShare, +} from '@stream-io/video-react-bindings'; +import { + ParticipantNetworkQualityIndicator as DefaultParticipantNetworkQualityIndicator, + ParticipantReaction as DefaultParticipantReaction, + ParticipantLabel as DefaultParticipantLabel, + ParticipantVideoFallback as DefaultParticipantVideoFallback, + VideoRenderer as DefaultVideoRenderer, + ParticipantView as DefaultParticipantView, + LocalParticipantView as DefaultLocalParticipantView, +} from '../../Participant'; +import { CallingState } from '@stream-io/video-client'; +import { useIncallManager } from '../../../hooks'; + +export type CallParticipantsComponentProps = Pick< + CallParticipantsGridProps, + | 'CallParticipantsList' + | 'LocalParticipantView' + | 'ParticipantLabel' + | 'ParticipantNetworkQualityIndicator' + | 'ParticipantReaction' + | 'ParticipantVideoFallback' + | 'ParticipantView' + | 'VideoRenderer' +> & { + /** + * Component to customize the CallTopView component. + */ + CallTopView?: React.ComponentType; + /** + * Component to customize the CallControls component. + */ + CallControls?: React.ComponentType; +}; + +export type CallContentProps = Pick & + Pick< + CallTopViewProps, + 'onBackPressed' | 'onParticipantInfoPress' | 'ParticipantsInfoBadge' + > & + CallParticipantsComponentProps & { + /** + * This switches the participant's layout between the grid and the spotlight mode. + */ + layout?: 'grid' | 'spotlight'; + }; + +export const CallContent = ({ + onBackPressed, + onParticipantInfoPress, + onHangupCallHandler, + CallParticipantsList = DefaultCallParticipantsList, + LocalParticipantView = DefaultLocalParticipantView, + ParticipantLabel = DefaultParticipantLabel, + ParticipantNetworkQualityIndicator = DefaultParticipantNetworkQualityIndicator, + ParticipantReaction = DefaultParticipantReaction, + ParticipantVideoFallback = DefaultParticipantVideoFallback, + ParticipantView = DefaultParticipantView, + ParticipantsInfoBadge = DefaultParticipantsInfoBadge, + VideoRenderer = DefaultVideoRenderer, + CallTopView = DefaultCallTopView, + CallControls = DefaultCallControls, + layout, +}: CallContentProps) => { + const hasScreenShare = useHasOngoingScreenShare(); + const showSpotlightLayout = hasScreenShare || layout === 'spotlight'; + /** + * This hook is used to handle IncallManager specs of the application. + */ + useIncallManager({ media: 'video', auto: true }); + + const call = useCall(); + const activeCallRef = useRef(call); + activeCallRef.current = call; + + useEffect(() => { + return () => { + if (activeCallRef.current?.state.callingState !== CallingState.LEFT) { + activeCallRef.current?.leave(); + } + }; + }, []); + + const participantViewProps: CallParticipantsComponentProps = { + CallParticipantsList, + LocalParticipantView, + ParticipantLabel, + ParticipantNetworkQualityIndicator, + ParticipantReaction, + ParticipantVideoFallback, + ParticipantView, + VideoRenderer, + }; + + return ( + + + + {showSpotlightLayout ? ( + + ) : ( + + )} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { flex: 1 }, +}); diff --git a/packages/react-native-sdk/src/components/Call/CallContent/index.ts b/packages/react-native-sdk/src/components/Call/CallContent/index.ts new file mode 100644 index 0000000000..24ee573079 --- /dev/null +++ b/packages/react-native-sdk/src/components/Call/CallContent/index.ts @@ -0,0 +1 @@ +export * from './CallContent'; diff --git a/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsGrid.tsx b/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsGrid.tsx index 5751584dc3..c23d9944e8 100644 --- a/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsGrid.tsx +++ b/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsGrid.tsx @@ -2,21 +2,9 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; import { useCallStateHooks } from '@stream-io/video-react-bindings'; import { useDebouncedValue } from '../../../utils/hooks/useDebouncedValue'; -import { - CallParticipantsList, - CallParticipantsListProps, -} from '../CallParticipantsList/CallParticipantsList'; +import { CallParticipantsListProps } from '../CallParticipantsList/CallParticipantsList'; import { ComponentTestIds } from '../../../constants/TestIds'; -import { - ParticipantNetworkQualityIndicator as DefaultParticipantNetworkQualityIndicator, - ParticipantReaction as DefaultParticipantReaction, - ParticipantLabel as DefaultParticipantLabel, - ParticipantVideoFallback as DefaultParticipantVideoFallback, - VideoRenderer as DefaultVideoRenderer, - ParticipantView as DefaultParticipantView, - LocalParticipantView as DefaultLocalParticipantView, - LocalParticipantViewProps, -} from '../../Participant'; +import { LocalParticipantViewProps } from '../../Participant'; /** * Props for the CallParticipantsGrid component. @@ -31,51 +19,57 @@ export type CallParticipantsGridProps = Pick< | 'VideoRenderer' > & { /** - * Component to customize the local participant view. + * Component to customize the LocalParticipantView. */ LocalParticipantView?: React.ComponentType; + /** + * Component to customize the CallParticipantsList. + */ + CallParticipantsList?: React.ComponentType; }; /** * Component used to display the list of participants in a grid mode. */ export const CallParticipantsGrid = ({ - ParticipantLabel = DefaultParticipantLabel, - ParticipantNetworkQualityIndicator = DefaultParticipantNetworkQualityIndicator, - ParticipantReaction = DefaultParticipantReaction, - ParticipantVideoFallback = DefaultParticipantVideoFallback, - ParticipantView = DefaultParticipantView, - VideoRenderer = DefaultVideoRenderer, - LocalParticipantView = DefaultLocalParticipantView, + CallParticipantsList, + ParticipantLabel, + ParticipantNetworkQualityIndicator, + ParticipantReaction, + ParticipantVideoFallback, + ParticipantView, + VideoRenderer, + LocalParticipantView, }: CallParticipantsGridProps) => { const { useRemoteParticipants, useParticipants } = useCallStateHooks(); const _remoteParticipants = useRemoteParticipants(); const allParticipants = useParticipants(); const remoteParticipants = useDebouncedValue(_remoteParticipants, 300); // we debounce the remote participants to avoid unnecessary rerenders that happen when participant tracks are all subscribed simultaneously - const showFloatingView = remoteParticipants.length < 3; - const isUserAloneInCall = remoteParticipants?.length === 0; - const participants = showFloatingView ? remoteParticipants : allParticipants; + const showFloatingView = + remoteParticipants.length > 0 && remoteParticipants.length < 3; - if (showFloatingView && isUserAloneInCall) { - return ; - } + const participants = showFloatingView ? remoteParticipants : allParticipants; return ( - {showFloatingView && } - + {showFloatingView && LocalParticipantView && } + {CallParticipantsList && ( + + )} ); }; diff --git a/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx b/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx index 78f4bf4328..34b5269698 100644 --- a/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx +++ b/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx @@ -9,19 +9,7 @@ import { StyleSheet, View } from 'react-native'; import { theme } from '../../../theme'; import { useDebouncedValue } from '../../../utils/hooks/useDebouncedValue'; import { ComponentTestIds } from '../../../constants/TestIds'; -import { - CallParticipantsList, - CallParticipantsListProps, -} from '../CallParticipantsList/CallParticipantsList'; -import { - LocalParticipantView, - ParticipantNetworkQualityIndicator as DefaultParticipantNetworkQualityIndicator, - ParticipantReaction as DefaultParticipantReaction, - ParticipantLabel as DefaultParticipantLabel, - ParticipantVideoFallback as DefaultParticipantVideoFallback, - VideoRenderer as DefaultVideoRenderer, - ParticipantView as DefaultParticipantView, -} from '../../Participant'; +import { CallParticipantsListProps } from '../CallParticipantsList/CallParticipantsList'; /** * Props for the CallParticipantsSpotlight component. @@ -34,7 +22,12 @@ export type CallParticipantsSpotlightProps = Pick< | 'ParticipantVideoFallback' | 'ParticipantView' | 'VideoRenderer' ->; +> & { + /** + * Component to customize the CallParticipantsList. + */ + CallParticipantsList?: React.ComponentType; +}; const hasScreenShare = (p: StreamVideoParticipant) => p.publishedTracks.includes(SfuModels.TrackType.SCREEN_SHARE); @@ -44,12 +37,13 @@ const hasScreenShare = (p: StreamVideoParticipant) => * This can be used when you want to render the screen sharing stream. */ export const CallParticipantsSpotlight = ({ - ParticipantLabel = DefaultParticipantLabel, - ParticipantNetworkQualityIndicator = DefaultParticipantNetworkQualityIndicator, - ParticipantReaction = DefaultParticipantReaction, - ParticipantVideoFallback = DefaultParticipantVideoFallback, - ParticipantView = DefaultParticipantView, - VideoRenderer = DefaultVideoRenderer, + CallParticipantsList, + ParticipantLabel, + ParticipantNetworkQualityIndicator, + ParticipantReaction, + ParticipantVideoFallback, + ParticipantView, + VideoRenderer, }: CallParticipantsSpotlightProps) => { const { useParticipants, useRemoteParticipants } = useCallStateHooks(); const _allParticipants = useParticipants({ @@ -61,19 +55,15 @@ export const CallParticipantsSpotlight = ({ const isScreenShareOnSpotlight = hasScreenShare(participantInSpotlight); const isUserAloneInCall = _remoteParticipants?.length === 0; - if (isUserAloneInCall) { - return ; - } - return ( - {participantInSpotlight && ( + {participantInSpotlight && ParticipantView && ( )} - - - + {!isUserAloneInCall && ( + + {CallParticipantsList && ( + + )} + + )} ); }; @@ -110,6 +104,9 @@ const styles = StyleSheet.create({ paddingVertical: theme.padding.sm, backgroundColor: theme.light.static_grey, }, + fullScreen: { + flex: 1, + }, participantView: { flex: 2, overflow: 'hidden', diff --git a/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx b/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx index 6c17a8075f..9778ee2884 100644 --- a/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx +++ b/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx @@ -7,10 +7,7 @@ import { ViewStyle, Pressable, } from 'react-native'; -import { - ParticipantsInfoBadge as DefaultParticipantsInfoBadge, - ParticipantsInfoBadgeProps, -} from './ParticipantsInfoBadge'; +import { ParticipantsInfoBadgeProps } from './ParticipantsInfoBadge'; import { theme } from '../../../theme'; import { Back } from '../../../icons/Back'; import { Z_INDEX } from '../../../constants'; @@ -48,7 +45,7 @@ export const CallTopView = ({ onParticipantInfoPress, title, style, - ParticipantsInfoBadge = DefaultParticipantsInfoBadge, + ParticipantsInfoBadge, }: CallTopViewProps) => { const { useCallCallingState } = useCallStateHooks(); const callingState = useCallCallingState(); diff --git a/packages/react-native-sdk/src/components/Call/index.ts b/packages/react-native-sdk/src/components/Call/index.ts index 2fb389a722..bdb5f0d359 100644 --- a/packages/react-native-sdk/src/components/Call/index.ts +++ b/packages/react-native-sdk/src/components/Call/index.ts @@ -1,3 +1,4 @@ +export * from './CallContent'; export * from './CallTopView'; export * from './CallLayout'; export * from './CallControls'; diff --git a/packages/react-native-sdk/src/components/Participant/LocalParticipantView/index.tsx b/packages/react-native-sdk/src/components/Participant/LocalParticipantView/index.tsx index 5e7edc61ab..b9d54d44d6 100644 --- a/packages/react-native-sdk/src/components/Participant/LocalParticipantView/index.tsx +++ b/packages/react-native-sdk/src/components/Participant/LocalParticipantView/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import { useCallStateHooks } from '@stream-io/video-react-bindings'; import { SfuModels } from '@stream-io/video-client'; import { LOCAL_VIDEO_VIEW_STYLE, Z_INDEX } from '../../../constants'; @@ -8,7 +8,6 @@ import { VideoSlash } from '../../../icons'; import { useMediaStreamManagement } from '../../../providers'; import { theme } from '../../../theme'; import { useDebouncedValue } from '../../../utils/hooks'; -import { Avatar } from '../../utility'; import { ParticipantReaction } from '../ParticipantView/ParticipantReaction'; import { FloatingViewAlignment } from './FloatingView/common'; import FloatingView from './FloatingView'; @@ -17,34 +16,12 @@ import { RTCView } from 'react-native-webrtc'; /** * Props to be passed for the LocalVideoView component. */ -export interface LocalParticipantViewProps { - /** - * An optional style object to be applied to the local video view - * @defaultValue - * The default is `{ - * position: 'absolute', - * height: 140, - * width: 80, - * right: 2 * theme.spacing.lg, - * top: 100, - * borderRadius: theme.rounded.sm, - * zIndex: 1, - * }` - */ - style?: StyleProp; - - /** - * The layout of the local video view controls weather the local participant's video will be rendered in full screen or floating - * @defaultValue 'floating' - */ - layout?: 'floating' | 'fullscreen'; -} +export type LocalParticipantViewProps = {}; /** * A component to render the local participant's video. */ -export const LocalParticipantView = (props: LocalParticipantViewProps) => { - const { layout = 'floating' } = props; +export const LocalParticipantView = () => { const { useLocalParticipant } = useCallStateHooks(); const localParticipant = useLocalParticipant(); const { isCameraOnFrontFacingMode } = useMediaStreamManagement(); @@ -71,30 +48,6 @@ export const LocalParticipantView = (props: LocalParticipantViewProps) => { const showBlankStream = isCameraOnFrontFacingMode !== debouncedCameraOnFrontFacingMode; - if (layout === 'fullscreen') { - return ( - - - - - {isVideoMuted ? ( - - ) : showBlankStream ? ( - - ) : ( - - )} - - ); - } - return ( { - const { name, userId, pin, sessionId } = participant; + const { name, userId, pin, sessionId, publishedTracks, isLocalParticipant } = + participant; const call = useCall(); - const { isAudioMuted, isVideoMuted } = useMediaStreamManagement(); const { t } = useI18n(); - const participantLabel = name ?? userId; + const participantName = name ?? userId; + const participantLabel = isLocalParticipant ? t('You') : participantName; const isPinningEnabled = pin?.isLocalPin; + const isAudioMuted = !publishedTracks.includes(SfuModels.TrackType.AUDIO); + const isVideoMuted = !publishedTracks.includes(SfuModels.TrackType.VIDEO); const unPinParticipantHandler = () => { call?.unpin(sessionId); }; - if (videoMode === 'video') { - return ( - - - {participantLabel} - - {isAudioMuted && ( - - - - )} - {isVideoMuted && ( - - - - )} - {isPinningEnabled && ( - - - - )} - - ); - } else if (videoMode === 'screen') { + if (videoMode === 'screen') { return ( ; + return ( + + + {participantLabel} + + {isAudioMuted && ( + + + + )} + {isVideoMuted && ( + + + + )} + {isPinningEnabled && ( + + + + )} + + ); }; const styles = StyleSheet.create({ status: { - alignSelf: 'flex-end', flexDirection: 'row', alignItems: 'center', padding: theme.padding.sm, diff --git a/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantReaction.tsx b/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantReaction.tsx index 7528d8b014..60d3004b2d 100644 --- a/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantReaction.tsx +++ b/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantReaction.tsx @@ -24,9 +24,11 @@ export type ParticipantReactionProps = Pick< /** * This component is used to display the current participant reaction. */ -export const ParticipantReaction = (props: ParticipantReactionProps) => { +export const ParticipantReaction = ({ + participant, + hideAfterTimeoutInMs = 5500, +}: ParticipantReactionProps) => { const { supportedReactions } = StreamVideoRN.getConfig(); - const { participant, hideAfterTimeoutInMs = 5500 } = props; const { reaction, sessionId } = participant; const call = useCall(); const [isShowing, setIsShowing] = useState(false); diff --git a/packages/react-native-sdk/src/constants/TestIds.ts b/packages/react-native-sdk/src/constants/TestIds.ts index 1374787152..166bcdbb30 100644 --- a/packages/react-native-sdk/src/constants/TestIds.ts +++ b/packages/react-native-sdk/src/constants/TestIds.ts @@ -10,7 +10,6 @@ export enum ComponentTestIds { CALL_PARTICIPANTS_SPOTLIGHT = 'call-participants-spotlight', CALL_PARTICIPANTS_GRID = 'call-participants-grid', LOCAL_PARTICIPANT = 'local-participant', - LOCAL_PARTICIPANT_FULLSCREEN = 'local-participants-fullscreen', PARTICIPANT_MEDIA_STREAM = 'participant-media-stream', PARTICIPANTS_INFO = 'participants-info', PARTICIPANT_SCREEN_SHARING = 'participant-screen-sharing', diff --git a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx index e86f260a8a..1ab81fb971 100644 --- a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx +++ b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx @@ -1,67 +1,40 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { - CallingState, - useCall, - useIncallManager, - theme, - ReactionButton, - ChatButton, - ToggleVideoPublishingButton, - ToggleAudioPublishingButton, - ToggleCameraFaceButton, - HangUpCallButton, - CallTopView, - ChatButtonProps, - useCallStateHooks, - CallParticipantsSpotlight, - CallParticipantsGrid, -} from '@stream-io/video-react-native-sdk'; -import { ActivityIndicator, StyleSheet, View } from 'react-native'; -import { appTheme } from '../theme'; -import { - SafeAreaView, - useSafeAreaInsets, -} from 'react-native-safe-area-context'; +import React, { useCallback, useState } from 'react'; +import { useCall, CallContent } from '@stream-io/video-react-native-sdk'; +import { ActivityIndicator, StyleSheet } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; import { ParticipantsInfoList } from './ParticipantsInfoList'; -import { Z_INDEX } from '../constants'; +import { + CallControlsComponent, + CallControlsComponentProps, +} from './CallControlsComponent'; -type ActiveCallProps = { - chatButton?: ChatButtonProps; - onHangupCallHandler?: () => void; +type ActiveCallProps = CallControlsComponentProps & { onBackPressed?: () => void; }; export const ActiveCall = ({ - chatButton, + onChatOpenHandler, onBackPressed, onHangupCallHandler, + unreadCountIndicator, }: ActiveCallProps) => { const [isCallParticipantsVisible, setIsCallParticipantsVisible] = useState(false); const call = useCall(); - const { useHasOngoingScreenShare } = useCallStateHooks(); - const hasScreenShare = useHasOngoingScreenShare(); - const activeCallRef = useRef(call); - activeCallRef.current = call; const onOpenCallParticipantsInfo = () => { setIsCallParticipantsVisible(true); }; - useEffect(() => { - return () => { - if (activeCallRef.current?.state.callingState !== CallingState.LEFT) { - activeCallRef.current?.leave(); - } - }; - }, []); - - /** - * This hook is used to handle IncallManager specs of the application. - */ - useIncallManager({ media: 'video', auto: true }); - - const { bottom } = useSafeAreaInsets(); + const CustomControlsComponent = useCallback(() => { + return ( + + ); + }, [onChatOpenHandler, onHangupCallHandler, unreadCountIndicator]); if (!call) { return ; @@ -69,36 +42,11 @@ export const ActiveCall = ({ return ( - - - {hasScreenShare ? ( - - ) : ( - - )} - - {/* Since we want the chat and the reaction button the entire call controls is customized */} - - - - - - - - + void; + onHangupCallHandler?: () => void; + unreadCountIndicator?: number; +}; + +export const CallControlsComponent = ({ + onChatOpenHandler, + onHangupCallHandler, + unreadCountIndicator, +}: CallControlsComponentProps) => { + const { bottom } = useSafeAreaInsets(); + + return ( + + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + callControlsWrapper: { + flexDirection: 'row', + justifyContent: 'space-evenly', + paddingVertical: appTheme.spacing.md, + zIndex: Z_INDEX.IN_FRONT, + backgroundColor: appTheme.colors.static_grey, + }, +}); diff --git a/sample-apps/react-native/dogfood/src/components/MeetingUI.tsx b/sample-apps/react-native/dogfood/src/components/MeetingUI.tsx index 2a926ed976..643f814949 100644 --- a/sample-apps/react-native/dogfood/src/components/MeetingUI.tsx +++ b/sample-apps/react-native/dogfood/src/components/MeetingUI.tsx @@ -26,7 +26,7 @@ export const MeetingUI = ({ callId, navigation, route }: Props) => { const [errorMessage, setErrorMessage] = useState(''); const appStoreSetState = useAppGlobalStoreSetState(); const { t } = useI18n(); - const unreadBadgeCount = useUnreadCount(); + const unreadCountIndicator = useUnreadCount(); const call = useCall(); const { useCallCallingState } = useCallStateHooks(); @@ -55,6 +55,10 @@ export const MeetingUI = ({ callId, navigation, route }: Props) => { } }, [call, appStoreSetState]); + const onChatOpenHandler = () => { + navigation.navigate('ChatScreen', { callId }); + }; + const onHangupCallHandler = async () => { setShow('loading'); try { @@ -105,12 +109,8 @@ export const MeetingUI = ({ callId, navigation, route }: Props) => { } else { return ( { - navigation.navigate('ChatScreen', { callId }); - }, - unreadBadgeCount, - }} + onChatOpenHandler={onChatOpenHandler} + unreadCountIndicator={unreadCountIndicator} onHangupCallHandler={onHangupCallHandler} onBackPressed={onHangupCallHandler} />