Receive rings for group calls

This commit is contained in:
Evan Hahn 2021-08-20 11:06:15 -05:00 committed by GitHub
parent fe040a2873
commit 79c976668b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 2112 additions and 359 deletions

View file

@ -1,23 +1,25 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import React, { useEffect, useRef, ReactChild } 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 { LocalizerType } from '../types/Util';
import { AvatarColors } from '../types/Colors';
import { CallMode } from '../types/Calling';
import { ConversationType } from '../state/ducks/conversations';
import { AcceptCallType, DeclineCallType } from '../state/ducks/calling';
import { missingCaseError } from '../util/missingCaseError';
export type PropsType = {
acceptCall: (_: AcceptCallType) => void;
declineCall: (_: DeclineCallType) => void;
i18n: LocalizerType;
call: {
isVideoCall: boolean;
};
conversation: Pick<
ConversationType,
| 'acceptedMessageRequest'
@ -30,8 +32,22 @@ export type PropsType = {
| 'profileName'
| 'sharedGroupNames'
| 'title'
| 'type'
>;
};
bounceAppIconStart(): unknown;
bounceAppIconStop(): unknown;
notifyForCall(conversationTitle: string, isVideoCall: boolean): unknown;
} & (
| {
callMode: CallMode.Direct;
isVideoCall: boolean;
}
| {
callMode: CallMode.Group;
otherMembersRung: Array<Pick<ConversationType, 'firstName' | 'title'>>;
ringer: Pick<ConversationType, 'firstName' | 'title'>;
}
);
type CallButtonProps = {
classSuffix: string;
@ -61,14 +77,93 @@ const CallButton = ({
);
};
export const IncomingCallBar = ({
acceptCall,
declineCall,
const GroupCallMessage = ({
i18n,
call,
conversation,
}: PropsType): JSX.Element | null => {
const { isVideoCall } = call;
otherMembersRung,
ringer,
}: Readonly<{
i18n: LocalizerType;
otherMembersRung: Array<Pick<ConversationType, 'firstName' | 'title'>>;
ringer: Pick<ConversationType, 'firstName' | 'title'>;
}>): JSX.Element => {
// As an optimization, we only process the first two names.
const [first, second] = otherMembersRung
.slice(0, 2)
.map(member => <Emojify text={getParticipantName(member)} />);
const ringerNode = <Emojify text={getParticipantName(ringer)} />;
switch (otherMembersRung.length) {
case 0:
return (
<Intl
id="incomingGroupCall__ringing-you"
i18n={i18n}
components={{ ringer: ringerNode }}
/>
);
case 1:
return (
<Intl
id="incomingGroupCall__ringing-1-other"
i18n={i18n}
components={{
ringer: ringerNode,
otherMember: first,
}}
/>
);
case 2:
return (
<Intl
id="incomingGroupCall__ringing-2-others"
i18n={i18n}
components={{
ringer: ringerNode,
first,
second,
}}
/>
);
break;
case 3:
return (
<Intl
id="incomingGroupCall__ringing-3-others"
i18n={i18n}
components={{
ringer: ringerNode,
first,
second,
}}
/>
);
break;
default:
return (
<Intl
id="incomingGroupCall__ringing-many"
i18n={i18n}
components={{
ringer: ringerNode,
first,
second,
remaining: String(otherMembersRung.length - 2),
}}
/>
);
}
};
export const IncomingCallBar = (props: PropsType): JSX.Element | null => {
const {
acceptCall,
bounceAppIconStart,
bounceAppIconStop,
conversation,
declineCall,
i18n,
notifyForCall,
} = props;
const {
id: conversationId,
acceptedMessageRequest,
@ -80,19 +175,71 @@ export const IncomingCallBar = ({
profileName,
sharedGroupNames,
title,
type: conversationType,
} = conversation;
let isVideoCall: boolean;
let headerNode: ReactChild;
let messageNode: ReactChild;
switch (props.callMode) {
case CallMode.Direct:
({ isVideoCall } = props);
headerNode = (
<ContactName
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
title={title}
i18n={i18n}
/>
);
messageNode = i18n(
isVideoCall ? 'incomingVideoCall' : 'incomingAudioCall'
);
break;
case CallMode.Group: {
const { otherMembersRung, ringer } = props;
isVideoCall = true;
headerNode = <Emojify text={title} />;
messageNode = (
<GroupCallMessage
i18n={i18n}
otherMembersRung={otherMembersRung}
ringer={ringer}
/>
);
break;
}
default:
throw missingCaseError(props);
}
// We don't want to re-notify if the title changes.
const initialTitleRef = useRef<string>(title);
useEffect(() => {
const initialTitle = initialTitleRef.current;
notifyForCall(initialTitle, isVideoCall);
}, [isVideoCall, notifyForCall]);
useEffect(() => {
bounceAppIconStart();
return () => {
bounceAppIconStop();
};
}, [bounceAppIconStart, bounceAppIconStop]);
return (
<div className="IncomingCallBar__container">
<div className="IncomingCallBar__bar">
<div className="IncomingCallBar__contact">
<div className="IncomingCallBar__contact--avatar">
<div className="IncomingCallBar__conversation">
<div className="IncomingCallBar__conversation--avatar">
<Avatar
acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath}
color={color || AvatarColors[0]}
noteToSelf={false}
conversationType="direct"
conversationType={conversationType}
i18n={i18n}
isMe={isMe}
name={name}
@ -103,18 +250,15 @@ export const IncomingCallBar = ({
size={52}
/>
</div>
<div className="IncomingCallBar__contact--name">
<div className="IncomingCallBar__contact--name-header">
<ContactName
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
title={title}
i18n={i18n}
/>
<div className="IncomingCallBar__conversation--name">
<div className="IncomingCallBar__conversation--name-header">
{headerNode}
</div>
<div dir="auto" className="IncomingCallBar__contact--message-text">
{i18n(isVideoCall ? 'incomingVideoCall' : 'incomingAudioCall')}
<div
dir="auto"
className="IncomingCallBar__conversation--message-text"
>
{messageNode}
</div>
</div>
</div>