@sendbird/uikit-react-native 3.0.4 → 3.1.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 (145) hide show
  1. package/lib/commonjs/components/ChannelInput/SendInput.js +151 -19
  2. package/lib/commonjs/components/ChannelInput/SendInput.js.map +1 -1
  3. package/lib/commonjs/components/ChannelInput/index.js +5 -13
  4. package/lib/commonjs/components/ChannelInput/index.js.map +1 -1
  5. package/lib/commonjs/components/ChannelMessageList/index.js +34 -20
  6. package/lib/commonjs/components/ChannelMessageList/index.js.map +1 -1
  7. package/lib/commonjs/components/ChatFlatList/index.js +1 -1
  8. package/lib/commonjs/components/ChatFlatList/index.js.map +1 -1
  9. package/lib/commonjs/components/GroupChannelMessageRenderer/GroupChannelMessageFocusAnimation.js +4 -1
  10. package/lib/commonjs/components/GroupChannelMessageRenderer/GroupChannelMessageFocusAnimation.js.map +1 -1
  11. package/lib/commonjs/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.js +191 -0
  12. package/lib/commonjs/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.js.map +1 -0
  13. package/lib/commonjs/components/GroupChannelMessageRenderer/index.js +12 -1
  14. package/lib/commonjs/components/GroupChannelMessageRenderer/index.js.map +1 -1
  15. package/lib/commonjs/components/MessageSearchResultItem.js +2 -8
  16. package/lib/commonjs/components/MessageSearchResultItem.js.map +1 -1
  17. package/lib/commonjs/components/ReactionAddons/BottomSheetReactionAddon.js +8 -7
  18. package/lib/commonjs/components/ReactionAddons/BottomSheetReactionAddon.js.map +1 -1
  19. package/lib/commonjs/containers/GroupChannelPreviewContainer.js +3 -9
  20. package/lib/commonjs/containers/GroupChannelPreviewContainer.js.map +1 -1
  21. package/lib/commonjs/containers/SendbirdUIKitContainer.js +43 -9
  22. package/lib/commonjs/containers/SendbirdUIKitContainer.js.map +1 -1
  23. package/lib/commonjs/contexts/SendbirdChatCtx.js +5 -0
  24. package/lib/commonjs/contexts/SendbirdChatCtx.js.map +1 -1
  25. package/lib/commonjs/domain/groupChannel/component/GroupChannelInput.js +5 -1
  26. package/lib/commonjs/domain/groupChannel/component/GroupChannelInput.js.map +1 -1
  27. package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js +47 -10
  28. package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
  29. package/lib/commonjs/domain/groupChannel/module/moduleContext.js +38 -3
  30. package/lib/commonjs/domain/groupChannel/module/moduleContext.js.map +1 -1
  31. package/lib/commonjs/domain/groupChannel/types.js.map +1 -1
  32. package/lib/commonjs/domain/openChannel/types.js.map +1 -1
  33. package/lib/commonjs/fragments/createGroupChannelFragment.js +19 -0
  34. package/lib/commonjs/fragments/createGroupChannelFragment.js.map +1 -1
  35. package/lib/commonjs/hooks/useConnection.js +2 -1
  36. package/lib/commonjs/hooks/useConnection.js.map +1 -1
  37. package/lib/commonjs/hooks/useMentionSuggestion.js +1 -1
  38. package/lib/commonjs/hooks/useMentionSuggestion.js.map +1 -1
  39. package/lib/commonjs/hooks/useMentionTextInput.js +4 -3
  40. package/lib/commonjs/hooks/useMentionTextInput.js.map +1 -1
  41. package/lib/commonjs/libs/MentionManager.js.map +1 -1
  42. package/lib/commonjs/localization/StringSet.type.js.map +1 -1
  43. package/lib/commonjs/localization/createBaseStringSet.js +29 -0
  44. package/lib/commonjs/localization/createBaseStringSet.js.map +1 -1
  45. package/lib/commonjs/platform/createMediaService.native.js +1 -1
  46. package/lib/commonjs/platform/createMediaService.native.js.map +1 -1
  47. package/lib/commonjs/platform/types.js.map +1 -1
  48. package/lib/commonjs/utils/pubsub.js +3 -1
  49. package/lib/commonjs/utils/pubsub.js.map +1 -1
  50. package/lib/commonjs/version.js +1 -1
  51. package/lib/commonjs/version.js.map +1 -1
  52. package/lib/module/components/ChannelInput/SendInput.js +153 -21
  53. package/lib/module/components/ChannelInput/SendInput.js.map +1 -1
  54. package/lib/module/components/ChannelInput/index.js +6 -14
  55. package/lib/module/components/ChannelInput/index.js.map +1 -1
  56. package/lib/module/components/ChannelMessageList/index.js +34 -20
  57. package/lib/module/components/ChannelMessageList/index.js.map +1 -1
  58. package/lib/module/components/ChatFlatList/index.js +1 -1
  59. package/lib/module/components/ChatFlatList/index.js.map +1 -1
  60. package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageFocusAnimation.js +4 -1
  61. package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageFocusAnimation.js.map +1 -1
  62. package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.js +182 -0
  63. package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.js.map +1 -0
  64. package/lib/module/components/GroupChannelMessageRenderer/index.js +13 -2
  65. package/lib/module/components/GroupChannelMessageRenderer/index.js.map +1 -1
  66. package/lib/module/components/MessageSearchResultItem.js +3 -9
  67. package/lib/module/components/MessageSearchResultItem.js.map +1 -1
  68. package/lib/module/components/ReactionAddons/BottomSheetReactionAddon.js +8 -7
  69. package/lib/module/components/ReactionAddons/BottomSheetReactionAddon.js.map +1 -1
  70. package/lib/module/containers/GroupChannelPreviewContainer.js +4 -10
  71. package/lib/module/containers/GroupChannelPreviewContainer.js.map +1 -1
  72. package/lib/module/containers/SendbirdUIKitContainer.js +43 -9
  73. package/lib/module/containers/SendbirdUIKitContainer.js.map +1 -1
  74. package/lib/module/contexts/SendbirdChatCtx.js +6 -1
  75. package/lib/module/contexts/SendbirdChatCtx.js.map +1 -1
  76. package/lib/module/domain/groupChannel/component/GroupChannelInput.js +5 -1
  77. package/lib/module/domain/groupChannel/component/GroupChannelInput.js.map +1 -1
  78. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js +50 -13
  79. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
  80. package/lib/module/domain/groupChannel/module/moduleContext.js +39 -4
  81. package/lib/module/domain/groupChannel/module/moduleContext.js.map +1 -1
  82. package/lib/module/domain/groupChannel/types.js.map +1 -1
  83. package/lib/module/domain/openChannel/types.js.map +1 -1
  84. package/lib/module/fragments/createGroupChannelFragment.js +19 -0
  85. package/lib/module/fragments/createGroupChannelFragment.js.map +1 -1
  86. package/lib/module/hooks/useConnection.js +2 -1
  87. package/lib/module/hooks/useConnection.js.map +1 -1
  88. package/lib/module/hooks/useMentionSuggestion.js +1 -1
  89. package/lib/module/hooks/useMentionSuggestion.js.map +1 -1
  90. package/lib/module/hooks/useMentionTextInput.js +4 -3
  91. package/lib/module/hooks/useMentionTextInput.js.map +1 -1
  92. package/lib/module/libs/MentionManager.js.map +1 -1
  93. package/lib/module/localization/StringSet.type.js.map +1 -1
  94. package/lib/module/localization/createBaseStringSet.js +30 -1
  95. package/lib/module/localization/createBaseStringSet.js.map +1 -1
  96. package/lib/module/platform/createMediaService.native.js +1 -1
  97. package/lib/module/platform/createMediaService.native.js.map +1 -1
  98. package/lib/module/platform/types.js.map +1 -1
  99. package/lib/module/utils/pubsub.js +3 -1
  100. package/lib/module/utils/pubsub.js.map +1 -1
  101. package/lib/module/version.js +1 -1
  102. package/lib/module/version.js.map +1 -1
  103. package/lib/typescript/src/components/ChannelInput/index.d.ts +2 -0
  104. package/lib/typescript/src/components/ChannelMessageList/index.d.ts +4 -1
  105. package/lib/typescript/src/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.d.ts +9 -0
  106. package/lib/typescript/src/components/GroupChannelMessageRenderer/index.d.ts +1 -0
  107. package/lib/typescript/src/components/OpenChannelMessageRenderer/index.d.ts +1 -0
  108. package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +4 -2
  109. package/lib/typescript/src/domain/groupChannel/component/GroupChannelMessageList.d.ts +4 -0
  110. package/lib/typescript/src/domain/groupChannel/types.d.ts +6 -2
  111. package/lib/typescript/src/domain/openChannel/component/OpenChannelHeader.d.ts +1 -1
  112. package/lib/typescript/src/domain/openChannel/types.d.ts +1 -2
  113. package/lib/typescript/src/libs/MentionManager.d.ts +1 -4
  114. package/lib/typescript/src/localization/StringSet.type.d.ts +7 -1
  115. package/lib/typescript/src/platform/types.d.ts +10 -11
  116. package/lib/typescript/src/version.d.ts +1 -1
  117. package/package.json +6 -6
  118. package/src/components/ChannelInput/SendInput.tsx +184 -51
  119. package/src/components/ChannelInput/index.tsx +11 -15
  120. package/src/components/ChannelMessageList/index.tsx +45 -27
  121. package/src/components/ChatFlatList/index.tsx +1 -1
  122. package/src/components/GroupChannelMessageRenderer/GroupChannelMessageFocusAnimation.tsx +5 -1
  123. package/src/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.tsx +177 -0
  124. package/src/components/GroupChannelMessageRenderer/index.tsx +16 -1
  125. package/src/components/MessageSearchResultItem.tsx +3 -5
  126. package/src/components/ReactionAddons/BottomSheetReactionAddon.tsx +6 -7
  127. package/src/containers/GroupChannelPreviewContainer.tsx +6 -7
  128. package/src/containers/SendbirdUIKitContainer.tsx +40 -10
  129. package/src/contexts/SendbirdChatCtx.tsx +7 -1
  130. package/src/domain/groupChannel/component/GroupChannelInput.tsx +5 -1
  131. package/src/domain/groupChannel/component/GroupChannelMessageList.tsx +46 -13
  132. package/src/domain/groupChannel/module/moduleContext.tsx +38 -3
  133. package/src/domain/groupChannel/types.ts +8 -2
  134. package/src/domain/openChannel/types.ts +1 -2
  135. package/src/fragments/createGroupChannelFragment.tsx +15 -0
  136. package/src/hooks/useConnection.ts +1 -1
  137. package/src/hooks/useMentionSuggestion.ts +2 -1
  138. package/src/hooks/useMentionTextInput.ts +2 -2
  139. package/src/libs/MentionManager.tsx +1 -8
  140. package/src/localization/StringSet.type.ts +11 -0
  141. package/src/localization/createBaseStringSet.ts +30 -0
  142. package/src/platform/createMediaService.native.tsx +1 -1
  143. package/src/platform/types.ts +9 -9
  144. package/src/utils/pubsub.ts +3 -1
  145. package/src/version.ts +1 -1
@@ -2,22 +2,26 @@ import React, { useContext, useEffect, useRef } from 'react';
2
2
  import type { FlatList } from 'react-native';
3
3
 
4
4
  import { useChannelHandler } from '@sendbird/uikit-chat-hooks';
5
+ import { useToast } from '@sendbird/uikit-react-native-foundation';
5
6
  import type { SendbirdMessage } from '@sendbird/uikit-utils';
6
- import { isDifferentChannel, useFreshCallback, useUniqHandlerId } from '@sendbird/uikit-utils';
7
+ import { isDifferentChannel, useFreshCallback, useIsFirstMount, useUniqHandlerId } from '@sendbird/uikit-utils';
7
8
 
8
9
  import ChannelMessageList from '../../../components/ChannelMessageList';
9
- import { MESSAGE_SEARCH_SAFE_SCROLL_DELAY } from '../../../constants';
10
- import { useSendbirdChat } from '../../../hooks/useContext';
10
+ import { MESSAGE_FOCUS_ANIMATION_DELAY, MESSAGE_SEARCH_SAFE_SCROLL_DELAY } from '../../../constants';
11
+ import { useLocalization, useSendbirdChat } from '../../../hooks/useContext';
11
12
  import { GroupChannelContexts } from '../module/moduleContext';
12
13
  import type { GroupChannelProps } from '../types';
13
14
 
14
15
  const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
16
+ const toast = useToast();
17
+ const { STRINGS } = useLocalization();
15
18
  const { sdk } = useSendbirdChat();
16
- const { setMessageToEdit } = useContext(GroupChannelContexts.Fragment);
19
+ const { setMessageToEdit, setMessageToReply } = useContext(GroupChannelContexts.Fragment);
17
20
  const { subscribe } = useContext(GroupChannelContexts.PubSub);
18
21
 
19
22
  const id = useUniqHandlerId('GroupChannelMessageList');
20
23
  const ref = useRef<FlatList<SendbirdMessage>>(null);
24
+ const isFirstMount = useIsFirstMount();
21
25
 
22
26
  // FIXME: Workaround, should run after data has been applied to UI.
23
27
  const lazyScrollToBottom = (animated = false, timeout = 0) => {
@@ -33,22 +37,35 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
33
37
  }, timeout);
34
38
  };
35
39
 
36
- useEffect(() => {
37
- if (props.searchItem) {
38
- const createdAt = props.searchItem.startingPoint;
39
- const foundMessageIndex = props.messages.findIndex((it) => it.createdAt === createdAt);
40
- const isIncludedInList = foundMessageIndex > -1;
41
- if (isIncludedInList) {
42
- lazyScrollToIndex(foundMessageIndex, true, MESSAGE_SEARCH_SAFE_SCROLL_DELAY);
40
+ const scrollToMessage = useFreshCallback((createdAt: number, focusAnimated = false): boolean => {
41
+ const foundMessageIndex = props.messages.findIndex((it) => it.createdAt === createdAt);
42
+ const isIncludedInList = foundMessageIndex > -1;
43
+
44
+ if (isIncludedInList) {
45
+ if (focusAnimated) {
46
+ setTimeout(() => props.onUpdateSearchItem({ startingPoint: createdAt }), MESSAGE_FOCUS_ANIMATION_DELAY);
47
+ }
48
+ lazyScrollToIndex(foundMessageIndex, true, isFirstMount ? MESSAGE_SEARCH_SAFE_SCROLL_DELAY : 0);
49
+ } else {
50
+ if (props.channel.messageOffsetTimestamp <= createdAt) {
51
+ if (focusAnimated) props.onUpdateSearchItem({ startingPoint: createdAt });
52
+ props.onResetMessageListWithStartingPoint(createdAt);
53
+ } else {
54
+ return false;
43
55
  }
44
56
  }
45
- }, [props.searchItem]);
57
+
58
+ return true;
59
+ });
46
60
 
47
61
  const scrollToBottom = useFreshCallback((animated = false) => {
48
62
  if (props.hasNext()) {
63
+ props.onUpdateSearchItem(undefined);
64
+ props.onScrolledAwayFromBottom(false);
65
+
49
66
  props.onResetMessageList(() => {
50
- lazyScrollToBottom(animated);
51
67
  props.onScrolledAwayFromBottom(false);
68
+ lazyScrollToBottom(animated);
52
69
  });
53
70
  } else {
54
71
  lazyScrollToBottom(animated);
@@ -85,11 +102,27 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
85
102
  });
86
103
  }, [props.scrolledAwayFromBottom]);
87
104
 
105
+ // Only trigger once when message list mount with initial props.searchItem
106
+ // - Search screen + searchItem > mount message list
107
+ // - Reset message list + searchItem > re-mount message list
108
+ useEffect(() => {
109
+ if (isFirstMount && props.searchItem) {
110
+ scrollToMessage(props.searchItem.startingPoint);
111
+ }
112
+ }, [isFirstMount]);
113
+
114
+ const onPressParentMessage = useFreshCallback((message: SendbirdMessage) => {
115
+ const canScrollToParent = scrollToMessage(message.createdAt, true);
116
+ if (!canScrollToParent) toast.show(STRINGS.TOAST.FIND_PARENT_MSG_ERROR, 'error');
117
+ });
118
+
88
119
  return (
89
120
  <ChannelMessageList
90
121
  {...props}
91
122
  ref={ref}
123
+ onReplyMessage={setMessageToReply}
92
124
  onEditMessage={setMessageToEdit}
125
+ onPressParentMessage={onPressParentMessage}
93
126
  onPressNewMessagesButton={scrollToBottom}
94
127
  onPressScrollToBottomButton={scrollToBottom}
95
128
  />
@@ -1,4 +1,4 @@
1
- import React, { createContext, useState } from 'react';
1
+ import React, { createContext, useCallback, useState } from 'react';
2
2
 
3
3
  import { useChannelHandler } from '@sendbird/uikit-chat-hooks';
4
4
  import {
@@ -21,6 +21,7 @@ export const GroupChannelContexts: GroupChannelContextsType = {
21
21
  headerTitle: '',
22
22
  channel: {} as SendbirdGroupChannel,
23
23
  setMessageToEdit: NOOP,
24
+ setMessageToReply: NOOP,
24
25
  }),
25
26
  TypingIndicator: createContext({
26
27
  typingUsers: [] as SendbirdUser[],
@@ -46,8 +47,40 @@ export const GroupChannelContextsProvider: GroupChannelModule['Provider'] = ({
46
47
 
47
48
  const [typingUsers, setTypingUsers] = useState<SendbirdUser[]>([]);
48
49
  const [messageToEdit, setMessageToEdit] = useState<SendbirdUserMessage | SendbirdFileMessage>();
50
+ const [messageToReply, setMessageToReply] = useState<SendbirdUserMessage | SendbirdFileMessage>();
51
+
52
+ const updateInputMode = (mode: 'send' | 'edit' | 'reply', message?: SendbirdUserMessage | SendbirdFileMessage) => {
53
+ if (mode === 'send' || !message) {
54
+ setMessageToEdit(undefined);
55
+ setMessageToReply(undefined);
56
+ return;
57
+ } else if (mode === 'edit') {
58
+ setMessageToEdit(message);
59
+ setMessageToReply(undefined);
60
+ return;
61
+ } else if (mode === 'reply') {
62
+ setMessageToEdit(undefined);
63
+ setMessageToReply(message);
64
+ return;
65
+ }
66
+ };
49
67
 
50
68
  useChannelHandler(sdk, handlerId, {
69
+ onMessageDeleted(_, messageId) {
70
+ if (messageToReply?.messageId === messageId) {
71
+ setMessageToReply(undefined);
72
+ }
73
+ },
74
+ onChannelFrozen(frozenChannel) {
75
+ if (frozenChannel.url === channel.url) {
76
+ setMessageToReply(undefined);
77
+ }
78
+ },
79
+ onUserMuted(mutedChannel, user) {
80
+ if (mutedChannel.url === channel.url && user.userId === sdk.currentUser?.userId) {
81
+ setMessageToReply(undefined);
82
+ }
83
+ },
51
84
  onTypingStatusUpdated(eventChannel) {
52
85
  if (isDifferentChannel(channel, eventChannel)) return;
53
86
  if (!enableTypingIndicator) return;
@@ -61,9 +94,11 @@ export const GroupChannelContextsProvider: GroupChannelModule['Provider'] = ({
61
94
  value={{
62
95
  headerTitle: STRINGS.GROUP_CHANNEL.HEADER_TITLE(currentUser?.userId ?? '', channel),
63
96
  channel,
64
- messageToEdit,
65
- setMessageToEdit,
66
97
  keyboardAvoidOffset,
98
+ messageToEdit,
99
+ setMessageToEdit: useCallback((message) => updateInputMode('edit', message), []),
100
+ messageToReply,
101
+ setMessageToReply: useCallback((message) => updateInputMode('reply', message), []),
67
102
  }}
68
103
  >
69
104
  <GroupChannelContexts.TypingIndicator.Provider value={{ typingUsers }}>
@@ -73,6 +73,10 @@ export interface GroupChannelProps {
73
73
  | 'searchItem'
74
74
  > & {
75
75
  onResetMessageList: (callback?: () => void) => void;
76
+ onResetMessageListWithStartingPoint: (startingPoint: number, callback?: () => void) => void;
77
+
78
+ // Changing the search item will trigger the focus animation on messages.
79
+ onUpdateSearchItem: (searchItem?: GroupChannelProps['MessageList']['searchItem']) => void;
76
80
  };
77
81
  Input: Pick<
78
82
  ChannelInputProps,
@@ -102,10 +106,12 @@ export interface GroupChannelProps {
102
106
  export interface GroupChannelContextsType {
103
107
  Fragment: React.Context<{
104
108
  headerTitle: string;
109
+ keyboardAvoidOffset?: number;
105
110
  channel: SendbirdGroupChannel;
106
111
  messageToEdit?: SendbirdUserMessage | SendbirdFileMessage;
107
112
  setMessageToEdit: (msg?: SendbirdUserMessage | SendbirdFileMessage) => void;
108
- keyboardAvoidOffset?: number;
113
+ messageToReply?: SendbirdUserMessage | SendbirdFileMessage;
114
+ setMessageToReply: (msg?: SendbirdUserMessage | SendbirdFileMessage) => void;
109
115
  }>;
110
116
  TypingIndicator: React.Context<{
111
117
  typingUsers: SendbirdUser[];
@@ -132,7 +138,7 @@ export type GroupChannelPubSubContextPayload =
132
138
  };
133
139
  }
134
140
  | {
135
- type: 'MESSAGES_RECEIVED';
141
+ type: 'MESSAGES_RECEIVED' | 'MESSAGES_UPDATED';
136
142
  data: {
137
143
  messages: SendbirdMessage[];
138
144
  };
@@ -18,7 +18,6 @@ import type { ChannelInputProps } from '../../components/ChannelInput';
18
18
  import type { ChannelMessageListProps } from '../../components/ChannelMessageList';
19
19
  import type { CommonComponent } from '../../types';
20
20
  import type { PubSub } from '../../utils/pubsub';
21
- import type { GroupChannelPubSubContextPayload } from '../groupChannel/types';
22
21
 
23
22
  export type OpenChannelProps = {
24
23
  Fragment: {
@@ -101,7 +100,7 @@ export type OpenChannelContextsType = {
101
100
  setMessageToEdit: (msg?: SendbirdUserMessage | SendbirdFileMessage) => void;
102
101
  keyboardAvoidOffset?: number;
103
102
  }>;
104
- PubSub: React.Context<PubSub<GroupChannelPubSubContextPayload>>;
103
+ PubSub: React.Context<PubSub<OpenChannelPubSubContextPayload>>;
105
104
  };
106
105
  export interface OpenChannelModule {
107
106
  Provider: CommonComponent<OpenChannelProps['Provider']>;
@@ -86,6 +86,9 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
86
86
  onMessagesReceived(messages) {
87
87
  groupChannelPubSub.publish({ type: 'MESSAGES_RECEIVED', data: { messages } });
88
88
  },
89
+ onMessagesUpdated(messages) {
90
+ groupChannelPubSub.publish({ type: 'MESSAGES_UPDATED', data: { messages } });
91
+ },
89
92
  collectionCreator,
90
93
  sortComparator,
91
94
  onChannelDeleted,
@@ -110,7 +113,17 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
110
113
 
111
114
  const onResetMessageList = useCallback((callback?: () => void) => {
112
115
  resetWithStartingPoint(Number.MAX_SAFE_INTEGER, callback);
116
+ }, []);
117
+
118
+ const onResetMessageListWithStartingPoint = useCallback((startingPoint: number, callback?: () => void) => {
119
+ resetWithStartingPoint(startingPoint, callback);
120
+ }, []);
121
+
122
+ // Changing the search item will trigger the focus animation on messages.
123
+ const onUpdateSearchItem: GroupChannelProps['MessageList']['onUpdateSearchItem'] = useCallback((searchItem) => {
124
+ // Clean up for animation trigger with useEffect
113
125
  setInternalSearchItem(undefined);
126
+ setInternalSearchItem(searchItem);
114
127
  }, []);
115
128
 
116
129
  const onPending = (message: SendbirdFileMessage | SendbirdUserMessage) => {
@@ -169,6 +182,8 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
169
182
  channel={channel}
170
183
  searchItem={internalSearchItem}
171
184
  onResetMessageList={onResetMessageList}
185
+ onResetMessageListWithStartingPoint={onResetMessageListWithStartingPoint}
186
+ onUpdateSearchItem={onUpdateSearchItem}
172
187
  enableMessageGrouping={enableMessageGrouping}
173
188
  currentUserId={currentUser?.userId}
174
189
  renderMessage={renderItem}
@@ -16,7 +16,7 @@ function isCacheRestrictedError(error: SendbirdError) {
16
16
 
17
17
  async function initEmoji(sdk: SendbirdChatSDK, emojiManager: EmojiManager) {
18
18
  await emojiManager.init();
19
- if (sdk.appInfo.emojiHash !== emojiManager.emojiHash) {
19
+ if (sdk.appInfo?.emojiHash !== emojiManager.emojiHash) {
20
20
  try {
21
21
  const container = await sdk.getAllEmoji();
22
22
  await emojiManager.init(container);
@@ -85,7 +85,8 @@ const useMentionSuggestion = (params: {
85
85
  limit: mentionManager.config.suggestionLimit + 1,
86
86
  })
87
87
  .next()
88
- .then((members) => members.filter((member) => member.userId !== currentUser?.userId));
88
+ .then((members) => members.filter((member) => member.userId !== currentUser?.userId))
89
+ .then((members) => members.slice(0, mentionManager.config.suggestionLimit));
89
90
  } else {
90
91
  return freshChannel.members
91
92
  .sort((a, b) => a.nickname?.localeCompare(b.nickname))
@@ -25,8 +25,8 @@ const useMentionTextInput = (params: { messageToEdit?: SendbirdUserMessage | Sen
25
25
  )
26
26
  ) {
27
27
  const result = mentionManager.templateToTextAndMentionedUsers(
28
- params.messageToEdit.mentionedMessageTemplate,
29
- params.messageToEdit.mentionedUsers,
28
+ params.messageToEdit?.mentionedMessageTemplate ?? '',
29
+ params.messageToEdit?.mentionedUsers ?? [],
30
30
  );
31
31
 
32
32
  mentionedUsersRef.current = result.mentionedUsers;
@@ -217,10 +217,7 @@ class MentionManager {
217
217
  public shouldUseMentionedMessageTemplate = (
218
218
  message?: SendbirdUserMessage | SendbirdFileMessage,
219
219
  mentionEnabled?: boolean,
220
- ): message is RequiredSpecific<
221
- SendbirdUserMessage | SendbirdFileMessage,
222
- 'mentionedMessageTemplate' | 'mentionedUsers' | 'mentionedUserIds' | 'mentionType'
223
- > => {
220
+ ): boolean => {
224
221
  return Boolean(
225
222
  mentionEnabled &&
226
223
  message?.mentionedMessageTemplate &&
@@ -230,10 +227,6 @@ class MentionManager {
230
227
  };
231
228
  }
232
229
 
233
- type RequiredSpecific<T, K extends keyof T> = T & {
234
- [P in K]-?: T[P];
235
- };
236
-
237
230
  const styles = createStyleSheet({
238
231
  mentionedText: { fontWeight: '700' },
239
232
  });
@@ -9,6 +9,7 @@ import type {
9
9
  SendbirdOpenChannel,
10
10
  SendbirdParticipant,
11
11
  SendbirdUser,
12
+ SendbirdUserMessage,
12
13
  } from '@sendbird/uikit-utils';
13
14
 
14
15
  /**
@@ -243,6 +244,11 @@ export interface StringSet {
243
244
  USER_NO_NAME: string;
244
245
  CHANNEL_NO_MEMBERS: string;
245
246
  TYPING_INDICATOR_TYPINGS: (users: SendbirdUser[]) => string | undefined;
247
+ REPLY_FROM_SENDER_TO_RECEIVER: (
248
+ replyMessage: SendbirdUserMessage | SendbirdFileMessage,
249
+ parentMessage: SendbirdUserMessage | SendbirdFileMessage,
250
+ currentUserId?: string,
251
+ ) => string;
246
252
 
247
253
  USER_BAR_ME_POSTFIX: string;
248
254
  USER_BAR_OPERATOR: string;
@@ -261,6 +267,7 @@ export interface StringSet {
261
267
  CHANNEL_INPUT_PLACEHOLDER_ACTIVE: string;
262
268
  CHANNEL_INPUT_PLACEHOLDER_DISABLED: string;
263
269
  CHANNEL_INPUT_PLACEHOLDER_MUTED: string;
270
+ CHANNEL_INPUT_PLACEHOLDER_REPLY: string;
264
271
  CHANNEL_INPUT_EDIT_OK: string;
265
272
  CHANNEL_INPUT_EDIT_CANCEL: string;
266
273
  /** ChannelInput > Attachments **/
@@ -268,12 +275,15 @@ export interface StringSet {
268
275
  CHANNEL_INPUT_ATTACHMENT_CAMERA_VIDEO: string;
269
276
  CHANNEL_INPUT_ATTACHMENT_PHOTO_LIBRARY: string;
270
277
  CHANNEL_INPUT_ATTACHMENT_FILES: string;
278
+ CHANNEL_INPUT_REPLY_PREVIEW_TITLE: (user: SendbirdUser) => string;
279
+ CHANNEL_INPUT_REPLY_PREVIEW_BODY: (message: SendbirdUserMessage | SendbirdFileMessage) => string;
271
280
 
272
281
  /** Channel > Message **/
273
282
  CHANNEL_MESSAGE_COPY: string;
274
283
  CHANNEL_MESSAGE_EDIT: string;
275
284
  CHANNEL_MESSAGE_SAVE: string;
276
285
  CHANNEL_MESSAGE_DELETE: string;
286
+ CHANNEL_MESSAGE_REPLY: string;
277
287
  /** Channel > Message > Delete confirm **/
278
288
  CHANNEL_MESSAGE_DELETE_CONFIRM_TITLE: string;
279
289
  CHANNEL_MESSAGE_DELETE_CONFIRM_OK: string;
@@ -327,6 +337,7 @@ export interface StringSet {
327
337
  LEAVE_CHANNEL_ERROR: string;
328
338
  UNKNOWN_ERROR: string;
329
339
  GET_CHANNEL_ERROR: string;
340
+ FIND_PARENT_MSG_ERROR: string;
330
341
  };
331
342
  PROFILE_CARD: {
332
343
  BUTTON_MESSAGE: string;
@@ -3,6 +3,7 @@ import type { Locale } from 'date-fns';
3
3
  import type { PartialDeep } from '@sendbird/uikit-utils';
4
4
  import {
5
5
  getDateSeparatorFormat,
6
+ getFileTypeFromMessage,
6
7
  getGroupChannelLastMessage,
7
8
  getGroupChannelPreviewTime,
8
9
  getGroupChannelTitle,
@@ -14,6 +15,7 @@ import {
14
15
  getOpenChannelTitle,
15
16
  } from '@sendbird/uikit-utils';
16
17
 
18
+ import { UNKNOWN_USER_ID } from '../constants';
17
19
  import type { StringSet } from './StringSet.type';
18
20
 
19
21
  type StringSetCreateOptions = {
@@ -248,6 +250,12 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
248
250
  if (users.length === 2) return `${userNames.join(' and ')} are typing...`;
249
251
  return 'Several people are typing...';
250
252
  },
253
+ REPLY_FROM_SENDER_TO_RECEIVER: (reply, parent, currentUserId = UNKNOWN_USER_ID) => {
254
+ const senderNickname = reply.sender.nickname || USER_NO_NAME;
255
+ const receiverNickname = parent.sender.nickname || USER_NO_NAME;
256
+ return `${reply.sender.userId !== currentUserId ? senderNickname : 'You'} replied to ${receiverNickname}`;
257
+ },
258
+
251
259
  USER_BAR_ME_POSTFIX: ' (You)',
252
260
  USER_BAR_OPERATOR: 'Operator',
253
261
  REGISTER_AS_OPERATOR: 'Register as operator',
@@ -261,6 +269,7 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
261
269
  CHANNEL_MESSAGE_EDIT: 'Edit',
262
270
  CHANNEL_MESSAGE_SAVE: 'Save',
263
271
  CHANNEL_MESSAGE_DELETE: 'Delete',
272
+ CHANNEL_MESSAGE_REPLY: 'Reply',
264
273
  CHANNEL_MESSAGE_DELETE_CONFIRM_TITLE: 'Delete message?',
265
274
  CHANNEL_MESSAGE_DELETE_CONFIRM_OK: 'Delete',
266
275
  CHANNEL_MESSAGE_DELETE_CONFIRM_CANCEL: 'Cancel',
@@ -273,8 +282,28 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
273
282
  CHANNEL_INPUT_PLACEHOLDER_ACTIVE: 'Enter message',
274
283
  CHANNEL_INPUT_PLACEHOLDER_DISABLED: 'Chat not available in this channel.',
275
284
  CHANNEL_INPUT_PLACEHOLDER_MUTED: "You're muted by the operator.",
285
+ CHANNEL_INPUT_PLACEHOLDER_REPLY: 'Reply to message',
276
286
  CHANNEL_INPUT_EDIT_OK: 'Save',
277
287
  CHANNEL_INPUT_EDIT_CANCEL: 'Cancel',
288
+ CHANNEL_INPUT_REPLY_PREVIEW_TITLE: (user) => `Reply to ${user.nickname || USER_NO_NAME}`,
289
+ CHANNEL_INPUT_REPLY_PREVIEW_BODY: (message) => {
290
+ if (message.isFileMessage()) {
291
+ const fileType = getFileTypeFromMessage(message);
292
+ switch (fileType) {
293
+ case 'image':
294
+ return message.type.toLowerCase().includes('gif') ? 'GIF' : 'Photo';
295
+ case 'video':
296
+ return 'Video';
297
+ case 'audio':
298
+ return 'Audio';
299
+ default:
300
+ return message.name;
301
+ }
302
+ } else if (message.isUserMessage()) {
303
+ return message.message;
304
+ }
305
+ return 'Unknown message type.';
306
+ },
278
307
  ...overrides?.LABELS,
279
308
  },
280
309
  FILE_VIEWER: {
@@ -325,6 +354,7 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
325
354
  LEAVE_CHANNEL_ERROR: "Couldn't leave channel.",
326
355
  UNKNOWN_ERROR: 'Something went wrong.',
327
356
  GET_CHANNEL_ERROR: "Couldn't retrieve channel.",
357
+ FIND_PARENT_MSG_ERROR: "Couldn't find the original message for this reply.",
328
358
  ...overrides?.TOAST,
329
359
  },
330
360
  PROFILE_CARD: {
@@ -29,7 +29,7 @@ const createNativeMediaService = ({
29
29
  url,
30
30
  format: 'jpeg',
31
31
  timeStamp: timeMills,
32
- cacheName: hash(url),
32
+ cacheName: hash(url.split('?')[0]),
33
33
  });
34
34
  return { path };
35
35
  } catch {
@@ -61,18 +61,20 @@ export interface FileSystemServiceInterface {
61
61
  }
62
62
 
63
63
  // ---------- MediaService ---------- //
64
- interface VideoProps {
64
+ export type VideoProps = {
65
65
  source: { uri: string } | number;
66
66
  resizeMode?: 'cover' | 'contain' | 'stretch';
67
67
  onLoad?: () => void;
68
- }
69
- interface GetVideoThumbnailOptions {
68
+ };
69
+
70
+ export type GetVideoThumbnailOptions = {
70
71
  url: string;
71
72
  timeMills?: number;
72
73
  quality?: number;
73
- }
74
+ };
75
+ export type GetVideoThumbnailResult = Promise<{ path: string } | null>;
74
76
 
75
- interface CompressImageOptions {
77
+ export type CompressImageOptions = {
76
78
  /**
77
79
  * A uri of image file to compress
78
80
  * */
@@ -93,10 +95,8 @@ interface CompressImageOptions {
93
95
  * 1 means highest quality and 0 the lowest quality.
94
96
  * */
95
97
  compressionRate?: number;
96
- }
97
-
98
- type GetVideoThumbnailResult = Promise<{ path: string } | null>;
99
- type CompressImageResult = Promise<{ uri: string; size: number } | null>;
98
+ };
99
+ export type CompressImageResult = Promise<{ uri: string; size: number } | null>;
100
100
 
101
101
  export interface MediaServiceInterface {
102
102
  VideoComponent<Props = {}>(props: VideoProps & Props): JSX.Element;
@@ -8,7 +8,9 @@ const pubsub = <T>(): PubSub<T> => {
8
8
 
9
9
  return {
10
10
  publish: (data: unknown) => {
11
- subscribers.forEach((subscriber) => subscriber(data));
11
+ subscribers.forEach((subscriber) => {
12
+ setTimeout(() => subscriber(data), 0);
13
+ });
12
14
  },
13
15
  subscribe: (subscriber: Function) => {
14
16
  subscribers.add(subscriber);
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
- const VERSION = '3.0.4';
1
+ const VERSION = '3.1.1';
2
2
  export default VERSION;