diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/02-replacing-call-controls.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/02-replacing-call-controls.mdx index 7f9e666191..e7bbf47fe4 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/02-replacing-call-controls.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/02-replacing-call-controls.mdx @@ -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 @@ -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 = () => { @@ -32,9 +39,29 @@ export const CustomAcceptCallButton = () => { }; return ( - + + Accept Call + ); }; + +const styles = StyleSheet.create({ + button: { + height: 80, + width: 80, + borderRadius: 40, + justifyContent: 'center', + }, + buttonText: { + textAlign: 'center', + }, + acceptButton: { + backgroundColor: '#20E070', + }, +}); ``` ### Button to hangup a call @@ -42,6 +69,7 @@ export const CustomAcceptCallButton = () => { 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 = () => { @@ -52,9 +80,29 @@ export const CustomHangupCallButton = () => { }; return ( - + + Hangup Call + ); }; + +const styles = StyleSheet.create({ + button: { + height: 80, + width: 80, + borderRadius: 40, + justifyContent: 'center', + }, + buttonText: { + textAlign: 'center', + }, + hangupButton: { + backgroundColor: '#FF3742', + }, +}); ``` ### Button to reject a call @@ -62,6 +110,7 @@ export const CustomHangupCallButton = () => { 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 = () => { @@ -72,9 +121,29 @@ export const CustomRejectCallButton = () => { }; return ( - + + Reject Call + ); }; + +const styles = StyleSheet.create({ + button: { + height: 80, + width: 80, + borderRadius: 40, + justifyContent: 'center', + }, + buttonText: { + textAlign: 'center', + }, + rejectButton: { + backgroundColor: '#FF3742', + }, +}); ``` ### Button to toggle audio @@ -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, @@ -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 ( - + ); }; + +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`. @@ -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 } = @@ -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 ( - + ); }; + +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`. @@ -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 ( - + ); }; + +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 ( + + + + + + ); +}; + +const styles = StyleSheet.create({ + buttonGroup: { + flexDirection: 'row', + justifyContent: 'space-evenly', + paddingVertical: 10, + }, +}) +``` + +#### Media Button + + + +```tsx +import { View, StyleSheet } from 'react-native'; + +export const CustomCallControlsView = () => { + return ( + + + + + + ); +}; + +const styles = StyleSheet.create({ + buttonGroup: { + flexDirection: 'row', + justifyContent: 'space-evenly', + paddingVertical: 10, + }, +}) ``` diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/05-incoming-and-outcoming-call.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/05-incoming-and-outcoming-call.mdx index a953b4293c..f4136d14e1 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/05-incoming-and-outcoming-call.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/05-incoming-and-outcoming-call.mdx @@ -10,6 +10,10 @@ import MediaStreamManagementButtonOff from '../assets/05-ui-cookbook/05-incoming-and-outgoing-call/media-stream-management-button-off.png'; import IncomingCallView from '../assets/05-ui-cookbook/05-incoming-and-outgoing-call/incoming-call-view.png'; import OutgoingCallView from '../assets/05-ui-cookbook/05-incoming-and-outgoing-call/outgoing-call-view.png'; +import IncomingCallViewCompleted + from '../assets/05-ui-cookbook/05-incoming-and-outgoing-call/incoming-call-view-completed.png'; +import OutgoingCallViewCompleted + from '../assets/05-ui-cookbook/05-incoming-and-outgoing-call/outgoing-call-view-completed.png'; The incoming and outgoing call views are responsible for showing the call preview before the call is finally joined. @@ -135,7 +139,7 @@ We can go through the usage through the following example: ```tsx title="MediaStreamButtonGroup.tsx" import React from 'react'; -import { Pressable, View, StyleSheet } from 'react-native'; +import { Pressable, View, Text, StyleSheet } from 'react-native'; import { useMediaStreamManagement } from '@stream-io/video-react-native-sdk'; @@ -162,6 +166,20 @@ export const MediaStreamButtonGroup = () => { }, ]; + const audioButtonTextStyles = [ + styles.mediaButtonText, + { + color: !initialAudioEnabled ? 'white' : '#080707dd', + }, + ]; + + const videoButtonTextStyles = [ + styles.mediaButtonText, + { + color: !initialVideoEnabled ? 'white' : '#080707dd', + }, + ]; + return ( { style={audioButtonStyles} > {initialAudioEnabled ? ( - <>{/* Your Disable Audio Icon*/} + Audio on ) : ( - <>{/* Your Enable Audio Icon*/} + Audio off )} { style={videoButtonStyles} > {initialVideoEnabled ? ( - <>{/* Your Disable Video Icon*/} + Video on ) : ( - <>{/* Your Enable Video Icon*/} + Video off )} @@ -197,6 +215,10 @@ const styles = StyleSheet.create({ height: 80, width: 80, borderRadius: 40, + justifyContent: "center" + }, + mediaButtonText: { + textAlign: 'center', }, }); ``` @@ -213,7 +235,7 @@ To build the buttons we would primarily need `useCall` hook, that gives us the c ```tsx title="IncomingCallButtonGroup.tsx" import React, { useCallback } from 'react'; -import { Pressable, View, StyleSheet } from 'react-native'; +import { Pressable, Text, View, StyleSheet } from 'react-native'; import { CallingState, useCall, @@ -249,13 +271,13 @@ export const IncomingCallButtonGroup = () => { style={[styles.button, styles.rejectButton]} onPress={rejectCallHandler} > - {/*Your Reject Call icon Here*/} + Reject - {/*Your Accept Call icon here*/} + Accept ); @@ -270,6 +292,7 @@ const styles = StyleSheet.create({ height: 80, width: 80, borderRadius: 40, + justifyContent: 'center', }, acceptButton: { backgroundColor: '#20E070', @@ -277,6 +300,10 @@ const styles = StyleSheet.create({ rejectButton: { backgroundColor: '#FF3742', }, + callButtonText: { + color: 'white', + textAlign: 'center', + }, }); ``` @@ -292,7 +319,7 @@ To build the buttons we would primarily need `useCall` hook, that gives us the c ```tsx title="OutgoingCallButtonGroup.tsx" import React, { useCallback } from 'react'; -import { Pressable, View, StyleSheet } from 'react-native'; +import { Pressable, Text, View, StyleSheet } from 'react-native'; import { CallingState, useCall, @@ -320,7 +347,7 @@ export const OutgoingCallButtonGroup = () => { style={[styles.button, styles.hangupButton]} onPress={hangupCallHandler} > - {/*Your Hangup Call icon Here*/} + Hang up ); @@ -335,10 +362,62 @@ const styles = StyleSheet.create({ height: 80, width: 80, borderRadius: 40, + justifyContent: 'center', }, hangupButton: { backgroundColor: '#FF3742', }, + callButtonText: { + color: 'white', + textAlign: 'center', + }, }); ``` +## Assembling it all together + + + +```tsx +import { StyleSheet, View } from 'react-native'; + +export const IncomingCallViewComponent = () => { + return ( + + + + + ); +}; + +export const OutgoingCallViewComponent = () => { + return ( + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "#272A30", + justifyContent: 'space-evenly', + } +}) +``` diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/06-lobby-preview.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/06-lobby-preview.mdx index 01a6b34605..71396eb78c 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/06-lobby-preview.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/06-lobby-preview.mdx @@ -10,6 +10,8 @@ import MediaStreamManagementButtonOn from '../assets/05-ui-cookbook/06-lobby-preview/media-stream-management-button-on.png'; import MediaStreamManagementButtonOff from '../assets/05-ui-cookbook/06-lobby-preview/media-stream-management-button-off.png'; +import LobbyViewCompletedOn from '../assets/05-ui-cookbook/06-lobby-preview/lobby-view-completed-on.png'; +import LobbyViewCompletedOff from '../assets/05-ui-cookbook/06-lobby-preview/lobby-view-completed-off.png'; In this article, we will discuss key considerations for creating a user-friendly entrance to your call. We will delve into the various elements that can enhance the user experience and ensure a smooth transition from the lobby to the video call itself. By implementing effective lobby page design principles, you can elevate the overall user satisfaction. @@ -34,6 +36,7 @@ We would like to show some basic information about the call, when users arrive t To retrieve the basic details such as call id and type you can use the `useCall` hook. Then we can use the following hooks: + - useCallMetadata :::note @@ -42,10 +45,11 @@ The initial call information using `useCallMetadata` hook can only be retrieved You can register an effect at the same place for the same, where you create the call(`client.call`). ```tsx -const call = /* Created call */ +const call = + /* Created call */ -useEffect(() => { - const getOrCreateCall = async () => { + useEffect(() => { + const getOrCreateCall = async () => { try { await call?.getOrCreate(); } catch (error) { @@ -54,8 +58,9 @@ useEffect(() => { }; getOrCreateCall(); -}, [call]); + }, [call]); ``` + ::: These hooks make sure, that the information about call metadata or call members is updated in real time. The updates are made automatically in response to [Stream's WebSocket events](../../advanced/events) arriving from the backend. @@ -107,19 +112,19 @@ export const LocalVideoRenderer = () => { } as StreamVideoParticipant; return ( - - - {isVideoAvailable ? ( - - ) : ( - - )} - - + + + {isVideoAvailable ? ( + + ) : ( + + )} + + ); }; @@ -135,7 +140,7 @@ const ParticipantStatus = () => { {!initialAudioEnabled && ( - {/* Mic off icon */} + (Mic off) )} @@ -168,7 +173,7 @@ const styles = StyleSheet.create({ svgContainerStyle: { marginLeft: 8, }, -}) +}); ``` ### Media Stream Management @@ -196,18 +201,17 @@ The button's created will orchestrate the video preview in the [Video Input Prev You can add this in the code above: ```tsx -const { - initialVideoEnabled, -} = useMediaStreamManagement(); +const { initialVideoEnabled } = useMediaStreamManagement(); const isVideoAvailable = !!localVideoStream && initialVideoEnabled; ``` + ::: We can go through the usage through the following example: ```tsx title="MediaStreamButtonGroup.tsx" import React from 'react'; -import { Pressable, View, StyleSheet } from 'react-native'; +import { Pressable, View, Text, StyleSheet } from 'react-native'; import { useMediaStreamManagement } from '@stream-io/video-react-native-sdk'; export const MediaStreamButtonGroup = () => { @@ -232,6 +236,20 @@ export const MediaStreamButtonGroup = () => { }, ]; + const audioButtonTextStyles = [ + styles.mediaButtonText, + { + color: !initialAudioEnabled ? 'white' : '#080707dd', + }, + ]; + + const videoButtonTextStyles = [ + styles.mediaButtonText, + { + color: !initialVideoEnabled ? 'white' : '#080707dd', + }, + ]; + return ( { style={audioButtonStyles} > {initialAudioEnabled ? ( - <>{/* Your Disable Audio Icon*/} + Audio on ) : ( - <>{/* Your Enable Audio Icon*/} + Audio off )} { style={videoButtonStyles} > {initialVideoEnabled ? ( - <>{/* Your Disable Video Icon*/} + Video on ) : ( - <>{/* Your Enable Video Icon*/} + Video off )} @@ -267,6 +285,10 @@ const styles = StyleSheet.create({ height: 80, width: 80, borderRadius: 40, + justifyContent: 'center', + }, + mediaButtonText: { + textAlign: 'center', }, }); ``` @@ -318,6 +340,7 @@ export const LobbyViewParticipantsPreview = () => { const styles = StyleSheet.create({ infoText: { color: 'white', + textAlign: 'center', }, userInfo: { flexDirection: 'row', @@ -344,7 +367,7 @@ Lastly, to join a call we simply invoke call.join(). Learn more about the topic ```tsx title="JoinCallButton.tsx" import React, { useCallback } from 'react'; -import { Pressable, Text } from 'react-native'; +import { Pressable, StyleSheet, Text } from 'react-native'; import { useCall } from '@stream-io/video-react-native-sdk'; export const JoinCallButton = () => { @@ -361,9 +384,61 @@ export const JoinCallButton = () => { }, [call]); return ( - - Join Call + + Join Call ); }; + +const styles = StyleSheet.create({ + joinButton: { + backgroundColor: 'blue', + paddingVertical: 10, + }, + joinButtonText: { + textAlign: 'center', + fontSize: 25, + color: 'white', + }, +}); +``` + +### Assembling it all together + + + +```tsx +import { StyleSheet, View } from 'react-native'; + +export const LobbyView = () => { + return ( + + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "#272A30", + justifyContent: 'space-evenly', + } +}) ``` diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-off.png b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-off.png new file mode 100644 index 0000000000..eb45fa4ae5 Binary files /dev/null and b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-off.png differ diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-on.png b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-on.png new file mode 100644 index 0000000000..616688d2fc Binary files /dev/null and b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-on.png differ diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button.png b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button.png new file mode 100644 index 0000000000..52844e13c8 Binary files /dev/null and b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button.png differ diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/05-incoming-and-outgoing-call/incoming-call-view-completed.png b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/05-incoming-and-outgoing-call/incoming-call-view-completed.png new file mode 100644 index 0000000000..758f26f423 Binary files /dev/null and b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/05-incoming-and-outgoing-call/incoming-call-view-completed.png differ diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/05-incoming-and-outgoing-call/outgoing-call-view-completed.png b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/05-incoming-and-outgoing-call/outgoing-call-view-completed.png new file mode 100644 index 0000000000..48fbf87579 Binary files /dev/null and b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/05-incoming-and-outgoing-call/outgoing-call-view-completed.png differ diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/06-lobby-preview/lobby-view-completed-off.png b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/06-lobby-preview/lobby-view-completed-off.png new file mode 100644 index 0000000000..eee1ee017c Binary files /dev/null and b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/06-lobby-preview/lobby-view-completed-off.png differ diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/06-lobby-preview/lobby-view-completed-on.png b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/06-lobby-preview/lobby-view-completed-on.png new file mode 100644 index 0000000000..bbf94b5e1c Binary files /dev/null and b/packages/react-native-sdk/docusaurus/docs/reactnative/assets/05-ui-cookbook/06-lobby-preview/lobby-view-completed-on.png differ