Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(design-v2): call recording flow #1535

Open
wants to merge 13 commits into
base: add-layout-switcher
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { useCallback, useEffect, useState } from 'react';
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
import { useCall } from '../contexts';
import { useIsCallRecordingInProgress } from './callStateHooks';

/**
* Custom hook for toggling call recording in a video call.
*
* This hook provides functionality to start and stop call recording,
* along with state management for tracking the recording status
* and the loading indicator while awaiting a response.
*/
export const useToggleCallRecording = () => {
const call = useCall();
const { useIsCallRecordingInProgress } = useCallStateHooks();
const isCallRecordingInProgress = useIsCallRecordingInProgress();
const [isAwaitingResponse, setIsAwaitingResponse] = useState(false);

Expand Down
1 change: 1 addition & 0 deletions packages/react-bindings/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as CallStateHooks from './callStateHooks';

export * from './store';
export * from './callUtilsHooks';

/**
* A hook-alike function that exposes all call state hooks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ id: call-top-view
title: CallTopView
---

import ParticipantsInfoBadge from '../../common-content/ui-components/call/call-content/participants-info-badge.mdx';
import OnBackPressed from '../../common-content/ui-components/call/call-content/on-back-pressed.mdx';
import OnParticipantInfoPress from '../../common-content/ui-components/call/call-content/on-participant-info-press.mdx';

Expand Down
6 changes: 3 additions & 3 deletions packages/react-native-sdk/src/theme/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const colors: ColorScheme = {
iconAlertSuccess: '#00e2a1',
sheetPrimary: '#000000',
sheetOverlay: '#0c0d0ea6',
typePrimary: '#eff0f1',
typeSecondary: '#b0b4b7',
buttonSecondaryHover: '#323b44',

buttonPrimaryDisabled: '#1b2c43',
buttonPrimaryHover: '#4c8fff',
Expand All @@ -46,7 +49,6 @@ const colors: ColorScheme = {
buttonQuaternaryHover: '#e77b76',
buttonQuaternaryPressed: '#7d3535',
buttonSecondaryDisabled: '#1e262e29',
buttonSecondaryHover: '#323b44',
buttonSecondaryPressed: '#101213',
buttonSecondaryActiveDefault: '#005fff',
buttonSecondaryActiveHover: '#4c8fff',
Expand Down Expand Up @@ -76,9 +78,7 @@ const colors: ColorScheme = {
sheetTertiary: '#19232d',
typeAccent: '#00e2a1',
typeOnAccent: '#eff0f1',
typePrimary: '#eff0f1',
typeQuaternary: '#101213',
typeSecondary: '#b0b4b7',
typeTertiary: '#656b72',
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
useMenuContext,
} from '../Menu';
import { LoadingIndicator } from '../LoadingIndicator';
import { useToggleCallRecording } from '../../hooks';
import { useToggleCallRecording } from '@stream-io/video-react-bindings';
import { WithTooltip } from '../Tooltip';

export type RecordCallButtonProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { PropsWithChildren, useEffect, useState } from 'react';
import { useI18n } from '@stream-io/video-react-bindings';
import { useToggleCallRecording } from '../../hooks';
import {
useI18n,
useToggleCallRecording,
} from '@stream-io/video-react-bindings';
import { Notification } from './Notification';

export type RecordingInProgressNotificationProps = {
Expand Down
1 change: 0 additions & 1 deletion packages/react-sdk/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './useFloatingUIPreset';
export * from './usePersistedDevicePreferences';
export * from './useScrollPosition';
export * from './useToggleCallRecording';
export * from './useRequestPermission';
43 changes: 33 additions & 10 deletions sample-apps/react-native/dogfood/src/components/ActiveCall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import {
import { ActivityIndicator, StatusBar, StyleSheet, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { ParticipantsInfoList } from './ParticipantsInfoList';
import {
CallControlsComponent,
BottomControlsProps,
} from './CallControlls/BottomControls';
import { BottomControls } from './CallControlls/BottomControls';
import { useOrientation } from '../hooks/useOrientation';
import { Z_INDEX } from '../constants';
import { TopControls } from './CallControlls/TopControls';
import { useToggleCallRecording } from '@stream-io/video-react-bindings';
import { useLayout } from '../contexts/LayoutContext';

type ActiveCallProps = BottomControlsProps & {
type ActiveCallProps = {
onHangupCallHandler?: () => void;
onCallEnded: () => void;
onChatOpenHandler: () => void;
unreadCountIndicator: number;
};

export const ActiveCall = ({
Expand All @@ -45,15 +45,38 @@ export const ActiveCall = ({
});
}, [call, onCallEnded]);

const CustomControlsComponent = useCallback(() => {
const { toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress } =
useToggleCallRecording();

const CustomBottomControls = useCallback(() => {
return (
<CallControlsComponent
<BottomControls
onParticipantInfoPress={onOpenCallParticipantsInfo}
onChatOpenHandler={onChatOpenHandler}
unreadCountIndicator={unreadCountIndicator}
toggleCallRecording={toggleCallRecording}
isCallRecordingInProgress={isCallRecordingInProgress}
isAwaitingResponse={isAwaitingResponse}
/>
);
}, [
onChatOpenHandler,
onOpenCallParticipantsInfo,
unreadCountIndicator,
toggleCallRecording,
isAwaitingResponse,
isCallRecordingInProgress,
]);

const CustomTopControls = useCallback(() => {
return (
<TopControls
isAwaitingResponse={isAwaitingResponse}
isCallRecordingInProgress={isCallRecordingInProgress}
onHangupCallHandler={onHangupCallHandler}
/>
);
}, [onChatOpenHandler, onOpenCallParticipantsInfo, unreadCountIndicator]);
}, [isAwaitingResponse, isCallRecordingInProgress, onHangupCallHandler]);

if (!call) {
return <ActivityIndicator size={'large'} style={StyleSheet.absoluteFill} />;
Expand All @@ -71,8 +94,8 @@ export const ActiveCall = ({
<SafeAreaView style={styles.safeArea} edges={['top', 'left', 'right']}>
<CallContent
onHangupCallHandler={onHangupCallHandler}
CallTopView={TopControls}
CallControls={CustomControlsComponent}
CallTopView={CustomTopControls}
CallControls={CustomBottomControls}
landscape={currentOrientation === 'landscape'}
layout={selectedLayout}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@ export type BottomControlsProps = Pick<
onChatOpenHandler?: () => void;
onParticipantInfoPress?: () => void;
unreadCountIndicator?: number;
toggleCallRecording: () => Promise<void>;
isAwaitingResponse: boolean;
isCallRecordingInProgress: boolean;
};

export const CallControlsComponent = ({
export const BottomControls = ({
onChatOpenHandler,
unreadCountIndicator,
onParticipantInfoPress,
toggleCallRecording,
isAwaitingResponse,
isCallRecordingInProgress,
}: BottomControlsProps) => {
const { useMicrophoneState } = useCallStateHooks();
const { isSpeakingWhileMuted } = useMicrophoneState();
Expand All @@ -46,7 +52,11 @@ export const CallControlsComponent = ({
<AudioButton />
<ToggleAudioPublishingButton />
<ToggleVideoPublishingButton />
<RecordCallButton />
<RecordCallButton
toggleCallRecording={toggleCallRecording}
isAwaitingResponse={isAwaitingResponse}
isCallRecordingInProgress={isCallRecordingInProgress}
/>
</View>
<View style={styles.right}>
<ParticipantsButton onParticipantInfoPress={onParticipantInfoPress} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { useTheme } from '@stream-io/video-react-native-sdk';
import { IconWrapper } from '@stream-io/video-react-native-sdk/src/icons';
import { RecordCall } from '@stream-io/video-react-native-sdk/src/icons/RecordCall';
import React, { useMemo } from 'react';
import {
Modal,
View,
Text,
TouchableOpacity,
TouchableWithoutFeedback,
StyleSheet,
} from 'react-native';

interface CallRecordingModalProps {
visible: boolean;
isLoading: boolean;
onCancel: () => void;
onConfirm: () => void;
message: string;
title: string;
confirmButton: string;
cancelButton: string;
isEndRecordingModal: boolean;
}

export const CallRecordingModal: React.FC<CallRecordingModalProps> = ({
visible,
isLoading,
onCancel,
onConfirm,
message,
title,
confirmButton,
cancelButton,
isEndRecordingModal,
}) => {
const styles = useStyles(isEndRecordingModal);
const {
theme: { colors, variants },
} = useTheme();

return (
<Modal
animationType="fade"
transparent={true}
visible={visible}
onRequestClose={onCancel}
>
<TouchableWithoutFeedback onPress={onCancel}>
<View style={styles.overlay}>
<View style={styles.modalView}>
<View style={styles.content}>
<View style={styles.headerContainer}>
<View style={styles.iconContainer}>
<IconWrapper>
<RecordCall
color={colors.iconAlertWarning}
size={variants.roundButtonSizes.sm}
/>
</IconWrapper>
</View>
<Text style={styles.title}>{title}</Text>
</View>
<Text style={styles.message}>{message}</Text>
</View>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[styles.button, styles.cancelButton]}
onPress={onCancel}
>
<Text style={styles.buttonText}>{cancelButton}</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.confirmButton]}
onPress={onConfirm}
>
{isLoading ? (
<IconWrapper>
<Text style={styles.buttonText}>Loading...</Text>
</IconWrapper>
) : (
<Text style={styles.buttonText}>{confirmButton}</Text>
)}
</TouchableOpacity>
</View>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
);
};

const useStyles = (isEndRecordingModal: boolean) => {
const { theme } = useTheme();
return useMemo(
() =>
StyleSheet.create({
overlay: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
modalView: {
backgroundColor: theme.colors.sheetSecondary,
borderRadius: theme.variants.borderRadiusSizes.lg,
padding: theme.variants.spacingSizes.xl,
width: '80%',
maxWidth: 380,
},
content: {
marginBottom: theme.variants.spacingSizes.xl,
},
headerContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.variants.spacingSizes.sm,
},
iconContainer: {
display: 'flex',
marginRight: theme.variants.spacingSizes.sm,
},
title: {
color: theme.colors.typePrimary,
fontSize: theme.variants.fontSizes.lg,
fontWeight: '600',
textAlign: 'center',
},
message: {
color: theme.colors.typeSecondary,
fontSize: theme.variants.fontSizes.md,
fontWeight: '400',
textAlign: 'left',
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
gap: theme.variants.spacingSizes.md,
},
button: {
flex: 1,
borderRadius: theme.variants.roundButtonSizes.md,
justifyContent: 'center',
alignItems: 'center',
},
cancelButton: {
backgroundColor: theme.colors.sheetSecondary,
height: 32,
borderWidth: 1,
borderColor: theme.colors.buttonSecondaryHover,
},
confirmButton: {
height: 32,
backgroundColor: isEndRecordingModal
? theme.colors.iconAlertWarning
: theme.colors.buttonPrimaryDefault,
},
buttonText: {
color: 'white',
fontSize: 13,
fontWeight: '600',
},
}),
[theme, isEndRecordingModal],
);
};
Loading
Loading