blob: 7cf7225efd0bbbd01b8322be1175838a8f6d5e74 [file] [log] [blame]
simon26e79f72022-10-05 22:16:08 -04001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
idillonef9ab812022-11-18 13:46:24 -050018import { Box, Chip, Divider, Stack, Tooltip, Typography } from '@mui/material';
simond47ef9e2022-09-28 22:24:28 -040019import { styled } from '@mui/material/styles';
Michelle Sepkap Simeb3dd3122022-11-03 02:12:39 -040020import { Dayjs } from 'dayjs';
idillon-sfl118ae442022-10-25 10:42:54 -040021import { Account, Contact, Message } from 'jami-web-common';
22import { ReactElement, ReactNode, useCallback, useMemo, useState } from 'react';
simond47ef9e2022-09-28 22:24:28 -040023import { useTranslation } from 'react-i18next';
simon07b4eb02022-09-29 17:50:26 -040024
Michelle Sepkap Simeb3dd3122022-11-03 02:12:39 -040025import dayjs from '../dayjsInitializer';
idillon-sfl9d956ab2022-10-20 16:33:24 -040026import { EmojiButton, MoreButton, ReplyMessageButton } from './Button';
simond47ef9e2022-09-28 22:24:28 -040027import ConversationAvatar from './ConversationAvatar';
idillonef9ab812022-11-18 13:46:24 -050028import PopoverList, { PopoverListItemData } from './PopoverList';
idillon-sflec735452022-10-27 13:18:41 -040029import {
30 ArrowLeftCurved,
31 ArrowLeftDown,
32 ArrowRightUp,
33 OppositeArrowsIcon,
34 TrashBinIcon,
35 TwoSheetsIcon,
36} from './SvgIcon';
Larbi Gharibe9af9732021-03-31 15:08:01 +010037
idillon-sfl9d956ab2022-10-20 16:33:24 -040038type MessagePosition = 'start' | 'end';
39
idillon-sfl118ae442022-10-25 10:42:54 -040040const notificationMessageTypes = ['initial', 'member'] as const;
41type NotificationMessageType = typeof notificationMessageTypes[number];
42const checkIsNotificationMessageType = (type: Message['type'] | undefined): type is NotificationMessageType => {
43 return notificationMessageTypes.includes(type as NotificationMessageType);
simond47ef9e2022-09-28 22:24:28 -040044};
Larbi Gharibe9af9732021-03-31 15:08:01 +010045
idillon-sfl118ae442022-10-25 10:42:54 -040046const invisibleMessageTypes = ['application/update-profile', 'merge', 'vote'] as const;
47type InvisibleMessageType = typeof invisibleMessageTypes[number];
48const checkIsInvisibleMessageType = (type: Message['type'] | undefined): type is InvisibleMessageType => {
49 return invisibleMessageTypes.includes(type as InvisibleMessageType);
simond47ef9e2022-09-28 22:24:28 -040050};
idillonbef18a52022-09-01 01:51:40 -040051
idillon-sfl118ae442022-10-25 10:42:54 -040052const userMessageTypes = ['text/plain', 'application/data-transfer+json', 'application/call-history+json'] as const;
53type UserMessageType = typeof userMessageTypes[number];
54const checkIsUserMessageType = (type: Message['type'] | undefined): type is UserMessageType => {
55 return userMessageTypes.includes(type as UserMessageType);
56};
57
58const checkShowsTime = (time: Dayjs, previousTime: Dayjs) => {
59 return !previousTime.isSame(time) && !time.isBetween(previousTime, previousTime?.add(1, 'minute'));
60};
61
62const findPreviousVisibleMessage = (messages: Message[], messageIndex: number) => {
63 for (let i = messageIndex + 1; i < messages.length; ++i) {
64 const message = messages[i];
65 if (!checkIsInvisibleMessageType(message?.type)) {
66 return message;
67 }
68 }
69};
70
71const findNextVisibleMessage = (messages: Message[], messageIndex: number) => {
72 for (let i = messageIndex - 1; i >= 0; --i) {
73 const message = messages[i];
74 if (!checkIsInvisibleMessageType(message?.type)) {
75 return message;
76 }
77 }
78};
79
80const avatarSize = '22px';
81const spacingBetweenAvatarAndBubble = '10px';
82const bubblePadding = '16px';
83
84interface MessageCallProps {
85 message: Message;
86 isAccountMessage: boolean;
idillon-sfl9d956ab2022-10-20 16:33:24 -040087 isFirstOfGroup: boolean;
88 isLastOfGroup: boolean;
89}
90
idillon-sflec735452022-10-27 13:18:41 -040091const MessageCall = ({ message, isAccountMessage, isFirstOfGroup, isLastOfGroup }: MessageCallProps) => {
idillon-sfl118ae442022-10-25 10:42:54 -040092 const position = isAccountMessage ? 'end' : 'start';
idillon-sflec735452022-10-27 13:18:41 -040093
94 const { t } = useTranslation();
95 const { bubbleColor, Icon, text, textColor } = useMemo(() => {
96 const callDuration = dayjs.duration(parseInt(message?.duration || ''));
97 if (callDuration.asSeconds() === 0) {
98 if (isAccountMessage) {
99 return {
100 text: t('message_call_outgoing_missed'),
101 Icon: ArrowLeftCurved,
102 textColor: 'white',
103 bubbleColor: '#005699' + '80', // opacity 50%
104 };
105 } else {
106 return {
107 text: t('message_call_incoming_missed'),
108 Icon: ArrowLeftCurved,
109 textColor: 'black',
110 bubbleColor: '#C6C6C6',
111 };
112 }
113 } else {
114 const minutes = Math.floor(callDuration.asMinutes()).toString().padStart(2, '0');
115 const seconds = callDuration.format('ss');
116 const interpolations = {
117 duration: `${minutes}:${seconds}`,
118 };
119 if (isAccountMessage) {
120 return {
121 text: t('message_call_outgoing', interpolations),
122 Icon: ArrowRightUp,
123 textColor: 'white',
124 bubbleColor: '#005699',
125 };
126 } else {
127 return {
128 text: t('message_call_incoming', interpolations),
129 Icon: ArrowLeftDown,
130 textcolor: 'black',
131 bubbleColor: '#E5E5E5',
132 };
133 }
134 }
135 }, [isAccountMessage, message, t]);
136
idillonbef18a52022-09-01 01:51:40 -0400137 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400138 <Bubble position={position} isFirstOfGroup={isFirstOfGroup} isLastOfGroup={isLastOfGroup} bubbleColor={bubbleColor}>
idillon-sflec735452022-10-27 13:18:41 -0400139 <Stack direction="row" spacing="10px" alignItems="center">
140 <Icon sx={{ fontSize: '16px', color: textColor }} />
141 <Typography variant="body1" color={textColor} textAlign={position} fontWeight="bold" textTransform="uppercase">
142 {text}
143 </Typography>
144 </Stack>
idillon-sfl118ae442022-10-25 10:42:54 -0400145 </Bubble>
146 );
147};
148
149const MessageInitial = () => {
150 const { t } = useTranslation();
151 return <>{t('message_swarm_created')}</>;
152};
153
154interface MessageDataTransferProps {
155 message: Message;
156 isAccountMessage: boolean;
157 isFirstOfGroup: boolean;
158 isLastOfGroup: boolean;
159}
160
161const MessageDataTransfer = ({ isAccountMessage, isFirstOfGroup, isLastOfGroup }: MessageDataTransferProps) => {
162 const position = isAccountMessage ? 'end' : 'start';
163 return (
164 <Bubble bubbleColor="#E5E5E5" position={position} isFirstOfGroup={isFirstOfGroup} isLastOfGroup={isLastOfGroup}>
simon80b7b3b2022-09-28 17:50:10 -0400165 &quot;data-transfer&quot;
idillon-sfl118ae442022-10-25 10:42:54 -0400166 </Bubble>
simond47ef9e2022-09-28 22:24:28 -0400167 );
168};
idillonbef18a52022-09-01 01:51:40 -0400169
idillon-sfl9d956ab2022-10-20 16:33:24 -0400170interface MessageMemberProps {
171 message: Message;
172}
173
idillon-sfl118ae442022-10-25 10:42:54 -0400174const MessageMember = ({ message }: MessageMemberProps) => {
simond47ef9e2022-09-28 22:24:28 -0400175 const { t } = useTranslation();
idillonbef18a52022-09-01 01:51:40 -0400176 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400177 <Chip
178 sx={{
179 width: 'fit-content',
180 }}
181 label={t('message_user_joined', { user: message.author })}
182 />
simond47ef9e2022-09-28 22:24:28 -0400183 );
184};
idillonbef18a52022-09-01 01:51:40 -0400185
idillon-sfl9d956ab2022-10-20 16:33:24 -0400186interface MessageTextProps {
187 message: Message;
idillon-sfl118ae442022-10-25 10:42:54 -0400188 isAccountMessage: boolean;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400189 isFirstOfGroup: boolean;
190 isLastOfGroup: boolean;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400191}
192
idillon-sfl118ae442022-10-25 10:42:54 -0400193const MessageText = ({ message, isAccountMessage, isFirstOfGroup, isLastOfGroup }: MessageTextProps) => {
194 const position = isAccountMessage ? 'end' : 'start';
195 const bubbleColor = isAccountMessage ? '#005699' : '#E5E5E5';
196 const textColor = isAccountMessage ? 'white' : 'black';
idillonbef18a52022-09-01 01:51:40 -0400197 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400198 <MessageTooltip position={position}>
199 <Bubble
200 bubbleColor={bubbleColor}
201 position={position}
202 isFirstOfGroup={isFirstOfGroup}
203 isLastOfGroup={isLastOfGroup}
204 >
205 <Typography variant="body1" color={textColor} textAlign={position}>
206 {message.body}
207 </Typography>
208 </Bubble>
209 </MessageTooltip>
simond47ef9e2022-09-28 22:24:28 -0400210 );
211};
idillonbef18a52022-09-01 01:51:40 -0400212
idillon-sfl118ae442022-10-25 10:42:54 -0400213interface DateIndicatorProps {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400214 time: Dayjs;
215}
216
idillon-sfl118ae442022-10-25 10:42:54 -0400217const DateIndicator = ({ time }: DateIndicatorProps) => {
idillon-sfl0e1a0d92022-10-25 16:52:44 -0400218 const { i18n } = useTranslation();
idillonbef18a52022-09-01 01:51:40 -0400219
idillon-sfl0e1a0d92022-10-25 16:52:44 -0400220 const textDate = useMemo(() => {
221 if (time.isToday()) {
222 return new Intl.RelativeTimeFormat(i18n.language, { numeric: 'auto' }).format(0, 'day');
223 } else if (time.isYesterday()) {
224 return new Intl.RelativeTimeFormat(i18n.language, { numeric: 'auto' }).format(-1, 'day');
225 } else {
226 return dayjs(time).locale(i18n.language).format('L');
227 }
228 }, [i18n, time]);
idillonbef18a52022-09-01 01:51:40 -0400229
230 return (
simond47ef9e2022-09-28 22:24:28 -0400231 <Box marginTop="30px">
idillon04245a12022-09-01 11:12:17 -0400232 <Divider
233 sx={{
simond47ef9e2022-09-28 22:24:28 -0400234 '.MuiDivider-wrapper': {
idillon04245a12022-09-01 11:12:17 -0400235 margin: 0,
236 padding: 0,
237 },
simond47ef9e2022-09-28 22:24:28 -0400238 '&::before': {
239 borderTop: '1px solid #E5E5E5',
idillon04245a12022-09-01 11:12:17 -0400240 },
simond47ef9e2022-09-28 22:24:28 -0400241 '&::after': {
242 borderTop: '1px solid #E5E5E5',
idillon04245a12022-09-01 11:12:17 -0400243 },
244 }}
245 >
246 <Typography
247 variant="caption"
248 fontWeight={700}
249 border="1px solid #E5E5E5"
250 borderRadius="5px"
251 padding="10px 16px"
idillon-sfl0e1a0d92022-10-25 16:52:44 -0400252 textTransform="capitalize"
idillon04245a12022-09-01 11:12:17 -0400253 >
254 {textDate}
255 </Typography>
idillonbef18a52022-09-01 01:51:40 -0400256 </Divider>
257 </Box>
simond47ef9e2022-09-28 22:24:28 -0400258 );
259};
idillonbef18a52022-09-01 01:51:40 -0400260
idillon-sfl118ae442022-10-25 10:42:54 -0400261interface TimeIndicatorProps {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400262 time: Dayjs;
263 hasDateOnTop: boolean;
264}
265
idillon-sfl118ae442022-10-25 10:42:54 -0400266const TimeIndicator = ({ time, hasDateOnTop }: TimeIndicatorProps) => {
idillon-sfl0e1a0d92022-10-25 16:52:44 -0400267 const { i18n } = useTranslation();
268
269 const textTime = useMemo(() => {
270 return dayjs(time).locale(i18n.language).format('LT');
271 }, [i18n, time]);
idillonbef18a52022-09-01 01:51:40 -0400272
273 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400274 <Stack direction="row" justifyContent="center" marginTop={hasDateOnTop ? '20px' : '30px'}>
simond47ef9e2022-09-28 22:24:28 -0400275 <Typography variant="caption" color="#A7A7A7" fontWeight={700}>
idillonbef18a52022-09-01 01:51:40 -0400276 {textTime}
277 </Typography>
278 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400279 );
280};
idillonbef18a52022-09-01 01:51:40 -0400281
idillon-sfl118ae442022-10-25 10:42:54 -0400282interface NotificationMessageRowProps {
283 message: Message;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400284}
285
idillon-sfl118ae442022-10-25 10:42:54 -0400286const NotificationMessageRow = ({ message }: NotificationMessageRowProps) => {
287 let messageComponent;
288 switch (message.type) {
289 case 'initial':
290 messageComponent = <MessageInitial />;
291 break;
292 case 'member':
293 messageComponent = <MessageMember message={message} />;
294 break;
295 default:
296 console.error(`${NotificationMessageRow.name} received unhandled message type: ${message.type}`);
297 return null;
idillonae655dd2022-10-14 18:11:02 -0400298 }
299
idillonbef18a52022-09-01 01:51:40 -0400300 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400301 <Stack paddingTop={'30px'} alignItems="center">
302 {messageComponent}
303 </Stack>
304 );
305};
306
307interface UserMessageRowProps {
308 message: Message;
309 isAccountMessage: boolean;
310 previousMessage: Message | undefined;
311 nextMessage: Message | undefined;
312 time: Dayjs;
313 showsTime: boolean;
314 author: Account | Contact;
315}
316
317const UserMessageRow = ({
318 message,
319 previousMessage,
320 nextMessage,
321 isAccountMessage,
322 time,
323 showsTime,
324 author,
325}: UserMessageRowProps) => {
326 const authorName = author.getDisplayName();
327 const position = isAccountMessage ? 'end' : 'start';
328
329 const previousIsUserMessageType = checkIsUserMessageType(previousMessage?.type);
330 const nextIsUserMessageType = checkIsUserMessageType(nextMessage?.type);
331 const nextTime = dayjs.unix(Number(nextMessage?.timestamp));
332 const nextShowsTime = checkShowsTime(nextTime, time);
333 const isFirstOfGroup = showsTime || !previousIsUserMessageType || previousMessage?.author !== message.author;
334 const isLastOfGroup = nextShowsTime || !nextIsUserMessageType || message.author !== nextMessage?.author;
335
336 const props = {
337 message,
338 isAccountMessage,
339 isFirstOfGroup,
340 isLastOfGroup,
341 };
342
343 let MessageComponent;
344 switch (message.type) {
345 case 'text/plain':
346 MessageComponent = MessageText;
347 break;
348 case 'application/data-transfer+json':
349 MessageComponent = MessageDataTransfer;
350 break;
351 case 'application/call-history+json':
352 MessageComponent = MessageCall;
353 break;
354 default:
355 console.error(`${UserMessageRow.name} received unhandled message type: ${message.type}`);
356 return null;
357 }
358
359 const participantNamePadding = isAccountMessage
360 ? bubblePadding
361 : parseInt(avatarSize) + parseInt(spacingBetweenAvatarAndBubble) + parseInt(bubblePadding) + 'px';
362
363 return (
364 <Stack alignItems={position}>
365 {isFirstOfGroup && (
366 <Box padding={`30px ${participantNamePadding} 0 ${participantNamePadding}`}>
367 <ParticipantName name={authorName} />
368 </Box>
simond47ef9e2022-09-28 22:24:28 -0400369 )}
idillon-sfl118ae442022-10-25 10:42:54 -0400370 <Stack
371 direction="row"
372 justifyContent={position}
373 alignItems="end"
374 spacing={spacingBetweenAvatarAndBubble}
375 paddingTop="6px"
idillonbef18a52022-09-01 01:51:40 -0400376 width="66.66%"
idillonbef18a52022-09-01 01:51:40 -0400377 >
idillon-sfl118ae442022-10-25 10:42:54 -0400378 <Box sx={{ width: avatarSize }}>
379 {!isAccountMessage && isLastOfGroup && (
380 <ConversationAvatar
381 displayName={authorName}
382 sx={{ width: avatarSize, height: avatarSize, fontSize: '15px' }}
383 />
384 )}
385 </Box>
386 <MessageComponent {...props} />
idillonbef18a52022-09-01 01:51:40 -0400387 </Stack>
388 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400389 );
390};
idillonbef18a52022-09-01 01:51:40 -0400391
idillon-sfl9d956ab2022-10-20 16:33:24 -0400392interface MessageTooltipProps {
393 className?: string;
394 position: MessagePosition;
395 children: ReactElement;
396}
397
398const MessageTooltip = styled(({ className, position, children }: MessageTooltipProps) => {
simond47ef9e2022-09-28 22:24:28 -0400399 const [open, setOpen] = useState(false);
400 const emojis = ['😎', '😄', '😍']; // Should be last three used emojis
idillonef9ab812022-11-18 13:46:24 -0500401 const additionalOptions: PopoverListItemData[] = [
idillon927b7592022-09-15 12:56:45 -0400402 {
403 Icon: TwoSheetsIcon,
idillonef9ab812022-11-18 13:46:24 -0500404 label: 'Copy',
405 onClick: () => {},
idillon927b7592022-09-15 12:56:45 -0400406 },
407 {
408 Icon: OppositeArrowsIcon,
idillonef9ab812022-11-18 13:46:24 -0500409 label: 'Transfer',
410 onClick: () => {},
idillon927b7592022-09-15 12:56:45 -0400411 },
412 {
413 Icon: TrashBinIcon,
idillonef9ab812022-11-18 13:46:24 -0500414 label: 'Delete message',
415 onClick: () => {},
idillon927b7592022-09-15 12:56:45 -0400416 },
simond47ef9e2022-09-28 22:24:28 -0400417 ];
idillon927b7592022-09-15 12:56:45 -0400418
simond47ef9e2022-09-28 22:24:28 -0400419 const toggleMoreMenu = useCallback(() => setOpen((open) => !open), [setOpen]);
idillon927b7592022-09-15 12:56:45 -0400420
simond47ef9e2022-09-28 22:24:28 -0400421 const onClose = useCallback(() => {
422 setOpen(false);
423 }, [setOpen]);
idillon927b7592022-09-15 12:56:45 -0400424
425 return (
426 <Tooltip
idillon927b7592022-09-15 12:56:45 -0400427 classes={{ tooltip: className }} // Required for styles. Don't know why
idillon-sfl9d956ab2022-10-20 16:33:24 -0400428 placement={position === 'start' ? 'right-start' : 'left-start'}
idillon927b7592022-09-15 12:56:45 -0400429 PopperProps={{
430 modifiers: [
431 {
simond47ef9e2022-09-28 22:24:28 -0400432 name: 'offset',
idillon927b7592022-09-15 12:56:45 -0400433 options: {
simond47ef9e2022-09-28 22:24:28 -0400434 offset: [-2, -30],
idillon927b7592022-09-15 12:56:45 -0400435 },
436 },
437 ],
438 }}
439 onClose={onClose}
440 title={
simond47ef9e2022-09-28 22:24:28 -0400441 <Stack>
idillon927b7592022-09-15 12:56:45 -0400442 <Stack // Main options
443 direction="row"
444 spacing="16px"
idillonef9ab812022-11-18 13:46:24 -0500445 padding="16px"
idillon927b7592022-09-15 12:56:45 -0400446 >
simond47ef9e2022-09-28 22:24:28 -0400447 {emojis.map((emoji) => (
448 <EmojiButton key={emoji} emoji={emoji} />
449 ))}
450 <ReplyMessageButton />
451 <MoreButton onClick={toggleMoreMenu} />
idillon927b7592022-09-15 12:56:45 -0400452 </Stack>
idillonef9ab812022-11-18 13:46:24 -0500453 {open && (
idillon927b7592022-09-15 12:56:45 -0400454 <>
idillonef9ab812022-11-18 13:46:24 -0500455 <Divider sx={{ marginX: '16px' }} />
456 <PopoverList items={additionalOptions} />
idillon927b7592022-09-15 12:56:45 -0400457 </>
simond47ef9e2022-09-28 22:24:28 -0400458 )}
idillon927b7592022-09-15 12:56:45 -0400459 </Stack>
460 }
idillon-sfl9d956ab2022-10-20 16:33:24 -0400461 >
idillon-sfl118ae442022-10-25 10:42:54 -0400462 {/* div fixes 'Function components cannot be given refs' error */}
463 <div>{children}</div>
idillon-sfl9d956ab2022-10-20 16:33:24 -0400464 </Tooltip>
simond47ef9e2022-09-28 22:24:28 -0400465 );
idillon-sfl9d956ab2022-10-20 16:33:24 -0400466})(({ position }) => {
simond47ef9e2022-09-28 22:24:28 -0400467 const largeRadius = '20px';
468 const smallRadius = '5px';
idillon927b7592022-09-15 12:56:45 -0400469 return {
simond47ef9e2022-09-28 22:24:28 -0400470 backgroundColor: 'white',
idillonef9ab812022-11-18 13:46:24 -0500471 padding: '0px',
simond47ef9e2022-09-28 22:24:28 -0400472 boxShadow: '3px 3px 7px #00000029',
idillon927b7592022-09-15 12:56:45 -0400473 borderRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400474 borderStartStartRadius: position === 'start' ? smallRadius : largeRadius,
475 borderStartEndRadius: position === 'end' ? smallRadius : largeRadius,
idillonef9ab812022-11-18 13:46:24 -0500476 overflow: 'hidden',
simond47ef9e2022-09-28 22:24:28 -0400477 };
idillon927b7592022-09-15 12:56:45 -0400478});
479
idillon-sfl118ae442022-10-25 10:42:54 -0400480interface BubbleProps {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400481 position: MessagePosition;
482 isFirstOfGroup: boolean;
483 isLastOfGroup: boolean;
idillon-sfl118ae442022-10-25 10:42:54 -0400484 bubbleColor: string;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400485 children: ReactNode;
486}
487
idillon-sfl118ae442022-10-25 10:42:54 -0400488const Bubble = ({ position, isFirstOfGroup, isLastOfGroup, bubbleColor, children }: BubbleProps) => {
simond47ef9e2022-09-28 22:24:28 -0400489 const largeRadius = '20px';
490 const smallRadius = '5px';
Adrien Béraud023f7cf2022-09-18 14:57:53 -0400491 const radius = useMemo(() => {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400492 if (position === 'start') {
idillonbef18a52022-09-01 01:51:40 -0400493 return {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400494 borderStartStartRadius: isFirstOfGroup ? largeRadius : smallRadius,
idillonbef18a52022-09-01 01:51:40 -0400495 borderStartEndRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400496 borderEndStartRadius: isLastOfGroup ? largeRadius : smallRadius,
idillonbef18a52022-09-01 01:51:40 -0400497 borderEndEndRadius: largeRadius,
simond47ef9e2022-09-28 22:24:28 -0400498 };
idillonbef18a52022-09-01 01:51:40 -0400499 }
500 return {
501 borderStartStartRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400502 borderStartEndRadius: isFirstOfGroup ? largeRadius : smallRadius,
idillonbef18a52022-09-01 01:51:40 -0400503 borderEndStartRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400504 borderEndEndRadius: isLastOfGroup ? largeRadius : smallRadius,
simond47ef9e2022-09-28 22:24:28 -0400505 };
idillon-sfl9d956ab2022-10-20 16:33:24 -0400506 }, [isFirstOfGroup, isLastOfGroup, position]);
idillonbef18a52022-09-01 01:51:40 -0400507
508 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400509 <Box
510 sx={{
511 width: 'fit-content',
512 backgroundColor: bubbleColor,
513 padding: bubblePadding,
514 ...radius,
515 }}
516 >
517 {children}
518 </Box>
simond47ef9e2022-09-28 22:24:28 -0400519 );
520};
idillonbef18a52022-09-01 01:51:40 -0400521
idillon-sfl9d956ab2022-10-20 16:33:24 -0400522interface ParticipantNameProps {
523 name: string;
524}
525
526const ParticipantName = ({ name }: ParticipantNameProps) => {
idillonbef18a52022-09-01 01:51:40 -0400527 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400528 <Typography variant="caption" color="#A7A7A7" fontWeight={700}>
529 {name}
530 </Typography>
531 );
532};
533
534interface MessageProps {
535 messageIndex: number;
536 messages: Message[];
537 isAccountMessage: boolean;
538 author: Account | Contact;
539}
540
541export const MessageRow = ({ messageIndex, messages, isAccountMessage, author }: MessageProps) => {
542 const message = messages[messageIndex];
543 const previousMessage = findPreviousVisibleMessage(messages, messageIndex);
544 const nextMessage = findNextVisibleMessage(messages, messageIndex);
545 const time = dayjs.unix(Number(message.timestamp));
546 const previousTime = dayjs.unix(Number(previousMessage?.timestamp));
547 const showDate =
548 message?.type === 'initial' || previousTime.year() !== time.year() || previousTime.dayOfYear() !== time.dayOfYear();
549 const showTime = checkShowsTime(time, previousTime);
550 let messageComponent;
551 if (checkIsUserMessageType(message.type)) {
552 messageComponent = (
553 <UserMessageRow
554 message={message}
555 previousMessage={previousMessage}
556 nextMessage={nextMessage}
557 time={time}
558 showsTime={showTime}
559 isAccountMessage={isAccountMessage}
560 author={author}
561 />
562 );
563 } else if (checkIsNotificationMessageType(message.type)) {
564 messageComponent = <NotificationMessageRow message={message} />;
565 } else if (checkIsInvisibleMessageType(message.type)) {
566 return null;
567 } else {
568 const _exhaustiveCheck: never = message.type;
569 return _exhaustiveCheck;
570 }
571
572 return (
573 <Stack>
574 {showDate && <DateIndicator time={time} />}
575 {showTime && <TimeIndicator time={time} hasDateOnTop={showDate} />}
576 {messageComponent}
577 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400578 );
579};