Improve styles for ConversationListItem

- Display last message
- Fix user talking to themself

Change-Id: Ia5bb3f9cd86a389f94bbfb3e279e7a82878f98ed
diff --git a/client/src/components/ConversationListItem.tsx b/client/src/components/ConversationListItem.tsx
index 710fe0b..985ee2c 100644
--- a/client/src/components/ConversationListItem.tsx
+++ b/client/src/components/ConversationListItem.tsx
@@ -15,7 +15,8 @@
  * License along with this program.  If not, see
  * <https://www.gnu.org/licenses/>.
  */
-import { Box, ListItem, ListItemAvatar, ListItemText } from '@mui/material';
+import { Box, ListItemButton, Stack, Typography } from '@mui/material';
+import dayjs from 'dayjs';
 import { IConversationSummary } from 'jami-web-common';
 import { QRCodeCanvas } from 'qrcode.react';
 import { useCallback, useContext, useMemo, useState } from 'react';
@@ -29,6 +30,8 @@
 import { setRefreshFromSlice } from '../redux/appSlice';
 import { useAppDispatch } from '../redux/hooks';
 import { ConversationRouteParams } from '../router';
+import { getMessageCallText, getMessageMemberText } from '../utils/chatmessages';
+import { formatRelativeDate, formatTime } from '../utils/dates&times';
 import ContextMenu, { ContextMenuHandler, useContextMenuHandler } from './ContextMenu';
 import ConversationAvatar from './ConversationAvatar';
 import { ConfirmationDialog, DialogContentList, InfosDialog, useDialogHandler } from './Dialog';
@@ -40,13 +43,14 @@
 };
 
 export default function ConversationListItem({ conversationSummary }: ConversationListItemProps) {
+  const { account } = useAuthContext();
   const {
     urlParams: { conversationId: selectedConversationId },
   } = useUrlParams<ConversationRouteParams>();
   const contextMenuHandler = useContextMenuHandler();
   const callContext = useCallContext(true);
   const { callData } = useContext(CallManagerContext);
-  const { t } = useTranslation();
+  const { t, i18n } = useTranslation();
   const navigate = useNavigate();
 
   const conversationId = conversationSummary.id;
@@ -58,9 +62,41 @@
     }
   }, [navigate, conversationId]);
 
-  const secondaryText = useMemo(() => {
+  const timeIndicator = useMemo(() => {
+    const message = conversationSummary.lastMessage;
+    const time = dayjs.unix(Number(message.timestamp));
+    if (time.isToday()) {
+      return formatTime(time, i18n);
+    } else {
+      return formatRelativeDate(time, i18n);
+    }
+  }, [conversationSummary, i18n]);
+
+  const lastMessageText = useMemo(() => {
     if (!callContext || !callData || callData.conversationId !== conversationSummary.id) {
-      return conversationSummary.lastMessage.body;
+      const message = conversationSummary.lastMessage;
+      switch (message.type) {
+        case 'initial': {
+          return t('message_swarm_created');
+        }
+        case 'application/data-transfer+json': {
+          return message.fileId;
+        }
+        case 'application/call-history+json': {
+          const isAccountMessage = message.author === account.getUri();
+          return getMessageCallText(isAccountMessage, message, i18n);
+        }
+        case 'member': {
+          return getMessageMemberText(message, i18n);
+        }
+        case 'text/plain': {
+          return message.body;
+        }
+        default: {
+          console.error(`${ConversationListItem.name} received an unexpected lastMessage type: ${message.type}`);
+          return '';
+        }
+      }
     }
 
     if (callContext.callStatus === CallStatus.InCall) {
@@ -72,11 +108,11 @@
     }
 
     return callContext.callRole === 'caller' ? t('outgoing_call') : t('incoming_call');
-  }, [conversationSummary, callContext, callData, t]);
+  }, [account, conversationSummary, callContext, callData, t, i18n]);
 
   const conversationName = useMemo(
-    () => conversationSummary.title ?? conversationSummary.membersNames.join(', '),
-    [conversationSummary]
+    () => conversationSummary.title || conversationSummary.membersNames.join(', ') || account.getDisplayName(),
+    [account, conversationSummary]
   );
 
   return (
@@ -88,18 +124,20 @@
         isSelected={isSelected}
         contextMenuProps={contextMenuHandler.props}
       />
-      <ListItem
-        button
-        alignItems="flex-start"
-        selected={isSelected}
-        onClick={onClick}
-        onContextMenu={contextMenuHandler.handleAnchorPosition}
-      >
-        <ListItemAvatar>
+      <ListItemButton alignItems="flex-start" selected={isSelected} onClick={onClick}>
+        <Stack direction="row" spacing="10px">
           <ConversationAvatar displayName={conversationName} />
-        </ListItemAvatar>
-        <ListItemText primary={conversationName} secondary={secondaryText} />
-      </ListItem>
+          <Stack>
+            <Typography variant="body1">{conversationName}</Typography>
+            <Stack direction="row" spacing="5px">
+              <Typography variant="body2" fontWeight={isSelected ? 'bold' : 'normal'}>
+                {timeIndicator}
+              </Typography>
+              <Typography variant="body2">{lastMessageText}</Typography>
+            </Stack>
+          </Stack>
+        </Stack>
+      </ListItemButton>
     </Box>
   );
 }
diff --git a/client/src/components/Message.tsx b/client/src/components/Message.tsx
index 8807295..a87d628 100644
--- a/client/src/components/Message.tsx
+++ b/client/src/components/Message.tsx
@@ -17,14 +17,15 @@
  */
 import { Box, Chip, Divider, Stack, Tooltip, Typography } from '@mui/material';
 import { styled } from '@mui/material/styles';
-import { Dayjs } from 'dayjs';
+import dayjs, { Dayjs } from 'dayjs';
 import { Message } from 'jami-web-common';
 import { ReactElement, ReactNode, useCallback, useMemo, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 
-import dayjs from '../dayjsInitializer';
 import { Account } from '../models/account';
 import { Contact } from '../models/contact';
+import { getMessageCallText, getMessageMemberText } from '../utils/chatmessages';
+import { formatRelativeDate, formatTime } from '../utils/dates&times';
 import { EmojiButton, MoreButton, ReplyMessageButton } from './Button';
 import ConversationAvatar from './ConversationAvatar';
 import PopoverList, { PopoverListItemData } from './PopoverList';
@@ -93,48 +94,44 @@
 const MessageCall = ({ message, isAccountMessage, isFirstOfGroup, isLastOfGroup }: MessageCallProps) => {
   const position = isAccountMessage ? 'end' : 'start';
 
-  const { t } = useTranslation();
+  const { i18n } = useTranslation();
   const { bubbleColor, Icon, text, textColor } = useMemo(() => {
+    const text = getMessageCallText(isAccountMessage, message, i18n);
     const callDuration = dayjs.duration(parseInt(message?.duration || ''));
     if (callDuration.asSeconds() === 0) {
       if (isAccountMessage) {
         return {
-          text: t('message_call_outgoing_missed'),
+          text,
           Icon: ArrowLeftCurved,
           textColor: 'white',
           bubbleColor: '#005699' + '80', // opacity 50%
         };
       } else {
         return {
-          text: t('message_call_incoming_missed'),
+          text,
           Icon: ArrowLeftCurved,
           textColor: 'black',
           bubbleColor: '#C6C6C6',
         };
       }
     } else {
-      const minutes = Math.floor(callDuration.asMinutes()).toString().padStart(2, '0');
-      const seconds = callDuration.format('ss');
-      const interpolations = {
-        duration: `${minutes}:${seconds}`,
-      };
       if (isAccountMessage) {
         return {
-          text: t('message_call_outgoing', interpolations),
+          text,
           Icon: ArrowRightUp,
           textColor: 'white',
           bubbleColor: '#005699',
         };
       } else {
         return {
-          text: t('message_call_incoming', interpolations),
+          text,
           Icon: ArrowLeftDown,
           textcolor: 'black',
           bubbleColor: '#E5E5E5',
         };
       }
     }
-  }, [isAccountMessage, message, t]);
+  }, [isAccountMessage, message, i18n]);
 
   return (
     <Bubble position={position} isFirstOfGroup={isFirstOfGroup} isLastOfGroup={isLastOfGroup} bubbleColor={bubbleColor}>
@@ -174,13 +171,15 @@
 }
 
 const MessageMember = ({ message }: MessageMemberProps) => {
-  const { t } = useTranslation();
+  const { i18n } = useTranslation();
+
+  const text = getMessageMemberText(message, i18n);
   return (
     <Chip
       sx={{
         width: 'fit-content',
       }}
-      label={t('message_user_joined', { user: message.author })}
+      label={text}
     />
   );
 };
@@ -218,16 +217,7 @@
 
 const DateIndicator = ({ time }: DateIndicatorProps) => {
   const { i18n } = useTranslation();
-
-  const textDate = useMemo(() => {
-    if (time.isToday()) {
-      return new Intl.RelativeTimeFormat(i18n.language, { numeric: 'auto' }).format(0, 'day');
-    } else if (time.isYesterday()) {
-      return new Intl.RelativeTimeFormat(i18n.language, { numeric: 'auto' }).format(-1, 'day');
-    } else {
-      return dayjs(time).locale(i18n.language).format('L');
-    }
-  }, [i18n, time]);
+  const textDate = useMemo(() => formatRelativeDate(time, i18n), [time, i18n]);
 
   return (
     <Box marginTop="30px">
@@ -267,10 +257,7 @@
 
 const TimeIndicator = ({ time, hasDateOnTop }: TimeIndicatorProps) => {
   const { i18n } = useTranslation();
-
-  const textTime = useMemo(() => {
-    return dayjs(time).locale(i18n.language).format('LT');
-  }, [i18n, time]);
+  const textTime = useMemo(() => formatTime(time, i18n), [time, i18n]);
 
   return (
     <Stack direction="row" justifyContent="center" marginTop={hasDateOnTop ? '20px' : '30px'}>