stream-chat-react-native-core 6.0.2-beta.1 → 6.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +1 -1
  2. package/lib/commonjs/components/Channel/Channel.js +371 -279
  3. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  4. package/lib/commonjs/components/Channel/hooks/useChannelDataState.js +8 -0
  5. package/lib/commonjs/components/Channel/hooks/useChannelDataState.js.map +1 -1
  6. package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js +10 -1
  7. package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
  8. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js +4 -0
  9. package/lib/commonjs/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  10. package/lib/commonjs/components/Channel/hooks/useMessageListPagination.js +161 -69
  11. package/lib/commonjs/components/Channel/hooks/useMessageListPagination.js.map +1 -1
  12. package/lib/commonjs/components/Channel/hooks/useTargetedMessage.js +10 -0
  13. package/lib/commonjs/components/Channel/hooks/useTargetedMessage.js.map +1 -1
  14. package/lib/commonjs/components/Chat/hooks/handleEventToSyncDB.js +81 -54
  15. package/lib/commonjs/components/Chat/hooks/handleEventToSyncDB.js.map +1 -1
  16. package/lib/commonjs/components/Message/Message.js +6 -0
  17. package/lib/commonjs/components/Message/Message.js.map +1 -1
  18. package/lib/commonjs/components/Message/hooks/useMessageActionHandlers.js +117 -79
  19. package/lib/commonjs/components/Message/hooks/useMessageActionHandlers.js.map +1 -1
  20. package/lib/commonjs/components/Message/hooks/useMessageActions.js +32 -14
  21. package/lib/commonjs/components/Message/hooks/useMessageActions.js.map +1 -1
  22. package/lib/commonjs/components/Message/utils/messageActions.js +4 -0
  23. package/lib/commonjs/components/Message/utils/messageActions.js.map +1 -1
  24. package/lib/commonjs/components/MessageList/InlineUnreadIndicator.js +19 -55
  25. package/lib/commonjs/components/MessageList/InlineUnreadIndicator.js.map +1 -1
  26. package/lib/commonjs/components/MessageList/MessageList.js +249 -211
  27. package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
  28. package/lib/commonjs/components/MessageList/UnreadMessagesNotification.js +148 -0
  29. package/lib/commonjs/components/MessageList/UnreadMessagesNotification.js.map +1 -0
  30. package/lib/commonjs/components/MessageMenu/MessageActionListItem.js.map +1 -1
  31. package/lib/commonjs/contexts/channelContext/ChannelContext.js.map +1 -1
  32. package/lib/commonjs/contexts/messagesContext/MessagesContext.js.map +1 -1
  33. package/lib/commonjs/contexts/themeContext/utils/theme.js +7 -1
  34. package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
  35. package/lib/commonjs/i18n/en.json +2 -0
  36. package/lib/commonjs/i18n/es.json +2 -0
  37. package/lib/commonjs/i18n/fr.json +2 -0
  38. package/lib/commonjs/i18n/he.json +2 -0
  39. package/lib/commonjs/i18n/hi.json +2 -0
  40. package/lib/commonjs/i18n/it.json +2 -0
  41. package/lib/commonjs/i18n/ja.json +2 -0
  42. package/lib/commonjs/i18n/ko.json +2 -0
  43. package/lib/commonjs/i18n/nl.json +2 -0
  44. package/lib/commonjs/i18n/pt-br.json +2 -0
  45. package/lib/commonjs/i18n/ru.json +2 -0
  46. package/lib/commonjs/i18n/tr.json +2 -0
  47. package/lib/commonjs/icons/UnreadIndicator.js +30 -0
  48. package/lib/commonjs/icons/UnreadIndicator.js.map +1 -0
  49. package/lib/commonjs/icons/index.js +11 -0
  50. package/lib/commonjs/icons/index.js.map +1 -1
  51. package/lib/commonjs/store/SqliteClient.js +1 -1
  52. package/lib/commonjs/store/schema.js +1 -0
  53. package/lib/commonjs/store/schema.js.map +1 -1
  54. package/lib/commonjs/types/types.js.map +1 -1
  55. package/lib/commonjs/utils/utils.js +35 -1
  56. package/lib/commonjs/utils/utils.js.map +1 -1
  57. package/lib/commonjs/version.json +1 -1
  58. package/lib/module/components/Channel/Channel.js +371 -279
  59. package/lib/module/components/Channel/Channel.js.map +1 -1
  60. package/lib/module/components/Channel/hooks/useChannelDataState.js +8 -0
  61. package/lib/module/components/Channel/hooks/useChannelDataState.js.map +1 -1
  62. package/lib/module/components/Channel/hooks/useCreateChannelContext.js +10 -1
  63. package/lib/module/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
  64. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js +4 -0
  65. package/lib/module/components/Channel/hooks/useCreateMessagesContext.js.map +1 -1
  66. package/lib/module/components/Channel/hooks/useMessageListPagination.js +161 -69
  67. package/lib/module/components/Channel/hooks/useMessageListPagination.js.map +1 -1
  68. package/lib/module/components/Channel/hooks/useTargetedMessage.js +10 -0
  69. package/lib/module/components/Channel/hooks/useTargetedMessage.js.map +1 -1
  70. package/lib/module/components/Chat/hooks/handleEventToSyncDB.js +81 -54
  71. package/lib/module/components/Chat/hooks/handleEventToSyncDB.js.map +1 -1
  72. package/lib/module/components/Message/Message.js +6 -0
  73. package/lib/module/components/Message/Message.js.map +1 -1
  74. package/lib/module/components/Message/hooks/useMessageActionHandlers.js +117 -79
  75. package/lib/module/components/Message/hooks/useMessageActionHandlers.js.map +1 -1
  76. package/lib/module/components/Message/hooks/useMessageActions.js +32 -14
  77. package/lib/module/components/Message/hooks/useMessageActions.js.map +1 -1
  78. package/lib/module/components/Message/utils/messageActions.js +4 -0
  79. package/lib/module/components/Message/utils/messageActions.js.map +1 -1
  80. package/lib/module/components/MessageList/InlineUnreadIndicator.js +19 -55
  81. package/lib/module/components/MessageList/InlineUnreadIndicator.js.map +1 -1
  82. package/lib/module/components/MessageList/MessageList.js +249 -211
  83. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  84. package/lib/module/components/MessageList/UnreadMessagesNotification.js +148 -0
  85. package/lib/module/components/MessageList/UnreadMessagesNotification.js.map +1 -0
  86. package/lib/module/components/MessageMenu/MessageActionListItem.js.map +1 -1
  87. package/lib/module/contexts/channelContext/ChannelContext.js.map +1 -1
  88. package/lib/module/contexts/messagesContext/MessagesContext.js.map +1 -1
  89. package/lib/module/contexts/themeContext/utils/theme.js +7 -1
  90. package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
  91. package/lib/module/i18n/en.json +2 -0
  92. package/lib/module/i18n/es.json +2 -0
  93. package/lib/module/i18n/fr.json +2 -0
  94. package/lib/module/i18n/he.json +2 -0
  95. package/lib/module/i18n/hi.json +2 -0
  96. package/lib/module/i18n/it.json +2 -0
  97. package/lib/module/i18n/ja.json +2 -0
  98. package/lib/module/i18n/ko.json +2 -0
  99. package/lib/module/i18n/nl.json +2 -0
  100. package/lib/module/i18n/pt-br.json +2 -0
  101. package/lib/module/i18n/ru.json +2 -0
  102. package/lib/module/i18n/tr.json +2 -0
  103. package/lib/module/icons/UnreadIndicator.js +30 -0
  104. package/lib/module/icons/UnreadIndicator.js.map +1 -0
  105. package/lib/module/icons/index.js +11 -0
  106. package/lib/module/icons/index.js.map +1 -1
  107. package/lib/module/store/SqliteClient.js +1 -1
  108. package/lib/module/store/schema.js +1 -0
  109. package/lib/module/store/schema.js.map +1 -1
  110. package/lib/module/types/types.js.map +1 -1
  111. package/lib/module/utils/utils.js +35 -1
  112. package/lib/module/utils/utils.js.map +1 -1
  113. package/lib/module/version.json +1 -1
  114. package/lib/typescript/components/Channel/Channel.d.ts +15 -3
  115. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  116. package/lib/typescript/components/Channel/hooks/useChannelDataState.d.ts +1 -0
  117. package/lib/typescript/components/Channel/hooks/useChannelDataState.d.ts.map +1 -1
  118. package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts +1 -1
  119. package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts.map +1 -1
  120. package/lib/typescript/components/Channel/hooks/useCreateMessagesContext.d.ts +4 -2
  121. package/lib/typescript/components/Channel/hooks/useCreateMessagesContext.d.ts.map +1 -1
  122. package/lib/typescript/components/Channel/hooks/useMessageListPagination.d.ts +4 -1
  123. package/lib/typescript/components/Channel/hooks/useMessageListPagination.d.ts.map +1 -1
  124. package/lib/typescript/components/Channel/hooks/useTargetedMessage.d.ts +2 -1
  125. package/lib/typescript/components/Channel/hooks/useTargetedMessage.d.ts.map +1 -1
  126. package/lib/typescript/components/Chat/hooks/handleEventToSyncDB.d.ts.map +1 -1
  127. package/lib/typescript/components/Message/Message.d.ts +2 -1
  128. package/lib/typescript/components/Message/Message.d.ts.map +1 -1
  129. package/lib/typescript/components/Message/hooks/useMessageActionHandlers.d.ts +2 -1
  130. package/lib/typescript/components/Message/hooks/useMessageActionHandlers.d.ts.map +1 -1
  131. package/lib/typescript/components/Message/hooks/useMessageActions.d.ts +3 -2
  132. package/lib/typescript/components/Message/hooks/useMessageActions.d.ts.map +1 -1
  133. package/lib/typescript/components/Message/hooks/useMessageData.d.ts +1 -1
  134. package/lib/typescript/components/Message/utils/messageActions.d.ts +2 -1
  135. package/lib/typescript/components/Message/utils/messageActions.d.ts.map +1 -1
  136. package/lib/typescript/components/MessageList/InlineUnreadIndicator.d.ts.map +1 -1
  137. package/lib/typescript/components/MessageList/MessageList.d.ts +1 -1
  138. package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
  139. package/lib/typescript/components/MessageList/UnreadMessagesNotification.d.ts +13 -0
  140. package/lib/typescript/components/MessageList/UnreadMessagesNotification.d.ts.map +1 -0
  141. package/lib/typescript/components/MessageMenu/MessageActionListItem.d.ts +2 -2
  142. package/lib/typescript/components/MessageMenu/MessageActionListItem.d.ts.map +1 -1
  143. package/lib/typescript/contexts/channelContext/ChannelContext.d.ts +25 -8
  144. package/lib/typescript/contexts/channelContext/ChannelContext.d.ts.map +1 -1
  145. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts +5 -0
  146. package/lib/typescript/contexts/messagesContext/MessagesContext.d.ts.map +1 -1
  147. package/lib/typescript/contexts/themeContext/utils/theme.d.ts +6 -0
  148. package/lib/typescript/contexts/themeContext/utils/theme.d.ts.map +1 -1
  149. package/lib/typescript/i18n/en.json +2 -0
  150. package/lib/typescript/i18n/es.json +2 -0
  151. package/lib/typescript/i18n/fr.json +2 -0
  152. package/lib/typescript/i18n/he.json +2 -0
  153. package/lib/typescript/i18n/hi.json +2 -0
  154. package/lib/typescript/i18n/it.json +2 -0
  155. package/lib/typescript/i18n/ja.json +2 -0
  156. package/lib/typescript/i18n/ko.json +2 -0
  157. package/lib/typescript/i18n/nl.json +2 -0
  158. package/lib/typescript/i18n/pt-br.json +2 -0
  159. package/lib/typescript/i18n/ru.json +2 -0
  160. package/lib/typescript/i18n/tr.json +2 -0
  161. package/lib/typescript/icons/UnreadIndicator.d.ts +8 -0
  162. package/lib/typescript/icons/UnreadIndicator.d.ts.map +1 -0
  163. package/lib/typescript/icons/index.d.ts +1 -0
  164. package/lib/typescript/icons/index.d.ts.map +1 -1
  165. package/lib/typescript/store/mappers/mapStorableToChannel.d.ts +1 -1
  166. package/lib/typescript/store/schema.d.ts +1 -0
  167. package/lib/typescript/store/schema.d.ts.map +1 -1
  168. package/lib/typescript/types/types.d.ts +2 -1
  169. package/lib/typescript/types/types.d.ts.map +1 -1
  170. package/lib/typescript/utils/i18n/Streami18n.d.ts +2 -0
  171. package/lib/typescript/utils/i18n/Streami18n.d.ts.map +1 -1
  172. package/lib/typescript/utils/utils.d.ts +21 -1
  173. package/lib/typescript/utils/utils.d.ts.map +1 -1
  174. package/package.json +1 -1
  175. package/src/components/Channel/Channel.tsx +101 -24
  176. package/src/components/Channel/__tests__/Channel.test.js +109 -58
  177. package/src/components/Channel/__tests__/ownCapabilities.test.js +26 -0
  178. package/src/components/Channel/__tests__/useMessageListPagination.test.js +234 -37
  179. package/src/components/Channel/hooks/useChannelDataState.ts +8 -0
  180. package/src/components/Channel/hooks/useCreateChannelContext.ts +11 -0
  181. package/src/components/Channel/hooks/useCreateMessagesContext.ts +4 -0
  182. package/src/components/Channel/hooks/useMessageListPagination.tsx +134 -64
  183. package/src/components/Channel/hooks/useTargetedMessage.ts +9 -2
  184. package/src/components/Chat/hooks/handleEventToSyncDB.ts +23 -1
  185. package/src/components/Message/Message.tsx +8 -0
  186. package/src/components/Message/hooks/useMessageActionHandlers.ts +54 -40
  187. package/src/components/Message/hooks/useMessageActions.tsx +31 -14
  188. package/src/components/Message/utils/messageActions.ts +6 -0
  189. package/src/components/MessageList/InlineUnreadIndicator.tsx +17 -26
  190. package/src/components/MessageList/MessageList.tsx +197 -231
  191. package/src/components/MessageList/UnreadMessagesNotification.tsx +107 -0
  192. package/src/components/MessageList/__tests__/MessageList.test.js +213 -0
  193. package/src/components/MessageMenu/MessageActionListItem.tsx +2 -1
  194. package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +669 -679
  195. package/src/contexts/channelContext/ChannelContext.tsx +35 -9
  196. package/src/contexts/messagesContext/MessagesContext.tsx +7 -2
  197. package/src/contexts/themeContext/utils/theme.ts +12 -0
  198. package/src/i18n/en.json +2 -0
  199. package/src/i18n/es.json +2 -0
  200. package/src/i18n/fr.json +2 -0
  201. package/src/i18n/he.json +2 -0
  202. package/src/i18n/hi.json +2 -0
  203. package/src/i18n/it.json +2 -0
  204. package/src/i18n/ja.json +2 -0
  205. package/src/i18n/ko.json +2 -0
  206. package/src/i18n/nl.json +2 -0
  207. package/src/i18n/pt-br.json +2 -0
  208. package/src/i18n/ru.json +2 -0
  209. package/src/i18n/tr.json +2 -0
  210. package/src/icons/UnreadIndicator.tsx +18 -0
  211. package/src/icons/index.ts +1 -0
  212. package/src/store/SqliteClient.ts +1 -1
  213. package/src/store/schema.ts +2 -0
  214. package/src/types/types.ts +5 -2
  215. package/src/utils/utils.ts +61 -1
  216. package/src/version.json +1 -1
@@ -6,6 +6,7 @@ import {
6
6
  ScrollViewProps,
7
7
  StyleSheet,
8
8
  View,
9
+ ViewabilityConfig,
9
10
  ViewToken,
10
11
  } from 'react-native';
11
12
 
@@ -54,7 +55,9 @@ import { ThreadContextValue, useThreadContext } from '../../contexts/threadConte
54
55
 
55
56
  import { DefaultStreamChatGenerics, FileTypes } from '../../types/types';
56
57
 
57
- const WAIT_FOR_SCROLL_TO_OFFSET_TIMEOUT = 150;
58
+ // This is just to make sure that the scrolling happens in a different task queue.
59
+ // TODO: Think if we really need this and strive to remove it if we can.
60
+ const WAIT_FOR_SCROLL_TIMEOUT = 0;
58
61
  const MAX_RETRIES_AFTER_SCROLL_FAILURE = 10;
59
62
  const styles = StyleSheet.create({
60
63
  container: {
@@ -63,7 +66,6 @@ const styles = StyleSheet.create({
63
66
  width: '100%',
64
67
  },
65
68
  contentContainer: {
66
- flexGrow: 1,
67
69
  /**
68
70
  * paddingBottom is set to 4 to account for the default date
69
71
  * header and inline indicator alignment. The top margin is 8
@@ -99,7 +101,7 @@ const keyExtractor = <
99
101
  return Date.now().toString();
100
102
  };
101
103
 
102
- const flatListViewabilityConfig = {
104
+ const flatListViewabilityConfig: ViewabilityConfig = {
103
105
  viewAreaCoveragePercentThreshold: 1,
104
106
  };
105
107
 
@@ -109,9 +111,11 @@ type MessageListPropsWithContext<
109
111
  Pick<
110
112
  ChannelContextValue<StreamChatGenerics>,
111
113
  | 'channel'
114
+ | 'channelUnreadState'
112
115
  | 'disabled'
113
116
  | 'EmptyStateIndicator'
114
117
  | 'hideStickyDateHeader'
118
+ | 'highlightedMessageId'
115
119
  | 'loadChannelAroundMessage'
116
120
  | 'loading'
117
121
  | 'LoadingIndicator'
@@ -133,7 +137,6 @@ type MessageListPropsWithContext<
133
137
  | 'DateHeader'
134
138
  | 'disableTypingIndicator'
135
139
  | 'FlatList'
136
- | 'initialScrollToFirstUnreadMessage'
137
140
  | 'InlineDateSeparator'
138
141
  | 'InlineUnreadIndicator'
139
142
  | 'legacyImageViewerSwipeBehaviour'
@@ -144,6 +147,7 @@ type MessageListPropsWithContext<
144
147
  | 'shouldShowUnreadUnderlay'
145
148
  | 'TypingIndicator'
146
149
  | 'TypingIndicatorContainer'
150
+ | 'UnreadMessagesNotification'
147
151
  > &
148
152
  Pick<
149
153
  ThreadContextValue<StreamChatGenerics>,
@@ -228,6 +232,7 @@ const MessageListWithContext = <
228
232
  const {
229
233
  additionalFlatListProps,
230
234
  channel,
235
+ channelUnreadState,
231
236
  client,
232
237
  closePicker,
233
238
  DateHeader,
@@ -238,7 +243,7 @@ const MessageListWithContext = <
238
243
  FooterComponent = InlineLoadingMoreIndicator,
239
244
  HeaderComponent = LoadingMoreRecentIndicator,
240
245
  hideStickyDateHeader,
241
- initialScrollToFirstUnreadMessage,
246
+ highlightedMessageId,
242
247
  InlineDateSeparator,
243
248
  InlineUnreadIndicator,
244
249
  inverted = true,
@@ -275,8 +280,9 @@ const MessageListWithContext = <
275
280
  threadList = false,
276
281
  TypingIndicator,
277
282
  TypingIndicatorContainer,
283
+ UnreadMessagesNotification,
278
284
  } = props;
279
-
285
+ const [isUnreadNotificationOpen, setIsUnreadNotificationOpen] = useState<boolean>(false);
280
286
  const { theme } = useTheme();
281
287
 
282
288
  const {
@@ -333,12 +339,6 @@ const MessageListWithContext = <
333
339
 
334
340
  const flatListRef = useRef<FlatListType<MessageType<StreamChatGenerics>> | null>(null);
335
341
 
336
- /**
337
- * Flag to track if the initial scroll has been set
338
- * If the prop `initialScrollToFirstUnreadMessage` was enabled, then we scroll to the unread msg and set it to true
339
- * If not, the default offset of 0 for flatList means that it has been set already
340
- */
341
- const [isInitialScrollDone, setInitialScrollDone] = useState(!initialScrollToFirstUnreadMessage);
342
342
  const channelResyncScrollSet = useRef<boolean>(true);
343
343
 
344
344
  /**
@@ -346,11 +346,6 @@ const MessageListWithContext = <
346
346
  */
347
347
  const scrollToDebounceTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
348
348
 
349
- /**
350
- * The timeout id used to lazier load the initial scroll set flag
351
- */
352
- const initialScrollSettingTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
353
-
354
349
  /**
355
350
  * The timeout id used to temporarily load the initial scroll set flag
356
351
  */
@@ -375,11 +370,18 @@ const MessageListWithContext = <
375
370
  channelRef.current = channel;
376
371
 
377
372
  const updateStickyHeaderDateIfNeeded = (viewableItems: ViewToken[]) => {
378
- if (viewableItems.length) {
379
- const lastItem = viewableItems.pop() as {
380
- item: MessageType<StreamChatGenerics>;
381
- };
373
+ if (!viewableItems.length) return;
374
+
375
+ const lastItem = viewableItems[viewableItems.length - 1];
382
376
 
377
+ if (lastItem) {
378
+ if (
379
+ !channel.state.messagePagination.hasPrev &&
380
+ processedMessageList[processedMessageList.length - 1].id === lastItem.item.id
381
+ ) {
382
+ setStickyHeaderDate(undefined);
383
+ return;
384
+ }
383
385
  const isMessageTypeDeleted = lastItem.item.type === 'deleted';
384
386
 
385
387
  if (
@@ -395,32 +397,60 @@ const MessageListWithContext = <
395
397
  };
396
398
 
397
399
  /**
398
- * FlatList doesn't accept changeable function for onViewableItemsChanged prop.
399
- * Thus useRef.
400
+ * This function should show or hide the unread indicator depending on the
400
401
  */
401
- const onViewableItemsChanged = useRef(
402
- ({ viewableItems }: { viewableItems: ViewToken[] | undefined }) => {
403
- /**
404
- * When a new message comes in, list scrolls down to the bottom automatically (using prop `maintainVisibleContentPosition`)
405
- * and we mark the channel as read from handleScroll function.
406
- * Although this logic is dependent on the fact that `onScroll` event gets triggered during this process.
407
- * But for Android, this event is not triggered when messages length is lesser than visible screen height.
408
- *
409
- * And thus we need to check if the message list length is lesser than visible screen height and mark the channel as read.
410
- */
402
+ const updateStickyUnreadIndicator = (viewableItems: ViewToken[]) => {
403
+ if (!viewableItems.length) {
404
+ setIsUnreadNotificationOpen(false);
405
+ return;
406
+ }
407
+
408
+ if (selectedPicker === 'images') {
409
+ setIsUnreadNotificationOpen(false);
410
+ return;
411
+ }
412
+
413
+ const lastItem = viewableItems[viewableItems.length - 1];
414
+
415
+ if (lastItem) {
416
+ const lastItemCreatedAt = lastItem.item.created_at;
417
+
418
+ const unreadIndicatorDate = channelUnreadState?.last_read.getTime();
419
+ const lastItemDate = lastItemCreatedAt.getTime();
420
+
411
421
  if (
412
- Platform.OS === 'android' &&
413
- viewableItems?.length &&
414
- viewableItems?.length >= messageListLengthBeforeUpdate.current
422
+ !channel.state.messagePagination.hasPrev &&
423
+ processedMessageList[processedMessageList.length - 1].id === lastItem.item.id
415
424
  ) {
416
- channel.markRead();
425
+ setIsUnreadNotificationOpen(false);
426
+ return;
417
427
  }
418
428
 
419
- if (viewableItems && !hideStickyDateHeader) {
420
- updateStickyHeaderDateIfNeeded(viewableItems);
429
+ if (unreadIndicatorDate && lastItemDate > unreadIndicatorDate) {
430
+ setIsUnreadNotificationOpen(true);
431
+ } else {
432
+ setIsUnreadNotificationOpen(false);
421
433
  }
422
- },
423
- );
434
+ }
435
+ };
436
+
437
+ /**
438
+ * FlatList doesn't accept changeable function for onViewableItemsChanged prop.
439
+ * Thus useRef.
440
+ */
441
+ const onViewableItemsChanged = ({
442
+ viewableItems,
443
+ }: {
444
+ viewableItems: ViewToken[] | undefined;
445
+ }) => {
446
+ if (!viewableItems) {
447
+ return;
448
+ }
449
+ if (!hideStickyDateHeader) {
450
+ updateStickyHeaderDateIfNeeded(viewableItems);
451
+ }
452
+ updateStickyUnreadIndicator(viewableItems);
453
+ };
424
454
 
425
455
  /**
426
456
  * Resets the pagination trackers, doing so cancels currently scheduled loading more calls
@@ -440,40 +470,19 @@ const MessageListWithContext = <
440
470
  * Effect to mark the channel as read when the user scrolls to the bottom of the message list.
441
471
  */
442
472
  useEffect(() => {
443
- const getShouldMarkReadAutomatically = (): boolean => {
444
- if (loading || !channel) {
445
- // nothing to do
446
- return false;
447
- } else if (channel.countUnread() > 0) {
448
- if (!initialScrollToFirstUnreadMessage) {
449
- /*
450
- * In this case MessageList won't scroll to first unread message when opened, so we can mark
451
- * the channel as read right after opening.
452
- * */
453
- return true;
454
- } else {
455
- /*
456
- * In this case MessageList will be opened to first unread message.
457
- * But if there are were not enough unread messages, so that scrollToBottom button was not shown
458
- * then MessageList won't need to scroll up. So we can safely mark the channel as read right after opening.
459
- *
460
- * NOTE: we must ensure that initial scroll is done, otherwise we do not wait till the unread scroll is finished
461
- * */
462
- if (scrollToBottomButtonVisible) return false;
463
- /* if scrollToBottom button was not visible, wait till
464
- * - initial scroll is done (indicates that if scrolling to index was needed it was triggered)
465
- * */
466
- return isInitialScrollDone;
467
- }
473
+ const listener: ReturnType<typeof channel.on> = channel.on('message.new', (event) => {
474
+ const newMessageToCurrentChannel = event.cid === channel.cid;
475
+ const mainChannelUpdated = !event.message?.parent_id || event.message?.show_in_channel;
476
+
477
+ if (newMessageToCurrentChannel && mainChannelUpdated && !scrollToBottomButtonVisible) {
478
+ markRead();
468
479
  }
469
- return false;
470
- };
480
+ });
471
481
 
472
- if (getShouldMarkReadAutomatically()) {
473
- markRead();
474
- }
475
- // eslint-disable-next-line react-hooks/exhaustive-deps
476
- }, [loading, scrollToBottomButtonVisible, isInitialScrollDone]);
482
+ return () => {
483
+ listener?.unsubscribe();
484
+ };
485
+ }, [channel, markRead, scrollToBottomButtonVisible]);
477
486
 
478
487
  useEffect(() => {
479
488
  const lastReceivedMessage = getLastReceivedMessage(processedMessageList);
@@ -487,6 +496,7 @@ const MessageListWithContext = <
487
496
  if (!client || !channel || rawMessageList.length === 0) {
488
497
  return;
489
498
  }
499
+
490
500
  /**
491
501
  * Condition to check if a message is removed from MessageList.
492
502
  * Eg: This would happen when giphy search is cancelled, etc.
@@ -508,7 +518,7 @@ const MessageListWithContext = <
508
518
  flatListRef.current?.scrollToOffset({
509
519
  offset: 0,
510
520
  });
511
- }, 50);
521
+ }, WAIT_FOR_SCROLL_TIMEOUT);
512
522
  setTimeout(() => {
513
523
  channelResyncScrollSet.current = true;
514
524
  if (channel.countUnread() > 0) {
@@ -518,10 +528,9 @@ const MessageListWithContext = <
518
528
  }
519
529
  };
520
530
 
531
+ // TODO: Think about if this is really needed?
521
532
  if (threadList) {
522
533
  scrollToBottomIfNeeded();
523
- } else {
524
- setScrollToBottomButtonVisible(false);
525
534
  }
526
535
 
527
536
  messageListLengthBeforeUpdate.current = messageListLengthAfterUpdate;
@@ -566,12 +575,74 @@ const MessageListWithContext = <
566
575
  animated: true,
567
576
  offset: 0,
568
577
  });
569
- }, 150); // flatlist might take a bit to update, so a small delay is needed
578
+ }, WAIT_FOR_SCROLL_TIMEOUT); // flatlist might take a bit to update, so a small delay is needed
570
579
  }
571
580
  }
572
581
  // eslint-disable-next-line react-hooks/exhaustive-deps
573
582
  }, [rawMessageList, threadList]);
574
583
 
584
+ const goToMessage = async (messageId: string) => {
585
+ const indexOfParentInMessageList = processedMessageList.findIndex(
586
+ (message) => message?.id === messageId,
587
+ );
588
+ try {
589
+ if (indexOfParentInMessageList === -1) {
590
+ await loadChannelAroundMessage({ messageId });
591
+ return;
592
+ } else {
593
+ if (!flatListRef.current) return;
594
+ clearTimeout(failScrollTimeoutId.current);
595
+ scrollToIndexFailedRetryCountRef.current = 0;
596
+ // keep track of this messageId, so that we dont scroll to again in useEffect for targeted message change
597
+ messageIdLastScrolledToRef.current = messageId;
598
+ setTargetedMessage(messageId);
599
+ // now scroll to it with animated=true
600
+ flatListRef.current.scrollToIndex({
601
+ animated: true,
602
+ index: indexOfParentInMessageList,
603
+ viewPosition: 0.5, // try to place message in the center of the screen
604
+ });
605
+ return;
606
+ }
607
+ } catch (e) {
608
+ console.warn('Error while scrolling to message', e);
609
+ }
610
+ };
611
+
612
+ /**
613
+ * Check if a messageId needs to be scrolled to after list loads, and scroll to it
614
+ * Note: This effect fires on every list change with a small debounce so that scrolling isnt abrupted by an immediate rerender
615
+ */
616
+ useEffect(() => {
617
+ if (!targetedMessage) return;
618
+ scrollToDebounceTimeoutRef.current = setTimeout(async () => {
619
+ const indexOfParentInMessageList = processedMessageList.findIndex(
620
+ (message) => message?.id === targetedMessage,
621
+ );
622
+
623
+ // the message we want to scroll to has not been loaded in the state yet
624
+ if (indexOfParentInMessageList === -1) {
625
+ await loadChannelAroundMessage({ messageId: targetedMessage, setTargetedMessage });
626
+ } else {
627
+ if (!flatListRef.current) return;
628
+ // By a fresh scroll we should clear the retries for the previous failed scroll
629
+ clearTimeout(scrollToDebounceTimeoutRef.current);
630
+ clearTimeout(failScrollTimeoutId.current);
631
+ // reset the retry count
632
+ scrollToIndexFailedRetryCountRef.current = 0;
633
+ // now scroll to it
634
+ flatListRef.current.scrollToIndex({
635
+ animated: true,
636
+ index: indexOfParentInMessageList,
637
+ viewPosition: 0.5, // try to place message in the center of the screen
638
+ });
639
+ setTargetedMessage(undefined);
640
+ }
641
+ }, WAIT_FOR_SCROLL_TIMEOUT);
642
+
643
+ // eslint-disable-next-line react-hooks/exhaustive-deps
644
+ }, [targetedMessage]);
645
+
575
646
  // TODO: do not apply on RN 0.73 and above
576
647
  const shouldApplyAndroidWorkaround = inverted && Platform.OS === 'android';
577
648
 
@@ -585,53 +656,20 @@ const MessageListWithContext = <
585
656
  if (!channel || channel.disconnected || (!channel.initialized && !channel.offlineMode))
586
657
  return null;
587
658
 
588
- const unreadCount = channel.countUnread();
589
- const lastRead = channel.lastRead();
590
-
591
- function isMessageUnread(messageArrayIndex: number): boolean {
592
- const isLatestMessageSetShown = !!channel.state.messageSets.find(
593
- (set) => set.isCurrent && set.isLatest,
594
- );
659
+ const createdAtTimestamp = message.created_at && new Date(message.created_at).getTime();
660
+ const lastReadTimestamp = channelUnreadState?.last_read.getTime();
661
+ const isNewestMessage = index === 0;
662
+ const isLastReadMessage =
663
+ channelUnreadState?.last_read_message_id === message.id ||
664
+ (!channelUnreadState?.unread_messages && createdAtTimestamp === lastReadTimestamp);
595
665
 
596
- if (!isLatestMessageSetShown) {
597
- const msg = processedMessageList?.[messageArrayIndex];
598
- if (
599
- channel.state.latestMessages.length !== 0 &&
600
- unreadCount > channel.state.latestMessages.length
601
- ) {
602
- return messageArrayIndex <= unreadCount - channel.state.latestMessages.length - 1;
603
- }
604
- // The `msg` can be undefined here, since `messageArrayIndex` can be out of bounds hence we add a check for `msg`.
605
- else if (lastRead && msg?.created_at) {
606
- return lastRead < msg.created_at;
607
- }
608
- return false;
609
- } else {
610
- return messageArrayIndex <= unreadCount - 1;
611
- }
612
- }
666
+ const showUnreadSeparator =
667
+ isLastReadMessage &&
668
+ !isNewestMessage &&
669
+ // The `channelUnreadState?.first_unread_message_id` is here for sent messages unread label
670
+ (!!channelUnreadState?.first_unread_message_id || !!channelUnreadState?.unread_messages);
613
671
 
614
- const isCurrentMessageUnread = isMessageUnread(index);
615
- const showUnreadUnderlay =
616
- !!shouldShowUnreadUnderlay &&
617
- !channel.muteStatus().muted &&
618
- isCurrentMessageUnread &&
619
- scrollToBottomButtonVisible;
620
- const insertInlineUnreadIndicator = showUnreadUnderlay && !isMessageUnread(index + 1); // show only if previous message is read
621
-
622
- if (message.type === 'system') {
623
- return (
624
- <View style={[shouldApplyAndroidWorkaround ? styles.invertAndroid : undefined]}>
625
- <View testID={`message-list-item-${index}`}>
626
- <MessageSystem
627
- message={message}
628
- style={[{ paddingHorizontal: screenPadding }, messageContainer]}
629
- />
630
- </View>
631
- {insertInlineUnreadIndicator && <InlineUnreadIndicator />}
632
- </View>
633
- );
634
- }
672
+ const showUnreadUnderlay = !!shouldShowUnreadUnderlay && showUnreadSeparator;
635
673
 
636
674
  const wrapMessageInTheme = client.userID === message.user?.id && !!myMessageTheme;
637
675
  const renderDateSeperator = isMessageWithStylesReadByAndDateSeparator(message) &&
@@ -640,7 +678,7 @@ const MessageListWithContext = <
640
678
  <Message
641
679
  goToMessage={goToMessage}
642
680
  groupStyles={isMessageWithStylesReadByAndDateSeparator(message) ? message.groupStyles : []}
643
- isTargetedMessage={targetedMessage === message.id}
681
+ isTargetedMessage={highlightedMessageId === message.id}
644
682
  lastReceivedId={
645
683
  lastReceivedId === message.id || message.quoted_message_id ? lastReceivedId : undefined
646
684
  }
@@ -651,33 +689,32 @@ const MessageListWithContext = <
651
689
  threadList={threadList}
652
690
  />
653
691
  );
692
+
654
693
  return (
655
- <>
656
- {wrapMessageInTheme ? (
694
+ <View
695
+ style={[shouldApplyAndroidWorkaround ? styles.invertAndroid : undefined]}
696
+ testID={`message-list-item-${index}`}
697
+ >
698
+ {message.type === 'system' ? (
699
+ <MessageSystem
700
+ message={message}
701
+ style={[{ paddingHorizontal: screenPadding }, messageContainer]}
702
+ />
703
+ ) : wrapMessageInTheme ? (
657
704
  <ThemeProvider mergedStyle={modifiedTheme}>
658
- <View
659
- style={[shouldApplyAndroidWorkaround ? styles.invertAndroid : undefined]}
660
- testID={`message-list-item-${index}`}
661
- >
662
- {shouldApplyAndroidWorkaround && renderDateSeperator}
705
+ <View testID={`message-list-item-${index}`}>
706
+ {renderDateSeperator}
663
707
  {renderMessage}
664
708
  </View>
665
709
  </ThemeProvider>
666
710
  ) : (
667
- <View
668
- style={[shouldApplyAndroidWorkaround ? styles.invertAndroid : undefined]}
669
- testID={`message-list-item-${index}`}
670
- >
671
- {shouldApplyAndroidWorkaround && renderDateSeperator}
711
+ <View testID={`message-list-item-${index}`}>
712
+ {renderDateSeperator}
672
713
  {renderMessage}
673
714
  </View>
674
715
  )}
675
- {!shouldApplyAndroidWorkaround && renderDateSeperator}
676
- {/* Adding indicator below the messages, since the list is inverted */}
677
- <View style={[shouldApplyAndroidWorkaround ? styles.invertAndroid : undefined]}>
678
- {insertInlineUnreadIndicator && <InlineUnreadIndicator />}
679
- </View>
680
- </>
716
+ {showUnreadUnderlay && <InlineUnreadIndicator />}
717
+ </View>
681
718
  );
682
719
  };
683
720
 
@@ -803,8 +840,8 @@ const MessageListWithContext = <
803
840
  };
804
841
 
805
842
  const handleScroll: ScrollViewProps['onScroll'] = (event) => {
806
- const offset = event.nativeEvent.contentOffset.y;
807
843
  const messageListHasMessages = processedMessageList.length > 0;
844
+ const offset = event.nativeEvent.contentOffset.y;
808
845
  // Show scrollToBottom button once scroll position goes beyond 150.
809
846
  const isScrollAtBottom = offset <= 150;
810
847
 
@@ -821,14 +858,6 @@ const MessageListWithContext = <
821
858
  */
822
859
  setScrollToBottomButtonVisible(showScrollToBottomButton);
823
860
 
824
- const shouldMarkRead = !threadList && !notLatestSet && offset <= 0 && channel.countUnread() > 0;
825
-
826
- if (shouldMarkRead) {
827
- markRead();
828
- }
829
-
830
- setInitialScrollDone(false);
831
-
832
861
  if (onListScroll) {
833
862
  onListScroll(event);
834
863
  }
@@ -842,14 +871,12 @@ const MessageListWithContext = <
842
871
  await reloadChannel();
843
872
  } else if (flatListRef.current) {
844
873
  flatListRef.current.scrollToOffset({
874
+ animated: true,
845
875
  offset: 0,
846
876
  });
847
877
  }
848
878
 
849
879
  setScrollToBottomButtonVisible(false);
850
- if (!threadList) {
851
- markRead();
852
- }
853
880
  };
854
881
 
855
882
  const scrollToIndexFailedRetryCountRef = useRef<number>(0);
@@ -860,16 +887,12 @@ const MessageListWithContext = <
860
887
  // We got a failure as we tried to scroll to an item that was outside the render length
861
888
  if (!flatListRef.current) return;
862
889
  // we don't know the actual size of all items but we can see the average, so scroll to the closest offset
863
- flatListRef.current.scrollToOffset({
864
- animated: false,
865
- offset: info.averageItemLength * info.index,
866
- });
867
890
  // since we used only an average offset... we won't go to the center of the item yet
868
891
  // with a little delay to wait for scroll to offset to complete, we can then scroll to the index
869
892
  failScrollTimeoutId.current = setTimeout(() => {
870
893
  try {
871
894
  flatListRef.current?.scrollToIndex({
872
- animated: false,
895
+ animated: true,
873
896
  index: info.index,
874
897
  viewPosition: 0.5, // try to place message in the center of the screen
875
898
  });
@@ -894,81 +917,12 @@ const MessageListWithContext = <
894
917
  scrollToIndexFailedRetryCountRef.current += 1;
895
918
  onScrollToIndexFailedRef.current(info);
896
919
  }
897
- }, WAIT_FOR_SCROLL_TO_OFFSET_TIMEOUT);
920
+ }, WAIT_FOR_SCROLL_TIMEOUT);
898
921
 
899
922
  // Only when index is greater than 0 and in range of items in FlatList
900
923
  // this onScrollToIndexFailed will be called again
901
924
  });
902
925
 
903
- const goToMessage = async (messageId: string) => {
904
- const indexOfParentInMessageList = processedMessageList.findIndex(
905
- (message) => message?.id === messageId,
906
- );
907
- if (indexOfParentInMessageList !== -1 && flatListRef.current) {
908
- clearTimeout(failScrollTimeoutId.current);
909
- scrollToIndexFailedRetryCountRef.current = 0;
910
- // keep track of this messageId, so that we dont scroll to again in useEffect for targeted message change
911
- messageIdLastScrolledToRef.current = messageId;
912
- setTargetedMessage(messageId);
913
- // now scroll to it with animated=true (in useEffect animated=false is used)
914
- flatListRef.current.scrollToIndex({
915
- animated: true,
916
- index: indexOfParentInMessageList,
917
- viewPosition: 0.5, // try to place message in the center of the screen
918
- });
919
- return;
920
- }
921
- // the message we want was not loaded yet, so lets load it
922
- await loadChannelAroundMessage({ messageId });
923
- };
924
-
925
- /**
926
- * Check if a messageId needs to be scrolled to after list loads, and scroll to it
927
- * Note: This effect fires on every list change with a small debounce so that scrolling isnt abrupted by an immediate rerender
928
- */
929
- useEffect(() => {
930
- scrollToDebounceTimeoutRef.current = setTimeout(() => {
931
- if (initialScrollToFirstUnreadMessage) {
932
- clearTimeout(initialScrollSettingTimeoutRef.current);
933
- initialScrollSettingTimeoutRef.current = setTimeout(() => {
934
- // small timeout to ensure that handleScroll is called after scrollToIndex to set this flag
935
- setInitialScrollDone(true);
936
- }, 2000);
937
- }
938
- let messageIdToScroll: string | undefined;
939
- if (targetedMessage && messageIdLastScrolledToRef.current !== targetedMessage) {
940
- // if some messageId was targeted but not scrolledTo yet
941
- // we have scroll to there after loading completes
942
- messageIdToScroll = targetedMessage;
943
- }
944
- if (!messageIdToScroll) return;
945
- const indexOfParentInMessageList = processedMessageList.findIndex(
946
- (message) => message?.id === messageIdToScroll,
947
- );
948
- if (indexOfParentInMessageList !== -1 && flatListRef.current) {
949
- // By a fresh scroll we should clear the retries for the previous failed scroll
950
- clearTimeout(scrollToDebounceTimeoutRef.current);
951
- clearTimeout(failScrollTimeoutId.current);
952
- // keep track of this messageId, so that we dont scroll to again for targeted message change
953
- messageIdLastScrolledToRef.current = messageIdToScroll;
954
- // reset the retry count
955
- scrollToIndexFailedRetryCountRef.current = 0;
956
- // now scroll to it
957
- flatListRef.current.scrollToIndex({
958
- animated: false,
959
- index: indexOfParentInMessageList,
960
- viewPosition: 0.5, // try to place message in the center of the screen
961
- });
962
- }
963
-
964
- // the message we want to scroll to has not been loaded in the state yet
965
- if (indexOfParentInMessageList === -1) {
966
- loadChannelAroundMessage({ messageId: messageIdToScroll });
967
- }
968
- }, 50);
969
- // eslint-disable-next-line react-hooks/exhaustive-deps
970
- }, [targetedMessage, initialScrollToFirstUnreadMessage]);
971
-
972
926
  const messagesWithImages =
973
927
  legacyImageViewerSwipeBehaviour &&
974
928
  processedMessageList.filter((message) => {
@@ -1047,6 +1001,11 @@ const MessageListWithContext = <
1047
1001
  }
1048
1002
  };
1049
1003
 
1004
+ const onUnreadNotificationClose = async () => {
1005
+ await markRead();
1006
+ setIsUnreadNotificationOpen(false);
1007
+ };
1008
+
1050
1009
  const debugRef = useDebugContext();
1051
1010
 
1052
1011
  const isDebugModeEnabled = __DEV__ && debugRef && debugRef.current;
@@ -1155,7 +1114,7 @@ const MessageListWithContext = <
1155
1114
  onScrollEndDrag={onScrollEndDrag}
1156
1115
  onScrollToIndexFailed={onScrollToIndexFailedRef.current}
1157
1116
  onTouchEnd={dismissImagePicker}
1158
- onViewableItemsChanged={onViewableItemsChanged.current}
1117
+ onViewableItemsChanged={onViewableItemsChanged}
1159
1118
  ref={refCallback}
1160
1119
  renderItem={renderItem}
1161
1120
  scrollEnabled={overlay === 'none'}
@@ -1187,6 +1146,9 @@ const MessageListWithContext = <
1187
1146
  unreadCount={threadList ? 0 : channel?.countUnread()}
1188
1147
  />
1189
1148
  <NetworkDownIndicator />
1149
+ {isUnreadNotificationOpen && !threadList ? (
1150
+ <UnreadMessagesNotification onCloseHandler={onUnreadNotificationClose} />
1151
+ ) : null}
1190
1152
  </View>
1191
1153
  );
1192
1154
  };
@@ -1203,11 +1165,13 @@ export const MessageList = <
1203
1165
  const { closePicker, selectedPicker, setSelectedPicker } = useAttachmentPickerContext();
1204
1166
  const {
1205
1167
  channel,
1168
+ channelUnreadState,
1206
1169
  disabled,
1207
1170
  EmptyStateIndicator,
1208
1171
  enableMessageGroupingByUser,
1209
1172
  error,
1210
1173
  hideStickyDateHeader,
1174
+ highlightedMessageId,
1211
1175
  isChannelActive,
1212
1176
  loadChannelAroundMessage,
1213
1177
  loading,
@@ -1227,7 +1191,6 @@ export const MessageList = <
1227
1191
  DateHeader,
1228
1192
  disableTypingIndicator,
1229
1193
  FlatList,
1230
- initialScrollToFirstUnreadMessage,
1231
1194
  InlineDateSeparator,
1232
1195
  InlineUnreadIndicator,
1233
1196
  legacyImageViewerSwipeBehaviour,
@@ -1238,6 +1201,7 @@ export const MessageList = <
1238
1201
  shouldShowUnreadUnderlay,
1239
1202
  TypingIndicator,
1240
1203
  TypingIndicatorContainer,
1204
+ UnreadMessagesNotification,
1241
1205
  } = useMessagesContext<StreamChatGenerics>();
1242
1206
  const { loadMore, loadMoreRecent } = usePaginatedMessageListContext<StreamChatGenerics>();
1243
1207
  const { overlay } = useOverlayContext();
@@ -1248,6 +1212,7 @@ export const MessageList = <
1248
1212
  <MessageListWithContext
1249
1213
  {...{
1250
1214
  channel,
1215
+ channelUnreadState,
1251
1216
  client,
1252
1217
  closePicker,
1253
1218
  DateHeader,
@@ -1258,7 +1223,7 @@ export const MessageList = <
1258
1223
  error,
1259
1224
  FlatList,
1260
1225
  hideStickyDateHeader,
1261
- initialScrollToFirstUnreadMessage,
1226
+ highlightedMessageId,
1262
1227
  InlineDateSeparator,
1263
1228
  InlineUnreadIndicator,
1264
1229
  isListActive: isChannelActive,
@@ -1291,6 +1256,7 @@ export const MessageList = <
1291
1256
  threadList,
1292
1257
  TypingIndicator,
1293
1258
  TypingIndicatorContainer,
1259
+ UnreadMessagesNotification,
1294
1260
  }}
1295
1261
  {...props}
1296
1262
  noGroupByUser={!enableMessageGroupingByUser || props.noGroupByUser}