Rename ConversationListItem as ConversationSummaryListItem

Change-Id: I6e4f7186a5b4ee0f1b71e95adb7a5422c4aeed36
diff --git a/client/src/components/ConversationSummaryList.tsx b/client/src/components/ConversationSummaryList.tsx
new file mode 100644
index 0000000..f8e3d22
--- /dev/null
+++ b/client/src/components/ConversationSummaryList.tsx
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+import { Box, List, 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';
+import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom';
+
+import { useAuthContext } from '../contexts/AuthProvider';
+import { CallManagerContext } from '../contexts/CallManagerProvider';
+import { CallStatus, useCallContext } from '../contexts/CallProvider';
+import { useConversationDisplayNameShort } from '../hooks/useConversationDisplayName';
+import { useUrlParams } from '../hooks/useUrlParams';
+import { ConversationRouteParams } from '../router';
+import { useRemoveConversationMutation } from '../services/conversationQueries';
+import { getMessageCallText, getMessageMemberText } from '../utils/chatmessages';
+import { formatRelativeDate, formatTime } from '../utils/dates&times';
+import ContextMenu, { ContextMenuHandler, useContextMenuHandler } from './ContextMenu';
+import ConversationAvatar from './ConversationAvatar';
+import { CustomListItemButton } from './CustomListItemButton';
+import { ConfirmationDialog, DialogContentList, InfosDialog, useDialogHandler } from './Dialog';
+import { PopoverListItemData } from './PopoverList';
+import { AudioCallIcon, CancelIcon, MessageIcon, PersonIcon, VideoCallIcon } from './SvgIcon';
+
+type ConversationSummaryListProps = {
+  conversationsSummaries: IConversationSummary[];
+};
+
+export const ConversationSummaryList = ({ conversationsSummaries }: ConversationSummaryListProps) => {
+  return (
+    <List>
+      {conversationsSummaries?.map((conversationSummary) => (
+        <ConversationSummaryListItem key={conversationSummary.id} conversationSummary={conversationSummary} />
+      ))}
+    </List>
+  );
+};
+
+type ConversationSummaryListItemProps = {
+  conversationSummary: IConversationSummary;
+};
+
+const ConversationSummaryListItem = ({ conversationSummary }: ConversationSummaryListItemProps) => {
+  const { account } = useAuthContext();
+  const {
+    urlParams: { conversationId: selectedConversationId },
+  } = useUrlParams<ConversationRouteParams>();
+  const contextMenuHandler = useContextMenuHandler();
+  const navigate = useNavigate();
+
+  const conversationId = conversationSummary.id;
+  const isSelected = conversationId === selectedConversationId;
+
+  const onClick = useCallback(() => {
+    if (conversationId) {
+      navigate(`/conversation/${conversationId}`);
+    }
+  }, [navigate, conversationId]);
+
+  const conversationName = useConversationDisplayNameShort(
+    account,
+    conversationSummary.title,
+    conversationSummary.membersNames
+  );
+
+  return (
+    <Box>
+      <ConversationMenu
+        conversationId={conversationId}
+        conversationName={conversationName}
+        onMessageClick={onClick}
+        isSelected={isSelected}
+        contextMenuProps={contextMenuHandler.props}
+      />
+      <CustomListItemButton
+        selected={isSelected}
+        onClick={onClick}
+        onContextMenu={contextMenuHandler.handleAnchorPosition}
+        icon={<ConversationAvatar displayName={conversationName} src={conversationSummary.avatar} />}
+        primaryText={<Typography variant="body1">{conversationName}</Typography>}
+        secondaryText={<SecondaryText conversationSummary={conversationSummary} isSelected={isSelected} />}
+      />
+    </Box>
+  );
+};
+
+type SecondaryTextProps = {
+  conversationSummary: IConversationSummary;
+  isSelected: boolean;
+};
+
+const SecondaryText = ({ conversationSummary, isSelected }: SecondaryTextProps) => {
+  const { account } = useAuthContext();
+  const callContext = useCallContext(true);
+  const { callData } = useContext(CallManagerContext);
+  const { t, i18n } = useTranslation();
+
+  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) {
+      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(`${ConversationSummaryListItem.name} received an unexpected lastMessage type: ${message.type}`);
+          return '';
+        }
+      }
+    }
+
+    if (callContext.callStatus === CallStatus.InCall) {
+      return callContext.isAudioOn ? t('ongoing_call_unmuted') : t('ongoing_call_muted');
+    }
+
+    if (callContext.callStatus === CallStatus.Connecting) {
+      return t('connecting_call');
+    }
+
+    return callContext.callRole === 'caller' ? t('outgoing_call') : t('incoming_call');
+  }, [account, conversationSummary, callContext, callData, t, i18n]);
+
+  return (
+    <Stack direction="row" spacing="5px">
+      <Typography variant="body2" fontWeight={isSelected ? 'bold' : 'normal'}>
+        {timeIndicator}
+      </Typography>
+      <Typography variant="body2">{lastMessageText}</Typography>
+    </Stack>
+  );
+};
+
+interface ConversationMenuProps {
+  conversationId: string;
+  conversationName: string;
+  onMessageClick: () => void;
+  isSelected: boolean;
+  contextMenuProps: ContextMenuHandler['props'];
+}
+
+const ConversationMenu = ({
+  conversationId,
+  conversationName,
+  onMessageClick,
+  isSelected,
+  contextMenuProps,
+}: ConversationMenuProps) => {
+  const { t } = useTranslation();
+  const { startCall } = useContext(CallManagerContext);
+  const [isSwarm] = useState(true);
+
+  const detailsDialogHandler = useDialogHandler();
+  const RemoveConversationDialogHandler = useDialogHandler();
+
+  const navigate = useNavigate();
+
+  const menuOptions: PopoverListItemData[] = useMemo(
+    () => [
+      {
+        label: t('conversation_message'),
+        Icon: MessageIcon,
+        onClick: onMessageClick,
+      },
+      {
+        label: t('conversation_start_audiocall'),
+        Icon: AudioCallIcon,
+        onClick: () => {
+          if (conversationId) {
+            startCall({
+              conversationId,
+              role: 'caller',
+            });
+          }
+        },
+      },
+      {
+        label: t('conversation_start_videocall'),
+        Icon: VideoCallIcon,
+        onClick: () => {
+          if (conversationId) {
+            startCall({
+              conversationId,
+              role: 'caller',
+              withVideoOn: true,
+            });
+          }
+        },
+      },
+      ...(isSelected
+        ? [
+            {
+              label: t('conversation_close'),
+              Icon: CancelIcon,
+              onClick: () => {
+                navigate(`/`);
+              },
+            },
+          ]
+        : []),
+      {
+        label: t('conversation_details'),
+        Icon: PersonIcon,
+        onClick: () => {
+          detailsDialogHandler.openDialog();
+        },
+      },
+      {
+        label: t('conversation_delete'),
+        Icon: CancelIcon,
+        onClick: () => {
+          RemoveConversationDialogHandler.openDialog();
+        },
+      },
+    ],
+    [
+      navigate,
+      onMessageClick,
+      isSelected,
+      detailsDialogHandler,
+      RemoveConversationDialogHandler,
+      t,
+      startCall,
+      conversationId,
+    ]
+  );
+
+  return (
+    <>
+      <ContextMenu {...contextMenuProps} items={menuOptions} />
+
+      <DetailsDialog
+        {...detailsDialogHandler.props}
+        conversationId={conversationId}
+        conversationName={conversationName}
+        isSwarm={isSwarm}
+      />
+
+      <RemoveConversationDialog
+        {...RemoveConversationDialogHandler.props}
+        conversationId={conversationId}
+        isSelected={isSelected}
+      />
+    </>
+  );
+};
+
+interface DetailsDialogProps {
+  conversationId: string;
+  conversationName: string;
+  open: boolean;
+  onClose: () => void;
+  isSwarm: boolean;
+}
+
+const DetailsDialog = ({ conversationId, conversationName, open, onClose, isSwarm }: DetailsDialogProps) => {
+  const { t } = useTranslation();
+  const items = useMemo(
+    () => [
+      {
+        label: t('conversation_details_name'),
+        value: conversationName,
+      },
+      {
+        label: t('conversation_details_identifier'),
+        value: conversationId,
+      },
+      {
+        label: t('conversation_details_qr_code'),
+        value: <QRCodeCanvas size={80} value={`${conversationId}`} />,
+      },
+      {
+        label: t('conversation_details_is_swarm'),
+        value: isSwarm ? t('conversation_details_is_swarm_true') : t('conversation_details_is_swarm_false'),
+      },
+    ],
+    [conversationId, conversationName, isSwarm, t]
+  );
+  return (
+    <InfosDialog
+      open={open}
+      onClose={onClose}
+      icon={<ConversationAvatar sx={{ width: 'inherit', height: 'inherit' }} displayName={conversationName} />}
+      title={conversationName}
+      content={<DialogContentList title={t('conversation_details_informations')} items={items} />}
+    />
+  );
+};
+
+interface RemoveConversationDialogProps {
+  conversationId: string;
+  isSelected: boolean;
+  open: boolean;
+  onClose: () => void;
+}
+
+const RemoveConversationDialog = ({ conversationId, isSelected, open, onClose }: RemoveConversationDialogProps) => {
+  const { t } = useTranslation();
+  const navigate = useNavigate();
+  const removeConversationMutation = useRemoveConversationMutation();
+
+  const remove = useCallback(async () => {
+    removeConversationMutation.mutate(
+      { conversationId },
+      {
+        onSuccess: () => {
+          if (isSelected) {
+            navigate('/conversation/');
+          }
+        },
+        onError: (e) => {
+          console.error(`Error removing conversation : `, e);
+        },
+        onSettled: () => {
+          onClose();
+        },
+      }
+    );
+  }, [conversationId, isSelected, navigate, onClose, removeConversationMutation]);
+
+  return (
+    <ConfirmationDialog
+      open={open}
+      onClose={onClose}
+      title={t('dialog_confirm_title_default')}
+      content={t('conversation_ask_confirm_remove')}
+      onConfirm={remove}
+      confirmButtonText={t('conversation_confirm_remove')}
+    />
+  );
+};