@sendbird/uikit-react-native 2.4.2 → 2.5.0-rc.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 (209) hide show
  1. package/lib/commonjs/components/ChannelMessageList/index.js +319 -0
  2. package/lib/commonjs/components/ChannelMessageList/index.js.map +1 -0
  3. package/lib/commonjs/components/ChatFlatList.js +30 -50
  4. package/lib/commonjs/components/ChatFlatList.js.map +1 -1
  5. package/lib/commonjs/components/MessageSearchResultItem.js +132 -0
  6. package/lib/commonjs/components/MessageSearchResultItem.js.map +1 -0
  7. package/lib/commonjs/components/ScrollToBottomButton.js +1 -1
  8. package/lib/commonjs/components/ScrollToBottomButton.js.map +1 -1
  9. package/lib/commonjs/constants.js +6 -2
  10. package/lib/commonjs/constants.js.map +1 -1
  11. package/lib/commonjs/containers/GroupChannelPreviewContainer.js +2 -2
  12. package/lib/commonjs/containers/GroupChannelPreviewContainer.js.map +1 -1
  13. package/lib/commonjs/containers/SendbirdUIKitContainer.js +4 -2
  14. package/lib/commonjs/containers/SendbirdUIKitContainer.js.map +1 -1
  15. package/lib/commonjs/contexts/SendbirdChatCtx.js +4 -2
  16. package/lib/commonjs/contexts/SendbirdChatCtx.js.map +1 -1
  17. package/lib/commonjs/domain/groupChannel/component/GroupChannelHeader.js +4 -2
  18. package/lib/commonjs/domain/groupChannel/component/GroupChannelHeader.js.map +1 -1
  19. package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js +84 -301
  20. package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
  21. package/lib/commonjs/domain/groupChannel/component/GroupChannelSuggestedMentionList.js +2 -0
  22. package/lib/commonjs/domain/groupChannel/component/GroupChannelSuggestedMentionList.js.map +1 -1
  23. package/lib/commonjs/domain/groupChannel/module/moduleContext.js +9 -2
  24. package/lib/commonjs/domain/groupChannel/module/moduleContext.js.map +1 -1
  25. package/lib/commonjs/domain/groupChannel/types.js.map +1 -1
  26. package/lib/commonjs/domain/groupChannelSettings/component/GroupChannelSettingsMenu.js +18 -4
  27. package/lib/commonjs/domain/groupChannelSettings/component/GroupChannelSettingsMenu.js.map +1 -1
  28. package/lib/commonjs/domain/groupChannelSettings/types.js.map +1 -1
  29. package/lib/commonjs/domain/messageSearch/component/MessageSearchHeader.js +105 -0
  30. package/lib/commonjs/domain/messageSearch/component/MessageSearchHeader.js.map +1 -0
  31. package/lib/commonjs/domain/messageSearch/component/MessageSearchList.js +40 -0
  32. package/lib/commonjs/domain/messageSearch/component/MessageSearchList.js.map +1 -0
  33. package/lib/commonjs/domain/messageSearch/component/MessageSearchStatusEmpty.js +22 -0
  34. package/lib/commonjs/domain/messageSearch/component/MessageSearchStatusEmpty.js.map +1 -0
  35. package/lib/commonjs/domain/messageSearch/component/MessageSearchStatusError.js +26 -0
  36. package/lib/commonjs/domain/messageSearch/component/MessageSearchStatusError.js.map +1 -0
  37. package/lib/commonjs/domain/messageSearch/component/MessageSearchStatusLoading.js +22 -0
  38. package/lib/commonjs/domain/messageSearch/component/MessageSearchStatusLoading.js.map +1 -0
  39. package/lib/commonjs/domain/messageSearch/index.js +62 -0
  40. package/lib/commonjs/domain/messageSearch/index.js.map +1 -0
  41. package/lib/commonjs/domain/messageSearch/module/createMessageSearchModule.js +36 -0
  42. package/lib/commonjs/domain/messageSearch/module/createMessageSearchModule.js.map +1 -0
  43. package/lib/commonjs/domain/messageSearch/module/moduleContext.js +25 -0
  44. package/lib/commonjs/domain/messageSearch/module/moduleContext.js.map +1 -0
  45. package/lib/commonjs/domain/messageSearch/types.js +6 -0
  46. package/lib/commonjs/domain/messageSearch/types.js.map +1 -0
  47. package/lib/commonjs/domain/openChannel/component/OpenChannelMessageList.js +38 -279
  48. package/lib/commonjs/domain/openChannel/component/OpenChannelMessageList.js.map +1 -1
  49. package/lib/commonjs/domain/openChannel/module/moduleContext.js +9 -2
  50. package/lib/commonjs/domain/openChannel/module/moduleContext.js.map +1 -1
  51. package/lib/commonjs/domain/openChannel/types.js.map +1 -1
  52. package/lib/commonjs/fragments/createGroupChannelFragment.js +107 -15
  53. package/lib/commonjs/fragments/createGroupChannelFragment.js.map +1 -1
  54. package/lib/commonjs/fragments/createGroupChannelSettingsFragment.js +2 -0
  55. package/lib/commonjs/fragments/createGroupChannelSettingsFragment.js.map +1 -1
  56. package/lib/commonjs/fragments/createMessageSearchFragment.js +145 -0
  57. package/lib/commonjs/fragments/createMessageSearchFragment.js.map +1 -0
  58. package/lib/commonjs/fragments/createOpenChannelFragment.js +40 -8
  59. package/lib/commonjs/fragments/createOpenChannelFragment.js.map +1 -1
  60. package/lib/commonjs/hooks/useMentionSuggestion.js +17 -0
  61. package/lib/commonjs/hooks/useMentionSuggestion.js.map +1 -1
  62. package/lib/commonjs/index.js +60 -40
  63. package/lib/commonjs/index.js.map +1 -1
  64. package/lib/commonjs/localization/StringSet.type.js.map +1 -1
  65. package/lib/commonjs/localization/createBaseStringSet.js +33 -20
  66. package/lib/commonjs/localization/createBaseStringSet.js.map +1 -1
  67. package/lib/commonjs/utils/pubsub.js +21 -0
  68. package/lib/commonjs/utils/pubsub.js.map +1 -0
  69. package/lib/commonjs/version.js +1 -1
  70. package/lib/commonjs/version.js.map +1 -1
  71. package/lib/module/components/ChannelMessageList/index.js +311 -0
  72. package/lib/module/components/ChannelMessageList/index.js.map +1 -0
  73. package/lib/module/components/ChatFlatList.js +32 -52
  74. package/lib/module/components/ChatFlatList.js.map +1 -1
  75. package/lib/module/components/MessageSearchResultItem.js +124 -0
  76. package/lib/module/components/MessageSearchResultItem.js.map +1 -0
  77. package/lib/module/components/ScrollToBottomButton.js +1 -1
  78. package/lib/module/components/ScrollToBottomButton.js.map +1 -1
  79. package/lib/module/constants.js +3 -1
  80. package/lib/module/constants.js.map +1 -1
  81. package/lib/module/containers/GroupChannelPreviewContainer.js +2 -2
  82. package/lib/module/containers/GroupChannelPreviewContainer.js.map +1 -1
  83. package/lib/module/containers/SendbirdUIKitContainer.js +4 -2
  84. package/lib/module/containers/SendbirdUIKitContainer.js.map +1 -1
  85. package/lib/module/contexts/SendbirdChatCtx.js +4 -2
  86. package/lib/module/contexts/SendbirdChatCtx.js.map +1 -1
  87. package/lib/module/domain/groupChannel/component/GroupChannelHeader.js +4 -2
  88. package/lib/module/domain/groupChannel/component/GroupChannelHeader.js.map +1 -1
  89. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js +88 -305
  90. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
  91. package/lib/module/domain/groupChannel/component/GroupChannelSuggestedMentionList.js +2 -0
  92. package/lib/module/domain/groupChannel/component/GroupChannelSuggestedMentionList.js.map +1 -1
  93. package/lib/module/domain/groupChannel/module/moduleContext.js +9 -2
  94. package/lib/module/domain/groupChannel/module/moduleContext.js.map +1 -1
  95. package/lib/module/domain/groupChannel/types.js.map +1 -1
  96. package/lib/module/domain/groupChannelSettings/component/GroupChannelSettingsMenu.js +18 -4
  97. package/lib/module/domain/groupChannelSettings/component/GroupChannelSettingsMenu.js.map +1 -1
  98. package/lib/module/domain/groupChannelSettings/types.js.map +1 -1
  99. package/lib/module/domain/messageSearch/component/MessageSearchHeader.js +96 -0
  100. package/lib/module/domain/messageSearch/component/MessageSearchHeader.js.map +1 -0
  101. package/lib/module/domain/messageSearch/component/MessageSearchList.js +32 -0
  102. package/lib/module/domain/messageSearch/component/MessageSearchList.js.map +1 -0
  103. package/lib/module/domain/messageSearch/component/MessageSearchStatusEmpty.js +14 -0
  104. package/lib/module/domain/messageSearch/component/MessageSearchStatusEmpty.js.map +1 -0
  105. package/lib/module/domain/messageSearch/component/MessageSearchStatusError.js +18 -0
  106. package/lib/module/domain/messageSearch/component/MessageSearchStatusError.js.map +1 -0
  107. package/lib/module/domain/messageSearch/component/MessageSearchStatusLoading.js +14 -0
  108. package/lib/module/domain/messageSearch/component/MessageSearchStatusLoading.js.map +1 -0
  109. package/lib/module/domain/messageSearch/index.js +8 -0
  110. package/lib/module/domain/messageSearch/index.js.map +1 -0
  111. package/lib/module/domain/messageSearch/module/createMessageSearchModule.js +28 -0
  112. package/lib/module/domain/messageSearch/module/createMessageSearchModule.js.map +1 -0
  113. package/lib/module/domain/messageSearch/module/moduleContext.js +14 -0
  114. package/lib/module/domain/messageSearch/module/moduleContext.js.map +1 -0
  115. package/lib/module/domain/messageSearch/types.js +2 -0
  116. package/lib/module/domain/messageSearch/types.js.map +1 -0
  117. package/lib/module/domain/openChannel/component/OpenChannelMessageList.js +40 -281
  118. package/lib/module/domain/openChannel/component/OpenChannelMessageList.js.map +1 -1
  119. package/lib/module/domain/openChannel/module/moduleContext.js +9 -2
  120. package/lib/module/domain/openChannel/module/moduleContext.js.map +1 -1
  121. package/lib/module/domain/openChannel/types.js.map +1 -1
  122. package/lib/module/fragments/createGroupChannelFragment.js +109 -17
  123. package/lib/module/fragments/createGroupChannelFragment.js.map +1 -1
  124. package/lib/module/fragments/createGroupChannelSettingsFragment.js +2 -0
  125. package/lib/module/fragments/createGroupChannelSettingsFragment.js.map +1 -1
  126. package/lib/module/fragments/createMessageSearchFragment.js +135 -0
  127. package/lib/module/fragments/createMessageSearchFragment.js.map +1 -0
  128. package/lib/module/fragments/createOpenChannelFragment.js +41 -9
  129. package/lib/module/fragments/createOpenChannelFragment.js.map +1 -1
  130. package/lib/module/hooks/useMentionSuggestion.js +18 -1
  131. package/lib/module/hooks/useMentionSuggestion.js.map +1 -1
  132. package/lib/module/index.js +2 -0
  133. package/lib/module/index.js.map +1 -1
  134. package/lib/module/localization/StringSet.type.js.map +1 -1
  135. package/lib/module/localization/createBaseStringSet.js +34 -21
  136. package/lib/module/localization/createBaseStringSet.js.map +1 -1
  137. package/lib/module/utils/pubsub.js +14 -0
  138. package/lib/module/utils/pubsub.js.map +1 -0
  139. package/lib/module/version.js +1 -1
  140. package/lib/module/version.js.map +1 -1
  141. package/lib/typescript/src/components/ChannelMessageList/index.d.ts +55 -0
  142. package/lib/typescript/src/components/ChatFlatList.d.ts +7 -8
  143. package/lib/typescript/src/components/MessageRenderer/index.d.ts +4 -0
  144. package/lib/typescript/src/components/MessageSearchResultItem.d.ts +2 -0
  145. package/lib/typescript/src/components/OpenChannelMessageRenderer/index.d.ts +1 -0
  146. package/lib/typescript/src/components/ScrollToBottomButton.d.ts +1 -1
  147. package/lib/typescript/src/constants.d.ts +3 -1
  148. package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +2 -1
  149. package/lib/typescript/src/contexts/SendbirdChatCtx.d.ts +3 -1
  150. package/lib/typescript/src/domain/groupChannel/component/GroupChannelHeader.d.ts +1 -1
  151. package/lib/typescript/src/domain/groupChannel/component/GroupChannelMessageList.d.ts +3 -32
  152. package/lib/typescript/src/domain/groupChannel/types.d.ts +23 -35
  153. package/lib/typescript/src/domain/groupChannelSettings/component/GroupChannelSettingsMenu.d.ts +1 -1
  154. package/lib/typescript/src/domain/groupChannelSettings/types.d.ts +2 -0
  155. package/lib/typescript/src/domain/messageSearch/component/MessageSearchHeader.d.ts +3 -0
  156. package/lib/typescript/src/domain/messageSearch/component/MessageSearchList.d.ts +3 -0
  157. package/lib/typescript/src/domain/messageSearch/component/MessageSearchStatusEmpty.d.ts +2 -0
  158. package/lib/typescript/src/domain/messageSearch/component/MessageSearchStatusError.d.ts +3 -0
  159. package/lib/typescript/src/domain/messageSearch/component/MessageSearchStatusLoading.d.ts +2 -0
  160. package/lib/typescript/src/domain/messageSearch/index.d.ts +7 -0
  161. package/lib/typescript/src/domain/messageSearch/module/createMessageSearchModule.d.ts +3 -0
  162. package/lib/typescript/src/domain/messageSearch/module/moduleContext.d.ts +3 -0
  163. package/lib/typescript/src/domain/messageSearch/types.d.ts +53 -0
  164. package/lib/typescript/src/domain/openChannel/component/OpenChannelMessageList.d.ts +1 -37
  165. package/lib/typescript/src/domain/openChannel/types.d.ts +17 -36
  166. package/lib/typescript/src/fragments/createMessageSearchFragment.d.ts +3 -0
  167. package/lib/typescript/src/hooks/useMentionSuggestion.d.ts +3 -2
  168. package/lib/typescript/src/index.d.ts +2 -0
  169. package/lib/typescript/src/localization/StringSet.type.d.ts +17 -3
  170. package/lib/typescript/src/utils/pubsub.d.ts +6 -0
  171. package/lib/typescript/src/version.d.ts +1 -1
  172. package/package.json +8 -7
  173. package/src/components/ChannelMessageList/index.tsx +392 -0
  174. package/src/components/ChatFlatList.tsx +33 -51
  175. package/src/components/MessageSearchResultItem.tsx +125 -0
  176. package/src/components/ScrollToBottomButton.tsx +3 -4
  177. package/src/constants.ts +3 -1
  178. package/src/containers/GroupChannelPreviewContainer.tsx +2 -2
  179. package/src/containers/SendbirdUIKitContainer.tsx +2 -0
  180. package/src/contexts/SendbirdChatCtx.tsx +7 -1
  181. package/src/domain/groupChannel/component/GroupChannelHeader.tsx +9 -3
  182. package/src/domain/groupChannel/component/GroupChannelMessageList.tsx +73 -316
  183. package/src/domain/groupChannel/component/GroupChannelSuggestedMentionList.tsx +2 -1
  184. package/src/domain/groupChannel/module/moduleContext.tsx +10 -2
  185. package/src/domain/groupChannel/types.ts +49 -38
  186. package/src/domain/groupChannelSettings/component/GroupChannelSettingsMenu.tsx +29 -13
  187. package/src/domain/groupChannelSettings/types.ts +2 -0
  188. package/src/domain/messageSearch/component/MessageSearchHeader.tsx +98 -0
  189. package/src/domain/messageSearch/component/MessageSearchList.tsx +26 -0
  190. package/src/domain/messageSearch/component/MessageSearchStatusEmpty.tsx +15 -0
  191. package/src/domain/messageSearch/component/MessageSearchStatusError.tsx +16 -0
  192. package/src/domain/messageSearch/component/MessageSearchStatusLoading.tsx +15 -0
  193. package/src/domain/messageSearch/index.ts +7 -0
  194. package/src/domain/messageSearch/module/createMessageSearchModule.tsx +21 -0
  195. package/src/domain/messageSearch/module/moduleContext.tsx +16 -0
  196. package/src/domain/messageSearch/types.ts +55 -0
  197. package/src/domain/openChannel/component/OpenChannelMessageList.tsx +35 -303
  198. package/src/domain/openChannel/module/moduleContext.tsx +8 -2
  199. package/src/domain/openChannel/types.ts +40 -38
  200. package/src/fragments/createGroupChannelFragment.tsx +114 -17
  201. package/src/fragments/createGroupChannelSettingsFragment.tsx +2 -0
  202. package/src/fragments/createMessageSearchFragment.tsx +159 -0
  203. package/src/fragments/createOpenChannelFragment.tsx +48 -12
  204. package/src/hooks/useMentionSuggestion.ts +23 -3
  205. package/src/index.ts +3 -0
  206. package/src/localization/StringSet.type.ts +20 -2
  207. package/src/localization/createBaseStringSet.ts +22 -2
  208. package/src/utils/pubsub.ts +20 -0
  209. package/src/version.ts +0 -2
@@ -0,0 +1,392 @@
1
+ import React, { Ref } from 'react';
2
+ import { FlatList, FlatListProps, ListRenderItem, View } from 'react-native';
3
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
4
+
5
+ import {
6
+ BottomSheetItem,
7
+ ChannelFrozenBanner,
8
+ createStyleSheet,
9
+ useAlert,
10
+ useBottomSheet,
11
+ useToast,
12
+ useUIKitTheme,
13
+ } from '@sendbird/uikit-react-native-foundation';
14
+ import {
15
+ Logger,
16
+ SendbirdFileMessage,
17
+ SendbirdGroupChannel,
18
+ SendbirdMessage,
19
+ SendbirdOpenChannel,
20
+ SendbirdUserMessage,
21
+ getAvailableUriFromFileMessage,
22
+ getFileExtension,
23
+ getFileType,
24
+ isMyMessage,
25
+ messageKeyExtractor,
26
+ shouldRenderReaction,
27
+ toMegabyte,
28
+ useFreshCallback,
29
+ } from '@sendbird/uikit-utils';
30
+
31
+ import { DEPRECATION_WARNING } from '../../constants';
32
+ import type { UserProfileContextType } from '../../contexts/UserProfileCtx';
33
+ import { useLocalization, usePlatformService, useSendbirdChat } from '../../hooks/useContext';
34
+ import SBUUtils from '../../libs/SBUUtils';
35
+ import type { CommonComponent } from '../../types';
36
+ import ChatFlatList from '../ChatFlatList';
37
+ import { ReactionAddons } from '../ReactionAddons';
38
+
39
+ type PressActions = { onPress?: () => void; onLongPress?: () => void };
40
+ type HandleableMessage = SendbirdUserMessage | SendbirdFileMessage;
41
+ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpenChannel> = {
42
+ enableMessageGrouping: boolean;
43
+ currentUserId?: string;
44
+ channel: T;
45
+ messages: SendbirdMessage[];
46
+ newMessages: SendbirdMessage[];
47
+ searchItem?: { startingPoint: number };
48
+
49
+ scrolledAwayFromBottom: boolean;
50
+ onScrolledAwayFromBottom: (value: boolean) => void;
51
+ onTopReached: () => void;
52
+ onBottomReached: () => void;
53
+ hasNext: () => boolean;
54
+
55
+ onPressNewMessagesButton: (animated?: boolean) => void;
56
+ onPressScrollToBottomButton: (animated?: boolean) => void;
57
+
58
+ onEditMessage: (message: HandleableMessage) => void;
59
+ onDeleteMessage: (message: HandleableMessage) => Promise<void>;
60
+ onResendFailedMessage: (failedMessage: HandleableMessage) => Promise<void>;
61
+ onPressMediaMessage?: (message: SendbirdFileMessage, deleteMessage: () => Promise<void>, uri: string) => void;
62
+
63
+ renderMessage: (props: {
64
+ focused: boolean;
65
+ message: SendbirdMessage;
66
+ prevMessage?: SendbirdMessage;
67
+ nextMessage?: SendbirdMessage;
68
+ onPress?: () => void;
69
+ onLongPress?: () => void;
70
+ onPressAvatar?: UserProfileContextType['show'];
71
+ channel: T;
72
+ currentUserId?: ChannelMessageListProps<T>['currentUserId'];
73
+ enableMessageGrouping: ChannelMessageListProps<T>['enableMessageGrouping'];
74
+ }) => React.ReactElement | null;
75
+ renderNewMessagesButton: null | CommonComponent<{
76
+ visible: boolean;
77
+ onPress: () => void;
78
+ newMessages: SendbirdMessage[];
79
+ }>;
80
+ renderScrollToBottomButton: null | CommonComponent<{
81
+ visible: boolean;
82
+ onPress: () => void;
83
+ }>;
84
+ flatListProps?: Omit<FlatListProps<SendbirdMessage>, 'data' | 'renderItem'>;
85
+
86
+ /** @deprecated Please use `onPressMediaMessage` instead **/
87
+ onPressImageMessage?: (message: SendbirdFileMessage, uri: string) => void;
88
+ } & {
89
+ ref?: Ref<FlatList<SendbirdMessage>> | undefined;
90
+ };
91
+
92
+ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel>(
93
+ {
94
+ searchItem,
95
+ hasNext,
96
+ channel,
97
+ onEditMessage,
98
+ onDeleteMessage,
99
+ onResendFailedMessage,
100
+ onPressMediaMessage,
101
+ currentUserId,
102
+ renderNewMessagesButton,
103
+ renderScrollToBottomButton,
104
+ renderMessage,
105
+ messages,
106
+ newMessages,
107
+ enableMessageGrouping,
108
+ onScrolledAwayFromBottom,
109
+ scrolledAwayFromBottom,
110
+ onBottomReached,
111
+ onTopReached,
112
+ flatListProps,
113
+ onPressNewMessagesButton,
114
+ onPressScrollToBottomButton,
115
+ onPressImageMessage,
116
+ }: ChannelMessageListProps<T>,
117
+ ref: React.ForwardedRef<FlatList<SendbirdMessage>>,
118
+ ) => {
119
+ const { STRINGS } = useLocalization();
120
+ const { colors } = useUIKitTheme();
121
+ const { left, right } = useSafeAreaInsets();
122
+ const getMessagePressActions = useGetMessagePressActions({
123
+ channel,
124
+ currentUserId,
125
+ onEditMessage,
126
+ onDeleteMessage,
127
+ onResendFailedMessage,
128
+ onPressImageMessage,
129
+ onPressMediaMessage,
130
+ });
131
+
132
+ const safeAreaLayout = { paddingLeft: left, paddingRight: right };
133
+
134
+ const renderItem: ListRenderItem<SendbirdMessage> = useFreshCallback(({ item, index }) => {
135
+ const { onPress, onLongPress } = getMessagePressActions(item);
136
+ return renderMessage({
137
+ message: item,
138
+ prevMessage: messages[index + 1],
139
+ nextMessage: messages[index - 1],
140
+ onPress,
141
+ onLongPress,
142
+ enableMessageGrouping,
143
+ channel,
144
+ currentUserId,
145
+ focused: (searchItem?.startingPoint ?? -1) === item.createdAt,
146
+ });
147
+ });
148
+
149
+ return (
150
+ <View style={[{ flex: 1, backgroundColor: colors.background }, safeAreaLayout]}>
151
+ {channel.isFrozen && (
152
+ <ChannelFrozenBanner style={styles.frozenBanner} text={STRINGS.LABELS.CHANNEL_MESSAGE_LIST_FROZEN} />
153
+ )}
154
+ <ChatFlatList
155
+ {...flatListProps}
156
+ onTopReached={onTopReached}
157
+ onBottomReached={onBottomReached}
158
+ onScrolledAwayFromBottom={onScrolledAwayFromBottom}
159
+ ref={ref}
160
+ data={messages}
161
+ renderItem={renderItem}
162
+ keyExtractor={messageKeyExtractor}
163
+ contentContainerStyle={[
164
+ // { minHeight: '100%', justifyContent: 'flex-end' },
165
+ channel.isFrozen && styles.frozenListPadding,
166
+ flatListProps?.contentContainerStyle,
167
+ ]}
168
+ />
169
+ {renderNewMessagesButton && (
170
+ <View style={[styles.newMsgButton, safeAreaLayout]}>
171
+ {renderNewMessagesButton({
172
+ visible: newMessages.length > 0 && (hasNext() || scrolledAwayFromBottom),
173
+ onPress: () => onPressNewMessagesButton(),
174
+ newMessages,
175
+ })}
176
+ </View>
177
+ )}
178
+ {renderScrollToBottomButton && (
179
+ <View style={[styles.scrollButton, safeAreaLayout]}>
180
+ {renderScrollToBottomButton({
181
+ visible: hasNext() || scrolledAwayFromBottom,
182
+ onPress: () => onPressScrollToBottomButton(),
183
+ })}
184
+ </View>
185
+ )}
186
+ </View>
187
+ );
188
+ };
189
+
190
+ const useGetMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpenChannel>({
191
+ channel,
192
+ currentUserId,
193
+ onResendFailedMessage,
194
+ onEditMessage,
195
+ onDeleteMessage,
196
+ onPressImageMessage,
197
+ onPressMediaMessage,
198
+ }: Pick<
199
+ ChannelMessageListProps<T>,
200
+ | 'channel'
201
+ | 'currentUserId'
202
+ | 'onEditMessage'
203
+ | 'onDeleteMessage'
204
+ | 'onResendFailedMessage'
205
+ | 'onPressImageMessage'
206
+ | 'onPressMediaMessage'
207
+ >) => {
208
+ const { colors } = useUIKitTheme();
209
+ const { STRINGS } = useLocalization();
210
+ const toast = useToast();
211
+ const { openSheet } = useBottomSheet();
212
+ const { alert } = useAlert();
213
+ const { clipboardService, fileService } = usePlatformService();
214
+ const { features } = useSendbirdChat();
215
+
216
+ const handleFailedMessage = (message: HandleableMessage) => {
217
+ openSheet({
218
+ sheetItems: [
219
+ {
220
+ title: STRINGS.LABELS.CHANNEL_MESSAGE_FAILED_RETRY,
221
+ onPress: () => {
222
+ onResendFailedMessage(message).catch(() => toast.show(STRINGS.TOAST.RESEND_MSG_ERROR, 'error'));
223
+ },
224
+ },
225
+ {
226
+ title: STRINGS.LABELS.CHANNEL_MESSAGE_FAILED_REMOVE,
227
+ titleColor: colors.ui.dialog.default.none.destructive,
228
+ onPress: () => confirmDelete(message),
229
+ },
230
+ ],
231
+ });
232
+ };
233
+ const confirmDelete = (message: HandleableMessage) => {
234
+ alert({
235
+ title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_TITLE,
236
+ buttons: [
237
+ {
238
+ text: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_CANCEL,
239
+ },
240
+ {
241
+ text: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_OK,
242
+ style: 'destructive',
243
+ onPress: () => {
244
+ onDeleteMessage(message).catch(() => toast.show(STRINGS.TOAST.DELETE_MSG_ERROR, 'error'));
245
+ },
246
+ },
247
+ ],
248
+ });
249
+ };
250
+
251
+ return (msg: SendbirdMessage) => {
252
+ if (!msg.isUserMessage() && !msg.isFileMessage()) {
253
+ return { onPress: undefined, onLongPress: undefined };
254
+ }
255
+
256
+ const sheetItems: BottomSheetItem['sheetItems'] = [];
257
+ const response: PressActions = {
258
+ onPress: undefined,
259
+ onLongPress: undefined,
260
+ };
261
+
262
+ if (msg.isUserMessage()) {
263
+ sheetItems.push({
264
+ icon: 'copy',
265
+ title: STRINGS.LABELS.CHANNEL_MESSAGE_COPY,
266
+ onPress: () => {
267
+ clipboardService.setString(msg.message || '');
268
+ toast.show(STRINGS.TOAST.COPY_OK, 'success');
269
+ },
270
+ });
271
+
272
+ if (isMyMessage(msg, currentUserId) && msg.sendingStatus === 'succeeded') {
273
+ sheetItems.push(
274
+ {
275
+ icon: 'edit',
276
+ title: STRINGS.LABELS.CHANNEL_MESSAGE_EDIT,
277
+ onPress: () => onEditMessage(msg),
278
+ },
279
+ {
280
+ icon: 'delete',
281
+ title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE,
282
+ onPress: () => confirmDelete(msg),
283
+ },
284
+ );
285
+ }
286
+ }
287
+
288
+ if (msg.isFileMessage()) {
289
+ sheetItems.push({
290
+ icon: 'download',
291
+ title: STRINGS.LABELS.CHANNEL_MESSAGE_SAVE,
292
+ onPress: async () => {
293
+ if (toMegabyte(msg.size) > 4) {
294
+ toast.show(STRINGS.TOAST.DOWNLOAD_START, 'success');
295
+ }
296
+
297
+ fileService
298
+ .save({ fileUrl: msg.url, fileName: msg.name, fileType: msg.type })
299
+ .then((response) => {
300
+ toast.show(STRINGS.TOAST.DOWNLOAD_OK, 'success');
301
+ Logger.log('File saved to', response);
302
+ })
303
+ .catch((err) => {
304
+ toast.show(STRINGS.TOAST.DOWNLOAD_ERROR, 'error');
305
+ Logger.log('File save failure', err);
306
+ });
307
+ },
308
+ });
309
+
310
+ if (isMyMessage(msg, currentUserId) && msg.sendingStatus === 'succeeded') {
311
+ sheetItems.push({
312
+ icon: 'delete',
313
+ title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE,
314
+ onPress: () => confirmDelete(msg),
315
+ });
316
+ }
317
+
318
+ const fileType = getFileType(msg.type || getFileExtension(msg.name));
319
+ switch (fileType) {
320
+ case 'image':
321
+ case 'video':
322
+ case 'audio': {
323
+ response.onPress = () => {
324
+ if (onPressImageMessage && fileType === 'image') {
325
+ Logger.warn(DEPRECATION_WARNING.CHANNEL.ON_PRESS_IMAGE_MESSAGE);
326
+ onPressImageMessage(msg, getAvailableUriFromFileMessage(msg));
327
+ }
328
+ onPressMediaMessage?.(msg, () => onDeleteMessage(msg), getAvailableUriFromFileMessage(msg));
329
+ };
330
+ break;
331
+ }
332
+ default: {
333
+ response.onPress = () => SBUUtils.openURL(msg.url);
334
+ break;
335
+ }
336
+ }
337
+ }
338
+
339
+ if (sheetItems.length > 0) {
340
+ response.onLongPress = () => {
341
+ openSheet({
342
+ sheetItems,
343
+ HeaderComponent: shouldRenderReaction(channel, features.reactionEnabled)
344
+ ? ({ onClose }) => <ReactionAddons.BottomSheet message={msg} channel={channel} onClose={onClose} />
345
+ : undefined,
346
+ });
347
+ };
348
+ }
349
+
350
+ if (msg.sendingStatus === 'failed') {
351
+ response.onLongPress = () => handleFailedMessage(msg);
352
+ response.onPress = () => {
353
+ onResendFailedMessage(msg).catch(() => toast.show(STRINGS.TOAST.RESEND_MSG_ERROR, 'error'));
354
+ };
355
+ }
356
+
357
+ if (msg.sendingStatus === 'pending') {
358
+ response.onLongPress = undefined;
359
+ response.onPress = undefined;
360
+ }
361
+
362
+ return response;
363
+ };
364
+ };
365
+
366
+ const styles = createStyleSheet({
367
+ frozenBanner: {
368
+ position: 'absolute',
369
+ zIndex: 999,
370
+ top: 8,
371
+ left: 8,
372
+ right: 8,
373
+ },
374
+ frozenListPadding: {
375
+ paddingBottom: 32,
376
+ },
377
+ newMsgButton: {
378
+ position: 'absolute',
379
+ zIndex: 999,
380
+ bottom: 10,
381
+ alignSelf: 'center',
382
+ },
383
+ scrollButton: {
384
+ position: 'absolute',
385
+ zIndex: 998,
386
+ bottom: 10,
387
+ right: 16,
388
+ },
389
+ });
390
+
391
+ // NOTE: Due to Generic inference is not working on forwardRef, we need to cast it as typeof ChannelMessageList and implicit `ref` prop
392
+ export default React.forwardRef(ChannelMessageList) as typeof ChannelMessageList;
@@ -1,69 +1,50 @@
1
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from 'react';
2
- import { FlatList, FlatListProps, Platform } from 'react-native';
1
+ import React, { forwardRef, useRef } from 'react';
2
+ import { FlatListProps, Platform, FlatList as RNFlatList, StyleSheet } from 'react-native';
3
3
 
4
+ import { FlatList } from '@sendbird/react-native-scrollview-enhancer';
4
5
  import { useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
5
- import { SendbirdMessage, getMessageUniqId, isMyMessage } from '@sendbird/uikit-utils';
6
+ import { NOOP, SendbirdMessage, getMessageUniqId, useFreshCallback } from '@sendbird/uikit-utils';
6
7
 
7
8
  let ANDROID_BUG_ALERT_SHOWED = Platform.OS !== 'android';
8
9
  const BOTTOM_DETECT_THRESHOLD = 25;
9
- // const AUTO_SCROLL_TO_TOP_THRESHOLD = 15;
10
+ const UNREACHABLE_THRESHOLD = Number.MIN_SAFE_INTEGER;
10
11
 
11
- function hasReachedToBottom(yPos: number, thresholdPx = 0) {
12
- return thresholdPx >= yPos;
13
- }
14
-
15
- export type ChatFlatListRef = { scrollToBottom: (animated?: boolean) => void };
16
12
  type Props = Omit<FlatListProps<SendbirdMessage>, 'onEndReached'> & {
17
- currentUserId?: string;
18
13
  onBottomReached: () => void;
19
14
  onTopReached: () => void;
20
- nextMessages: SendbirdMessage[];
21
- onLeaveScrollBottom: (value: boolean) => void;
15
+ onScrolledAwayFromBottom: (value: boolean) => void;
16
+
17
+ /** @deprecated Please use `onScrolledAwayFromBottom` **/
18
+ onLeaveScrollBottom?: (value: boolean) => void;
19
+ /** @deprecated Not used anymore **/
20
+ nextMessages?: unknown;
22
21
  };
23
22
  // FIXME: Inverted FlatList performance issue on Android {@link https://github.com/facebook/react-native/issues/30034}
24
- const ChatFlatList = forwardRef<ChatFlatListRef, Props>(function CustomFlatList(
25
- { onTopReached, nextMessages, onBottomReached, onLeaveScrollBottom, onScroll, currentUserId, ...props },
23
+ const ChatFlatList = forwardRef<RNFlatList<SendbirdMessage>, Props>(function CustomFlatList(
24
+ { onTopReached, onBottomReached, onScrolledAwayFromBottom, onLeaveScrollBottom, onScroll, ...props },
26
25
  ref,
27
26
  ) {
28
27
  const { select } = useUIKitTheme();
29
- const scrollRef = useRef<FlatList<SendbirdMessage>>(null);
30
- const yPos = useRef(0);
28
+ const contentOffsetY = useRef(0);
31
29
 
32
- useImperativeHandle(
33
- ref,
34
- () => ({
35
- scrollToBottom: (animated = true) => scrollRef.current?.scrollToOffset({ animated, offset: 0 }),
36
- }),
37
- [],
38
- );
30
+ const _onScroll = useFreshCallback<NonNullable<Props['onScroll']>>((event) => {
31
+ onScroll?.(event);
39
32
 
40
- useEffect(() => {
41
- const latestMessage = nextMessages[nextMessages.length - 1];
42
- if (!latestMessage) return;
33
+ const { contentOffset } = event.nativeEvent;
43
34
 
44
- if (hasReachedToBottom(yPos.current)) {
45
- onBottomReached();
46
- } else if (isMyMessage(latestMessage, currentUserId)) {
47
- scrollRef.current?.scrollToOffset({ animated: false, offset: 0 });
48
- }
49
- }, [onBottomReached, nextMessages, currentUserId]);
35
+ const prevOffsetY = contentOffsetY.current;
36
+ const currOffsetY = contentOffset.y;
50
37
 
51
- const _onScroll = useCallback<NonNullable<Props['onScroll']>>(
52
- (event) => {
53
- const { contentOffset } = event.nativeEvent;
54
- if (BOTTOM_DETECT_THRESHOLD < yPos.current && contentOffset.y <= BOTTOM_DETECT_THRESHOLD) {
55
- onLeaveScrollBottom(false);
56
- } else if (BOTTOM_DETECT_THRESHOLD < contentOffset.y && yPos.current <= BOTTOM_DETECT_THRESHOLD) {
57
- onLeaveScrollBottom(true);
58
- }
59
-
60
- yPos.current = contentOffset.y;
38
+ if (BOTTOM_DETECT_THRESHOLD < prevOffsetY && currOffsetY <= BOTTOM_DETECT_THRESHOLD) {
39
+ onScrolledAwayFromBottom(false);
40
+ onLeaveScrollBottom?.(false);
41
+ } else if (BOTTOM_DETECT_THRESHOLD < currOffsetY && prevOffsetY <= BOTTOM_DETECT_THRESHOLD) {
42
+ onScrolledAwayFromBottom(true);
43
+ onLeaveScrollBottom?.(true);
44
+ }
61
45
 
62
- onScroll?.(event);
63
- if (hasReachedToBottom(yPos.current)) onBottomReached();
64
- },
65
- [onScroll, onBottomReached],
66
- );
46
+ contentOffsetY.current = contentOffset.y;
47
+ });
67
48
 
68
49
  if (__DEV__ && !ANDROID_BUG_ALERT_SHOWED) {
69
50
  ANDROID_BUG_ALERT_SHOWED = true;
@@ -83,14 +64,15 @@ const ChatFlatList = forwardRef<ChatFlatListRef, Props>(function CustomFlatList(
83
64
  {...props}
84
65
  // FIXME: inverted list of ListEmptyComponent is reversed {@link https://github.com/facebook/react-native/issues/21196#issuecomment-836937743}
85
66
  inverted={Boolean(props.data?.length)}
86
- // FIXME: maintainVisibleContentPosition is not working on Android {@link https://github.com/facebook/react-native/issues/25239}
87
- // maintainVisibleContentPosition={{ minIndexForVisible: 1, autoscrollToTopThreshold: AUTO_SCROLL_TO_TOP_THRESHOLD }}
88
- ref={scrollRef}
89
- onEndReachedThreshold={0.5}
67
+ ref={ref}
90
68
  onEndReached={onTopReached}
69
+ onScrollToIndexFailed={NOOP}
70
+ onStartReached={onBottomReached}
91
71
  scrollEventThrottle={16}
92
72
  onScroll={_onScroll}
93
73
  keyExtractor={getMessageUniqId}
74
+ style={{ flex: 1, ...StyleSheet.flatten(props.style) }}
75
+ maintainVisibleContentPosition={{ minIndexForVisible: 0, autoscrollToTopThreshold: UNREACHABLE_THRESHOLD }}
94
76
  />
95
77
  );
96
78
  });
@@ -0,0 +1,125 @@
1
+ import React from 'react';
2
+
3
+ import {
4
+ Avatar,
5
+ Box,
6
+ Icon,
7
+ PressBox,
8
+ Text,
9
+ createStyleSheet,
10
+ useUIKitTheme,
11
+ } from '@sendbird/uikit-react-native-foundation';
12
+ import type { SendbirdBaseMessage } from '@sendbird/uikit-utils';
13
+ import { getFileExtension, getFileType, useIIFE } from '@sendbird/uikit-utils';
14
+
15
+ import type { MessageSearchProps } from '../domain/messageSearch/types';
16
+ import { useLocalization } from '../hooks/useContext';
17
+
18
+ const iconMapper = { audio: 'file-audio', image: 'photo', video: 'play', file: 'file-document' } as const;
19
+
20
+ export const MessageSearchResultItem: MessageSearchProps['List']['renderSearchResultItem'] = ({ onPress, message }) => {
21
+ const { colors, select, palette } = useUIKitTheme();
22
+ const { STRINGS } = useLocalization();
23
+
24
+ const fileIcon = useIIFE(() => {
25
+ if (!message?.isFileMessage()) return undefined;
26
+ return iconMapper[getFileType(message.type || getFileExtension(message.name))];
27
+ });
28
+
29
+ return (
30
+ <PressBox onPress={onPress}>
31
+ <Box style={styles.container}>
32
+ <Avatar size={styles.avatarSize.width} uri={getSenderProfile(message)} containerStyle={styles.avatar} />
33
+
34
+ <Box flex={1} paddingRight={16}>
35
+ <Box style={styles.titleLine}>
36
+ <Box flex={1} marginRight={4} justifyContent={'center'}>
37
+ <Text subtitle2 color={colors.onBackground01} numberOfLines={1}>
38
+ {STRINGS.MESSAGE_SEARCH.SEARCH_RESULT_ITEM_TITLE(message)}
39
+ </Text>
40
+ </Box>
41
+ <Box paddingTop={2}>
42
+ <Text caption2 color={colors.onBackground02}>
43
+ {STRINGS.MESSAGE_SEARCH.SEARCH_RESULT_ITEM_TITLE_CAPTION(message)}
44
+ </Text>
45
+ </Box>
46
+ </Box>
47
+
48
+ <Box flex={1}>
49
+ <Box alignItems={'center'} flexDirection={'row'}>
50
+ {fileIcon && (
51
+ <Icon
52
+ size={18}
53
+ icon={fileIcon}
54
+ color={colors.onBackground02}
55
+ containerStyle={[
56
+ styles.bodyIcon,
57
+ { backgroundColor: select({ light: palette.background100, dark: palette.background500 }) },
58
+ ]}
59
+ />
60
+ )}
61
+
62
+ <Text
63
+ body3
64
+ numberOfLines={fileIcon ? 1 : 2}
65
+ ellipsizeMode={fileIcon ? 'middle' : 'tail'}
66
+ style={styles.bodyText}
67
+ color={colors.onBackground03}
68
+ >
69
+ {STRINGS.MESSAGE_SEARCH.SEARCH_RESULT_ITEM_BODY(message)}
70
+ </Text>
71
+ </Box>
72
+ </Box>
73
+
74
+ <Box style={styles.separator} backgroundColor={colors.onBackground04} />
75
+ </Box>
76
+ </Box>
77
+ </PressBox>
78
+ );
79
+ };
80
+
81
+ function getSenderProfile(message: SendbirdBaseMessage) {
82
+ if (message.isUserMessage() || message.isFileMessage()) {
83
+ return message.sender.profileUrl;
84
+ } else {
85
+ return undefined;
86
+ }
87
+ }
88
+
89
+ const styles = createStyleSheet({
90
+ container: {
91
+ height: 76,
92
+ width: '100%',
93
+ flexDirection: 'row',
94
+ alignItems: 'center',
95
+ justifyContent: 'center',
96
+ },
97
+ avatar: {
98
+ marginHorizontal: 16,
99
+ },
100
+ avatarSize: {
101
+ width: 56,
102
+ },
103
+ titleLine: {
104
+ flexDirection: 'row',
105
+ marginTop: 10,
106
+ marginBottom: 4,
107
+ },
108
+ bodyIcon: {
109
+ borderRadius: 8,
110
+ width: 26,
111
+ height: 26,
112
+ marginRight: 4,
113
+ },
114
+ bodyText: {
115
+ flex: 1,
116
+ lineHeight: 16,
117
+ },
118
+ separator: {
119
+ position: 'absolute',
120
+ left: 0,
121
+ right: -16,
122
+ bottom: 0,
123
+ height: 1,
124
+ },
125
+ });
@@ -9,6 +9,8 @@ type Props = {
9
9
  };
10
10
  const ScrollToBottomButton = ({ visible, onPress }: Props) => {
11
11
  const { palette, select } = useUIKitTheme();
12
+ if (!visible) return null;
13
+
12
14
  return (
13
15
  <TouchableOpacity
14
16
  onPress={onPress}
@@ -16,10 +18,7 @@ const ScrollToBottomButton = ({ visible, onPress }: Props) => {
16
18
  disabled={!visible}
17
19
  style={[
18
20
  styles.container,
19
- {
20
- opacity: visible ? 1 : 0,
21
- backgroundColor: select({ dark: palette.background400, light: palette.background50 }),
22
- },
21
+ { backgroundColor: select({ dark: palette.background400, light: palette.background50 }) },
23
22
  ]}
24
23
  >
25
24
  <Icon icon={'chevron-down'} size={22} />
package/src/constants.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export const DEFAULT_LONG_PRESS_DELAY = 350;
2
+ export const MESSAGE_SEARCH_SAFE_SCROLL_DELAY = 500;
3
+ export const MESSAGE_FOCUS_ANIMATION_DELAY = 250;
2
4
  export const DEPRECATION_WARNING = {
3
- GROUP_CHANNEL: {
5
+ CHANNEL: {
4
6
  ON_PRESS_IMAGE_MESSAGE: '`onPressImageMessage` is deprecated, please use `onPressMediaMessage` instead',
5
7
  },
6
8
  } as const;
@@ -55,7 +55,7 @@ const GroupChannelPreviewContainer = ({ onPress, onLongPress, channel }: Props)
55
55
  else return STRINGS.GROUP_CHANNEL_LIST.CHANNEL_PREVIEW_BODY(channel);
56
56
  });
57
57
 
58
- const bodyIcon = useIIFE(() => {
58
+ const fileIcon = useIIFE(() => {
59
59
  if (!channel.lastMessage?.isFileMessage()) return undefined;
60
60
  if (typingUsers.length > 0) return undefined;
61
61
  return iconMapper[getFileType(channel.lastMessage.type || getFileExtension(channel.lastMessage.name))];
@@ -98,7 +98,7 @@ const GroupChannelPreviewContainer = ({ onPress, onLongPress, channel }: Props)
98
98
  titleCaptionLeft={titleCaptionIcon}
99
99
  titleCaption={STRINGS.GROUP_CHANNEL_LIST.CHANNEL_PREVIEW_TITLE_CAPTION(channel)}
100
100
  body={bodyText}
101
- bodyIcon={bodyIcon}
101
+ bodyIcon={fileIcon}
102
102
  badgeCount={channel.unreadMessageCount}
103
103
  mentioned={channel.unreadMentionCount > 0}
104
104
  mentionTrigger={mentionManager.config.trigger}