add 'Scroll to the end of the conversation' bar

Change-Id: I63e41433db4999d021bc13375cf5b7ba07843c3d
diff --git a/client/package.json b/client/package.json
index 875bf02..523d9f1 100644
--- a/client/package.json
+++ b/client/package.json
@@ -57,6 +57,7 @@
     "react-modal": "^3.15.1",
     "react-redux": "^8.0.2",
     "react-router-dom": "^6.3.0",
+    "react-waypoint": "^10.3.0",
     "socket.io-client": "^4.5.2"
   },
   "devDependencies": {
diff --git a/client/src/components/ConversationView.tsx b/client/src/components/ConversationView.tsx
index a269c89..1242541 100644
--- a/client/src/components/ConversationView.tsx
+++ b/client/src/components/ConversationView.tsx
@@ -101,31 +101,25 @@
 
   return (
     <Stack height="100%">
-      <Stack padding="16px">
-        <ConversationHeader
-          account={account}
-          members={conversation.getMembers()}
-          adminTitle={conversation.infos.title as string}
-          conversationId={conversationId}
-        />
-      </Stack>
+      <ConversationHeader
+        account={account}
+        members={conversation.getMembers()}
+        adminTitle={conversation.infos.title as string}
+        conversationId={conversationId}
+      />
       <Divider
         sx={{
           borderTop: '1px solid #E5E5E5',
         }}
       />
-      <Stack flex={1} overflow="auto" direction="column-reverse" padding="0px 16px">
-        <MessageList account={account} members={conversation.getMembers()} messages={messages} />
-      </Stack>
+      <MessageList account={account} members={conversation.getMembers()} messages={messages} />
       <Divider
         sx={{
           margin: '30px 16px 0px 16px',
           borderTop: '1px solid #E5E5E5',
         }}
       />
-      <Stack padding="16px">
-        <SendMessageForm account={account} members={conversation.getMembers()} onSend={sendMessage} />
-      </Stack>
+      <SendMessageForm account={account} members={conversation.getMembers()} onSend={sendMessage} />
     </Stack>
   );
 };
@@ -173,7 +167,7 @@
   };
 
   return (
-    <Stack direction="row">
+    <Stack direction="row" padding="16px">
       <Stack flex={1} justifyContent="center" whiteSpace="nowrap" overflow="hidden">
         <Typography variant="h3" textOverflow="ellipsis">
           {title}
diff --git a/client/src/components/MessageList.tsx b/client/src/components/MessageList.tsx
index 6e5e233..7b9bf2d 100644
--- a/client/src/components/MessageList.tsx
+++ b/client/src/components/MessageList.tsx
@@ -15,10 +15,15 @@
  * License along with this program.  If not, see
  * <https://www.gnu.org/licenses/>.
  */
-import { Stack } from '@mui/system';
+import { Typography } from '@mui/material';
+import { Box, Stack } from '@mui/system';
 import { Account, ConversationMember, Message } from 'jami-web-common';
+import { MutableRefObject, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Waypoint } from 'react-waypoint';
 
 import { MessageRow } from './Message';
+import { ArrowDownIcon } from './SvgIcon';
 
 interface MessageListProps {
   account: Account;
@@ -27,31 +32,82 @@
 }
 
 export default function MessageList({ account, members, messages }: MessageListProps) {
+  const [showScrollButton, setShowScrollButton] = useState(false);
+  const listBottomRef = useRef<HTMLElement>();
+
   return (
-    <Stack direction="column-reverse">
-      {
-        // most recent messages first
-        messages.map((message, index) => {
-          const isAccountMessage = message.author === account.getUri();
-          let author;
-          if (isAccountMessage) {
-            author = account;
-          } else {
-            const member = members.find((member) => message.author === member.contact.getUri());
-            author = member?.contact;
+    <>
+      <Stack flex={1} overflow="auto" padding="0px 16px" direction="column-reverse">
+        {/* Here is the bottom of the list of messages because of 'column-reverse' */}
+        <Box ref={listBottomRef} />
+        <Waypoint
+          onEnter={() => setShowScrollButton(false)}
+          onLeave={() => setShowScrollButton(true)}
+          bottomOffset="-100px"
+        />
+        <Stack direction="column-reverse">
+          {
+            // most recent messages first
+            messages.map((message, index) => {
+              const isAccountMessage = message.author === account.getUri();
+              let author;
+              if (isAccountMessage) {
+                author = account;
+              } else {
+                const member = members.find((member) => message.author === member.contact.getUri());
+                author = member?.contact;
+              }
+              if (!author) {
+                return null;
+              }
+              const props = {
+                messageIndex: index,
+                messages,
+                isAccountMessage,
+                author,
+              };
+              return <MessageRow key={message.id} {...props} />;
+            })
           }
-          if (!author) {
-            return null;
-          }
-          const props = {
-            messageIndex: index,
-            messages,
-            isAccountMessage,
-            author,
-          };
-          return <MessageRow key={message.id} {...props} />;
-        })
-      }
-    </Stack>
+        </Stack>
+        <Waypoint onEnter={() => console.log('should load more messages')} topOffset="-200px" />
+      </Stack>
+      {showScrollButton && (
+        <Box position="relative">
+          <Box position="absolute" bottom="10px" left="50%" sx={{ transform: 'translate(-50%)' }}>
+            <ScrollToEndButton listBottomRef={listBottomRef} />
+          </Box>
+        </Box>
+      )}
+    </>
   );
 }
+
+interface ScrollToEndButtonProps {
+  listBottomRef: MutableRefObject<HTMLElement | undefined>;
+}
+
+const ScrollToEndButton = ({ listBottomRef }: ScrollToEndButtonProps) => {
+  const { t } = useTranslation();
+  const textColor = 'white';
+  return (
+    <Stack
+      direction="row"
+      borderRadius="5px"
+      height="30px"
+      alignItems="center"
+      padding="0 16px"
+      spacing="12px"
+      sx={{
+        backgroundColor: '#005699', // Should be same color as message bubble
+        cursor: 'pointer',
+      }}
+      onClick={() => listBottomRef.current?.scrollIntoView({ behavior: 'smooth' })}
+    >
+      <ArrowDownIcon sx={{ fontSize: '12px', color: textColor }} />
+      <Typography variant="caption" fontWeight="bold" color={textColor}>
+        {t('messages_scroll_to_end')}
+      </Typography>
+    </Stack>
+  );
+};
diff --git a/client/src/components/SendMessageForm.tsx b/client/src/components/SendMessageForm.tsx
index 7230dd0..f003aae 100644
--- a/client/src/components/SendMessageForm.tsx
+++ b/client/src/components/SendMessageForm.tsx
@@ -55,7 +55,7 @@
   );
 
   return (
-    <Stack component="form" onSubmit={handleSubmit} direction="row" alignItems="center" spacing="20px">
+    <Stack component="form" onSubmit={handleSubmit} direction="row" alignItems="center" spacing="20px" padding="16px">
       <UploadFileButton />
       <RecordVoiceMessageButton />
       <RecordVideoMessageButton />
diff --git a/client/src/components/SvgIcon.tsx b/client/src/components/SvgIcon.tsx
index 9e560dc..0b8b9ed 100644
--- a/client/src/components/SvgIcon.tsx
+++ b/client/src/components/SvgIcon.tsx
@@ -64,6 +64,17 @@
   );
 };
 
+export const ArrowDownIcon = (props: SvgIconProps) => {
+  return (
+    <SvgIcon {...props} viewBox="0 0 8 11.607">
+      <path
+        fillRule="evenodd"
+        d="m4.353 11.43 3.441-3.5.058-.058a.564.564 0 0 0-.058-.816.62.62 0 0 0-.816.058l-2.45 2.508V.583A.551.551 0 0 0 3.945 0a.551.551 0 0 0-.581.583v9.039L.912 7.114a.613.613 0 0 0-.758 0 .621.621 0 0 0 0 .816l3.441 3.5a.452.452 0 0 0 .583.117c.058 0 .117-.058.175-.117"
+      />
+    </SvgIcon>
+  );
+};
+
 export const ArrowLeftCurved = (props: SvgIconProps) => {
   return (
     <SvgIcon {...props} viewBox="0 0 16 8.814">
diff --git a/client/src/locale/en/translation.json b/client/src/locale/en/translation.json
index bef16ed..049bc55 100644
--- a/client/src/locale/en/translation.json
+++ b/client/src/locale/en/translation.json
@@ -26,6 +26,8 @@
   "message_input_placeholder_four": "Write to {{member0}}, {{member1}}, {{member2}}, +1 other member",
   "message_input_placeholder_more": "Write to {{member0}}, {{member1}}, {{member2}}, +{{excess}} other members",
 
+  "messages_scroll_to_end": "Scroll to the end of the conversation",
+
   "username_input_default_helper_text": "",
   "username_input_success_helper_text": "Username available",
   "username_input_taken_helper_text": "Username already taken",
diff --git a/client/src/locale/en/translation_old.json b/client/src/locale/en/translation_old.json
new file mode 100644
index 0000000..a1a3e9a
--- /dev/null
+++ b/client/src/locale/en/translation_old.json
@@ -0,0 +1,15 @@
+{
+  "password_input_default_helper_text": "",
+  "password_input_medium_helper_text": "Medium",
+  "password_input_registration_failed_helper_text": "Choose another password!",
+  "password_input_strong_helper_text": "Strong",
+  "password_input_too_weak_helper_text": "Too weak",
+  "password_input_weak_helper_text": "Weak",
+  "severity_error": "Error",
+  "severity_success": "Success",
+  "username_input_default_helper_text": "",
+  "username_input_invalid_helper_text": "Username doesn't follow required pattern",
+  "username_input_registration_failed_helper_text": "Username not correct!",
+  "username_input_success_helper_text": "Username available",
+  "username_input_taken_helper_text": "Username already taken"
+}
diff --git a/client/src/locale/fr/translation.json b/client/src/locale/fr/translation.json
index 97a1cf6..346f0aa 100644
--- a/client/src/locale/fr/translation.json
+++ b/client/src/locale/fr/translation.json
@@ -24,7 +24,9 @@
   "message_input_placeholder_two": "Écrire à {{member0}} et {{member1}}",
   "message_input_placeholder_three": "Écrire à {{member0}}, {{member1}} et {{member2}}",
   "message_input_placeholder_four": "Écrire à {{member0}}, {{member1}}, {{member2}}, +1 autre membre",
-  "message_input_placeholder_more": "Écrire à {{member01}}, {{member1}}, {{member2}}, +{{excess}} autres membres",
+  "message_input_placeholder_more": "Écrire à {{member0}}, {{member1}}, {{member2}}, +{{excess}} autres membres",
+
+  "messages_scroll_to_end": "Faire défiler jusqu'à la fin de la conversation",
 
   "share_screen": "Partager votre écran",
   "share_screen_area": "Partager une partie de l'écran",