blob: 0256e83597d3e39bc9d2bbe97a92f52a3028c53e [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 */
idillon-sfl9d956ab2022-10-20 16:33:24 -040018import {
19 Box,
20 Chip,
21 Divider,
22 List,
23 ListItemButton,
24 ListItemText,
25 Stack,
26 Theme,
27 Tooltip,
28 Typography,
29} from '@mui/material';
simond47ef9e2022-09-28 22:24:28 -040030import { styled } from '@mui/material/styles';
idillon-sfl9d956ab2022-10-20 16:33:24 -040031import dayjs, { Dayjs } from 'dayjs';
idillon-sfl118ae442022-10-25 10:42:54 -040032import { Account, Contact, Message } from 'jami-web-common';
33import { ReactElement, ReactNode, useCallback, useMemo, useState } from 'react';
simond47ef9e2022-09-28 22:24:28 -040034import { useTranslation } from 'react-i18next';
simon07b4eb02022-09-29 17:50:26 -040035
idillon-sfl9d956ab2022-10-20 16:33:24 -040036import { EmojiButton, MoreButton, ReplyMessageButton } from './Button';
simond47ef9e2022-09-28 22:24:28 -040037import ConversationAvatar from './ConversationAvatar';
idillon-sfl9d956ab2022-10-20 16:33:24 -040038import { OppositeArrowsIcon, TrashBinIcon, TwoSheetsIcon } from './SvgIcon';
Larbi Gharibe9af9732021-03-31 15:08:01 +010039
idillon-sfl9d956ab2022-10-20 16:33:24 -040040type MessagePosition = 'start' | 'end';
41
idillon-sfl118ae442022-10-25 10:42:54 -040042const notificationMessageTypes = ['initial', 'member'] as const;
43type NotificationMessageType = typeof notificationMessageTypes[number];
44const checkIsNotificationMessageType = (type: Message['type'] | undefined): type is NotificationMessageType => {
45 return notificationMessageTypes.includes(type as NotificationMessageType);
simond47ef9e2022-09-28 22:24:28 -040046};
Larbi Gharibe9af9732021-03-31 15:08:01 +010047
idillon-sfl118ae442022-10-25 10:42:54 -040048const invisibleMessageTypes = ['application/update-profile', 'merge', 'vote'] as const;
49type InvisibleMessageType = typeof invisibleMessageTypes[number];
50const checkIsInvisibleMessageType = (type: Message['type'] | undefined): type is InvisibleMessageType => {
51 return invisibleMessageTypes.includes(type as InvisibleMessageType);
simond47ef9e2022-09-28 22:24:28 -040052};
idillonbef18a52022-09-01 01:51:40 -040053
idillon-sfl118ae442022-10-25 10:42:54 -040054const userMessageTypes = ['text/plain', 'application/data-transfer+json', 'application/call-history+json'] as const;
55type UserMessageType = typeof userMessageTypes[number];
56const checkIsUserMessageType = (type: Message['type'] | undefined): type is UserMessageType => {
57 return userMessageTypes.includes(type as UserMessageType);
58};
59
60const checkShowsTime = (time: Dayjs, previousTime: Dayjs) => {
61 return !previousTime.isSame(time) && !time.isBetween(previousTime, previousTime?.add(1, 'minute'));
62};
63
64const findPreviousVisibleMessage = (messages: Message[], messageIndex: number) => {
65 for (let i = messageIndex + 1; i < messages.length; ++i) {
66 const message = messages[i];
67 if (!checkIsInvisibleMessageType(message?.type)) {
68 return message;
69 }
70 }
71};
72
73const findNextVisibleMessage = (messages: Message[], messageIndex: number) => {
74 for (let i = messageIndex - 1; i >= 0; --i) {
75 const message = messages[i];
76 if (!checkIsInvisibleMessageType(message?.type)) {
77 return message;
78 }
79 }
80};
81
82const avatarSize = '22px';
83const spacingBetweenAvatarAndBubble = '10px';
84const bubblePadding = '16px';
85
86interface MessageCallProps {
87 message: Message;
88 isAccountMessage: boolean;
idillon-sfl9d956ab2022-10-20 16:33:24 -040089 isFirstOfGroup: boolean;
90 isLastOfGroup: boolean;
91}
92
idillon-sfl118ae442022-10-25 10:42:54 -040093const MessageCall = ({ isAccountMessage, isFirstOfGroup, isLastOfGroup }: MessageCallProps) => {
94 const position = isAccountMessage ? 'end' : 'start';
95 const bubbleColor = isAccountMessage ? '#005699' : '#E5E5E5';
96 const textColor = isAccountMessage ? 'white' : 'black';
idillonbef18a52022-09-01 01:51:40 -040097 return (
idillon-sfl118ae442022-10-25 10:42:54 -040098 <Bubble position={position} isFirstOfGroup={isFirstOfGroup} isLastOfGroup={isLastOfGroup} bubbleColor={bubbleColor}>
99 <Typography variant="body1" color={textColor} textAlign={position}>
100 &quot;Appel&quot;
101 </Typography>
102 </Bubble>
103 );
104};
105
106const MessageInitial = () => {
107 const { t } = useTranslation();
108 return <>{t('message_swarm_created')}</>;
109};
110
111interface MessageDataTransferProps {
112 message: Message;
113 isAccountMessage: boolean;
114 isFirstOfGroup: boolean;
115 isLastOfGroup: boolean;
116}
117
118const MessageDataTransfer = ({ isAccountMessage, isFirstOfGroup, isLastOfGroup }: MessageDataTransferProps) => {
119 const position = isAccountMessage ? 'end' : 'start';
120 return (
121 <Bubble bubbleColor="#E5E5E5" position={position} isFirstOfGroup={isFirstOfGroup} isLastOfGroup={isLastOfGroup}>
simon80b7b3b2022-09-28 17:50:10 -0400122 &quot;data-transfer&quot;
idillon-sfl118ae442022-10-25 10:42:54 -0400123 </Bubble>
simond47ef9e2022-09-28 22:24:28 -0400124 );
125};
idillonbef18a52022-09-01 01:51:40 -0400126
idillon-sfl9d956ab2022-10-20 16:33:24 -0400127interface MessageMemberProps {
128 message: Message;
129}
130
idillon-sfl118ae442022-10-25 10:42:54 -0400131const MessageMember = ({ message }: MessageMemberProps) => {
simond47ef9e2022-09-28 22:24:28 -0400132 const { t } = useTranslation();
idillonbef18a52022-09-01 01:51:40 -0400133 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400134 <Chip
135 sx={{
136 width: 'fit-content',
137 }}
138 label={t('message_user_joined', { user: message.author })}
139 />
simond47ef9e2022-09-28 22:24:28 -0400140 );
141};
idillonbef18a52022-09-01 01:51:40 -0400142
idillon-sfl9d956ab2022-10-20 16:33:24 -0400143interface MessageTextProps {
144 message: Message;
idillon-sfl118ae442022-10-25 10:42:54 -0400145 isAccountMessage: boolean;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400146 isFirstOfGroup: boolean;
147 isLastOfGroup: boolean;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400148}
149
idillon-sfl118ae442022-10-25 10:42:54 -0400150const MessageText = ({ message, isAccountMessage, isFirstOfGroup, isLastOfGroup }: MessageTextProps) => {
151 const position = isAccountMessage ? 'end' : 'start';
152 const bubbleColor = isAccountMessage ? '#005699' : '#E5E5E5';
153 const textColor = isAccountMessage ? 'white' : 'black';
idillonbef18a52022-09-01 01:51:40 -0400154 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400155 <MessageTooltip position={position}>
156 <Bubble
157 bubbleColor={bubbleColor}
158 position={position}
159 isFirstOfGroup={isFirstOfGroup}
160 isLastOfGroup={isLastOfGroup}
161 >
162 <Typography variant="body1" color={textColor} textAlign={position}>
163 {message.body}
164 </Typography>
165 </Bubble>
166 </MessageTooltip>
simond47ef9e2022-09-28 22:24:28 -0400167 );
168};
idillonbef18a52022-09-01 01:51:40 -0400169
idillon-sfl118ae442022-10-25 10:42:54 -0400170interface DateIndicatorProps {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400171 time: Dayjs;
172}
173
idillon-sfl118ae442022-10-25 10:42:54 -0400174const DateIndicator = ({ time }: DateIndicatorProps) => {
idillon-sfl0e1a0d92022-10-25 16:52:44 -0400175 const { i18n } = useTranslation();
idillonbef18a52022-09-01 01:51:40 -0400176
idillon-sfl0e1a0d92022-10-25 16:52:44 -0400177 const textDate = useMemo(() => {
178 if (time.isToday()) {
179 return new Intl.RelativeTimeFormat(i18n.language, { numeric: 'auto' }).format(0, 'day');
180 } else if (time.isYesterday()) {
181 return new Intl.RelativeTimeFormat(i18n.language, { numeric: 'auto' }).format(-1, 'day');
182 } else {
183 return dayjs(time).locale(i18n.language).format('L');
184 }
185 }, [i18n, time]);
idillonbef18a52022-09-01 01:51:40 -0400186
187 return (
simond47ef9e2022-09-28 22:24:28 -0400188 <Box marginTop="30px">
idillon04245a12022-09-01 11:12:17 -0400189 <Divider
190 sx={{
simond47ef9e2022-09-28 22:24:28 -0400191 '.MuiDivider-wrapper': {
idillon04245a12022-09-01 11:12:17 -0400192 margin: 0,
193 padding: 0,
194 },
simond47ef9e2022-09-28 22:24:28 -0400195 '&::before': {
196 borderTop: '1px solid #E5E5E5',
idillon04245a12022-09-01 11:12:17 -0400197 },
simond47ef9e2022-09-28 22:24:28 -0400198 '&::after': {
199 borderTop: '1px solid #E5E5E5',
idillon04245a12022-09-01 11:12:17 -0400200 },
201 }}
202 >
203 <Typography
204 variant="caption"
205 fontWeight={700}
206 border="1px solid #E5E5E5"
207 borderRadius="5px"
208 padding="10px 16px"
idillon-sfl0e1a0d92022-10-25 16:52:44 -0400209 textTransform="capitalize"
idillon04245a12022-09-01 11:12:17 -0400210 >
211 {textDate}
212 </Typography>
idillonbef18a52022-09-01 01:51:40 -0400213 </Divider>
214 </Box>
simond47ef9e2022-09-28 22:24:28 -0400215 );
216};
idillonbef18a52022-09-01 01:51:40 -0400217
idillon-sfl118ae442022-10-25 10:42:54 -0400218interface TimeIndicatorProps {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400219 time: Dayjs;
220 hasDateOnTop: boolean;
221}
222
idillon-sfl118ae442022-10-25 10:42:54 -0400223const TimeIndicator = ({ time, hasDateOnTop }: TimeIndicatorProps) => {
idillon-sfl0e1a0d92022-10-25 16:52:44 -0400224 const { i18n } = useTranslation();
225
226 const textTime = useMemo(() => {
227 return dayjs(time).locale(i18n.language).format('LT');
228 }, [i18n, time]);
idillonbef18a52022-09-01 01:51:40 -0400229
230 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400231 <Stack direction="row" justifyContent="center" marginTop={hasDateOnTop ? '20px' : '30px'}>
simond47ef9e2022-09-28 22:24:28 -0400232 <Typography variant="caption" color="#A7A7A7" fontWeight={700}>
idillonbef18a52022-09-01 01:51:40 -0400233 {textTime}
234 </Typography>
235 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400236 );
237};
idillonbef18a52022-09-01 01:51:40 -0400238
idillon-sfl118ae442022-10-25 10:42:54 -0400239interface NotificationMessageRowProps {
240 message: Message;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400241}
242
idillon-sfl118ae442022-10-25 10:42:54 -0400243const NotificationMessageRow = ({ message }: NotificationMessageRowProps) => {
244 let messageComponent;
245 switch (message.type) {
246 case 'initial':
247 messageComponent = <MessageInitial />;
248 break;
249 case 'member':
250 messageComponent = <MessageMember message={message} />;
251 break;
252 default:
253 console.error(`${NotificationMessageRow.name} received unhandled message type: ${message.type}`);
254 return null;
idillonae655dd2022-10-14 18:11:02 -0400255 }
256
idillonbef18a52022-09-01 01:51:40 -0400257 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400258 <Stack paddingTop={'30px'} alignItems="center">
259 {messageComponent}
260 </Stack>
261 );
262};
263
264interface UserMessageRowProps {
265 message: Message;
266 isAccountMessage: boolean;
267 previousMessage: Message | undefined;
268 nextMessage: Message | undefined;
269 time: Dayjs;
270 showsTime: boolean;
271 author: Account | Contact;
272}
273
274const UserMessageRow = ({
275 message,
276 previousMessage,
277 nextMessage,
278 isAccountMessage,
279 time,
280 showsTime,
281 author,
282}: UserMessageRowProps) => {
283 const authorName = author.getDisplayName();
284 const position = isAccountMessage ? 'end' : 'start';
285
286 const previousIsUserMessageType = checkIsUserMessageType(previousMessage?.type);
287 const nextIsUserMessageType = checkIsUserMessageType(nextMessage?.type);
288 const nextTime = dayjs.unix(Number(nextMessage?.timestamp));
289 const nextShowsTime = checkShowsTime(nextTime, time);
290 const isFirstOfGroup = showsTime || !previousIsUserMessageType || previousMessage?.author !== message.author;
291 const isLastOfGroup = nextShowsTime || !nextIsUserMessageType || message.author !== nextMessage?.author;
292
293 const props = {
294 message,
295 isAccountMessage,
296 isFirstOfGroup,
297 isLastOfGroup,
298 };
299
300 let MessageComponent;
301 switch (message.type) {
302 case 'text/plain':
303 MessageComponent = MessageText;
304 break;
305 case 'application/data-transfer+json':
306 MessageComponent = MessageDataTransfer;
307 break;
308 case 'application/call-history+json':
309 MessageComponent = MessageCall;
310 break;
311 default:
312 console.error(`${UserMessageRow.name} received unhandled message type: ${message.type}`);
313 return null;
314 }
315
316 const participantNamePadding = isAccountMessage
317 ? bubblePadding
318 : parseInt(avatarSize) + parseInt(spacingBetweenAvatarAndBubble) + parseInt(bubblePadding) + 'px';
319
320 return (
321 <Stack alignItems={position}>
322 {isFirstOfGroup && (
323 <Box padding={`30px ${participantNamePadding} 0 ${participantNamePadding}`}>
324 <ParticipantName name={authorName} />
325 </Box>
simond47ef9e2022-09-28 22:24:28 -0400326 )}
idillon-sfl118ae442022-10-25 10:42:54 -0400327 <Stack
328 direction="row"
329 justifyContent={position}
330 alignItems="end"
331 spacing={spacingBetweenAvatarAndBubble}
332 paddingTop="6px"
idillonbef18a52022-09-01 01:51:40 -0400333 width="66.66%"
idillonbef18a52022-09-01 01:51:40 -0400334 >
idillon-sfl118ae442022-10-25 10:42:54 -0400335 <Box sx={{ width: avatarSize }}>
336 {!isAccountMessage && isLastOfGroup && (
337 <ConversationAvatar
338 displayName={authorName}
339 sx={{ width: avatarSize, height: avatarSize, fontSize: '15px' }}
340 />
341 )}
342 </Box>
343 <MessageComponent {...props} />
idillonbef18a52022-09-01 01:51:40 -0400344 </Stack>
345 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400346 );
347};
idillonbef18a52022-09-01 01:51:40 -0400348
idillon-sfl9d956ab2022-10-20 16:33:24 -0400349interface MessageTooltipProps {
350 className?: string;
351 position: MessagePosition;
352 children: ReactElement;
353}
354
355const MessageTooltip = styled(({ className, position, children }: MessageTooltipProps) => {
simond47ef9e2022-09-28 22:24:28 -0400356 const [open, setOpen] = useState(false);
357 const emojis = ['😎', '😄', '😍']; // Should be last three used emojis
idillon927b7592022-09-15 12:56:45 -0400358 const additionalOptions = [
359 {
360 Icon: TwoSheetsIcon,
simond47ef9e2022-09-28 22:24:28 -0400361 text: 'Copy',
idillon927b7592022-09-15 12:56:45 -0400362 action: () => {},
363 },
364 {
365 Icon: OppositeArrowsIcon,
simond47ef9e2022-09-28 22:24:28 -0400366 text: 'Transfer',
idillon927b7592022-09-15 12:56:45 -0400367 action: () => {},
368 },
369 {
370 Icon: TrashBinIcon,
simond47ef9e2022-09-28 22:24:28 -0400371 text: 'Delete message',
idillon927b7592022-09-15 12:56:45 -0400372 action: () => {},
373 },
simond47ef9e2022-09-28 22:24:28 -0400374 ];
idillon927b7592022-09-15 12:56:45 -0400375
simond47ef9e2022-09-28 22:24:28 -0400376 const toggleMoreMenu = useCallback(() => setOpen((open) => !open), [setOpen]);
idillon927b7592022-09-15 12:56:45 -0400377
simond47ef9e2022-09-28 22:24:28 -0400378 const onClose = useCallback(() => {
379 setOpen(false);
380 }, [setOpen]);
idillon927b7592022-09-15 12:56:45 -0400381
382 return (
383 <Tooltip
idillon927b7592022-09-15 12:56:45 -0400384 classes={{ tooltip: className }} // Required for styles. Don't know why
idillon-sfl9d956ab2022-10-20 16:33:24 -0400385 placement={position === 'start' ? 'right-start' : 'left-start'}
idillon927b7592022-09-15 12:56:45 -0400386 PopperProps={{
387 modifiers: [
388 {
simond47ef9e2022-09-28 22:24:28 -0400389 name: 'offset',
idillon927b7592022-09-15 12:56:45 -0400390 options: {
simond47ef9e2022-09-28 22:24:28 -0400391 offset: [-2, -30],
idillon927b7592022-09-15 12:56:45 -0400392 },
393 },
394 ],
395 }}
396 onClose={onClose}
397 title={
simond47ef9e2022-09-28 22:24:28 -0400398 <Stack>
simond47ef9e2022-09-28 22:24:28 -0400399 {/* Whole tooltip's content */}
idillon927b7592022-09-15 12:56:45 -0400400 <Stack // Main options
401 direction="row"
402 spacing="16px"
403 >
simond47ef9e2022-09-28 22:24:28 -0400404 {emojis.map((emoji) => (
405 <EmojiButton key={emoji} emoji={emoji} />
406 ))}
407 <ReplyMessageButton />
408 <MoreButton onClick={toggleMoreMenu} />
idillon927b7592022-09-15 12:56:45 -0400409 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400410 {open && ( // Additional menu options
idillon927b7592022-09-15 12:56:45 -0400411 <>
simond47ef9e2022-09-28 22:24:28 -0400412 <Divider sx={{ paddingTop: '16px' }} />
413 <List sx={{ padding: 0, paddingTop: '8px', marginBottom: '-8px' }}>
414 {additionalOptions.map((option) => (
415 <ListItemButton
416 key={option.text}
417 sx={{
418 padding: '8px',
419 }}
420 >
421 <Stack // Could not find proper way to set spacing between ListItemIcon and ListItemText
422 direction="row"
423 spacing="16px"
idillon927b7592022-09-15 12:56:45 -0400424 >
simond47ef9e2022-09-28 22:24:28 -0400425 <option.Icon
426 sx={{
427 height: '16px',
428 margin: 0,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400429 color: (theme: Theme) => theme?.palette?.primary?.dark,
simond47ef9e2022-09-28 22:24:28 -0400430 }}
431 />
432 <ListItemText
433 primary={option.text}
434 primaryTypographyProps={{
435 fontSize: '12px',
436 lineHeight: '16px',
437 }}
438 sx={{
439 height: '16px',
440 margin: 0,
441 }}
442 />
443 </Stack>
444 </ListItemButton>
445 ))}
446 </List>
idillon927b7592022-09-15 12:56:45 -0400447 </>
simond47ef9e2022-09-28 22:24:28 -0400448 )}
idillon927b7592022-09-15 12:56:45 -0400449 </Stack>
450 }
idillon-sfl9d956ab2022-10-20 16:33:24 -0400451 >
idillon-sfl118ae442022-10-25 10:42:54 -0400452 {/* div fixes 'Function components cannot be given refs' error */}
453 <div>{children}</div>
idillon-sfl9d956ab2022-10-20 16:33:24 -0400454 </Tooltip>
simond47ef9e2022-09-28 22:24:28 -0400455 );
idillon-sfl9d956ab2022-10-20 16:33:24 -0400456})(({ position }) => {
simond47ef9e2022-09-28 22:24:28 -0400457 const largeRadius = '20px';
458 const smallRadius = '5px';
idillon927b7592022-09-15 12:56:45 -0400459 return {
simond47ef9e2022-09-28 22:24:28 -0400460 backgroundColor: 'white',
461 padding: '16px',
462 boxShadow: '3px 3px 7px #00000029',
idillon927b7592022-09-15 12:56:45 -0400463 borderRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400464 borderStartStartRadius: position === 'start' ? smallRadius : largeRadius,
465 borderStartEndRadius: position === 'end' ? smallRadius : largeRadius,
simond47ef9e2022-09-28 22:24:28 -0400466 };
idillon927b7592022-09-15 12:56:45 -0400467});
468
idillon-sfl118ae442022-10-25 10:42:54 -0400469interface BubbleProps {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400470 position: MessagePosition;
471 isFirstOfGroup: boolean;
472 isLastOfGroup: boolean;
idillon-sfl118ae442022-10-25 10:42:54 -0400473 bubbleColor: string;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400474 children: ReactNode;
475}
476
idillon-sfl118ae442022-10-25 10:42:54 -0400477const Bubble = ({ position, isFirstOfGroup, isLastOfGroup, bubbleColor, children }: BubbleProps) => {
simond47ef9e2022-09-28 22:24:28 -0400478 const largeRadius = '20px';
479 const smallRadius = '5px';
Adrien Béraud023f7cf2022-09-18 14:57:53 -0400480 const radius = useMemo(() => {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400481 if (position === 'start') {
idillonbef18a52022-09-01 01:51:40 -0400482 return {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400483 borderStartStartRadius: isFirstOfGroup ? largeRadius : smallRadius,
idillonbef18a52022-09-01 01:51:40 -0400484 borderStartEndRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400485 borderEndStartRadius: isLastOfGroup ? largeRadius : smallRadius,
idillonbef18a52022-09-01 01:51:40 -0400486 borderEndEndRadius: largeRadius,
simond47ef9e2022-09-28 22:24:28 -0400487 };
idillonbef18a52022-09-01 01:51:40 -0400488 }
489 return {
490 borderStartStartRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400491 borderStartEndRadius: isFirstOfGroup ? largeRadius : smallRadius,
idillonbef18a52022-09-01 01:51:40 -0400492 borderEndStartRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400493 borderEndEndRadius: isLastOfGroup ? largeRadius : smallRadius,
simond47ef9e2022-09-28 22:24:28 -0400494 };
idillon-sfl9d956ab2022-10-20 16:33:24 -0400495 }, [isFirstOfGroup, isLastOfGroup, position]);
idillonbef18a52022-09-01 01:51:40 -0400496
497 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400498 <Box
499 sx={{
500 width: 'fit-content',
501 backgroundColor: bubbleColor,
502 padding: bubblePadding,
503 ...radius,
504 }}
505 >
506 {children}
507 </Box>
simond47ef9e2022-09-28 22:24:28 -0400508 );
509};
idillonbef18a52022-09-01 01:51:40 -0400510
idillon-sfl9d956ab2022-10-20 16:33:24 -0400511interface ParticipantNameProps {
512 name: string;
513}
514
515const ParticipantName = ({ name }: ParticipantNameProps) => {
idillonbef18a52022-09-01 01:51:40 -0400516 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400517 <Typography variant="caption" color="#A7A7A7" fontWeight={700}>
518 {name}
519 </Typography>
520 );
521};
522
523interface MessageProps {
524 messageIndex: number;
525 messages: Message[];
526 isAccountMessage: boolean;
527 author: Account | Contact;
528}
529
530export const MessageRow = ({ messageIndex, messages, isAccountMessage, author }: MessageProps) => {
531 const message = messages[messageIndex];
532 const previousMessage = findPreviousVisibleMessage(messages, messageIndex);
533 const nextMessage = findNextVisibleMessage(messages, messageIndex);
534 const time = dayjs.unix(Number(message.timestamp));
535 const previousTime = dayjs.unix(Number(previousMessage?.timestamp));
536 const showDate =
537 message?.type === 'initial' || previousTime.year() !== time.year() || previousTime.dayOfYear() !== time.dayOfYear();
538 const showTime = checkShowsTime(time, previousTime);
539 let messageComponent;
540 if (checkIsUserMessageType(message.type)) {
541 messageComponent = (
542 <UserMessageRow
543 message={message}
544 previousMessage={previousMessage}
545 nextMessage={nextMessage}
546 time={time}
547 showsTime={showTime}
548 isAccountMessage={isAccountMessage}
549 author={author}
550 />
551 );
552 } else if (checkIsNotificationMessageType(message.type)) {
553 messageComponent = <NotificationMessageRow message={message} />;
554 } else if (checkIsInvisibleMessageType(message.type)) {
555 return null;
556 } else {
557 const _exhaustiveCheck: never = message.type;
558 return _exhaustiveCheck;
559 }
560
561 return (
562 <Stack>
563 {showDate && <DateIndicator time={time} />}
564 {showTime && <TimeIndicator time={time} hasDateOnTop={showDate} />}
565 {messageComponent}
566 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400567 );
568};