blob: 981d3d567b59b8e157392c79f5398ec8f380ae65 [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 dayOfYear from 'dayjs/plugin/dayOfYear';
33import isBetween from 'dayjs/plugin/isBetween';
simond47ef9e2022-09-28 22:24:28 -040034import isToday from 'dayjs/plugin/isToday';
35import isYesterday from 'dayjs/plugin/isYesterday';
idillon-sfl118ae442022-10-25 10:42:54 -040036import { Account, Contact, Message } from 'jami-web-common';
37import { ReactElement, ReactNode, useCallback, useMemo, useState } from 'react';
simond47ef9e2022-09-28 22:24:28 -040038import { useTranslation } from 'react-i18next';
simon07b4eb02022-09-29 17:50:26 -040039
idillon-sfl9d956ab2022-10-20 16:33:24 -040040import { EmojiButton, MoreButton, ReplyMessageButton } from './Button';
simond47ef9e2022-09-28 22:24:28 -040041import ConversationAvatar from './ConversationAvatar';
idillon-sfl9d956ab2022-10-20 16:33:24 -040042import { OppositeArrowsIcon, TrashBinIcon, TwoSheetsIcon } from './SvgIcon';
Larbi Gharibe9af9732021-03-31 15:08:01 +010043
idillon-sfl118ae442022-10-25 10:42:54 -040044dayjs.extend(dayOfYear);
45dayjs.extend(isBetween);
simond47ef9e2022-09-28 22:24:28 -040046dayjs.extend(isToday);
47dayjs.extend(isYesterday);
idillonbef18a52022-09-01 01:51:40 -040048
idillon-sfl9d956ab2022-10-20 16:33:24 -040049type MessagePosition = 'start' | 'end';
50
idillon-sfl118ae442022-10-25 10:42:54 -040051const notificationMessageTypes = ['initial', 'member'] as const;
52type NotificationMessageType = typeof notificationMessageTypes[number];
53const checkIsNotificationMessageType = (type: Message['type'] | undefined): type is NotificationMessageType => {
54 return notificationMessageTypes.includes(type as NotificationMessageType);
simond47ef9e2022-09-28 22:24:28 -040055};
Larbi Gharibe9af9732021-03-31 15:08:01 +010056
idillon-sfl118ae442022-10-25 10:42:54 -040057const invisibleMessageTypes = ['application/update-profile', 'merge', 'vote'] as const;
58type InvisibleMessageType = typeof invisibleMessageTypes[number];
59const checkIsInvisibleMessageType = (type: Message['type'] | undefined): type is InvisibleMessageType => {
60 return invisibleMessageTypes.includes(type as InvisibleMessageType);
simond47ef9e2022-09-28 22:24:28 -040061};
idillonbef18a52022-09-01 01:51:40 -040062
idillon-sfl118ae442022-10-25 10:42:54 -040063const userMessageTypes = ['text/plain', 'application/data-transfer+json', 'application/call-history+json'] as const;
64type UserMessageType = typeof userMessageTypes[number];
65const checkIsUserMessageType = (type: Message['type'] | undefined): type is UserMessageType => {
66 return userMessageTypes.includes(type as UserMessageType);
67};
68
69const checkShowsTime = (time: Dayjs, previousTime: Dayjs) => {
70 return !previousTime.isSame(time) && !time.isBetween(previousTime, previousTime?.add(1, 'minute'));
71};
72
73const findPreviousVisibleMessage = (messages: Message[], messageIndex: number) => {
74 for (let i = messageIndex + 1; i < messages.length; ++i) {
75 const message = messages[i];
76 if (!checkIsInvisibleMessageType(message?.type)) {
77 return message;
78 }
79 }
80};
81
82const findNextVisibleMessage = (messages: Message[], messageIndex: number) => {
83 for (let i = messageIndex - 1; i >= 0; --i) {
84 const message = messages[i];
85 if (!checkIsInvisibleMessageType(message?.type)) {
86 return message;
87 }
88 }
89};
90
91const avatarSize = '22px';
92const spacingBetweenAvatarAndBubble = '10px';
93const bubblePadding = '16px';
94
95interface MessageCallProps {
96 message: Message;
97 isAccountMessage: boolean;
idillon-sfl9d956ab2022-10-20 16:33:24 -040098 isFirstOfGroup: boolean;
99 isLastOfGroup: boolean;
100}
101
idillon-sfl118ae442022-10-25 10:42:54 -0400102const MessageCall = ({ isAccountMessage, isFirstOfGroup, isLastOfGroup }: MessageCallProps) => {
103 const position = isAccountMessage ? 'end' : 'start';
104 const bubbleColor = isAccountMessage ? '#005699' : '#E5E5E5';
105 const textColor = isAccountMessage ? 'white' : 'black';
idillonbef18a52022-09-01 01:51:40 -0400106 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400107 <Bubble position={position} isFirstOfGroup={isFirstOfGroup} isLastOfGroup={isLastOfGroup} bubbleColor={bubbleColor}>
108 <Typography variant="body1" color={textColor} textAlign={position}>
109 &quot;Appel&quot;
110 </Typography>
111 </Bubble>
112 );
113};
114
115const MessageInitial = () => {
116 const { t } = useTranslation();
117 return <>{t('message_swarm_created')}</>;
118};
119
120interface MessageDataTransferProps {
121 message: Message;
122 isAccountMessage: boolean;
123 isFirstOfGroup: boolean;
124 isLastOfGroup: boolean;
125}
126
127const MessageDataTransfer = ({ isAccountMessage, isFirstOfGroup, isLastOfGroup }: MessageDataTransferProps) => {
128 const position = isAccountMessage ? 'end' : 'start';
129 return (
130 <Bubble bubbleColor="#E5E5E5" position={position} isFirstOfGroup={isFirstOfGroup} isLastOfGroup={isLastOfGroup}>
simon80b7b3b2022-09-28 17:50:10 -0400131 &quot;data-transfer&quot;
idillon-sfl118ae442022-10-25 10:42:54 -0400132 </Bubble>
simond47ef9e2022-09-28 22:24:28 -0400133 );
134};
idillonbef18a52022-09-01 01:51:40 -0400135
idillon-sfl9d956ab2022-10-20 16:33:24 -0400136interface MessageMemberProps {
137 message: Message;
138}
139
idillon-sfl118ae442022-10-25 10:42:54 -0400140const MessageMember = ({ message }: MessageMemberProps) => {
simond47ef9e2022-09-28 22:24:28 -0400141 const { t } = useTranslation();
idillonbef18a52022-09-01 01:51:40 -0400142 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400143 <Chip
144 sx={{
145 width: 'fit-content',
146 }}
147 label={t('message_user_joined', { user: message.author })}
148 />
simond47ef9e2022-09-28 22:24:28 -0400149 );
150};
idillonbef18a52022-09-01 01:51:40 -0400151
idillon-sfl9d956ab2022-10-20 16:33:24 -0400152interface MessageTextProps {
153 message: Message;
idillon-sfl118ae442022-10-25 10:42:54 -0400154 isAccountMessage: boolean;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400155 isFirstOfGroup: boolean;
156 isLastOfGroup: boolean;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400157}
158
idillon-sfl118ae442022-10-25 10:42:54 -0400159const MessageText = ({ message, isAccountMessage, isFirstOfGroup, isLastOfGroup }: MessageTextProps) => {
160 const position = isAccountMessage ? 'end' : 'start';
161 const bubbleColor = isAccountMessage ? '#005699' : '#E5E5E5';
162 const textColor = isAccountMessage ? 'white' : 'black';
idillonbef18a52022-09-01 01:51:40 -0400163 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400164 <MessageTooltip position={position}>
165 <Bubble
166 bubbleColor={bubbleColor}
167 position={position}
168 isFirstOfGroup={isFirstOfGroup}
169 isLastOfGroup={isLastOfGroup}
170 >
171 <Typography variant="body1" color={textColor} textAlign={position}>
172 {message.body}
173 </Typography>
174 </Bubble>
175 </MessageTooltip>
simond47ef9e2022-09-28 22:24:28 -0400176 );
177};
idillonbef18a52022-09-01 01:51:40 -0400178
idillon-sfl118ae442022-10-25 10:42:54 -0400179interface DateIndicatorProps {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400180 time: Dayjs;
181}
182
idillon-sfl118ae442022-10-25 10:42:54 -0400183const DateIndicator = ({ time }: DateIndicatorProps) => {
simond47ef9e2022-09-28 22:24:28 -0400184 let textDate;
idillonbef18a52022-09-01 01:51:40 -0400185
186 if (time.isToday()) {
simond47ef9e2022-09-28 22:24:28 -0400187 textDate = 'Today';
188 } else if (time.isYesterday()) {
189 textDate = 'Yesterday';
190 } else {
191 const date = time.date().toString().padStart(2, '0');
192 const month = (time.month() + 1).toString().padStart(2, '0');
193 textDate = `${date}/${month}/${time.year()}`;
idillonbef18a52022-09-01 01:51:40 -0400194 }
195
196 return (
simond47ef9e2022-09-28 22:24:28 -0400197 <Box marginTop="30px">
idillon04245a12022-09-01 11:12:17 -0400198 <Divider
199 sx={{
simond47ef9e2022-09-28 22:24:28 -0400200 '.MuiDivider-wrapper': {
idillon04245a12022-09-01 11:12:17 -0400201 margin: 0,
202 padding: 0,
203 },
simond47ef9e2022-09-28 22:24:28 -0400204 '&::before': {
205 borderTop: '1px solid #E5E5E5',
idillon04245a12022-09-01 11:12:17 -0400206 },
simond47ef9e2022-09-28 22:24:28 -0400207 '&::after': {
208 borderTop: '1px solid #E5E5E5',
idillon04245a12022-09-01 11:12:17 -0400209 },
210 }}
211 >
212 <Typography
213 variant="caption"
214 fontWeight={700}
215 border="1px solid #E5E5E5"
216 borderRadius="5px"
217 padding="10px 16px"
218 >
219 {textDate}
220 </Typography>
idillonbef18a52022-09-01 01:51:40 -0400221 </Divider>
222 </Box>
simond47ef9e2022-09-28 22:24:28 -0400223 );
224};
idillonbef18a52022-09-01 01:51:40 -0400225
idillon-sfl118ae442022-10-25 10:42:54 -0400226interface TimeIndicatorProps {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400227 time: Dayjs;
228 hasDateOnTop: boolean;
229}
230
idillon-sfl118ae442022-10-25 10:42:54 -0400231const TimeIndicator = ({ time, hasDateOnTop }: TimeIndicatorProps) => {
simond47ef9e2022-09-28 22:24:28 -0400232 const hour = time.hour().toString().padStart(2, '0');
233 const minute = time.minute().toString().padStart(2, '0');
234 const textTime = `${hour}:${minute}`;
idillonbef18a52022-09-01 01:51:40 -0400235
236 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400237 <Stack direction="row" justifyContent="center" marginTop={hasDateOnTop ? '20px' : '30px'}>
simond47ef9e2022-09-28 22:24:28 -0400238 <Typography variant="caption" color="#A7A7A7" fontWeight={700}>
idillonbef18a52022-09-01 01:51:40 -0400239 {textTime}
240 </Typography>
241 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400242 );
243};
idillonbef18a52022-09-01 01:51:40 -0400244
idillon-sfl118ae442022-10-25 10:42:54 -0400245interface NotificationMessageRowProps {
246 message: Message;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400247}
248
idillon-sfl118ae442022-10-25 10:42:54 -0400249const NotificationMessageRow = ({ message }: NotificationMessageRowProps) => {
250 let messageComponent;
251 switch (message.type) {
252 case 'initial':
253 messageComponent = <MessageInitial />;
254 break;
255 case 'member':
256 messageComponent = <MessageMember message={message} />;
257 break;
258 default:
259 console.error(`${NotificationMessageRow.name} received unhandled message type: ${message.type}`);
260 return null;
idillonae655dd2022-10-14 18:11:02 -0400261 }
262
idillonbef18a52022-09-01 01:51:40 -0400263 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400264 <Stack paddingTop={'30px'} alignItems="center">
265 {messageComponent}
266 </Stack>
267 );
268};
269
270interface UserMessageRowProps {
271 message: Message;
272 isAccountMessage: boolean;
273 previousMessage: Message | undefined;
274 nextMessage: Message | undefined;
275 time: Dayjs;
276 showsTime: boolean;
277 author: Account | Contact;
278}
279
280const UserMessageRow = ({
281 message,
282 previousMessage,
283 nextMessage,
284 isAccountMessage,
285 time,
286 showsTime,
287 author,
288}: UserMessageRowProps) => {
289 const authorName = author.getDisplayName();
290 const position = isAccountMessage ? 'end' : 'start';
291
292 const previousIsUserMessageType = checkIsUserMessageType(previousMessage?.type);
293 const nextIsUserMessageType = checkIsUserMessageType(nextMessage?.type);
294 const nextTime = dayjs.unix(Number(nextMessage?.timestamp));
295 const nextShowsTime = checkShowsTime(nextTime, time);
296 const isFirstOfGroup = showsTime || !previousIsUserMessageType || previousMessage?.author !== message.author;
297 const isLastOfGroup = nextShowsTime || !nextIsUserMessageType || message.author !== nextMessage?.author;
298
299 const props = {
300 message,
301 isAccountMessage,
302 isFirstOfGroup,
303 isLastOfGroup,
304 };
305
306 let MessageComponent;
307 switch (message.type) {
308 case 'text/plain':
309 MessageComponent = MessageText;
310 break;
311 case 'application/data-transfer+json':
312 MessageComponent = MessageDataTransfer;
313 break;
314 case 'application/call-history+json':
315 MessageComponent = MessageCall;
316 break;
317 default:
318 console.error(`${UserMessageRow.name} received unhandled message type: ${message.type}`);
319 return null;
320 }
321
322 const participantNamePadding = isAccountMessage
323 ? bubblePadding
324 : parseInt(avatarSize) + parseInt(spacingBetweenAvatarAndBubble) + parseInt(bubblePadding) + 'px';
325
326 return (
327 <Stack alignItems={position}>
328 {isFirstOfGroup && (
329 <Box padding={`30px ${participantNamePadding} 0 ${participantNamePadding}`}>
330 <ParticipantName name={authorName} />
331 </Box>
simond47ef9e2022-09-28 22:24:28 -0400332 )}
idillon-sfl118ae442022-10-25 10:42:54 -0400333 <Stack
334 direction="row"
335 justifyContent={position}
336 alignItems="end"
337 spacing={spacingBetweenAvatarAndBubble}
338 paddingTop="6px"
idillonbef18a52022-09-01 01:51:40 -0400339 width="66.66%"
idillonbef18a52022-09-01 01:51:40 -0400340 >
idillon-sfl118ae442022-10-25 10:42:54 -0400341 <Box sx={{ width: avatarSize }}>
342 {!isAccountMessage && isLastOfGroup && (
343 <ConversationAvatar
344 displayName={authorName}
345 sx={{ width: avatarSize, height: avatarSize, fontSize: '15px' }}
346 />
347 )}
348 </Box>
349 <MessageComponent {...props} />
idillonbef18a52022-09-01 01:51:40 -0400350 </Stack>
351 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400352 );
353};
idillonbef18a52022-09-01 01:51:40 -0400354
idillon-sfl9d956ab2022-10-20 16:33:24 -0400355interface MessageTooltipProps {
356 className?: string;
357 position: MessagePosition;
358 children: ReactElement;
359}
360
361const MessageTooltip = styled(({ className, position, children }: MessageTooltipProps) => {
simond47ef9e2022-09-28 22:24:28 -0400362 const [open, setOpen] = useState(false);
363 const emojis = ['😎', '😄', '😍']; // Should be last three used emojis
idillon927b7592022-09-15 12:56:45 -0400364 const additionalOptions = [
365 {
366 Icon: TwoSheetsIcon,
simond47ef9e2022-09-28 22:24:28 -0400367 text: 'Copy',
idillon927b7592022-09-15 12:56:45 -0400368 action: () => {},
369 },
370 {
371 Icon: OppositeArrowsIcon,
simond47ef9e2022-09-28 22:24:28 -0400372 text: 'Transfer',
idillon927b7592022-09-15 12:56:45 -0400373 action: () => {},
374 },
375 {
376 Icon: TrashBinIcon,
simond47ef9e2022-09-28 22:24:28 -0400377 text: 'Delete message',
idillon927b7592022-09-15 12:56:45 -0400378 action: () => {},
379 },
simond47ef9e2022-09-28 22:24:28 -0400380 ];
idillon927b7592022-09-15 12:56:45 -0400381
simond47ef9e2022-09-28 22:24:28 -0400382 const toggleMoreMenu = useCallback(() => setOpen((open) => !open), [setOpen]);
idillon927b7592022-09-15 12:56:45 -0400383
simond47ef9e2022-09-28 22:24:28 -0400384 const onClose = useCallback(() => {
385 setOpen(false);
386 }, [setOpen]);
idillon927b7592022-09-15 12:56:45 -0400387
388 return (
389 <Tooltip
idillon927b7592022-09-15 12:56:45 -0400390 classes={{ tooltip: className }} // Required for styles. Don't know why
idillon-sfl9d956ab2022-10-20 16:33:24 -0400391 placement={position === 'start' ? 'right-start' : 'left-start'}
idillon927b7592022-09-15 12:56:45 -0400392 PopperProps={{
393 modifiers: [
394 {
simond47ef9e2022-09-28 22:24:28 -0400395 name: 'offset',
idillon927b7592022-09-15 12:56:45 -0400396 options: {
simond47ef9e2022-09-28 22:24:28 -0400397 offset: [-2, -30],
idillon927b7592022-09-15 12:56:45 -0400398 },
399 },
400 ],
401 }}
402 onClose={onClose}
403 title={
simond47ef9e2022-09-28 22:24:28 -0400404 <Stack>
simond47ef9e2022-09-28 22:24:28 -0400405 {/* Whole tooltip's content */}
idillon927b7592022-09-15 12:56:45 -0400406 <Stack // Main options
407 direction="row"
408 spacing="16px"
409 >
simond47ef9e2022-09-28 22:24:28 -0400410 {emojis.map((emoji) => (
411 <EmojiButton key={emoji} emoji={emoji} />
412 ))}
413 <ReplyMessageButton />
414 <MoreButton onClick={toggleMoreMenu} />
idillon927b7592022-09-15 12:56:45 -0400415 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400416 {open && ( // Additional menu options
idillon927b7592022-09-15 12:56:45 -0400417 <>
simond47ef9e2022-09-28 22:24:28 -0400418 <Divider sx={{ paddingTop: '16px' }} />
419 <List sx={{ padding: 0, paddingTop: '8px', marginBottom: '-8px' }}>
420 {additionalOptions.map((option) => (
421 <ListItemButton
422 key={option.text}
423 sx={{
424 padding: '8px',
425 }}
426 >
427 <Stack // Could not find proper way to set spacing between ListItemIcon and ListItemText
428 direction="row"
429 spacing="16px"
idillon927b7592022-09-15 12:56:45 -0400430 >
simond47ef9e2022-09-28 22:24:28 -0400431 <option.Icon
432 sx={{
433 height: '16px',
434 margin: 0,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400435 color: (theme: Theme) => theme?.palette?.primary?.dark,
simond47ef9e2022-09-28 22:24:28 -0400436 }}
437 />
438 <ListItemText
439 primary={option.text}
440 primaryTypographyProps={{
441 fontSize: '12px',
442 lineHeight: '16px',
443 }}
444 sx={{
445 height: '16px',
446 margin: 0,
447 }}
448 />
449 </Stack>
450 </ListItemButton>
451 ))}
452 </List>
idillon927b7592022-09-15 12:56:45 -0400453 </>
simond47ef9e2022-09-28 22:24:28 -0400454 )}
idillon927b7592022-09-15 12:56:45 -0400455 </Stack>
456 }
idillon-sfl9d956ab2022-10-20 16:33:24 -0400457 >
idillon-sfl118ae442022-10-25 10:42:54 -0400458 {/* div fixes 'Function components cannot be given refs' error */}
459 <div>{children}</div>
idillon-sfl9d956ab2022-10-20 16:33:24 -0400460 </Tooltip>
simond47ef9e2022-09-28 22:24:28 -0400461 );
idillon-sfl9d956ab2022-10-20 16:33:24 -0400462})(({ position }) => {
simond47ef9e2022-09-28 22:24:28 -0400463 const largeRadius = '20px';
464 const smallRadius = '5px';
idillon927b7592022-09-15 12:56:45 -0400465 return {
simond47ef9e2022-09-28 22:24:28 -0400466 backgroundColor: 'white',
467 padding: '16px',
468 boxShadow: '3px 3px 7px #00000029',
idillon927b7592022-09-15 12:56:45 -0400469 borderRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400470 borderStartStartRadius: position === 'start' ? smallRadius : largeRadius,
471 borderStartEndRadius: position === 'end' ? smallRadius : largeRadius,
simond47ef9e2022-09-28 22:24:28 -0400472 };
idillon927b7592022-09-15 12:56:45 -0400473});
474
idillon-sfl118ae442022-10-25 10:42:54 -0400475interface BubbleProps {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400476 position: MessagePosition;
477 isFirstOfGroup: boolean;
478 isLastOfGroup: boolean;
idillon-sfl118ae442022-10-25 10:42:54 -0400479 bubbleColor: string;
idillon-sfl9d956ab2022-10-20 16:33:24 -0400480 children: ReactNode;
481}
482
idillon-sfl118ae442022-10-25 10:42:54 -0400483const Bubble = ({ position, isFirstOfGroup, isLastOfGroup, bubbleColor, children }: BubbleProps) => {
simond47ef9e2022-09-28 22:24:28 -0400484 const largeRadius = '20px';
485 const smallRadius = '5px';
Adrien Béraud023f7cf2022-09-18 14:57:53 -0400486 const radius = useMemo(() => {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400487 if (position === 'start') {
idillonbef18a52022-09-01 01:51:40 -0400488 return {
idillon-sfl9d956ab2022-10-20 16:33:24 -0400489 borderStartStartRadius: isFirstOfGroup ? largeRadius : smallRadius,
idillonbef18a52022-09-01 01:51:40 -0400490 borderStartEndRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400491 borderEndStartRadius: isLastOfGroup ? largeRadius : smallRadius,
idillonbef18a52022-09-01 01:51:40 -0400492 borderEndEndRadius: largeRadius,
simond47ef9e2022-09-28 22:24:28 -0400493 };
idillonbef18a52022-09-01 01:51:40 -0400494 }
495 return {
496 borderStartStartRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400497 borderStartEndRadius: isFirstOfGroup ? largeRadius : smallRadius,
idillonbef18a52022-09-01 01:51:40 -0400498 borderEndStartRadius: largeRadius,
idillon-sfl9d956ab2022-10-20 16:33:24 -0400499 borderEndEndRadius: isLastOfGroup ? largeRadius : smallRadius,
simond47ef9e2022-09-28 22:24:28 -0400500 };
idillon-sfl9d956ab2022-10-20 16:33:24 -0400501 }, [isFirstOfGroup, isLastOfGroup, position]);
idillonbef18a52022-09-01 01:51:40 -0400502
503 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400504 <Box
505 sx={{
506 width: 'fit-content',
507 backgroundColor: bubbleColor,
508 padding: bubblePadding,
509 ...radius,
510 }}
511 >
512 {children}
513 </Box>
simond47ef9e2022-09-28 22:24:28 -0400514 );
515};
idillonbef18a52022-09-01 01:51:40 -0400516
idillon-sfl9d956ab2022-10-20 16:33:24 -0400517interface ParticipantNameProps {
518 name: string;
519}
520
521const ParticipantName = ({ name }: ParticipantNameProps) => {
idillonbef18a52022-09-01 01:51:40 -0400522 return (
idillon-sfl118ae442022-10-25 10:42:54 -0400523 <Typography variant="caption" color="#A7A7A7" fontWeight={700}>
524 {name}
525 </Typography>
526 );
527};
528
529interface MessageProps {
530 messageIndex: number;
531 messages: Message[];
532 isAccountMessage: boolean;
533 author: Account | Contact;
534}
535
536export const MessageRow = ({ messageIndex, messages, isAccountMessage, author }: MessageProps) => {
537 const message = messages[messageIndex];
538 const previousMessage = findPreviousVisibleMessage(messages, messageIndex);
539 const nextMessage = findNextVisibleMessage(messages, messageIndex);
540 const time = dayjs.unix(Number(message.timestamp));
541 const previousTime = dayjs.unix(Number(previousMessage?.timestamp));
542 const showDate =
543 message?.type === 'initial' || previousTime.year() !== time.year() || previousTime.dayOfYear() !== time.dayOfYear();
544 const showTime = checkShowsTime(time, previousTime);
545 let messageComponent;
546 if (checkIsUserMessageType(message.type)) {
547 messageComponent = (
548 <UserMessageRow
549 message={message}
550 previousMessage={previousMessage}
551 nextMessage={nextMessage}
552 time={time}
553 showsTime={showTime}
554 isAccountMessage={isAccountMessage}
555 author={author}
556 />
557 );
558 } else if (checkIsNotificationMessageType(message.type)) {
559 messageComponent = <NotificationMessageRow message={message} />;
560 } else if (checkIsInvisibleMessageType(message.type)) {
561 return null;
562 } else {
563 const _exhaustiveCheck: never = message.type;
564 return _exhaustiveCheck;
565 }
566
567 return (
568 <Stack>
569 {showDate && <DateIndicator time={time} />}
570 {showTime && <TimeIndicator time={time} hasDateOnTop={showDate} />}
571 {messageComponent}
572 </Stack>
simond47ef9e2022-09-28 22:24:28 -0400573 );
574};