Skip to content

Commit

Permalink
Merge branch 'add-layout-switcher' into rn-design-v2-record-call-flow
Browse files Browse the repository at this point in the history
  • Loading branch information
kristian-mkd committed Oct 26, 2024
2 parents 4324c10 + 5acff28 commit 5a0a71a
Show file tree
Hide file tree
Showing 30 changed files with 933 additions and 391 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ import {
ScreenShareOverlayProps,
} from '../../utility/ScreenShareOverlay';
import RTCViewPipIOS from './RTCViewPipIOS';
import {
CallParticipantsFullscreen,
CallParticipantsFullscreenProps,
} from '../CallLayout/CallParticipantsFullscreen';

export type StreamReactionType = StreamReaction & {
icon: string;
Expand Down Expand Up @@ -68,9 +72,9 @@ export type CallContentProps = Pick<
> &
CallContentComponentProps & {
/**
* This switches the participant's layout between the grid and the spotlight mode.
* This switches the participant's layout between the grid, spotlight and fullscreen mode.
*/
layout?: 'grid' | 'spotlight';
layout?: 'grid' | 'spotlight' | 'fullscreen';
/**
* Reactions that are to be supported in the call
*/
Expand Down Expand Up @@ -116,12 +120,8 @@ export const CallContent = ({
const {
theme: { callContent },
} = useTheme();
const {
useCallSettings,
useHasOngoingScreenShare,
useRemoteParticipants,
useLocalParticipant,
} = useCallStateHooks();
const { useCallSettings, useRemoteParticipants, useLocalParticipant } =
useCallStateHooks();

useAutoEnterPiPEffect(disablePictureInPicture);

Expand All @@ -132,14 +132,9 @@ export const CallContent = ({
const remoteParticipants = useDebouncedValue(_remoteParticipants, 300); // we debounce the remote participants to avoid unnecessary rerenders that happen when participant tracks are all subscribed simultaneously
const localParticipant = useLocalParticipant();
const isInPiPMode = useIsInPiPMode(disablePictureInPicture);
const hasScreenShare = useHasOngoingScreenShare();
const showSpotlightLayout = hasScreenShare || layout === 'spotlight';

const showFloatingView =
!showSpotlightLayout &&
!isInPiPMode &&
remoteParticipants.length > 0 &&
remoteParticipants.length < 3;
const isFullScreen = layout === 'fullscreen';
const showFloatingView = isFullScreen && remoteParticipants.length === 1;

const isRemoteParticipantInFloatingView =
showFloatingView &&
showRemoteParticipantInFloatingView &&
Expand Down Expand Up @@ -174,6 +169,13 @@ export const CallContent = ({
const callParticipantsGridProps: CallParticipantsGridProps = {
...participantViewProps,
landscape,
ParticipantView,
CallParticipantsList,
supportedReactions,
};

const callParticipantsFullscreenProps: CallParticipantsFullscreenProps = {
...participantViewProps,
showLocalParticipant: isRemoteParticipantInFloatingView,
ParticipantView,
CallParticipantsList,
Expand All @@ -189,6 +191,21 @@ export const CallContent = ({
supportedReactions,
};

const renderCallParticipants = (selectedLayout: string) => {
switch (selectedLayout) {
case 'fullscreen':
return (
<CallParticipantsFullscreen {...callParticipantsFullscreenProps} />
);
case 'spotlight':
return (
<CallParticipantsSpotlight {...callParticipantsSpotlightProps} />
);
default:
return <CallParticipantsGrid {...callParticipantsGridProps} />;
}
};

return (
<>
{!disablePictureInPicture && (
Expand All @@ -197,7 +214,7 @@ export const CallContent = ({
/>
)}
<View style={[styles.container, callContent.container]}>
<View style={[styles.container, callContent.callParticipantsContainer]}>
<View style={[styles.content, callContent.callParticipantsContainer]}>
{!isInPiPMode && CallTopView && (
<CallTopView onHangupCallHandler={onHangupCallHandler} />
)}
Expand All @@ -220,11 +237,7 @@ export const CallContent = ({
/>
)}
</View>
{showSpotlightLayout ? (
<CallParticipantsSpotlight {...callParticipantsSpotlightProps} />
) : (
<CallParticipantsGrid {...callParticipantsGridProps} />
)}
{renderCallParticipants(layout)}
</View>

{!isInPiPMode && CallControls && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const ToggleCameraFaceButton = ({
const isVideoEnabledInCall = callSettings?.video.enabled;

const {
theme: { colors, toggleCameraFaceButton, defaults },
theme: { colors, toggleCameraFaceButton, variants },
} = useTheme();
const onPress = async () => {
if (onPressHandler) {
Expand All @@ -54,7 +54,7 @@ export const ToggleCameraFaceButton = ({
>
<IconWrapper>
<CameraSwitch
size={defaults.iconSize}
size={variants.iconSizes.md}
color={
optimisticIsMute
? colors.buttonPrimaryDisabled
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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 as DefaultCallParticipantsList,
CallParticipantsListComponentProps,
} from '../CallParticipantsList/CallParticipantsList';
import { ComponentTestIds } from '../../../constants/TestIds';
import { useTheme } from '../../../contexts/ThemeContext';
import { CallContentProps } from '../CallContent';
import { ParticipantViewComponentProps } from '../../Participant';

/**
* Props for the CallParticipantsFullscreen component.
*/
export type CallParticipantsFullscreenProps = ParticipantViewComponentProps &
Pick<
CallContentProps,
'supportedReactions' | 'CallParticipantsList' | 'disablePictureInPicture'
> &
Pick<CallParticipantsListComponentProps, 'ParticipantView'> & {
/**
* Boolean to decide if local participant will be visible in the grid when there is 1:1 call.
*/
showLocalParticipant?: boolean;
};

/**
* Component used to display a participant in fullscreen mode.
*/
export const CallParticipantsFullscreen = ({
CallParticipantsList = DefaultCallParticipantsList,
ParticipantLabel,
ParticipantNetworkQualityIndicator,
ParticipantReaction,
ParticipantVideoFallback,
ParticipantView,
VideoRenderer,
supportedReactions,
showLocalParticipant,
}: CallParticipantsFullscreenProps) => {
const {
theme: { colors, callParticipantsFullscreen },
} = useTheme();
const { useRemoteParticipants, useLocalParticipant } = useCallStateHooks();
const remoteParticipants = useDebouncedValue(useRemoteParticipants(), 300);
const localParticipant = useLocalParticipant();

let participants =
showLocalParticipant && localParticipant
? [localParticipant]
: remoteParticipants;

if (remoteParticipants.length === 0 && localParticipant) {
participants = [localParticipant];
}

const participantViewProps: CallParticipantsListComponentProps = {
ParticipantView,
ParticipantLabel,
ParticipantNetworkQualityIndicator,
ParticipantReaction,
ParticipantVideoFallback,
VideoRenderer,
};

return (
<View
style={[
styles.container,
{ backgroundColor: colors.sheetPrimary },
callParticipantsFullscreen?.container,
]}
testID={ComponentTestIds.CALL_PARTICIPANTS_FULLSCREEN}
>
{CallParticipantsList && (
<CallParticipantsList
participants={participants}
supportedReactions={supportedReactions}
{...participantViewProps}
/>
)}
</View>
);
};

const styles = StyleSheet.create({
container: { flex: 1 },
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { CallContentProps } from '../CallContent';
import { ParticipantViewComponentProps } from '../../Participant';
import { useIsInPiPMode } from '../../../hooks/useIsInPiPMode';
import { StreamVideoParticipant } from '@stream-io/video-client';
// import { generateMockParticipants } from '.';

/**
* Props for the CallParticipantsGrid component.
Expand All @@ -22,10 +23,6 @@ export type CallParticipantsGridProps = ParticipantViewComponentProps &
'supportedReactions' | 'CallParticipantsList' | 'disablePictureInPicture'
> &
Pick<CallParticipantsListComponentProps, 'ParticipantView'> & {
/**
* Boolean to decide if local participant will be visible in the grid when there is 1:1 call.
*/
showLocalParticipant?: boolean;
/**
* Check if device is in landscape mode.
* This will apply the landscape mode styles to the component.
Expand All @@ -44,7 +41,6 @@ export const CallParticipantsGrid = ({
ParticipantVideoFallback,
ParticipantView,
VideoRenderer,
showLocalParticipant = false,
supportedReactions,
landscape,
disablePictureInPicture,
Expand All @@ -64,19 +60,11 @@ export const CallParticipantsGrid = ({
flexDirection: landscape ? 'row' : 'column',
};

const isInPiPMode = useIsInPiPMode(disablePictureInPicture);

const showFloatingView =
!isInPiPMode &&
remoteParticipants.length > 0 &&
remoteParticipants.length < 3;

let participants = showFloatingView
? showLocalParticipant && localParticipant
? [localParticipant]
: remoteParticipants
: allParticipants;
let participants = allParticipants;
// console.log('🚀 ~ participants:', participants);
// let participants = generateMockParticipants(9);

const isInPiPMode = useIsInPiPMode(disablePictureInPicture);
if (isInPiPMode) {
participants =
remoteParticipants.length > 0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import {
hasScreenShare,
speakerLayoutSortPreset,
Expand All @@ -18,6 +18,7 @@ import {
import { useTheme } from '../../../contexts/ThemeContext';
import { CallContentProps } from '../CallContent';
import { useIsInPiPMode } from '../../../hooks/useIsInPiPMode';
// import { generateMockParticipants } from '.';

/**
* Props for the CallParticipantsSpotlight component.
Expand Down Expand Up @@ -56,17 +57,19 @@ export const CallParticipantsSpotlight = ({
disablePictureInPicture,
}: CallParticipantsSpotlightProps) => {
const {
theme: { colors, callParticipantsSpotlight },
theme: { callParticipantsSpotlight, variants },
} = useTheme();
const styles = useStyles(landscape);
const { useParticipants } = useCallStateHooks();
const _allParticipants = useParticipants({
sortBy: speakerLayoutSortPreset,
});
const allParticipants = useDebouncedValue(_allParticipants, 300); // we debounce the participants to avoid unnecessary rerenders that happen when participant tracks are all subscribed simultaneously
let allParticipants = useDebouncedValue(_allParticipants, 300); // we debounce the participants to avoid unnecessary rerenders that happen when participant tracks are all subscribed simultaneously
// allParticipants = generateMockParticipants(10); // for testing
const [participantInSpotlight, ...otherParticipants] = allParticipants;
const isScreenShareOnSpotlight =
participantInSpotlight && hasScreenShare(participantInSpotlight);
const isUserAloneInCall = _allParticipants?.length === 1;
const isUserAloneInCall = allParticipants?.length === 1;

const isInPiP = useIsInPiPMode(disablePictureInPicture);

Expand All @@ -88,7 +91,7 @@ export const CallParticipantsSpotlight = ({
};

const spotlightContainerLandscapeStyles: ViewStyle = {
marginHorizontal: landscape ? 0 : 8,
marginHorizontal: landscape ? 0 : variants.spacingSizes.xs,
};

return (
Expand All @@ -97,15 +100,12 @@ export const CallParticipantsSpotlight = ({
style={[
styles.container,
landscapeStyles,
{
backgroundColor: colors.background2,
},
callParticipantsSpotlight.container,
]}
>
{participantInSpotlight &&
ParticipantView &&
(participantInSpotlight.isLocalParticipant && ScreenShareOverlay ? (
(isScreenShareOnSpotlight && ScreenShareOverlay ? (
<ScreenShareOverlay />
) : (
<ParticipantView
Expand Down Expand Up @@ -155,20 +155,32 @@ export const CallParticipantsSpotlight = ({
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
},
fullScreenSpotlightContainer: {
flex: 1,
},
spotlightContainer: {
flex: 2,
overflow: 'hidden',
borderRadius: 10,
marginHorizontal: 8,
},
callParticipantsListContainer: {
flex: 1,
},
});
const useStyles = (landscape: boolean | undefined) => {
const { theme } = useTheme();
return useMemo(
() =>
StyleSheet.create({
container: {
flex: 1,
padding: theme.variants.spacingSizes.xs,
backgroundColor: theme.colors.sheetPrimary,
},
fullScreenSpotlightContainer: {
flex: 1,
},
spotlightContainer: {
flex: landscape ? 3 : 4,
overflow: 'hidden',
borderRadius: theme.variants.borderRadiusSizes.md,
marginHorizontal: theme.variants.spacingSizes.xs,
},
callParticipantsListContainer: {
flex: 1,
flexDirection: 'row',
backgroundColor: theme.colors.sheetPrimary,
marginLeft: landscape ? theme.variants.spacingSizes.sm : 0,
},
}),
[theme, landscape]
);
};
Loading

0 comments on commit 5a0a71a

Please sign in to comment.