From c73fd35e939192d62202d7bff3687211b2754643 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Mon, 13 May 2024 10:21:32 +0530 Subject: [PATCH 1/7] fix: incorrect mentioned users regex users with same prefix (#2508) * fix: incorrect mentioned users regex users with same prefix * refactor: remove redundant console.log --- .../MessageSimple/utils/renderText.tsx | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/package/src/components/Message/MessageSimple/utils/renderText.tsx b/package/src/components/Message/MessageSimple/utils/renderText.tsx index c246c0a6ee..f096bb9adf 100644 --- a/package/src/components/Message/MessageSimple/utils/renderText.tsx +++ b/package/src/components/Message/MessageSimple/utils/renderText.tsx @@ -211,25 +211,19 @@ export const renderText = < ); }; + function escapeRegExp(text: string) { + return text.replace(/[-[\]{}()*+?.,/\\^$|#]/g, '\\$&'); + } + // take the @ mentions and turn them into markdown? // translate links const { mentioned_users } = message; - const mentionedUsers = Array.isArray(mentioned_users) - ? mentioned_users.reduce((acc, cur) => { - const userName = cur.name || cur.id || ''; - if (userName) { - acc += `${acc.length ? '|' : ''}@${userName.replace( - /[.*+?^${}()|[\]\\]/g, - function (match) { - return '\\' + match; - }, - )}`; - } - - return acc; - }, '') - : ''; - + const mentionedUsernames = (mentioned_users || []) + .map((user) => user.name || user.id) + .filter(Boolean) + .sort((a, b) => b.length - a.length) + .map(escapeRegExp); + const mentionedUsers = mentionedUsernames.map((username) => `@${username}`).join('|'); const regEx = new RegExp(`^\\B(${mentionedUsers})`, 'g'); const mentionsMatchFunction: MatchFunction = (source) => regEx.exec(source); From bdca95725c7089ea75154b7c9dfa8730c5be9a97 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Tue, 14 May 2024 22:09:44 +0530 Subject: [PATCH 2/7] fix: handle react native audio recorder player optionally (#2515) --- package/native-package/src/optionalDependencies/Audio.ts | 2 +- .../src/components/MessageInput/hooks/useAudioController.tsx | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/package/native-package/src/optionalDependencies/Audio.ts b/package/native-package/src/optionalDependencies/Audio.ts index c4f6dde293..4ed2c36f28 100644 --- a/package/native-package/src/optionalDependencies/Audio.ts +++ b/package/native-package/src/optionalDependencies/Audio.ts @@ -8,8 +8,8 @@ try { audioRecorderPlayer = new AudioRecorderPackage(); audioRecorderPlayer.setSubscriptionDuration(Platform.OS === 'android' ? 0.1 : 0.06); } catch (e) { + console.log('react-native-audio-recorder-player is not installed.'); console.log(e); - console.warn('react-native-audio-recorder-player is not installed.'); } export enum AudioSourceAndroidType { diff --git a/package/src/components/MessageInput/hooks/useAudioController.tsx b/package/src/components/MessageInput/hooks/useAudioController.tsx index 42e0c62460..111d97bd57 100644 --- a/package/src/components/MessageInput/hooks/useAudioController.tsx +++ b/package/src/components/MessageInput/hooks/useAudioController.tsx @@ -82,6 +82,7 @@ export const useAudioController = () => { }; const onVoicePlayerPlayPause = async () => { + if (!Audio) return; if (paused) { if (progress === 0) await startVoicePlayer(); else { @@ -103,6 +104,7 @@ export const useAudioController = () => { * Function to start playing voice recording to preview it after recording. */ const startVoicePlayer = async () => { + if (!Audio) return; if (!recording) return; // For Native CLI if (Audio.startPlayer) @@ -130,6 +132,7 @@ export const useAudioController = () => { * Function to stop playing voice recording. */ const stopVoicePlayer = async () => { + if (!Audio) return; // For Native CLI if (Audio.stopPlayer) { await Audio.stopPlayer(); @@ -161,6 +164,7 @@ export const useAudioController = () => { * Function to start voice recording. */ const startVoiceRecording = async () => { + if (!Audio) return; setRecordingStatus('recording'); const recordingInfo = await Audio.startRecording( { @@ -188,6 +192,7 @@ export const useAudioController = () => { * Function to stop voice recording. */ const stopVoiceRecording = async () => { + if (!Audio) return; if (recording) { // For Expo CLI if (typeof recording !== 'string') { From b9cb8905e2783c95ac0c2b203197475abfccde21 Mon Sep 17 00:00:00 2001 From: Angel Oliver Date: Wed, 15 May 2024 03:43:34 -0400 Subject: [PATCH 3/7] fix: Stream chat fix http no such file (#2512) * [fix] http: no such file * [fix] doble new * replace backslash * [lint] fix --- .../src/components/MessageInput/hooks/useAudioController.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package/src/components/MessageInput/hooks/useAudioController.tsx b/package/src/components/MessageInput/hooks/useAudioController.tsx index 111d97bd57..0708c2c403 100644 --- a/package/src/components/MessageInput/hooks/useAudioController.tsx +++ b/package/src/components/MessageInput/hooks/useAudioController.tsx @@ -250,10 +250,13 @@ export const useAudioController = () => { const resampledWaveformData = resampleWaveformData(waveformData, 100); + const clearFilter = new RegExp('[.:]', 'g'); + const date = new Date().toISOString().replace(clearFilter, '_'); + const file: File = { duration: durationInSeconds, mimeType: 'audio/aac', - name: `audio_recording_${new Date().toISOString()}.aac`, + name: `audio_recording_${date}.aac`, type: 'voiceRecording', uri: typeof recording !== 'string' ? (recording?.getURI() as string) : (recording as string), waveform_data: resampledWaveformData, From fb971a67da203f0783209af5b505758a7504dc42 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 15 May 2024 15:37:48 +0530 Subject: [PATCH 4/7] fix: new message in empty messagelist and channel preview latest message (#2509) * fix: new message in empty messagelist and channel preview latest message * fix: use new message * fix: add channel id check --- package/src/components/Channel/Channel.tsx | 24 +++++++++---------- .../hooks/listeners/useNewMessage.ts | 23 +++++++----------- package/src/components/ChannelList/utils.ts | 3 ++- .../ChannelPreview/ChannelPreview.tsx | 7 +----- .../useLatestMessagePreview.test.tsx | 2 +- .../hooks/useLatestMessagePreview.ts | 19 +++++---------- .../Indicators/EmptyStateIndicator.tsx | 21 ++++++++++++++-- package/src/hooks/useTranslatedMessage.ts | 4 +++- package/src/i18n/en.json | 1 + package/src/i18n/es.json | 1 + package/src/i18n/fr.json | 1 + package/src/i18n/he.json | 1 + package/src/i18n/hi.json | 1 + package/src/i18n/it.json | 1 + package/src/i18n/ja.json | 1 + package/src/i18n/ko.json | 1 + package/src/i18n/nl.json | 1 + package/src/i18n/pt-BR.json | 1 + package/src/i18n/ru.json | 1 + package/src/i18n/tr.json | 1 + package/src/icons/ChatIcon.tsx | 12 ++++++++++ package/src/icons/index.ts | 1 + 22 files changed, 78 insertions(+), 50 deletions(-) create mode 100644 package/src/icons/ChatIcon.tsx diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 7dc2c72c78..314767b3c7 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -598,7 +598,7 @@ const ChannelWithContext = < const [error, setError] = useState(false); const [hasMore, setHasMore] = useState(true); const [lastRead, setLastRead] = useState['lastRead']>(); - const [loading, setLoading] = useState(!channel?.state.messages.length); + const [loading, setLoading] = useState(false); const [loadingMore, setLoadingMore] = useState(false); const [loadingMoreRecent, setLoadingMoreRecent] = useState(false); @@ -645,10 +645,8 @@ const ChannelWithContext = < * Also there is no use case from UX perspective, why one would need loading uninitialized channel at particular message. * If the channel is not initiated, then we need to do channel.watch, which is more expensive for backend than channel.query. */ - let channelLoaded = false; if (!channel.initialized) { await loadChannel(); - channelLoaded = true; } if (messageId) { @@ -658,8 +656,6 @@ const ChannelWithContext = < channel.countUnread() > scrollToFirstUnreadThreshold ) { loadChannelAtFirstUnreadMessage(); - } else if (!channelLoaded) { - loadChannel(); } }; @@ -1167,15 +1163,19 @@ const ChannelWithContext = < }); const loadChannel = () => - channelQueryCallRef.current(async () => { - if (!channel?.initialized || !channel.state.isUpToDate) { - await channel?.watch(); + channelQueryCallRef.current( + async () => { + if (!channel?.initialized || !channel.state.isUpToDate) { + await channel?.watch(); + } else { + await channel.state.loadMessageIntoState('latest'); + } + }, + () => { channel?.state.setIsUpToDate(true); setHasNoMoreRecentMessagesToLoad(true); - } else { - await channel.state.loadMessageIntoState('latest'); - } - }); + }, + ); const reloadThread = async () => { if (!channel || !thread?.id) return; diff --git a/package/src/components/ChannelList/hooks/listeners/useNewMessage.ts b/package/src/components/ChannelList/hooks/listeners/useNewMessage.ts index 12703cb7d9..046b78df5b 100644 --- a/package/src/components/ChannelList/hooks/listeners/useNewMessage.ts +++ b/package/src/components/ChannelList/hooks/listeners/useNewMessage.ts @@ -34,27 +34,22 @@ export const useNewMessage = < } else { setChannels((channels) => { if (!channels) return channels; + const channelInList = channels.filter((channel) => channel.cid === event.cid).length > 0; - if (!lockChannelOrder && event.cid && event.channel_type && event.channel_id) { - const targetChannelIndex = channels.findIndex((c) => c.cid === event.cid); - - if (targetChannelIndex >= 0) { - return moveChannelUp({ - channels, - cid: event.cid, - }); - } - + if (!channelInList && event.channel_type && event.channel_id) { // If channel doesn't exist in existing list, check in activeChannels as well. // It may happen that channel was hidden using channel.hide(). In that case // We remove it from `channels` state, but its still being watched and exists in client.activeChannels. const channel = client.channel(event.channel_type, event.channel_id); - - if (channel.initialized) { - return [channel, ...channels]; - } + return [channel, ...channels]; } + if (!lockChannelOrder && event.cid) + return moveChannelUp({ + channels, + cid: event.cid, + }); + return [...channels]; }); } diff --git a/package/src/components/ChannelList/utils.ts b/package/src/components/ChannelList/utils.ts index bce8798e2e..19b9f15a0e 100644 --- a/package/src/components/ChannelList/utils.ts +++ b/package/src/components/ChannelList/utils.ts @@ -1,3 +1,4 @@ +import uniqBy from 'lodash/uniqBy'; import type { Channel, StreamChat } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../types/types'; @@ -24,7 +25,7 @@ export const moveChannelUp = < channels.splice(index, 1); channels.unshift(channel); - return [...channels]; + return uniqBy([channel, ...channels], 'cid'); }; type GetParameters< diff --git a/package/src/components/ChannelPreview/ChannelPreview.tsx b/package/src/components/ChannelPreview/ChannelPreview.tsx index 4206858fa5..4f1be10cf9 100644 --- a/package/src/components/ChannelPreview/ChannelPreview.tsx +++ b/package/src/components/ChannelPreview/ChannelPreview.tsx @@ -10,7 +10,6 @@ import { } from '../../contexts/channelsContext/ChannelsContext'; import { ChatContextValue, useChatContext } from '../../contexts/chatContext/ChatContext'; -import { useTranslatedMessage } from '../../hooks/useTranslatedMessage'; import type { DefaultStreamChatGenerics } from '../../types/types'; export type ChannelPreviewPropsWithContext< @@ -40,14 +39,10 @@ const ChannelPreviewWithContext = < | undefined >(channel.state.messages[channel.state.messages.length - 1]); - const translatedLastMessage = useTranslatedMessage( - lastMessage || ({} as MessageResponse), - ); - const [forceUpdate, setForceUpdate] = useState(0); const [unread, setUnread] = useState(channel.countUnread()); - const latestMessagePreview = useLatestMessagePreview(channel, forceUpdate, translatedLastMessage); + const latestMessagePreview = useLatestMessagePreview(channel, forceUpdate); const channelLastMessage = channel.lastMessage(); const channelLastMessageString = `${channelLastMessage?.id}${channelLastMessage?.updated_at}`; diff --git a/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx b/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx index aacc6c0d61..9e51b11b00 100644 --- a/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx +++ b/package/src/components/ChannelPreview/hooks/__tests__/useLatestMessagePreview.test.tsx @@ -45,7 +45,7 @@ describe('useLatestMessagePreview', () => { ); it('should return a deleted message preview if the latest message is deleted', async () => { - const latestMessage = { type: 'deleted' } as unknown as MessageResponse; + const latestMessage = { cid: 'test', type: 'deleted' } as unknown as MessageResponse; const { result } = renderHook( () => useLatestMessagePreview(CHANNEL_WITH_DELETED_MESSAGES, FORCE_UPDATE, latestMessage), diff --git a/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts b/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts index b728a0e9c5..24af947d98 100644 --- a/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +++ b/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts @@ -9,6 +9,7 @@ import { useTranslationContext, } from '../../../contexts/translationContext/TranslationContext'; +import { useTranslatedMessage } from '../../../hooks/useTranslatedMessage'; import type { DefaultStreamChatGenerics } from '../../../types/types'; type LatestMessage< @@ -237,9 +238,6 @@ export const useLatestMessagePreview = < >( channel: Channel, forceUpdate: number, - lastMessage?: - | ReturnType['formatMessage']> - | MessageResponse, ) => { const { client } = useChatContext(); const { t, tDateTimeParser } = useTranslationContext(); @@ -249,9 +247,9 @@ export const useLatestMessagePreview = < const messages = channel.state.messages; const message = messages.length ? messages[messages.length - 1] : undefined; - const channelLastMessageString = `${lastMessage?.id || message?.id}${ - lastMessage?.updated_at || message?.updated_at - }`; + const translatedLastMessage = useTranslatedMessage(message); + + const channelLastMessageString = `${message?.id}${message?.updated_at}`; const [readEvents, setReadEvents] = useState(true); const [latestMessagePreview, setLatestMessagePreview] = useState< @@ -268,12 +266,7 @@ export const useLatestMessagePreview = < status: MessageReadStatus.NOT_SENT_BY_CURRENT_USER, }); - const readStatus = getLatestMessageReadStatus( - channel, - client, - lastMessage || message, - readEvents, - ); + const readStatus = getLatestMessageReadStatus(channel, client, translatedLastMessage, readEvents); useEffect(() => { if (channelConfigExists) { @@ -290,7 +283,7 @@ export const useLatestMessagePreview = < getLatestMessagePreview({ channel, client, - lastMessage, + lastMessage: translatedLastMessage, readEvents, t, tDateTimeParser, diff --git a/package/src/components/Indicators/EmptyStateIndicator.tsx b/package/src/components/Indicators/EmptyStateIndicator.tsx index fef7e8973c..114c0f906d 100644 --- a/package/src/components/Indicators/EmptyStateIndicator.tsx +++ b/package/src/components/Indicators/EmptyStateIndicator.tsx @@ -4,7 +4,7 @@ import { StyleSheet, Text, View } from 'react-native'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; import { useViewport } from '../../hooks/useViewport'; -import { MessageIcon } from '../../icons/MessageIcon'; +import { ChatIcon, MessageIcon } from '../../icons'; const styles = StyleSheet.create({ channelContainer: { @@ -21,6 +21,16 @@ const styles = StyleSheet.create({ paddingBottom: 8, paddingTop: 16, }, + messageContainer: { + alignItems: 'center', + flex: 1, + justifyContent: 'center', + }, + messageTitle: { + fontSize: 20, + fontWeight: 'bold', + paddingBottom: 8, + }, }); export type EmptyStateProps = { @@ -58,7 +68,14 @@ export const EmptyStateIndicator = ({ listType }: EmptyStateProps) => { ); case 'message': - return null; + return ( + + + + {t('No chats here yet…')} + + + ); default: return No items exist; } diff --git a/package/src/hooks/useTranslatedMessage.ts b/package/src/hooks/useTranslatedMessage.ts index b11246d41e..a60bde1ae7 100644 --- a/package/src/hooks/useTranslatedMessage.ts +++ b/package/src/hooks/useTranslatedMessage.ts @@ -9,7 +9,7 @@ type TranslationKey = `${TranslationLanguages}_text`; export const useTranslatedMessage = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( - message: MessageResponse | FormatMessageResponse, + message?: MessageResponse | FormatMessageResponse, ) => { const { userLanguage: translationContextUserLanguage } = useTranslationContext(); const messageOverlayContextValue = useMessageOverlayContext(); @@ -19,6 +19,8 @@ export const useTranslatedMessage = < const translationKey: TranslationKey = `${userLanguage}_text`; + if (!message) return undefined; + if (message.i18n && translationKey in message.i18n && message.type !== 'deleted') { return { ...message, diff --git a/package/src/i18n/en.json b/package/src/i18n/en.json index cfa52931ec..f35f9ec53f 100644 --- a/package/src/i18n/en.json +++ b/package/src/i18n/en.json @@ -40,6 +40,7 @@ "Message deleted": "Message deleted", "Message flagged": "Message flagged", "Mute User": "Mute User", + "No chats here yet…": "No chats here yet…", "Not supported": "Not supported", "Nothing yet...": "Nothing yet...", "Ok": "Ok", diff --git a/package/src/i18n/es.json b/package/src/i18n/es.json index 3054d16201..9ec9d6a767 100644 --- a/package/src/i18n/es.json +++ b/package/src/i18n/es.json @@ -40,6 +40,7 @@ "Message deleted": "Mensaje eliminado", "Message flagged": "Mensaje reportado", "Mute User": "Silenciar usuario", + "No chats here yet…": "No hay chats aquí todavía...", "Not supported": "No admitido", "Nothing yet...": "Aún no hay nada...", "Ok": "Aceptar", diff --git a/package/src/i18n/fr.json b/package/src/i18n/fr.json index bacfea36e5..f82f036481 100644 --- a/package/src/i18n/fr.json +++ b/package/src/i18n/fr.json @@ -40,6 +40,7 @@ "Message deleted": "Message supprimé", "Message flagged": "Message signalé", "Mute User": "Utilisateur muet", + "No chats here yet…": "Pas de discussions ici pour le moment…", "Not supported": "Non pris en charge", "Nothing yet...": "Aucun message...", "Ok": "Ok", diff --git a/package/src/i18n/he.json b/package/src/i18n/he.json index 0636ef522d..e0188119f4 100644 --- a/package/src/i18n/he.json +++ b/package/src/i18n/he.json @@ -40,6 +40,7 @@ "Message deleted": "ההודעה נמחקה", "Message flagged": "ההודעה סומנה", "Mute User": "השתק/י משתמש", + "No chats here yet…": "אין צ'אטים כאן עדיין...", "Not supported": "לא נתמך", "Nothing yet...": "אינפורמציה תתקבל בהמשך...", "Ok": "אוקיי", diff --git a/package/src/i18n/hi.json b/package/src/i18n/hi.json index f36688a5d2..659fef72f4 100644 --- a/package/src/i18n/hi.json +++ b/package/src/i18n/hi.json @@ -40,6 +40,7 @@ "Message deleted": "मैसेज हटा दिया गया", "Message flagged": "संदेश को ध्वजांकित किया गया", "Mute User": "उपयोगकर्ता को म्यूट करें", + "No chats here yet…": "अभी तक यहाँ कोई चैट नहीं है...", "Not supported": "समर्थित नहीं", "Nothing yet...": "कोई मैसेज नहीं है...", "Ok": "ठीक", diff --git a/package/src/i18n/it.json b/package/src/i18n/it.json index bc4d2a396a..37e7a00c87 100644 --- a/package/src/i18n/it.json +++ b/package/src/i18n/it.json @@ -40,6 +40,7 @@ "Message deleted": "Messaggio cancellato", "Message flagged": "Messaggio contrassegnato", "Mute User": "Utente Muto", + "No chats here yet…": "Non ci sono ancora chat qui...", "Not supported": "non supportato", "Nothing yet...": "Ancora niente...", "Ok": "Ok", diff --git a/package/src/i18n/ja.json b/package/src/i18n/ja.json index 2a972462a9..22f46e86d1 100644 --- a/package/src/i18n/ja.json +++ b/package/src/i18n/ja.json @@ -40,6 +40,7 @@ "Message deleted": "メッセージが削除されました", "Message flagged": "メッセージにフラグが付けられました", "Mute User": "ユーザーをミュートする", + "No chats here yet…": "まだチャットはありません…", "Not supported": "サポートしていません", "Nothing yet...": "まだ何もありません...", "Ok": "確認", diff --git a/package/src/i18n/ko.json b/package/src/i18n/ko.json index e5615b0bc0..c88e2eac81 100644 --- a/package/src/i18n/ko.json +++ b/package/src/i18n/ko.json @@ -40,6 +40,7 @@ "Message deleted": "메시지가 삭제되었습니다.", "Message flagged": "메시지에 플래그가 지정되었습니다", "Mute User": "사용자를 음소거", + "No chats here yet…": "아직 여기에 채팅이 없어요…", "Not supported": "지원하지 않습니다", "Nothing yet...": "아직 아무것도...", "Ok": "확인", diff --git a/package/src/i18n/nl.json b/package/src/i18n/nl.json index 44c96cc749..07669fe016 100644 --- a/package/src/i18n/nl.json +++ b/package/src/i18n/nl.json @@ -40,6 +40,7 @@ "Message deleted": "Bericht verwijderd", "Message flagged": "Bericht gemarkeerd", "Mute User": "Gebruiker dempen", + "No chats here yet…": "Nog geen chats hier…", "Not supported": "niet ondersteund", "Nothing yet...": "Nog niets...", "Ok": "Oké", diff --git a/package/src/i18n/pt-BR.json b/package/src/i18n/pt-BR.json index e495313b16..d16ee209b5 100644 --- a/package/src/i18n/pt-BR.json +++ b/package/src/i18n/pt-BR.json @@ -40,6 +40,7 @@ "Message deleted": "Mensagem excluída", "Message flagged": "Mensagem sinalizada", "Mute User": "Silenciar Usuário", + "No chats here yet…": "Ainda não há chats aqui...", "Not supported": "Não suportado", "Nothing yet...": "Nada ainda...", "Ok": "Ok", diff --git a/package/src/i18n/ru.json b/package/src/i18n/ru.json index a0536a702c..97fa55e433 100644 --- a/package/src/i18n/ru.json +++ b/package/src/i18n/ru.json @@ -40,6 +40,7 @@ "Message deleted": "Сообщение удалено", "Message flagged": "Сообщение отмечено", "Mute User": "Отключить пользователя", + "No chats here yet…": "Здесь пока нет чатов…", "Not supported": "не поддерживается", "Nothing yet...": "Пока ничего нет...", "Ok": "Oк", diff --git a/package/src/i18n/tr.json b/package/src/i18n/tr.json index 3acf6d96bd..53c8952605 100644 --- a/package/src/i18n/tr.json +++ b/package/src/i18n/tr.json @@ -40,6 +40,7 @@ "Message deleted": "Mesaj silindi", "Message flagged": "Mesaj işaretlendi", "Mute User": "Kullanıcıyı sessize al", + "No chats here yet…": "Henüz burada sohbet yok…", "Not supported": "Desteklenmiyor", "Nothing yet...": "Henüz değil...", "Ok": "Tamam", diff --git a/package/src/icons/ChatIcon.tsx b/package/src/icons/ChatIcon.tsx new file mode 100644 index 0000000000..e8afec4e30 --- /dev/null +++ b/package/src/icons/ChatIcon.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +import { IconProps, RootPath, RootSvg } from './utils/base'; + +export const ChatIcon = (props: IconProps) => ( + + + +); diff --git a/package/src/icons/index.ts b/package/src/icons/index.ts index 9edb551332..d71893bf70 100644 --- a/package/src/icons/index.ts +++ b/package/src/icons/index.ts @@ -7,6 +7,7 @@ export * from './AtMentions'; export * from './Attach'; export * from './Audio'; export * from './Camera'; +export * from './ChatIcon'; export * from './Check'; export * from './CheckAll'; export * from './CheckSend'; From e19be3ca85ea21c29deb3e26bc43c5e8d1f7c6d5 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 15 May 2024 21:08:40 +0530 Subject: [PATCH 5/7] feat: show edited message label in message UI (#2514) * feat: show edited message label in message UI * fix: unused prop * fix: message timestamp excessive memo * fix: lint issues * upgrade: stream-chat version * fix: remove excessive memoization --- .../props/message-edited-timestamp.mdx | 5 + .../reactnative/contexts/messages-context.mdx | 5 + .../reactnative/core-components/channel.mdx | 5 + .../reactnative/guides/custom-message.mdx | 2 + .../ui-components/message-deleted.mdx | 2 +- .../message-edited-timestamp.mdx | 48 +++++ docusaurus/sidebars-react-native.json | 1 + examples/ExpoMessaging/yarn.lock | 23 ++- examples/SampleApp/yarn.lock | 23 ++- examples/TypeScriptMessaging/yarn.lock | 23 ++- package/expo-package/yarn.lock | 8 +- package/native-package/yarn.lock | 8 +- package/package.json | 2 +- package/src/components/Channel/Channel.tsx | 7 +- .../Channel/hooks/useCreateMessagesContext.ts | 4 +- .../components/ImageGalleryHeader.tsx | 34 +--- package/src/components/Message/Message.tsx | 7 + .../Message/MessageSimple/MessageContent.tsx | 46 ++--- .../Message/MessageSimple/MessageDeleted.tsx | 44 ++--- .../MessageSimple/MessageEditedTimestamp.tsx | 56 ++++++ .../Message/MessageSimple/MessageFooter.tsx | 167 +++++++++++------- .../Message/MessageSimple/MessageSimple.tsx | 16 +- .../MessageSimple/MessageTimestamp.tsx | 64 +++++++ .../Message/hooks/useCreateMessageContext.ts | 5 + .../src/components/MessageList/DateHeader.tsx | 2 +- .../MessageList/InlineDateSeparator.tsx | 16 +- .../components/MessageList/MessageList.tsx | 18 +- .../components/MessageList/MessageSystem.tsx | 78 ++++---- .../MessageList/utils/getGroupStyles.ts | 11 +- .../__snapshots__/Thread.test.js.snap | 32 ++-- package/src/components/index.ts | 1 + .../channelContext/ChannelContext.tsx | 2 +- .../messageContext/MessageContext.tsx | 4 + .../messagesContext/MessagesContext.tsx | 11 +- .../src/contexts/themeContext/utils/theme.ts | 6 + package/src/i18n/en.json | 1 + package/src/i18n/es.json | 1 + package/src/i18n/fr.json | 1 + package/src/i18n/he.json | 1 + package/src/i18n/hi.json | 1 + package/src/i18n/it.json | 1 + package/src/i18n/ja.json | 1 + package/src/i18n/ko.json | 1 + package/src/i18n/nl.json | 1 + package/src/i18n/pt-BR.json | 1 + package/src/i18n/ru.json | 1 + package/src/i18n/tr.json | 1 + package/src/utils/getDateString.ts | 67 +++++++ package/src/utils/utils.ts | 11 ++ package/yarn.lock | 8 +- 50 files changed, 610 insertions(+), 274 deletions(-) create mode 100644 docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-edited-timestamp.mdx create mode 100644 docusaurus/docs/reactnative/ui-components/message-edited-timestamp.mdx create mode 100644 package/src/components/Message/MessageSimple/MessageEditedTimestamp.tsx create mode 100644 package/src/components/Message/MessageSimple/MessageTimestamp.tsx create mode 100644 package/src/utils/getDateString.ts diff --git a/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-edited-timestamp.mdx b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-edited-timestamp.mdx new file mode 100644 index 0000000000..128742682c --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/ui-components/channel/props/message-edited-timestamp.mdx @@ -0,0 +1,5 @@ +Component to render message edited label in message when the message is clicked, within [`MessageList`](../../../../ui-components/message-list.mdx). This component is only rendered for messages from other users. + +| Type | Default | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ComponentType | [`MessageEditedTimestamp`](https://github.com/GetStream/stream-chat-react-native/blob/develop/package/src/components/Message/MessageSimple/MessageEditedTimestamp.tsx) | diff --git a/docusaurus/docs/reactnative/contexts/messages-context.mdx b/docusaurus/docs/reactnative/contexts/messages-context.mdx index b2198b9866..a179987447 100644 --- a/docusaurus/docs/reactnative/contexts/messages-context.mdx +++ b/docusaurus/docs/reactnative/contexts/messages-context.mdx @@ -47,6 +47,7 @@ import MessageContent from '../common-content/ui-components/channel/props/messag import MessageActions from '../common-content/ui-components/channel/props/message_actions.mdx'; import MessageContentOrder from '../common-content/ui-components/channel/props/message_content_order.mdx'; import MessageDeleted from '../common-content/ui-components/channel/props/message-deleted.mdx'; +import MessageEditedTimestamp from '../common-content/ui-components/channel/props/message-edited-timestamp.mdx'; import MessageError from '../common-content/ui-components/channel/props/message-error.mdx'; import MessageFooter from '../common-content/ui-components/channel/props/message-footer.mdx'; import MessageHeader from '../common-content/ui-components/channel/props/message-header.mdx'; @@ -337,6 +338,10 @@ Upserts a given message in local channel state. Please note that this function d +###
_forwarded from [Channel](../../core-components/channel#messageeditedtimestamp)_ props
MessageEditedTimestamp {#messageeditedtimestamp} + + + ###
_forwarded from [Channel](../../core-components/channel#messageerror)_ props
MessageError {#messageerror} diff --git a/docusaurus/docs/reactnative/core-components/channel.mdx b/docusaurus/docs/reactnative/core-components/channel.mdx index 32542a36a7..9352e0f8f4 100644 --- a/docusaurus/docs/reactnative/core-components/channel.mdx +++ b/docusaurus/docs/reactnative/core-components/channel.mdx @@ -104,6 +104,7 @@ import MessageContent from '../common-content/ui-components/channel/props/messag import MessageActions from '../common-content/ui-components/channel/props/message_actions.mdx'; import MessageContentOrder from '../common-content/ui-components/channel/props/message_content_order.mdx'; import MessageDeleted from '../common-content/ui-components/channel/props/message-deleted.mdx'; +import MessageEditedTimestamp from '../common-content/ui-components/channel/props/message-edited-timestamp.mdx'; import MessageError from '../common-content/ui-components/channel/props/message-error.mdx'; import MessageFooter from '../common-content/ui-components/channel/props/message-footer.mdx'; import MessageHeader from '../common-content/ui-components/channel/props/message-header.mdx'; @@ -900,6 +901,10 @@ Component to render full screen error indicator, when channel fails to load. +### MessageEditedTimestamp + + + ### MessageError diff --git a/docusaurus/docs/reactnative/guides/custom-message.mdx b/docusaurus/docs/reactnative/guides/custom-message.mdx index df86c7e4ae..3763f60456 100644 --- a/docusaurus/docs/reactnative/guides/custom-message.mdx +++ b/docusaurus/docs/reactnative/guides/custom-message.mdx @@ -71,6 +71,8 @@ If you want to customize only a specific part of `MessageSimple` component, you - [MessageFooter](../../core-components/channel#messagefooter) - [MessageAvatar](../../core-components/channel#messageavatar) - [MessageBounce](../../core-components/channel#messagebounce) +- [MessageDeleted](../../core-components/channel#messagedeleted) +- [MessageEditedTimestamp](../../core-components/channel#messageeditedtimestamp) - [MessageStatus](../../core-components/channel#messagestatus) - [MessageText](../../core-components/channel#messagetext) - [MessageSystem](../../core-components/channel#messagesystem) diff --git a/docusaurus/docs/reactnative/ui-components/message-deleted.mdx b/docusaurus/docs/reactnative/ui-components/message-deleted.mdx index 859dcaa3ba..5f8ac3ab66 100644 --- a/docusaurus/docs/reactnative/ui-components/message-deleted.mdx +++ b/docusaurus/docs/reactnative/ui-components/message-deleted.mdx @@ -11,7 +11,7 @@ This is the default component provided to the prop [`MessageDeleted`](../../core ## Props -###
required
`formattedDate` +###
required
`date` DateTime stamp to be shown in footer for deleted message. diff --git a/docusaurus/docs/reactnative/ui-components/message-edited-timestamp.mdx b/docusaurus/docs/reactnative/ui-components/message-edited-timestamp.mdx new file mode 100644 index 0000000000..904b7185c7 --- /dev/null +++ b/docusaurus/docs/reactnative/ui-components/message-edited-timestamp.mdx @@ -0,0 +1,48 @@ +--- +id: message-edited-timestamp +title: MessageEditedTimestamp +--- + +import MessageProp from '../common-content/contexts/message-context/message.mdx'; + +Component to render message edited label in message when the message is clicked, within [`MessageList`](./message-list.mdx). + +This is the default component provided to the prop [`MessageEditedTimestamp`](../../core-components/channel#messageeditedtimestamp) on the `Channel` component. + +## Props + +###
_overrides the value from [MessageContext](../../contexts/message-context#message)_
`message` {#message} + + + +### `calendar` + +Whether to show the time in Calendar time format. Calendar time displays time relative to a today's date. + +| Type | Default | +| ---------------------- | ----------- | +| `Boolean`\|`undefined` | `undefined` | + +### `format` + +Format of the date. + +| Type | Default | +| --------------------- | ----------- | +| `String`\|`undefined` | `undefined` | + +### `formatDate` + +Function to format the date. + +| Type | Default | +| ----------------------- | ----------- | +| `Function`\|`undefined` | `undefined` | + +### `timestamp` + +The date to be shown after formatting. + +| Type | Default | +| ----------------------------- | ----------- | +| `String`\|`Date`\|`undefined` | `undefined` | diff --git a/docusaurus/sidebars-react-native.json b/docusaurus/sidebars-react-native.json index 206f24fad5..b4879ebd97 100644 --- a/docusaurus/sidebars-react-native.json +++ b/docusaurus/sidebars-react-native.json @@ -79,6 +79,7 @@ "ui-components/message-bounce", "ui-components/message-content", "ui-components/message-deleted", + "ui-components/message-edited-timestamp", "ui-components/message-error", "ui-components/message-footer", "ui-components/message-pinned-header", diff --git a/examples/ExpoMessaging/yarn.lock b/examples/ExpoMessaging/yarn.lock index 5db5a9ce7e..381673668a 100644 --- a/examples/ExpoMessaging/yarn.lock +++ b/examples/ExpoMessaging/yarn.lock @@ -7327,10 +7327,10 @@ stream-buffers@2.2.x: version "0.0.0" uid "" -stream-chat-react-native-core@5.28.0: - version "5.28.0" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.28.0.tgz#66449c014e034bf6041f364ccf8f25302b8ff580" - integrity sha512-G+NmlCYPO84OE2p1soNoKRi/RlfM0ICnw3gm5wq8bPWaBvt1XozZuX6usO2+nEGVcdhaKTvXqX5i4+6FEGDPBw== +stream-chat-react-native-core@5.29.0: + version "5.29.0" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.29.0.tgz#b89d5f954c2063316ab6dc254e0b98188f01524d" + integrity sha512-cm8CQUIHPE+hHxM1hVQ6V3ogUJLpGPRZBvMYbJW7+MBdhUTn4+40EqEbqHTEtHdkLgPUmxaDJsh/gFCtVEGgog== dependencies: "@gorhom/bottom-sheet" "4.4.8" dayjs "1.10.5" @@ -7363,6 +7363,21 @@ stream-chat@8.17.0: jsonwebtoken "~9.0.0" ws "^7.4.4" +stream-chat@8.31.0: + version "8.31.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.31.0.tgz#387ed3109ac930e222bf260d98afc37dd1fcb1ac" + integrity sha512-lORUtfDYljoAgRv7QHUADH1QTGydSWo+U8KmUKwv7BZyUZ2JGwh6VZbu9WL4Nmo7PDU7N/Ug0Q5PE59S6WE+OA== + dependencies: + "@babel/runtime" "^7.16.3" + "@types/jsonwebtoken" "~9.0.0" + "@types/ws" "^7.4.0" + axios "^1.6.0" + base64-js "^1.5.1" + form-data "^4.0.0" + isomorphic-ws "^4.0.1" + jsonwebtoken "~9.0.0" + ws "^7.4.4" + stream-slice@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/stream-slice/-/stream-slice-0.1.2.tgz#2dc4f4e1b936fb13f3eb39a2def1932798d07a4b" diff --git a/examples/SampleApp/yarn.lock b/examples/SampleApp/yarn.lock index 130f0be665..3bd6a0b2e8 100644 --- a/examples/SampleApp/yarn.lock +++ b/examples/SampleApp/yarn.lock @@ -6825,10 +6825,10 @@ statuses@~1.5.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -stream-chat-react-native-core@5.28.1: - version "5.28.1" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.28.1.tgz#9bde6c2af43a24e8a5c9d4ca35da19ccef95cb34" - integrity sha512-+vsLbqFmxm6Xyt4U3f2zfNvTTB9cXWTk8gyyw4b2RJ2qMZ/8wuN/luS5xwKdNbBZZCuaDlwuJpWE/x5/ERbpWg== +stream-chat-react-native-core@5.29.0: + version "5.29.0" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.29.0.tgz#b89d5f954c2063316ab6dc254e0b98188f01524d" + integrity sha512-cm8CQUIHPE+hHxM1hVQ6V3ogUJLpGPRZBvMYbJW7+MBdhUTn4+40EqEbqHTEtHdkLgPUmxaDJsh/gFCtVEGgog== dependencies: "@gorhom/bottom-sheet" "4.4.8" dayjs "1.10.5" @@ -6865,6 +6865,21 @@ stream-chat@8.17.0: jsonwebtoken "~9.0.0" ws "^7.4.4" +stream-chat@8.31.0: + version "8.31.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.31.0.tgz#387ed3109ac930e222bf260d98afc37dd1fcb1ac" + integrity sha512-lORUtfDYljoAgRv7QHUADH1QTGydSWo+U8KmUKwv7BZyUZ2JGwh6VZbu9WL4Nmo7PDU7N/Ug0Q5PE59S6WE+OA== + dependencies: + "@babel/runtime" "^7.16.3" + "@types/jsonwebtoken" "~9.0.0" + "@types/ws" "^7.4.0" + axios "^1.6.0" + base64-js "^1.5.1" + form-data "^4.0.0" + isomorphic-ws "^4.0.1" + jsonwebtoken "~9.0.0" + ws "^7.4.4" + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" diff --git a/examples/TypeScriptMessaging/yarn.lock b/examples/TypeScriptMessaging/yarn.lock index 9525a2e15b..870402704a 100644 --- a/examples/TypeScriptMessaging/yarn.lock +++ b/examples/TypeScriptMessaging/yarn.lock @@ -6882,10 +6882,10 @@ statuses@~1.5.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -stream-chat-react-native-core@5.28.0: - version "5.28.0" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.28.0.tgz#66449c014e034bf6041f364ccf8f25302b8ff580" - integrity sha512-G+NmlCYPO84OE2p1soNoKRi/RlfM0ICnw3gm5wq8bPWaBvt1XozZuX6usO2+nEGVcdhaKTvXqX5i4+6FEGDPBw== +stream-chat-react-native-core@5.29.0: + version "5.29.0" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.29.0.tgz#b89d5f954c2063316ab6dc254e0b98188f01524d" + integrity sha512-cm8CQUIHPE+hHxM1hVQ6V3ogUJLpGPRZBvMYbJW7+MBdhUTn4+40EqEbqHTEtHdkLgPUmxaDJsh/gFCtVEGgog== dependencies: "@gorhom/bottom-sheet" "4.4.8" dayjs "1.10.5" @@ -6927,6 +6927,21 @@ stream-chat@8.17.0: jsonwebtoken "~9.0.0" ws "^7.4.4" +stream-chat@8.31.0: + version "8.31.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.31.0.tgz#387ed3109ac930e222bf260d98afc37dd1fcb1ac" + integrity sha512-lORUtfDYljoAgRv7QHUADH1QTGydSWo+U8KmUKwv7BZyUZ2JGwh6VZbu9WL4Nmo7PDU7N/Ug0Q5PE59S6WE+OA== + dependencies: + "@babel/runtime" "^7.16.3" + "@types/jsonwebtoken" "~9.0.0" + "@types/ws" "^7.4.0" + axios "^1.6.0" + base64-js "^1.5.1" + form-data "^4.0.0" + isomorphic-ws "^4.0.1" + jsonwebtoken "~9.0.0" + ws "^7.4.4" + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" diff --git a/package/expo-package/yarn.lock b/package/expo-package/yarn.lock index dd5d92c352..f96199ce1e 100644 --- a/package/expo-package/yarn.lock +++ b/package/expo-package/yarn.lock @@ -2929,10 +2929,10 @@ stream-buffers@2.2.x: resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== -stream-chat-react-native-core@5.28.1: - version "5.28.1" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.28.1.tgz#9bde6c2af43a24e8a5c9d4ca35da19ccef95cb34" - integrity sha512-+vsLbqFmxm6Xyt4U3f2zfNvTTB9cXWTk8gyyw4b2RJ2qMZ/8wuN/luS5xwKdNbBZZCuaDlwuJpWE/x5/ERbpWg== +stream-chat-react-native-core@5.29.0: + version "5.29.0" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.29.0.tgz#b89d5f954c2063316ab6dc254e0b98188f01524d" + integrity sha512-cm8CQUIHPE+hHxM1hVQ6V3ogUJLpGPRZBvMYbJW7+MBdhUTn4+40EqEbqHTEtHdkLgPUmxaDJsh/gFCtVEGgog== dependencies: "@gorhom/bottom-sheet" "4.4.8" dayjs "1.10.5" diff --git a/package/native-package/yarn.lock b/package/native-package/yarn.lock index a01d84dae2..efb9c9b103 100644 --- a/package/native-package/yarn.lock +++ b/package/native-package/yarn.lock @@ -4237,10 +4237,10 @@ statuses@~1.5.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -stream-chat-react-native-core@5.28.1: - version "5.28.1" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.28.1.tgz#9bde6c2af43a24e8a5c9d4ca35da19ccef95cb34" - integrity sha512-+vsLbqFmxm6Xyt4U3f2zfNvTTB9cXWTk8gyyw4b2RJ2qMZ/8wuN/luS5xwKdNbBZZCuaDlwuJpWE/x5/ERbpWg== +stream-chat-react-native-core@5.29.0: + version "5.29.0" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.29.0.tgz#b89d5f954c2063316ab6dc254e0b98188f01524d" + integrity sha512-cm8CQUIHPE+hHxM1hVQ6V3ogUJLpGPRZBvMYbJW7+MBdhUTn4+40EqEbqHTEtHdkLgPUmxaDJsh/gFCtVEGgog== dependencies: "@gorhom/bottom-sheet" "4.4.8" dayjs "1.10.5" diff --git a/package/package.json b/package/package.json index 6ff9bcf1a0..47494204f9 100644 --- a/package/package.json +++ b/package/package.json @@ -78,7 +78,7 @@ "path": "0.12.7", "react-native-markdown-package": "1.8.2", "react-native-url-polyfill": "^1.3.0", - "stream-chat": "8.17.0" + "stream-chat": "8.31.0" }, "peerDependencies": { "react-native-quick-sqlite": ">=5.1.0", diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 314767b3c7..bfb0f1b545 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -118,6 +118,7 @@ import { MessageAvatar as MessageAvatarDefault } from '../Message/MessageSimple/ import { MessageBounce as MessageBounceDefault } from '../Message/MessageSimple/MessageBounce'; import { MessageContent as MessageContentDefault } from '../Message/MessageSimple/MessageContent'; import { MessageDeleted as MessageDeletedDefault } from '../Message/MessageSimple/MessageDeleted'; +import { MessageEditedTimestamp as MessageEditedTimestampDefault } from '../Message/MessageSimple/MessageEditedTimestamp'; import { MessageError as MessageErrorDefault } from '../Message/MessageSimple/MessageError'; import { MessageFooter as MessageFooterDefault } from '../Message/MessageSimple/MessageFooter'; import { MessagePinnedHeader as MessagePinnedHeaderDefault } from '../Message/MessageSimple/MessagePinnedHeader'; @@ -262,7 +263,6 @@ export type ChannelPropsWithContext< | 'FileAttachmentGroup' | 'FlatList' | 'forceAlignMessages' - | 'formatDate' | 'Gallery' | 'getMessagesGroupStyles' | 'Giphy' @@ -292,6 +292,7 @@ export type ChannelPropsWithContext< | 'MessageContent' | 'messageContentOrder' | 'MessageDeleted' + | 'MessageEditedTimestamp' | 'MessageError' | 'MessageFooter' | 'MessageHeader' @@ -469,7 +470,6 @@ const ChannelWithContext = < FileUploadPreview = FileUploadPreviewDefault, FlatList = FlatListDefault, forceAlignMessages, - formatDate, Gallery = GalleryDefault, getMessagesGroupStyles, Giphy = GiphyDefault, @@ -527,6 +527,7 @@ const ChannelWithContext = < MessageContent = MessageContentDefault, messageContentOrder = ['quoted_reply', 'gallery', 'files', 'text', 'attachments'], MessageDeleted = MessageDeletedDefault, + MessageEditedTimestamp = MessageEditedTimestampDefault, MessageError = MessageErrorDefault, MessageFooter = MessageFooterDefault, MessageHeader, @@ -2295,7 +2296,6 @@ const ChannelWithContext = < FileAttachmentIcon, FlatList, forceAlignMessages, - formatDate, Gallery, getMessagesGroupStyles, Giphy, @@ -2326,6 +2326,7 @@ const ChannelWithContext = < MessageContent, messageContentOrder, MessageDeleted, + MessageEditedTimestamp, MessageError, MessageFooter, MessageHeader, diff --git a/package/src/components/Channel/hooks/useCreateMessagesContext.ts b/package/src/components/Channel/hooks/useCreateMessagesContext.ts index aceb116475..87c33be1e1 100644 --- a/package/src/components/Channel/hooks/useCreateMessagesContext.ts +++ b/package/src/components/Channel/hooks/useCreateMessagesContext.ts @@ -27,7 +27,6 @@ export const useCreateMessagesContext = < FileAttachmentIcon, FlatList, forceAlignMessages, - formatDate, Gallery, getMessagesGroupStyles, Giphy, @@ -58,6 +57,7 @@ export const useCreateMessagesContext = < MessageContent, messageContentOrder, MessageDeleted, + MessageEditedTimestamp, MessageError, MessageFooter, MessageHeader, @@ -123,7 +123,6 @@ export const useCreateMessagesContext = < FileAttachmentIcon, FlatList, forceAlignMessages, - formatDate, Gallery, getMessagesGroupStyles, Giphy, @@ -154,6 +153,7 @@ export const useCreateMessagesContext = < MessageContent, messageContentOrder, MessageDeleted, + MessageEditedTimestamp, MessageError, MessageFooter, MessageHeader, diff --git a/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx b/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx index 2208ead010..834eb6ba61 100644 --- a/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx +++ b/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx @@ -4,14 +4,11 @@ import Animated, { Extrapolate, interpolate, useAnimatedStyle } from 'react-nati import { useOverlayContext } from '../../../contexts/overlayContext/OverlayContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; -import { - isDayOrMoment, - TDateTimeParserOutput, - useTranslationContext, -} from '../../../contexts/translationContext/TranslationContext'; +import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { Close } from '../../../icons'; import type { DefaultStreamChatGenerics } from '../../../types/types'; +import { getDateString } from '../../../utils/getDateString'; import type { Photo } from '../ImageGallery'; const ReanimatedSafeAreaView = Animated.createAnimatedComponent @@ -101,32 +98,7 @@ export const ImageGalleryHeader = < const { t, tDateTimeParser } = useTranslationContext(); const { setOverlay } = useOverlayContext(); - const parsedDate = photo ? tDateTimeParser(photo?.created_at) : null; - - /** - * .calendar and .fromNow can be initialized after the first render, - * and attempting to access them at that time will cause an error - * to be thrown. - * - * This falls back to null if neither exist. - */ - const getDateObject = (date: TDateTimeParserOutput | null) => { - if (date === null || !isDayOrMoment(date)) { - return null; - } - - if (date.calendar) { - return date.calendar(); - } - - if (date.fromNow) { - return date.fromNow(); - } - - return null; - }; - - const date = getDateObject(parsedDate); + const date = getDateString({ calendar: true, date: photo?.created_at, tDateTimeParser }); const headerStyle = useAnimatedStyle(() => ({ opacity: opacity.value, diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx index d9b522e0ea..3f08e89cad 100644 --- a/package/src/components/Message/Message.tsx +++ b/package/src/components/Message/Message.tsx @@ -48,6 +48,7 @@ import { hasOnlyEmojis, isBlockedMessage, isBouncedMessage, + isEditedMessage, MessageStatusTypes, } from '../../utils/utils'; @@ -233,6 +234,7 @@ const MessageWithContext = < props: MessagePropsWithContext, ) => { const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false); + const [isEditedMessageOpen, setIsEditedMessageOpen] = useState(false); const isMessageTypeDeleted = props.message.type === 'deleted'; const { @@ -334,6 +336,9 @@ const MessageWithContext = < if (dismissKeyboardOnMessageTouch) { Keyboard.dismiss(); } + if (isEditedMessage(message)) { + setIsEditedMessageOpen((prevState) => !prevState); + } const quotedMessage = message.quoted_message as MessageType; if (error) { /** @@ -677,6 +682,7 @@ const MessageWithContext = < handleToggleReaction, hasReactions, images: attachments.images, + isEditedMessageOpen, isMyMessage, lastGroupMessage: groupStyles?.[0] === 'single' || groupStyles?.[0] === 'bottom', lastReceivedId, @@ -727,6 +733,7 @@ const MessageWithContext = < otherAttachments: attachments.other, preventPress, reactions, + setIsEditedMessageOpen, showAvatar, showMessageOverlay, showMessageStatus: typeof showMessageStatus === 'boolean' ? showMessageStatus : isMyMessage, diff --git a/package/src/components/Message/MessageSimple/MessageContent.tsx b/package/src/components/Message/MessageSimple/MessageContent.tsx index 6e37601e05..ce4cc161aa 100644 --- a/package/src/components/Message/MessageSimple/MessageContent.tsx +++ b/package/src/components/Message/MessageSimple/MessageContent.tsx @@ -19,8 +19,6 @@ import { } from '../../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { - isDayOrMoment, - TDateTimeParserInput, TranslationContextValue, useTranslationContext, } from '../../../contexts/translationContext/TranslationContext'; @@ -66,6 +64,7 @@ export type MessageContentPropsWithContext< MessageContextValue, | 'alignment' | 'disabled' + | 'isEditedMessageOpen' | 'goToMessage' | 'groupStyles' | 'hasReactions' @@ -88,7 +87,6 @@ export type MessageContentPropsWithContext< | 'additionalTouchableProps' | 'Attachment' | 'FileAttachmentGroup' - | 'formatDate' | 'Gallery' | 'isAttachmentEqual' | 'MessageFooter' @@ -102,7 +100,7 @@ export type MessageContentPropsWithContext< | 'onPressInMessage' | 'Reply' > & - Pick & { + Pick & { setMessageContentWidth: React.Dispatch>; }; @@ -120,7 +118,6 @@ const MessageContentWithContext = < Attachment, disabled, FileAttachmentGroup, - formatDate, Gallery, groupStyles, hasReactions, @@ -145,7 +142,6 @@ const MessageContentWithContext = < Reply, setMessageContentWidth, showMessageStatus, - tDateTimeParser, threadList, } = props; @@ -178,21 +174,6 @@ const MessageContentWithContext = < } = useTheme(); const { vw } = useViewport(); - const getDateText = (formatter?: (date: TDateTimeParserInput) => string) => { - if (!message.created_at) return ''; - - if (formatter) { - return formatter(message.created_at); - } - - const parserOutput = tDateTimeParser(message.created_at); - - if (isDayOrMoment(parserOutput)) { - return parserOutput.format('LT'); - } - return message.created_at; - }; - const onLayout: (event: LayoutChangeEvent) => void = ({ nativeEvent: { layout: { width }, @@ -221,7 +202,7 @@ const MessageContentWithContext = < if (isMessageTypeDeleted) { return ( } - + ); }; @@ -426,6 +407,7 @@ const areEqual = (); - const { t, tDateTimeParser } = useTranslationContext(); + const { t } = useTranslationContext(); return ( @@ -618,12 +599,12 @@ export const MessageContent = < Attachment, disabled, FileAttachmentGroup, - formatDate, Gallery, goToMessage, groupStyles, hasReactions, isAttachmentEqual, + isEditedMessageOpen, isMyMessage, lastGroupMessage, lastReceivedId, @@ -647,7 +628,6 @@ export const MessageContent = < Reply, showMessageStatus, t, - tDateTimeParser, threadList, }} {...props} diff --git a/package/src/components/Message/MessageSimple/MessageDeleted.tsx b/package/src/components/Message/MessageSimple/MessageDeleted.tsx index 07667600b6..330f8a9f7d 100644 --- a/package/src/components/Message/MessageSimple/MessageDeleted.tsx +++ b/package/src/components/Message/MessageSimple/MessageDeleted.tsx @@ -3,11 +3,9 @@ import { LayoutChangeEvent, StyleSheet, View } from 'react-native'; import merge from 'lodash/merge'; -import type { MessageFooterProps } from './MessageFooter'; import { MessageTextContainer } from './MessageTextContainer'; import { - Alignment, MessageContextValue, useMessageContext, } from '../../../contexts/messageContext/MessageContext'; @@ -16,13 +14,9 @@ import { useMessagesContext, } from '../../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; -import { - TranslationContextValue, - useTranslationContext, -} from '../../../contexts/translationContext/TranslationContext'; +import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import type { DefaultStreamChatGenerics } from '../../../types/types'; -import type { MessageType } from '../../MessageList/hooks/useMessageList'; const styles = StyleSheet.create({ containerInner: { @@ -40,17 +34,16 @@ const styles = StyleSheet.create({ }); type MessageDeletedComponentProps = { - formattedDate: string | Date; groupStyle: string; noBorder: boolean; onLayout: (event: LayoutChangeEvent) => void; + date?: string | Date; }; type MessageDeletedPropsWithContext< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Pick, 'alignment' | 'message'> & Pick, 'MessageFooter'> & - Pick & MessageDeletedComponentProps; const MessageDeletedWithContext = < @@ -58,8 +51,7 @@ const MessageDeletedWithContext = < >( props: MessageDeletedPropsWithContext, ) => { - const { alignment, formattedDate, groupStyle, message, MessageFooter, noBorder, onLayout, t } = - props; + const { alignment, date, groupStyle, message, MessageFooter, noBorder, onLayout } = props; const { theme: { @@ -74,6 +66,7 @@ const MessageDeletedWithContext = < }, }, } = useTheme(); + const { t } = useTranslationContext(); return ( - + ); }; @@ -117,16 +110,8 @@ const areEqual = , nextProps: MessageDeletedPropsWithContext, ) => { - const { - alignment: prevAlignment, - formattedDate: prevFormattedDate, - message: prevMessage, - } = prevProps; - const { - alignment: nextAlignment, - formattedDate: nextFormattedDate, - message: nextMessage, - } = nextProps; + const { alignment: prevAlignment, date: prevDate, message: prevMessage } = prevProps; + const { alignment: nextAlignment, date: nextDate, message: nextMessage } = nextProps; const alignmentEqual = prevAlignment === nextAlignment; if (!alignmentEqual) return false; @@ -142,8 +127,8 @@ const areEqual = = MessageDeletedComponentProps & { - alignment?: Alignment; - message?: MessageType; - MessageFooter?: React.ComponentType>; +> = Partial> & { + groupStyle: string; + noBorder: boolean; + onLayout: (event: LayoutChangeEvent) => void; }; export const MessageDeleted = < @@ -170,15 +155,12 @@ export const MessageDeleted = < const { MessageFooter } = useMessagesContext(); - const { t } = useTranslationContext(); - return ( diff --git a/package/src/components/Message/MessageSimple/MessageEditedTimestamp.tsx b/package/src/components/Message/MessageSimple/MessageEditedTimestamp.tsx new file mode 100644 index 0000000000..7db27bf123 --- /dev/null +++ b/package/src/components/Message/MessageSimple/MessageEditedTimestamp.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; + +import { MessageTimestamp, MessageTimestampProps } from './MessageTimestamp'; + +import { + MessageContextValue, + useMessageContext, +} from '../../../contexts/messageContext/MessageContext'; +import { useTheme } from '../../../contexts/themeContext/ThemeContext'; +import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; +import { DefaultStreamChatGenerics } from '../../../types/types'; +import { isEditedMessage } from '../../../utils/utils'; + +export type MessageEditedTimestampProps< + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = Partial, 'message'>> & MessageTimestampProps; + +export const MessageEditedTimestamp = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + props: MessageEditedTimestampProps, +) => { + const { message: propMessage, timestamp } = props; + const { + theme: { + colors: { grey }, + messageSimple: { + content: { editedLabel, editedTimestampContainer }, + }, + }, + } = useTheme(); + const { t } = useTranslationContext(); + const { message: contextMessage } = useMessageContext(); + const message = propMessage || contextMessage; + + if (!isEditedMessage(message)) { + return null; + } + + return ( + + {t('Edited') + ' '} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + }, + text: { + fontSize: 12, + }, +}); diff --git a/package/src/components/Message/MessageSimple/MessageFooter.tsx b/package/src/components/Message/MessageSimple/MessageFooter.tsx index 86dc8e7040..0ad55ba610 100644 --- a/package/src/components/Message/MessageSimple/MessageFooter.tsx +++ b/package/src/components/Message/MessageSimple/MessageFooter.tsx @@ -5,6 +5,8 @@ import type { Attachment } from 'stream-chat'; import type { MessageStatusProps } from './MessageStatus'; +import { MessageTimestamp } from './MessageTimestamp'; + import type { ChannelContextValue } from '../../../contexts/channelContext/ChannelContext'; import { Alignment, @@ -21,11 +23,11 @@ import { useTranslationContext } from '../../../contexts/translationContext/Tran import { Eye } from '../../../icons'; import type { DefaultStreamChatGenerics } from '../../../types/types'; -import { MessageStatusTypes } from '../../../utils/utils'; +import { isEditedMessage, MessageStatusTypes } from '../../../utils/utils'; import type { MessageType } from '../../MessageList/hooks/useMessageList'; type MessageFooterComponentProps = { - formattedDate: string | Date; + date?: string | Date; isDeleted?: boolean; }; @@ -34,6 +36,7 @@ type MessageFooterPropsWithContext< > = Pick< MessageContextValue, | 'alignment' + | 'isEditedMessageOpen' | 'members' | 'message' | 'otherAttachments' @@ -42,10 +45,41 @@ type MessageFooterPropsWithContext< > & Pick< MessagesContextValue, - 'deletedMessagesVisibilityType' | 'MessageStatus' + 'deletedMessagesVisibilityType' | 'MessageEditedTimestamp' | 'MessageStatus' > & MessageFooterComponentProps; +const OnlyVisibleToYouComponent = ({ alignment }: { alignment: Alignment }) => { + const { + theme: { + colors: { grey_dark }, + messageSimple: { + content: { deletedMetaText, eyeIcon, metaText }, + }, + }, + } = useTheme(); + const { t } = useTranslationContext(); + + return ( + <> + + + {t('Only visible to you')} + + + ); +}; + const MessageFooterWithContext = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( @@ -53,12 +87,14 @@ const MessageFooterWithContext = < ) => { const { alignment, + date, deletedMessagesVisibilityType, - formattedDate, isDeleted, + isEditedMessageOpen, lastGroupMessage, members, message, + MessageEditedTimestamp, MessageStatus, otherAttachments, showMessageStatus, @@ -66,9 +102,9 @@ const MessageFooterWithContext = < const { theme: { - colors: { grey, grey_dark }, + colors: { grey }, messageSimple: { - content: { deletedMetaText, eyeIcon, messageUser, metaContainer, metaText }, + content: { editedLabel, messageUser, metaContainer, metaText }, }, }, } = useTheme(); @@ -78,69 +114,51 @@ const MessageFooterWithContext = < return ( {deletedMessagesVisibilityType === 'sender' && ( + + )} + + + ); + } + + if (lastGroupMessage === false && message.status === MessageStatusTypes.RECEIVED) { + return null; + } + + return ( + <> + + {otherAttachments.length && otherAttachments[0].actions ? ( + + ) : null} + {Object.keys(members).length > 2 && alignment === 'left' && message.user?.name ? ( + {message.user.name} + ) : null} + {showMessageStatus && } + + + {isEditedMessage(message) && !isEditedMessageOpen && ( <> - - {t('Only visible to you')} + ⦁ + + + {t('Edited')} )} - - {formattedDate.toString()} - - ); - } - - if (lastGroupMessage === false && message.status === MessageStatusTypes.RECEIVED) { - return null; - } - - return ( - - {otherAttachments.length && otherAttachments[0].actions ? ( - <> - - - {t('Only visible to you')} - - - ) : null} - {Object.keys(members).length > 2 && alignment === 'left' && message.user?.name ? ( - {message.user.name} - ) : null} - {showMessageStatus && } - - {formattedDate.toString()} - - + {isEditedMessageOpen && } + ); }; @@ -150,7 +168,8 @@ const areEqual = { const { alignment: prevAlignment, - formattedDate: prevFormattedDate, + date: prevDate, + isEditedMessageOpen: prevIsEditedMessageOpen, lastGroupMessage: prevLastGroupMessage, members: prevMembers, message: prevMessage, @@ -159,7 +178,8 @@ const areEqual = ( props: MessageFooterProps, ) => { - const { alignment, lastGroupMessage, members, message, otherAttachments, showMessageStatus } = - useMessageContext(); + const { + alignment, + isEditedMessageOpen, + lastGroupMessage, + members, + message, + otherAttachments, + showMessageStatus, + } = useMessageContext(); - const { deletedMessagesVisibilityType, MessageStatus } = useMessagesContext(); + const { deletedMessagesVisibilityType, MessageEditedTimestamp, MessageStatus } = + useMessagesContext(); return ( = Pick< MessageContextValue, - 'alignment' | 'channel' | 'disabled' | 'groupStyles' | 'hasReactions' | 'message' + | 'alignment' + | 'channel' + | 'disabled' + | 'isEditedMessageOpen' + | 'groupStyles' + | 'hasReactions' + | 'message' > & Pick< MessagesContextValue, @@ -122,6 +128,7 @@ const areEqual = ( props: MessageSimpleProps, ) => { - const { alignment, channel, disabled, groupStyles, hasReactions, message } = + const { alignment, channel, disabled, groupStyles, hasReactions, isEditedMessageOpen, message } = useMessageContext(); const { enableMessageGroupingByUser, @@ -238,6 +249,7 @@ export const MessageSimple = < enableMessageGroupingByUser, groupStyles, hasReactions, + isEditedMessageOpen, message, MessageAvatar, MessageContent, diff --git a/package/src/components/Message/MessageSimple/MessageTimestamp.tsx b/package/src/components/Message/MessageSimple/MessageTimestamp.tsx new file mode 100644 index 0000000000..b861287654 --- /dev/null +++ b/package/src/components/Message/MessageSimple/MessageTimestamp.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { StyleSheet, Text } from 'react-native'; + +import { useTheme } from '../../../contexts/themeContext/ThemeContext'; +import { + TDateTimeParserInput, + TranslationContextValue, + useTranslationContext, +} from '../../../contexts/translationContext/TranslationContext'; +import { getDateString } from '../../../utils/getDateString'; + +export type MessageTimestampProps = Partial> & { + /** + * Whether to show the time in Calendar time format. Calendar time displays time relative to a today's date. + */ + calendar?: boolean; + /** + * The format in which the date should be displayed. + */ + format?: string; + /** + * A function to format the date. + */ + formatDate?: (date: TDateTimeParserInput) => string; + /** + * The timestamp of the message. + */ + timestamp?: string | Date; +}; + +export const MessageTimestamp = (props: MessageTimestampProps) => { + const { calendar, format, formatDate, tDateTimeParser: propsTDateTimeParser, timestamp } = props; + const { + theme: { + colors: { grey }, + messageSimple: { + content: { timestampText }, + }, + }, + } = useTheme(); + const { tDateTimeParser: contextTDateTimeParser } = useTranslationContext(); + + if (!timestamp) return null; + + const formattedDate = getDateString({ + calendar, + date: timestamp, + format, + formatDate, + tDateTimeParser: propsTDateTimeParser || contextTDateTimeParser, + }); + + if (!formattedDate) return null; + + return ( + {formattedDate.toString()} + ); +}; + +const styles = StyleSheet.create({ + text: { + fontSize: 12, + }, +}); diff --git a/package/src/components/Message/hooks/useCreateMessageContext.ts b/package/src/components/Message/hooks/useCreateMessageContext.ts index b2bfa53723..a4e656dc0c 100644 --- a/package/src/components/Message/hooks/useCreateMessageContext.ts +++ b/package/src/components/Message/hooks/useCreateMessageContext.ts @@ -24,6 +24,7 @@ export const useCreateMessageContext = < handleToggleReaction, hasReactions, images, + isEditedMessageOpen, isMyMessage, lastGroupMessage, lastReceivedId, @@ -39,6 +40,7 @@ export const useCreateMessageContext = < otherAttachments, preventPress, reactions, + setIsEditedMessageOpen, showAvatar, showMessageOverlay, showMessageStatus, @@ -78,6 +80,7 @@ export const useCreateMessageContext = < handleToggleReaction, hasReactions, images, + isEditedMessageOpen, isMyMessage, lastGroupMessage, lastReceivedId, @@ -93,6 +96,7 @@ export const useCreateMessageContext = < otherAttachments, preventPress, reactions, + setIsEditedMessageOpen, showAvatar, showMessageOverlay, showMessageStatus, @@ -107,6 +111,7 @@ export const useCreateMessageContext = < goToMessage, groupStylesLength, hasReactions, + isEditedMessageOpen, lastGroupMessage, lastReceivedId, membersValue, diff --git a/package/src/components/MessageList/DateHeader.tsx b/package/src/components/MessageList/DateHeader.tsx index 4f78063290..52c8025765 100644 --- a/package/src/components/MessageList/DateHeader.tsx +++ b/package/src/components/MessageList/DateHeader.tsx @@ -21,7 +21,7 @@ const styles = StyleSheet.create({ }); export type DateHeaderProps = { - dateString: string; + dateString: string | number; }; export const DateHeader = ({ dateString }: DateHeaderProps) => { diff --git a/package/src/components/MessageList/InlineDateSeparator.tsx b/package/src/components/MessageList/InlineDateSeparator.tsx index 6ffcea81a0..19444cfcca 100644 --- a/package/src/components/MessageList/InlineDateSeparator.tsx +++ b/package/src/components/MessageList/InlineDateSeparator.tsx @@ -2,10 +2,8 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import { - isDayOrMoment, - useTranslationContext, -} from '../../contexts/translationContext/TranslationContext'; +import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; +import { getDateString } from '../../utils/getDateString'; const styles = StyleSheet.create({ container: { @@ -42,10 +40,12 @@ export const InlineDateSeparator = ({ date }: InlineDateSeparatorProps) => { } const dateFormat = date.getFullYear() === new Date().getFullYear() ? 'MMM D' : 'MMM D, YYYY'; - const tDate = tDateTimeParser(date); - const dateString = isDayOrMoment(tDate) - ? tDate.format(dateFormat) - : new Date(tDate).toDateString(); + + const dateString = getDateString({ + date, + format: dateFormat, + tDateTimeParser, + }); return ( { - if (tStickyHeaderDate === null || hideStickyDateHeader) return null; - if (isDayOrMoment(tStickyHeaderDate)) return tStickyHeaderDate.format(stickyHeaderFormatDate); - - return new Date(tStickyHeaderDate).toDateString(); - }, [tStickyHeaderDate, stickyHeaderFormatDate, hideStickyDateHeader]); + if (!stickyHeaderDate) return null; + return getDateString({ + date: stickyHeaderDate, + format: stickyHeaderDateFormat, + tDateTimeParser, + }); + }, [stickyHeaderDate, stickyHeaderDateFormat]); const dismissImagePicker = () => { if (!hasMoved && selectedPicker) { diff --git a/package/src/components/MessageList/MessageSystem.tsx b/package/src/components/MessageList/MessageSystem.tsx index 338837e44f..c33955d30e 100644 --- a/package/src/components/MessageList/MessageSystem.tsx +++ b/package/src/components/MessageList/MessageSystem.tsx @@ -4,48 +4,16 @@ import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native'; import type { MessageType } from './hooks/useMessageList'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; -import { - isDayOrMoment, - TDateTimeParserInput, - useTranslationContext, -} from '../../contexts/translationContext/TranslationContext'; +import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; import type { DefaultStreamChatGenerics } from '../../types/types'; - -const styles = StyleSheet.create({ - container: { - alignItems: 'center', - flexDirection: 'row', - justifyContent: 'center', - marginBottom: 10, - }, - line: { - flex: 1, - height: 0.5, - }, - text: { - fontSize: 10, - fontWeight: 'bold', - textAlign: 'center', - }, - textContainer: { - flex: 3, - marginTop: 10, - }, -}); +import { getDateString } from '../../utils/getDateString'; export type MessageSystemProps< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { /** Current [message object](https://getstream.io/chat/docs/#message_format) */ message: MessageType; - /** - * Formatter function for date object. - * - * @param date TDateTimeParserInput object of message - * @returns string - */ - formatDate?: (date: TDateTimeParserInput) => string; style?: StyleProp; }; @@ -59,7 +27,7 @@ export const MessageSystem = < >( props: MessageSystemProps, ) => { - const { formatDate, message, style } = props; + const { message, style } = props; const { theme: { @@ -72,13 +40,11 @@ export const MessageSystem = < const { tDateTimeParser } = useTranslationContext(); const createdAt = message.created_at; - const parsedDate = tDateTimeParser(createdAt); - const date = - formatDate && createdAt - ? formatDate(createdAt) - : parsedDate && isDayOrMoment(parsedDate) - ? parsedDate.calendar().toUpperCase() - : parsedDate; + const formattedDate = getDateString({ + calendar: true, + date: createdAt, + tDateTimeParser, + }); return ( @@ -87,7 +53,11 @@ export const MessageSystem = < {message.text?.toUpperCase() || ''} - {date.toString()} + {formattedDate && ( + + {formattedDate.toString().toUpperCase()} + + )} @@ -95,3 +65,25 @@ export const MessageSystem = < }; MessageSystem.displayName = 'MessageSystem{messageList{messageSystem}}'; + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'center', + marginBottom: 10, + }, + line: { + flex: 1, + height: 0.5, + }, + text: { + fontSize: 10, + fontWeight: 'bold', + textAlign: 'center', + }, + textContainer: { + flex: 3, + marginTop: 10, + }, +}); diff --git a/package/src/components/MessageList/utils/getGroupStyles.ts b/package/src/components/MessageList/utils/getGroupStyles.ts index 15a0e1a3a3..596e950cf1 100644 --- a/package/src/components/MessageList/utils/getGroupStyles.ts +++ b/package/src/components/MessageList/utils/getGroupStyles.ts @@ -3,6 +3,7 @@ import type { DateSeparators } from './getDateSeparators'; import type { PaginatedMessageListContextValue } from '../../../contexts/paginatedMessageListContext/PaginatedMessageListContext'; import type { ThreadContextValue } from '../../../contexts/threadContext/ThreadContext'; import type { DefaultStreamChatGenerics } from '../../../types/types'; +import { isEditedMessage } from '../../../utils/utils'; import type { GroupType } from '../hooks/useMessageList'; export type GetGroupStylesParams< @@ -59,22 +60,24 @@ export const getGroupStyles = < const isTopMessage = !previousMessage || previousMessage.type === 'system' || - userId !== previousMessage?.user?.id || previousMessage.type === 'error' || + userId !== previousMessage?.user?.id || !!isPrevMessageTypeDeleted || (!hideDateSeparators && dateSeparators[message.id]) || - messageGroupStyles[previousMessage.id]?.includes('bottom'); + messageGroupStyles[previousMessage.id]?.includes('bottom') || + isEditedMessage(previousMessage); const isBottomMessage = !nextMessage || nextMessage.type === 'system' || - userId !== nextMessage?.user?.id || nextMessage.type === 'error' || + userId !== nextMessage?.user?.id || !!isNextMessageTypeDeleted || (!hideDateSeparators && dateSeparators[nextMessage.id]) || (maxTimeBetweenGroupedMessages !== undefined && nextMessage.created_at.getTime() - message.created_at.getTime() > - maxTimeBetweenGroupedMessages); + maxTimeBetweenGroupedMessages) || + isEditedMessage(message); /** * Add group styles key for top message diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap index 4854dc28f2..f60208cd68 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap @@ -466,7 +466,9 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { + "alignItems": "center", "flexDirection": "row", + "justifyContent": "center", "marginTop": 4, }, {}, @@ -478,12 +480,12 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { - "color": "#7A7A7A", - "textAlign": "left", + "fontSize": 12, }, { - "fontSize": 12, + "color": "#7A7A7A", }, + {}, ] } > @@ -744,7 +746,9 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { + "alignItems": "center", "flexDirection": "row", + "justifyContent": "center", "marginTop": 4, }, {}, @@ -756,12 +760,12 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { - "color": "#7A7A7A", - "textAlign": "left", + "fontSize": 12, }, { - "fontSize": 12, + "color": "#7A7A7A", }, + {}, ] } > @@ -1022,7 +1026,9 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { + "alignItems": "center", "flexDirection": "row", + "justifyContent": "center", "marginTop": 4, }, {}, @@ -1034,12 +1040,12 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { - "color": "#7A7A7A", - "textAlign": "left", + "fontSize": 12, }, { - "fontSize": 12, + "color": "#7A7A7A", }, + {}, ] } > @@ -1340,7 +1346,9 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { + "alignItems": "center", "flexDirection": "row", + "justifyContent": "center", "marginTop": 4, }, {}, @@ -1352,12 +1360,12 @@ exports[`Thread should match thread snapshot 1`] = ` style={ [ { - "color": "#7A7A7A", - "textAlign": "left", + "fontSize": 12, }, { - "fontSize": 12, + "color": "#7A7A7A", }, + {}, ] } > diff --git a/package/src/components/index.ts b/package/src/components/index.ts index dadfbeccce..eb3e342f27 100644 --- a/package/src/components/index.ts +++ b/package/src/components/index.ts @@ -99,6 +99,7 @@ export * from './Message/MessageSimple/MessageAvatar'; export * from './Message/MessageSimple/MessageBounce'; export * from './Message/MessageSimple/MessageContent'; export * from './Message/MessageSimple/MessageDeleted'; +export * from './Message/MessageSimple/MessageEditedTimestamp'; export * from './Message/MessageSimple/MessageError'; export * from './Message/MessageSimple/MessageFooter'; export * from './Message/MessageSimple/MessagePinnedHeader'; diff --git a/package/src/contexts/channelContext/ChannelContext.tsx b/package/src/contexts/channelContext/ChannelContext.tsx index de6ff2c3f8..e317e3cc6d 100644 --- a/package/src/contexts/channelContext/ChannelContext.tsx +++ b/package/src/contexts/channelContext/ChannelContext.tsx @@ -180,7 +180,7 @@ export type ChannelContextValue< * * **Default** [DateHeader](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageList/DateHeader.tsx) */ - StickyHeader?: React.ComponentType<{ dateString: string }>; + StickyHeader?: React.ComponentType<{ dateString: string | number }>; /** * Id of message, around which Channel/MessageList gets loaded when opened. * You will see a highlighted background for targetted message, when opened. diff --git a/package/src/contexts/messageContext/MessageContext.tsx b/package/src/contexts/messageContext/MessageContext.tsx index 0d18839e63..31b7d7a1b4 100644 --- a/package/src/contexts/messageContext/MessageContext.tsx +++ b/package/src/contexts/messageContext/MessageContext.tsx @@ -53,6 +53,8 @@ export type MessageContextValue< hasReactions: boolean; /** The images attached to a message */ images: Attachment[]; + /** Boolean that determines if the edited message is pressed. */ + isEditedMessageOpen: boolean; /** Whether or not this is the active user's message */ isMyMessage: boolean; /** Whether or not this is the last message in a group of messages */ @@ -91,6 +93,8 @@ export type MessageContextValue< /** The images attached to a message */ otherAttachments: Attachment[]; reactions: Reactions; + /** React set state function to set the state of `isEditedMessageOpen` */ + setIsEditedMessageOpen: React.Dispatch>; showMessageOverlay: (messageReactions?: boolean, error?: boolean) => void; showMessageStatus: boolean; /** Whether or not the Message is part of a Thread */ diff --git a/package/src/contexts/messagesContext/MessagesContext.tsx b/package/src/contexts/messagesContext/MessagesContext.tsx index f60ba4dec6..3f5dae13b9 100644 --- a/package/src/contexts/messagesContext/MessagesContext.tsx +++ b/package/src/contexts/messagesContext/MessagesContext.tsx @@ -24,6 +24,7 @@ import type { MessageAvatarProps } from '../../components/Message/MessageSimple/ import type { MessageBounceProps } from '../../components/Message/MessageSimple/MessageBounce'; import type { MessageContentProps } from '../../components/Message/MessageSimple/MessageContent'; import type { MessageDeletedProps } from '../../components/Message/MessageSimple/MessageDeleted'; +import type { MessageEditedTimestampProps } from '../../components/Message/MessageSimple/MessageEditedTimestamp'; import type { MessageErrorProps } from '../../components/Message/MessageSimple/MessageError'; import type { MessageFooterProps } from '../../components/Message/MessageSimple/MessageFooter'; import type { MessagePinnedHeaderProps } from '../../components/Message/MessageSimple/MessagePinnedHeader'; @@ -53,7 +54,6 @@ import type { Alignment } from '../messageContext/MessageContext'; import type { SuggestionCommand } from '../suggestionsContext/SuggestionsContext'; import type { DeepPartial } from '../themeContext/ThemeContext'; import type { Theme } from '../themeContext/utils/theme'; -import type { TDateTimeParserInput } from '../translationContext/TranslationContext'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { getDisplayName } from '../utils/getDisplayName'; @@ -173,6 +173,11 @@ export type MessagesContextValue< * Defaults to: [MessageDeleted](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageSimple/MessageDeleted.tsx) */ MessageDeleted: React.ComponentType>; + /** + * UI component for MessageEditedTimestamp + * Defaults to: [MessageEditedTimestamp](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageSimple/MessageEditedTimestamp.tsx) + */ + MessageEditedTimestamp: React.ComponentType; /** * UI component for the MessageError. */ @@ -304,10 +309,6 @@ export type MessagesContextValue< * sent messages will be aligned to right. */ forceAlignMessages?: Alignment | boolean; - /** - * Optional function to custom format the message date - */ - formatDate?: (date: TDateTimeParserInput) => string; getMessagesGroupStyles?: typeof getGroupStyles; handleBlock?: (message: MessageType) => Promise; /** Handler to access when a copy message action is invoked */ diff --git a/package/src/contexts/themeContext/utils/theme.ts b/package/src/contexts/themeContext/utils/theme.ts index 543babc400..891b7710b5 100644 --- a/package/src/contexts/themeContext/utils/theme.ts +++ b/package/src/contexts/themeContext/utils/theme.ts @@ -450,6 +450,8 @@ export type Theme = { deletedContainerInner: ViewStyle; deletedMetaText: TextStyle; deletedText: MarkdownStyle; + editedLabel: TextStyle; + editedTimestampContainer: ViewStyle; errorContainer: ViewStyle; errorIcon: IconProps; errorIconContainer: ViewStyle; @@ -470,6 +472,7 @@ export type Theme = { wrapper: ViewStyle; receiverMessageBackgroundColor?: ColorValue; senderMessageBackgroundColor?: ColorValue; + timestampText?: TextStyle; }; file: { container: ViewStyle; @@ -1021,6 +1024,8 @@ export const defaultTheme: Theme = { fontWeight: '400', }, }, + editedLabel: {}, + editedTimestampContainer: {}, errorContainer: { paddingRight: 12, paddingTop: 0, @@ -1053,6 +1058,7 @@ export const defaultTheme: Theme = { textContainer: { onlyEmojiMarkdown: { text: { fontSize: 50 } }, }, + timestampText: {}, wrapper: {}, }, file: { diff --git a/package/src/i18n/en.json b/package/src/i18n/en.json index f35f9ec53f..2452609a40 100644 --- a/package/src/i18n/en.json +++ b/package/src/i18n/en.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "Device camera is used to take photos or videos.", "Do you want to send a copy of this message to a moderator for further investigation?": "Do you want to send a copy of this message to a moderator for further investigation?", "Edit Message": "Edit Message", + "Edited": "Edited", "Editing Message": "Editing Message", "Emoji matching": "Emoji matching", "Empty message...": "Empty message...", diff --git a/package/src/i18n/es.json b/package/src/i18n/es.json index 9ec9d6a767..76d2619d1f 100644 --- a/package/src/i18n/es.json +++ b/package/src/i18n/es.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "La cámara del dispositivo se utiliza para tomar fotografías o vídeos.", "Do you want to send a copy of this message to a moderator for further investigation?": "¿Deseas enviar una copia de este mensaje a un moderador para una investigación adicional?", "Edit Message": "Editar mensaje", + "Edited": "Editado", "Editing Message": "Editando mensaje", "Emoji matching": "Coincidencia de emoji", "Empty message...": "Mensaje vacío...", diff --git a/package/src/i18n/fr.json b/package/src/i18n/fr.json index f82f036481..80418fb92f 100644 --- a/package/src/i18n/fr.json +++ b/package/src/i18n/fr.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "L'appareil photo de l'appareil est utilisé pour prendre des photos ou des vidéos.", "Do you want to send a copy of this message to a moderator for further investigation?": "Voulez-vous envoyer une copie de ce message à un modérateur pour une enquête plus approfondie?", "Edit Message": "Éditer un message", + "Edited": "Édité", "Editing Message": "Édite un message", "Emoji matching": "Correspondance Emoji", "Empty message...": "Message vide...", diff --git a/package/src/i18n/he.json b/package/src/i18n/he.json index e0188119f4..176ccac631 100644 --- a/package/src/i18n/he.json +++ b/package/src/i18n/he.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "מצלמת המכשיר משמשת לצילום תמונות או סרטונים.", "Do you want to send a copy of this message to a moderator for further investigation?": "האם את/ה רוצה לשלוח עותק של הודעה זו למנחה להמשך חקירה?", "Edit Message": "ערוך הודעה", + "Edited": "נערך", "Editing Message": "הודעה בעריכה", "Emoji matching": "התאמת אמוג'י", "Empty message...": "הודעה ריקה...", diff --git a/package/src/i18n/hi.json b/package/src/i18n/hi.json index 659fef72f4..dd3f53b93a 100644 --- a/package/src/i18n/hi.json +++ b/package/src/i18n/hi.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "डिवाइस कैमरे का उपयोग फ़ोटो या वीडियो लेने के लिए किया जाता है।", "Do you want to send a copy of this message to a moderator for further investigation?": "क्या आप इस संदेश की एक प्रति आगे की जाँच के लिए किसी मॉडरेटर को भेजना चाहते हैं?", "Edit Message": "मैसेज में बदलाव करे", + "Edited": "मैसेज बदला गया है", "Editing Message": "मैसेज बदला जा रहा है", "Emoji matching": "इमोजी मिलान", "Empty message...": "खाली संदेश...", diff --git a/package/src/i18n/it.json b/package/src/i18n/it.json index 37e7a00c87..337aa74b0d 100644 --- a/package/src/i18n/it.json +++ b/package/src/i18n/it.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "La fotocamera del dispositivo viene utilizzata per scattare foto o video.", "Do you want to send a copy of this message to a moderator for further investigation?": "Vuoi inviare una copia di questo messaggio a un moderatore per ulteriori indagini?", "Edit Message": "Modifica Messaggio", + "Edited": "Modificato", "Editing Message": "Modificando il Messaggio", "Emoji matching": "Abbinamento emoji", "Empty message...": "Message vuoto...", diff --git a/package/src/i18n/ja.json b/package/src/i18n/ja.json index 22f46e86d1..2a48186e62 100644 --- a/package/src/i18n/ja.json +++ b/package/src/i18n/ja.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "デバイスのカメラは写真やビデオの撮影に使用されます。", "Do you want to send a copy of this message to a moderator for further investigation?": "このメッセージのコピーをモデレーターに送信して、さらに調査しますか?", "Edit Message": "メッセージを編集", + "Edited": "編集済み", "Editing Message": "メッセージを編集中", "Emoji matching": "絵文字マッチング", "Empty message...": "空のメッセージ...", diff --git a/package/src/i18n/ko.json b/package/src/i18n/ko.json index c88e2eac81..940d832c6c 100644 --- a/package/src/i18n/ko.json +++ b/package/src/i18n/ko.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "기기 카메라는 사진이나 동영상을 촬영하는 데 사용됩니다.", "Do you want to send a copy of this message to a moderator for further investigation?": "이 메시지의 복사본을 운영자에게 보내 추가 조사를합니까?", "Edit Message": "메시지 수정", + "Edited": "편집됨", "Editing Message": "메시지 편집중", "Emoji matching": "이모티콘 매칭", "Empty message...": "빈 메시지...", diff --git a/package/src/i18n/nl.json b/package/src/i18n/nl.json index 07669fe016..cd2c54442e 100644 --- a/package/src/i18n/nl.json +++ b/package/src/i18n/nl.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "De camera van het apparaat wordt gebruikt om foto's of video's te maken.", "Do you want to send a copy of this message to a moderator for further investigation?": "Wil je een kopie van dit bericht naar een moderator sturen voor verder onderzoek?", "Edit Message": "Pas bericht aan", + "Edited": "Bewerkt", "Editing Message": "Bericht aanpassen", "Emoji matching": "Emoji-overeenkomsten", "Empty message...": "Leeg bericht...", diff --git a/package/src/i18n/pt-BR.json b/package/src/i18n/pt-BR.json index d16ee209b5..d8eca5a390 100644 --- a/package/src/i18n/pt-BR.json +++ b/package/src/i18n/pt-BR.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "A câmera do dispositivo é usada para tirar fotos ou vídeos.", "Do you want to send a copy of this message to a moderator for further investigation?": "Deseja enviar uma cópia desta mensagem para um moderador para investigação adicional?", "Edit Message": "Editar Mensagem", + "Edited": "Editado", "Editing Message": "Editando Mensagem", "Emoji matching": "Correspondência de Emoji", "Empty message...": "Mensagem vazia...", diff --git a/package/src/i18n/ru.json b/package/src/i18n/ru.json index 97fa55e433..82f2f20142 100644 --- a/package/src/i18n/ru.json +++ b/package/src/i18n/ru.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "Камера устройства используется для съемки фотографий или видео.", "Do you want to send a copy of this message to a moderator for further investigation?": "Вы хотите отправить копию этого сообщения модератору для дальнейшего изучения?", "Edit Message": "Редактировать сообщение", + "Edited": "Отредактировано", "Editing Message": "Редактирование сообщения", "Emoji matching": "Соответствие эмодзи", "Empty message...": "Пустое сообщение...", diff --git a/package/src/i18n/tr.json b/package/src/i18n/tr.json index 53c8952605..5d8ad405b6 100644 --- a/package/src/i18n/tr.json +++ b/package/src/i18n/tr.json @@ -16,6 +16,7 @@ "Device camera is used to take photos or videos.": "Cihaz kamerası fotoğraf veya video çekmek için kullanılır.", "Do you want to send a copy of this message to a moderator for further investigation?": "Detaylı inceleme için bu mesajın kopyasını moderatöre göndermek istiyor musunuz?", "Edit Message": "Mesajı Düzenle", + "Edited": "Düzenlendi", "Editing Message": "Mesaj Düzenleniyor", "Emoji matching": "Emoji eşleştirme", "Empty message...": "Boş mesaj...", diff --git a/package/src/utils/getDateString.ts b/package/src/utils/getDateString.ts new file mode 100644 index 0000000000..b92b24058e --- /dev/null +++ b/package/src/utils/getDateString.ts @@ -0,0 +1,67 @@ +import { + isDayOrMoment, + TDateTimeParser, + TDateTimeParserInput, +} from '../contexts/translationContext/TranslationContext'; + +interface DateFormatterOptions { + /** + * Whether to show the time in Calendar time format. Calendar time displays time relative to a today's date. + */ + calendar?: boolean; + /** + * The timestamp to be formatted. + */ + date?: string | Date; + /** + * The format in which the date should be displayed. + */ + format?: string; + /** + * A function to format the date. + */ + formatDate?: (date: TDateTimeParserInput) => string; + /** + * The datetime parsing function. + */ + tDateTimeParser?: TDateTimeParser; +} + +export const noParsingFunctionWarning = + 'MessageTimestamp was called but there is no datetime parsing function available'; + +/** + * Utility funcyion to format the date string. + */ +export function getDateString({ + calendar, + date, + format, + formatDate, + tDateTimeParser, +}: DateFormatterOptions): string | number | null { + if (!date || (typeof date === 'string' && !Date.parse(date))) { + return null; + } + + if (typeof formatDate === 'function') { + return formatDate(new Date(date)); + } + + if (!tDateTimeParser) { + console.log(noParsingFunctionWarning); + return null; + } + + const parsedTime = tDateTimeParser(date); + + if (isDayOrMoment(parsedTime)) { + /** + * parsedTime.calendar is guaranteed on the type but is only + * available when a user calls dayjs.extend(calendar) + */ + return calendar && parsedTime.calendar ? parsedTime.calendar() : parsedTime.format(format); + } + + return new Date(date).toDateString(); +} diff --git a/package/src/utils/utils.ts b/package/src/utils/utils.ts index f04bc1c080..e7e04fb03f 100644 --- a/package/src/utils/utils.ts +++ b/package/src/utils/utils.ts @@ -113,6 +113,17 @@ export const isBouncedMessage = < message: MessageType, ) => message.type === 'error' && message.moderation_details !== undefined; +/** + * Utility to check if the message is a edited message. + * @param message + * @returns boolean + */ +export const isEditedMessage = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + message: MessageType, +) => !!message.message_text_updated_at; + const defaultAutoCompleteSuggestionsLimit = 10; const defaultMentionAllAppUsersQuery = { filters: {}, diff --git a/package/yarn.lock b/package/yarn.lock index 1fef6845ba..320f74906e 100644 --- a/package/yarn.lock +++ b/package/yarn.lock @@ -10138,10 +10138,10 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-chat@8.17.0: - version "8.17.0" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.17.0.tgz#01c4aacbcdb5dd734b088e70f40cd42a0bcd0fb7" - integrity sha512-0cYKSroKGiLilElk8Ol6AKAowWIIpXz3wsY97o+cAqixOwwHdnbuPZ00L2CzAjNB2c94Vl5L48n1K+9iOEpv3w== +stream-chat@8.31.0: + version "8.31.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.31.0.tgz#387ed3109ac930e222bf260d98afc37dd1fcb1ac" + integrity sha512-lORUtfDYljoAgRv7QHUADH1QTGydSWo+U8KmUKwv7BZyUZ2JGwh6VZbu9WL4Nmo7PDU7N/Ug0Q5PE59S6WE+OA== dependencies: "@babel/runtime" "^7.16.3" "@types/jsonwebtoken" "~9.0.0" From 851d3148555fc2eaf9927271089b56f6c4bde02d Mon Sep 17 00:00:00 2001 From: ohayoyogi <77435669+ohayoyogi@users.noreply.github.com> Date: Thu, 16 May 2024 00:46:42 +0900 Subject: [PATCH 6/7] Fix: channel.lastRead() returns null after posting a message (#2480) * create copy of channel.state.read * run lint-fix --- .../components/ChannelPreview/hooks/useLatestMessagePreview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts b/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts index 24af947d98..0238cf9f5f 100644 --- a/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +++ b/package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts @@ -166,7 +166,7 @@ const getLatestMessageReadStatus = < return MessageReadStatus.NOT_SENT_BY_CURRENT_USER; } - const readList = channel.state.read; + const readList = { ...channel.state.read }; if (currentUserId) { delete readList[currentUserId]; } From a9a308a58d596b923c110821b6b3e266b4607d5b Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Thu, 16 May 2024 15:29:56 +0530 Subject: [PATCH 7/7] fix: remove redundant console.logs from Audio.ts --- package/expo-package/src/handlers/Audio.ts | 3 --- .../src/optionalDependencies/Audio.ts | 14 ++++++-------- .../MessageInput/hooks/useAudioController.tsx | 8 +++----- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/package/expo-package/src/handlers/Audio.ts b/package/expo-package/src/handlers/Audio.ts index f67c3fcb94..5283131e8f 100644 --- a/package/expo-package/src/handlers/Audio.ts +++ b/package/expo-package/src/handlers/Audio.ts @@ -216,7 +216,6 @@ export const Audio = AudioComponent ? { startRecording: async (recordingOptions: RecordingOptions, onRecordingStatusUpdate) => { try { - console.log('Requesting permissions..'); const permissionsGranted = await AudioComponent.getPermissionsAsync().granted; if (!permissionsGranted) { await AudioComponent.requestPermissionsAsync(); @@ -225,7 +224,6 @@ export const Audio = AudioComponent allowsRecordingIOS: true, playsInSilentModeIOS: true, }); - console.log('Starting recording..'); const androidOptions = { audioEncoder: AndroidAudioEncoder.AAC, extension: '.aac', @@ -258,7 +256,6 @@ export const Audio = AudioComponent }, stopRecording: async () => { try { - console.log('Stopping recording..'); await AudioComponent.setAudioModeAsync({ allowsRecordingIOS: false, }); diff --git a/package/native-package/src/optionalDependencies/Audio.ts b/package/native-package/src/optionalDependencies/Audio.ts index 4ed2c36f28..7de216b604 100644 --- a/package/native-package/src/optionalDependencies/Audio.ts +++ b/package/native-package/src/optionalDependencies/Audio.ts @@ -171,16 +171,13 @@ const verifyAndroidPermissions = async () => { export const Audio = AudioRecorderPackage ? { pausePlayer: async () => { - console.log('Pause Player..'); await audioRecorderPlayer.pausePlayer(); }, resumePlayer: async () => { - console.log('Resume Player..'); await audioRecorderPlayer.resumePlayer(); }, startPlayer: async (uri, _, onPlaybackStatusUpdate) => { try { - console.log('Starting Player..'); const playback = await audioRecorderPlayer.startPlayer(uri); console.log({ playback }); audioRecorderPlayer.addPlayBackListener((status) => { @@ -191,7 +188,6 @@ export const Audio = AudioRecorderPackage } }, startRecording: async (options: RecordingOptions, onRecordingStatusUpdate) => { - console.log('Starting recording..'); if (Platform.OS === 'android') { try { await verifyAndroidPermissions(); @@ -230,12 +226,14 @@ export const Audio = AudioRecorderPackage } }, stopPlayer: async () => { - console.log('Stopping player..'); - await audioRecorderPlayer.stopPlayer(); - audioRecorderPlayer.removePlayBackListener(); + try { + await audioRecorderPlayer.stopPlayer(); + audioRecorderPlayer.removePlayBackListener(); + } catch (error) { + console.log(error); + } }, stopRecording: async () => { - console.log('Stopping recording..'); await audioRecorderPlayer.stopRecorder(); audioRecorderPlayer.removeRecordBackListener(); }, diff --git a/package/src/components/MessageInput/hooks/useAudioController.tsx b/package/src/components/MessageInput/hooks/useAudioController.tsx index 0708c2c403..917336f606 100644 --- a/package/src/components/MessageInput/hooks/useAudioController.tsx +++ b/package/src/components/MessageInput/hooks/useAudioController.tsx @@ -138,11 +138,9 @@ export const useAudioController = () => { await Audio.stopPlayer(); } // For Expo CLI - if (recording && typeof recording !== 'string') { - if (soundRef.current?.stopAsync && soundRef.current?.unloadAsync) { - await soundRef.current.stopAsync(); - await soundRef.current?.unloadAsync(); - } + if (soundRef.current?.stopAsync && soundRef.current?.unloadAsync) { + await soundRef.current.stopAsync(); + await soundRef.current?.unloadAsync(); } };