// Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; import classNames from 'classnames'; import { partition } from 'lodash'; import { Avatar, AvatarSize } from './Avatar'; import { ContactName } from './conversation/ContactName'; import { InContactsIcon } from './InContactsIcon'; import type { CallLinkType } from '../types/CallLink'; import type { LocalizerType } from '../types/Util'; import type { ServiceIdString } from '../types/ServiceId'; import { sortByTitle } from '../util/sortByTitle'; import type { ConversationType } from '../state/ducks/conversations'; import { ModalHost } from './ModalHost'; import { isInSystemContacts } from '../util/isInSystemContacts'; import type { RemoveClientType } from '../state/ducks/calling'; import { AVATAR_COLOR_COUNT, AvatarColors } from '../types/Colors'; import { Button } from './Button'; import { Modal } from './Modal'; import { Theme } from '../util/theme'; import { ConfirmationDialog } from './ConfirmationDialog'; const MAX_UNKNOWN_AVATARS_COUNT = 3; type ParticipantType = ConversationType & { hasRemoteAudio?: boolean; hasRemoteVideo?: boolean; isHandRaised?: boolean; presenting?: boolean; demuxId?: number; }; export type PropsType = { readonly callLink: CallLinkType; readonly i18n: LocalizerType; readonly isCallLinkAdmin: boolean; readonly isUnknownContactDiscrete: boolean; readonly ourServiceId: ServiceIdString | undefined; readonly participants: Array; readonly onClose: () => void; readonly onCopyCallLink: () => void; readonly onShareCallLinkViaSignal: () => void; readonly removeClient: (payload: RemoveClientType) => void; readonly blockClient: (payload: RemoveClientType) => void; readonly showContactModal: ( contactId: string, conversationId?: string ) => void; }; type UnknownContactsPropsType = { readonly i18n: LocalizerType; readonly isInAdditionToKnownContacts: boolean; readonly participants: Array; readonly showUnknownContactDialog: () => void; }; function UnknownContacts({ i18n, isInAdditionToKnownContacts, participants, showUnknownContactDialog, }: UnknownContactsPropsType): JSX.Element { const renderUnknownAvatar = React.useCallback( ({ participant, key, size, }: { participant: ParticipantType; key: React.Key; size: AvatarSize; }) => { const colorIndex = participant.serviceId ? (parseInt(participant.serviceId.slice(-4), 16) || 0) % AVATAR_COLOR_COUNT : 0; return ( ); }, [i18n] ); const visibleParticipants = participants.slice(0, MAX_UNKNOWN_AVATARS_COUNT); let avatarSize: AvatarSize; if (visibleParticipants.length === 1) { avatarSize = AvatarSize.THIRTY_SIX; } else if (visibleParticipants.length === 2) { avatarSize = AvatarSize.THIRTY; } else { avatarSize = AvatarSize.TWENTY_EIGHT; } return (
  • {visibleParticipants.map((participant, key) => renderUnknownAvatar({ participant, key, size: avatarSize }) )}
    {i18n( isInAdditionToKnownContacts ? 'icu:CallingAdhocCallInfo__UnknownContactLabel--in-addition' : 'icu:CallingAdhocCallInfo__UnknownContactLabel', { count: participants.length } )}
  • ); } export function CallingAdhocCallInfo({ i18n, isCallLinkAdmin, isUnknownContactDiscrete, ourServiceId, participants, blockClient, onClose, onCopyCallLink, onShareCallLinkViaSignal, removeClient, showContactModal, }: PropsType): JSX.Element | null { const [isUnknownContactDialogVisible, setIsUnknownContactDialogVisible] = React.useState(false); const [removeClientDialogState, setRemoveClientDialogState] = React.useState<{ demuxId: number; name: string; } | null>(null); const hideUnknownContactDialog = React.useCallback( () => setIsUnknownContactDialogVisible(false), [setIsUnknownContactDialogVisible] ); const onClickShareCallLinkViaSignal = React.useCallback(() => { onClose(); onShareCallLinkViaSignal(); }, [onClose, onShareCallLinkViaSignal]); const [visibleParticipants, unknownParticipants] = React.useMemo< [Array, Array] >( () => partition( participants, (participant: ParticipantType) => isUnknownContactDiscrete || Boolean(participant.titleNoDefault) ), [isUnknownContactDiscrete, participants] ); const sortedParticipants = React.useMemo>( () => sortByTitle(visibleParticipants), [visibleParticipants] ); const renderParticipant = React.useCallback( (participant: ParticipantType, key: React.Key) => ( ), [ i18n, isCallLinkAdmin, onClose, ourServiceId, setRemoveClientDialogState, showContactModal, ] ); return ( <> {removeClientDialogState != null ? ( blockClient({ demuxId: removeClientDialogState.demuxId }), style: 'negative', text: i18n( 'icu:CallingAdhocCallInfo__RemoveClientDialogButton--block' ), }, { action: () => removeClient({ demuxId: removeClientDialogState.demuxId }), style: 'negative', text: i18n( 'icu:CallingAdhocCallInfo__RemoveClientDialogButton--remove' ), }, ]} cancelText={i18n('icu:cancel')} i18n={i18n} theme={Theme.Dark} onClose={() => setRemoveClientDialogState(null)} > {i18n('icu:CallingAdhocCallInfo__RemoveClientDialogBody', { name: removeClientDialogState.name, })} ) : null} {isUnknownContactDialogVisible ? ( {i18n('icu:CallingAdhocCallInfo__UnknownContactInfoDialogOk')} } onClose={hideUnknownContactDialog} theme={Theme.Dark} > {i18n('icu:CallingAdhocCallInfo__UnknownContactInfoDialogBody')} ) : null}
    {participants.length ? i18n('icu:calling__in-this-call', { people: participants.length, }) : i18n('icu:calling__in-this-call--zero')}
      {sortedParticipants.map(renderParticipant)} {unknownParticipants.length > 0 && ( setIsUnknownContactDialogVisible(true) } /> )}
    ); }