stream-chat-react-native-core 5.38.1 → 5.39.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 (235) hide show
  1. package/lib/commonjs/components/Channel/Channel.js +58 -25
  2. package/lib/commonjs/components/Channel/Channel.js.map +1 -1
  3. package/lib/commonjs/components/Channel/hooks/useCreateThreadContext.js +33 -21
  4. package/lib/commonjs/components/Channel/hooks/useCreateThreadContext.js.map +1 -1
  5. package/lib/commonjs/components/Chat/Chat.js +7 -0
  6. package/lib/commonjs/components/Chat/Chat.js.map +1 -1
  7. package/lib/commonjs/components/Indicators/EmptyStateIndicator.js +14 -0
  8. package/lib/commonjs/components/Indicators/EmptyStateIndicator.js.map +1 -1
  9. package/lib/commonjs/components/Indicators/LoadingIndicator.js +4 -0
  10. package/lib/commonjs/components/Indicators/LoadingIndicator.js.map +1 -1
  11. package/lib/commonjs/components/Message/hooks/useProcessReactions.js +13 -7
  12. package/lib/commonjs/components/Message/hooks/useProcessReactions.js.map +1 -1
  13. package/lib/commonjs/components/MessageList/InlineLoadingMoreRecentThreadIndicator.js +52 -0
  14. package/lib/commonjs/components/MessageList/InlineLoadingMoreRecentThreadIndicator.js.map +1 -0
  15. package/lib/commonjs/components/MessageList/MessageList.js +14 -6
  16. package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
  17. package/lib/commonjs/components/Thread/Thread.js +12 -5
  18. package/lib/commonjs/components/Thread/Thread.js.map +1 -1
  19. package/lib/commonjs/components/Thread/components/ThreadFooterComponent.js +41 -18
  20. package/lib/commonjs/components/Thread/components/ThreadFooterComponent.js.map +1 -1
  21. package/lib/commonjs/components/ThreadList/ThreadList.js +132 -0
  22. package/lib/commonjs/components/ThreadList/ThreadList.js.map +1 -0
  23. package/lib/commonjs/components/ThreadList/ThreadListItem.js +246 -0
  24. package/lib/commonjs/components/ThreadList/ThreadListItem.js.map +1 -0
  25. package/lib/commonjs/components/ThreadList/ThreadListUnreadBanner.js +66 -0
  26. package/lib/commonjs/components/ThreadList/ThreadListUnreadBanner.js.map +1 -0
  27. package/lib/commonjs/components/index.js +11 -11
  28. package/lib/commonjs/components/index.js.map +1 -1
  29. package/lib/commonjs/contexts/themeContext/utils/theme.js +19 -0
  30. package/lib/commonjs/contexts/themeContext/utils/theme.js.map +1 -1
  31. package/lib/commonjs/contexts/threadContext/ThreadContext.js.map +1 -1
  32. package/lib/commonjs/contexts/threadsContext/ThreadListItemContext.js +28 -0
  33. package/lib/commonjs/contexts/threadsContext/ThreadListItemContext.js.map +1 -0
  34. package/lib/commonjs/contexts/threadsContext/ThreadsContext.js +33 -0
  35. package/lib/commonjs/contexts/threadsContext/ThreadsContext.js.map +1 -0
  36. package/lib/commonjs/hooks/index.js +11 -0
  37. package/lib/commonjs/hooks/index.js.map +1 -1
  38. package/lib/commonjs/hooks/useStateStore.js +23 -0
  39. package/lib/commonjs/hooks/useStateStore.js.map +1 -0
  40. package/lib/commonjs/i18n/en.json +7 -0
  41. package/lib/commonjs/i18n/es.json +7 -0
  42. package/lib/commonjs/i18n/fr.json +7 -0
  43. package/lib/commonjs/i18n/he.json +7 -0
  44. package/lib/commonjs/i18n/hi.json +7 -0
  45. package/lib/commonjs/i18n/it.json +7 -0
  46. package/lib/commonjs/i18n/ja.json +7 -0
  47. package/lib/commonjs/i18n/ko.json +7 -0
  48. package/lib/commonjs/i18n/nl.json +7 -0
  49. package/lib/commonjs/i18n/pt-br.json +7 -0
  50. package/lib/commonjs/i18n/ru.json +7 -0
  51. package/lib/commonjs/i18n/tr.json +7 -0
  52. package/lib/commonjs/icons/MessageBubble.js +19 -0
  53. package/lib/commonjs/icons/MessageBubble.js.map +1 -0
  54. package/lib/commonjs/icons/MessageBubbleEmpty.js +19 -0
  55. package/lib/commonjs/icons/MessageBubbleEmpty.js.map +1 -0
  56. package/lib/commonjs/icons/Reload.js +19 -0
  57. package/lib/commonjs/icons/Reload.js.map +1 -0
  58. package/lib/commonjs/icons/index.js +33 -0
  59. package/lib/commonjs/icons/index.js.map +1 -1
  60. package/lib/commonjs/store/mappers/mapDateTimeToStorable.js.map +1 -1
  61. package/lib/commonjs/version.json +1 -1
  62. package/lib/module/components/Channel/Channel.js +58 -25
  63. package/lib/module/components/Channel/Channel.js.map +1 -1
  64. package/lib/module/components/Channel/hooks/useCreateThreadContext.js +33 -21
  65. package/lib/module/components/Channel/hooks/useCreateThreadContext.js.map +1 -1
  66. package/lib/module/components/Chat/Chat.js +7 -0
  67. package/lib/module/components/Chat/Chat.js.map +1 -1
  68. package/lib/module/components/Indicators/EmptyStateIndicator.js +14 -0
  69. package/lib/module/components/Indicators/EmptyStateIndicator.js.map +1 -1
  70. package/lib/module/components/Indicators/LoadingIndicator.js +4 -0
  71. package/lib/module/components/Indicators/LoadingIndicator.js.map +1 -1
  72. package/lib/module/components/Message/hooks/useProcessReactions.js +13 -7
  73. package/lib/module/components/Message/hooks/useProcessReactions.js.map +1 -1
  74. package/lib/module/components/MessageList/InlineLoadingMoreRecentThreadIndicator.js +52 -0
  75. package/lib/module/components/MessageList/InlineLoadingMoreRecentThreadIndicator.js.map +1 -0
  76. package/lib/module/components/MessageList/MessageList.js +14 -6
  77. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  78. package/lib/module/components/Thread/Thread.js +12 -5
  79. package/lib/module/components/Thread/Thread.js.map +1 -1
  80. package/lib/module/components/Thread/components/ThreadFooterComponent.js +41 -18
  81. package/lib/module/components/Thread/components/ThreadFooterComponent.js.map +1 -1
  82. package/lib/module/components/ThreadList/ThreadList.js +132 -0
  83. package/lib/module/components/ThreadList/ThreadList.js.map +1 -0
  84. package/lib/module/components/ThreadList/ThreadListItem.js +246 -0
  85. package/lib/module/components/ThreadList/ThreadListItem.js.map +1 -0
  86. package/lib/module/components/ThreadList/ThreadListUnreadBanner.js +66 -0
  87. package/lib/module/components/ThreadList/ThreadListUnreadBanner.js.map +1 -0
  88. package/lib/module/components/index.js +11 -11
  89. package/lib/module/components/index.js.map +1 -1
  90. package/lib/module/contexts/themeContext/utils/theme.js +19 -0
  91. package/lib/module/contexts/themeContext/utils/theme.js.map +1 -1
  92. package/lib/module/contexts/threadContext/ThreadContext.js.map +1 -1
  93. package/lib/module/contexts/threadsContext/ThreadListItemContext.js +28 -0
  94. package/lib/module/contexts/threadsContext/ThreadListItemContext.js.map +1 -0
  95. package/lib/module/contexts/threadsContext/ThreadsContext.js +33 -0
  96. package/lib/module/contexts/threadsContext/ThreadsContext.js.map +1 -0
  97. package/lib/module/hooks/index.js +11 -0
  98. package/lib/module/hooks/index.js.map +1 -1
  99. package/lib/module/hooks/useStateStore.js +23 -0
  100. package/lib/module/hooks/useStateStore.js.map +1 -0
  101. package/lib/module/i18n/en.json +7 -0
  102. package/lib/module/i18n/es.json +7 -0
  103. package/lib/module/i18n/fr.json +7 -0
  104. package/lib/module/i18n/he.json +7 -0
  105. package/lib/module/i18n/hi.json +7 -0
  106. package/lib/module/i18n/it.json +7 -0
  107. package/lib/module/i18n/ja.json +7 -0
  108. package/lib/module/i18n/ko.json +7 -0
  109. package/lib/module/i18n/nl.json +7 -0
  110. package/lib/module/i18n/pt-br.json +7 -0
  111. package/lib/module/i18n/ru.json +7 -0
  112. package/lib/module/i18n/tr.json +7 -0
  113. package/lib/module/icons/MessageBubble.js +19 -0
  114. package/lib/module/icons/MessageBubble.js.map +1 -0
  115. package/lib/module/icons/MessageBubbleEmpty.js +19 -0
  116. package/lib/module/icons/MessageBubbleEmpty.js.map +1 -0
  117. package/lib/module/icons/Reload.js +19 -0
  118. package/lib/module/icons/Reload.js.map +1 -0
  119. package/lib/module/icons/index.js +33 -0
  120. package/lib/module/icons/index.js.map +1 -1
  121. package/lib/module/store/mappers/mapDateTimeToStorable.js.map +1 -1
  122. package/lib/module/version.json +1 -1
  123. package/lib/typescript/components/AttachmentPicker/AttachmentPicker.d.ts +1 -1
  124. package/lib/typescript/components/Channel/Channel.d.ts +3 -2
  125. package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
  126. package/lib/typescript/components/Channel/hooks/useCreateThreadContext.d.ts +34 -1
  127. package/lib/typescript/components/Channel/hooks/useCreateThreadContext.d.ts.map +1 -1
  128. package/lib/typescript/components/Chat/Chat.d.ts.map +1 -1
  129. package/lib/typescript/components/Indicators/EmptyStateIndicator.d.ts +1 -1
  130. package/lib/typescript/components/Indicators/EmptyStateIndicator.d.ts.map +1 -1
  131. package/lib/typescript/components/Indicators/LoadingIndicator.d.ts +1 -1
  132. package/lib/typescript/components/Indicators/LoadingIndicator.d.ts.map +1 -1
  133. package/lib/typescript/components/Message/hooks/useProcessReactions.d.ts.map +1 -1
  134. package/lib/typescript/components/MessageList/InlineLoadingMoreRecentThreadIndicator.d.ts +8 -0
  135. package/lib/typescript/components/MessageList/InlineLoadingMoreRecentThreadIndicator.d.ts.map +1 -0
  136. package/lib/typescript/components/MessageList/MessageList.d.ts +1 -1
  137. package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
  138. package/lib/typescript/components/Thread/Thread.d.ts +1 -1
  139. package/lib/typescript/components/Thread/Thread.d.ts.map +1 -1
  140. package/lib/typescript/components/Thread/components/ThreadFooterComponent.d.ts +1 -0
  141. package/lib/typescript/components/Thread/components/ThreadFooterComponent.d.ts.map +1 -1
  142. package/lib/typescript/components/ThreadList/ThreadList.d.ts +11 -0
  143. package/lib/typescript/components/ThreadList/ThreadList.d.ts.map +1 -0
  144. package/lib/typescript/components/ThreadList/ThreadListItem.d.ts +16 -0
  145. package/lib/typescript/components/ThreadList/ThreadListItem.d.ts.map +1 -0
  146. package/lib/typescript/components/ThreadList/ThreadListUnreadBanner.d.ts +3 -0
  147. package/lib/typescript/components/ThreadList/ThreadListUnreadBanner.d.ts.map +1 -0
  148. package/lib/typescript/components/index.d.ts +1 -1
  149. package/lib/typescript/components/index.d.ts.map +1 -1
  150. package/lib/typescript/contexts/attachmentPickerContext/AttachmentPickerContext.d.ts +1 -1
  151. package/lib/typescript/contexts/messageContext/MessageContext.d.ts +1 -1
  152. package/lib/typescript/contexts/messageInputContext/MessageInputContext.d.ts +1 -1
  153. package/lib/typescript/contexts/themeContext/utils/theme.d.ts +19 -0
  154. package/lib/typescript/contexts/themeContext/utils/theme.d.ts.map +1 -1
  155. package/lib/typescript/contexts/threadContext/ThreadContext.d.ts +11 -2
  156. package/lib/typescript/contexts/threadContext/ThreadContext.d.ts.map +1 -1
  157. package/lib/typescript/contexts/threadsContext/ThreadListItemContext.d.ts +19 -0
  158. package/lib/typescript/contexts/threadsContext/ThreadListItemContext.d.ts.map +1 -0
  159. package/lib/typescript/contexts/threadsContext/ThreadsContext.d.ts +25 -0
  160. package/lib/typescript/contexts/threadsContext/ThreadsContext.d.ts.map +1 -0
  161. package/lib/typescript/hooks/index.d.ts +1 -0
  162. package/lib/typescript/hooks/index.d.ts.map +1 -1
  163. package/lib/typescript/hooks/useStateStore.d.ts +4 -0
  164. package/lib/typescript/hooks/useStateStore.d.ts.map +1 -0
  165. package/lib/typescript/i18n/en.json +7 -0
  166. package/lib/typescript/i18n/es.json +7 -0
  167. package/lib/typescript/i18n/fr.json +7 -0
  168. package/lib/typescript/i18n/he.json +7 -0
  169. package/lib/typescript/i18n/hi.json +7 -0
  170. package/lib/typescript/i18n/it.json +7 -0
  171. package/lib/typescript/i18n/ja.json +7 -0
  172. package/lib/typescript/i18n/ko.json +7 -0
  173. package/lib/typescript/i18n/nl.json +7 -0
  174. package/lib/typescript/i18n/pt-br.json +7 -0
  175. package/lib/typescript/i18n/ru.json +7 -0
  176. package/lib/typescript/i18n/tr.json +7 -0
  177. package/lib/typescript/icons/MessageBubble.d.ts +4 -0
  178. package/lib/typescript/icons/MessageBubble.d.ts.map +1 -0
  179. package/lib/typescript/icons/MessageBubbleEmpty.d.ts +4 -0
  180. package/lib/typescript/icons/MessageBubbleEmpty.d.ts.map +1 -0
  181. package/lib/typescript/icons/Reload.d.ts +4 -0
  182. package/lib/typescript/icons/Reload.d.ts.map +1 -0
  183. package/lib/typescript/icons/index.d.ts +3 -0
  184. package/lib/typescript/icons/index.d.ts.map +1 -1
  185. package/lib/typescript/store/mappers/mapDateTimeToStorable.d.ts +1 -1
  186. package/lib/typescript/store/mappers/mapDateTimeToStorable.d.ts.map +1 -1
  187. package/lib/typescript/utils/i18n/Streami18n.d.ts +7 -0
  188. package/lib/typescript/utils/i18n/Streami18n.d.ts.map +1 -1
  189. package/package.json +2 -2
  190. package/src/components/Channel/Channel.tsx +54 -20
  191. package/src/components/Channel/hooks/useCreateThreadContext.ts +36 -31
  192. package/src/components/Chat/Chat.tsx +10 -0
  193. package/src/components/Indicators/EmptyStateIndicator.tsx +9 -2
  194. package/src/components/Indicators/LoadingIndicator.tsx +3 -1
  195. package/src/components/Message/hooks/useProcessReactions.ts +25 -4
  196. package/src/components/MessageList/InlineLoadingMoreRecentThreadIndicator.tsx +64 -0
  197. package/src/components/MessageList/MessageList.tsx +23 -9
  198. package/src/components/Thread/Thread.tsx +18 -9
  199. package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +6 -0
  200. package/src/components/Thread/components/ThreadFooterComponent.tsx +28 -2
  201. package/src/components/ThreadList/ThreadList.tsx +118 -0
  202. package/src/components/ThreadList/ThreadListItem.tsx +268 -0
  203. package/src/components/ThreadList/ThreadListUnreadBanner.tsx +52 -0
  204. package/src/components/index.ts +1 -1
  205. package/src/contexts/themeContext/utils/theme.ts +36 -0
  206. package/src/contexts/threadContext/ThreadContext.tsx +9 -2
  207. package/src/contexts/threadsContext/ThreadListItemContext.tsx +41 -0
  208. package/src/contexts/threadsContext/ThreadsContext.tsx +62 -0
  209. package/src/hooks/index.ts +1 -0
  210. package/src/hooks/useStateStore.ts +31 -0
  211. package/src/i18n/en.json +7 -0
  212. package/src/i18n/es.json +7 -0
  213. package/src/i18n/fr.json +7 -0
  214. package/src/i18n/he.json +7 -0
  215. package/src/i18n/hi.json +7 -0
  216. package/src/i18n/it.json +7 -0
  217. package/src/i18n/ja.json +7 -0
  218. package/src/i18n/ko.json +7 -0
  219. package/src/i18n/nl.json +7 -0
  220. package/src/i18n/pt-br.json +7 -0
  221. package/src/i18n/ru.json +7 -0
  222. package/src/i18n/tr.json +7 -0
  223. package/src/icons/MessageBubble.tsx +12 -0
  224. package/src/icons/MessageBubbleEmpty.tsx +12 -0
  225. package/src/icons/Reload.tsx +12 -0
  226. package/src/icons/index.ts +3 -0
  227. package/src/store/mappers/mapDateTimeToStorable.ts +1 -1
  228. package/src/version.json +1 -1
  229. package/lib/commonjs/components/MessageList/InlineLoadingMoreThreadIndicator.js +0 -52
  230. package/lib/commonjs/components/MessageList/InlineLoadingMoreThreadIndicator.js.map +0 -1
  231. package/lib/module/components/MessageList/InlineLoadingMoreThreadIndicator.js +0 -52
  232. package/lib/module/components/MessageList/InlineLoadingMoreThreadIndicator.js.map +0 -1
  233. package/lib/typescript/components/MessageList/InlineLoadingMoreThreadIndicator.d.ts +0 -8
  234. package/lib/typescript/components/MessageList/InlineLoadingMoreThreadIndicator.d.ts.map +0 -1
  235. package/src/components/MessageList/InlineLoadingMoreThreadIndicator.tsx +0 -64
@@ -0,0 +1,268 @@
1
+ import React, { useCallback, useMemo } from 'react';
2
+ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3
+
4
+ import { Thread, ThreadState } from 'stream-chat';
5
+
6
+ import {
7
+ TranslationContextValue,
8
+ useChatContext,
9
+ useTheme,
10
+ useTranslationContext,
11
+ } from '../../contexts';
12
+ import {
13
+ ThreadListItemProvider,
14
+ useThreadListItemContext,
15
+ } from '../../contexts/threadsContext/ThreadListItemContext';
16
+ import { useThreadsContext } from '../../contexts/threadsContext/ThreadsContext';
17
+ import { useStateStore } from '../../hooks';
18
+ import { MessageBubble } from '../../icons';
19
+ import { getDateString } from '../../utils/i18n/getDateString';
20
+ import { Avatar } from '../Avatar/Avatar';
21
+ import { useChannelPreviewDisplayName } from '../ChannelPreview/hooks/useChannelPreviewDisplayName';
22
+ import { MessageType } from '../MessageList/hooks/useMessageList';
23
+
24
+ export type ThreadListItemProps = {
25
+ thread: Thread;
26
+ timestampTranslationKey?: string;
27
+ };
28
+
29
+ const styles = StyleSheet.create({
30
+ boldText: { fontSize: 14, fontWeight: '500' },
31
+ contentRow: {
32
+ flexDirection: 'row',
33
+ marginTop: 6,
34
+ },
35
+ contentTextWrapper: {
36
+ flex: 1,
37
+ marginLeft: 8,
38
+ },
39
+ dateText: { alignSelf: 'flex-end' },
40
+ headerRow: {
41
+ flexDirection: 'row',
42
+ },
43
+ infoRow: {
44
+ alignItems: 'center',
45
+ flexDirection: 'row',
46
+ },
47
+ lastReplyText: { flex: 1, fontSize: 14, marginTop: 4 },
48
+ parentMessageText: { flex: 1, fontSize: 12 },
49
+ touchableWrapper: {
50
+ flex: 1,
51
+ paddingHorizontal: 8,
52
+ paddingVertical: 14,
53
+ },
54
+ unreadBubbleWrapper: {
55
+ alignItems: 'center',
56
+ alignSelf: 'flex-end',
57
+ borderRadius: 50,
58
+ height: 22,
59
+ justifyContent: 'center',
60
+ width: 22,
61
+ },
62
+ });
63
+
64
+ export const attachmentTypeIconMap = {
65
+ audio: '🔈',
66
+ file: '📄',
67
+ image: '📷',
68
+ video: '🎥',
69
+ voiceRecording: '🎙️',
70
+ } as const;
71
+
72
+ const getTitleFromMessage = ({
73
+ currentUserId,
74
+ message,
75
+ t,
76
+ }: {
77
+ t: TranslationContextValue['t'];
78
+ currentUserId?: string;
79
+ message?: MessageType | undefined;
80
+ }) => {
81
+ const attachment = message?.attachments?.at(0);
82
+
83
+ const attachmentIcon = attachment
84
+ ? `${
85
+ attachmentTypeIconMap[(attachment.type as keyof typeof attachmentTypeIconMap) ?? 'file'] ??
86
+ attachmentTypeIconMap.file
87
+ } `
88
+ : '';
89
+
90
+ const messageBelongsToCurrentUserPrefix =
91
+ message?.user?.id === currentUserId ? `${t('You')}: ` : '';
92
+
93
+ if (message?.deleted_at && message.parent_id)
94
+ return `${messageBelongsToCurrentUserPrefix}${t('This reply was deleted')}.`;
95
+
96
+ if (message?.deleted_at && !message.parent_id)
97
+ return `${messageBelongsToCurrentUserPrefix}${t('The source message was deleted')}.`;
98
+
99
+ if (attachment?.type === 'voiceRecording')
100
+ return `${attachmentIcon}${messageBelongsToCurrentUserPrefix}${t('Voice message')}.`;
101
+
102
+ return `${attachmentIcon}${messageBelongsToCurrentUserPrefix}${
103
+ message?.text || attachment?.fallback || 'N/A'
104
+ }`;
105
+ };
106
+
107
+ export const ThreadListItemComponent = () => {
108
+ const {
109
+ channel,
110
+ dateString,
111
+ deletedAtDateString,
112
+ lastReply,
113
+ ownUnreadMessageCount,
114
+ parentMessage,
115
+ thread,
116
+ } = useThreadListItemContext();
117
+ const displayName = useChannelPreviewDisplayName(channel);
118
+ const { onThreadSelect } = useThreadsContext();
119
+ const { client } = useChatContext();
120
+ const { t } = useTranslationContext();
121
+ const {
122
+ theme: {
123
+ colors: { accent_red, text_low_emphasis, white },
124
+ threadListItem,
125
+ },
126
+ } = useTheme();
127
+
128
+ return (
129
+ <TouchableOpacity
130
+ onPress={() => {
131
+ if (onThreadSelect) {
132
+ onThreadSelect({ thread: parentMessage as MessageType, threadInstance: thread }, channel);
133
+ }
134
+ }}
135
+ style={[styles.touchableWrapper, threadListItem.touchableWrapper]}
136
+ testID='thread-list-item'
137
+ >
138
+ <View style={[styles.headerRow, threadListItem.headerRow]}>
139
+ <MessageBubble />
140
+ <Text style={[styles.boldText, { color: text_low_emphasis }, threadListItem.boldText]}>
141
+ {displayName || 'N/A'}
142
+ </Text>
143
+ </View>
144
+ <View style={[styles.infoRow, threadListItem.infoRow]}>
145
+ <Text
146
+ numberOfLines={1}
147
+ style={[
148
+ styles.parentMessageText,
149
+ { color: text_low_emphasis },
150
+ threadListItem.parentMessageText,
151
+ ]}
152
+ >
153
+ {t<string>('replied to')}: {getTitleFromMessage({ message: parentMessage, t })}
154
+ </Text>
155
+ {ownUnreadMessageCount > 0 && !deletedAtDateString ? (
156
+ <View
157
+ style={[
158
+ styles.unreadBubbleWrapper,
159
+ { backgroundColor: accent_red },
160
+ threadListItem.unreadBubbleWrapper,
161
+ ]}
162
+ >
163
+ <Text style={[{ color: white }, threadListItem.unreadBubbleText]}>
164
+ {ownUnreadMessageCount}
165
+ </Text>
166
+ </View>
167
+ ) : null}
168
+ </View>
169
+ <View style={[styles.contentRow, threadListItem.contentRow]}>
170
+ <Avatar
171
+ image={lastReply?.user?.image as string}
172
+ online={lastReply?.user?.online}
173
+ size={40}
174
+ />
175
+ <View style={[styles.contentTextWrapper, threadListItem.contentTextWrapper]}>
176
+ <Text style={[styles.boldText, { color: text_low_emphasis }, threadListItem.boldText]}>
177
+ {lastReply?.user?.name}
178
+ </Text>
179
+ <View style={[styles.headerRow, threadListItem.headerRow]}>
180
+ <Text
181
+ numberOfLines={1}
182
+ style={[
183
+ styles.lastReplyText,
184
+ { color: text_low_emphasis },
185
+ threadListItem.lastReplyText,
186
+ ]}
187
+ >
188
+ {deletedAtDateString
189
+ ? 'This thread was deleted.'
190
+ : getTitleFromMessage({
191
+ currentUserId: client.userID,
192
+ message: lastReply,
193
+ t,
194
+ })}
195
+ </Text>
196
+ <Text style={[styles.dateText, { color: text_low_emphasis }, threadListItem.dateText]}>
197
+ {deletedAtDateString ?? dateString}
198
+ </Text>
199
+ </View>
200
+ </View>
201
+ </View>
202
+ </TouchableOpacity>
203
+ );
204
+ };
205
+
206
+ export const ThreadListItem = (props: ThreadListItemProps) => {
207
+ const { client } = useChatContext();
208
+ const { t, tDateTimeParser } = useTranslationContext();
209
+ const { thread, timestampTranslationKey = 'timestamp/ThreadListItem' } = props;
210
+ const { ThreadListItem = ThreadListItemComponent } = useThreadsContext();
211
+
212
+ const selector = useCallback(
213
+ (nextValue: ThreadState) =>
214
+ [
215
+ nextValue.replies.at(-1),
216
+ (client.userID && nextValue.read[client.userID]?.unreadMessageCount) || 0,
217
+ nextValue.parentMessage,
218
+ nextValue.channel,
219
+ nextValue.deletedAt,
220
+ ] as const,
221
+ [client],
222
+ );
223
+
224
+ const [lastReply, ownUnreadMessageCount, parentMessage, channel, deletedAt] = useStateStore(
225
+ thread.state,
226
+ selector,
227
+ );
228
+
229
+ const timestamp = lastReply?.created_at;
230
+
231
+ // TODO: Please rethink this, we have the same line of code in about 5 places in the SDK.
232
+ const dateString = useMemo(
233
+ () =>
234
+ getDateString({
235
+ date: timestamp,
236
+ t,
237
+ tDateTimeParser,
238
+ timestampTranslationKey,
239
+ }),
240
+ [timestamp, t, tDateTimeParser, timestampTranslationKey],
241
+ );
242
+ const deletedAtDateString = useMemo(
243
+ () =>
244
+ getDateString({
245
+ date: deletedAt as Date | undefined,
246
+ t,
247
+ tDateTimeParser,
248
+ timestampTranslationKey,
249
+ }),
250
+ [deletedAt, t, tDateTimeParser, timestampTranslationKey],
251
+ );
252
+
253
+ return (
254
+ <ThreadListItemProvider
255
+ value={{
256
+ channel,
257
+ dateString,
258
+ deletedAtDateString,
259
+ lastReply,
260
+ ownUnreadMessageCount,
261
+ parentMessage,
262
+ thread,
263
+ }}
264
+ >
265
+ <ThreadListItem />
266
+ </ThreadListItemProvider>
267
+ );
268
+ };
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import { StyleSheet, Text, TouchableOpacity } from 'react-native';
3
+
4
+ import { ThreadManagerState } from 'stream-chat';
5
+
6
+ import { useChatContext, useTheme } from '../../contexts';
7
+ import { useStateStore } from '../../hooks';
8
+ import { Reload } from '../../icons';
9
+
10
+ const styles = StyleSheet.create({
11
+ text: { alignSelf: 'flex-start', flex: 1, fontSize: 16 },
12
+ touchableWrapper: {
13
+ borderRadius: 16,
14
+ flexDirection: 'row',
15
+ marginHorizontal: 8,
16
+ marginVertical: 6,
17
+ paddingHorizontal: 16,
18
+ paddingVertical: 14,
19
+ },
20
+ });
21
+
22
+ const selector = (nextValue: ThreadManagerState) => [nextValue.unseenThreadIds] as const;
23
+
24
+ export const ThreadListUnreadBanner = () => {
25
+ const { client } = useChatContext();
26
+ const {
27
+ theme: {
28
+ colors: { text_high_emphasis, white },
29
+ threadListUnreadBanner,
30
+ },
31
+ } = useTheme();
32
+ const [unseenThreadIds] = useStateStore(client.threads.state, selector);
33
+ if (!unseenThreadIds.length) {
34
+ return null;
35
+ }
36
+
37
+ return (
38
+ <TouchableOpacity
39
+ onPress={() => client.threads.reload()}
40
+ style={[
41
+ styles.touchableWrapper,
42
+ { backgroundColor: text_high_emphasis },
43
+ threadListUnreadBanner.touchableWrapper,
44
+ ]}
45
+ >
46
+ <Text style={[styles.text, { color: white }, threadListUnreadBanner.text]}>
47
+ {unseenThreadIds.length} unread threads
48
+ </Text>
49
+ <Reload pathFill={white} />
50
+ </TouchableOpacity>
51
+ );
52
+ };
@@ -139,7 +139,6 @@ export * from './MessageList/hooks/useTypingString';
139
139
  export * from './MessageList/InlineDateSeparator';
140
140
  export * from './MessageList/InlineLoadingMoreIndicator';
141
141
  export * from './MessageList/InlineLoadingMoreRecentIndicator';
142
- export * from './MessageList/InlineLoadingMoreThreadIndicator';
143
142
  export * from './MessageList/InlineUnreadIndicator';
144
143
  export * from './MessageList/MessageList';
145
144
  export * from './MessageList/MessageSystem';
@@ -169,3 +168,4 @@ export * from './Spinner/Spinner';
169
168
 
170
169
  export * from './Thread/Thread';
171
170
  export * from './Thread/components/ThreadFooterComponent';
171
+ export * from './ThreadList/ThreadList';
@@ -27,6 +27,8 @@ export const Colors = {
27
27
  static_black: '#000000',
28
28
  static_white: '#ffffff',
29
29
  targetedMessageBackground: '#FBF4DD', // dark mode = #302D22
30
+ text_high_emphasis: '#080707',
31
+ text_low_emphasis: '#7E828B',
30
32
  transparent: 'transparent',
31
33
  white: '#FFFFFF',
32
34
  white_smoke: '#F2F2F2',
@@ -637,6 +639,23 @@ export type Theme = {
637
639
  threadHeight?: number;
638
640
  };
639
641
  };
642
+ threadListItem: {
643
+ boldText: TextStyle;
644
+ contentRow: ViewStyle;
645
+ contentTextWrapper: ViewStyle;
646
+ dateText: TextStyle;
647
+ headerRow: ViewStyle;
648
+ infoRow: ViewStyle;
649
+ lastReplyText: TextStyle;
650
+ parentMessageText: TextStyle;
651
+ touchableWrapper: ViewStyle;
652
+ unreadBubbleText: TextStyle;
653
+ unreadBubbleWrapper: ViewStyle;
654
+ };
655
+ threadListUnreadBanner: {
656
+ text: TextStyle;
657
+ touchableWrapper: ViewStyle;
658
+ };
640
659
  typingIndicator: {
641
660
  container: ViewStyle;
642
661
  text: TextStyle & {
@@ -1241,6 +1260,23 @@ export const defaultTheme: Theme = {
1241
1260
  text: {},
1242
1261
  },
1243
1262
  },
1263
+ threadListItem: {
1264
+ boldText: {},
1265
+ contentRow: {},
1266
+ contentTextWrapper: {},
1267
+ dateText: {},
1268
+ headerRow: {},
1269
+ infoRow: {},
1270
+ lastReplyText: {},
1271
+ parentMessageText: {},
1272
+ touchableWrapper: {},
1273
+ unreadBubbleText: {},
1274
+ unreadBubbleWrapper: {},
1275
+ },
1276
+ threadListUnreadBanner: {
1277
+ text: {},
1278
+ touchableWrapper: {},
1279
+ },
1244
1280
  typingIndicator: {
1245
1281
  container: {},
1246
1282
  text: {
@@ -1,6 +1,6 @@
1
1
  import React, { PropsWithChildren, useContext } from 'react';
2
2
 
3
- import type { ChannelState } from 'stream-chat';
3
+ import { ChannelState, Thread } from 'stream-chat';
4
4
 
5
5
  import type { MessageType } from '../../components/MessageList/hooks/useMessageList';
6
6
  import type { DefaultStreamChatGenerics, UnknownType } from '../../types/types';
@@ -9,6 +9,10 @@ import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
9
9
  import { getDisplayName } from '../utils/getDisplayName';
10
10
  import { isTestEnvironment } from '../utils/isTestEnvironment';
11
11
 
12
+ export type ThreadType<
13
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
14
+ > = { thread: MessageType<StreamChatGenerics>; threadInstance: Thread };
15
+
12
16
  export type ThreadContextValue<
13
17
  StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
14
18
  > = {
@@ -20,12 +24,15 @@ export type ThreadContextValue<
20
24
  setThreadLoadingMore: React.Dispatch<React.SetStateAction<boolean>>;
21
25
  thread: MessageType<StreamChatGenerics> | null;
22
26
  threadHasMore: boolean;
23
- threadLoadingMore: boolean;
24
27
  threadMessages: ChannelState<StreamChatGenerics>['threads'][string];
28
+ loadMoreRecentThread?: (opts: { limit?: number }) => Promise<void>;
25
29
  /**
26
30
  * Boolean to enable/disable parent message press
27
31
  */
28
32
  parentMessagePreventPress?: boolean;
33
+ threadInstance?: Thread | null;
34
+ threadLoadingMore?: boolean;
35
+ threadLoadingMoreRecent?: boolean;
29
36
  };
30
37
 
31
38
  export const ThreadContext = React.createContext(DEFAULT_BASE_CONTEXT_VALUE as ThreadContextValue);
@@ -0,0 +1,41 @@
1
+ import React, { PropsWithChildren, useContext } from 'react';
2
+
3
+ import { Channel, Thread } from 'stream-chat';
4
+
5
+ import { MessageType } from '../../components';
6
+ import type { DefaultStreamChatGenerics } from '../../types/types';
7
+ import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
8
+
9
+ export type ThreadListItemContextValue<
10
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
11
+ > = {
12
+ channel: Channel<StreamChatGenerics>;
13
+ dateString: string | number | undefined;
14
+ deletedAtDateString: string | number | undefined;
15
+ lastReply: MessageType<StreamChatGenerics> | undefined;
16
+ ownUnreadMessageCount: number;
17
+ parentMessage: MessageType<StreamChatGenerics> | undefined;
18
+ thread: Thread<StreamChatGenerics>;
19
+ };
20
+
21
+ export const ThreadListItemContext = React.createContext(
22
+ DEFAULT_BASE_CONTEXT_VALUE as ThreadListItemContextValue,
23
+ );
24
+
25
+ export const ThreadListItemProvider = <
26
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
27
+ >({
28
+ children,
29
+ value,
30
+ }: PropsWithChildren<{
31
+ value: ThreadListItemContextValue<StreamChatGenerics>;
32
+ }>) => (
33
+ <ThreadListItemContext.Provider value={value as unknown as ThreadListItemContextValue}>
34
+ {children}
35
+ </ThreadListItemContext.Provider>
36
+ );
37
+
38
+ export const useThreadListItemContext = <
39
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
40
+ >() =>
41
+ useContext(ThreadListItemContext) as unknown as ThreadListItemContextValue<StreamChatGenerics>;
@@ -0,0 +1,62 @@
1
+ import React, { PropsWithChildren, useContext } from 'react';
2
+
3
+ import { FlatListProps } from 'react-native';
4
+
5
+ import { Channel, Thread } from 'stream-chat';
6
+
7
+ import type { DefaultStreamChatGenerics } from '../../types/types';
8
+ import { ThreadType } from '../threadContext/ThreadContext';
9
+ import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
10
+
11
+ import { isTestEnvironment } from '../utils/isTestEnvironment';
12
+
13
+ export type ThreadsContextValue<
14
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
15
+ > = {
16
+ isFocused: boolean;
17
+ isLoading: boolean;
18
+ isLoadingNext: boolean;
19
+ threads: Thread<StreamChatGenerics>[];
20
+ additionalFlatListProps?: Partial<FlatListProps<Thread>>;
21
+ loadMore?: () => Promise<void>;
22
+ onThreadSelect?: (thread: ThreadType, channel: Channel) => void;
23
+ ThreadListEmptyPlaceholder?: React.ComponentType;
24
+ ThreadListItem?: React.ComponentType;
25
+ ThreadListLoadingIndicator?: React.ComponentType;
26
+ ThreadListLoadingMoreIndicator?: React.ComponentType;
27
+ ThreadListUnreadBanner?: React.ComponentType;
28
+ };
29
+
30
+ export const ThreadsContext = React.createContext(
31
+ DEFAULT_BASE_CONTEXT_VALUE as ThreadsContextValue,
32
+ );
33
+
34
+ export const ThreadsProvider = <
35
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
36
+ >({
37
+ children,
38
+ value,
39
+ }: PropsWithChildren<{
40
+ value: ThreadsContextValue<StreamChatGenerics>;
41
+ }>) => (
42
+ <ThreadsContext.Provider value={value as unknown as ThreadsContextValue}>
43
+ {children}
44
+ </ThreadsContext.Provider>
45
+ );
46
+
47
+ export const useThreadsContext = <
48
+ StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
49
+ >() => {
50
+ const contextValue = useContext(
51
+ ThreadsContext,
52
+ ) as unknown as ThreadsContextValue<StreamChatGenerics>;
53
+
54
+ if (contextValue === DEFAULT_BASE_CONTEXT_VALUE && !isTestEnvironment()) {
55
+ throw new Error(
56
+ // TODO: Set correct link to docs page, should be the new ThreadList instead of Channel
57
+ `The useThreadsContext hook was called outside of the ThreadsContext provider. Make sure you have configured the ThreadList component correctly - https://getstream.io/chat/docs/sdk/reactnative/basics/hello_stream_chat/#channel`,
58
+ );
59
+ }
60
+
61
+ return contextValue;
62
+ };
@@ -2,3 +2,4 @@ export * from './useAppStateListener';
2
2
  export * from './useStreami18n';
3
3
  export * from './useViewport';
4
4
  export * from './useScreenDimensions';
5
+ export * from './useStateStore';
@@ -0,0 +1,31 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ import type { StateStore } from 'stream-chat';
4
+
5
+ export function useStateStore<T extends Record<string, unknown>, O extends readonly unknown[]>(
6
+ store: StateStore<T>,
7
+ selector: (v: T) => O,
8
+ ): O;
9
+ export function useStateStore<T extends Record<string, unknown>, O extends readonly unknown[]>(
10
+ store: StateStore<T> | undefined,
11
+ selector: (v: T) => O,
12
+ ): O | undefined;
13
+ export function useStateStore<T extends Record<string, unknown>, O extends readonly unknown[]>(
14
+ store: StateStore<T> | undefined,
15
+ selector: (v: T) => O,
16
+ ) {
17
+ const [state, setState] = useState<O | undefined>(() => {
18
+ if (!store) return undefined;
19
+ return selector(store.getLatestValue());
20
+ });
21
+
22
+ useEffect(() => {
23
+ if (!store) return;
24
+
25
+ const unsubscribe = store.subscribeWithSelector(selector, setState);
26
+
27
+ return unsubscribe;
28
+ }, [store, selector]);
29
+
30
+ return state;
31
+ }
package/src/i18n/en.json CHANGED
@@ -38,12 +38,14 @@
38
38
  "Links are disabled": "Links are disabled",
39
39
  "Loading channels...": "Loading channels...",
40
40
  "Loading messages...": "Loading messages...",
41
+ "Loading threads...": "Loading threads...",
41
42
  "Loading...": "Loading...",
42
43
  "Message Reactions": "Message Reactions",
43
44
  "Message deleted": "Message deleted",
44
45
  "Message flagged": "Message flagged",
45
46
  "Mute User": "Mute User",
46
47
  "No chats here yet…": "No chats here yet…",
48
+ "No threads here yet": "No threads here yet",
47
49
  "Not supported": "Not supported",
48
50
  "Nothing yet...": "Nothing yet...",
49
51
  "Ok": "Ok",
@@ -67,6 +69,8 @@
67
69
  "Sending links is not allowed in this conversation": "Sending links is not allowed in this conversation",
68
70
  "Slow mode ON": "Slow mode ON",
69
71
  "The message has been reported to a moderator.": "The message has been reported to a moderator.",
72
+ "The source message was deleted": "The source message was deleted",
73
+ "This reply was deleted": "This reply was deleted",
70
74
  "Thread Reply": "Thread Reply",
71
75
  "Unban User": "Unban User",
72
76
  "Unblock User": "Unblock User",
@@ -75,8 +79,10 @@
75
79
  "Unpin from Conversation": "Unpin from Conversation",
76
80
  "Unread Messages": "Unread Messages",
77
81
  "Video": "Video",
82
+ "Voice message": "Voice message",
78
83
  "You": "You",
79
84
  "You can't send messages in this channel": "You can't send messages in this channel",
85
+ "replied to": "replied to",
80
86
  "timestamp/ChannelPreviewStatus": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: {\"lastDay\":\"[Yesterday]\", \"lastWeek\":\"dddd\", \"nextDay\":\"[Tomorrow]\", \"nextWeek\":\"dddd [at] LT\", \"sameDay\":\"LT\", \"sameElse\":\"L\"}) }}",
81
87
  "timestamp/ImageGalleryHeader": "{{ timestamp | timestampFormatter(calendar: true) }}",
82
88
  "timestamp/InlineDateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}",
@@ -84,6 +90,7 @@
84
90
  "timestamp/MessageSystem": "{{ timestamp | timestampFormatter(calendar: true) }}",
85
91
  "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(format: LT) }}",
86
92
  "timestamp/StickyHeader": "{{ timestamp | timestampFormatter(calendar: true) }}",
93
+ "timestamp/ThreadListItem": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: {\"lastDay\":\"[Yesterday]\", \"lastWeek\":\"dddd\", \"nextDay\":\"[Tomorrow]\", \"nextWeek\":\"dddd [at] LT\", \"sameDay\":\"LT\", \"sameElse\":\"L\"}) }}",
87
94
  "{{ firstUser }} and {{ nonSelfUserLength }} more are typing": "{{ firstUser }} and {{ nonSelfUserLength }} more are typing",
88
95
  "{{ index }} of {{ photoLength }}": "{{ index }} of {{ photoLength }}",
89
96
  "{{ replyCount }} Replies": "{{ replyCount }} Replies",
package/src/i18n/es.json CHANGED
@@ -38,12 +38,14 @@
38
38
  "Links are disabled": "Los enlaces están desactivados",
39
39
  "Loading channels...": "Cargando canales...",
40
40
  "Loading messages...": "Cargando mensajes...",
41
+ "Loading threads...": "Cargando hilos...",
41
42
  "Loading...": "Cargando...",
42
43
  "Message Reactions": "Reacciones al mensaje",
43
44
  "Message deleted": "Mensaje eliminado",
44
45
  "Message flagged": "Mensaje reportado",
45
46
  "Mute User": "Silenciar usuario",
46
47
  "No chats here yet…": "No hay chats aquí todavía...",
48
+ "No threads here yet": "Aún no hay hilos aquí",
47
49
  "Not supported": "No admitido",
48
50
  "Nothing yet...": "Aún no hay nada...",
49
51
  "Ok": "Aceptar",
@@ -67,6 +69,8 @@
67
69
  "Sending links is not allowed in this conversation": "No está permitido enviar enlaces en esta conversación",
68
70
  "Slow mode ON": "Modo lento ACTIVADO",
69
71
  "The message has been reported to a moderator.": "El mensaje ha sido reportado a un moderador.",
72
+ "The source message was deleted": "El mensaje original fue eliminado",
73
+ "This reply was deleted": "Esta respuesta fue eliminada",
70
74
  "Thread Reply": "Respuesta de hilo",
71
75
  "Unban User": "Desbloquear usuario",
72
76
  "Unblock User": "Usuario desconocido",
@@ -75,8 +79,10 @@
75
79
  "Unpin from Conversation": "Desmarcar de la conversación",
76
80
  "Unread Messages": "Mensajes no leídos",
77
81
  "Video": "Video",
82
+ "Voice message": "Mensaje de voz",
78
83
  "You": "Tú",
79
84
  "You can't send messages in this channel": "No puedes enviar mensajes en este canal",
85
+ "replied to": "respondió a",
80
86
  "timestamp/ChannelPreviewStatus": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: {\"lastDay\":\"[Ayer]\", \"lastWeek\":\"dddd\", \"nextDay\":\"[Mañana]\", \"nextWeek\":\"dddd [a las] LT\", \"sameDay\":\"LT\", \"sameElse\":\"L\"}) }}",
81
87
  "timestamp/ImageGalleryHeader": "{{ timestamp | timestampFormatter(calendar: true) }}",
82
88
  "timestamp/InlineDateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}",
@@ -84,6 +90,7 @@
84
90
  "timestamp/MessageSystem": "{{ timestamp | timestampFormatter(calendar: true) }}",
85
91
  "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(format: LT) }}",
86
92
  "timestamp/StickyHeader": "{{ timestamp | timestampFormatter(calendar: true) }}",
93
+ "timestamp/ThreadListItem": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: {\"lastDay\":\"[Ayer]\", \"lastWeek\":\"dddd\", \"nextDay\":\"[Mañana]\", \"nextWeek\":\"dddd [a las] LT\", \"sameDay\":\"LT\", \"sameElse\":\"L\"}) }}",
87
94
  "{{ firstUser }} and {{ nonSelfUserLength }} more are typing": "{{ firstUser }} y {{ nonSelfUserLength }} más están escribiendo",
88
95
  "{{ index }} of {{ photoLength }}": "{{ index }} de {{ photoLength }}",
89
96
  "{{ replyCount }} Replies": "{{ replyCount }} Respuestas",