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.
- package/lib/commonjs/components/ChannelPreview/ChannelPreviewStatus.js +6 -5
- package/lib/commonjs/components/ChannelPreview/ChannelPreviewStatus.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewData.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js +13 -22
- package/lib/commonjs/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/hooks/useMessageDeliveryStatus.js +84 -0
- package/lib/commonjs/components/ChannelPreview/hooks/useMessageDeliveryStatus.js.map +1 -0
- package/lib/commonjs/components/Message/Message.js +18 -6
- package/lib/commonjs/components/Message/Message.js.map +1 -1
- package/lib/commonjs/components/Message/MessageSimple/MessageStatus.js +61 -51
- package/lib/commonjs/components/Message/MessageSimple/MessageStatus.js.map +1 -1
- package/lib/commonjs/components/Message/MessageSimple/MessageTextContainer.js.map +1 -1
- package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js +3 -1
- package/lib/commonjs/components/Message/hooks/useCreateMessageContext.js.map +1 -1
- package/lib/commonjs/components/Message/hooks/useMessageDeliveryData.js +43 -0
- package/lib/commonjs/components/Message/hooks/useMessageDeliveryData.js.map +1 -0
- package/lib/commonjs/components/Message/hooks/useMessageReadData.js +43 -0
- package/lib/commonjs/components/Message/hooks/useMessageReadData.js.map +1 -0
- package/lib/commonjs/components/index.js +15 -4
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/contexts/messageContext/MessageContext.js.map +1 -1
- package/lib/commonjs/hooks/useTranslatedMessage.js.map +1 -1
- package/lib/commonjs/store/mappers/mapReadToStorable.js +5 -1
- package/lib/commonjs/store/mappers/mapReadToStorable.js.map +1 -1
- package/lib/commonjs/store/mappers/mapStorableToRead.js +5 -1
- package/lib/commonjs/store/mappers/mapStorableToRead.js.map +1 -1
- package/lib/commonjs/store/schema.js +2 -0
- package/lib/commonjs/store/schema.js.map +1 -1
- package/lib/commonjs/types/types.js +12 -1
- package/lib/commonjs/types/types.js.map +1 -1
- package/lib/commonjs/utils/utils.js +1 -0
- package/lib/commonjs/utils/utils.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/ChannelPreview/ChannelPreviewStatus.js +6 -5
- package/lib/module/components/ChannelPreview/ChannelPreviewStatus.js.map +1 -1
- package/lib/module/components/ChannelPreview/hooks/useChannelPreviewData.js.map +1 -1
- package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js +13 -22
- package/lib/module/components/ChannelPreview/hooks/useLatestMessagePreview.js.map +1 -1
- package/lib/module/components/ChannelPreview/hooks/useMessageDeliveryStatus.js +84 -0
- package/lib/module/components/ChannelPreview/hooks/useMessageDeliveryStatus.js.map +1 -0
- package/lib/module/components/Message/Message.js +18 -6
- package/lib/module/components/Message/Message.js.map +1 -1
- package/lib/module/components/Message/MessageSimple/MessageStatus.js +61 -51
- package/lib/module/components/Message/MessageSimple/MessageStatus.js.map +1 -1
- package/lib/module/components/Message/MessageSimple/MessageTextContainer.js.map +1 -1
- package/lib/module/components/Message/hooks/useCreateMessageContext.js +3 -1
- package/lib/module/components/Message/hooks/useCreateMessageContext.js.map +1 -1
- package/lib/module/components/Message/hooks/useMessageDeliveryData.js +43 -0
- package/lib/module/components/Message/hooks/useMessageDeliveryData.js.map +1 -0
- package/lib/module/components/Message/hooks/useMessageReadData.js +43 -0
- package/lib/module/components/Message/hooks/useMessageReadData.js.map +1 -0
- package/lib/module/components/index.js +15 -4
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/contexts/messageContext/MessageContext.js.map +1 -1
- package/lib/module/hooks/useTranslatedMessage.js.map +1 -1
- package/lib/module/store/mappers/mapReadToStorable.js +5 -1
- package/lib/module/store/mappers/mapReadToStorable.js.map +1 -1
- package/lib/module/store/mappers/mapStorableToRead.js +5 -1
- package/lib/module/store/mappers/mapStorableToRead.js.map +1 -1
- package/lib/module/store/schema.js +2 -0
- package/lib/module/store/schema.js.map +1 -1
- package/lib/module/types/types.js +12 -1
- package/lib/module/types/types.js.map +1 -1
- package/lib/module/utils/utils.js +1 -0
- package/lib/module/utils/utils.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/ChannelPreview/ChannelPreviewStatus.d.ts.map +1 -1
- package/lib/typescript/components/ChannelPreview/hooks/useChannelPreviewData.d.ts +12 -4
- package/lib/typescript/components/ChannelPreview/hooks/useChannelPreviewData.d.ts.map +1 -1
- package/lib/typescript/components/ChannelPreview/hooks/useLatestMessagePreview.d.ts +21 -10
- package/lib/typescript/components/ChannelPreview/hooks/useLatestMessagePreview.d.ts.map +1 -1
- package/lib/typescript/components/ChannelPreview/hooks/useMessageDeliveryStatus.d.ts +17 -0
- package/lib/typescript/components/ChannelPreview/hooks/useMessageDeliveryStatus.d.ts.map +1 -0
- package/lib/typescript/components/Message/Message.d.ts +1 -1
- package/lib/typescript/components/Message/Message.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageSimple/MessageStatus.d.ts +3 -1
- package/lib/typescript/components/Message/MessageSimple/MessageStatus.d.ts.map +1 -1
- package/lib/typescript/components/Message/hooks/useCreateMessageContext.d.ts +1 -1
- package/lib/typescript/components/Message/hooks/useCreateMessageContext.d.ts.map +1 -1
- package/lib/typescript/components/Message/hooks/useMessageDeliveryData.d.ts +5 -0
- package/lib/typescript/components/Message/hooks/useMessageDeliveryData.d.ts.map +1 -0
- package/lib/typescript/components/Message/hooks/useMessageReadData.d.ts +5 -0
- package/lib/typescript/components/Message/hooks/useMessageReadData.d.ts.map +1 -0
- package/lib/typescript/components/index.d.ts +2 -1
- package/lib/typescript/components/index.d.ts.map +1 -1
- package/lib/typescript/contexts/messageContext/MessageContext.d.ts +3 -1
- package/lib/typescript/contexts/messageContext/MessageContext.d.ts.map +1 -1
- package/lib/typescript/hooks/useTranslatedMessage.d.ts +2 -61
- package/lib/typescript/hooks/useTranslatedMessage.d.ts.map +1 -1
- package/lib/typescript/store/mappers/mapReadToStorable.d.ts.map +1 -1
- package/lib/typescript/store/mappers/mapStorableToRead.d.ts.map +1 -1
- package/lib/typescript/store/schema.d.ts +2 -0
- package/lib/typescript/store/schema.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +11 -1
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/lib/typescript/utils/utils.d.ts +1 -0
- package/lib/typescript/utils/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelPreview/ChannelPreviewStatus.tsx +7 -4
- package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts +7 -3
- package/src/components/ChannelPreview/hooks/useLatestMessagePreview.ts +28 -47
- package/src/components/ChannelPreview/hooks/useMessageDeliveryStatus.ts +103 -0
- package/src/components/Message/Message.tsx +25 -5
- package/src/components/Message/MessageSimple/MessageStatus.tsx +78 -59
- package/src/components/Message/MessageSimple/MessageTextContainer.tsx +2 -2
- package/src/components/Message/MessageSimple/__tests__/MessageStatus.test.js +26 -30
- package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageStatus.test.js.snap +3 -2
- package/src/components/Message/hooks/useCreateMessageContext.ts +3 -0
- package/src/components/Message/hooks/useMessageDeliveryData.ts +37 -0
- package/src/components/Message/hooks/useMessageReadData.ts +38 -0
- package/src/components/index.ts +2 -1
- package/src/contexts/messageContext/MessageContext.tsx +3 -1
- package/src/hooks/useTranslatedMessage.ts +2 -2
- package/src/store/mappers/mapReadToStorable.ts +10 -1
- package/src/store/mappers/mapStorableToRead.ts +10 -1
- package/src/store/schema.ts +4 -0
- package/src/types/types.ts +14 -9
- package/src/utils/utils.ts +1 -0
- package/src/version.json +1 -1
- package/lib/commonjs/components/MessageList/utils/getReadState.js +0 -20
- package/lib/commonjs/components/MessageList/utils/getReadState.js.map +0 -1
- package/lib/module/components/MessageList/utils/getReadState.js +0 -20
- package/lib/module/components/MessageList/utils/getReadState.js.map +0 -1
- package/lib/typescript/components/MessageList/utils/getReadState.d.ts +0 -9
- package/lib/typescript/components/MessageList/utils/getReadState.d.ts.map +0 -1
- 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
|
-
|
|
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:
|
|
27
|
+
messageObject: LocalMessage | undefined;
|
|
29
28
|
previews: {
|
|
30
29
|
bold: boolean;
|
|
31
30
|
text: string;
|
|
32
31
|
draft?: boolean;
|
|
33
32
|
}[];
|
|
34
|
-
status
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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
|
|
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?:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
147
|
+
| 'groupStyles'
|
|
148
|
+
| 'handleReaction'
|
|
149
|
+
| 'message'
|
|
150
|
+
| 'isMessageAIGenerated'
|
|
151
|
+
| 'deliveredToCount'
|
|
152
|
+
| 'readBy'
|
|
147
153
|
>
|
|
148
154
|
> &
|
|
149
|
-
Pick<
|
|
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
|
|
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 =
|
|
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 {
|
|
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.
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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 {
|
|
94
|
-
|
|
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 {
|
|
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
|
|
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)
|
|
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 {
|
|
79
|
-
|
|
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(
|
|
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(
|
|
96
|
+
expect(getByLabelText('Read by count')).toBeTruthy();
|
|
109
97
|
expect(getByText((readBy - 1).toString())).toBeTruthy();
|
|
110
98
|
});
|
|
111
99
|
});
|
|
112
100
|
|
|
113
|
-
it(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
});
|