diff --git a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx index c5206c9737..3115611681 100644 --- a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx +++ b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx @@ -23,7 +23,7 @@ import { StyleSheet, View, } from 'react-native'; -import { ParticipantsInfoList } from './ParticipantsInfoList'; +import { ParticipantsInfoListModal } from './ParticipantsInfoListModal'; import { BottomControls } from './CallControlls/BottomControls'; import { useOrientation } from '../hooks/useOrientation'; import { Z_INDEX } from '../constants'; @@ -32,7 +32,6 @@ import { useLayout } from '../contexts/LayoutContext'; import { useAppGlobalStoreValue } from '../contexts/AppContext'; import DeviceInfo from 'react-native-device-info'; import Toast from 'react-native-toast-message'; -import { ClosedCaptions } from './ClosedCaptions'; type ActiveCallProps = { onHangupCallHandler?: () => void; @@ -118,22 +117,17 @@ export const ActiveCall = ({ const { toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress } = useToggleCallRecording(); - const { useIsCallCaptioningInProgress } = useCallStateHooks(); - const isCaptioningInProgress = useIsCallCaptioningInProgress(); const CustomBottomControls = useCallback(() => { return ( - <> - {isCaptioningInProgress && } - - + ); }, [ onChatOpenHandler, @@ -142,7 +136,6 @@ export const ActiveCall = ({ toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress, - isCaptioningInProgress, ]); const CustomTopControls = useCallback(() => { @@ -173,7 +166,7 @@ export const ActiveCall = ({ landscape={isLandscape} layout={selectedLayout} /> - diff --git a/sample-apps/react-native/dogfood/src/components/CallControlls/BottomControls.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/BottomControls.tsx index 988dccbc85..68a49471af 100644 --- a/sample-apps/react-native/dogfood/src/components/CallControlls/BottomControls.tsx +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/BottomControls.tsx @@ -6,13 +6,13 @@ import { useCallStateHooks, useTheme, } from '@stream-io/video-react-native-sdk'; -import React, { useMemo } from 'react'; -import { StyleSheet, Text, View } from 'react-native'; -import { BOTTOM_CONTROLS_HEIGHT, Z_INDEX } from '../../constants'; +import React, { useMemo, useState } from 'react'; +import { LayoutChangeEvent, StyleSheet, Text, View } from 'react-native'; import { MoreActionsButton } from './MoreActionsButton'; import { ParticipantsButton } from './ParticipantsButton'; import { ChatButton } from './ChatButton'; import { RecordCallButton } from './RecordCallButton'; +import { ClosedCaptions } from './ClosedCaptions'; export type BottomControlsProps = Pick< CallContentProps, @@ -34,20 +34,20 @@ export const BottomControls = ({ isAwaitingResponse, isCallRecordingInProgress, }: BottomControlsProps) => { - const { useMicrophoneState } = useCallStateHooks(); - const { isSpeakingWhileMuted } = useMicrophoneState(); - const styles = useStyles(isSpeakingWhileMuted); + const styles = useStyles(); + const [controlsContainerHeight, setControlsContainerHeight] = useState(0); + + const onLayout = (event: LayoutChangeEvent) => { + setControlsContainerHeight(event.nativeEvent.layout.height); + }; return ( - - {isSpeakingWhileMuted && ( - - You are muted. Unmute to speak. - - )} - + <> + - + @@ -65,36 +65,64 @@ export const BottomControls = ({ /> + {!!controlsContainerHeight && ( + + )} + + ); +}; + +// speaking while muted and caption controls - aka subtitle on top of video +const SubtitleContainer = ({ + controlsContainerHeight, +}: { + controlsContainerHeight: number; +}) => { + const styles = useStyles(); + const { useIsCallCaptioningInProgress, useMicrophoneState } = + useCallStateHooks(); + const isCaptioningInProgress = useIsCallCaptioningInProgress(); + const { isSpeakingWhileMuted } = useMicrophoneState(); + if (!isCaptioningInProgress || !isSpeakingWhileMuted) { + return null; + } + return ( + + + + {'You are muted. Unmute to speak.'} + ); }; -const useStyles = (showMicLabel: boolean) => { +const useStyles = () => { const { theme } = useTheme(); return useMemo( () => StyleSheet.create({ container: { - paddingVertical: !showMicLabel ? theme.variants.spacingSizes.md : 0, + paddingTop: theme.variants.spacingSizes.sm, + paddingBottom: theme.variants.spacingSizes.md, paddingHorizontal: theme.variants.spacingSizes.md, - backgroundColor: theme.colors.sheetPrimary, - height: BOTTOM_CONTROLS_HEIGHT, + flexDirection: 'row', + justifyContent: 'flex-start', + }, + subtitleContainer: { + position: 'absolute', + left: 0, + right: 0, }, speakingLabelContainer: { backgroundColor: theme.colors.sheetPrimary, - width: '100%', }, label: { textAlign: 'center', color: theme.colors.textPrimary, }, - callControlsWrapper: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'flex-start', - zIndex: Z_INDEX.IN_FRONT, - }, left: { flex: 2.5, flexDirection: 'row', @@ -108,6 +136,6 @@ const useStyles = (showMicLabel: boolean) => { gap: theme.variants.spacingSizes.xs, }, }), - [theme, showMicLabel], + [theme], ); }; diff --git a/sample-apps/react-native/dogfood/src/components/ClosedCaptions.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/ClosedCaptions.tsx similarity index 81% rename from sample-apps/react-native/dogfood/src/components/ClosedCaptions.tsx rename to sample-apps/react-native/dogfood/src/components/CallControlls/ClosedCaptions.tsx index 09c10c83ca..f675c40beb 100644 --- a/sample-apps/react-native/dogfood/src/components/ClosedCaptions.tsx +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/ClosedCaptions.tsx @@ -6,11 +6,16 @@ export const ClosedCaptions = () => { const { useCallClosedCaptions } = useCallStateHooks(); const closedCaptions = useCallClosedCaptions(); const styles = useStyles(); + + if (closedCaptions.length === 0) { + return null; + } + return ( {closedCaptions.map(({ user, start_time, text }) => ( - {user.name}: + {`${user.name}:`} {text} ))} @@ -25,17 +30,13 @@ const useStyles = () => { StyleSheet.create({ rootContainer: { backgroundColor: theme.colors.sheetPrimary, - padding: theme.variants.spacingSizes.md, + padding: theme.variants.spacingSizes.sm, width: '100%', - minHeight: 55, }, closedCaptionItem: { flexDirection: 'row', flexWrap: 'wrap', - flexShrink: 1, - gap: theme.variants.spacingSizes.xs, - alignItems: 'flex-start', - marginBottom: 4, + columnGap: theme.variants.spacingSizes.xs, }, speakerName: { color: theme.colors.textSecondary, diff --git a/sample-apps/react-native/dogfood/src/components/AndroidAudioRoutePickerDrawer.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton/AndroidAudioRoutePickerDrawer.tsx similarity index 92% rename from sample-apps/react-native/dogfood/src/components/AndroidAudioRoutePickerDrawer.tsx rename to sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton/AndroidAudioRoutePickerDrawer.tsx index 90d2186474..0052cd1cb1 100644 --- a/sample-apps/react-native/dogfood/src/components/AndroidAudioRoutePickerDrawer.tsx +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton/AndroidAudioRoutePickerDrawer.tsx @@ -20,7 +20,7 @@ import { useWindowDimensions, View, } from 'react-native'; -import { BOTTOM_CONTROLS_HEIGHT } from '../constants'; + import { SafeAreaView } from 'react-native-safe-area-context'; type DrawerProps = { @@ -31,20 +31,23 @@ type DrawerProps = { const endpointNameToIconImage = (endPointName: AudioDeviceEndpointType) => { switch (endPointName) { case 'Speaker': - return require('../assets/audio-routes/volume_up_24dp.png'); + return require('../../../assets/audio-routes/volume_up_24dp.png'); case 'Earpiece': - return require('../assets/audio-routes/call_24dp.png'); + return require('../../../assets/audio-routes/call_24dp.png'); case 'Wired Headset': - return require('../assets/audio-routes/headphones_24dp.png'); + return require('../../../assets/audio-routes/headphones_24dp.png'); default: - return require('../assets/audio-routes/bluetooth_connected_24dp.png'); + return require('../../../assets/audio-routes/bluetooth_connected_24dp.png'); } }; -export const AndroidAudioRoutePickerDrawer: React.FC = ({ - isVisible, - onClose, -}) => { +type AndroidAudioRoutePickerDrawerProps = DrawerProps & { + bottomControlsHeight: number; +}; + +export const AndroidAudioRoutePickerDrawer: React.FC< + AndroidAudioRoutePickerDrawerProps +> = ({ isVisible, onClose, bottomControlsHeight }) => { const screenHeight = useWindowDimensions().height; const drawerHeight = screenHeight * 0.8; const styles = useStyles(); @@ -63,7 +66,7 @@ export const AndroidAudioRoutePickerDrawer: React.FC = ({ const selectedAudioDeviceName = audioDeviceStatus?.selectedDevice; // negative offset is needed so the drawer component start above the bottom controls - const offset = -BOTTOM_CONTROLS_HEIGHT; + const offset = -bottomControlsHeight; const translateY = useRef( new Animated.Value(drawerHeight + offset), diff --git a/sample-apps/react-native/dogfood/src/components/BottomControlsDrawer.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton/BottomControlsDrawer.tsx similarity index 96% rename from sample-apps/react-native/dogfood/src/components/BottomControlsDrawer.tsx rename to sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton/BottomControlsDrawer.tsx index 0136282373..cd11fa9f0b 100644 --- a/sample-apps/react-native/dogfood/src/components/BottomControlsDrawer.tsx +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton/BottomControlsDrawer.tsx @@ -19,10 +19,9 @@ import { TouchableWithoutFeedback, View, } from 'react-native'; -import { BOTTOM_CONTROLS_HEIGHT } from '../constants'; -import RaiseHand from '../assets/RaiseHand'; -import { CallStats } from './CallStats'; -import { VideoFilters } from './VideoEffects'; +import RaiseHand from '../../../assets/RaiseHand'; +import { CallStats } from '../../CallStats'; +import { VideoFilters } from '../../VideoEffects'; import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; export type DrawerOption = { @@ -37,6 +36,7 @@ type DrawerProps = { showCallStats: boolean; onClose: () => void; options: DrawerOption[]; + bottomControlsHeight: number; }; export const BottomControlsDrawer: React.FC = ({ @@ -44,6 +44,7 @@ export const BottomControlsDrawer: React.FC = ({ showCallStats, onClose, options, + bottomControlsHeight, }) => { const { theme } = useTheme(); const screenHeight = Dimensions.get('window').height; @@ -51,8 +52,8 @@ export const BottomControlsDrawer: React.FC = ({ const styles = useStyles(); const call = useCall(); - // negative offset is needed so the drawer component start above the bottom controls - const offset = -BOTTOM_CONTROLS_HEIGHT; + // negative offset to position the drawer component above the bottom controls + const offset = -bottomControlsHeight; const translateY = useRef( new Animated.Value(drawerHeight + offset), diff --git a/sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton.tsx b/sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton/index.tsx similarity index 88% rename from sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton.tsx rename to sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton/index.tsx index 084dfd1d56..cd306e2cd0 100644 --- a/sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton.tsx +++ b/sample-apps/react-native/dogfood/src/components/CallControlls/MoreActionsButton/index.tsx @@ -20,31 +20,35 @@ import { View, } from 'react-native'; import { IconWrapper } from '@stream-io/video-react-native-sdk/src/icons'; -import MoreActions from '../../assets/MoreActions'; -import { BottomControlsDrawer, DrawerOption } from '../BottomControlsDrawer'; -import Feedback from '../../assets/Feedback'; -import FeedbackModal from '../FeedbackModal'; +import MoreActions from '../../../assets/MoreActions'; +import { BottomControlsDrawer, DrawerOption } from './BottomControlsDrawer'; +import Feedback from '../../../assets/Feedback'; +import FeedbackModal from '../../FeedbackModal'; import { ThemeMode, useAppGlobalStoreSetState, useAppGlobalStoreValue, -} from '../../contexts/AppContext'; -import LightDark from '../../assets/LightDark'; -import Stats from '../../assets/Stats'; -import ClosedCaptions from '../../assets/ClosedCaptions'; -import Screenshot from '../../assets/Screenshot'; -import Hearing from '../../assets/Hearing'; -import { AudioOutput } from '../../assets/AudioOutput'; -import { AndroidAudioRoutePickerDrawer } from '../AndroidAudioRoutePickerDrawer'; +} from '../../../contexts/AppContext'; +import LightDark from '../../../assets/LightDark'; +import Stats from '../../../assets/Stats'; +import ClosedCaptions from '../../../assets/ClosedCaptions'; +import Screenshot from '../../../assets/Screenshot'; +import Hearing from '../../../assets/Hearing'; +import { AudioOutput } from '../../../assets/AudioOutput'; +import { AndroidAudioRoutePickerDrawer } from './AndroidAudioRoutePickerDrawer'; /** * The props for the More Actions Button in the Call Controls. */ -export type MoreActionsButtonProps = { +type MoreActionsButtonProps = { /** * Handler to be called when the more actions button is pressed. */ onPressHandler?: () => void; + /** + * The height of the bottom controls container. + */ + controlsContainerHeight: number; }; /** @@ -54,6 +58,7 @@ export type MoreActionsButtonProps = { */ export const MoreActionsButton = ({ onPressHandler, + controlsContainerHeight, }: MoreActionsButtonProps) => { const { theme: { colors, variants, moreActionsButton, defaults }, @@ -309,23 +314,27 @@ export const MoreActionsButton = ({ style={moreActionsButton} color={buttonColor} > - {Platform.OS === 'android' && ( + {Platform.OS === 'android' && !!controlsContainerHeight && ( { setIsAndroidAudioRoutePickerDrawerVisible(false); }} /> )} - { - setShowCallStats(false); - setIsDrawerVisible(false); - }} - options={options} - showCallStats={showCallStats} - /> + {!!controlsContainerHeight && ( + { + setShowCallStats(false); + setIsDrawerVisible(false); + }} + options={options} + showCallStats={showCallStats} + /> + )} setFeedbackModalVisible(false)} diff --git a/sample-apps/react-native/dogfood/src/components/ParticipantsInfoList.tsx b/sample-apps/react-native/dogfood/src/components/ParticipantsInfoListModal.tsx similarity index 99% rename from sample-apps/react-native/dogfood/src/components/ParticipantsInfoList.tsx rename to sample-apps/react-native/dogfood/src/components/ParticipantsInfoListModal.tsx index d2280ae06c..01a979068b 100644 --- a/sample-apps/react-native/dogfood/src/components/ParticipantsInfoList.tsx +++ b/sample-apps/react-native/dogfood/src/components/ParticipantsInfoListModal.tsx @@ -35,7 +35,7 @@ import { ButtonTestIds } from '../constants/TestIds'; import { useAppGlobalStoreValue } from '../contexts/AppContext'; import { SafeAreaView } from 'react-native-safe-area-context'; -export interface ParticipantsInfoListProps { +interface ParticipantsInfoListProps { /** * Boolean that decides whether the CallParticipantsInfo modal should be open or not. */ @@ -53,7 +53,7 @@ export interface ParticipantsInfoListProps { * their mute states, video states, screen share states, etc. * Mute all participants, invite participants, etc. **/ -export const ParticipantsInfoList = ({ +export const ParticipantsInfoListModal = ({ isCallParticipantsInfoVisible, setIsCallParticipantsInfoVisible, }: ParticipantsInfoListProps) => { diff --git a/sample-apps/react-native/dogfood/src/constants/index.ts b/sample-apps/react-native/dogfood/src/constants/index.ts index acdf552504..3e5414f059 100644 --- a/sample-apps/react-native/dogfood/src/constants/index.ts +++ b/sample-apps/react-native/dogfood/src/constants/index.ts @@ -1,7 +1,6 @@ export const BUTTON_HEIGHT = 50; export const INPUT_HEIGHT = 50; export const AVATAR_SIZE = 50; -export const BOTTOM_CONTROLS_HEIGHT = 76; export const FEEDBACK_MODAL_MAX_WIDTH = 385; export const Z_INDEX = {