signal-desktop/ts/components/conversation/CallingNotification.tsx

200 lines
5.4 KiB
TypeScript
Raw Normal View History

// Copyright 2020-2021 Signal Messenger, LLC
2020-10-30 20:34:04 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReactNode } from 'react';
import React, { useState, useEffect } from 'react';
import Measure from 'react-measure';
2021-08-26 20:51:55 +00:00
import { noop } from 'lodash';
2020-06-04 18:16:19 +00:00
import { SystemMessage } from './SystemMessage';
2021-08-26 20:51:55 +00:00
import { Button, ButtonSize, ButtonVariant } from '../Button';
2020-06-04 18:16:19 +00:00
import { Timestamp } from './Timestamp';
import type { LocalizerType } from '../../types/Util';
import { CallMode } from '../../types/Calling';
import type { CallingNotificationType } from '../../util/callingNotification';
import {
2021-08-26 20:51:55 +00:00
getCallingIcon,
getCallingNotificationText,
} from '../../util/callingNotification';
2021-09-17 22:24:21 +00:00
import { usePrevious } from '../../hooks/usePrevious';
import { missingCaseError } from '../../util/missingCaseError';
import { Tooltip, TooltipPlacement } from '../Tooltip';
import type { TimelineItemType } from './TimelineItem';
import * as log from '../../logging/log';
2020-06-04 18:16:19 +00:00
export type PropsActionsType = {
messageSizeChanged: (messageId: string, conversationId: string) => void;
returnToActiveCall: () => void;
startCallingLobby: (_: {
conversationId: string;
isVideoCall: boolean;
}) => void;
};
2020-06-04 18:16:19 +00:00
type PropsHousekeeping = {
i18n: LocalizerType;
conversationId: string;
messageId: string;
nextItem: undefined | TimelineItemType;
2020-06-04 18:16:19 +00:00
};
type PropsType = CallingNotificationType & PropsActionsType & PropsHousekeeping;
2020-06-04 18:16:19 +00:00
export const CallingNotification: React.FC<PropsType> = React.memo(props => {
const { conversationId, i18n, messageId, messageSizeChanged } = props;
const [height, setHeight] = useState<null | number>(null);
const previousHeight = usePrevious<null | number>(null, height);
useEffect(() => {
if (height === null) {
return;
2020-06-04 18:16:19 +00:00
}
if (previousHeight !== null && height !== previousHeight) {
messageSizeChanged(messageId, conversationId);
2020-09-14 19:51:27 +00:00
}
}, [height, previousHeight, conversationId, messageId, messageSizeChanged]);
let timestamp: number;
2021-08-26 20:51:55 +00:00
let wasMissed = false;
switch (props.callMode) {
case CallMode.Direct:
timestamp = props.acceptedTime || props.endedTime;
2021-08-26 20:51:55 +00:00
wasMissed =
props.wasIncoming && !props.acceptedTime && !props.wasDeclined;
break;
case CallMode.Group:
timestamp = props.startedTime;
break;
default:
log.error(`CallingNotification missing case: ${missingCaseError(props)}`);
return null;
2020-06-04 18:16:19 +00:00
}
2021-08-26 20:51:55 +00:00
const icon = getCallingIcon(props);
return (
<Measure
bounds
onResize={({ bounds }) => {
if (!bounds) {
log.error('We should be measuring the bounds');
return;
}
setHeight(bounds.height);
}}
>
{({ measureRef }) => (
<SystemMessage
button={renderCallingNotificationButton(props)}
contents={
<>
2021-08-26 20:51:55 +00:00
{getCallingNotificationText(props, i18n)} &middot;{' '}
<Timestamp
direction="outgoing"
extended
i18n={i18n}
timestamp={timestamp}
withImageNoCaption={false}
withSticker={false}
withTapToViewExpired={false}
/>
</>
}
icon={icon}
isError={wasMissed}
ref={measureRef}
/>
)}
</Measure>
);
});
function renderCallingNotificationButton(
props: Readonly<PropsType>
): ReactNode {
const {
conversationId,
i18n,
nextItem,
returnToActiveCall,
startCallingLobby,
} = props;
if (nextItem?.type === 'callHistory') {
return null;
}
let buttonText: string;
let disabledTooltipText: undefined | string;
2021-08-26 20:51:55 +00:00
let onClick: () => void;
switch (props.callMode) {
case CallMode.Direct: {
const { wasIncoming, wasVideoCall } = props;
buttonText = wasIncoming
? i18n('calling__call-back')
: i18n('calling__call-again');
onClick = () => {
startCallingLobby({ conversationId, isVideoCall: wasVideoCall });
};
break;
}
case CallMode.Group: {
if (props.ended) {
return null;
}
const { activeCallConversationId, deviceCount, maxDevices } = props;
if (activeCallConversationId) {
if (activeCallConversationId === conversationId) {
buttonText = i18n('calling__return');
onClick = returnToActiveCall;
} else {
buttonText = i18n('calling__join');
disabledTooltipText = i18n(
'calling__call-notification__button__in-another-call-tooltip'
);
onClick = noop;
}
} else if (deviceCount >= maxDevices) {
buttonText = i18n('calling__call-is-full');
disabledTooltipText = i18n(
'calling__call-notification__button__call-full-tooltip',
[String(deviceCount)]
);
onClick = noop;
} else {
buttonText = i18n('calling__join');
onClick = () => {
startCallingLobby({ conversationId, isVideoCall: true });
};
}
break;
}
default:
log.error(missingCaseError(props));
return null;
}
const button = (
2021-08-26 20:51:55 +00:00
<Button
disabled={Boolean(disabledTooltipText)}
onClick={onClick}
2021-08-26 20:51:55 +00:00
size={ButtonSize.Small}
variant={ButtonVariant.SystemMessage}
2020-06-04 18:16:19 +00:00
>
{buttonText}
2021-08-26 20:51:55 +00:00
</Button>
2020-06-04 18:16:19 +00:00
);
if (disabledTooltipText) {
return (
<Tooltip content={disabledTooltipText} direction={TooltipPlacement.Top}>
{button}
</Tooltip>
);
}
return button;
}