Add composing notification
Change-Id: I2c052c4395a56ba6acf882cea3be4b82e2fde761
diff --git a/client/src/pages/ChatInterface.tsx b/client/src/pages/ChatInterface.tsx
index 37e75e2..fa428ac 100644
--- a/client/src/pages/ChatInterface.tsx
+++ b/client/src/pages/ChatInterface.tsx
@@ -15,10 +15,12 @@
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
-import { Divider, Stack } from '@mui/material';
+import { Box, Divider, Fade, Stack, Typography } from '@mui/material';
+import { motion } from 'framer-motion';
import { ConversationMessage, Message, WebSocketMessageType } from 'jami-web-common';
-import { useCallback, useContext, useEffect, useState } from 'react';
+import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useDropzone } from 'react-dropzone';
+import { useTranslation } from 'react-i18next';
import { FilePreviewRemovable } from '../components/FilePreview';
import LoadingPage from '../components/Loading';
@@ -27,8 +29,10 @@
import SendMessageForm from '../components/SendMessageForm';
import { useConversationContext } from '../contexts/ConversationProvider';
import { WebSocketContext } from '../contexts/WebSocketProvider';
+import { ConversationMember } from '../models/conversation-member';
import { useMessagesQuery, useSendMessageMutation } from '../services/conversationQueries';
import { FileHandler } from '../utils/files';
+import { translateEnumeration, TranslateEnumerationOptions } from '../utils/translations';
const ChatInterface = () => {
const webSocket = useContext(WebSocketContext);
@@ -111,9 +115,10 @@
{isDragActive && <FileDragOverlay />}
<input {...getInputProps()} />
<MessageList messages={messages} />
+ <ComposingMembersIndicator />
<Divider
sx={{
- margin: '30px 16px 0px 16px',
+ marginX: '16px',
borderTop: '1px solid #E5E5E5',
}}
/>
@@ -152,6 +157,74 @@
);
};
+export const ComposingMembersIndicator = () => {
+ const { t } = useTranslation();
+ const { composingMembers } = useConversationContext();
+
+ const text = useMemo(() => {
+ const options: TranslateEnumerationOptions<ConversationMember> = {
+ elementPartialKey: 'member',
+ getElementValue: (member) => member.getDisplayName(),
+ translaters: [
+ () => '',
+ (interpolations) => t('are_composing_1', interpolations),
+ (interpolations) => t('are_composing_2', interpolations),
+ (interpolations) => t('are_composing_3', interpolations),
+ (interpolations) => t('are_composing_more', interpolations),
+ ],
+ };
+
+ return translateEnumeration<ConversationMember>(composingMembers, options);
+ }, [composingMembers, t]);
+
+ return (
+ <Stack height="30px" padding="0 16px" justifyContent="center">
+ <Fade in={composingMembers.length !== 0}>
+ <Stack
+ alignItems="center"
+ direction="row"
+ spacing="8.5px"
+ sx={(theme: any) => ({
+ height: theme.typography.caption.lineHeight,
+ })}
+ >
+ <WaitingDots />
+ <Typography variant="caption">{text}</Typography>
+ </Stack>
+ </Fade>
+ </Stack>
+ );
+};
+
+const SingleDot = ({ delay }: { delay: number }) => (
+ <Box
+ width="8px"
+ height="8px"
+ borderRadius="100%"
+ sx={{ backgroundColor: '#000000' }}
+ component={motion.div}
+ animate={{ scale: [0.75, 1, 0.75] }}
+ transition={{
+ delay,
+ duration: 0.5,
+ repeatDelay: 1,
+ repeatType: 'loop',
+ repeat: Infinity,
+ ease: 'easeInOut',
+ }}
+ />
+);
+
+const WaitingDots = () => {
+ return (
+ <Stack direction="row" spacing="5px">
+ <SingleDot delay={0} />
+ <SingleDot delay={0.5} />
+ <SingleDot delay={1} />
+ </Stack>
+ );
+};
+
const addMessage = (sortedMessages: Message[], message: Message) => {
if (sortedMessages.length === 0) {
return [message];