Handles safety number changes while in a call

This commit is contained in:
Josh Perez 2020-12-08 14:37:04 -05:00 committed by GitHub
parent 561baf6309
commit 318013e83d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 387 additions and 162 deletions

View file

@ -17,7 +17,9 @@ import {
} from '../types/Calling';
import { ConversationTypeType } from '../state/ducks/conversations';
import { Colors, ColorType } from '../types/Colors';
import { getDefaultConversation } from '../util/getDefaultConversation';
import { setup as setupI18n } from '../../js/modules/i18n';
import { Props as SafetyNumberViewerProps } from '../state/smart/SafetyNumberViewer';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
@ -68,12 +70,16 @@ const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
getGroupCallVideoFrameSource: noop as any,
hangUp: action('hang-up'),
i18n,
keyChangeOk: action('key-change-ok'),
me: {
...getDefaultConversation({
color: select('Caller color', Colors, 'ultramarine' as ColorType),
title: text('Caller Title', 'Morty Smith'),
}),
uuid: 'cb0dd0c8-7393-41e9-a0aa-d631c4109541',
color: select('Caller color', Colors, 'ultramarine' as ColorType),
title: text('Caller Title', 'Morty Smith'),
},
renderDeviceSelection: () => <div />,
renderSafetyNumberViewer: (_: SafetyNumberViewerProps) => <div />,
setGroupCallVideoRequest: action('set-group-call-video-request'),
setLocalAudio: action('set-local-audio'),
setLocalPreview: action('set-local-preview'),
@ -110,6 +116,7 @@ story.add('Ongoing Group Call', () => (
...getCommonActiveCallData(),
callMode: CallMode.Group,
connectionState: GroupCallConnectionState.Connected,
conversationsWithSafetyNumberChanges: [],
deviceCount: 0,
joinState: GroupCallJoinState.Joined,
maxDevices: 5,
@ -145,3 +152,27 @@ story.add('Call Request Needed', () => (
})}
/>
));
story.add('Group call - Safety Number Changed', () => (
<CallManager
{...createProps({
activeCall: {
...getCommonActiveCallData(),
callMode: CallMode.Group,
connectionState: GroupCallConnectionState.Connected,
conversationsWithSafetyNumberChanges: [
{
...getDefaultConversation({
title: 'Aaron',
}),
},
],
deviceCount: 0,
joinState: GroupCallJoinState.Joined,
maxDevices: 5,
peekedParticipants: [],
remoteParticipants: [],
},
})}
/>
));

View file

@ -8,6 +8,10 @@ import { CallingLobby } from './CallingLobby';
import { CallingParticipantsList } from './CallingParticipantsList';
import { CallingPip } from './CallingPip';
import { IncomingCallBar } from './IncomingCallBar';
import {
SafetyNumberChangeDialog,
SafetyNumberProps,
} from './SafetyNumberChangeDialog';
import {
ActiveCallType,
CallEndedReason,
@ -24,6 +28,7 @@ import {
DeclineCallType,
DirectCallStateType,
HangUpType,
KeyChangeOkType,
SetGroupCallVideoRequestType,
SetLocalAudioType,
SetLocalPreviewType,
@ -32,9 +37,12 @@ import {
StartCallType,
} from '../state/ducks/calling';
import { LocalizerType } from '../types/Util';
import { ColorType } from '../types/Colors';
import { missingCaseError } from '../util/missingCaseError';
interface MeType extends ConversationType {
uuid: string;
}
export interface PropsType {
activeCall?: ActiveCallType;
availableCameras: Array<MediaDeviceInfo>;
@ -48,21 +56,15 @@ export interface PropsType {
call: DirectCallStateType;
conversation: ConversationType;
};
keyChangeOk: (_: KeyChangeOkType) => void;
renderDeviceSelection: () => JSX.Element;
renderSafetyNumberViewer: (props: SafetyNumberProps) => JSX.Element;
startCall: (payload: StartCallType) => void;
toggleParticipants: () => void;
acceptCall: (_: AcceptCallType) => void;
declineCall: (_: DeclineCallType) => void;
i18n: LocalizerType;
me: {
avatarPath?: string;
color?: ColorType;
name?: string;
phoneNumber?: string;
profileName?: string;
title: string;
uuid: string;
};
me: MeType;
setGroupCallVideoRequest: (_: SetGroupCallVideoRequestType) => void;
setLocalAudio: (_: SetLocalAudioType) => void;
setLocalVideo: (_: SetLocalVideoType) => void;
@ -84,9 +86,11 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
closeNeedPermissionScreen,
hangUp,
i18n,
keyChangeOk,
getGroupCallVideoFrameSource,
me,
renderDeviceSelection,
renderSafetyNumberViewer,
setGroupCallVideoRequest,
setLocalAudio,
setLocalPreview,
@ -203,6 +207,7 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
<CallingParticipantsList
i18n={i18n}
onClose={toggleParticipants}
ourUuid={me.uuid}
participants={peekedParticipants}
/>
) : null}
@ -233,13 +238,11 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
...participant,
hasAudio: participant.hasRemoteAudio,
hasVideo: participant.hasRemoteVideo,
isSelf: false,
})),
{
...me,
hasAudio: hasLocalAudio,
hasVideo: hasLocalVideo,
isSelf: true,
},
]
: [];
@ -268,9 +271,25 @@ const ActiveCallManager: React.FC<ActiveCallManagerPropsType> = ({
<CallingParticipantsList
i18n={i18n}
onClose={toggleParticipants}
ourUuid={me.uuid}
participants={groupCallParticipantsForParticipantsList}
/>
) : null}
{activeCall.callMode === CallMode.Group &&
activeCall.conversationsWithSafetyNumberChanges.length ? (
<SafetyNumberChangeDialog
confirmText={i18n('continueCall')}
contacts={activeCall.conversationsWithSafetyNumberChanges}
i18n={i18n}
onCancel={() => {
hangUp({ conversationId: activeCall.conversation.id });
}}
onConfirm={() => {
keyChangeOk({ conversationId: activeCall.conversation.id });
}}
renderSafetyNumber={renderSafetyNumberViewer}
/>
) : null}
</>
);
};

View file

@ -12,13 +12,14 @@ import {
CallState,
GroupCallConnectionState,
GroupCallJoinState,
GroupCallPeekedParticipantType,
GroupCallRemoteParticipantType,
} from '../types/Calling';
import { ConversationType } from '../state/ducks/conversations';
import { Colors } from '../types/Colors';
import { CallScreen, PropsType } from './CallScreen';
import { setup as setupI18n } from '../../js/modules/i18n';
import { missingCaseError } from '../util/missingCaseError';
import { getDefaultConversation } from '../util/getDefaultConversation';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
@ -50,7 +51,7 @@ interface DirectCallOverrideProps extends OverridePropsBase {
interface GroupCallOverrideProps extends OverridePropsBase {
callMode: CallMode.Group;
connectionState?: GroupCallConnectionState;
peekedParticipants?: Array<GroupCallPeekedParticipantType>;
peekedParticipants?: Array<ConversationType>;
remoteParticipants?: Array<GroupCallRemoteParticipantType>;
}
@ -83,6 +84,7 @@ const createActiveGroupCallProp = (overrideProps: GroupCallOverrideProps) => ({
callMode: CallMode.Group as CallMode.Group,
connectionState:
overrideProps.connectionState || GroupCallConnectionState.Connected,
conversationsWithSafetyNumberChanges: [],
joinState: GroupCallJoinState.Joined,
maxDevices: 5,
deviceCount: (overrideProps.remoteParticipants || []).length,
@ -240,14 +242,15 @@ story.add('Group call - 1', () => (
callMode: CallMode.Group,
remoteParticipants: [
{
uuid: '72fa60e5-25fb-472d-8a56-e56867c57dda',
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
isBlocked: false,
isSelf: false,
title: 'Tyler',
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: false,
uuid: '72fa60e5-25fb-472d-8a56-e56867c57dda',
title: 'Tyler',
}),
},
],
})}
@ -260,34 +263,37 @@ story.add('Group call - Many', () => (
callMode: CallMode.Group,
remoteParticipants: [
{
uuid: '094586f5-8fc2-4ce2-a152-2dfcc99f4630',
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
isBlocked: false,
isSelf: false,
title: 'Amy',
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: false,
title: 'Amy',
uuid: '094586f5-8fc2-4ce2-a152-2dfcc99f4630',
}),
},
{
uuid: 'cb5bdb24-4cbb-4650-8a7a-1a2807051e74',
demuxId: 1,
hasRemoteAudio: true,
hasRemoteVideo: true,
isBlocked: false,
isSelf: true,
title: 'Bob',
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: false,
title: 'Bob',
uuid: 'cb5bdb24-4cbb-4650-8a7a-1a2807051e74',
}),
},
{
uuid: '2d7d13ae-53dc-4a51-8dc7-976cd85e0b57',
demuxId: 2,
hasRemoteAudio: true,
hasRemoteVideo: true,
isBlocked: true,
isSelf: false,
title: 'Alice',
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: true,
title: 'Alice',
uuid: '2d7d13ae-53dc-4a51-8dc7-976cd85e0b57',
}),
},
],
})}
@ -301,14 +307,15 @@ story.add('Group call - reconnecting', () => (
connectionState: GroupCallConnectionState.Reconnecting,
remoteParticipants: [
{
uuid: '33871c64-0c22-45ce-8aa4-0ec237ac4a31',
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
isBlocked: false,
isSelf: false,
title: 'Tyler',
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: false,
title: 'Tyler',
uuid: '33871c64-0c22-45ce-8aa4-0ec237ac4a31',
}),
},
],
})}

View file

@ -11,6 +11,7 @@ import { ColorType } from '../types/Colors';
import { CallingLobby, PropsType } from './CallingLobby';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
import { getDefaultConversation } from '../util/getDefaultConversation';
const i18n = setupI18n('en', enMessages);
@ -33,7 +34,10 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
hasLocalVideo: boolean('hasLocalVideo', overrideProps.hasLocalVideo || false),
i18n,
isGroupCall: boolean('isGroupCall', overrideProps.isGroupCall || false),
me: overrideProps.me || { color: 'ultramarine' as ColorType },
me: overrideProps.me || {
color: 'ultramarine' as ColorType,
uuid: generateUuid(),
},
onCallCanceled: action('on-call-canceled'),
onJoinCall: action('on-join-call'),
peekedParticipants: overrideProps.peekedParticipants || [],
@ -48,11 +52,11 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
toggleSettings: action('toggle-settings'),
});
const fakePeekedParticipant = (title: string) => ({
isSelf: false,
title,
uuid: generateUuid(),
});
const fakePeekedParticipant = (title: string) =>
getDefaultConversation({
title,
uuid: generateUuid(),
});
const story = storiesOf('Components/CallingLobby', module);
@ -72,8 +76,9 @@ story.add('No Camera, local avatar', () => {
const props = createProps({
availableCameras: [],
me: {
color: 'ultramarine' as ColorType,
avatarPath: '/fixtures/kitten-4-112-112.jpg',
color: 'ultramarine' as ColorType,
uuid: generateUuid(),
},
});
return <CallingLobby {...props} />;

View file

@ -14,6 +14,7 @@ import { CallingHeader } from './CallingHeader';
import { Spinner } from './Spinner';
import { ColorType } from '../types/Colors';
import { LocalizerType } from '../types/Util';
import { ConversationType } from '../state/ducks/conversations';
export type PropsType = {
availableCameras: Array<MediaDeviceInfo>;
@ -28,15 +29,11 @@ export type PropsType = {
me: {
avatarPath?: string;
color?: ColorType;
uuid: string;
};
onCallCanceled: () => void;
onJoinCall: () => void;
peekedParticipants: Array<{
firstName?: string;
isSelf: boolean;
title: string;
uuid: string;
}>;
peekedParticipants: Array<ConversationType>;
setLocalAudio: (_: SetLocalAudioType) => void;
setLocalVideo: (_: SetLocalVideoType) => void;
setLocalPreview: (_: SetLocalPreviewType) => void;
@ -124,7 +121,7 @@ export const CallingLobby = ({
// device.
// TODO: Improve the "it's you" case; see DESKTOP-926.
const participantNames = peekedParticipants.map(participant =>
participant.isSelf
participant.uuid === me.uuid
? i18n('you')
: participant.firstName || participant.title
);

View file

@ -9,6 +9,7 @@ import { v4 as generateUuid } from 'uuid';
import { CallingParticipantsList, PropsType } from './CallingParticipantsList';
import { Colors } from '../types/Colors';
import { GroupCallRemoteParticipantType } from '../types/Calling';
import { getDefaultConversation } from '../util/getDefaultConversation';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
@ -19,24 +20,26 @@ function createParticipant(
): GroupCallRemoteParticipantType {
const randomColor = Math.floor(Math.random() * Colors.length - 1);
return {
avatarPath: participantProps.avatarPath,
color: Colors[randomColor],
demuxId: 2,
hasRemoteAudio: Boolean(participantProps.hasRemoteAudio),
hasRemoteVideo: Boolean(participantProps.hasRemoteVideo),
isBlocked: Boolean(participantProps.isBlocked),
isSelf: Boolean(participantProps.isSelf),
name: participantProps.name,
profileName: participantProps.title,
title: String(participantProps.title),
videoAspectRatio: 1.3,
uuid: generateUuid(),
...getDefaultConversation({
avatarPath: participantProps.avatarPath,
color: Colors[randomColor],
isBlocked: Boolean(participantProps.isBlocked),
name: participantProps.name,
profileName: participantProps.title,
title: String(participantProps.title),
uuid: generateUuid(),
}),
};
}
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
i18n,
onClose: action('on-close'),
ourUuid: 'cf085e6a-e70b-41ec-a310-c198248af13f',
participants: overrideProps.participants || [],
});
@ -62,7 +65,6 @@ story.add('Many Participants', () => {
const props = createProps({
participants: [
createParticipant({
isSelf: true,
title: 'Son Goku',
}),
createParticipant({

View file

@ -9,10 +9,10 @@ import { Avatar } from './Avatar';
import { ContactName } from './conversation/ContactName';
import { InContactsIcon } from './InContactsIcon';
import { LocalizerType } from '../types/Util';
import { GroupCallPeekedParticipantType } from '../types/Calling';
import { sortByTitle } from '../util/sortByTitle';
import { ConversationType } from '../state/ducks/conversations';
interface ParticipantType extends GroupCallPeekedParticipantType {
interface ParticipantType extends ConversationType {
hasAudio?: boolean;
hasVideo?: boolean;
}
@ -20,11 +20,12 @@ interface ParticipantType extends GroupCallPeekedParticipantType {
export type PropsType = {
readonly i18n: LocalizerType;
readonly onClose: () => void;
readonly ourUuid: string;
readonly participants: Array<ParticipantType>;
};
export const CallingParticipantsList = React.memo(
({ i18n, onClose, participants }: PropsType) => {
({ i18n, onClose, ourUuid, participants }: PropsType) => {
const [root, setRoot] = React.useState<HTMLElement | null>(null);
const sortedParticipants = React.useMemo<Array<ParticipantType>>(
@ -100,7 +101,7 @@ export const CallingParticipantsList = React.memo(
title={participant.title}
size={32}
/>
{participant.isSelf ? (
{participant.uuid === ourUuid ? (
<span className="module-calling-participants-list__name">
{i18n('you')}
</span>

View file

@ -105,6 +105,7 @@ story.add('Group Call', () => {
...getCommonActiveCallData(),
callMode: CallMode.Group as CallMode.Group,
connectionState: GroupCallConnectionState.Connected,
conversationsWithSafetyNumberChanges: [],
joinState: GroupCallJoinState.Joined,
maxDevices: 5,
deviceCount: 0,

View file

@ -9,6 +9,7 @@ import {
GroupCallRemoteParticipant,
PropsType,
} from './GroupCallRemoteParticipant';
import { getDefaultConversation } from '../util/getDefaultConversation';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
@ -37,12 +38,13 @@ const createProps = (
demuxId: 123,
hasRemoteAudio: false,
hasRemoteVideo: true,
isBlocked: Boolean(isBlocked),
isSelf: false,
title:
'Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y Picasso',
videoAspectRatio: 1.3,
uuid: '992ed3b9-fc9b-47a9-bdb4-e0c7cbb0fda5',
...getDefaultConversation({
isBlocked: Boolean(isBlocked),
title:
'Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y Picasso',
uuid: '992ed3b9-fc9b-47a9-bdb4-e0c7cbb0fda5',
}),
},
...overrideProps,
});

View file

@ -10,7 +10,7 @@ import { InContactsIcon } from './InContactsIcon';
import { ConversationType } from '../state/ducks/conversations';
import { LocalizerType } from '../types/Util';
type SafetyNumberProps = {
export type SafetyNumberProps = {
contactID: string;
onClose?: () => void;
};