Skip to content

Commit

Permalink
docs(react-native): UI cookbook - Add assembling guide to LobbyView, …
Browse files Browse the repository at this point in the history
…Incoming/OutgoingCallView, CallControlsView (#895)
  • Loading branch information
khushal87 authored Aug 7, 2023
1 parent a756ade commit fd714a9
Show file tree
Hide file tree
Showing 10 changed files with 440 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ title: Call Controls
description: A guide on how to add/remove or replace call controls
---

import ImageShowcase from '@site/src/components/ImageShowcase';
import CallControlsViewMediaButtonOn
from '../assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-on.png';
import CallControlsViewMediaButtonOff
from '../assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-off.png';

The Stream Video React Native SDK allows building your own Call controls view/layout. Call Controls View generally comprises several buttons that control the call. Each button controls its area of responsibility. Our task, as integrators, is to create a component that puts these buttons together as we wish. In this example, we intend to show how to do just that.

:::note
Expand All @@ -22,6 +28,7 @@ Implementing call controls buttons will often be in reality associated with hand
We will need a call accept button when building an app that makes use of [ring call workflow](../../advanced/ringing). To accept a call we just invoke `call.join()`. So the minimal call accept button could look like this:

```tsx
import { Pressable, Text, StyleSheet } from 'react-native';
import { useCall } from '@stream-io/video-react-native-sdk';

export const CustomAcceptCallButton = () => {
Expand All @@ -32,16 +39,37 @@ export const CustomAcceptCallButton = () => {
};

return (
<Button onClick={onCallAcceptHandler}>{/* Your Icon component */}</Button>
<Pressable
onPress={onCallAcceptHandler}
style={[styles.button, styles.acceptButton]}
>
<Text style={styles.buttonText}>Accept Call</Text>
</Pressable>
);
};

const styles = StyleSheet.create({
button: {
height: 80,
width: 80,
borderRadius: 40,
justifyContent: 'center',
},
buttonText: {
textAlign: 'center',
},
acceptButton: {
backgroundColor: '#20E070',
},
});
```

### Button to hangup a call

We will need a call hangup button when hanging up a call or to leave the ongoing call. To accept a call we just invoke `call.leave()`. So the minimal call hang up button could look like this:

```tsx
import { Pressable, Text, StyleSheet } from 'react-native';
import { useCall } from '@stream-io/video-react-native-sdk';

export const CustomHangupCallButton = () => {
Expand All @@ -52,16 +80,37 @@ export const CustomHangupCallButton = () => {
};

return (
<Button onClick={onCallHangupHandler}>{/* Your Icon component */}</Button>
<Pressable
onPress={onCallHangupHandler}
style={[styles.button, styles.hangupButton]}
>
<Text style={styles.buttonText}>Hangup Call</Text>
</Pressable>
);
};

const styles = StyleSheet.create({
button: {
height: 80,
width: 80,
borderRadius: 40,
justifyContent: 'center',
},
buttonText: {
textAlign: 'center',
},
hangupButton: {
backgroundColor: '#FF3742',
},
});
```

### Button to reject a call

We will need a call reject button when building an app that makes use of [ring call workflow](../../advanced/ringing). To reject a call we just invoke `call.leave()` function with an object parameter having key as `reject` and value of it being `true`. So the minimal call reject button could look like this:

```tsx
import { Pressable, Text, StyleSheet } from 'react-native';
import { useCall } from '@stream-io/video-react-native-sdk';

export const CustomRejectCallButton = () => {
Expand All @@ -72,9 +121,29 @@ export const CustomRejectCallButton = () => {
};

return (
<Button onClick={onCallRejectHandler}>{/* Your Icon component */}</Button>
<Pressable
onPress={onCallRejectHandler}
style={[styles.button, styles.rejectButton]}
>
<Text style={styles.buttonText}>Reject Call</Text>
</Pressable>
);
};

const styles = StyleSheet.create({
button: {
height: 80,
width: 80,
borderRadius: 40,
justifyContent: 'center',
},
buttonText: {
textAlign: 'center',
},
rejectButton: {
backgroundColor: '#FF3742',
},
});
```

### Button to toggle audio
Expand All @@ -83,6 +152,7 @@ Toggling microphone in an active call turns around publishing audio input stream

```tsx
import { useCallback } from 'react';
import { Pressable, Text } from 'react-native';
import {
SfuModels,
useLocalParticipant,
Expand All @@ -102,20 +172,49 @@ export const ToggleAudioButton = () => {
if (isAudioPublished) {
stopPublishingAudio();
} else {
publishAudioStream();
await publishAudioStream();
}
}, [isAudioPublished, publishAudioStream, stopPublishingAudio]);

const audioButtonStyles = [
styles.button,
{
backgroundColor: !isAudioPublished ? '#080707dd' : 'white',
},
];

const audioButtonTextStyles = [
styles.mediaButtonText,
{
color: !isAudioPublished ? 'white' : '#080707dd',
},
];

return (
<Button onClick={toggleAudioMuted}>
<Pressable onPress={toggleAudioMuted} style={audioButtonStyles}>
{isAudioPublished ? (
<>{/* Show Audio enabled Icon component */}</>
<Text style={audioButtonTextStyles}>Audio On</Text>
) : (
<>{/* Show Audio disabled Icon component */}</>
<Text style={audioButtonTextStyles}>Audio Off</Text>
)}
</Button>
</Pressable>
);
};

const styles = StyleSheet.create({
button: {
height: 80,
width: 80,
borderRadius: 40,
justifyContent: 'center',
},
buttonText: {
textAlign: 'center',
},
mediaButtonText: {
textAlign: 'center',
},
})
```

We check the audio mute status by checking if `SfuModels.TrackType.AUDIO` exists in the array of types of tracks published by `localParticipant`.
Expand All @@ -127,16 +226,17 @@ Toggling video in an active call turns around publishing video input streams and

```tsx
import { useCallback } from 'react';
import { Pressable, Text } from 'react-native';
import {
SfuModels,
useLocalParticipant,
useMediaStreamManagement,
} from '@stream-io/video-react-native-sdk';

export const ToggleAudioButton = () => {
export const ToggleVideoButton = () => {
const localParticipant = useLocalParticipant();
const isVideoPublished = localParticipant?.publishedTracks.includes(
SfuModels.TrackType.Video,
SfuModels.TrackType.VIDEO,
);

const { publishVideoStream, stopPublishingVideo } =
Expand All @@ -146,20 +246,49 @@ export const ToggleAudioButton = () => {
if (isVideoPublished) {
stopPublishingVideo();
} else {
publishVideoStream();
await publishVideoStream();
}
}, [isVideoPublished, publishVideoStream, stopPublishingVideo]);

const videoButtonStyles = [
styles.button,
{
backgroundColor: !isVideoPublished ? '#080707dd' : 'white',
},
];

const videoButtonTextStyles = [
styles.mediaButtonText,
{
color: !isVideoPublished ? 'white' : '#080707dd',
},
];

return (
<Button onClick={toggleVideoMuted}>
<Pressable onPress={toggleVideoMuted} style={videoButtonStyles}>
{isVideoPublished ? (
<>{/* Show Video enabled Icon component */}</>
<Text style={videoButtonTextStyles}>Video On</Text>
) : (
<>{/* Show Video disabled Icon component */}</>
<Text style={videoButtonTextStyles}>Video Off</Text>
)}
</Button>
</Pressable>
);
};

const styles = StyleSheet.create({
button: {
height: 80,
width: 80,
borderRadius: 40,
justifyContent: 'center',
},
buttonText: {
textAlign: 'center',
},
mediaButtonText: {
textAlign: 'center',
},
})
```

We check the video mute status by checking if `SfuModels.TrackType.VIDEO` exists in the array of types of tracks published by `localParticipant`.
Expand All @@ -172,24 +301,119 @@ Toggling camera face for mobile devices is an important feature. So, to built it
The `isCameraOnFrontFacingMode` is a boolean that tracks if the camera is `front` facing. The `toggleCameraFacingMode` is used to toggle camera between `front` and `back`/`environment` facing modes.

```tsx
import { useCallback } from 'react';
import { Pressable, Text } from 'react-native';
import {
SfuModels,
useMediaStreamManagement,
} from '@stream-io/video-react-native-sdk';

export const ToggleAudioButton = () => {
export const ToggleCameraFaceButton = () => {
const { isCameraOnFrontFacingMode, toggleCameraFacingMode } =
useMediaStreamManagement();

const videoFaceButtonStyles = [
styles.button,
{
backgroundColor: !isCameraOnFrontFacingMode ? '#080707dd' : 'white',
},
];

const videoFaceButtonTextStyles = [
styles.mediaButtonText,
{
color: !isCameraOnFrontFacingMode ? 'white' : '#080707dd',
},
];

return (
<Button onClick={toggleCameraFacingMode}>
<Pressable onPress={toggleCameraFacingMode} style={videoFaceButtonStyles}>
{isCameraOnFrontFacingMode ? (
<>{/* Show Front Facing Feedback Icon component */}</>
<Text style={videoFaceButtonTextStyles}>Front</Text>
) : (
<>{/* Show Back Facing Feedback Icon component */}</>
<Text style={videoFaceButtonTextStyles}>Back</Text>
)}
</Button>
</Pressable>
);
};

const styles = StyleSheet.create({
button: {
height: 80,
width: 80,
borderRadius: 40,
justifyContent: 'center',
},
buttonText: {
textAlign: 'center',
},
mediaButtonText: {
textAlign: 'center',
},
})
```

### Assembling it all together

![Preview of Call Buttons Call Controls View](../assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button.png)

#### Call Buttons

```tsx
import { View, StyleSheet } from 'react-native';

export const CustomCallControlsView = () => {
return (
<View style={styles.buttonGroup}>
<ToggleAudioButton />
<ToggleVideoButton />
<ToggleCameraFaceButton />
</View>
);
};

const styles = StyleSheet.create({
buttonGroup: {
flexDirection: 'row',
justifyContent: 'space-evenly',
paddingVertical: 10,
},
})
```

#### Media Button

<ImageShowcase
items={[
{
image: CallControlsViewMediaButtonOff,
caption: 'Call Controls Media Button Off',
alt: 'Call Controls Media Button Off',
},
{
image: CallControlsViewMediaButtonOn,
caption: 'Call Controls Media Button On',
alt: 'Call Controls Media Button On',
},
]}
/>

```tsx
import { View, StyleSheet } from 'react-native';

export const CustomCallControlsView = () => {
return (
<View style={styles.buttonGroup}>
<CustomAcceptCallButton />
<CustomHangupCallButton />
<CustomRejectCallButton />
</View>
);
};

const styles = StyleSheet.create({
buttonGroup: {
flexDirection: 'row',
justifyContent: 'space-evenly',
paddingVertical: 10,
},
})
```
Loading

0 comments on commit fd714a9

Please sign in to comment.