diff --git a/ts/components/conversation/MissedCallNotification.tsx b/ts/components/conversation/MissedCallNotification.tsx new file mode 100644 index 000000000..226868a74 --- /dev/null +++ b/ts/components/conversation/MissedCallNotification.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import styled from 'styled-components'; +import { PubKey } from '../../session/types'; + +import { PropsForMissedCallNotification } from '../../state/ducks/conversations'; +import { getSelectedConversation } from '../../state/selectors/conversations'; +import { ReadableMessage } from './ReadableMessage'; + +const MissedCallContent = styled.div` + background-color: red; + width: 100%; + height: 30px; +`; + +export const MissedCallNotification = (props: PropsForMissedCallNotification) => { + const { messageId, receivedAt, isUnread } = props; + + const selectedConvoProps = useSelector(getSelectedConversation); + + const displayName = + selectedConvoProps?.profileName || + selectedConvoProps?.name || + (selectedConvoProps?.id && PubKey.shorten(selectedConvoProps?.id)); + + return ( + + {window.i18n('callMissed', displayName)} + + ); +}; diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index b555301a4..5bcbafbc7 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -5,6 +5,7 @@ import { PropsForExpirationTimer, PropsForGroupInvitation, PropsForGroupUpdate, + PropsForMissedCallNotification, } from '../../../state/ducks/conversations'; import { getSortedMessagesTypesOfSelectedConversation } from '../../../state/selectors/conversations'; import { DataExtractionNotification } from '../../conversation/DataExtractionNotification'; @@ -12,6 +13,7 @@ import { GroupInvitation } from '../../conversation/GroupInvitation'; import { GroupNotification } from '../../conversation/GroupNotification'; import { Message } from '../../conversation/Message'; import { MessageDateBreak } from '../../conversation/message/DateBreak'; +import { MissedCallNotification } from '../../conversation/MissedCallNotification'; import { TimerNotification } from '../../conversation/TimerNotification'; import { SessionLastSeenIndicator } from './SessionLastSeenIndicator'; @@ -62,6 +64,16 @@ export const SessionMessagesList = (props: { return [, dateBreak, unreadIndicator]; } + if (messageProps.message?.messageType === 'missed-call-notification') { + const msgProps = messageProps.message.props as PropsForMissedCallNotification; + + return [ + , + dateBreak, + unreadIndicator, + ]; + } + if (!messageProps) { return null; } diff --git a/ts/models/message.ts b/ts/models/message.ts index 8164af8fb..ce61f6b59 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -88,6 +88,7 @@ export class MessageModel extends Backbone.Model { const propsForGroupInvitation = this.getPropsForGroupInvitation(); const propsForGroupNotification = this.getPropsForGroupNotification(); const propsForTimerNotification = this.getPropsForTimerNotification(); + const isMissedCall = this.get('isMissedCall'); const messageProps: MessageModelPropsWithoutConvoProps = { propsForMessage: this.getPropsForMessage(), }; @@ -103,6 +104,15 @@ export class MessageModel extends Backbone.Model { if (propsForTimerNotification) { messageProps.propsForTimerNotification = propsForTimerNotification; } + + if (isMissedCall) { + messageProps.propsForMissedCall = { + isMissedCall, + messageId: this.id, + receivedAt: this.get('received_at') || Date.now(), + isUnread: this.isUnread(), + }; + } perfEnd(`getPropsMessage-${this.id}`, 'getPropsMessage'); return messageProps; } diff --git a/ts/models/messageType.ts b/ts/models/messageType.ts index 6e7509cdc..f880be5be 100644 --- a/ts/models/messageType.ts +++ b/ts/models/messageType.ts @@ -108,6 +108,8 @@ export interface MessageAttributes { * This field is used for unsending messages and used in sending unsend message requests. */ isDeleted?: boolean; + + isMissedCall?: boolean; } export interface DataExtractionNotificationMsg { @@ -177,6 +179,7 @@ export interface MessageAttributesOptionals { direction?: any; messageHash?: string; isDeleted?: boolean; + isMissedCall?: boolean; } /** diff --git a/ts/session/utils/CallManager.ts b/ts/session/utils/CallManager.ts index 412e27ac7..5d2afbb0d 100644 --- a/ts/session/utils/CallManager.ts +++ b/ts/session/utils/CallManager.ts @@ -674,7 +674,11 @@ export async function handleCallTypeOffer( async function handleMissedCall(sender: string, incomingOfferTimestamp: number) { const incomingCallConversation = await getConversationById(sender); - ToastUtils.pushedMissedCall(incomingCallConversation?.getNickname() || 'Unknown'); + ToastUtils.pushedMissedCall( + incomingCallConversation?.getNickname() || + incomingCallConversation?.getProfileName() || + 'Unknown' + ); await incomingCallConversation?.addSingleMessage({ conversationId: incomingCallConversation.id, @@ -683,7 +687,7 @@ async function handleMissedCall(sender: string, incomingOfferTimestamp: number) sent_at: incomingOfferTimestamp, received_at: Date.now(), expireTimer: 0, - body: 'Missed call', + isMissedCall: true, unread: 1, }); incomingCallConversation?.updateLastMessage(); diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index f31be1a8a..b4492c517 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -18,12 +18,20 @@ import { QuotedAttachmentType } from '../../components/conversation/Quote'; import { perfEnd, perfStart } from '../../session/utils/Performance'; import { omit } from 'lodash'; +export type PropsForMissedCallNotification = { + isMissedCall: boolean; + messageId: string; + receivedAt: number; + isUnread: boolean; +}; + export type MessageModelPropsWithoutConvoProps = { propsForMessage: PropsForMessageWithoutConvoProps; propsForGroupInvitation?: PropsForGroupInvitation; propsForTimerNotification?: PropsForExpirationTimer; propsForDataExtractionNotification?: PropsForDataExtractionNotification; propsForGroupNotification?: PropsForGroupUpdate; + propsForMissedCall?: PropsForMissedCallNotification; }; export type MessageModelPropsWithConvoProps = SortedMessageModelProps & { diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 2a01c8a38..1e2783942 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -184,7 +184,8 @@ export type MessagePropsType = | 'data-extraction' | 'timer-notification' | 'regular-message' - | 'unread-indicator'; + | 'unread-indicator' + | 'missed-call-notification'; export const getSortedMessagesTypesOfSelectedConversation = createSelector( getSortedMessagesOfSelectedConversation, @@ -251,6 +252,20 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector( }; } + if (msg.propsForMissedCall) { + return { + showUnreadIndicator: isFirstUnread, + showDateBreak, + message: { + messageType: 'missed-call-notification', + props: { + ...msg.propsForMissedCall, + messageId: msg.propsForMessage.id, + }, + }, + }; + } + return { showUnreadIndicator: isFirstUnread, showDateBreak,