diff --git a/jest/fixtures.ts b/jest/fixtures.ts index a89768a..a61900b 100644 --- a/jest/fixtures.ts +++ b/jest/fixtures.ts @@ -2,11 +2,11 @@ import { MessageType, Size, User } from '../src/types' export const fileMessage: MessageType.File = { authorId: 'userId', - id: 'uuidv4', + fileName: 'flyer.pdf', + id: 'file-uuidv4', mimeType: 'application/pdf', - name: 'flyer.pdf', size: 15000, - timestamp: 0, + timestamp: 2000000, type: 'file', url: 'file:///Users/admin/flyer.pdf', } @@ -14,7 +14,9 @@ export const fileMessage: MessageType.File = { export const imageMessage: MessageType.Image = { authorId: 'userId', height: 100, - id: 'uuidv4', + id: 'image-uuidv4', + imageName: 'imageName', + size: 15000, timestamp: 0, type: 'image', url: 'https://avatars1.githubusercontent.com/u/59206044', diff --git a/package.json b/package.json index eb37c31..57f56f7 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "dependencies": { "@flyerhq/react-native-keyboard-accessory-view": "^1.5.1", "@flyerhq/react-native-link-preview": "^1.0.2", + "dayjs": "^1.9.1", "react-native-image-viewing": "^0.2.0", "react-native-parsed-text": "^0.0.22" }, diff --git a/src/components/Chat/Chat.tsx b/src/components/Chat/Chat.tsx index 9559675..d849d13 100644 --- a/src/components/Chat/Chat.tsx +++ b/src/components/Chat/Chat.tsx @@ -2,6 +2,8 @@ import { useComponentSize, usePanResponder, } from '@flyerhq/react-native-keyboard-accessory-view' +import dayjs from 'dayjs' +import calendar from 'dayjs/plugin/calendar' import * as React from 'react' import { FlatList, @@ -10,6 +12,7 @@ import { StatusBar, StatusBarProps, StyleSheet, + Text, View, } from 'react-native' import ImageView from 'react-native-image-viewing' @@ -19,6 +22,8 @@ import { Input, InputAdditionalProps, InputTopLevelProps } from '../Input' import { Message, MessageTopLevelProps } from '../Message' import styles from './styles' +dayjs.extend(calendar) + export type ChatTopLevelProps = InputTopLevelProps & MessageTopLevelProps export interface ChatProps extends ChatTopLevelProps { @@ -92,22 +97,64 @@ export const Chat = ({ index: number }) => { const messageWidth = Math.floor(Math.min(size.width * 0.77, 440)) - const previousMessageSameAuthor = - messages[index - 1]?.authorId === message.authorId + // TODO: Update the logic after pagination is introduced + const isFirst = index === 0 + const isLast = index === messages.length - 1 + const previousMessage = messages[index - 1] + const nextMessage = messages[index + 1] + + let nextMessageDifferentDay = false + let nextMessageSameAuthor = false + let previousMessageSameAuthor = false + let previousMessageWithinTimeRange = false + + if (!isLast) { + nextMessageDifferentDay = !dayjs + .unix(message.timestamp) + .isSame(dayjs.unix(nextMessage.timestamp), 'day') + nextMessageSameAuthor = nextMessage.authorId === message.authorId + } + + if (!isFirst) { + previousMessageSameAuthor = previousMessage.authorId === message.authorId + previousMessageWithinTimeRange = + previousMessageSameAuthor && + previousMessage.timestamp - message.timestamp < 3600 + } return ( - + <> + + {(nextMessageDifferentDay || isLast) && ( + + {dayjs.unix(message.timestamp).calendar(undefined, { + sameDay: '[Today]', + nextDay: 'DD MMMM', + nextWeek: 'DD MMMM', + lastDay: '[Yesterday]', + lastWeek: 'DD MMMM', + sameElse: 'DD MMMM', + })} + + )} + ) } diff --git a/src/components/Chat/__tests__/Chat.test.tsx b/src/components/Chat/__tests__/Chat.test.tsx index dea679d..0ce7301 100644 --- a/src/components/Chat/__tests__/Chat.test.tsx +++ b/src/components/Chat/__tests__/Chat.test.tsx @@ -11,7 +11,7 @@ import { Chat } from '../Chat' describe('chat', () => { it('renders image preview', async () => { expect.assertions(1) - const messages = [imageMessage] + const messages = [textMessage, fileMessage, imageMessage] const onSendPress = jest.fn() const { getByRole, getByText } = render( @@ -25,7 +25,7 @@ describe('chat', () => { it('sends a text message', () => { expect.assertions(1) - const messages = [textMessage] + const messages = [textMessage, fileMessage, imageMessage] const onSendPress = jest.fn() const { getByLabelText } = render( { it('opens file on a file message tap', () => { expect.assertions(1) - const messages = [fileMessage] + const messages = [textMessage, fileMessage, imageMessage] const onSendPress = jest.fn() const onFilePress = jest.fn() const { getByLabelText } = render( diff --git a/src/components/Chat/styles.ts b/src/components/Chat/styles.ts index 7f5c542..9bf42ca 100644 --- a/src/components/Chat/styles.ts +++ b/src/components/Chat/styles.ts @@ -4,8 +4,16 @@ export default StyleSheet.create({ container: { flex: 1, }, + dateDivider: { + color: '#1d1c21', + fontSize: 12, + fontWeight: 'bold', + lineHeight: 16, + marginBottom: 32, + textAlign: 'center', + }, footer: { - height: 24, + height: 16, }, list: { backgroundColor: '#fff', diff --git a/src/components/ImageMessage/ImageMessage.tsx b/src/components/ImageMessage/ImageMessage.tsx index 782927f..55c427e 100644 --- a/src/components/ImageMessage/ImageMessage.tsx +++ b/src/components/ImageMessage/ImageMessage.tsx @@ -31,7 +31,6 @@ export const ImageMessage = ({ const aspectRatio = size.width / (size.height || 1) const isMinimized = aspectRatio < 0.1 || aspectRatio > 10 const { - background, image, minimizedImage, minimizedImageContainer, @@ -80,11 +79,7 @@ export const ImageMessage = ({ ) : ( - + {renderImage()} diff --git a/src/components/ImageMessage/styles.ts b/src/components/ImageMessage/styles.ts index c90619a..ae51fee 100644 --- a/src/components/ImageMessage/styles.ts +++ b/src/components/ImageMessage/styles.ts @@ -13,9 +13,6 @@ const styles = ({ user?: User }) => StyleSheet.create({ - background: { - flex: 1, - }, image: { aspectRatio, maxHeight: messageWidth, diff --git a/src/components/Message/Message.tsx b/src/components/Message/Message.tsx index c0d5bd9..fbaf2a9 100644 --- a/src/components/Message/Message.tsx +++ b/src/components/Message/Message.tsx @@ -1,5 +1,6 @@ +import dayjs from 'dayjs' import * as React from 'react' -import { View } from 'react-native' +import { Text, View } from 'react-native' import { MessageType } from '../../types' import { UserContext } from '../../utils' import { FileMessage } from '../FileMessage' @@ -28,6 +29,7 @@ export interface MessageProps extends MessageTopLevelProps { messageWidth: number onImagePress: (url: string) => void previousMessageSameAuthor: boolean + previousMessageWithinTimeRange: boolean } export const Message = ({ @@ -36,15 +38,17 @@ export const Message = ({ onFilePress, onImagePress, previousMessageSameAuthor, + previousMessageWithinTimeRange, renderFileMessage, renderImageMessage, renderTextMessage, }: MessageProps) => { const user = React.useContext(UserContext) - const { container, contentContainer } = styles({ + const { container, contentContainer, statusContainer, time } = styles({ message, messageWidth, previousMessageSameAuthor, + previousMessageWithinTimeRange, user, }) @@ -78,6 +82,13 @@ export const Message = ({ return ( {renderMessage()} + {!previousMessageWithinTimeRange && ( + + + {dayjs.unix(message.timestamp).format('h:mm a')} + + + )} ) } diff --git a/src/components/Message/styles.ts b/src/components/Message/styles.ts index 946c527..e8566fb 100644 --- a/src/components/Message/styles.ts +++ b/src/components/Message/styles.ts @@ -5,19 +5,22 @@ const styles = ({ message, messageWidth, previousMessageSameAuthor, + previousMessageWithinTimeRange, user, }: { message: MessageType.Any messageWidth: number previousMessageSameAuthor: boolean + previousMessageWithinTimeRange: boolean user?: User }) => StyleSheet.create({ container: { + alignSelf: user?.id === message.authorId ? 'flex-end' : 'flex-start', flex: 1, - flexDirection: 'row', - justifyContent: user?.id === message.authorId ? 'flex-end' : 'flex-start', - marginBottom: previousMessageSameAuthor ? 8 : 24, + marginBottom: + previousMessageSameAuthor || previousMessageWithinTimeRange ? 8 : 16, + marginHorizontal: 24, }, contentContainer: { backgroundColor: @@ -27,10 +30,20 @@ const styles = ({ borderBottomLeftRadius: user?.id === message.authorId ? 20 : 0, borderBottomRightRadius: user?.id === message.authorId ? 0 : 20, borderRadius: 20, - marginHorizontal: 24, maxWidth: messageWidth, overflow: 'hidden', }, + statusContainer: { + alignSelf: 'flex-end', + marginRight: 8, + marginTop: 8, + }, + time: { + color: '#9e9cab', + fontSize: 12, + fontWeight: '500', + lineHeight: 16, + }, }) export default styles diff --git a/yarn.lock b/yarn.lock index 24d7f1f..4d2fc28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2407,6 +2407,11 @@ dayjs@^1.8.15: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.36.tgz#be36e248467afabf8f5a86bae0de0cdceecced50" integrity sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw== +dayjs@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.9.1.tgz#201a755f7db5103ed6de63ba93a984141c754541" + integrity sha512-01NCTBg8cuMJG1OQc6PR7T66+AFYiPwgDvdJmvJBn29NGzIG+DIFxPLNjHzwz3cpFIvG+NcwIjP9hSaPVoOaDg== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"