stream-chat-react-native-core 9.5.2 → 9.6.0-beta.2

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 (42) hide show
  1. package/README.md +1 -1
  2. package/lib/commonjs/components/Channel/Channel.js +37 -25
  3. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  4. package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js +1 -6
  5. package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
  6. package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewData.js +8 -5
  7. package/lib/commonjs/components/ChannelPreview/hooks/useChannelPreviewData.js.map +1 -1
  8. package/lib/commonjs/components/MessageList/MessageFlashList.js +2 -1
  9. package/lib/commonjs/components/MessageList/MessageFlashList.js.map +1 -1
  10. package/lib/commonjs/components/MessageList/MessageList.js +2 -1
  11. package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
  12. package/lib/commonjs/contexts/channelContext/ChannelContext.js.map +1 -1
  13. package/lib/commonjs/version.json +1 -1
  14. package/lib/module/components/Channel/Channel.js +37 -25
  15. package/lib/module/components/Channel/Channel.js.map +1 -1
  16. package/lib/module/components/Channel/hooks/useCreateChannelContext.js +1 -6
  17. package/lib/module/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
  18. package/lib/module/components/ChannelPreview/hooks/useChannelPreviewData.js +8 -5
  19. package/lib/module/components/ChannelPreview/hooks/useChannelPreviewData.js.map +1 -1
  20. package/lib/module/components/MessageList/MessageFlashList.js +2 -1
  21. package/lib/module/components/MessageList/MessageFlashList.js.map +1 -1
  22. package/lib/module/components/MessageList/MessageList.js +2 -1
  23. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  24. package/lib/module/contexts/channelContext/ChannelContext.js.map +1 -1
  25. package/lib/module/version.json +1 -1
  26. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  27. package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts +1 -1
  28. package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts.map +1 -1
  29. package/lib/typescript/components/ChannelPreview/hooks/useChannelPreviewData.d.ts.map +1 -1
  30. package/lib/typescript/components/MessageList/MessageFlashList.d.ts.map +1 -1
  31. package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
  32. package/lib/typescript/contexts/channelContext/ChannelContext.d.ts +0 -2
  33. package/lib/typescript/contexts/channelContext/ChannelContext.d.ts.map +1 -1
  34. package/package.json +2 -2
  35. package/src/components/Channel/Channel.tsx +33 -10
  36. package/src/components/Channel/hooks/useCreateChannelContext.ts +0 -6
  37. package/src/components/ChannelPreview/hooks/useChannelPreviewData.ts +8 -2
  38. package/src/components/MessageList/MessageFlashList.tsx +6 -1
  39. package/src/components/MessageList/MessageList.tsx +6 -1
  40. package/src/components/Thread/__tests__/Thread.test.tsx +5 -3
  41. package/src/contexts/channelContext/ChannelContext.tsx +0 -3
  42. package/src/version.json +1 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stream-chat-react-native-core",
3
3
  "description": "The official React Native and Expo components for Stream Chat, a service for building chat applications",
4
- "version": "9.5.2",
4
+ "version": "9.6.0-beta.2",
5
5
  "author": {
6
6
  "company": "Stream.io Inc",
7
7
  "name": "Stream.io Inc"
@@ -78,7 +78,7 @@
78
78
  "path": "0.12.7",
79
79
  "react-native-markdown-package": "1.8.2",
80
80
  "react-native-url-polyfill": "^2.0.0",
81
- "stream-chat": "^9.48.0",
81
+ "stream-chat": "^9.50.0",
82
82
  "use-sync-external-store": "^1.5.0"
83
83
  },
84
84
  "peerDependencies": {
@@ -511,12 +511,12 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
511
511
  const styles = useStyles();
512
512
  const [deleted, setDeleted] = useState<boolean>(false);
513
513
  const [error, setError] = useState<Error | boolean>(false);
514
- const [lastRead, setLastRead] = useState<Date | undefined>();
514
+ const lastReadRef = useRef<Date | undefined>(undefined);
515
515
  const [thread, setThread] = useState<LocalMessage | null>(threadProps || null);
516
516
  const [threadHasMore, setThreadHasMore] = useState(true);
517
517
  const [threadLoadingMore, setThreadLoadingMore] = useState(false);
518
- const [channelUnreadStateStore] = useState(new ChannelUnreadStateStore());
519
- const [messageInputHeightStore] = useState(new MessageInputHeightStore());
518
+ const [channelUnreadStateStore] = useState(() => new ChannelUnreadStateStore());
519
+ const [messageInputHeightStore] = useState(() => new MessageInputHeightStore());
520
520
  // TODO: Think if we can remove this and just rely on the channelUnreadStateStore everywhere.
521
521
  const setChannelUnreadState = useCallback(
522
522
  (data: ChannelUnreadStateStoreType['channelUnreadState']) => {
@@ -690,6 +690,13 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
690
690
  return;
691
691
  }
692
692
 
693
+ if (event.type === 'message.read_locally') {
694
+ // When local unread reset happens, the count is already updated in the client state,
695
+ // and the preview badge / unread divider are handled elsewhere, so there is nothing
696
+ // to copy into channel state here. Thus, we skip it.
697
+ return;
698
+ }
699
+
693
700
  if (event.type === 'message.read' || event.type === 'notification.mark_read') {
694
701
  setReadThrottled();
695
702
  return;
@@ -703,7 +710,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
703
710
  useEffect(() => {
704
711
  let listener: ReturnType<typeof channel.on>;
705
712
  const initChannel = async () => {
706
- setLastRead(new Date());
713
+ lastReadRef.current = new Date();
707
714
  const unreadCount = channel.countUnread();
708
715
  const shouldLoadAtFirstUnread = shouldLoadInitialChannelAtFirstUnreadMessage(unreadCount);
709
716
  if (!channel || !shouldSyncChannel) {
@@ -812,7 +819,25 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
812
819
  const markReadInternal: ChannelContextValue['markRead'] = throttle(
813
820
  async (options?: MarkReadFunctionOptions) => {
814
821
  const { updateChannelUnreadState = true } = options ?? {};
815
- if (!channel || channel?.disconnected || !clientChannelConfig?.read_events) {
822
+ if (!channel || channel?.disconnected) {
823
+ return;
824
+ }
825
+
826
+ // When read events are disabled (e.g. livestreams) we cannot mark read on the backend. If the
827
+ // client opted into a local unread count, reset it locally instead so the user's "caught up"
828
+ // state is reflected without a server round trip.
829
+ if (!clientChannelConfig?.read_events) {
830
+ if (client.options.isLocalUnreadCountEnabled) {
831
+ const event = channel.markReadLocally();
832
+ if (updateChannelUnreadState && event && lastReadRef.current) {
833
+ setChannelUnreadState({
834
+ last_read: lastReadRef.current,
835
+ last_read_message_id: event.last_read_message_id,
836
+ unread_messages: 0,
837
+ });
838
+ lastReadRef.current = new Date();
839
+ }
840
+ }
816
841
  return;
817
842
  }
818
843
 
@@ -821,13 +846,13 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
821
846
  } else {
822
847
  try {
823
848
  const response = await channel.markRead();
824
- if (updateChannelUnreadState && response && lastRead) {
849
+ if (updateChannelUnreadState && response && lastReadRef.current) {
825
850
  setChannelUnreadState({
826
- last_read: lastRead,
851
+ last_read: lastReadRef.current,
827
852
  last_read_message_id: response?.event.last_read_message_id,
828
853
  unread_messages: 0,
829
854
  });
830
- setLastRead(new Date());
855
+ lastReadRef.current = new Date();
831
856
  }
832
857
  } catch (err) {
833
858
  console.log('Error marking channel as read:', err);
@@ -1578,7 +1603,6 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
1578
1603
  hideStickyDateHeader,
1579
1604
  highlightedMessageId,
1580
1605
  isChannelActive: shouldSyncChannel,
1581
- lastRead,
1582
1606
  loadChannelAroundMessage,
1583
1607
  loadChannelAtFirstUnreadMessage,
1584
1608
  loading: channelMessagesState.loading,
@@ -1590,7 +1614,6 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
1590
1614
  reloadChannel,
1591
1615
  scrollToFirstUnreadThreshold,
1592
1616
  setChannelUnreadState,
1593
- setLastRead,
1594
1617
  setTargetedMessage,
1595
1618
  hasPendingInitialTargetLoad,
1596
1619
  targetedMessage,
@@ -13,7 +13,6 @@ export const useCreateChannelContext = ({
13
13
  hideStickyDateHeader,
14
14
  highlightedMessageId,
15
15
  isChannelActive,
16
- lastRead,
17
16
  loadChannelAroundMessage,
18
17
  loadChannelAtFirstUnreadMessage,
19
18
  loading,
@@ -25,7 +24,6 @@ export const useCreateChannelContext = ({
25
24
  reloadChannel,
26
25
  scrollToFirstUnreadThreshold,
27
26
  setChannelUnreadState,
28
- setLastRead,
29
27
  setTargetedMessage,
30
28
  hasPendingInitialTargetLoad,
31
29
  targetedMessage,
@@ -35,7 +33,6 @@ export const useCreateChannelContext = ({
35
33
  watchers,
36
34
  }: ChannelContextValue) => {
37
35
  const channelId = channel?.id;
38
- const lastReadTime = lastRead?.getTime();
39
36
  const membersLength = Object.keys(members).length;
40
37
 
41
38
  const readUsers = Object.values(read);
@@ -56,7 +53,6 @@ export const useCreateChannelContext = ({
56
53
  hideStickyDateHeader,
57
54
  highlightedMessageId,
58
55
  isChannelActive,
59
- lastRead,
60
56
  loadChannelAroundMessage,
61
57
  loadChannelAtFirstUnreadMessage,
62
58
  loading,
@@ -68,7 +64,6 @@ export const useCreateChannelContext = ({
68
64
  reloadChannel,
69
65
  scrollToFirstUnreadThreshold,
70
66
  setChannelUnreadState,
71
- setLastRead,
72
67
  setTargetedMessage,
73
68
  hasPendingInitialTargetLoad,
74
69
  targetedMessage,
@@ -84,7 +79,6 @@ export const useCreateChannelContext = ({
84
79
  error,
85
80
  isChannelActive,
86
81
  highlightedMessageId,
87
- lastReadTime,
88
82
  loading,
89
83
  membersLength,
90
84
  readUsersLength,
@@ -109,8 +109,14 @@ export const useChannelPreviewData = (
109
109
  setForceUpdate((prev) => prev + 1);
110
110
  }
111
111
  };
112
- const { unsubscribe } = client.on('message.read', handleReadEvent);
113
- return unsubscribe;
112
+ const readSubscription = client.on('message.read', handleReadEvent);
113
+ // `message.read_locally` is the client-only equivalent emitted by `channel.markReadLocally()` when
114
+ // read events are disabled (e.g. livestreams with `isLocalUnreadCountEnabled`).
115
+ const localReadSubscription = client.on('message.read_locally', handleReadEvent);
116
+ return () => {
117
+ readSubscription.unsubscribe();
118
+ localReadSubscription.unsubscribe();
119
+ };
114
120
  }, [client, channel]);
115
121
 
116
122
  /**
@@ -718,9 +718,14 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
718
718
  const lastReadMessageId = channelUnreadState?.last_read_message_id;
719
719
  const lastReadMessageVisible = viewableItems.some((item) => item.item.id === lastReadMessageId);
720
720
 
721
+ // Channels with disabled `read-events` (i.e livestreams) still surface the unread
722
+ // notification when the client opted into a local unread count, so the gate accepts
723
+ // either source.
724
+ const unreadNotificationSupported = readEvents || client.options.isLocalUnreadCountEnabled;
725
+
721
726
  if (
722
727
  !viewableItems.length ||
723
- !readEvents ||
728
+ !unreadNotificationSupported ||
724
729
  lastReadMessageVisible ||
725
730
  attachmentPickerStore.state.getLatestValue().selectedPicker === 'images'
726
731
  ) {
@@ -555,9 +555,14 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
555
555
  (item) => item.item.message.id === lastReadMessageId,
556
556
  );
557
557
 
558
+ // Channels with disabled `read-events` (i.e livestreams) still surface the unread
559
+ // notification when the client opted into a local unread count, so the gate accepts
560
+ // either source.
561
+ const unreadNotificationSupported = readEvents || client.options.isLocalUnreadCountEnabled;
562
+
558
563
  if (
559
564
  !viewableItems.length ||
560
- !readEvents ||
565
+ !unreadNotificationSupported ||
561
566
  lastReadMessageVisible ||
562
567
  attachmentPickerStore.state.getLatestValue().selectedPicker === 'images'
563
568
  ) {
@@ -142,7 +142,9 @@ describe('Thread', () => {
142
142
  threadResponses as unknown as Parameters<typeof channel.state.addMessagesSorted>[0],
143
143
  );
144
144
 
145
- let setLastRead: ((date?: Date) => void) | undefined;
145
+ let setChannelUnreadState:
146
+ | React.ContextType<typeof ChannelContext>['setChannelUnreadState']
147
+ | undefined;
146
148
 
147
149
  const { getByText, toJSON } = render(
148
150
  <ChannelsStateProvider>
@@ -161,7 +163,7 @@ describe('Thread', () => {
161
163
  <Channel channel={channel} thread={thread} threadList>
162
164
  <ChannelContext.Consumer>
163
165
  {(c) => {
164
- setLastRead = c.setLastRead;
166
+ setChannelUnreadState = c.setChannelUnreadState;
165
167
  return <Thread />;
166
168
  }}
167
169
  </ChannelContext.Consumer>
@@ -178,7 +180,7 @@ describe('Thread', () => {
178
180
  expect(getByText('Message6')).toBeTruthy();
179
181
  });
180
182
 
181
- act(() => setLastRead!(new Date('2020-08-17T18:08:03.196Z')));
183
+ act(() => setChannelUnreadState!(undefined));
182
184
 
183
185
  const snapshot = toJSON() as unknown as {
184
186
  children: Array<{
@@ -110,7 +110,6 @@ export type ChannelContextValue = {
110
110
  reloadChannel: () => Promise<void>;
111
111
  scrollToFirstUnreadThreshold: number;
112
112
  setChannelUnreadState: (data: ChannelUnreadStateStoreType['channelUnreadState']) => void;
113
- setLastRead: React.Dispatch<React.SetStateAction<Date | undefined>>;
114
113
  setTargetedMessage: (messageId?: string) => void;
115
114
  /**
116
115
  * Returns true when Channel is about to load an initial targeted message.
@@ -131,8 +130,6 @@ export type ChannelContextValue = {
131
130
  */
132
131
  highlightedMessageId?: string;
133
132
  isChannelActive?: boolean;
134
-
135
- lastRead?: Date;
136
133
  loading?: boolean;
137
134
  /**
138
135
  * Maximum time in milliseconds that should occur between messages
package/src/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "9.5.2"
2
+ "version": "9.6.0-beta.2"
3
3
  }