@sendbird/uikit-react-native 3.0.4-rc.1 → 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 (150) 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/createFileService.native.js +4 -1
  46. package/lib/commonjs/platform/createFileService.native.js.map +1 -1
  47. package/lib/commonjs/platform/createMediaService.native.js +1 -1
  48. package/lib/commonjs/platform/createMediaService.native.js.map +1 -1
  49. package/lib/commonjs/platform/types.js.map +1 -1
  50. package/lib/commonjs/utils/pubsub.js +3 -1
  51. package/lib/commonjs/utils/pubsub.js.map +1 -1
  52. package/lib/commonjs/version.js +1 -1
  53. package/lib/commonjs/version.js.map +1 -1
  54. package/lib/module/components/ChannelInput/SendInput.js +153 -21
  55. package/lib/module/components/ChannelInput/SendInput.js.map +1 -1
  56. package/lib/module/components/ChannelInput/index.js +6 -14
  57. package/lib/module/components/ChannelInput/index.js.map +1 -1
  58. package/lib/module/components/ChannelMessageList/index.js +34 -20
  59. package/lib/module/components/ChannelMessageList/index.js.map +1 -1
  60. package/lib/module/components/ChatFlatList/index.js +1 -1
  61. package/lib/module/components/ChatFlatList/index.js.map +1 -1
  62. package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageFocusAnimation.js +4 -1
  63. package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageFocusAnimation.js.map +1 -1
  64. package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.js +182 -0
  65. package/lib/module/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.js.map +1 -0
  66. package/lib/module/components/GroupChannelMessageRenderer/index.js +13 -2
  67. package/lib/module/components/GroupChannelMessageRenderer/index.js.map +1 -1
  68. package/lib/module/components/MessageSearchResultItem.js +3 -9
  69. package/lib/module/components/MessageSearchResultItem.js.map +1 -1
  70. package/lib/module/components/ReactionAddons/BottomSheetReactionAddon.js +8 -7
  71. package/lib/module/components/ReactionAddons/BottomSheetReactionAddon.js.map +1 -1
  72. package/lib/module/containers/GroupChannelPreviewContainer.js +4 -10
  73. package/lib/module/containers/GroupChannelPreviewContainer.js.map +1 -1
  74. package/lib/module/containers/SendbirdUIKitContainer.js +43 -9
  75. package/lib/module/containers/SendbirdUIKitContainer.js.map +1 -1
  76. package/lib/module/contexts/SendbirdChatCtx.js +6 -1
  77. package/lib/module/contexts/SendbirdChatCtx.js.map +1 -1
  78. package/lib/module/domain/groupChannel/component/GroupChannelInput.js +5 -1
  79. package/lib/module/domain/groupChannel/component/GroupChannelInput.js.map +1 -1
  80. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js +50 -13
  81. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
  82. package/lib/module/domain/groupChannel/module/moduleContext.js +39 -4
  83. package/lib/module/domain/groupChannel/module/moduleContext.js.map +1 -1
  84. package/lib/module/domain/groupChannel/types.js.map +1 -1
  85. package/lib/module/domain/openChannel/types.js.map +1 -1
  86. package/lib/module/fragments/createGroupChannelFragment.js +19 -0
  87. package/lib/module/fragments/createGroupChannelFragment.js.map +1 -1
  88. package/lib/module/hooks/useConnection.js +2 -1
  89. package/lib/module/hooks/useConnection.js.map +1 -1
  90. package/lib/module/hooks/useMentionSuggestion.js +1 -1
  91. package/lib/module/hooks/useMentionSuggestion.js.map +1 -1
  92. package/lib/module/hooks/useMentionTextInput.js +4 -3
  93. package/lib/module/hooks/useMentionTextInput.js.map +1 -1
  94. package/lib/module/libs/MentionManager.js.map +1 -1
  95. package/lib/module/localization/StringSet.type.js.map +1 -1
  96. package/lib/module/localization/createBaseStringSet.js +30 -1
  97. package/lib/module/localization/createBaseStringSet.js.map +1 -1
  98. package/lib/module/platform/createFileService.native.js +5 -2
  99. package/lib/module/platform/createFileService.native.js.map +1 -1
  100. package/lib/module/platform/createMediaService.native.js +1 -1
  101. package/lib/module/platform/createMediaService.native.js.map +1 -1
  102. package/lib/module/platform/types.js.map +1 -1
  103. package/lib/module/utils/pubsub.js +3 -1
  104. package/lib/module/utils/pubsub.js.map +1 -1
  105. package/lib/module/version.js +1 -1
  106. package/lib/module/version.js.map +1 -1
  107. package/lib/typescript/src/components/ChannelInput/index.d.ts +2 -0
  108. package/lib/typescript/src/components/ChannelMessageList/index.d.ts +4 -1
  109. package/lib/typescript/src/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.d.ts +9 -0
  110. package/lib/typescript/src/components/GroupChannelMessageRenderer/index.d.ts +1 -0
  111. package/lib/typescript/src/components/OpenChannelMessageRenderer/index.d.ts +1 -0
  112. package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +4 -2
  113. package/lib/typescript/src/domain/groupChannel/component/GroupChannelMessageList.d.ts +4 -0
  114. package/lib/typescript/src/domain/groupChannel/types.d.ts +6 -2
  115. package/lib/typescript/src/domain/openChannel/component/OpenChannelHeader.d.ts +1 -1
  116. package/lib/typescript/src/domain/openChannel/types.d.ts +1 -2
  117. package/lib/typescript/src/libs/MentionManager.d.ts +1 -4
  118. package/lib/typescript/src/localization/StringSet.type.d.ts +7 -1
  119. package/lib/typescript/src/platform/types.d.ts +10 -11
  120. package/lib/typescript/src/version.d.ts +1 -1
  121. package/package.json +7 -6
  122. package/src/components/ChannelInput/SendInput.tsx +184 -51
  123. package/src/components/ChannelInput/index.tsx +11 -15
  124. package/src/components/ChannelMessageList/index.tsx +45 -27
  125. package/src/components/ChatFlatList/index.tsx +1 -1
  126. package/src/components/GroupChannelMessageRenderer/GroupChannelMessageFocusAnimation.tsx +5 -1
  127. package/src/components/GroupChannelMessageRenderer/GroupChannelMessageParentMessage.tsx +177 -0
  128. package/src/components/GroupChannelMessageRenderer/index.tsx +16 -1
  129. package/src/components/MessageSearchResultItem.tsx +3 -5
  130. package/src/components/ReactionAddons/BottomSheetReactionAddon.tsx +6 -7
  131. package/src/containers/GroupChannelPreviewContainer.tsx +6 -7
  132. package/src/containers/SendbirdUIKitContainer.tsx +40 -10
  133. package/src/contexts/SendbirdChatCtx.tsx +7 -1
  134. package/src/domain/groupChannel/component/GroupChannelInput.tsx +5 -1
  135. package/src/domain/groupChannel/component/GroupChannelMessageList.tsx +46 -13
  136. package/src/domain/groupChannel/module/moduleContext.tsx +38 -3
  137. package/src/domain/groupChannel/types.ts +8 -2
  138. package/src/domain/openChannel/types.ts +1 -2
  139. package/src/fragments/createGroupChannelFragment.tsx +15 -0
  140. package/src/hooks/useConnection.ts +1 -1
  141. package/src/hooks/useMentionSuggestion.ts +2 -1
  142. package/src/hooks/useMentionTextInput.ts +2 -2
  143. package/src/libs/MentionManager.tsx +1 -8
  144. package/src/localization/StringSet.type.ts +11 -0
  145. package/src/localization/createBaseStringSet.ts +30 -0
  146. package/src/platform/createFileService.native.ts +5 -1
  147. package/src/platform/createMediaService.native.tsx +1 -1
  148. package/src/platform/types.ts +9 -9
  149. package/src/utils/pubsub.ts +3 -1
  150. package/src/version.ts +1 -1
@@ -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}
@@ -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
  };