// Copyright 2020-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { ReactChild } from 'react'; import React, { useCallback, useEffect, useRef } from 'react'; import { Avatar } from './Avatar'; import { Tooltip } from './Tooltip'; import { Intl } from './Intl'; import { Theme } from '../util/theme'; import { getParticipantName } from '../util/callingGetParticipantName'; import { ContactName } from './conversation/ContactName'; import { Emojify } from './conversation/Emojify'; import type { LocalizerType } from '../types/Util'; import { AvatarColors } from '../types/Colors'; import { CallMode } from '../types/Calling'; import type { ConversationType } from '../state/ducks/conversations'; import type { AcceptCallType, DeclineCallType } from '../state/ducks/calling'; import { missingCaseError } from '../util/missingCaseError'; import { useIncomingCallShortcuts, useKeyboardShortcuts, } from '../hooks/useKeyboardShortcuts'; export type PropsType = { acceptCall: (_: AcceptCallType) => void; declineCall: (_: DeclineCallType) => void; i18n: LocalizerType; conversation: Pick< ConversationType, | 'acceptedMessageRequest' | 'avatarPath' | 'color' | 'id' | 'isMe' | 'name' | 'phoneNumber' | 'profileName' | 'sharedGroupNames' | 'title' | 'type' >; bounceAppIconStart(): unknown; bounceAppIconStop(): unknown; notifyForCall(conversationTitle: string, isVideoCall: boolean): unknown; } & ( | { callMode: CallMode.Direct; isVideoCall: boolean; } | { callMode: CallMode.Group; otherMembersRung: Array>; ringer: Pick; } ); type CallButtonProps = { classSuffix: string; tabIndex: number; tooltipContent: string; onClick: () => void; }; const CallButton = ({ classSuffix, onClick, tabIndex, tooltipContent, }: CallButtonProps): JSX.Element => { return ( ); }; const GroupCallMessage = ({ i18n, otherMembersRung, ringer, }: Readonly<{ i18n: LocalizerType; otherMembersRung: Array>; ringer: Pick; }>): JSX.Element => { // As an optimization, we only process the first two names. const [first, second] = otherMembersRung .slice(0, 2) .map(member => ); const ringerNode = ; switch (otherMembersRung.length) { case 0: return ( ); case 1: return ( ); case 2: return ( ); case 3: return ( ); default: return ( ); } }; export const IncomingCallBar = (props: PropsType): JSX.Element | null => { const { acceptCall, bounceAppIconStart, bounceAppIconStop, conversation, declineCall, i18n, notifyForCall, } = props; const { id: conversationId, acceptedMessageRequest, avatarPath, color, isMe, phoneNumber, profileName, sharedGroupNames, title, type: conversationType, } = conversation; let isVideoCall: boolean; let headerNode: ReactChild; let messageNode: ReactChild; switch (props.callMode) { case CallMode.Direct: ({ isVideoCall } = props); headerNode = ; messageNode = i18n( isVideoCall ? 'incomingVideoCall' : 'incomingAudioCall' ); break; case CallMode.Group: { const { otherMembersRung, ringer } = props; isVideoCall = true; headerNode = ; messageNode = ( ); break; } default: throw missingCaseError(props); } // We don't want to re-notify if the title changes. const initialTitleRef = useRef(title); useEffect(() => { const initialTitle = initialTitleRef.current; notifyForCall(initialTitle, isVideoCall); }, [isVideoCall, notifyForCall]); useEffect(() => { bounceAppIconStart(); return () => { bounceAppIconStop(); }; }, [bounceAppIconStart, bounceAppIconStop]); const acceptVideoCall = useCallback(() => { acceptCall({ conversationId, asVideoCall: true }); }, [acceptCall, conversationId]); const acceptAudioCall = useCallback(() => { acceptCall({ conversationId, asVideoCall: false }); }, [acceptCall, conversationId]); const declineIncomingCall = useCallback(() => { declineCall({ conversationId }); }, [conversationId, declineCall]); const incomingCallShortcuts = useIncomingCallShortcuts( acceptAudioCall, acceptVideoCall, declineIncomingCall ); useKeyboardShortcuts(incomingCallShortcuts); return (
{headerNode}
{messageNode}
{isVideoCall ? ( <> ) : ( )}
); };