stream-chat-react-native-core 10.0.0-rc.3 → 10.0.0-rc.5
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/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js +4 -1
- package/lib/commonjs/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js.map +1 -1
- package/lib/commonjs/components/Channel/Channel.js +1 -0
- package/lib/commonjs/components/Channel/Channel.js.map +1 -1
- package/lib/commonjs/components/Channel/ChannelShell.js +8 -1
- package/lib/commonjs/components/Channel/ChannelShell.js.map +1 -1
- package/lib/commonjs/components/Channel/hooks/useChannelLifecycle.js +2 -2
- package/lib/commonjs/components/Channel/hooks/useChannelLifecycle.js.map +1 -1
- package/lib/commonjs/components/Channel/hooks/useJumpToLatestOnOpen.js +26 -0
- package/lib/commonjs/components/Channel/hooks/useJumpToLatestOnOpen.js.map +1 -0
- package/lib/commonjs/components/ChannelList/hooks/usePaginatedChannels.js +1 -1
- package/lib/commonjs/components/ChannelList/hooks/usePaginatedChannels.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js +7 -0
- package/lib/commonjs/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/ChannelPreviewMessage.js +4 -5
- package/lib/commonjs/components/ChannelPreview/ChannelPreviewMessage.js.map +1 -1
- package/lib/commonjs/components/Message/Message.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/Headers/SentToChannelHeader.js +6 -32
- package/lib/commonjs/components/Message/MessageItemView/Headers/SentToChannelHeader.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/MessageContent.js +2 -2
- package/lib/commonjs/components/Message/MessageItemView/MessageContent.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/MessageStatus.js +29 -9
- package/lib/commonjs/components/Message/MessageItemView/MessageStatus.js.map +1 -1
- package/lib/commonjs/components/Message/hooks/useOpenThreadAtMessage.js +57 -0
- package/lib/commonjs/components/Message/hooks/useOpenThreadAtMessage.js.map +1 -0
- package/lib/commonjs/components/Message/utils/messageActions.js +1 -1
- package/lib/commonjs/components/Message/utils/messageActions.js.map +1 -1
- package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js +12 -8
- package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js.map +1 -1
- package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js +3 -2
- package/lib/commonjs/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js.map +1 -1
- package/lib/commonjs/components/MessageInput/hooks/useAudioRecorder.js +2 -2
- package/lib/commonjs/components/MessageInput/hooks/useAudioRecorder.js.map +1 -1
- package/lib/commonjs/components/MessageList/InlineUnreadIndicator.js +2 -2
- package/lib/commonjs/components/MessageList/InlineUnreadIndicator.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageList.js +1 -4
- package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
- package/lib/commonjs/components/MessageList/hooks/useMessageListFocus.js +18 -2
- package/lib/commonjs/components/MessageList/hooks/useMessageListFocus.js.map +1 -1
- package/lib/commonjs/components/Thread/components/ThreadFooterComponent.js +1 -2
- package/lib/commonjs/components/Thread/components/ThreadFooterComponent.js.map +1 -1
- package/lib/commonjs/components/index.js +12 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js +0 -15
- package/lib/commonjs/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js.map +1 -1
- package/lib/commonjs/contexts/messageContext/MessageContext.js.map +1 -1
- package/lib/commonjs/contexts/messageListItemContext/MessageListItemContext.js.map +1 -1
- package/lib/commonjs/index.js +0 -12
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/types/types.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js +4 -1
- package/lib/module/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.js.map +1 -1
- package/lib/module/components/Channel/Channel.js +1 -0
- package/lib/module/components/Channel/Channel.js.map +1 -1
- package/lib/module/components/Channel/ChannelShell.js +8 -1
- package/lib/module/components/Channel/ChannelShell.js.map +1 -1
- package/lib/module/components/Channel/hooks/useChannelLifecycle.js +2 -2
- package/lib/module/components/Channel/hooks/useChannelLifecycle.js.map +1 -1
- package/lib/module/components/Channel/hooks/useJumpToLatestOnOpen.js +26 -0
- package/lib/module/components/Channel/hooks/useJumpToLatestOnOpen.js.map +1 -0
- package/lib/module/components/ChannelList/hooks/usePaginatedChannels.js +1 -1
- package/lib/module/components/ChannelList/hooks/usePaginatedChannels.js.map +1 -1
- package/lib/module/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js +7 -0
- package/lib/module/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js.map +1 -1
- package/lib/module/components/ChannelPreview/ChannelPreviewMessage.js +4 -5
- package/lib/module/components/ChannelPreview/ChannelPreviewMessage.js.map +1 -1
- package/lib/module/components/Message/Message.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/Headers/SentToChannelHeader.js +6 -32
- package/lib/module/components/Message/MessageItemView/Headers/SentToChannelHeader.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/MessageContent.js +2 -2
- package/lib/module/components/Message/MessageItemView/MessageContent.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/MessageStatus.js +29 -9
- package/lib/module/components/Message/MessageItemView/MessageStatus.js.map +1 -1
- package/lib/module/components/Message/hooks/useOpenThreadAtMessage.js +57 -0
- package/lib/module/components/Message/hooks/useOpenThreadAtMessage.js.map +1 -0
- package/lib/module/components/Message/utils/messageActions.js +1 -1
- package/lib/module/components/Message/utils/messageActions.js.map +1 -1
- package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js +12 -8
- package/lib/module/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.js.map +1 -1
- package/lib/module/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js +3 -2
- package/lib/module/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.js.map +1 -1
- package/lib/module/components/MessageInput/hooks/useAudioRecorder.js +2 -2
- package/lib/module/components/MessageInput/hooks/useAudioRecorder.js.map +1 -1
- package/lib/module/components/MessageList/InlineUnreadIndicator.js +2 -2
- package/lib/module/components/MessageList/InlineUnreadIndicator.js.map +1 -1
- package/lib/module/components/MessageList/MessageList.js +1 -4
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/components/MessageList/hooks/useMessageListFocus.js +18 -2
- package/lib/module/components/MessageList/hooks/useMessageListFocus.js.map +1 -1
- package/lib/module/components/Thread/components/ThreadFooterComponent.js +1 -2
- package/lib/module/components/Thread/components/ThreadFooterComponent.js.map +1 -1
- package/lib/module/components/index.js +12 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js +0 -15
- package/lib/module/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.js.map +1 -1
- package/lib/module/contexts/messageContext/MessageContext.js.map +1 -1
- package/lib/module/contexts/messageListItemContext/MessageListItemContext.js.map +1 -1
- package/lib/module/index.js +0 -12
- package/lib/module/index.js.map +1 -1
- package/lib/module/types/types.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/ai-components/services/media-picker-service/index.d.ts +1 -1
- package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
- package/lib/typescript/components/Channel/ChannelShell.d.ts +2 -0
- package/lib/typescript/components/Channel/ChannelShell.d.ts.map +1 -1
- package/lib/typescript/components/Channel/hooks/useChannelLifecycle.d.ts.map +1 -1
- package/lib/typescript/components/Channel/hooks/useJumpToLatestOnOpen.d.ts +15 -0
- package/lib/typescript/components/Channel/hooks/useJumpToLatestOnOpen.d.ts.map +1 -0
- package/lib/typescript/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.d.ts.map +1 -1
- package/lib/typescript/components/ChannelPreview/ChannelPreviewMessage.d.ts.map +1 -1
- package/lib/typescript/components/Message/Message.d.ts +2 -1
- package/lib/typescript/components/Message/Message.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/Headers/SentToChannelHeader.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/MessageContent.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/MessageStatus.d.ts +6 -0
- package/lib/typescript/components/Message/MessageItemView/MessageStatus.d.ts.map +1 -1
- package/lib/typescript/components/Message/hooks/useOpenThreadAtMessage.d.ts +14 -0
- package/lib/typescript/components/Message/hooks/useOpenThreadAtMessage.d.ts.map +1 -0
- package/lib/typescript/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.d.ts.map +1 -1
- package/lib/typescript/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/hooks/useMessageListFocus.d.ts +5 -2
- package/lib/typescript/components/MessageList/hooks/useMessageListFocus.d.ts.map +1 -1
- package/lib/typescript/components/Thread/components/ThreadFooterComponent.d.ts.map +1 -1
- package/lib/typescript/components/index.d.ts +1 -0
- package/lib/typescript/components/index.d.ts.map +1 -1
- package/lib/typescript/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.d.ts.map +1 -1
- package/lib/typescript/contexts/messageContext/MessageContext.d.ts +2 -1
- package/lib/typescript/contexts/messageContext/MessageContext.d.ts.map +1 -1
- package/lib/typescript/contexts/messageListItemContext/MessageListItemContext.d.ts +10 -6
- package/lib/typescript/contexts/messageListItemContext/MessageListItemContext.d.ts.map +1 -1
- package/lib/typescript/i18n/keys.d.ts +2 -2
- package/lib/typescript/i18n/keys.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +0 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +0 -4
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/AttachmentPicker/components/AttachmentMediaPicker/AttachmentPickerItem.tsx +1 -1
- package/src/components/AttachmentPicker/components/AttachmentMediaPicker/__tests__/AttachmentPickerItem.test.tsx +29 -1
- package/src/components/Channel/Channel.tsx +1 -0
- package/src/components/Channel/ChannelShell.tsx +12 -0
- package/src/components/Channel/__tests__/Channel.test.tsx +142 -7
- package/src/components/Channel/hooks/useChannelLifecycle.ts +8 -4
- package/src/components/Channel/hooks/useCreateOwnCapabilitiesContext.ts +1 -1
- package/src/components/Channel/hooks/useJumpToLatestOnOpen.ts +42 -0
- package/src/components/ChannelList/__tests__/ChannelList.test.tsx +28 -0
- package/src/components/ChannelList/hooks/usePaginatedChannels.ts +1 -1
- package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx +8 -0
- package/src/components/ChannelPreview/ChannelPreviewMessage.tsx +2 -7
- package/src/components/ChannelPreview/__tests__/ChannelPreviewView.test.tsx +41 -0
- package/src/components/Message/Message.tsx +5 -2
- package/src/components/Message/MessageItemView/Headers/SentToChannelHeader.tsx +4 -32
- package/src/components/Message/MessageItemView/MessageContent.tsx +3 -2
- package/src/components/Message/MessageItemView/MessageStatus.tsx +43 -4
- package/src/components/Message/MessageItemView/__tests__/MessageContent.test.tsx +68 -1
- package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx +57 -0
- package/src/components/Message/hooks/useOpenThreadAtMessage.ts +50 -0
- package/src/components/Message/utils/__tests__/messageActions.test.ts +57 -0
- package/src/components/Message/utils/messageActions.ts +1 -1
- package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx +34 -6
- package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.tsx +24 -3
- package/src/components/MessageInput/__tests__/SendMessageDisallowedIndicator.test.tsx +3 -6
- package/src/components/MessageInput/components/AttachmentPreview/AttachmentUploadPreviewList.tsx +19 -8
- package/src/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.tsx +2 -5
- package/src/components/MessageInput/hooks/useAudioRecorder.tsx +2 -2
- package/src/components/MessageList/InlineUnreadIndicator.tsx +2 -2
- package/src/components/MessageList/MessageList.tsx +1 -5
- package/src/components/MessageList/__tests__/InlineUnreadIndicator.test.tsx +2 -2
- package/src/components/MessageList/__tests__/MessageList.test.tsx +252 -6
- package/src/components/MessageList/hooks/useMessageListFocus.ts +22 -2
- package/src/components/Thread/__tests__/Thread.test.tsx +131 -1
- package/src/components/Thread/components/ThreadFooterComponent.tsx +0 -1
- package/src/components/index.ts +1 -0
- package/src/contexts/channelPinnedMessageListContext/ChannelPinnedMessageListContext.tsx +5 -14
- package/src/contexts/messageContext/MessageContext.tsx +2 -1
- package/src/contexts/messageListItemContext/MessageListItemContext.tsx +15 -6
- package/src/i18n/__tests__/catalog.fixture.json +2 -2
- package/src/i18n/keys.ts +2 -2
- package/src/index.ts +0 -1
- package/src/types/stream-chat-common-custom-data.d.ts +0 -3
- package/src/types/types.ts +0 -5
- package/src/version.json +1 -1
- package/lib/commonjs/utils/setupCommandUIMiddlewares.js +0 -34
- package/lib/commonjs/utils/setupCommandUIMiddlewares.js.map +0 -1
- package/lib/module/utils/setupCommandUIMiddlewares.js +0 -34
- package/lib/module/utils/setupCommandUIMiddlewares.js.map +0 -1
- package/lib/typescript/utils/setupCommandUIMiddlewares.d.ts +0 -3
- package/lib/typescript/utils/setupCommandUIMiddlewares.d.ts.map +0 -1
- package/src/utils/setupCommandUIMiddlewares.ts +0 -31
|
@@ -147,12 +147,12 @@ describe('Channel', () => {
|
|
|
147
147
|
await waitFor(() => expect(watchSpy).toHaveBeenCalledTimes(1));
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
it('does not watch or load a
|
|
150
|
+
it('does not watch, query or load a channel built from members', async () => {
|
|
151
151
|
const draft = chatClient.channel('messaging', {
|
|
152
152
|
members: [{ user_id: user.id }, { user_id: 'other' }],
|
|
153
153
|
} as never);
|
|
154
|
-
draft.initialized = true;
|
|
155
154
|
const watchSpy = jest.spyOn(draft, 'watch');
|
|
155
|
+
const querySpy = jest.spyOn(draft, 'query');
|
|
156
156
|
const seedSpy = jest.spyOn(draft.messagePaginator, 'reload');
|
|
157
157
|
|
|
158
158
|
renderComponent({ channel: draft });
|
|
@@ -162,7 +162,9 @@ describe('Channel', () => {
|
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
expect(draft.isProvisional).toBe(true);
|
|
165
|
+
expect(draft.initialized).toBe(false);
|
|
165
166
|
expect(watchSpy).not.toHaveBeenCalled();
|
|
167
|
+
expect(querySpy).not.toHaveBeenCalled();
|
|
166
168
|
expect(seedSpy).not.toHaveBeenCalled();
|
|
167
169
|
});
|
|
168
170
|
|
|
@@ -223,6 +225,37 @@ describe('Channel', () => {
|
|
|
223
225
|
);
|
|
224
226
|
});
|
|
225
227
|
|
|
228
|
+
describe('connection.recovered', () => {
|
|
229
|
+
const renderAndRecover = async () => {
|
|
230
|
+
const markReadSpy = jest
|
|
231
|
+
.spyOn(chatClient.messageDeliveryReporter, 'throttledMarkRead')
|
|
232
|
+
.mockImplementation(() => undefined);
|
|
233
|
+
renderComponent({ channel });
|
|
234
|
+
await waitFor(() => expect(channel.initialized).toBe(true));
|
|
235
|
+
markReadSpy.mockClear();
|
|
236
|
+
|
|
237
|
+
act(() => {
|
|
238
|
+
chatClient.dispatchEvent({ type: 'connection.recovered' });
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return markReadSpy;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
it('marks the channel read after a recovery', async () => {
|
|
245
|
+
const markReadSpy = await renderAndRecover();
|
|
246
|
+
|
|
247
|
+
expect(markReadSpy).toHaveBeenCalledWith(channel);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('does not mark the channel read when recovery is disabled', async () => {
|
|
251
|
+
chatClient.config.set({ client: { connectionRecovery: { enabled: false } } });
|
|
252
|
+
|
|
253
|
+
const markReadSpy = await renderAndRecover();
|
|
254
|
+
|
|
255
|
+
expect(markReadSpy).not.toHaveBeenCalled();
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
226
259
|
const queryChannelWithNewMessages = (newMessages: ReturnType<typeof generateMessage>[]) =>
|
|
227
260
|
// generate new channel mock from existing channel with new messages added
|
|
228
261
|
getOrCreateChannelApi(
|
|
@@ -400,8 +433,6 @@ describe('Channel', () => {
|
|
|
400
433
|
members: [{ user_id: user.id }, { user_id: otherUser.id }],
|
|
401
434
|
});
|
|
402
435
|
const stored = chatClient.channel('messaging', '!members-xyz');
|
|
403
|
-
// the mount's first-page load would query (and so supersede) the draft before the test does
|
|
404
|
-
jest.spyOn(draft.messagePaginator, 'reload').mockResolvedValue(undefined);
|
|
405
436
|
useMockedApis(chatClient, [
|
|
406
437
|
getOrCreateChannelApi(
|
|
407
438
|
generateChannelResponse({
|
|
@@ -610,12 +641,14 @@ describe('Channel', () => {
|
|
|
610
641
|
});
|
|
611
642
|
const rendersBefore = renderSpy.mock.calls.length;
|
|
612
643
|
const capabilitiesBefore = channel.state.getLatestValue().ownCapabilities;
|
|
644
|
+
const dataBefore = channel.data;
|
|
613
645
|
|
|
614
646
|
act(() => dispatchChannelUpdatedEvent(chatClient, { ...channel.data }));
|
|
615
647
|
|
|
616
|
-
// Guard against a vacuous pass: the update was applied
|
|
617
|
-
|
|
618
|
-
expect(channel.
|
|
648
|
+
// Guard against a vacuous pass: the update was applied. The capabilities did not change, so
|
|
649
|
+
// the client keeps their array.
|
|
650
|
+
expect(channel.data).not.toBe(dataBefore);
|
|
651
|
+
expect(channel.state.getLatestValue().ownCapabilities).toBe(capabilitiesBefore);
|
|
619
652
|
expect(renderSpy.mock.calls.length).toBe(rendersBefore);
|
|
620
653
|
});
|
|
621
654
|
|
|
@@ -796,6 +829,108 @@ describe('Channel initial load useEffect', () => {
|
|
|
796
829
|
});
|
|
797
830
|
});
|
|
798
831
|
|
|
832
|
+
describe('reopening a channel left on an older page', () => {
|
|
833
|
+
const OLDER_PAGE_START = new Date('2020-01-01T00:00:00.000Z').getTime();
|
|
834
|
+
|
|
835
|
+
// A watched channel whose newest messages are loaded, but whose list shows an older page, as it
|
|
836
|
+
// does after a jump to a quoted, pinned or searched message.
|
|
837
|
+
const setUpChannelOnOlderPage = async () => {
|
|
838
|
+
const newestMessages = Array.from({ length: 10 }, (_, i) =>
|
|
839
|
+
generateMessage({ id: `newest-${i}` }),
|
|
840
|
+
);
|
|
841
|
+
const mockedChannel = generateChannelResponse({ messages: newestMessages });
|
|
842
|
+
useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
|
|
843
|
+
const channel = chatClient.channel('messaging', mockedChannel.channel.id);
|
|
844
|
+
await channel.watch();
|
|
845
|
+
|
|
846
|
+
const olderMessages = Array.from({ length: 5 }, (_, i) =>
|
|
847
|
+
generateMessage({ id: `older-${i}`, timestamp: OLDER_PAGE_START + i * 1000 }),
|
|
848
|
+
);
|
|
849
|
+
channel.messagePaginator.ingestPage({
|
|
850
|
+
page: olderMessages,
|
|
851
|
+
policy: 'strict-overlap-only',
|
|
852
|
+
setActive: true,
|
|
853
|
+
});
|
|
854
|
+
expect(channel.messagePaginator.isActiveIntervalAtHead).toBe(false);
|
|
855
|
+
|
|
856
|
+
return channel;
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
it('shows the newest messages again', async () => {
|
|
860
|
+
const channel = await setUpChannelOnOlderPage();
|
|
861
|
+
|
|
862
|
+
renderComponent({ channel, markReadOnMount: false });
|
|
863
|
+
|
|
864
|
+
await waitFor(() => expect(channel.messagePaginator.isActiveIntervalAtHead).toBe(true));
|
|
865
|
+
expect(channel.messagePaginator.items?.map((message) => message.id)).toContain('newest-9');
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
it('keeps a jump the integrator already made to a loaded message', async () => {
|
|
869
|
+
const channel = await setUpChannelOnOlderPage();
|
|
870
|
+
// A deep link from the screen: the target is loaded, so the jump lands at once.
|
|
871
|
+
await channel.messagePaginator.jumpToMessage('older-2', { focusReason: 'jump-to-message' });
|
|
872
|
+
const seedSpy = jest.spyOn(channel.messagePaginator, 'seedUnreadSnapshot');
|
|
873
|
+
|
|
874
|
+
renderComponent({ channel, markReadOnMount: false });
|
|
875
|
+
|
|
876
|
+
await waitFor(() => expect(seedSpy).toHaveBeenCalled());
|
|
877
|
+
expect(channel.messagePaginator.isActiveIntervalAtHead).toBe(false);
|
|
878
|
+
expect(channel.messagePaginator.messageFocusSignal.getLatestValue().signal?.messageId).toBe(
|
|
879
|
+
'older-2',
|
|
880
|
+
);
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
it('leaves a jump that is still in flight alone', async () => {
|
|
884
|
+
const channel = await setUpChannelOnOlderPage();
|
|
885
|
+
// A deep link to a message that is not loaded: its query is in flight.
|
|
886
|
+
jest.spyOn(channel.messagePaginator, 'isLoading', 'get').mockReturnValue(true);
|
|
887
|
+
const jumpToTheLatestMessageSpy = jest.spyOn(
|
|
888
|
+
channel.messagePaginator,
|
|
889
|
+
'jumpToTheLatestMessage',
|
|
890
|
+
);
|
|
891
|
+
const seedSpy = jest.spyOn(channel.messagePaginator, 'seedUnreadSnapshot');
|
|
892
|
+
|
|
893
|
+
renderComponent({ channel, markReadOnMount: false });
|
|
894
|
+
|
|
895
|
+
await waitFor(() => expect(seedSpy).toHaveBeenCalled());
|
|
896
|
+
expect(jumpToTheLatestMessageSpy).not.toHaveBeenCalled();
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
it('leaves the list alone when the app places its pages itself', async () => {
|
|
900
|
+
const channel = await setUpChannelOnOlderPage();
|
|
901
|
+
const jumpToTheLatestMessageSpy = jest.spyOn(
|
|
902
|
+
channel.messagePaginator,
|
|
903
|
+
'jumpToTheLatestMessage',
|
|
904
|
+
);
|
|
905
|
+
|
|
906
|
+
renderComponent({ channel, initializeOnMount: false, markReadOnMount: false });
|
|
907
|
+
|
|
908
|
+
expect(jumpToTheLatestMessageSpy).not.toHaveBeenCalled();
|
|
909
|
+
expect(channel.messagePaginator.isActiveIntervalAtHead).toBe(false);
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
it('leaves the jump to the first unread message to the initial load', async () => {
|
|
913
|
+
const channel = await setUpChannelOnOlderPage();
|
|
914
|
+
jest.spyOn(channel, 'countUnread').mockImplementation(() => 3);
|
|
915
|
+
const jumpToTheFirstUnreadMessageSpy = jest
|
|
916
|
+
.spyOn(channel.messagePaginator, 'jumpToTheFirstUnreadMessage')
|
|
917
|
+
.mockResolvedValue(undefined as never);
|
|
918
|
+
const jumpToTheLatestMessageSpy = jest.spyOn(
|
|
919
|
+
channel.messagePaginator,
|
|
920
|
+
'jumpToTheLatestMessage',
|
|
921
|
+
);
|
|
922
|
+
|
|
923
|
+
renderComponent({
|
|
924
|
+
channel,
|
|
925
|
+
initialScrollToFirstUnreadMessage: true,
|
|
926
|
+
markReadOnMount: false,
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
await waitFor(() => expect(jumpToTheFirstUnreadMessageSpy).toHaveBeenCalledTimes(1));
|
|
930
|
+
expect(jumpToTheLatestMessageSpy).not.toHaveBeenCalled();
|
|
931
|
+
});
|
|
932
|
+
});
|
|
933
|
+
|
|
799
934
|
describe('message page size', () => {
|
|
800
935
|
const mountChannel = async () => {
|
|
801
936
|
const mockedChannel = generateChannelResponse({ messages: [] });
|
|
@@ -97,8 +97,9 @@ export const useChannelLifecycle = ({
|
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
// A
|
|
101
|
-
|
|
100
|
+
// A channel built from members has no ID yet, and querying it would create it on the server. It
|
|
101
|
+
// is the app's responsibility to create it before trying to interact.
|
|
102
|
+
if (channel.isProvisional) {
|
|
102
103
|
return;
|
|
103
104
|
}
|
|
104
105
|
|
|
@@ -155,9 +156,12 @@ export const useChannelLifecycle = ({
|
|
|
155
156
|
useEffect(() => {
|
|
156
157
|
// Waits for `connection.recovered`, which fires after the reloads land, so `hasMoreHead`
|
|
157
158
|
// reflects the refreshed window. Marks read only when that window is at the newest message,
|
|
158
|
-
// leaving the read state alone when the user has paginated up into older history.
|
|
159
|
+
// leaving the read state alone when the user has paginated up into older history. With
|
|
160
|
+
// recovery disabled the event fires on every reconnect without a reload, so the messages sent
|
|
161
|
+
// during the drop were never shown and are not marked read. A manual `recover()` dispatches the
|
|
162
|
+
// same event, so it does not mark read either.
|
|
159
163
|
const { unsubscribe } = client.on('connection.recovered', () => {
|
|
160
|
-
if (channel.messagePaginator.hasMoreHead) {
|
|
164
|
+
if (!client.connectionRecovery.config.enabled || channel.messagePaginator.hasMoreHead) {
|
|
161
165
|
return;
|
|
162
166
|
}
|
|
163
167
|
markRead();
|
|
@@ -22,7 +22,7 @@ export const useCreateOwnCapabilitiesContext = ({
|
|
|
22
22
|
channel: Channel;
|
|
23
23
|
overrideCapabilities?: Partial<OwnCapabilitiesContextValue>;
|
|
24
24
|
}) => {
|
|
25
|
-
// Updated by the client on watch, query and `
|
|
25
|
+
// Updated by the client on watch, query and `channel.updated`.
|
|
26
26
|
const { ownCapabilities } = useStateStore(channel.state, selector);
|
|
27
27
|
|
|
28
28
|
const overrideCapabilitiesKey = overrideCapabilities
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
|
|
4
|
+
import { useThreadContext } from '../../../contexts/threadContext/ThreadContext';
|
|
5
|
+
import { useMessagePaginator } from '../../../hooks/useMessagePaginator';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shows the newest messages when a channel or thread is opened on an older page. Its paginator
|
|
9
|
+
* outlives the screen, so it keeps the page it was left on, such as one after a jump to a quoted,
|
|
10
|
+
* pinned or searched message. Runs before the first paint, so the old page never flashes.
|
|
11
|
+
*
|
|
12
|
+
* Stays out of the way of a jump that already landed or is in flight (a deep link from the screen)
|
|
13
|
+
* and of the jump to the first unread message. Runs once per channel or thread: its dependencies
|
|
14
|
+
* are stable, so nothing that changes while it is open moves the list.
|
|
15
|
+
*
|
|
16
|
+
* `<Channel>` and `<Thread>` already run it, unless `<Channel initializeOnMount={false}>`. Call it
|
|
17
|
+
* from a component inside `<Channel>` or `<Thread>` only when the app opted out that way and still
|
|
18
|
+
* wants the newest messages shown on open.
|
|
19
|
+
*/
|
|
20
|
+
export const useJumpToLatestOnOpen = () => {
|
|
21
|
+
const { channel, hasPendingInitialTargetLoad } = useChannelContext();
|
|
22
|
+
const { thread } = useThreadContext();
|
|
23
|
+
const paginator = useMessagePaginator();
|
|
24
|
+
|
|
25
|
+
useLayoutEffect(() => {
|
|
26
|
+
// A channel that is not loaded yet, or a draft, gets its first page from `<Channel>` itself.
|
|
27
|
+
const isChannelLoading = !thread && (!channel.initialized || channel.isProvisional);
|
|
28
|
+
if (
|
|
29
|
+
isChannelLoading ||
|
|
30
|
+
!paginator.items?.length ||
|
|
31
|
+
paginator.isActiveIntervalAtHead ||
|
|
32
|
+
paginator.isLoading ||
|
|
33
|
+
hasPendingInitialTargetLoad?.()
|
|
34
|
+
) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
paginator
|
|
39
|
+
.jumpToTheLatestMessage()
|
|
40
|
+
.catch((error: unknown) => console.warn('Jumping to the latest message failed:', error));
|
|
41
|
+
}, [channel, hasPendingInitialTargetLoad, paginator, thread]);
|
|
42
|
+
};
|
|
@@ -1154,6 +1154,34 @@ describe('ChannelList', () => {
|
|
|
1154
1154
|
expect(querySpy).not.toHaveBeenCalled();
|
|
1155
1155
|
});
|
|
1156
1156
|
|
|
1157
|
+
it('loads the first page when an event put a channel in the list before it was ever queried', async () => {
|
|
1158
|
+
const seededChannel = chatClient.channel(testChannel1.channel.type, testChannel1.channel.id);
|
|
1159
|
+
// What `message.new` / `member.updated` do for a channel matching a list nobody queried yet.
|
|
1160
|
+
chatClient.channelManager.ingestChannel(seededChannel);
|
|
1161
|
+
expect(props.paginator.items?.map((channel) => channel.id)).toEqual([
|
|
1162
|
+
testChannel1.channel.id,
|
|
1163
|
+
]);
|
|
1164
|
+
expect(props.paginator.isInitialized).toBe(false);
|
|
1165
|
+
|
|
1166
|
+
useMockedApis(chatClient, [queryChannelsApi([testChannel2, testChannel1])]);
|
|
1167
|
+
const querySpy = jest.spyOn(chatClient, 'queryChannels');
|
|
1168
|
+
render(
|
|
1169
|
+
<Chat client={chatClient}>
|
|
1170
|
+
<WithComponents overrides={{ ChannelPreview: ChannelPreviewComponent }}>
|
|
1171
|
+
<ChannelList {...props} />
|
|
1172
|
+
</WithComponents>
|
|
1173
|
+
</Chat>,
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
// The seeded channel does not count as a loaded first page, so the list queries on mount.
|
|
1177
|
+
await act(async () => {
|
|
1178
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1179
|
+
});
|
|
1180
|
+
expect(querySpy).toHaveBeenCalledTimes(1);
|
|
1181
|
+
await waitFor(() => expect(screen.getByTestId(testChannel2.channel.id)).toBeTruthy());
|
|
1182
|
+
expect(props.paginator.isInitialized).toBe(true);
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1157
1185
|
it('leaves the paginator registered and loaded when it unmounts', async () => {
|
|
1158
1186
|
useMockedApis(chatClient, [queryChannelsApi([testChannel1])]);
|
|
1159
1187
|
|
|
@@ -95,7 +95,7 @@ export const usePaginatedChannels = ({ paginator }: { paginator: ChannelPaginato
|
|
|
95
95
|
// new paginator, and after `client.disconnectUser` resets it. A paginator that already has
|
|
96
96
|
// channels is shown as is, and a load in flight is left alone.
|
|
97
97
|
const userId = useClientUserId(client);
|
|
98
|
-
const isUnloaded = channels === undefined;
|
|
98
|
+
const isUnloaded = channels === undefined || !paginator.isInitialized;
|
|
99
99
|
useEffect(() => {
|
|
100
100
|
if (!isUnloaded || !userId || paginator.isLoading) return;
|
|
101
101
|
reloadList();
|
|
@@ -103,6 +103,14 @@ export const ChannelMessagePreviewDeliveryStatus = ({
|
|
|
103
103
|
);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
if (message.type === 'deleted') {
|
|
107
|
+
return (
|
|
108
|
+
<Text style={styles.text} testID='channel-preview-sender'>
|
|
109
|
+
{t('common.you.label', 'You')}:
|
|
110
|
+
</Text>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
106
114
|
return (
|
|
107
115
|
<CompositeAccessibilityProbe label={statusLabel}>
|
|
108
116
|
<View style={styles.container}>
|
|
@@ -55,7 +55,6 @@ export const ChannelPreviewMessage = (props: ChannelPreviewMessageProps) => {
|
|
|
55
55
|
const lastMessageStatus = lastMessage && 'status' in lastMessage ? lastMessage.status : undefined;
|
|
56
56
|
const isFailedMessage =
|
|
57
57
|
lastMessageStatus === MessageStatusTypes.FAILED || lastMessage?.type === 'error';
|
|
58
|
-
const showMessageDeliveryStatus = !isMessageDeleted;
|
|
59
58
|
|
|
60
59
|
if (usersTyping.length > 0) {
|
|
61
60
|
return <ChannelPreviewTypingIndicator channel={channel} usersTyping={usersTyping} />;
|
|
@@ -103,18 +102,14 @@ export const ChannelPreviewMessage = (props: ChannelPreviewMessageProps) => {
|
|
|
103
102
|
if (channel.data?.custom?.name || membersWithoutSelf.length > 1) {
|
|
104
103
|
return (
|
|
105
104
|
<View style={styles.container}>
|
|
106
|
-
{
|
|
107
|
-
<ChannelPreviewMessageDeliveryStatus channel={channel} message={lastMessage} />
|
|
108
|
-
) : null}
|
|
105
|
+
<ChannelPreviewMessageDeliveryStatus channel={channel} message={lastMessage} />
|
|
109
106
|
<ChannelPreviewLastMessage message={lastMessage} />
|
|
110
107
|
</View>
|
|
111
108
|
);
|
|
112
109
|
} else {
|
|
113
110
|
return (
|
|
114
111
|
<View style={styles.container}>
|
|
115
|
-
{
|
|
116
|
-
<ChannelPreviewMessageDeliveryStatus channel={channel} message={lastMessage} />
|
|
117
|
-
) : null}
|
|
112
|
+
<ChannelPreviewMessageDeliveryStatus channel={channel} message={lastMessage} />
|
|
118
113
|
<ChannelPreviewLastMessage message={lastMessage} />
|
|
119
114
|
</View>
|
|
120
115
|
);
|
|
@@ -126,6 +126,47 @@ describe('ChannelPreviewView', () => {
|
|
|
126
126
|
await waitFor(() => screen.queryByText(expectedDisplayName));
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
+
describe('the sender of the latest message', () => {
|
|
130
|
+
const otherUser = generateUser({ name: 'Count Dooku' });
|
|
131
|
+
|
|
132
|
+
const renderWithLatestMessage = async (latestMessage: ReturnType<typeof generateMessage>) => {
|
|
133
|
+
await initializeChannel(
|
|
134
|
+
generateChannelResponse({
|
|
135
|
+
channel: { custom: { name: 'group' } },
|
|
136
|
+
members: [generateMember({ user: clientUser }), generateMember({ user: otherUser })],
|
|
137
|
+
messages: [latestMessage],
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
render(
|
|
141
|
+
<OverlayProvider accessibility={{ enabled: true }}>
|
|
142
|
+
<Chat client={chatClient}>
|
|
143
|
+
<ChannelPreviewView
|
|
144
|
+
channel={channel as ChannelType}
|
|
145
|
+
lastMessage={latestMessage}
|
|
146
|
+
onSelect={jest.fn()}
|
|
147
|
+
/>
|
|
148
|
+
</Chat>
|
|
149
|
+
</OverlayProvider>,
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
it.each([
|
|
154
|
+
['an own message', clientUser, 'regular', 'You:', 1],
|
|
155
|
+
['an own deleted message', clientUser, 'deleted', 'You:', 0],
|
|
156
|
+
['a deleted message from another member', otherUser, 'deleted', 'Count Dooku:', 0],
|
|
157
|
+
] as const)('names the sender of %s', async (_, user, type, prefix, statusLabels) => {
|
|
158
|
+
await renderWithLatestMessage(generateMessage({ status: 'received', type, user }));
|
|
159
|
+
|
|
160
|
+
// An own message's status hides its sender text from screen readers, so hidden elements count.
|
|
161
|
+
const sender = () =>
|
|
162
|
+
screen.getByTestId('channel-preview-sender', { includeHiddenElements: true });
|
|
163
|
+
await waitFor(() => expect(sender()).toBeTruthy());
|
|
164
|
+
expect(sender().props.children.join('')).toBe(prefix);
|
|
165
|
+
// The delivery status announces itself with "Sent by you" or "Read, sent by you".
|
|
166
|
+
expect(screen.queryAllByLabelText(/sent by you/i)).toHaveLength(statusLabels);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
129
170
|
it('should render latest message, truncated to length given by latestMessageLength', async () => {
|
|
130
171
|
const message = generateMessage();
|
|
131
172
|
await initializeChannel(generateChannelResponse());
|
|
@@ -47,7 +47,10 @@ import {
|
|
|
47
47
|
MessageOverlayRuntimeProvider,
|
|
48
48
|
MessageProvider,
|
|
49
49
|
} from '../../contexts/messageContext/MessageContext';
|
|
50
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
GoToMessage,
|
|
52
|
+
useMessageListItemContext,
|
|
53
|
+
} from '../../contexts/messageListItemContext/MessageListItemContext';
|
|
51
54
|
import {
|
|
52
55
|
MessagesContextValue,
|
|
53
56
|
useMessagesContext,
|
|
@@ -223,7 +226,7 @@ export type MessagePropsWithContext = Pick<
|
|
|
223
226
|
messagesContext: MessagesContextValue;
|
|
224
227
|
/** Whether long pressing a message is enabled. Defaults to true. */
|
|
225
228
|
enableLongPress?: boolean;
|
|
226
|
-
goToMessage?:
|
|
229
|
+
goToMessage?: GoToMessage;
|
|
227
230
|
isTargetedMessage?: boolean;
|
|
228
231
|
/**
|
|
229
232
|
* Called with the message's thread when its replies button is pressed.
|
|
@@ -2,8 +2,6 @@ import React, { useMemo } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
4
4
|
|
|
5
|
-
import { useChannelContext } from '../../../../contexts/channelContext/ChannelContext';
|
|
6
|
-
import { useChatContext } from '../../../../contexts/chatContext/ChatContext';
|
|
7
5
|
import { useComponentsContext } from '../../../../contexts/componentsContext/ComponentsContext';
|
|
8
6
|
import { useMessageContext } from '../../../../contexts/messageContext/MessageContext';
|
|
9
7
|
import { useMessagesContext } from '../../../../contexts/messagesContext/MessagesContext';
|
|
@@ -15,6 +13,7 @@ import {
|
|
|
15
13
|
import { useTranslationContext } from '../../../../contexts/translationContext/TranslationContext';
|
|
16
14
|
import { useStableCallback } from '../../../../hooks';
|
|
17
15
|
import { primitives } from '../../../../theme';
|
|
16
|
+
import { useOpenThreadAtMessage } from '../../hooks/useOpenThreadAtMessage';
|
|
18
17
|
import { useShouldUseOverlayStyles } from '../../hooks/useShouldUseOverlayStyles';
|
|
19
18
|
|
|
20
19
|
type SentToChannelHeaderPropsWithContext = Pick<ThreadContextValue, 'thread'> & {
|
|
@@ -67,10 +66,9 @@ export type SentToChannelHeaderProps = Partial<SentToChannelHeaderPropsWithConte
|
|
|
67
66
|
|
|
68
67
|
export const SentToChannelHeader = (props: SentToChannelHeaderProps) => {
|
|
69
68
|
const { onAlsoSentToChannelHeaderPress } = useMessagesContext();
|
|
70
|
-
const { client } = useChatContext();
|
|
71
|
-
const { channel } = useChannelContext();
|
|
72
69
|
const { thread } = useThreadContext();
|
|
73
70
|
const { message } = useMessageContext();
|
|
71
|
+
const openThreadAtMessage = useOpenThreadAtMessage();
|
|
74
72
|
|
|
75
73
|
const handleOnPress = useStableCallback(async () => {
|
|
76
74
|
if (thread) {
|
|
@@ -78,34 +76,8 @@ export const SentToChannelHeader = (props: SentToChannelHeaderProps) => {
|
|
|
78
76
|
return;
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// The parent is only fetched when neither its thread nor the message itself is loaded. Opening
|
|
87
|
-
// the thread loads whatever else it is missing.
|
|
88
|
-
const registeredThread = client.threads.get(parentId);
|
|
89
|
-
if (registeredThread) {
|
|
90
|
-
onAlsoSentToChannelHeaderPress?.({ targetedMessageId: message.id, thread: registeredThread });
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
const parentMessage =
|
|
96
|
-
client.messageStore.get(parentId) ??
|
|
97
|
-
(await channel.getMessagesById([parentId])).messages?.[0];
|
|
98
|
-
|
|
99
|
-
if (!parentMessage) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
onAlsoSentToChannelHeaderPress?.({
|
|
104
|
-
targetedMessageId: message.id,
|
|
105
|
-
thread: client.threads.ensure({ channel, parentMessage }),
|
|
106
|
-
});
|
|
107
|
-
} catch (error) {
|
|
108
|
-
console.error('Error querying parent message:', error);
|
|
79
|
+
if (message.parent_id) {
|
|
80
|
+
await openThreadAtMessage({ parentId: message.parent_id, targetedMessageId: message.id });
|
|
109
81
|
}
|
|
110
82
|
});
|
|
111
83
|
|
|
@@ -254,14 +254,15 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
|
|
|
254
254
|
|
|
255
255
|
if (onPress) {
|
|
256
256
|
onPress({
|
|
257
|
-
defaultHandler: () =>
|
|
257
|
+
defaultHandler: () =>
|
|
258
|
+
goToMessage(message.quoted_message!.id, message.quoted_message!),
|
|
258
259
|
emitter: 'messageContent',
|
|
259
260
|
event,
|
|
260
261
|
});
|
|
261
262
|
return;
|
|
262
263
|
}
|
|
263
264
|
|
|
264
|
-
goToMessage(message.quoted_message.id);
|
|
265
|
+
goToMessage(message.quoted_message.id, message.quoted_message);
|
|
265
266
|
}}
|
|
266
267
|
onPressIn={(event) => {
|
|
267
268
|
if (onPressIn) {
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
3
|
|
|
4
|
+
import type { ChannelConfig } from 'stream-chat';
|
|
5
|
+
|
|
4
6
|
import { useA11yLabel } from '../../../a11y/hooks/useA11yLabel';
|
|
7
|
+
import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
|
|
5
8
|
import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext';
|
|
6
9
|
import {
|
|
7
10
|
MessageContextValue,
|
|
8
11
|
useMessageContext,
|
|
9
12
|
} from '../../../contexts/messageContext/MessageContext';
|
|
10
13
|
import { useTheme } from '../../../contexts/themeContext/ThemeContext';
|
|
14
|
+
import { useStateStore } from '../../../hooks/useStateStore';
|
|
11
15
|
import { primitives } from '../../../theme';
|
|
12
16
|
import { MessageStatusTypes } from '../../../utils/utils';
|
|
13
17
|
import { HiddenA11yText } from '../../Accessibility/HiddenA11yText';
|
|
@@ -15,15 +19,27 @@ import { useIsMessageDeliveredToOthers } from '../hooks/useIsMessageDeliveredToO
|
|
|
15
19
|
import { useIsMessageReadByOthers } from '../hooks/useIsMessageReadByOthers';
|
|
16
20
|
import { useShouldUseOverlayStyles } from '../hooks/useShouldUseOverlayStyles';
|
|
17
21
|
|
|
22
|
+
const readEventsSelector = ({ readEvents }: ChannelConfig) => ({
|
|
23
|
+
readEventsEnabled: readEvents.enabled,
|
|
24
|
+
});
|
|
25
|
+
|
|
18
26
|
export type MessageStatusPropsWithContext = Pick<MessageContextValue, 'message'> & {
|
|
19
27
|
/** Whether the message reached at least one other member. */
|
|
20
28
|
delivered: boolean;
|
|
21
29
|
/** Whether at least one other member read the message. */
|
|
22
30
|
read: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether the channel has read events. Without them only the sending status shows, as in the
|
|
33
|
+
* channel preview.
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
readEventsEnabled?: boolean;
|
|
23
37
|
};
|
|
24
38
|
|
|
25
39
|
const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => {
|
|
26
|
-
const {
|
|
40
|
+
const { message, readEventsEnabled = true } = props;
|
|
41
|
+
const delivered = readEventsEnabled && props.delivered;
|
|
42
|
+
const read = readEventsEnabled && props.read;
|
|
27
43
|
|
|
28
44
|
const styles = useStyles();
|
|
29
45
|
const { icons } = useComponentsContext();
|
|
@@ -38,6 +54,7 @@ const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => {
|
|
|
38
54
|
|
|
39
55
|
const sending = message.status === MessageStatusTypes.SENDING;
|
|
40
56
|
const sent =
|
|
57
|
+
readEventsEnabled &&
|
|
41
58
|
message.status === MessageStatusTypes.RECEIVED &&
|
|
42
59
|
!delivered &&
|
|
43
60
|
!read &&
|
|
@@ -55,7 +72,11 @@ const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => {
|
|
|
55
72
|
: '',
|
|
56
73
|
);
|
|
57
74
|
|
|
58
|
-
if (
|
|
75
|
+
if (
|
|
76
|
+
message.status === MessageStatusTypes.FAILED ||
|
|
77
|
+
message.type === 'error' ||
|
|
78
|
+
message.type === 'deleted'
|
|
79
|
+
) {
|
|
59
80
|
return null;
|
|
60
81
|
}
|
|
61
82
|
|
|
@@ -109,8 +130,23 @@ const areEqual = (
|
|
|
109
130
|
prevProps: MessageStatusPropsWithContext,
|
|
110
131
|
nextProps: MessageStatusPropsWithContext,
|
|
111
132
|
) => {
|
|
112
|
-
const {
|
|
113
|
-
|
|
133
|
+
const {
|
|
134
|
+
delivered: prevDelivered,
|
|
135
|
+
message: prevMessage,
|
|
136
|
+
read: prevRead,
|
|
137
|
+
readEventsEnabled: prevReadEventsEnabled,
|
|
138
|
+
} = prevProps;
|
|
139
|
+
const {
|
|
140
|
+
delivered: nextDelivered,
|
|
141
|
+
message: nextMessage,
|
|
142
|
+
read: nextRead,
|
|
143
|
+
readEventsEnabled: nextReadEventsEnabled,
|
|
144
|
+
} = nextProps;
|
|
145
|
+
|
|
146
|
+
const readEventsEnabledEqual = prevReadEventsEnabled === nextReadEventsEnabled;
|
|
147
|
+
if (!readEventsEnabledEqual) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
114
150
|
|
|
115
151
|
const deliveredEqual = prevDelivered === nextDelivered;
|
|
116
152
|
if (!deliveredEqual) {
|
|
@@ -141,14 +177,17 @@ export type MessageStatusProps = Partial<MessageStatusPropsWithContext>;
|
|
|
141
177
|
export const MessageStatus = (props: MessageStatusProps) => {
|
|
142
178
|
const { message: contextMessage } = useMessageContext();
|
|
143
179
|
const message = props.message ?? contextMessage;
|
|
180
|
+
const { channel } = useChannelContext();
|
|
144
181
|
const readByOthers = useIsMessageReadByOthers({ message });
|
|
145
182
|
const deliveredToOthers = useIsMessageDeliveredToOthers({ message });
|
|
183
|
+
const { readEventsEnabled } = useStateStore(channel?.configState, readEventsSelector) ?? {};
|
|
146
184
|
|
|
147
185
|
return (
|
|
148
186
|
<MemoizedMessageStatus
|
|
149
187
|
delivered={props.delivered ?? deliveredToOthers}
|
|
150
188
|
message={message}
|
|
151
189
|
read={props.read ?? readByOthers}
|
|
190
|
+
readEventsEnabled={props.readEventsEnabled ?? readEventsEnabled}
|
|
152
191
|
/>
|
|
153
192
|
);
|
|
154
193
|
};
|