@sendbird/uikit-react-native 3.0.4 → 3.1.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 (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
@@ -55,8 +55,10 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
55
55
  onPressScrollToBottomButton: (animated?: boolean) => void;
56
56
 
57
57
  onEditMessage: (message: HandleableMessage) => void;
58
+ onReplyMessage?: (message: HandleableMessage) => void; // only available on group channel
58
59
  onDeleteMessage: (message: HandleableMessage) => Promise<void>;
59
60
  onResendFailedMessage: (failedMessage: HandleableMessage) => Promise<void>;
61
+ onPressParentMessage?: (parentMessage: SendbirdMessage) => void;
60
62
  onPressMediaMessage?: (message: SendbirdFileMessage, deleteMessage: () => Promise<void>, uri: string) => void;
61
63
 
62
64
  renderMessage: (props: {
@@ -66,6 +68,7 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
66
68
  nextMessage?: SendbirdMessage;
67
69
  onPress?: () => void;
68
70
  onLongPress?: () => void;
71
+ onPressParentMessage?: ChannelMessageListProps<T>['onPressParentMessage'];
69
72
  onShowUserProfile?: UserProfileContextType['show'];
70
73
  channel: T;
71
74
  currentUserId?: ChannelMessageListProps<T>['currentUserId'];
@@ -91,9 +94,11 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
91
94
  hasNext,
92
95
  channel,
93
96
  onEditMessage,
97
+ onReplyMessage,
94
98
  onDeleteMessage,
95
99
  onResendFailedMessage,
96
100
  onPressMediaMessage,
101
+ onPressParentMessage,
97
102
  currentUserId,
98
103
  renderNewMessagesButton,
99
104
  renderScrollToBottomButton,
@@ -119,6 +124,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
119
124
  channel,
120
125
  currentUserId,
121
126
  onEditMessage,
127
+ onReplyMessage,
122
128
  onDeleteMessage,
123
129
  onResendFailedMessage,
124
130
  onPressMediaMessage,
@@ -134,6 +140,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
134
140
  nextMessage: messages[index - 1],
135
141
  onPress,
136
142
  onLongPress,
143
+ onPressParentMessage,
137
144
  onShowUserProfile: show,
138
145
  enableMessageGrouping,
139
146
  channel,
@@ -188,11 +195,18 @@ const useGetMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpen
188
195
  currentUserId,
189
196
  onResendFailedMessage,
190
197
  onEditMessage,
198
+ onReplyMessage,
191
199
  onDeleteMessage,
192
200
  onPressMediaMessage,
193
201
  }: Pick<
194
202
  ChannelMessageListProps<T>,
195
- 'channel' | 'currentUserId' | 'onEditMessage' | 'onDeleteMessage' | 'onResendFailedMessage' | 'onPressMediaMessage'
203
+ | 'channel'
204
+ | 'currentUserId'
205
+ | 'onEditMessage'
206
+ | 'onReplyMessage'
207
+ | 'onDeleteMessage'
208
+ | 'onResendFailedMessage'
209
+ | 'onPressMediaMessage'
196
210
  >) => {
197
211
  const { colors } = useUIKitTheme();
198
212
  const { STRINGS } = useLocalization();
@@ -202,13 +216,18 @@ const useGetMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpen
202
216
  const { clipboardService, fileService } = usePlatformService();
203
217
  const { sbOptions } = useSendbirdChat();
204
218
 
219
+ const onFailureToReSend = (error: Error) => {
220
+ toast.show(STRINGS.TOAST.RESEND_MSG_ERROR, 'error');
221
+ Logger.error(STRINGS.TOAST.RESEND_MSG_ERROR, error);
222
+ };
223
+
205
224
  const handleFailedMessage = (message: HandleableMessage) => {
206
225
  openSheet({
207
226
  sheetItems: [
208
227
  {
209
228
  title: STRINGS.LABELS.CHANNEL_MESSAGE_FAILED_RETRY,
210
229
  onPress: () => {
211
- onResendFailedMessage(message).catch(() => toast.show(STRINGS.TOAST.RESEND_MSG_ERROR, 'error'));
230
+ onResendFailedMessage(message).catch(onFailureToReSend);
212
231
  },
213
232
  },
214
233
  {
@@ -257,25 +276,7 @@ const useGetMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpen
257
276
  toast.show(STRINGS.TOAST.COPY_OK, 'success');
258
277
  },
259
278
  });
260
-
261
- if (!channel.isEphemeral) {
262
- if (isMyMessage(msg, currentUserId) && msg.sendingStatus === 'succeeded') {
263
- sheetItems.push(
264
- {
265
- icon: 'edit',
266
- title: STRINGS.LABELS.CHANNEL_MESSAGE_EDIT,
267
- onPress: () => onEditMessage(msg),
268
- },
269
- {
270
- icon: 'delete',
271
- title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE,
272
- onPress: () => confirmDelete(msg),
273
- },
274
- );
275
- }
276
- }
277
279
  }
278
-
279
280
  if (msg.isFileMessage()) {
280
281
  sheetItems.push({
281
282
  icon: 'download',
@@ -297,17 +298,34 @@ const useGetMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpen
297
298
  });
298
299
  },
299
300
  });
300
-
301
- if (!channel.isEphemeral) {
302
- if (isMyMessage(msg, currentUserId) && msg.sendingStatus === 'succeeded') {
301
+ }
302
+ if (!channel.isEphemeral) {
303
+ if (isMyMessage(msg, currentUserId) && msg.sendingStatus === 'succeeded') {
304
+ if (msg.isUserMessage()) {
303
305
  sheetItems.push({
304
- icon: 'delete',
305
- title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE,
306
- onPress: () => confirmDelete(msg),
306
+ icon: 'edit',
307
+ title: STRINGS.LABELS.CHANNEL_MESSAGE_EDIT,
308
+ onPress: () => onEditMessage(msg),
307
309
  });
308
310
  }
311
+ sheetItems.push({
312
+ disabled: msg.threadInfo ? msg.threadInfo.replyCount > 0 : undefined,
313
+ icon: 'delete',
314
+ title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE,
315
+ onPress: () => confirmDelete(msg),
316
+ });
317
+ }
318
+ if (channel.isGroupChannel() && sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
319
+ sheetItems.push({
320
+ disabled: Boolean(msg.parentMessageId),
321
+ icon: 'reply',
322
+ title: STRINGS.LABELS.CHANNEL_MESSAGE_REPLY,
323
+ onPress: () => onReplyMessage?.(msg),
324
+ });
309
325
  }
326
+ }
310
327
 
328
+ if (msg.isFileMessage()) {
311
329
  const fileType = getFileType(msg.type || getFileExtension(msg.name));
312
330
  switch (fileType) {
313
331
  case 'image':
@@ -342,7 +360,7 @@ const useGetMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpen
342
360
  if (msg.sendingStatus === 'failed') {
343
361
  response.onLongPress = () => handleFailedMessage(msg);
344
362
  response.onPress = () => {
345
- onResendFailedMessage(msg).catch(() => toast.show(STRINGS.TOAST.RESEND_MSG_ERROR, 'error'));
363
+ onResendFailedMessage(msg).catch(onFailureToReSend);
346
364
  };
347
365
  }
348
366
 
@@ -7,7 +7,7 @@ import { NOOP, SendbirdMessage, getMessageUniqId, useFreshCallback } from '@send
7
7
  import FlatListInternal from './FlatListInternal';
8
8
 
9
9
  let ANDROID_BUG_ALERT_SHOWED = Platform.OS !== 'android';
10
- const BOTTOM_DETECT_THRESHOLD = 25;
10
+ const BOTTOM_DETECT_THRESHOLD = 50;
11
11
  const UNREACHABLE_THRESHOLD = Number.MIN_SAFE_INTEGER;
12
12
 
13
13
  type Props = Omit<FlatListProps<SendbirdMessage>, 'onEndReached'> & {
@@ -1,13 +1,17 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
2
  import { Animated, Easing } from 'react-native';
3
3
 
4
+ import { useIsFirstMount } from '@sendbird/uikit-utils';
5
+
4
6
  import { MESSAGE_FOCUS_ANIMATION_DELAY, MESSAGE_SEARCH_SAFE_SCROLL_DELAY } from '../../constants';
5
7
 
6
8
  const GroupChannelMessageFocusAnimation = (props: React.PropsWithChildren<{ focused: boolean }>) => {
9
+ const isFirstMount = useIsFirstMount();
7
10
  const translateY = useRef(new Animated.Value(0)).current;
8
11
 
9
12
  useEffect(() => {
10
13
  if (props.focused) {
14
+ const delay = MESSAGE_FOCUS_ANIMATION_DELAY + (isFirstMount ? MESSAGE_SEARCH_SAFE_SCROLL_DELAY : 0);
11
15
  setTimeout(() => {
12
16
  Animated.sequence(
13
17
  [
@@ -19,7 +23,7 @@ const GroupChannelMessageFocusAnimation = (props: React.PropsWithChildren<{ focu
19
23
  Animated.timing(translateY, { ...value, useNativeDriver: true, easing: Easing.inOut(Easing.ease) }),
20
24
  ),
21
25
  ).start();
22
- }, MESSAGE_SEARCH_SAFE_SCROLL_DELAY + MESSAGE_FOCUS_ANIMATION_DELAY);
26
+ }, delay);
23
27
  }
24
28
  }, [props.focused]);
25
29
 
@@ -0,0 +1,177 @@
1
+ import React, { useContext, useEffect, useState } from 'react';
2
+
3
+ import {
4
+ Box,
5
+ Icon,
6
+ ImageWithPlaceholder,
7
+ PressBox,
8
+ Text,
9
+ VideoThumbnail,
10
+ createStyleSheet,
11
+ useUIKitTheme,
12
+ } from '@sendbird/uikit-react-native-foundation';
13
+ import {
14
+ SendbirdFileMessage,
15
+ SendbirdMessage,
16
+ SendbirdUserMessage,
17
+ getAvailableUriFromFileMessage,
18
+ getFileIconFromMessageType,
19
+ getMessageType,
20
+ truncate,
21
+ useIIFE,
22
+ } from '@sendbird/uikit-utils';
23
+
24
+ import { GroupChannelContexts } from '../../domain/groupChannel/module/moduleContext';
25
+ import { useLocalization, usePlatformService, useSendbirdChat } from '../../hooks/useContext';
26
+
27
+ type Props = {
28
+ variant: 'outgoing' | 'incoming';
29
+ message: SendbirdUserMessage | SendbirdFileMessage;
30
+ childMessage: SendbirdUserMessage | SendbirdFileMessage;
31
+ onPress?: (message: SendbirdMessage) => void;
32
+ };
33
+
34
+ const GroupChannelMessageParentMessage = ({ variant, message, childMessage, onPress }: Props) => {
35
+ const { currentUser } = useSendbirdChat();
36
+ const groupChannelPubSub = useContext(GroupChannelContexts.PubSub);
37
+ const { select, colors, palette } = useUIKitTheme();
38
+ const { STRINGS } = useLocalization();
39
+ const { mediaService } = usePlatformService();
40
+
41
+ const [parentMessage, setParentMessage] = useState(() => message);
42
+ const type = getMessageType(parentMessage);
43
+
44
+ useEffect(() => {
45
+ return groupChannelPubSub.subscribe(({ type, data }) => {
46
+ if (type === 'MESSAGES_UPDATED') {
47
+ const updatedParent = data.messages.find((it): it is SendbirdUserMessage | SendbirdFileMessage => {
48
+ return it.messageId === parentMessage.messageId;
49
+ });
50
+ if (updatedParent) setParentMessage(updatedParent);
51
+ }
52
+ });
53
+ }, []);
54
+
55
+ const renderFileMessageAsVideoThumbnail = (url: string) => {
56
+ return (
57
+ <VideoThumbnail
58
+ style={styles.image}
59
+ iconSize={18}
60
+ videoSource={url}
61
+ fetchThumbnailFromVideoSource={(uri) => mediaService.getVideoThumbnail({ url: uri, timeMills: 1000 })}
62
+ />
63
+ );
64
+ };
65
+ const renderFileMessageAsPreview = (url: string) => {
66
+ return <ImageWithPlaceholder style={styles.image} source={{ uri: url }} />;
67
+ };
68
+ const renderFileMessageAsDownloadable = (name: string) => {
69
+ return (
70
+ <Box
71
+ style={styles.bubbleContainer}
72
+ backgroundColor={select({ light: palette.background100, dark: palette.background400 })}
73
+ >
74
+ <Icon
75
+ icon={getFileIconFromMessageType(type)}
76
+ size={16}
77
+ color={colors.onBackground03}
78
+ containerStyle={styles.fileIcon}
79
+ />
80
+ <Text body3 color={colors.onBackground03} numberOfLines={1} ellipsizeMode={'middle'}>
81
+ {truncate(name, { mode: 'mid', maxLen: 20 })}
82
+ </Text>
83
+ </Box>
84
+ );
85
+ };
86
+
87
+ const parentMessageComponent = useIIFE(() => {
88
+ switch (type) {
89
+ case 'user':
90
+ case 'user.opengraph': {
91
+ return (
92
+ <Box
93
+ style={styles.bubbleContainer}
94
+ backgroundColor={select({ light: palette.background100, dark: palette.background400 })}
95
+ >
96
+ <Text body3 color={colors.onBackground03} suppressHighlighting numberOfLines={2} ellipsizeMode={'tail'}>
97
+ {(parentMessage as SendbirdUserMessage).message}
98
+ </Text>
99
+ </Box>
100
+ );
101
+ }
102
+ case 'file':
103
+ case 'file.audio': {
104
+ return renderFileMessageAsDownloadable((parentMessage as SendbirdFileMessage).name);
105
+ }
106
+ case 'file.video': {
107
+ return renderFileMessageAsVideoThumbnail(getAvailableUriFromFileMessage(parentMessage as SendbirdFileMessage));
108
+ }
109
+ case 'file.image': {
110
+ return renderFileMessageAsPreview(getAvailableUriFromFileMessage(parentMessage as SendbirdFileMessage));
111
+ }
112
+ default: {
113
+ return null;
114
+ }
115
+ }
116
+ });
117
+
118
+ return (
119
+ <Box>
120
+ <Box
121
+ alignItems={variant === 'outgoing' ? 'flex-end' : 'flex-start'}
122
+ paddingLeft={variant === 'outgoing' ? 0 : 12}
123
+ paddingRight={variant === 'outgoing' ? 12 : 0}
124
+ >
125
+ <PressBox onPress={() => onPress?.(parentMessage)} style={styles.senderLabel}>
126
+ <Icon icon={'reply-filled'} size={13} color={colors.onBackground03} containerStyle={{ marginRight: 4 }} />
127
+ <Text caption1 color={colors.onBackground03}>
128
+ {STRINGS.LABELS.REPLY_FROM_SENDER_TO_RECEIVER(childMessage, parentMessage, currentUser?.userId)}
129
+ </Text>
130
+ </PressBox>
131
+ </Box>
132
+ <Box
133
+ flexDirection={'row'}
134
+ justifyContent={variant === 'outgoing' ? 'flex-end' : 'flex-start'}
135
+ style={styles.messageContainer}
136
+ >
137
+ <PressBox onPress={() => onPress?.(parentMessage)}>{parentMessageComponent}</PressBox>
138
+ </Box>
139
+ </Box>
140
+ );
141
+ };
142
+
143
+ const styles = createStyleSheet({
144
+ messageContainer: {
145
+ opacity: 0.5,
146
+ marginTop: 4,
147
+ marginBottom: -6,
148
+ },
149
+ bubbleContainer: {
150
+ maxWidth: 220,
151
+ overflow: 'hidden',
152
+ flexDirection: 'row',
153
+ alignItems: 'center',
154
+ borderRadius: 16,
155
+ paddingHorizontal: 12,
156
+ paddingBottom: 12,
157
+ paddingTop: 6,
158
+ },
159
+ image: {
160
+ width: 156,
161
+ height: 104,
162
+ borderRadius: 16,
163
+ overflow: 'hidden',
164
+ },
165
+ fileIcon: {
166
+ width: 16,
167
+ height: 16,
168
+ borderRadius: 10,
169
+ marginRight: 4,
170
+ marginTop: 2,
171
+ },
172
+ senderLabel: {
173
+ flexDirection: 'row',
174
+ },
175
+ });
176
+
177
+ export default GroupChannelMessageParentMessage;
@@ -10,6 +10,7 @@ import {
10
10
  calcMessageGrouping,
11
11
  getMessageType,
12
12
  isMyMessage,
13
+ shouldRenderParentMessage,
13
14
  shouldRenderReaction,
14
15
  useIIFE,
15
16
  } from '@sendbird/uikit-utils';
@@ -21,12 +22,14 @@ import { ReactionAddons } from '../ReactionAddons';
21
22
  import GroupChannelMessageDateSeparator from './GroupChannelMessageDateSeparator';
22
23
  import GroupChannelMessageFocusAnimation from './GroupChannelMessageFocusAnimation';
23
24
  import GroupChannelMessageOutgoingStatus from './GroupChannelMessageOutgoingStatus';
25
+ import GroupChannelMessageParentMessage from './GroupChannelMessageParentMessage';
24
26
 
25
27
  const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'] = ({
26
28
  channel,
27
29
  message,
28
30
  onPress,
29
31
  onLongPress,
32
+ onPressParentMessage,
30
33
  onShowUserProfile,
31
34
  enableMessageGrouping,
32
35
  focused,
@@ -55,9 +58,11 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
55
58
  return null;
56
59
  });
57
60
 
61
+ const variant = isMyMessage(message, currentUser?.userId) ? 'outgoing' : 'incoming';
62
+
58
63
  const messageProps: Omit<GroupChannelMessageProps<SendbirdMessage>, 'message'> = {
59
64
  channel,
60
- variant: isMyMessage(message, currentUser?.userId) ? 'outgoing' : 'incoming',
65
+ variant,
61
66
  onPress,
62
67
  onLongPress,
63
68
  onPressURL: (url) => SBUUtils.openURL(url),
@@ -73,6 +78,14 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
73
78
  sendingStatus: isMyMessage(message, currentUser?.userId) ? (
74
79
  <GroupChannelMessageOutgoingStatus channel={channel} message={message} />
75
80
  ) : null,
81
+ parentMessage: shouldRenderParentMessage(message) ? (
82
+ <GroupChannelMessageParentMessage
83
+ variant={variant}
84
+ childMessage={message}
85
+ message={message.parentMessage}
86
+ onPress={onPressParentMessage}
87
+ />
88
+ ) : null,
76
89
  strings: {
77
90
  edited: STRINGS.GROUP_CHANNEL.MESSAGE_BUBBLE_EDITED_POSTFIX,
78
91
  senderName: ('sender' in message && message.sender.nickname) || STRINGS.LABELS.USER_NO_NAME,
@@ -185,6 +198,8 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
185
198
  } else {
186
199
  return 16;
187
200
  }
201
+ } else if (nextMessage && shouldRenderParentMessage(nextMessage)) {
202
+ return 16;
188
203
  } else if (groupWithNext) {
189
204
  return 2;
190
205
  } else {
@@ -10,20 +10,18 @@ import {
10
10
  useUIKitTheme,
11
11
  } from '@sendbird/uikit-react-native-foundation';
12
12
  import type { SendbirdBaseMessage } from '@sendbird/uikit-utils';
13
- import { getFileExtension, getFileType, useIIFE } from '@sendbird/uikit-utils';
13
+ import { getFileIconFromMessage, useIIFE } from '@sendbird/uikit-utils';
14
14
 
15
15
  import type { MessageSearchProps } from '../domain/messageSearch/types';
16
16
  import { useLocalization } from '../hooks/useContext';
17
17
 
18
- const iconMapper = { audio: 'file-audio', image: 'photo', video: 'play', file: 'file-document' } as const;
19
-
20
18
  const MessageSearchResultItem: MessageSearchProps['List']['renderSearchResultItem'] = ({ onPress, message }) => {
21
19
  const { colors, select, palette } = useUIKitTheme();
22
20
  const { STRINGS } = useLocalization();
23
21
 
24
22
  const fileIcon = useIIFE(() => {
25
23
  if (!message?.isFileMessage()) return undefined;
26
- return iconMapper[getFileType(message.type || getFileExtension(message.name))];
24
+ return getFileIconFromMessage(message);
27
25
  });
28
26
 
29
27
  return (
@@ -49,8 +47,8 @@ const MessageSearchResultItem: MessageSearchProps['List']['renderSearchResultIte
49
47
  <Box alignItems={'center'} flexDirection={'row'}>
50
48
  {fileIcon && (
51
49
  <Icon
52
- size={18}
53
50
  icon={fileIcon}
51
+ size={18}
54
52
  color={colors.onBackground02}
55
53
  containerStyle={[
56
54
  styles.bodyIcon,
@@ -24,14 +24,13 @@ const BottomSheetReactionAddon = ({ onClose, message, channel }: Props) => {
24
24
  useChannelHandler(sdk, handlerId, {
25
25
  async onReactionUpdated(eventChannel, event) {
26
26
  if (channel?.url === eventChannel.url && event.messageId === message?.messageId) {
27
- updateReactionFocusedItem({
28
- message: await sdk.message.getMessage({
29
- includeReactions: true,
30
- messageId: message.messageId,
31
- channelUrl: message.channelUrl,
32
- channelType: message.channelType,
33
- }),
27
+ const msg = await sdk.message.getMessage({
28
+ includeReactions: true,
29
+ messageId: message.messageId,
30
+ channelUrl: message.channelUrl,
31
+ channelType: message.channelType,
34
32
  });
33
+ if (msg) updateReactionFocusedItem({ message: msg });
35
34
  }
36
35
  },
37
36
  });
@@ -12,8 +12,9 @@ import {
12
12
  import {
13
13
  SendbirdGroupChannel,
14
14
  SendbirdUser,
15
- getFileExtension,
16
- getFileType,
15
+ convertFileTypeToMessageType,
16
+ getFileIconFromMessageType,
17
+ getFileTypeFromMessage,
17
18
  isDifferentChannel,
18
19
  isMyMessage,
19
20
  useIIFE,
@@ -24,8 +25,6 @@ import ChannelCover from '../components/ChannelCover';
24
25
  import { DEFAULT_LONG_PRESS_DELAY } from '../constants';
25
26
  import { useLocalization, useSendbirdChat } from '../hooks/useContext';
26
27
 
27
- const iconMapper = { audio: 'file-audio', image: 'photo', video: 'play', file: 'file-document' } as const;
28
-
29
28
  type Props = {
30
29
  channel: SendbirdGroupChannel;
31
30
  onPress: () => void;
@@ -54,10 +53,10 @@ const GroupChannelPreviewContainer = ({ onPress, onLongPress, channel }: Props)
54
53
  else return STRINGS.GROUP_CHANNEL_LIST.CHANNEL_PREVIEW_BODY(channel);
55
54
  });
56
55
 
57
- const fileIcon = useIIFE(() => {
56
+ const fileType = useIIFE(() => {
58
57
  if (!channel.lastMessage?.isFileMessage()) return undefined;
59
58
  if (typingUsers.length > 0) return undefined;
60
- return iconMapper[getFileType(channel.lastMessage.type || getFileExtension(channel.lastMessage.name))];
59
+ return getFileTypeFromMessage(channel.lastMessage);
61
60
  });
62
61
 
63
62
  const titleCaptionIcon = useIIFE(() => {
@@ -99,7 +98,7 @@ const GroupChannelPreviewContainer = ({ onPress, onLongPress, channel }: Props)
99
98
  titleCaptionLeft={titleCaptionIcon}
100
99
  titleCaption={STRINGS.GROUP_CHANNEL_LIST.CHANNEL_PREVIEW_TITLE_CAPTION(channel)}
101
100
  body={bodyText}
102
- bodyIcon={fileIcon}
101
+ bodyIcon={fileType && getFileIconFromMessageType(convertFileTypeToMessageType(fileType))}
103
102
  badgeCount={unreadMessageCount}
104
103
  mentioned={channel.unreadMentionCount > 0}
105
104
  mentionTrigger={mentionManager.config.trigger}
@@ -2,7 +2,7 @@ import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
2
2
  import { Platform } from 'react-native';
3
3
  import { SafeAreaProvider } from 'react-native-safe-area-context';
4
4
 
5
- import Sendbird from '@sendbird/chat';
5
+ import Sendbird, { DeviceOsPlatform, SendbirdPlatform, SendbirdProduct } from '@sendbird/chat';
6
6
  import { GroupChannelModule } from '@sendbird/chat/groupChannel';
7
7
  import { OpenChannelModule } from '@sendbird/chat/openChannel';
8
8
  import type { HeaderStyleContextType, UIKitTheme } from '@sendbird/uikit-react-native-foundation';
@@ -76,7 +76,9 @@ export type SendbirdUIKitContainerProps = React.PropsWithChildren<{
76
76
  } & Partial<ChatRelatedFeaturesInUIKit>;
77
77
  uikitOptions?: PartialDeep<{
78
78
  common: SBUConfig['common'];
79
- groupChannel: Omit<SBUConfig['groupChannel']['channel'], UnimplementedFeatures>;
79
+ groupChannel: Omit<SBUConfig['groupChannel']['channel'], UnimplementedFeatures> & {
80
+ replyType: Extract<SBUConfig['groupChannel']['channel']['replyType'], 'none' | 'quote_reply'>;
81
+ };
80
82
  groupChannelList: SBUConfig['groupChannel']['channelList'];
81
83
  groupChannelSettings: SBUConfig['groupChannel']['setting'];
82
84
  openChannel: SBUConfig['openChannel']['channel'];
@@ -169,13 +171,11 @@ const SendbirdUIKitContainer = ({
169
171
  }
170
172
 
171
173
  return () => {
172
- if (!isFirstMount) {
173
- unsubscribes.current.forEach((u) => {
174
- try {
175
- u();
176
- } catch {}
177
- });
178
- }
174
+ unsubscribes.current.forEach((u) => {
175
+ try {
176
+ u();
177
+ } catch {}
178
+ });
179
179
  };
180
180
  }, [appId, internalStorage]);
181
181
 
@@ -288,7 +288,17 @@ const initializeSendbird = (
288
288
  chatSDK = onInitialized(chatSDK);
289
289
  }
290
290
 
291
- if (SendbirdUIKit.VERSION) {
291
+ const platform = getDeviceOSPlatform();
292
+ if (SendbirdUIKit.VERSION && platform) {
293
+ const deviceOSInfo = { platform, version: String(Platform.Version) };
294
+ const customData = { platform_version: getReactNativeVersion() };
295
+ const uikitExtension = {
296
+ product: SendbirdProduct.UIKIT_CHAT,
297
+ version: SendbirdUIKit.VERSION,
298
+ platform: SendbirdPlatform.REACT_NATIVE,
299
+ };
300
+
301
+ chatSDK.addSendbirdExtensions([uikitExtension], deviceOSInfo, customData);
292
302
  chatSDK.addExtension('sb_uikit', SendbirdUIKit.VERSION);
293
303
  }
294
304
 
@@ -331,4 +341,24 @@ const initializeSendbird = (
331
341
  return { chatSDK, unsubscribes };
332
342
  };
333
343
 
344
+ function getDeviceOSPlatform() {
345
+ switch (Platform.OS) {
346
+ case 'android':
347
+ return DeviceOsPlatform.ANDROID;
348
+ case 'ios':
349
+ return DeviceOsPlatform.IOS;
350
+ case 'web':
351
+ return DeviceOsPlatform.WEB;
352
+ case 'windows':
353
+ return DeviceOsPlatform.WINDOWS;
354
+ default:
355
+ return undefined;
356
+ }
357
+ }
358
+
359
+ function getReactNativeVersion() {
360
+ const { major, minor, patch } = Platform.constants.reactNativeVersion;
361
+ return `${major}.${minor}.${patch}`;
362
+ }
363
+
334
364
  export default SendbirdUIKitContainer;
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useState } from 'react';
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
2
 
3
3
  import { useAppFeatures } from '@sendbird/uikit-chat-hooks';
4
4
  import { SBUConfig, useUIKitConfig } from '@sendbird/uikit-tools';
@@ -144,6 +144,12 @@ export const SendbirdChatProvider = ({
144
144
  else if (status === 'background') sdkInstance.connectionState === 'OPEN' && sdkInstance.setBackgroundState();
145
145
  });
146
146
 
147
+ useEffect(() => {
148
+ return () => {
149
+ sdkInstance.disconnect().then(() => _setCurrentUser(undefined));
150
+ };
151
+ }, [sdkInstance]);
152
+
147
153
  const value: SendbirdChatContextType = {
148
154
  sdk: sdkInstance,
149
155
  emojiManager,
@@ -9,9 +9,11 @@ import type { GroupChannelProps } from '../types';
9
9
  const GroupChannelInput = (props: GroupChannelProps['Input']) => {
10
10
  const {
11
11
  channel,
12
+ keyboardAvoidOffset = 0,
12
13
  messageToEdit,
13
14
  setMessageToEdit,
14
- keyboardAvoidOffset = 0,
15
+ messageToReply,
16
+ setMessageToReply,
15
17
  } = useContext(GroupChannelContexts.Fragment);
16
18
 
17
19
  const chatAvailableState = getGroupChannelChatAvailableState(channel);
@@ -21,6 +23,8 @@ const GroupChannelInput = (props: GroupChannelProps['Input']) => {
21
23
  channel={channel}
22
24
  messageToEdit={messageToEdit}
23
25
  setMessageToEdit={setMessageToEdit}
26
+ messageToReply={messageToReply}
27
+ setMessageToReply={setMessageToReply}
24
28
  inputMuted={chatAvailableState.muted}
25
29
  inputFrozen={chatAvailableState.frozen}
26
30
  inputDisabled={chatAvailableState.disabled}