stream-chat-react-native-core 8.7.1-beta.2 → 8.8.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/lib/commonjs/components/ChannelPreview/ChannelPreviewStatus.js +6 -5
  2. package/lib/commonjs/components/ChannelPreview/ChannelPreviewStatus.js.map +1 -1
  3. package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewData.js.map +1 -1
  4. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js +13 -22
  5. package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  6. package/lib/commonjs/components/ChannelPreview/hooks/useMessageDeliveryStatus.js +84 -0
  7. package/lib/commonjs/components/ChannelPreview/hooks/useMessageDeliveryStatus.js.map +1 -0
  8. package/lib/commonjs/components/Message/Message.js +18 -6
  9. package/lib/commonjs/components/Message/Message.js.map +1 -1
  10. package/lib/commonjs/components/Message/MessageSimple/MessageStatus.js +61 -51
  11. package/lib/commonjs/components/Message/MessageSimple/MessageStatus.js.map +1 -1
  12. package/lib/commonjs/components/Message/MessageSimple/MessageTextContainer.js.map +1 -1
  13. package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js +3 -1
  14. package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js.map +1 -1
  15. package/lib/commonjs/components/Message/hooks/useMessageDeliveryData.js +43 -0
  16. package/lib/commonjs/components/Message/hooks/useMessageDeliveryData.js.map +1 -0
  17. package/lib/commonjs/components/Message/hooks/useMessageReadData.js +43 -0
  18. package/lib/commonjs/components/Message/hooks/useMessageReadData.js.map +1 -0
  19. package/lib/commonjs/components/index.js +15 -4
  20. package/lib/commonjs/components/index.js.map +1 -1
  21. package/lib/commonjs/contexts/messageContext/MessageContext.js.map +1 -1
  22. package/lib/commonjs/hooks/useTranslatedMessage.js.map +1 -1
  23. package/lib/commonjs/store/mappers/mapReadToStorable.js +5 -1
  24. package/lib/commonjs/store/mappers/mapReadToStorable.js.map +1 -1
  25. package/lib/commonjs/store/mappers/mapStorableToRead.js +5 -1
  26. package/lib/commonjs/store/mappers/mapStorableToRead.js.map +1 -1
  27. package/lib/commonjs/store/schema.js +2 -0
  28. package/lib/commonjs/store/schema.js.map +1 -1
  29. package/lib/commonjs/types/types.js +12 -1
  30. package/lib/commonjs/types/types.js.map +1 -1
  31. package/lib/commonjs/utils/utils.js +1 -0
  32. package/lib/commonjs/utils/utils.js.map +1 -1
  33. package/lib/commonjs/version.json +1 -1
  34. package/lib/module/components/ChannelPreview/ChannelPreviewStatus.js +6 -5
  35. package/lib/module/components/ChannelPreview/ChannelPreviewStatus.js.map +1 -1
  36. package/lib/module/components/ChannelPreview/hooks/useChannelPreviewData.js.map +1 -1
  37. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js +13 -22
  38. package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
  39. package/lib/module/components/ChannelPreview/hooks/useMessageDeliveryStatus.js +84 -0
  40. package/lib/module/components/ChannelPreview/hooks/useMessageDeliveryStatus.js.map +1 -0
  41. package/lib/module/components/Message/Message.js +18 -6
  42. package/lib/module/components/Message/Message.js.map +1 -1
  43. package/lib/module/components/Message/MessageSimple/MessageStatus.js +61 -51
  44. package/lib/module/components/Message/MessageSimple/MessageStatus.js.map +1 -1
  45. package/lib/module/components/Message/MessageSimple/MessageTextContainer.js.map +1 -1
  46. package/lib/module/components/Message/hooks/useCreateMessageContext.js +3 -1
  47. package/lib/module/components/Message/hooks/useCreateMessageContext.js.map +1 -1
  48. package/lib/module/components/Message/hooks/useMessageDeliveryData.js +43 -0
  49. package/lib/module/components/Message/hooks/useMessageDeliveryData.js.map +1 -0
  50. package/lib/module/components/Message/hooks/useMessageReadData.js +43 -0
  51. package/lib/module/components/Message/hooks/useMessageReadData.js.map +1 -0
  52. package/lib/module/components/index.js +15 -4
  53. package/lib/module/components/index.js.map +1 -1
  54. package/lib/module/contexts/messageContext/MessageContext.js.map +1 -1
  55. package/lib/module/hooks/useTranslatedMessage.js.map +1 -1
  56. package/lib/module/store/mappers/mapReadToStorable.js +5 -1
  57. package/lib/module/store/mappers/mapReadToStorable.js.map +1 -1
  58. package/lib/module/store/mappers/mapStorableToRead.js +5 -1
  59. package/lib/module/store/mappers/mapStorableToRead.js.map +1 -1
  60. package/lib/module/store/schema.js +2 -0
  61. package/lib/module/store/schema.js.map +1 -1
  62. package/lib/module/types/types.js +12 -1
  63. package/lib/module/types/types.js.map +1 -1
  64. package/lib/module/utils/utils.js +1 -0
  65. package/lib/module/utils/utils.js.map +1 -1
  66. package/lib/module/version.json +1 -1
  67. package/lib/typescript/components/ChannelPreview/ChannelPreviewStatus.d.ts.map +1 -1
  68. package/lib/typescript/components/ChannelPreview/hooks/useChannelPreviewData.d.ts +12 -4
  69. package/lib/typescript/components/ChannelPreview/hooks/useChannelPreviewData.d.ts.map +1 -1
  70. package/lib/typescript/components/ChannelPreview/hooks/useLatestMessagePreview.d.ts +21 -10
  71. package/lib/typescript/components/ChannelPreview/hooks/useLatestMessagePreview.d.ts.map +1 -1
  72. package/lib/typescript/components/ChannelPreview/hooks/useMessageDeliveryStatus.d.ts +17 -0
  73. package/lib/typescript/components/ChannelPreview/hooks/useMessageDeliveryStatus.d.ts.map +1 -0
  74. package/lib/typescript/components/Message/Message.d.ts +1 -1
  75. package/lib/typescript/components/Message/Message.d.ts.map +1 -1
  76. package/lib/typescript/components/Message/MessageSimple/MessageStatus.d.ts +3 -1
  77. package/lib/typescript/components/Message/MessageSimple/MessageStatus.d.ts.map +1 -1
  78. package/lib/typescript/components/Message/hooks/useCreateMessageContext.d.ts +1 -1
  79. package/lib/typescript/components/Message/hooks/useCreateMessageContext.d.ts.map +1 -1
  80. package/lib/typescript/components/Message/hooks/useMessageDeliveryData.d.ts +5 -0
  81. package/lib/typescript/components/Message/hooks/useMessageDeliveryData.d.ts.map +1 -0
  82. package/lib/typescript/components/Message/hooks/useMessageReadData.d.ts +5 -0
  83. package/lib/typescript/components/Message/hooks/useMessageReadData.d.ts.map +1 -0
  84. package/lib/typescript/components/index.d.ts +2 -1
  85. package/lib/typescript/components/index.d.ts.map +1 -1
  86. package/lib/typescript/contexts/messageContext/MessageContext.d.ts +3 -1
  87. package/lib/typescript/contexts/messageContext/MessageContext.d.ts.map +1 -1
  88. package/lib/typescript/hooks/useTranslatedMessage.d.ts +2 -61
  89. package/lib/typescript/hooks/useTranslatedMessage.d.ts.map +1 -1
  90. package/lib/typescript/store/mappers/mapReadToStorable.d.ts.map +1 -1
  91. package/lib/typescript/store/mappers/mapStorableToRead.d.ts.map +1 -1
  92. package/lib/typescript/store/schema.d.ts +2 -0
  93. package/lib/typescript/store/schema.d.ts.map +1 -1
  94. package/lib/typescript/types/types.d.ts +11 -1
  95. package/lib/typescript/types/types.d.ts.map +1 -1
  96. package/lib/typescript/utils/utils.d.ts +1 -0
  97. package/lib/typescript/utils/utils.d.ts.map +1 -1
  98. package/package.json +1 -1
  99. package/src/components/ChannelPreview/ChannelPreviewStatus.tsx +7 -4
  100. package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts +7 -3
  101. package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +28 -47
  102. package/src/components/ChannelPreview/hooks/useMessageDeliveryStatus.ts +103 -0
  103. package/src/components/Message/Message.tsx +25 -5
  104. package/src/components/Message/MessageSimple/MessageStatus.tsx +78 -59
  105. package/src/components/Message/MessageSimple/MessageTextContainer.tsx +2 -2
  106. package/src/components/Message/MessageSimple/__tests__/MessageStatus.test.js +26 -30
  107. package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageStatus.test.js.snap +3 -2
  108. package/src/components/Message/hooks/useCreateMessageContext.ts +3 -0
  109. package/src/components/Message/hooks/useMessageDeliveryData.ts +37 -0
  110. package/src/components/Message/hooks/useMessageReadData.ts +38 -0
  111. package/src/components/index.ts +2 -1
  112. package/src/contexts/messageContext/MessageContext.tsx +3 -1
  113. package/src/hooks/useTranslatedMessage.ts +2 -2
  114. package/src/store/mappers/mapReadToStorable.ts +10 -1
  115. package/src/store/mappers/mapStorableToRead.ts +10 -1
  116. package/src/store/schema.ts +4 -0
  117. package/src/types/types.ts +14 -9
  118. package/src/utils/utils.ts +1 -0
  119. package/src/version.json +1 -1
  120. package/lib/commonjs/components/MessageList/utils/getReadState.js +0 -20
  121. package/lib/commonjs/components/MessageList/utils/getReadState.js.map +0 -1
  122. package/lib/module/components/MessageList/utils/getReadState.js +0 -20
  123. package/lib/module/components/MessageList/utils/getReadState.js.map +0 -1
  124. package/lib/typescript/components/MessageList/utils/getReadState.d.ts +0 -9
  125. package/lib/typescript/components/MessageList/utils/getReadState.d.ts.map +0 -1
  126. package/src/components/MessageList/utils/getReadState.ts +0 -27
@@ -4,9 +4,8 @@ import { TFunction } from 'i18next';
4
4
  import type {
5
5
  AttachmentManagerState,
6
6
  Channel,
7
- ChannelState,
8
7
  DraftMessage,
9
- MessageResponse,
8
+ LocalMessage,
10
9
  PollState,
11
10
  PollVote,
12
11
  StreamChat,
@@ -14,6 +13,8 @@ import type {
14
13
  UserResponse,
15
14
  } from 'stream-chat';
16
15
 
16
+ import { MessageDeliveryStatus, useMessageDeliveryStatus } from './useMessageDeliveryStatus';
17
+
17
18
  import { useChatContext } from '../../../contexts/chatContext/ChatContext';
18
19
  import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext';
19
20
 
@@ -22,16 +23,14 @@ import { useTranslatedMessage } from '../../../hooks/useTranslatedMessage';
22
23
 
23
24
  import { stringifyMessage } from '../../../utils/utils';
24
25
 
25
- type LatestMessage = ReturnType<ChannelState['formatMessage']> | MessageResponse;
26
-
27
26
  export type LatestMessagePreview = {
28
- messageObject: LatestMessage | undefined;
27
+ messageObject: LocalMessage | undefined;
29
28
  previews: {
30
29
  bold: boolean;
31
30
  text: string;
32
31
  draft?: boolean;
33
32
  }[];
34
- status: number;
33
+ status?: MessageDeliveryStatus;
35
34
  created_at?: string | Date;
36
35
  };
37
36
 
@@ -48,7 +47,7 @@ const selector = (nextValue: PollState): LatestMessagePreviewSelectorReturnType
48
47
  });
49
48
 
50
49
  const getMessageSenderName = (
51
- message: LatestMessage | undefined,
50
+ message: LocalMessage | undefined,
52
51
  currentUserId: string | undefined,
53
52
  t: (key: string) => string,
54
53
  membersLength: number,
@@ -87,7 +86,7 @@ const getLatestMessageDisplayText = (
87
86
  channel: Channel,
88
87
  client: StreamChat,
89
88
  draftMessage: DraftMessage | undefined,
90
- message: LatestMessage | undefined,
89
+ message: LocalMessage | undefined,
91
90
  t: (key: string) => string,
92
91
  pollState: LatestMessagePreviewSelectorReturnType | undefined,
93
92
  ) => {
@@ -194,47 +193,23 @@ export enum MessageReadStatus {
194
193
  NOT_SENT_BY_CURRENT_USER = 0,
195
194
  UNREAD = 1,
196
195
  READ = 2,
196
+ DELIVERED = 3,
197
197
  }
198
198
 
199
- const getLatestMessageReadStatus = (
200
- channel: Channel,
201
- client: StreamChat,
202
- message: LatestMessage | undefined,
203
- readEvents: boolean,
204
- ): MessageReadStatus => {
205
- const currentUserId = client.userID;
206
- if (!message || currentUserId !== message.user?.id || readEvents === false) {
207
- return MessageReadStatus.NOT_SENT_BY_CURRENT_USER;
208
- }
209
-
210
- const readList = { ...channel.state.read };
211
- if (currentUserId) {
212
- delete readList[currentUserId];
213
- }
214
-
215
- const messageUpdatedAt = message.updated_at
216
- ? typeof message.updated_at === 'string'
217
- ? new Date(message.updated_at)
218
- : message.updated_at
219
- : undefined;
220
-
221
- return Object.values(readList).some(
222
- ({ last_read }) => messageUpdatedAt && messageUpdatedAt < last_read,
223
- )
224
- ? MessageReadStatus.READ
225
- : MessageReadStatus.UNREAD;
226
- };
227
-
228
199
  const getLatestMessagePreview = (params: {
229
200
  channel: Channel;
230
201
  client: StreamChat;
231
202
  draftMessage?: DraftMessage;
232
203
  pollState: LatestMessagePreviewSelectorReturnType | undefined;
233
- readEvents: boolean;
204
+ /**
205
+ * @deprecated This parameter is no longer used and will be removed in the next major release.
206
+ */
207
+ readEvents?: boolean;
208
+ lastMessage?: LocalMessage;
209
+ status?: MessageDeliveryStatus;
234
210
  t: TFunction;
235
- lastMessage?: ReturnType<ChannelState['formatMessage']> | MessageResponse;
236
211
  }) => {
237
- const { channel, client, draftMessage, lastMessage, pollState, readEvents, t } = params;
212
+ const { channel, client, draftMessage, lastMessage, pollState, status, t } = params;
238
213
 
239
214
  const messages = channel.state.messages;
240
215
 
@@ -248,7 +223,7 @@ const getLatestMessagePreview = (params: {
248
223
  text: t('Nothing yet...'),
249
224
  },
250
225
  ],
251
- status: MessageReadStatus.NOT_SENT_BY_CURRENT_USER,
226
+ status: MessageDeliveryStatus.NOT_SENT_BY_CURRENT_USER,
252
227
  };
253
228
  }
254
229
 
@@ -260,7 +235,7 @@ const getLatestMessagePreview = (params: {
260
235
  created_at: message?.created_at,
261
236
  messageObject: message,
262
237
  previews: getLatestMessageDisplayText(channel, client, draftMessage, message, t, pollState),
263
- status: getLatestMessageReadStatus(channel, client, message, readEvents),
238
+ status,
264
239
  };
265
240
  };
266
241
 
@@ -275,6 +250,9 @@ const stateSelector = (state: AttachmentManagerState) => ({
275
250
  /**
276
251
  * Hook to set the display preview for latest message on channel.
277
252
  *
253
+ * FIXME: This hook is very poorly implemented and needs to be refactored with granular hooks
254
+ * to avoid unnecessary re-renders and to make the code more readable.
255
+ *
278
256
  * @param {*} channel Channel object
279
257
  *
280
258
  * @returns {object} latest message preview e.g.. { text: 'this was last message ...', created_at: '11/12/2020', messageObject: { originalMessageObject } }
@@ -282,7 +260,7 @@ const stateSelector = (state: AttachmentManagerState) => ({
282
260
  export const useLatestMessagePreview = (
283
261
  channel: Channel,
284
262
  forceUpdate: number,
285
- lastMessage?: ReturnType<ChannelState['formatMessage']> | MessageResponse,
263
+ lastMessage?: LocalMessage,
286
264
  ) => {
287
265
  const { client } = useChatContext();
288
266
  const { t } = useTranslationContext();
@@ -328,7 +306,11 @@ export const useLatestMessagePreview = (
328
306
  return read_events;
329
307
  }, [channelConfigExists, channel]);
330
308
 
331
- const readStatus = getLatestMessageReadStatus(channel, client, translatedLastMessage, readEvents);
309
+ const { status } = useMessageDeliveryStatus({
310
+ channel,
311
+ isReadEventsEnabled: readEvents,
312
+ lastMessage: lastMessage as LocalMessage,
313
+ });
332
314
 
333
315
  const pollId = lastMessage?.poll_id ?? '';
334
316
  const poll = client.polls.fromState(pollId);
@@ -343,16 +325,15 @@ export const useLatestMessagePreview = (
343
325
  draftMessage,
344
326
  lastMessage: translatedLastMessage,
345
327
  pollState,
346
- readEvents,
328
+ status,
347
329
  t,
348
330
  });
349
331
  // eslint-disable-next-line react-hooks/exhaustive-deps
350
332
  }, [
351
333
  channelLastMessageString,
334
+ status,
352
335
  draftMessage,
353
336
  forceUpdate,
354
- readEvents,
355
- readStatus,
356
337
  latestVotesByOption,
357
338
  createdBy,
358
339
  name,
@@ -0,0 +1,103 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+
3
+ import { Channel, Event, LocalMessage, MessageResponse } from 'stream-chat';
4
+
5
+ import { useChatContext } from '../../../contexts/chatContext/ChatContext';
6
+
7
+ export enum MessageDeliveryStatus {
8
+ NOT_SENT_BY_CURRENT_USER = 'not_sent_by_current_user',
9
+ DELIVERED = 'delivered',
10
+ READ = 'read',
11
+ SENT = 'sent',
12
+ }
13
+
14
+ type MessageDeliveryStatusProps = {
15
+ channel: Channel;
16
+ lastMessage: LocalMessage;
17
+ isReadEventsEnabled: boolean;
18
+ };
19
+
20
+ export const useMessageDeliveryStatus = ({
21
+ channel,
22
+ lastMessage,
23
+ isReadEventsEnabled = true,
24
+ }: MessageDeliveryStatusProps) => {
25
+ const { client } = useChatContext();
26
+ const [status, setStatus] = useState<MessageDeliveryStatus | undefined>(undefined);
27
+
28
+ const isOwnMessage = useCallback(
29
+ (message: LocalMessage | MessageResponse) =>
30
+ client.user && message && message.user?.id === client.user.id,
31
+ [client],
32
+ );
33
+
34
+ useEffect(() => {
35
+ if (!lastMessage) {
36
+ setStatus(undefined);
37
+ }
38
+
39
+ if (!isReadEventsEnabled) {
40
+ setStatus(MessageDeliveryStatus.NOT_SENT_BY_CURRENT_USER);
41
+ return;
42
+ }
43
+
44
+ if (!lastMessage?.created_at || !isOwnMessage(lastMessage)) {
45
+ return;
46
+ }
47
+
48
+ const msgRef = {
49
+ msgId: lastMessage.id,
50
+ timestampMs: new Date(lastMessage.created_at).getTime(),
51
+ };
52
+
53
+ const readerOfMessage = channel.messageReceiptsTracker.readersForMessage(msgRef);
54
+ const deliveredForMessage = channel.messageReceiptsTracker.deliveredForMessage(msgRef);
55
+
56
+ setStatus(
57
+ readerOfMessage.length > 1 ||
58
+ (readerOfMessage.length === 1 && readerOfMessage[0].id !== client.user?.id)
59
+ ? MessageDeliveryStatus.READ
60
+ : deliveredForMessage.length > 1 ||
61
+ (deliveredForMessage.length === 1 && deliveredForMessage[0].id !== client.user?.id)
62
+ ? MessageDeliveryStatus.DELIVERED
63
+ : MessageDeliveryStatus.SENT,
64
+ );
65
+ }, [channel, client.user?.id, isOwnMessage, isReadEventsEnabled, lastMessage]);
66
+
67
+ useEffect(() => {
68
+ const handleMessageNew = (event: Event) => {
69
+ // the last message is not mine, so do not show the delivery status
70
+ if (event.message && !isOwnMessage(event.message)) {
71
+ return setStatus(undefined);
72
+ }
73
+ return setStatus(MessageDeliveryStatus.SENT);
74
+ };
75
+ const { unsubscribe } = channel.on('message.new', handleMessageNew);
76
+ return unsubscribe;
77
+ }, [channel, isOwnMessage]);
78
+
79
+ useEffect(() => {
80
+ if (!isOwnMessage(lastMessage)) return;
81
+ const handleMessageDelivered = (event: Event) => {
82
+ if (
83
+ event.user?.id !== client.user?.id &&
84
+ lastMessage &&
85
+ lastMessage.id === event.last_delivered_message_id
86
+ )
87
+ setStatus(MessageDeliveryStatus.DELIVERED);
88
+ };
89
+
90
+ const handleMarkRead = (event: Event) => {
91
+ if (event.user?.id !== client.user?.id) setStatus(MessageDeliveryStatus.READ);
92
+ };
93
+
94
+ const listeners = [
95
+ channel.on('message.delivered', handleMessageDelivered),
96
+ channel.on('message.read', handleMarkRead),
97
+ ];
98
+
99
+ return () => listeners.forEach((l) => l.unsubscribe());
100
+ }, [channel, client, isOwnMessage, lastMessage]);
101
+
102
+ return { status };
103
+ };
@@ -6,6 +6,8 @@ import type { Attachment, LocalMessage, UserResponse } from 'stream-chat';
6
6
  import { useCreateMessageContext } from './hooks/useCreateMessageContext';
7
7
  import { useMessageActionHandlers } from './hooks/useMessageActionHandlers';
8
8
  import { useMessageActions } from './hooks/useMessageActions';
9
+ import { useMessageDeliveredData } from './hooks/useMessageDeliveryData';
10
+ import { useMessageReadData } from './hooks/useMessageReadData';
9
11
  import { useProcessReactions } from './hooks/useProcessReactions';
10
12
  import { messageActions as defaultMessageActions } from './utils/messageActions';
11
13
 
@@ -46,7 +48,6 @@ import {
46
48
  MessageStatusTypes,
47
49
  } from '../../utils/utils';
48
50
  import type { Thumbnail } from '../Attachment/utils/buildGallery/types';
49
- import { getReadState } from '../MessageList/utils/getReadState';
50
51
 
51
52
  export type TouchableEmitter =
52
53
  | 'fileAttachment'
@@ -143,10 +144,18 @@ export type MessagePropsWithContext = Pick<
143
144
  Partial<
144
145
  Omit<
145
146
  MessageContextValue,
146
- 'groupStyles' | 'handleReaction' | 'message' | 'isMessageAIGenerated' | 'readBy'
147
+ | 'groupStyles'
148
+ | 'handleReaction'
149
+ | 'message'
150
+ | 'isMessageAIGenerated'
151
+ | 'deliveredToCount'
152
+ | 'readBy'
147
153
  >
148
154
  > &
149
- Pick<MessageContextValue, 'groupStyles' | 'message' | 'isMessageAIGenerated' | 'readBy'> &
155
+ Pick<
156
+ MessageContextValue,
157
+ 'groupStyles' | 'message' | 'isMessageAIGenerated' | 'readBy' | 'deliveredToCount'
158
+ > &
150
159
  Pick<
151
160
  MessagesContextValue,
152
161
  | 'sendReaction'
@@ -221,6 +230,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
221
230
  chatContext,
222
231
  deleteMessage: deleteMessageFromContext,
223
232
  deleteReaction,
233
+ deliveredToCount,
224
234
  dismissKeyboard,
225
235
  dismissKeyboardOnMessageTouch,
226
236
  enableLongPress = true,
@@ -626,6 +636,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
626
636
  actionsEnabled,
627
637
  alignment,
628
638
  channel,
639
+ deliveredToCount,
629
640
  dismissOverlay,
630
641
  files: attachments.files,
631
642
  goToMessage,
@@ -767,6 +778,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
767
778
  const areEqual = (prevProps: MessagePropsWithContext, nextProps: MessagePropsWithContext) => {
768
779
  const {
769
780
  chatContext: { mutedUsers: prevMutedUsers },
781
+ deliveredToCount: prevDeliveredBy,
770
782
  goToMessage: prevGoToMessage,
771
783
  groupStyles: prevGroupStyles,
772
784
  isAttachmentEqual,
@@ -781,6 +793,7 @@ const areEqual = (prevProps: MessagePropsWithContext, nextProps: MessagePropsWit
781
793
  } = prevProps;
782
794
  const {
783
795
  chatContext: { mutedUsers: nextMutedUsers },
796
+ deliveredToCount: nextDeliveredBy,
784
797
  goToMessage: nextGoToMessage,
785
798
  groupStyles: nextGroupStyles,
786
799
  isTargetedMessage: nextIsTargetedMessage,
@@ -793,6 +806,11 @@ const areEqual = (prevProps: MessagePropsWithContext, nextProps: MessagePropsWit
793
806
  t: nextT,
794
807
  } = nextProps;
795
808
 
809
+ const deliveredByEqual = prevDeliveredBy === nextDeliveredBy;
810
+ if (!deliveredByEqual) {
811
+ return false;
812
+ }
813
+
796
814
  const readByEqual = prevReadBy === nextReadBy;
797
815
  if (!readByEqual) {
798
816
  return false;
@@ -957,13 +975,14 @@ export type MessageProps = Partial<
957
975
  */
958
976
  export const Message = (props: MessageProps) => {
959
977
  const { message } = props;
960
- const { channel, enforceUniqueReaction, members, read } = useChannelContext();
978
+ const { channel, enforceUniqueReaction, members } = useChannelContext();
961
979
  const chatContext = useChatContext();
962
980
  const { dismissKeyboard } = useKeyboardContext();
963
981
  const messagesContext = useMessagesContext();
964
982
  const { openThread } = useThreadContext();
965
983
  const { t } = useTranslationContext();
966
- const readBy = useMemo(() => getReadState(message, read), [message, read]);
984
+ const readBy = useMessageReadData({ message });
985
+ const deliveredToCount = useMessageDeliveredData({ message });
967
986
  const { setQuotedMessage, setEditingState } = useMessageComposerAPIContext();
968
987
 
969
988
  return (
@@ -972,6 +991,7 @@ export const Message = (props: MessageProps) => {
972
991
  {...{
973
992
  channel,
974
993
  chatContext,
994
+ deliveredToCount,
975
995
  dismissKeyboard,
976
996
  enforceUniqueReaction,
977
997
  members,
@@ -14,12 +14,13 @@ import { MessageStatusTypes } from '../../../utils/utils';
14
14
 
15
15
  export type MessageStatusPropsWithContext = Pick<
16
16
  MessageContextValue,
17
- 'message' | 'readBy' | 'threadList'
18
- >;
17
+ 'deliveredToCount' | 'message' | 'readBy' | 'threadList'
18
+ > & {
19
+ channelMembersCount: number;
20
+ };
19
21
 
20
22
  const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => {
21
- const { channel } = useChannelContext();
22
- const { message, readBy, threadList } = props;
23
+ const { channelMembersCount, deliveredToCount, message, readBy, threadList } = props;
23
24
 
24
25
  const {
25
26
  theme: {
@@ -30,68 +31,73 @@ const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => {
30
31
  },
31
32
  } = useTheme();
32
33
 
33
- if (message.status === MessageStatusTypes.SENDING) {
34
- return (
35
- <View style={[styles.statusContainer, statusContainer]} testID='sending-container'>
36
- <Time {...timeIcon} />
37
- </View>
38
- );
39
- }
40
-
41
- if (threadList || message.status === MessageStatusTypes.FAILED) {
34
+ if (threadList || message.status === MessageStatusTypes.FAILED || message.type === 'error') {
42
35
  return null;
43
36
  }
44
37
 
45
- if (readBy) {
46
- const members = channel?.state.members;
47
- const otherMembers = Object.values(members).filter(
48
- (member) => member.user_id !== message.user?.id,
49
- );
50
- const hasOtherMembersGreaterThanOne = otherMembers.length > 1;
51
- const hasReadByGreaterThanOne = typeof readBy === 'number' && readBy > 1;
52
- const shouldDisplayReadByCount = hasOtherMembersGreaterThanOne && hasReadByGreaterThanOne;
53
- const countOfReadBy =
54
- typeof readBy === 'number' && hasOtherMembersGreaterThanOne ? readBy - 1 : 0;
55
- const showDoubleCheck = hasReadByGreaterThanOne || readBy === true;
56
-
57
- return (
58
- <View style={[styles.statusContainer, statusContainer]}>
59
- {shouldDisplayReadByCount ? (
60
- <Text
61
- style={[styles.readByCount, { color: accent_blue }, readByCount]}
62
- testID='read-by-container'
63
- >
64
- {countOfReadBy}
65
- </Text>
66
- ) : null}
67
- {message.type !== 'error' ? (
68
- showDoubleCheck ? (
69
- <CheckAll pathFill={accent_blue} {...checkAllIcon} />
70
- ) : (
71
- <Check pathFill={grey_dark} {...checkIcon} />
72
- )
73
- ) : null}
74
- </View>
75
- );
76
- }
77
-
78
- if (message.status === MessageStatusTypes.RECEIVED && message.type !== 'ephemeral') {
79
- return (
80
- <View style={[styles.statusContainer, statusContainer]} testID='delivered-container'>
81
- <Check pathFill={grey_dark} {...checkIcon} />
82
- </View>
83
- );
84
- }
85
-
86
- return null;
38
+ const hasReadByGreaterThanOne = typeof readBy === 'number' && readBy > 1;
39
+
40
+ // Variables to determine the status of the message
41
+ const read = hasReadByGreaterThanOne || readBy === true;
42
+ const delivered = deliveredToCount > 1;
43
+ const sending = message.status === MessageStatusTypes.SENDING;
44
+ const sent =
45
+ message.status === MessageStatusTypes.RECEIVED &&
46
+ !delivered &&
47
+ !read &&
48
+ message.type !== 'ephemeral';
49
+
50
+ const isGroupChannel = channelMembersCount > 2;
51
+
52
+ const shouldDisplayReadByCount = isGroupChannel && hasReadByGreaterThanOne;
53
+ const countOfReadBy = typeof readBy === 'number' && shouldDisplayReadByCount ? readBy - 1 : 0;
54
+
55
+ return (
56
+ <View style={[styles.statusContainer, statusContainer]}>
57
+ {shouldDisplayReadByCount ? (
58
+ <Text
59
+ accessibilityLabel='Read by count'
60
+ style={[styles.readByCount, { color: accent_blue }, readByCount]}
61
+ >
62
+ {countOfReadBy}
63
+ </Text>
64
+ ) : null}
65
+ {read ? (
66
+ <CheckAll pathFill={accent_blue} {...checkAllIcon} accessibilityLabel='Read' />
67
+ ) : delivered ? (
68
+ <CheckAll pathFill={grey_dark} {...checkAllIcon} accessibilityLabel='Delivered' />
69
+ ) : sending ? (
70
+ <Time pathFill={grey_dark} {...timeIcon} accessibilityLabel='Sending' />
71
+ ) : sent ? (
72
+ <Check pathFill={grey_dark} {...checkIcon} accessibilityLabel='Sent' />
73
+ ) : null}
74
+ </View>
75
+ );
87
76
  };
88
77
 
89
78
  const areEqual = (
90
79
  prevProps: MessageStatusPropsWithContext,
91
80
  nextProps: MessageStatusPropsWithContext,
92
81
  ) => {
93
- const { message: prevMessage, readBy: prevReadBy, threadList: prevThreadList } = prevProps;
94
- const { message: nextMessage, readBy: nextReadBy, threadList: nextThreadList } = nextProps;
82
+ const {
83
+ deliveredToCount: prevDeliveredBy,
84
+ message: prevMessage,
85
+ readBy: prevReadBy,
86
+ threadList: prevThreadList,
87
+ channelMembersCount: prevChannelMembersCount,
88
+ } = prevProps;
89
+ const {
90
+ deliveredToCount: nextDeliveredBy,
91
+ message: nextMessage,
92
+ readBy: nextReadBy,
93
+ threadList: nextThreadList,
94
+ channelMembersCount: nextChannelMembersCount,
95
+ } = nextProps;
96
+
97
+ const deliveredByEqual = prevDeliveredBy === nextDeliveredBy;
98
+ if (!deliveredByEqual) {
99
+ return false;
100
+ }
95
101
 
96
102
  const threadListEqual = prevThreadList === nextThreadList;
97
103
  if (!threadListEqual) {
@@ -103,6 +109,11 @@ const areEqual = (
103
109
  return false;
104
110
  }
105
111
 
112
+ const channelMembersCountEqual = prevChannelMembersCount === nextChannelMembersCount;
113
+ if (!channelMembersCountEqual) {
114
+ return false;
115
+ }
116
+
106
117
  const messageEqual =
107
118
  prevMessage.status === nextMessage.status && prevMessage.type === nextMessage.type;
108
119
  if (!messageEqual) {
@@ -120,9 +131,17 @@ const MemoizedMessageStatus = React.memo(
120
131
  export type MessageStatusProps = Partial<MessageStatusPropsWithContext>;
121
132
 
122
133
  export const MessageStatus = (props: MessageStatusProps) => {
123
- const { message, readBy, threadList } = useMessageContext();
134
+ const { channel } = useChannelContext();
135
+ const { deliveredToCount, message, readBy, threadList } = useMessageContext();
136
+
137
+ const channelMembersCount = Object.keys(channel?.state.members).length;
124
138
 
125
- return <MemoizedMessageStatus {...{ message, readBy, threadList }} {...props} />;
139
+ return (
140
+ <MemoizedMessageStatus
141
+ {...{ channel, channelMembersCount, deliveredToCount, message, readBy, threadList }}
142
+ {...props}
143
+ />
144
+ );
126
145
  };
127
146
 
128
147
  MessageStatus.displayName = 'MessageStatus{messageSimple{status}}';
@@ -71,7 +71,7 @@ const MessageTextContainerWithContext = (props: MessageTextContainerPropsWithCon
71
71
  },
72
72
  } = theme;
73
73
 
74
- const translatedMessage = useTranslatedMessage(message) as LocalMessage;
74
+ const translatedMessage = useTranslatedMessage(message);
75
75
 
76
76
  if (!message.text) {
77
77
  return null;
@@ -94,7 +94,7 @@ const MessageTextContainerWithContext = (props: MessageTextContainerPropsWithCon
94
94
  ...markdownStyles,
95
95
  ...(onlyEmojis ? onlyEmojiMarkdown : {}),
96
96
  },
97
- message: translatedMessage,
97
+ message: translatedMessage as LocalMessage,
98
98
  messageOverlay,
99
99
  messageTextNumberOfLines,
100
100
  onLongPress,
@@ -42,6 +42,8 @@ describe('MessageStatus', () => {
42
42
  chatClient = await getTestClientWithUser(user1);
43
43
  useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
44
44
  channel = chatClient.channel('messaging', mockedChannel.id);
45
+
46
+ channel.state.members = Object.fromEntries(members.map((member) => [member.user_id, member]));
45
47
  });
46
48
  afterEach(cleanup);
47
49
 
@@ -56,33 +58,19 @@ describe('MessageStatus', () => {
56
58
  </ChannelsStateProvider>,
57
59
  );
58
60
 
59
- it('should render message status with delivered container', async () => {
60
- const user = generateUser();
61
- const message = generateMessage({ user });
62
-
63
- const { getByTestId } = renderMessageStatus({
64
- lastReceivedId: message.id,
65
- message: { ...message, status: 'received' },
66
- });
67
-
68
- await waitFor(() => {
69
- expect(getByTestId('delivered-container')).toBeTruthy();
70
- });
71
- });
72
-
73
61
  it('should render message status with read by container', async () => {
74
62
  const user = generateUser();
75
63
  const message = generateMessage({ user });
76
64
  const readBy = 2;
77
65
 
78
- const { getByTestId, getByText, rerender, toJSON } = renderMessageStatus({
79
- lastReceivedId: message.id,
66
+ const { getByLabelText, getByText, rerender, toJSON } = renderMessageStatus({
67
+ deliveredToCount: 2,
80
68
  message,
81
69
  readBy,
82
70
  });
83
71
 
84
72
  await waitFor(() => {
85
- expect(getByTestId('read-by-container')).toBeTruthy();
73
+ expect(getByLabelText('Read by count')).toBeTruthy();
86
74
  expect(getByText((readBy - 1).toString())).toBeTruthy();
87
75
  });
88
76
 
@@ -105,21 +93,29 @@ describe('MessageStatus', () => {
105
93
 
106
94
  await waitFor(() => {
107
95
  expect(toJSON()).toMatchSnapshot();
108
- expect(getByTestId('read-by-container')).toBeTruthy();
96
+ expect(getByLabelText('Read by count')).toBeTruthy();
109
97
  expect(getByText((readBy - 1).toString())).toBeTruthy();
110
98
  });
111
99
  });
112
100
 
113
- it('should render message status with sending container', async () => {
114
- const user = generateUser();
115
- const message = generateMessage({ user });
116
-
117
- const { getByTestId } = renderMessageStatus({
118
- message: { ...message, status: 'sending' },
119
- });
120
-
121
- await waitFor(() => {
122
- expect(getByTestId('sending-container')).toBeTruthy();
123
- });
124
- });
101
+ it.each([
102
+ [1, 1, 'sending', 'Sending'],
103
+ [2, 2, 'received', 'Read'],
104
+ [1, 1, 'received', 'Sent'],
105
+ [2, 1, 'received', 'Delivered'],
106
+ ])(
107
+ 'should render message status with %s container when deliveredToCount is %s and readBy is %s and status is %s',
108
+ async (deliveredToCount, readBy, status, accessibilityLabel) => {
109
+ const user = generateUser();
110
+ const message = generateMessage({ user });
111
+ const { getByLabelText } = renderMessageStatus({
112
+ deliveredToCount,
113
+ message: { ...message, status },
114
+ readBy,
115
+ });
116
+ await waitFor(() => {
117
+ expect(getByLabelText(accessibilityLabel)).toBeTruthy();
118
+ });
119
+ },
120
+ );
125
121
  });