Improve display of unknown contacts in call links

This commit is contained in:
ayumi-signal 2024-06-10 14:12:45 -07:00 committed by GitHub
parent 7e31b37417
commit 33ec40d7b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 506 additions and 146 deletions

View file

@ -3686,6 +3686,26 @@
"messageformat": "Remove this person from the call",
"description": "Button in the in-call info popup for call link calls showing all participants. The action is to remove the participant from the call."
},
"icu:CallingAdhocCallInfo__UnknownContactLabel": {
"messageformat": "{count, plural, one {# person} other {# people}}",
"description": "Label showing number of unknown contacts in the in-call participant info popup for call links."
},
"icu:CallingAdhocCallInfo__UnknownContactLabel--in-addition": {
"messageformat": "+{count, number} more",
"description": "Label showing number of unknown contacts in the in-call participant info popup for call links, when known contacts are also present in the call."
},
"icu:CallingAdhocCallInfo__UnknownContactInfoButton": {
"messageformat": "More info about new contacts",
"description": "Aria label for info button in the in-call participant info popup for call links when unknown contacts are in the call."
},
"icu:CallingAdhocCallInfo__UnknownContactInfoDialogBody": {
"messageformat": "Before joining a call you can only see the names of phone contacts, people youre in a group with, or people youve chatted with 1:1. Youll see all names and photos once youve joined the call.",
"description": "Text for an info dialog which can be opened from the in-call participant list, which is available in call links when unknown contacts are in the call"
},
"icu:CallingAdhocCallInfo__UnknownContactInfoDialogOk": {
"messageformat": "Got it",
"description": "Button text for info dialog which can be opened from the in-call participant list, which is available in call links when unknown contacts are in the call."
},
"icu:callingDeviceSelection__label--video": {
"messageformat": "Video",
"description": "Label for video input selector"

View file

@ -52,10 +52,10 @@
.CallingAdhocCallInfo__MenuItemIcon {
background: $color-gray-65;
display: flex;
width: 32px;
height: 32px;
width: 36px;
height: 36px;
margin-inline-end: 8px;
border-radius: 32px;
border-radius: 36px;
align-items: center;
justify-content: center;
}
@ -93,3 +93,47 @@
margin-inline: 8px;
background: $color-white;
}
.CallingAdhocCallInfo__UnknownContactInfoButton {
@include button-reset;
@include color-svg('../images/icons/v3/info/info.svg', $color-white);
display: flex;
flex: none;
width: 16px;
height: 16px;
margin-inline: 8px;
}
.CallingAdhocCallInfo__UnknownContactInfoButton:focus {
@include keyboard-mode {
background: $color-ultramarine;
}
}
.CallingAdhocCallInfo__UnknownContactInfoDialog__body {
padding-block-start: 22px;
padding-block-end: 8px;
}
.CallingAdhocCallInfo__UnknownContactAvatarSet {
height: 36px;
}
.CallingAdhocCallInfo__UnknownContactAvatar {
&:not(:first-child) {
margin-inline-start: -24px;
}
.module-Avatar__contents {
outline: 2px solid;
outline-color: $color-gray-80;
}
}
.CallingAdhocCallInfo
.module-calling-participants-list__contact:hover
.CallingAdhocCallInfo__UnknownContactAvatar
.module-Avatar__contents {
// Should match background of .module-calling-participants-list__contact:hover
outline-color: $color-gray-62;
}

View file

@ -38,6 +38,7 @@ export enum AvatarSize {
TWENTY = 20,
TWENTY_FOUR = 24,
TWENTY_EIGHT = 28,
THIRTY = 30,
THIRTY_TWO = 32,
THIRTY_SIX = 36,
FORTY = 40,
@ -86,6 +87,7 @@ export type Props = {
const BADGE_PLACEMENT_BY_SIZE = new Map<number, BadgePlacementType>([
[28, { bottom: -4, right: -2 }],
[30, { bottom: -4, right: -2 }],
[32, { bottom: -4, right: -2 }],
[36, { bottom: -3, right: 0 }],
[40, { bottom: -6, right: -4 }],
@ -159,7 +161,10 @@ export function Avatar({
const initials = getInitials(title);
const hasImage = !noteToSelf && avatarPath && !imageBroken;
const shouldUseInitials =
!hasImage && conversationType === 'direct' && Boolean(initials);
!hasImage &&
conversationType === 'direct' &&
Boolean(initials) &&
title !== i18n('icu:unknownContact');
let contentsChildren: ReactNode;
if (loading) {

View file

@ -7,6 +7,7 @@ import type { Meta } from '@storybook/react';
import type { PropsType } from './CallManager';
import { CallManager } from './CallManager';
import {
type ActiveGroupCallType,
CallEndedReason,
CallMode,
CallState,
@ -25,6 +26,12 @@ import { fakeGetGroupCallVideoFrameSource } from '../test-both/helpers/fakeGetGr
import { setupI18n } from '../util/setupI18n';
import enMessages from '../../_locales/en/messages.json';
import { StorySendMode } from '../types/Stories';
import {
FAKE_CALL_LINK,
getDefaultCallLinkConversation,
} from '../test-both/helpers/fakeCallLink';
import { allRemoteParticipants } from './CallScreen.stories';
import { getPlaceholderContact } from '../state/selectors/conversations';
const i18n = setupI18n('en', enMessages);
@ -42,6 +49,11 @@ const getConversation = () =>
lastUpdated: Date.now(),
});
const getUnknownContact = (): ConversationType => ({
...getPlaceholderContact(),
serviceId: generateAci(),
});
const getCommonActiveCallData = () => ({
conversation: getConversation(),
joinedAt: Date.now(),
@ -69,12 +81,13 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
denyUser: action('deny-user'),
getGroupCallVideoFrameSource: (_: string, demuxId: number) =>
fakeGetGroupCallVideoFrameSource(demuxId),
getIsSharingPhoneNumberWithEverybody: () => false,
getPresentingSources: action('get-presenting-sources'),
hangUpActiveCall: action('hang-up-active-call'),
hasInitialLoadCompleted: true,
i18n,
incomingCall: null,
callLink: undefined,
callLink: storyProps.callLink ?? undefined,
me: {
...getDefaultConversation({
color: AvatarColors[0],
@ -113,6 +126,38 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
pauseVoiceNotePlayer: action('pause-audio-player'),
});
const getActiveCallForCallLink = (
overrideProps: Partial<ActiveGroupCallType> = {}
): ActiveGroupCallType => {
return {
conversation: getDefaultCallLinkConversation(),
joinedAt: Date.now(),
hasLocalAudio: true,
hasLocalVideo: true,
localAudioLevel: 0,
viewMode: CallViewMode.Paginated,
outgoingRing: false,
pip: false,
settingsDialogOpen: false,
showParticipantsList: overrideProps.showParticipantsList ?? true,
callMode: CallMode.Adhoc,
connectionState: GroupCallConnectionState.NotConnected,
conversationsByDemuxId: new Map<number, ConversationType>(),
deviceCount: 0,
joinState: GroupCallJoinState.NotJoined,
localDemuxId: 1,
maxDevices: 5,
groupMembers: [],
isConversationTooBigToRing: false,
peekedParticipants:
overrideProps.peekedParticipants ?? allRemoteParticipants.slice(0, 3),
remoteParticipants: overrideProps.remoteParticipants ?? [],
pendingParticipants: [],
raisedHands: new Set<number>(),
remoteAudioLevels: new Map<number, number>(),
};
};
export default {
title: 'Components/CallManager',
argTypes: {},
@ -226,3 +271,93 @@ export function CallRequestNeeded(): JSX.Element {
/>
);
}
export function CallLinkLobbyParticipantsKnown(): JSX.Element {
return (
<CallManager
{...createProps({
activeCall: getActiveCallForCallLink(),
callLink: FAKE_CALL_LINK,
})}
/>
);
}
export function CallLinkLobbyParticipants1Unknown(): JSX.Element {
return (
<CallManager
{...createProps({
activeCall: getActiveCallForCallLink({
peekedParticipants: [getPlaceholderContact()],
}),
callLink: FAKE_CALL_LINK,
})}
/>
);
}
export function CallLinkLobbyParticipants1Known1Unknown(): JSX.Element {
return (
<CallManager
{...createProps({
activeCall: getActiveCallForCallLink({
peekedParticipants: [allRemoteParticipants[0], getUnknownContact()],
}),
callLink: FAKE_CALL_LINK,
})}
/>
);
}
export function CallLinkLobbyParticipants1Known2Unknown(): JSX.Element {
return (
<CallManager
{...createProps({
activeCall: getActiveCallForCallLink({
peekedParticipants: [
getUnknownContact(),
allRemoteParticipants[0],
getUnknownContact(),
],
}),
callLink: FAKE_CALL_LINK,
})}
/>
);
}
export function CallLinkLobbyParticipants1Known12Unknown(): JSX.Element {
const peekedParticipants: Array<ConversationType> = [
allRemoteParticipants[0],
];
for (let n = 12; n > 0; n -= 1) {
peekedParticipants.push(getUnknownContact());
}
return (
<CallManager
{...createProps({
activeCall: getActiveCallForCallLink({
peekedParticipants,
}),
callLink: FAKE_CALL_LINK,
})}
/>
);
}
export function CallLinkLobbyParticipants3Unknown(): JSX.Element {
return (
<CallManager
{...createProps({
activeCall: getActiveCallForCallLink({
peekedParticipants: [
getUnknownContact(),
getUnknownContact(),
getUnknownContact(),
],
}),
callLink: FAKE_CALL_LINK,
})}
/>
);
}

View file

@ -53,7 +53,6 @@ import * as log from '../logging/log';
import { isGroupOrAdhocActiveCall } from '../util/isGroupOrAdhocCall';
import { CallingAdhocCallInfo } from './CallingAdhocCallInfo';
import { callLinkRootKeyToUrl } from '../util/callLinkRootKeyToUrl';
import { isSharingPhoneNumberWithEverybody } from '../util/phoneNumberSharingMode';
import { usePrevious } from '../hooks/usePrevious';
import { copyCallLink } from '../util/copyLinksWithToast';
@ -90,6 +89,7 @@ export type PropsType = {
conversationId: string,
demuxId: number
) => VideoFrameSource;
getIsSharingPhoneNumberWithEverybody: () => boolean;
getPresentingSources: () => void;
incomingCall: DirectIncomingCall | GroupIncomingCall | null;
renderDeviceSelection: () => JSX.Element;
@ -146,7 +146,6 @@ type ActiveCallManagerPropsType = {
| 'declineCall'
| 'hasInitialLoadCompleted'
| 'incomingCall'
| 'isConversationTooBigToRin'
| 'notifyForCall'
| 'playRingtone'
| 'setIsCallActive'
@ -165,6 +164,7 @@ function ActiveCallManager({
denyUser,
hangUpActiveCall,
i18n,
getIsSharingPhoneNumberWithEverybody,
getGroupCallVideoFrameSource,
getPresentingSources,
me,
@ -349,7 +349,9 @@ function ActiveCallManager({
isAdhocJoinRequestPending={isAdhocJoinRequestPending}
isCallFull={isCallFull}
isConversationTooBigToRing={isConvoTooBigToRing}
isSharingPhoneNumberWithEverybody={isSharingPhoneNumberWithEverybody()}
getIsSharingPhoneNumberWithEverybody={
getIsSharingPhoneNumberWithEverybody
}
me={me}
onCallCanceled={cancelActiveCall}
onJoinCall={joinActiveCall}
@ -501,6 +503,7 @@ export function CallManager({
i18n,
incomingCall,
isConversationTooBigToRing,
getIsSharingPhoneNumberWithEverybody,
me,
notifyForCall,
openSystemPreferencesAction,
@ -589,6 +592,9 @@ export function CallManager({
getPresentingSources={getPresentingSources}
hangUpActiveCall={hangUpActiveCall}
i18n={i18n}
getIsSharingPhoneNumberWithEverybody={
getIsSharingPhoneNumberWithEverybody
}
me={me}
openSystemPreferencesAction={openSystemPreferencesAction}
pauseVoiceNotePlayer={pauseVoiceNotePlayer}

View file

@ -235,6 +235,7 @@ export default {
title: 'Components/CallScreen',
argTypes: {},
args: {},
excludeStories: ['allRemoteParticipants'],
} satisfies Meta<PropsType>;
export function Default(): JSX.Element {
@ -378,7 +379,7 @@ export function GroupCallYourHandRaised(): JSX.Element {
const PARTICIPANT_EMOJIS = ['❤️', '🤔', '✨', '😂', '🦄'] as const;
// We generate these upfront so that the list is stable when you move the slider.
const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => {
export const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => {
const mediaKeysReceived = (index + 1) % 20 !== 0;
return {

View file

@ -1,11 +1,10 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable react/no-array-index-key */
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';
@ -17,6 +16,12 @@ import type { ConversationType } from '../state/ducks/conversations';
import { ModalHost } from './ModalHost';
import { isInSystemContacts } from '../util/isInSystemContacts';
import type { RemoveClientType } from '../state/ducks/calling';
import { AvatarColors } from '../types/Colors';
import { Button } from './Button';
import { Modal } from './Modal';
import { Theme } from '../util/theme';
const MAX_UNKNOWN_AVATARS_COUNT = 3;
type ParticipantType = ConversationType & {
hasRemoteAudio?: boolean;
@ -37,6 +42,99 @@ export type PropsType = {
readonly removeClient: ((payload: RemoveClientType) => void) | null;
};
type UnknownContactsPropsType = {
readonly i18n: LocalizerType;
readonly isInAdditionToKnownContacts: boolean;
readonly participants: Array<ParticipantType>;
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) %
AvatarColors.length
: 0;
return (
<Avatar
acceptedMessageRequest={participant.acceptedMessageRequest}
avatarPath={participant.avatarPath}
badge={undefined}
className="CallingAdhocCallInfo__UnknownContactAvatar"
color={AvatarColors[colorIndex]}
conversationType="direct"
key={key}
i18n={i18n}
isMe={participant.isMe}
profileName={participant.profileName}
title={participant.title}
sharedGroupNames={participant.sharedGroupNames}
size={size}
/>
);
},
[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 (
<li
className="module-calling-participants-list__contact"
key="unknown-contacts"
>
<div className="module-calling-participants-list__avatar-and-name">
<div
className={classNames(
'CallingAdhocCallInfo__UnknownContactAvatarSet',
'module-calling-participants-list__avatar-and-name'
)}
>
{visibleParticipants.map((participant, key) =>
renderUnknownAvatar({ participant, key, size: avatarSize })
)}
<div className="module-contact-name module-calling-participants-list__name">
{i18n(
isInAdditionToKnownContacts
? 'icu:CallingAdhocCallInfo__UnknownContactLabel--in-addition'
: 'icu:CallingAdhocCallInfo__UnknownContactLabel',
{ count: participants.length }
)}
</div>
</div>
</div>
<button
aria-label="icu:CallingAdhocCallInfo__UnknownContactInfoButton"
className="CallingAdhocCallInfo__UnknownContactInfoButton module-calling-participants-list__status-icon module-calling-participants-list__unknown-contact"
onClick={showUnknownContactDialog}
type="button"
/>
</li>
);
}
export function CallingAdhocCallInfo({
i18n,
isCallLinkAdmin,
@ -46,141 +144,188 @@ export function CallingAdhocCallInfo({
onCopyCallLink,
removeClient,
}: PropsType): JSX.Element | null {
const sortedParticipants = React.useMemo<Array<ParticipantType>>(
() => sortByTitle(participants),
const [isUnknownContactDialogVisible, setIsUnknownContactDialogVisible] =
React.useState(false);
const hideUnknownContactDialog = React.useCallback(
() => setIsUnknownContactDialogVisible(false),
[setIsUnknownContactDialogVisible]
);
const [knownParticipants, unknownParticipants] = React.useMemo<
[Array<ParticipantType>, Array<ParticipantType>]
>(
() =>
partition(participants, (participant: ParticipantType) =>
Boolean(participant.titleNoDefault)
),
[participants]
);
const sortedParticipants = React.useMemo<Array<ParticipantType>>(
() => sortByTitle(knownParticipants),
[knownParticipants]
);
const renderParticipant = React.useCallback(
(participant: ParticipantType, key: React.Key) => (
<li
className="module-calling-participants-list__contact"
// It's tempting to use `participant.serviceId` as the `key`
// here, but that can result in duplicate keys for
// participants who have joined on multiple devices.
key={key}
>
<div className="module-calling-participants-list__avatar-and-name">
<Avatar
acceptedMessageRequest={participant.acceptedMessageRequest}
avatarPath={participant.avatarPath}
badge={undefined}
color={participant.color}
conversationType="direct"
i18n={i18n}
isMe={participant.isMe}
profileName={participant.profileName}
title={participant.title}
sharedGroupNames={participant.sharedGroupNames}
size={AvatarSize.THIRTY_SIX}
/>
{ourServiceId && participant.serviceId === ourServiceId ? (
<span className="module-calling-participants-list__name">
{i18n('icu:you')}
</span>
) : (
<>
<ContactName
module="module-calling-participants-list__name"
title={participant.title}
/>
{isInSystemContacts(participant) ? (
<span>
{' '}
<InContactsIcon
className="module-calling-participants-list__contact-icon"
i18n={i18n}
/>
</span>
) : null}
</>
)}
</div>
<span
className={classNames(
'module-calling-participants-list__status-icon',
participant.isHandRaised &&
'module-calling-participants-list__hand-raised'
)}
/>
<span
className={classNames(
'module-calling-participants-list__status-icon',
participant.presenting &&
'module-calling-participants-list__presenting',
!participant.hasRemoteVideo &&
'module-calling-participants-list__muted--video'
)}
/>
<span
className={classNames(
'module-calling-participants-list__status-icon',
!participant.hasRemoteAudio &&
'module-calling-participants-list__muted--audio'
)}
/>
{isCallLinkAdmin &&
removeClient &&
participant.demuxId &&
!(ourServiceId && participant.serviceId === ourServiceId) ? (
<button
aria-label={i18n('icu:CallingAdhocCallInfo__RemoveClient')}
className={classNames(
'CallingAdhocCallInfo__RemoveClient',
'module-calling-participants-list__status-icon',
'module-calling-participants-list__remove'
)}
onClick={() => {
if (!participant.demuxId) {
return;
}
removeClient({ demuxId: participant.demuxId });
}}
type="button"
/>
) : null}
</li>
),
[i18n, isCallLinkAdmin, ourServiceId, removeClient]
);
return (
<ModalHost
modalName="CallingAdhocCallInfo"
moduleClassName="CallingAdhocCallInfo"
onClose={onClose}
>
<div className="CallingAdhocCallInfo module-calling-participants-list">
<div className="module-calling-participants-list__header">
<div className="module-calling-participants-list__title">
{participants.length
? i18n('icu:calling__in-this-call', {
people: participants.length,
})
: i18n('icu:calling__in-this-call--zero')}
<>
{isUnknownContactDialogVisible ? (
<Modal
modalName="CallingAdhocCallInfo.UnknownContactInfo"
moduleClassName="CallingAdhocCallInfo__UnknownContactInfoDialog"
i18n={i18n}
modalFooter={
<Button onClick={hideUnknownContactDialog}>
{i18n('icu:CallingAdhocCallInfo__UnknownContactInfoDialogOk')}
</Button>
}
onClose={hideUnknownContactDialog}
theme={Theme.Dark}
>
{i18n('icu:CallingAdhocCallInfo__UnknownContactInfoDialogBody')}
</Modal>
) : null}
<ModalHost
modalName="CallingAdhocCallInfo"
moduleClassName="CallingAdhocCallInfo"
onClose={onClose}
>
<div className="CallingAdhocCallInfo module-calling-participants-list">
<div className="module-calling-participants-list__header">
<div className="module-calling-participants-list__title">
{participants.length
? i18n('icu:calling__in-this-call', {
people: participants.length,
})
: i18n('icu:calling__in-this-call--zero')}
</div>
<button
type="button"
className="module-calling-participants-list__close"
onClick={onClose}
tabIndex={0}
aria-label={i18n('icu:close')}
/>
</div>
<ul className="module-calling-participants-list__list">
{sortedParticipants.map(renderParticipant)}
{unknownParticipants.length > 0 && (
<UnknownContacts
i18n={i18n}
isInAdditionToKnownContacts={Boolean(knownParticipants.length)}
participants={unknownParticipants}
showUnknownContactDialog={() =>
setIsUnknownContactDialogVisible(true)
}
/>
)}
</ul>
<div className="CallingAdhocCallInfo__Divider" />
<div className="CallingAdhocCallInfo__CallLinkInfo">
<button
className="CallingAdhocCallInfo__MenuItem"
onClick={onCopyCallLink}
type="button"
>
<span className="CallingAdhocCallInfo__MenuItemIcon CallingAdhocCallInfo__MenuItemIcon--copy-link" />
<span className="CallingAdhocCallInfo__MenuItemText">
{i18n('icu:CallingAdhocCallInfo__CopyLink')}
</span>
</button>
</div>
<button
type="button"
className="module-calling-participants-list__close"
onClick={onClose}
tabIndex={0}
aria-label={i18n('icu:close')}
/>
</div>
<ul className="module-calling-participants-list__list">
{sortedParticipants.map(
(participant: ParticipantType, index: number) => (
<li
className="module-calling-participants-list__contact"
// It's tempting to use `participant.serviceId` as the `key`
// here, but that can result in duplicate keys for
// participants who have joined on multiple devices.
key={index}
>
<div className="module-calling-participants-list__avatar-and-name">
<Avatar
acceptedMessageRequest={participant.acceptedMessageRequest}
avatarPath={participant.avatarPath}
badge={undefined}
color={participant.color}
conversationType="direct"
i18n={i18n}
isMe={participant.isMe}
profileName={participant.profileName}
title={participant.title}
sharedGroupNames={participant.sharedGroupNames}
size={AvatarSize.THIRTY_TWO}
/>
{ourServiceId && participant.serviceId === ourServiceId ? (
<span className="module-calling-participants-list__name">
{i18n('icu:you')}
</span>
) : (
<>
<ContactName
module="module-calling-participants-list__name"
title={participant.title}
/>
{isInSystemContacts(participant) ? (
<span>
{' '}
<InContactsIcon
className="module-calling-participants-list__contact-icon"
i18n={i18n}
/>
</span>
) : null}
</>
)}
</div>
<span
className={classNames(
'module-calling-participants-list__status-icon',
participant.isHandRaised &&
'module-calling-participants-list__hand-raised'
)}
/>
<span
className={classNames(
'module-calling-participants-list__status-icon',
participant.presenting &&
'module-calling-participants-list__presenting',
!participant.hasRemoteVideo &&
'module-calling-participants-list__muted--video'
)}
/>
<span
className={classNames(
'module-calling-participants-list__status-icon',
!participant.hasRemoteAudio &&
'module-calling-participants-list__muted--audio'
)}
/>
{isCallLinkAdmin &&
removeClient &&
participant.demuxId &&
!(ourServiceId && participant.serviceId === ourServiceId) ? (
<button
aria-label={i18n('icu:CallingAdhocCallInfo__RemoveClient')}
className={classNames(
'CallingAdhocCallInfo__RemoveClient',
'module-calling-participants-list__status-icon',
'module-calling-participants-list__remove'
)}
onClick={() => {
if (!participant.demuxId) {
return;
}
removeClient({ demuxId: participant.demuxId });
}}
type="button"
/>
) : null}
</li>
)
)}
</ul>
<div className="CallingAdhocCallInfo__Divider" />
<div className="CallingAdhocCallInfo__CallLinkInfo">
<button
className="CallingAdhocCallInfo__MenuItem"
onClick={onCopyCallLink}
type="button"
>
<span className="CallingAdhocCallInfo__MenuItemIcon CallingAdhocCallInfo__MenuItemIcon--copy-link" />
<span className="CallingAdhocCallInfo__MenuItemText">
{i18n('icu:CallingAdhocCallInfo__CopyLink')}
</span>
</button>
</div>
</div>
</ModalHost>
</ModalHost>
</>
);
}

View file

@ -70,8 +70,8 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
isAdhocJoinRequestPending: overrideProps.isAdhocJoinRequestPending ?? false,
isConversationTooBigToRing: false,
isCallFull: overrideProps.isCallFull ?? false,
isSharingPhoneNumberWithEverybody:
overrideProps.isSharingPhoneNumberWithEverybody ?? false,
getIsSharingPhoneNumberWithEverybody:
overrideProps.getIsSharingPhoneNumberWithEverybody ?? (() => false),
me:
overrideProps.me ||
getDefaultConversation({

View file

@ -51,6 +51,7 @@ export type PropsType = {
| 'type'
| 'unblurredAvatarPath'
>;
getIsSharingPhoneNumberWithEverybody: () => boolean;
groupMembers?: Array<
Pick<
ConversationType,
@ -64,7 +65,6 @@ export type PropsType = {
isAdhocJoinRequestPending: boolean;
isConversationTooBigToRing: boolean;
isCallFull?: boolean;
isSharingPhoneNumberWithEverybody: boolean;
me: Readonly<
Pick<ConversationType, 'avatarPath' | 'color' | 'id' | 'serviceId'>
>;
@ -94,7 +94,7 @@ export function CallingLobby({
isAdhocJoinRequestPending,
isCallFull = false,
isConversationTooBigToRing,
isSharingPhoneNumberWithEverybody,
getIsSharingPhoneNumberWithEverybody,
me,
onCallCanceled,
onJoinCall,
@ -333,7 +333,7 @@ export function CallingLobby({
</div>
) : (
<div className="CallingLobby__CallLinkNotice">
{isSharingPhoneNumberWithEverybody
{getIsSharingPhoneNumberWithEverybody()
? i18n('icu:CallingLobby__CallLinkNotice--phone-sharing')
: i18n('icu:CallingLobby__CallLinkNotice')}
</div>

View file

@ -53,6 +53,7 @@ import { getIntl } from '../selectors/user';
import { SmartCallingDeviceSelection } from './CallingDeviceSelection';
import { renderEmojiPicker } from './renderEmojiPicker';
import { renderReactionPicker } from './renderReactionPicker';
import { isSharingPhoneNumberWithEverybody as getIsSharingPhoneNumberWithEverybody } from '../../util/phoneNumberSharingMode';
function renderDeviceSelection(): JSX.Element {
return <SmartCallingDeviceSelection />;
@ -468,6 +469,9 @@ export const SmartCallManager = memo(function SmartCallManager() {
declineCall={declineCall}
denyUser={denyUser}
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
getIsSharingPhoneNumberWithEverybody={
getIsSharingPhoneNumberWithEverybody
}
getPresentingSources={getPresentingSources}
hangUpActiveCall={hangUpActiveCall}
hasInitialLoadCompleted={hasInitialLoadCompleted}