@sendbird/uikit-react-native 2.4.2 → 2.5.0-rc.0

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
@@ -1,20 +1,38 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { Animated, Easing } from 'react-native';
2
3
 
3
4
  import { useGroupChannelMessages } from '@sendbird/uikit-chat-hooks';
4
- import { NOOP, PASS, SendbirdGroupChannel, messageComparator, useFreshCallback } from '@sendbird/uikit-utils';
5
+ import {
6
+ NOOP,
7
+ PASS,
8
+ SendbirdFileMessage,
9
+ SendbirdGroupChannel,
10
+ SendbirdUserMessage,
11
+ messageComparator,
12
+ useFreshCallback,
13
+ useRefTracker,
14
+ } from '@sendbird/uikit-utils';
5
15
 
6
16
  import MessageRenderer from '../components/MessageRenderer';
7
17
  import NewMessagesButton from '../components/NewMessagesButton';
8
18
  import ScrollToBottomButton from '../components/ScrollToBottomButton';
9
19
  import StatusComposition from '../components/StatusComposition';
20
+ import { MESSAGE_FOCUS_ANIMATION_DELAY, MESSAGE_SEARCH_SAFE_SCROLL_DELAY } from '../constants';
10
21
  import createGroupChannelModule from '../domain/groupChannel/module/createGroupChannelModule';
11
- import type { GroupChannelFragment, GroupChannelModule, GroupChannelProps } from '../domain/groupChannel/types';
22
+ import type {
23
+ GroupChannelFragment,
24
+ GroupChannelModule,
25
+ GroupChannelProps,
26
+ GroupChannelPubSubContextPayload,
27
+ } from '../domain/groupChannel/types';
12
28
  import { useSendbirdChat } from '../hooks/useContext';
29
+ import pubsub from '../utils/pubsub';
13
30
 
14
31
  const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): GroupChannelFragment => {
15
32
  const GroupChannelModule = createGroupChannelModule(initModule);
16
33
 
17
34
  return ({
35
+ searchItem,
18
36
  renderNewMessagesButton = (props) => <NewMessagesButton {...props} />,
19
37
  renderScrollToBottomButton = (props) => <ScrollToBottomButton {...props} />,
20
38
  renderMessage,
@@ -38,30 +56,48 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
38
56
  }) => {
39
57
  const { sdk, currentUser } = useSendbirdChat();
40
58
 
59
+ const [internalSearchItem, setInternalSearchItem] = useState(searchItem);
60
+ const navigateFromMessageSearch = useCallback(() => Boolean(searchItem), []);
61
+
62
+ const [groupChannelPubSub] = useState(() => pubsub<GroupChannelPubSubContextPayload>());
63
+ const [scrolledAwayFromBottom, setScrolledAwayFromBottom] = useState(false);
64
+ const scrolledAwayFromBottomRef = useRefTracker(scrolledAwayFromBottom);
65
+
41
66
  const {
67
+ loading,
42
68
  messages,
43
- nextMessages,
44
- newMessagesFromMembers,
69
+ newMessages,
70
+ resetNewMessages,
45
71
  next,
46
72
  prev,
73
+ hasNext,
47
74
  sendFileMessage,
48
75
  sendUserMessage,
49
76
  updateFileMessage,
50
77
  updateUserMessage,
51
78
  resendMessage,
52
79
  deleteMessage,
53
- loading,
80
+ resetWithStartingPoint,
54
81
  } = useGroupChannelMessages(sdk, channel, currentUser?.userId, {
55
82
  collectionCreator,
56
83
  queryCreator,
57
84
  sortComparator,
58
85
  onChannelDeleted,
59
86
  enableCollectionWithoutLocalCache: !queryCreator,
87
+ shouldCountNewMessages: () => scrolledAwayFromBottomRef.current,
88
+ onMessagesReceived(messages) {
89
+ groupChannelPubSub.publish({ type: 'MESSAGES_RECEIVED', data: { messages } });
90
+ },
91
+ startingPoint: internalSearchItem?.startingPoint,
60
92
  });
61
93
 
94
+ const MessageComponent: GroupChannelProps['MessageList']['renderMessage'] = useCallback(
95
+ withFocusingAnimation(renderMessage ? (props) => <>{renderMessage(props)}</> : MessageRenderer),
96
+ [renderMessage],
97
+ );
98
+
62
99
  const _renderMessage: GroupChannelProps['MessageList']['renderMessage'] = useFreshCallback((props) => {
63
- if (renderMessage) return renderMessage(props);
64
- return <MessageRenderer {...props} />;
100
+ return <MessageComponent {...props} />;
65
101
  });
66
102
 
67
103
  const memoizedFlatListProps = useMemo(
@@ -70,19 +106,34 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
70
106
  contentContainerStyle: { flexGrow: 1 },
71
107
  ...flatListProps,
72
108
  }),
73
- [loading, flatListProps],
109
+ [flatListProps],
74
110
  );
75
111
 
112
+ const onResetMessageList = useCallback((callback?: () => void) => {
113
+ resetWithStartingPoint(Number.MAX_SAFE_INTEGER, callback);
114
+ setInternalSearchItem(undefined);
115
+ }, []);
116
+
117
+ const onPending = (message: SendbirdFileMessage | SendbirdUserMessage) => {
118
+ groupChannelPubSub.publish({ type: 'MESSAGE_SENT_PENDING', data: { message } });
119
+ };
120
+
121
+ const onSent = (message: SendbirdFileMessage | SendbirdUserMessage) => {
122
+ groupChannelPubSub.publish({ type: 'MESSAGE_SENT_SUCCESS', data: { message } });
123
+ };
124
+
76
125
  const onPressSendUserMessage: GroupChannelProps['Input']['onPressSendUserMessage'] = useFreshCallback(
77
126
  async (params) => {
78
127
  const processedParams = await onBeforeSendUserMessage(params);
79
- await sendUserMessage(processedParams);
128
+ const message = await sendUserMessage(processedParams, onPending);
129
+ onSent(message);
80
130
  },
81
131
  );
82
132
  const onPressSendFileMessage: GroupChannelProps['Input']['onPressSendFileMessage'] = useFreshCallback(
83
133
  async (params) => {
84
134
  const processedParams = await onBeforeSendFileMessage(params);
85
- await sendFileMessage(processedParams);
135
+ const message = await sendFileMessage(processedParams, onPending);
136
+ onSent(message);
86
137
  },
87
138
  );
88
139
  const onPressUpdateUserMessage: GroupChannelProps['Input']['onPressUpdateUserMessage'] = useFreshCallback(
@@ -97,11 +148,16 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
97
148
  await updateFileMessage(message.messageId, processedParams);
98
149
  },
99
150
  );
151
+ const onScrolledAwayFromBottom = useFreshCallback((value: boolean) => {
152
+ if (!value) resetNewMessages();
153
+ setScrolledAwayFromBottom(value);
154
+ });
100
155
 
101
156
  /** @deprecated **/
102
157
  const onSendFileMessage: GroupChannelProps['Input']['onSendFileMessage'] = useFreshCallback(async (file) => {
103
158
  const processedParams = await onBeforeSendFileMessage({ file });
104
- await sendFileMessage(processedParams);
159
+ const message = await sendFileMessage(processedParams, onPending);
160
+ onSent(message);
105
161
  });
106
162
  /** @deprecated **/
107
163
  const onSendUserMessage: GroupChannelProps['Input']['onSendUserMessage'] = useFreshCallback(
@@ -112,7 +168,8 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
112
168
  mentionedMessageTemplate: mention?.messageTemplate,
113
169
  mentionType: mention?.type,
114
170
  });
115
- await sendUserMessage(processedParams);
171
+ const message = await sendUserMessage(processedParams, onPending);
172
+ onSent(message);
116
173
  },
117
174
  );
118
175
  /** @deprecated **/
@@ -138,28 +195,39 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
138
195
  return (
139
196
  <GroupChannelModule.Provider
140
197
  channel={channel}
198
+ groupChannelPubSub={groupChannelPubSub}
141
199
  enableTypingIndicator={enableTypingIndicator}
142
200
  keyboardAvoidOffset={keyboardAvoidOffset}
143
201
  >
144
- <GroupChannelModule.Header onPressHeaderLeft={onPressHeaderLeft} onPressHeaderRight={onPressHeaderRight} />
202
+ <GroupChannelModule.Header
203
+ shouldHideRight={navigateFromMessageSearch}
204
+ onPressHeaderLeft={onPressHeaderLeft}
205
+ onPressHeaderRight={onPressHeaderRight}
206
+ />
145
207
  <StatusComposition loading={loading} LoadingComponent={<GroupChannelModule.StatusLoading />}>
146
208
  <GroupChannelModule.MessageList
147
209
  channel={channel}
210
+ searchItem={internalSearchItem}
211
+ onResetMessageList={onResetMessageList}
148
212
  enableMessageGrouping={enableMessageGrouping}
149
213
  currentUserId={currentUser?.userId}
150
214
  renderMessage={_renderMessage}
151
215
  messages={messages}
152
- nextMessages={nextMessages}
153
- newMessagesFromMembers={newMessagesFromMembers}
216
+ newMessages={newMessages}
154
217
  onTopReached={prev}
155
218
  onBottomReached={next}
219
+ hasNext={hasNext}
220
+ scrolledAwayFromBottom={scrolledAwayFromBottom}
221
+ onScrolledAwayFromBottom={onScrolledAwayFromBottom}
156
222
  renderNewMessagesButton={renderNewMessagesButton}
157
223
  renderScrollToBottomButton={renderScrollToBottomButton}
158
224
  onResendFailedMessage={resendMessage}
159
225
  onDeleteMessage={deleteMessage}
160
- onPressImageMessage={onPressImageMessage}
161
226
  onPressMediaMessage={onPressMediaMessage}
162
227
  flatListProps={memoizedFlatListProps}
228
+ nextMessages={newMessages}
229
+ newMessagesFromMembers={newMessages}
230
+ onPressImageMessage={onPressImageMessage}
163
231
  />
164
232
  <GroupChannelModule.Input
165
233
  SuggestedMentionList={GroupChannelModule.SuggestedMentionList}
@@ -187,4 +255,33 @@ function shouldRenderInput(channel: SendbirdGroupChannel) {
187
255
  return true;
188
256
  }
189
257
 
258
+ function withFocusingAnimation<P extends unknown & { focused: boolean }>(Component: React.ComponentType<P>) {
259
+ return React.memo<P>((props) => {
260
+ const translateY = useRef(new Animated.Value(0)).current;
261
+
262
+ useEffect(() => {
263
+ if (props.focused) {
264
+ setTimeout(() => {
265
+ Animated.sequence(
266
+ [
267
+ { toValue: -10, duration: 500 },
268
+ { toValue: 0, duration: 100 },
269
+ { toValue: -10, duration: 200 },
270
+ { toValue: 0, duration: 100 },
271
+ ].map((value) =>
272
+ Animated.timing(translateY, { ...value, useNativeDriver: true, easing: Easing.inOut(Easing.ease) }),
273
+ ),
274
+ ).start();
275
+ }, MESSAGE_SEARCH_SAFE_SCROLL_DELAY + MESSAGE_FOCUS_ANIMATION_DELAY);
276
+ }
277
+ }, [props.focused]);
278
+
279
+ return (
280
+ <Animated.View style={{ transform: [{ translateY }] }}>
281
+ <Component {...props} />
282
+ </Animated.View>
283
+ );
284
+ });
285
+ }
286
+
190
287
  export default createGroupChannelFragment;
@@ -18,6 +18,7 @@ const createGroupChannelSettingsFragment = (
18
18
  channel,
19
19
  onPressMenuModeration,
20
20
  onPressMenuMembers,
21
+ onPressMenuSearchInChannel,
21
22
  onPressMenuLeaveChannel,
22
23
  onPressMenuNotification,
23
24
  menuItemsCreator,
@@ -40,6 +41,7 @@ const createGroupChannelSettingsFragment = (
40
41
  menuItemsCreator={menuItemsCreator}
41
42
  onPressMenuModeration={onPressMenuModeration}
42
43
  onPressMenuMembers={onPressMenuMembers}
44
+ onPressMenuSearchInChannel={onPressMenuSearchInChannel}
43
45
  onPressMenuLeaveChannel={onPressMenuLeaveChannel}
44
46
  onPressMenuNotification={onPressMenuNotification}
45
47
  />
@@ -0,0 +1,159 @@
1
+ import React, { useRef, useState } from 'react';
2
+
3
+ import { MessageSearchOrder } from '@sendbird/chat/message';
4
+ import {
5
+ Logger,
6
+ NOOP,
7
+ SendbirdBaseMessage,
8
+ SendbirdChatSDK,
9
+ SendbirdGroupChannel,
10
+ SendbirdMessageSearchQuery,
11
+ useFreshCallback,
12
+ useSafeAreaPadding,
13
+ } from '@sendbird/uikit-utils';
14
+
15
+ import { MessageSearchResultItem } from '../components/MessageSearchResultItem';
16
+ import StatusComposition from '../components/StatusComposition';
17
+ import { createMessageSearchModule } from '../domain/messageSearch';
18
+ import type { MessageSearchFragment, MessageSearchModule, MessageSearchProps } from '../domain/messageSearch/types';
19
+ import { useSendbirdChat } from '../hooks/useContext';
20
+
21
+ type DefaultMessageSearchQueryParams = {
22
+ keyword: string;
23
+ channelUrl: string;
24
+ messageTimestampFrom: number;
25
+ order: MessageSearchOrder;
26
+ };
27
+
28
+ type SearchQueryOptions = DefaultMessageSearchQueryParams & {
29
+ queryCreator?: (params: DefaultMessageSearchQueryParams) => SendbirdMessageSearchQuery;
30
+ };
31
+
32
+ function getMessageSearchQuery(sdk: SendbirdChatSDK, options: SearchQueryOptions) {
33
+ if (options.queryCreator) return options.queryCreator(options);
34
+ return sdk.createMessageSearchQuery(options);
35
+ }
36
+
37
+ const createMessageSearchFragment = (initModule?: Partial<MessageSearchModule>): MessageSearchFragment => {
38
+ const MessageSearchModule = createMessageSearchModule(initModule);
39
+
40
+ return ({ onPressHeaderLeft = NOOP, channel, queryCreator, renderSearchResultItem, onPressSearchResultItem }) => {
41
+ const padding = useSafeAreaPadding(['left', 'right', 'bottom']);
42
+
43
+ const { sdk } = useSendbirdChat();
44
+ const { keyword, setKeyword, search, searchResults, loading, next, error, query } = useMessageSearch(sdk, {
45
+ channel,
46
+ queryCreator,
47
+ });
48
+
49
+ const renderItem: MessageSearchProps['List']['renderSearchResultItem'] = useFreshCallback((props) => {
50
+ if (renderSearchResultItem) return renderSearchResultItem(props);
51
+ return <MessageSearchResultItem {...props} />;
52
+ });
53
+
54
+ return (
55
+ <MessageSearchModule.Provider>
56
+ <MessageSearchModule.Header
57
+ keyword={keyword}
58
+ onChangeKeyword={setKeyword}
59
+ onPressHeaderLeft={onPressHeaderLeft}
60
+ onPressHeaderRight={search}
61
+ />
62
+ <StatusComposition
63
+ loading={loading}
64
+ LoadingComponent={<MessageSearchModule.StatusLoading />}
65
+ error={Boolean(error)}
66
+ ErrorComponent={<MessageSearchModule.StatusError onPressRetry={search} />}
67
+ >
68
+ {query && (
69
+ <MessageSearchModule.List
70
+ channel={channel}
71
+ onPressSearchResultItem={onPressSearchResultItem}
72
+ messages={searchResults}
73
+ renderSearchResultItem={renderItem}
74
+ flatListProps={{
75
+ keyboardDismissMode: 'on-drag',
76
+ keyboardShouldPersistTaps: 'handled',
77
+ onEndReached: next,
78
+ ListEmptyComponent: MessageSearchModule.StatusEmpty,
79
+ contentContainerStyle: { flexGrow: 1, ...padding },
80
+ }}
81
+ />
82
+ )}
83
+ </StatusComposition>
84
+ </MessageSearchModule.Provider>
85
+ );
86
+ };
87
+ };
88
+
89
+ const useMessageSearch = (
90
+ sdk: SendbirdChatSDK,
91
+ { channel, queryCreator }: { channel: SendbirdGroupChannel; queryCreator: SearchQueryOptions['queryCreator'] },
92
+ ) => {
93
+ const [query, setQuery] = useState<SendbirdMessageSearchQuery>();
94
+ const [keyword, setKeyword] = useState('');
95
+ const [loading, setLoading] = useState(false);
96
+ const [error, setError] = useState<unknown>(null);
97
+ const [searchResults, setSearchResults] = useState<SendbirdBaseMessage[]>([]);
98
+ const queryInProgress = useRef(false);
99
+
100
+ const search = useFreshCallback(async () => {
101
+ if (keyword.length <= 0) return;
102
+ if (queryInProgress.current) return;
103
+
104
+ const query = getMessageSearchQuery(sdk, {
105
+ keyword,
106
+ channelUrl: channel.url,
107
+ messageTimestampFrom: Math.max(channel.joinedAt, channel.invitedAt),
108
+ order: MessageSearchOrder.TIMESTAMP,
109
+ queryCreator,
110
+ });
111
+
112
+ setQuery(query);
113
+ setLoading(true);
114
+ setError(null);
115
+
116
+ try {
117
+ queryInProgress.current = true;
118
+ const result = await query.next();
119
+ setSearchResults(result);
120
+ } catch (err) {
121
+ Logger.warn('[MessageSearchFragment] search failure', err);
122
+ setError(err);
123
+ } finally {
124
+ queryInProgress.current = false;
125
+ setLoading(false);
126
+ }
127
+ });
128
+
129
+ const next = useFreshCallback(async () => {
130
+ if (!query?.hasNext) return;
131
+ if (queryInProgress.current) return;
132
+
133
+ try {
134
+ queryInProgress.current = true;
135
+ const result = await query.next();
136
+ setSearchResults((prev) => [...prev, ...result]);
137
+ } catch (err) {
138
+ Logger.warn('[MessageSearchFragment] next failure', err);
139
+ setError(err);
140
+ } finally {
141
+ queryInProgress.current = false;
142
+ }
143
+ });
144
+
145
+ return {
146
+ keyword,
147
+ setKeyword,
148
+
149
+ query,
150
+ loading,
151
+ error,
152
+ searchResults,
153
+
154
+ search,
155
+ next,
156
+ };
157
+ };
158
+
159
+ export default createMessageSearchFragment;
@@ -1,17 +1,31 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useMemo, useState } from 'react';
2
2
 
3
3
  import { SendbirdError } from '@sendbird/chat';
4
4
  import { useOpenChannelMessages } from '@sendbird/uikit-chat-hooks';
5
5
  import { useToast } from '@sendbird/uikit-react-native-foundation';
6
- import { NOOP, PASS, SBErrorCode, messageComparator, useFreshCallback } from '@sendbird/uikit-utils';
6
+ import {
7
+ NOOP,
8
+ PASS,
9
+ SBErrorCode,
10
+ SendbirdFileMessage,
11
+ SendbirdUserMessage,
12
+ messageComparator,
13
+ useFreshCallback,
14
+ } from '@sendbird/uikit-utils';
7
15
 
8
16
  import OpenChannelMessageRenderer from '../components/OpenChannelMessageRenderer';
9
17
  import ScrollToBottomButton from '../components/ScrollToBottomButton';
10
18
  import StatusComposition from '../components/StatusComposition';
11
19
  import { UNKNOWN_USER_ID } from '../constants';
12
20
  import { createOpenChannelModule } from '../domain/openChannel';
13
- import type { OpenChannelFragment, OpenChannelModule, OpenChannelProps } from '../domain/openChannel/types';
21
+ import type {
22
+ OpenChannelFragment,
23
+ OpenChannelModule,
24
+ OpenChannelProps,
25
+ OpenChannelPubSubContextPayload,
26
+ } from '../domain/openChannel/types';
14
27
  import { useLocalization, useSendbirdChat, useUserProfile } from '../hooks/useContext';
28
+ import pubsub from '../utils/pubsub';
15
29
 
16
30
  const createOpenChannelFragment = (initModule?: Partial<OpenChannelModule>): OpenChannelFragment => {
17
31
  const OpenChannelModule = createOpenChannelModule(initModule);
@@ -40,16 +54,19 @@ const createOpenChannelFragment = (initModule?: Partial<OpenChannelModule>): Ope
40
54
  sortComparator = messageComparator,
41
55
  }) => {
42
56
  const { sdk, currentUser } = useSendbirdChat();
57
+
43
58
  const { STRINGS } = useLocalization();
44
59
  const { show: showToast } = useToast();
45
60
  const { show: showUserProfile } = useUserProfile();
46
61
 
62
+ const [openChannelPubSub] = useState(() => pubsub<OpenChannelPubSubContextPayload>());
63
+
47
64
  const {
48
65
  messages,
49
- nextMessages,
50
- newMessagesFromMembers,
66
+ newMessages,
51
67
  next,
52
68
  prev,
69
+ hasNext,
53
70
  sendFileMessage,
54
71
  sendUserMessage,
55
72
  updateFileMessage,
@@ -64,8 +81,9 @@ const createOpenChannelFragment = (initModule?: Partial<OpenChannelModule>): Ope
64
81
  onError(error) {
65
82
  if (error instanceof SendbirdError) {
66
83
  switch (error.code) {
67
- case SBErrorCode.CHANNEL_NOT_FOUND_SDK:
68
- case SBErrorCode.CHANNEL_NOT_FOUND_SERVER: {
84
+ case SBErrorCode.RESOURCE_NOT_FOUND:
85
+ case SBErrorCode.CHANNEL_NOT_FOUND:
86
+ case SBErrorCode.BANNED_USER_SEND_MESSAGE_NOT_ALLOWED: {
69
87
  return showToast(STRINGS.TOAST.GET_CHANNEL_ERROR, 'error');
70
88
  }
71
89
  }
@@ -73,6 +91,9 @@ const createOpenChannelFragment = (initModule?: Partial<OpenChannelModule>): Ope
73
91
 
74
92
  showToast(STRINGS.TOAST.UNKNOWN_ERROR, 'error');
75
93
  },
94
+ onMessagesReceived(messages) {
95
+ openChannelPubSub.publish({ type: 'MESSAGES_RECEIVED', data: { messages } });
96
+ },
76
97
  });
77
98
 
78
99
  const isOperator = channel.isOperator(currentUser?.userId ?? UNKNOWN_USER_ID);
@@ -91,16 +112,25 @@ const createOpenChannelFragment = (initModule?: Partial<OpenChannelModule>): Ope
91
112
  [loading, flatListProps],
92
113
  );
93
114
 
115
+ const onPending = (message: SendbirdFileMessage | SendbirdUserMessage) => {
116
+ openChannelPubSub.publish({ type: 'MESSAGE_SENT_PENDING', data: { message } });
117
+ };
118
+ const onSent = (message: SendbirdFileMessage | SendbirdUserMessage) => {
119
+ openChannelPubSub.publish({ type: 'MESSAGE_SENT_SUCCESS', data: { message } });
120
+ };
121
+
94
122
  const onPressSendUserMessage: OpenChannelProps['Input']['onPressSendUserMessage'] = useFreshCallback(
95
123
  async (params) => {
96
124
  const processedParams = await onBeforeSendUserMessage(params);
97
- await sendUserMessage(processedParams);
125
+ const message = await sendUserMessage(processedParams, onPending);
126
+ onSent(message);
98
127
  },
99
128
  );
100
129
  const onPressSendFileMessage: OpenChannelProps['Input']['onPressSendFileMessage'] = useFreshCallback(
101
130
  async (params) => {
102
131
  const processedParams = await onBeforeSendFileMessage(params);
103
- await sendFileMessage(processedParams);
132
+ const message = await sendFileMessage(processedParams, onPending);
133
+ onSent(message);
104
134
  },
105
135
  );
106
136
  const onPressUpdateUserMessage: OpenChannelProps['Input']['onPressUpdateUserMessage'] = useFreshCallback(
@@ -117,7 +147,11 @@ const createOpenChannelFragment = (initModule?: Partial<OpenChannelModule>): Ope
117
147
  );
118
148
 
119
149
  return (
120
- <OpenChannelModule.Provider channel={channel} keyboardAvoidOffset={keyboardAvoidOffset}>
150
+ <OpenChannelModule.Provider
151
+ openChannelPubSub={openChannelPubSub}
152
+ channel={channel}
153
+ keyboardAvoidOffset={keyboardAvoidOffset}
154
+ >
121
155
  <OpenChannelModule.Header
122
156
  onPressHeaderLeft={onPressHeaderLeft}
123
157
  rightIconName={isOperator ? 'info' : 'members'}
@@ -126,14 +160,16 @@ const createOpenChannelFragment = (initModule?: Partial<OpenChannelModule>): Ope
126
160
  <StatusComposition loading={loading} LoadingComponent={<OpenChannelModule.StatusLoading />}>
127
161
  <OpenChannelModule.MessageList
128
162
  channel={channel}
163
+ hasNext={hasNext}
129
164
  enableMessageGrouping={enableMessageGrouping}
130
165
  currentUserId={currentUser?.userId}
131
166
  renderMessage={_renderMessage}
132
167
  messages={messages}
133
- nextMessages={nextMessages}
134
- newMessagesFromMembers={newMessagesFromMembers}
168
+ newMessages={newMessages}
135
169
  onTopReached={prev}
136
170
  onBottomReached={next}
171
+ scrolledAwayFromBottom={false}
172
+ onScrolledAwayFromBottom={NOOP}
137
173
  renderNewMessagesButton={renderNewMessagesButton}
138
174
  renderScrollToBottomButton={renderScrollToBottomButton}
139
175
  onResendFailedMessage={resendMessage}
@@ -1,7 +1,8 @@
1
1
  import { useCallback, useRef, useState } from 'react';
2
2
 
3
- import type { SendbirdGroupChannel, SendbirdMember, SendbirdUser } from '@sendbird/uikit-utils';
4
- import { useAsyncEffect, useDebounceEffect } from '@sendbird/uikit-utils';
3
+ import { useChannelHandler } from '@sendbird/uikit-chat-hooks';
4
+ import type { SendbirdChatSDK, SendbirdGroupChannel, SendbirdMember, SendbirdUser } from '@sendbird/uikit-utils';
5
+ import { isDifferentChannel, useAsyncEffect, useDebounceEffect, useUniqHandlerId } from '@sendbird/uikit-utils';
5
6
 
6
7
  import { useSendbirdChat } from '../hooks/useContext';
7
8
  import type { Range } from '../types';
@@ -9,8 +10,9 @@ import type { Range } from '../types';
9
10
  const useMentionSuggestion = (params: {
10
11
  text: string;
11
12
  selection: Range;
12
- channel: SendbirdGroupChannel;
13
13
  mentionedUsers: { user: SendbirdUser; range: Range }[];
14
+ sdk: SendbirdChatSDK;
15
+ channel: SendbirdGroupChannel;
14
16
  }) => {
15
17
  const { text, selection, channel, mentionedUsers } = params;
16
18
 
@@ -20,6 +22,24 @@ const useMentionSuggestion = (params: {
20
22
  setFreshChannel(await channel.refresh());
21
23
  }, [channel.url]);
22
24
 
25
+ const id = useUniqHandlerId('useMentionSuggestion');
26
+
27
+ useChannelHandler(params.sdk, id, {
28
+ onUserJoined(eventChannel) {
29
+ if (isDifferentChannel(eventChannel, channel)) return;
30
+ setFreshChannel(eventChannel);
31
+ },
32
+ onUserLeft(eventChannel) {
33
+ if (isDifferentChannel(eventChannel, channel)) return;
34
+ setFreshChannel(eventChannel);
35
+ },
36
+ onUserBanned(eventChannel) {
37
+ if (isDifferentChannel(eventChannel, channel)) return;
38
+ if (!eventChannel.isGroupChannel()) return;
39
+ setFreshChannel(eventChannel);
40
+ },
41
+ });
42
+
23
43
  const { mentionManager, currentUser } = useSendbirdChat();
24
44
  const [members, setMembers] = useState<SendbirdMember[]>([]);
25
45
 
package/src/index.ts CHANGED
@@ -32,6 +32,7 @@ export { default as createGroupChannelRegisterOperatorFragment } from './fragmen
32
32
  export { default as createGroupChannelMutedMembersFragment } from './fragments/createGroupChannelMutedMembersFragment';
33
33
  export { default as createGroupChannelBannedUsersFragment } from './fragments/createGroupChannelBannedUsersFragment';
34
34
  export { default as createGroupChannelNotificationsFragment } from './fragments/createGroupChannelNotificationsFragment';
35
+ export { default as createMessageSearchFragment } from './fragments/createMessageSearchFragment';
35
36
 
36
37
  /** Fragments - open channels **/
37
38
  export { default as createOpenChannelFragment } from './fragments/createOpenChannelFragment';
@@ -91,6 +92,8 @@ export * from './domain/groupChannelNotifications/types';
91
92
 
92
93
  export * from './domain/groupChannelUserList/types';
93
94
 
95
+ export * from './domain/messageSearch/types';
96
+
94
97
  /** Feature - open channels **/
95
98
  export * from './domain/openChannel';
96
99
  export * from './domain/openChannel/types';