@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
@@ -9,17 +9,31 @@ import {
9
9
  } from 'react-native';
10
10
 
11
11
  import { MentionType } from '@sendbird/chat/message';
12
+ import type { BottomSheetItem } from '@sendbird/uikit-react-native-foundation';
12
13
  import {
13
14
  Icon,
15
+ ImageWithPlaceholder,
16
+ Text,
14
17
  TextInput,
18
+ VideoThumbnail,
15
19
  createStyleSheet,
16
20
  useAlert,
17
21
  useBottomSheet,
18
22
  useToast,
19
23
  useUIKitTheme,
20
24
  } from '@sendbird/uikit-react-native-foundation';
21
- import type { BottomSheetItem } from '@sendbird/uikit-react-native-foundation';
22
- import { SendbirdChannel, isImage, shouldCompressImage, useIIFE } from '@sendbird/uikit-utils';
25
+ import {
26
+ FileIcon,
27
+ Logger,
28
+ SendbirdBaseMessage,
29
+ SendbirdChannel,
30
+ getAvailableUriFromFileMessage,
31
+ getFileIconFromMessageType,
32
+ getMessageType,
33
+ isImage,
34
+ shouldCompressImage,
35
+ useIIFE,
36
+ } from '@sendbird/uikit-utils';
23
37
 
24
38
  import { useLocalization, usePlatformService, useSendbirdChat } from '../../hooks/useContext';
25
39
  import SBUError from '../../libs/SBUError';
@@ -48,81 +62,192 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
48
62
  inputFrozen,
49
63
  inputMuted,
50
64
  channel,
65
+ messageToReply,
66
+ setMessageToReply,
51
67
  },
52
68
  ref,
53
69
  ) {
54
70
  const { mentionManager, sbOptions } = useSendbirdChat();
71
+ const { select, colors, palette } = useUIKitTheme();
55
72
  const { STRINGS } = useLocalization();
56
- const { colors } = useUIKitTheme();
57
73
  const { openSheet } = useBottomSheet();
58
74
  const toast = useToast();
75
+ const { mediaService } = usePlatformService();
76
+
77
+ const messageReplyParams = useIIFE(() => {
78
+ const { groupChannel } = sbOptions.uikit;
79
+ if (!channel.isGroupChannel() || groupChannel.channel.replyType === 'none' || !messageToReply) return {};
80
+ return {
81
+ parentMessageId: messageToReply.messageId,
82
+ isReplyToChannel: true,
83
+ };
84
+ });
59
85
 
60
- const onFailureToSend = () => toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error');
86
+ const messageMentionParams = useIIFE(() => {
87
+ const { groupChannel } = sbOptions.uikit;
88
+ if (!channel.isGroupChannel() || !groupChannel.channel.enableMention) return {};
89
+ return {
90
+ mentionType: MentionType.USERS,
91
+ mentionedUserIds: mentionedUsers.map((it) => it.user.userId),
92
+ mentionedMessageTemplate: mentionManager.textToMentionedMessageTemplate(
93
+ text,
94
+ mentionedUsers,
95
+ groupChannel.channel.enableMention,
96
+ ),
97
+ };
98
+ });
61
99
 
62
- const sendUserMessage = () => {
63
- const mentionType = MentionType.USERS;
64
- const mentionedUserIds = mentionedUsers.map((it) => it.user.userId);
65
- const mentionedMessageTemplate = mentionManager.textToMentionedMessageTemplate(
66
- text,
67
- mentionedUsers,
68
- sbOptions.uikit.groupChannel.channel.enableMention,
69
- );
100
+ const onFailureToSend = (error: Error) => {
101
+ toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error');
102
+ Logger.error(STRINGS.TOAST.SEND_MSG_ERROR, error);
103
+ };
70
104
 
105
+ const sendUserMessage = () => {
71
106
  onPressSendUserMessage({
72
107
  message: text,
73
- mentionType,
74
- mentionedUserIds,
75
- mentionedMessageTemplate,
108
+ ...messageMentionParams,
109
+ ...messageReplyParams,
76
110
  }).catch(onFailureToSend);
77
111
 
78
112
  onChangeText('');
113
+ setMessageToReply?.();
79
114
  };
80
115
 
81
- const sheetItems = useChannelInputItems(channel, (file) => {
82
- onPressSendFileMessage({ file }).catch(onFailureToSend);
83
- });
116
+ const sendFileMessage = (file: FileType) => {
117
+ onPressSendFileMessage({
118
+ file,
119
+ ...messageReplyParams,
120
+ }).catch(onFailureToSend);
121
+
122
+ setMessageToReply?.();
123
+ };
124
+
125
+ const sheetItems = useChannelInputItems(channel, sendFileMessage);
84
126
  const onPressAttachment = () => openSheet({ sheetItems });
85
127
 
86
128
  const getPlaceholder = () => {
87
- if (!inputDisabled) return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_ACTIVE;
88
- if (inputFrozen) return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_DISABLED;
89
129
  if (inputMuted) return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_MUTED;
130
+ if (inputFrozen) return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_DISABLED;
131
+ if (inputDisabled) return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_DISABLED;
132
+ if (messageToReply) return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_REPLY;
90
133
 
91
- return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_DISABLED;
134
+ return STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_ACTIVE;
92
135
  };
93
136
 
94
- return (
95
- <View style={styles.sendInputContainer}>
96
- {AttachmentsButton && <AttachmentsButton onPress={onPressAttachment} disabled={inputDisabled} />}
97
- <TextInput
98
- ref={ref}
99
- multiline
100
- disableFullscreenUI
101
- onSelectionChange={onSelectionChange}
102
- editable={!inputDisabled}
103
- onChangeText={onChangeText}
104
- style={styles.input}
105
- placeholder={getPlaceholder()}
106
- >
107
- {mentionManager.textToMentionedComponents(
108
- text,
109
- mentionedUsers,
110
- sbOptions.uikit.groupChannel.channel.enableMention,
111
- )}
112
- </TextInput>
137
+ const getFileIconAsImage = (url: string) => {
138
+ return <ImageWithPlaceholder source={{ uri: url }} style={styles.previewImage} />;
139
+ };
113
140
 
114
- {Boolean(text.trim()) && (
115
- <TouchableOpacity onPress={sendUserMessage} disabled={inputDisabled}>
116
- <Icon
117
- color={
118
- inputDisabled ? colors.ui.input.default.disabled.highlight : colors.ui.input.default.active.highlight
119
- }
120
- icon={'send'}
121
- size={24}
122
- containerStyle={styles.iconSend}
123
- />
124
- </TouchableOpacity>
141
+ const getFileIconAsVideoThumbnail = (url: string) => {
142
+ return (
143
+ <VideoThumbnail
144
+ style={styles.previewImage}
145
+ iconSize={0}
146
+ videoSource={url}
147
+ fetchThumbnailFromVideoSource={(uri) => mediaService.getVideoThumbnail({ url: uri, timeMills: 1000 })}
148
+ />
149
+ );
150
+ };
151
+
152
+ const getFileIconAsSymbol = (icon: FileIcon) => {
153
+ return (
154
+ <Icon
155
+ icon={icon}
156
+ size={20}
157
+ color={colors.onBackground02}
158
+ containerStyle={{
159
+ backgroundColor: select({
160
+ light: palette.background100,
161
+ dark: palette.background500,
162
+ }),
163
+ width: 36,
164
+ height: 36,
165
+ borderRadius: 10,
166
+ marginRight: 10,
167
+ marginTop: 2,
168
+ }}
169
+ />
170
+ );
171
+ };
172
+
173
+ const getFileIcon = (messageToReply: SendbirdBaseMessage) => {
174
+ if (messageToReply?.isFileMessage()) {
175
+ const messageType = getMessageType(messageToReply);
176
+ switch (messageType) {
177
+ case 'file.image':
178
+ return getFileIconAsImage(getAvailableUriFromFileMessage(messageToReply));
179
+ case 'file.video':
180
+ return getFileIconAsVideoThumbnail(getAvailableUriFromFileMessage(messageToReply));
181
+ default:
182
+ return getFileIconAsSymbol(getFileIconFromMessageType(messageType));
183
+ }
184
+ }
185
+ return null;
186
+ };
187
+
188
+ return (
189
+ <View>
190
+ {messageToReply && (
191
+ <View
192
+ style={{
193
+ flexDirection: 'row',
194
+ paddingLeft: 18,
195
+ paddingRight: 16,
196
+ paddingTop: 10,
197
+ paddingBottom: 8,
198
+ alignItems: 'center',
199
+ borderTopWidth: 1,
200
+ borderColor: colors.onBackground04,
201
+ }}
202
+ >
203
+ <View style={{ flex: 1, flexDirection: 'row' }}>
204
+ {getFileIcon(messageToReply)}
205
+ <View style={{ flex: 1, flexDirection: 'column' }}>
206
+ <Text numberOfLines={1} style={{ fontSize: 13, fontWeight: '900', marginBottom: 4 }}>
207
+ {STRINGS.LABELS.CHANNEL_INPUT_REPLY_PREVIEW_TITLE(messageToReply.sender)}
208
+ </Text>
209
+ <Text numberOfLines={1} style={{ fontSize: 13, color: colors.onBackground03 }}>
210
+ {STRINGS.LABELS.CHANNEL_INPUT_REPLY_PREVIEW_BODY(messageToReply)}
211
+ </Text>
212
+ </View>
213
+ </View>
214
+ <TouchableOpacity onPress={() => setMessageToReply?.(undefined)}>
215
+ <Icon icon={'close'} size={24} color={colors.onBackground01} containerStyle={styles.iconSend} />
216
+ </TouchableOpacity>
217
+ </View>
125
218
  )}
219
+ <View style={styles.sendInputContainer}>
220
+ {AttachmentsButton && <AttachmentsButton onPress={onPressAttachment} disabled={inputDisabled} />}
221
+ <TextInput
222
+ ref={ref}
223
+ multiline
224
+ disableFullscreenUI
225
+ onSelectionChange={onSelectionChange}
226
+ editable={!inputDisabled}
227
+ onChangeText={onChangeText}
228
+ style={styles.input}
229
+ placeholder={getPlaceholder()}
230
+ >
231
+ {mentionManager.textToMentionedComponents(
232
+ text,
233
+ mentionedUsers,
234
+ sbOptions.uikit.groupChannel.channel.enableMention,
235
+ )}
236
+ </TextInput>
237
+
238
+ {Boolean(text.trim()) && (
239
+ <TouchableOpacity onPress={sendUserMessage} disabled={inputDisabled}>
240
+ <Icon
241
+ color={
242
+ inputDisabled ? colors.ui.input.default.disabled.highlight : colors.ui.input.default.active.highlight
243
+ }
244
+ icon={'send'}
245
+ size={24}
246
+ containerStyle={styles.iconSend}
247
+ />
248
+ </TouchableOpacity>
249
+ )}
250
+ </View>
126
251
  </View>
127
252
  );
128
253
  });
@@ -353,6 +478,14 @@ const styles = createStyleSheet({
353
478
  marginLeft: 4,
354
479
  padding: 4,
355
480
  },
481
+ previewImage: {
482
+ width: 36,
483
+ height: 36,
484
+ borderRadius: 10,
485
+ marginTop: 2,
486
+ marginRight: 10,
487
+ overflow: 'hidden',
488
+ },
356
489
  });
357
490
 
358
491
  export default SendInput;
@@ -1,4 +1,4 @@
1
- import React, { MutableRefObject, useEffect, useRef, useState } from 'react';
1
+ import React, { MutableRefObject, useEffect, useState } from 'react';
2
2
  import { KeyboardAvoidingView, Platform, TextInput, View } from 'react-native';
3
3
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
4
4
 
@@ -56,6 +56,10 @@ export type ChannelInputProps = {
56
56
  messageToEdit: undefined | SendbirdUserMessage | SendbirdFileMessage;
57
57
  setMessageToEdit: (message?: undefined | SendbirdUserMessage | SendbirdFileMessage) => void;
58
58
 
59
+ // reply - only available on group channel
60
+ messageToReply?: undefined | SendbirdUserMessage | SendbirdFileMessage;
61
+ setMessageToReply?: (message?: undefined | SendbirdUserMessage | SendbirdFileMessage) => void;
62
+
59
63
  // mention
60
64
  SuggestedMentionList?: (props: SuggestedMentionListProps) => JSX.Element | null;
61
65
 
@@ -83,9 +87,8 @@ const ChannelInput = (props: ChannelInputProps) => {
83
87
  messageToEdit,
84
88
  });
85
89
  const inputMode = useIIFE(() => {
86
- if (!messageToEdit) return 'send';
87
- if (messageToEdit.isFileMessage()) return 'send';
88
- return 'edit';
90
+ if (messageToEdit && !messageToEdit.isFileMessage()) return 'edit';
91
+ else return 'send';
89
92
  });
90
93
 
91
94
  const mentionAvailable =
@@ -95,7 +98,7 @@ const ChannelInput = (props: ChannelInputProps) => {
95
98
  const [inputHeight, setInputHeight] = useState(styles.inputDefault.height);
96
99
 
97
100
  useTypingTrigger(text, channel);
98
- useTextPersistenceOnDisabled(text, onChangeText, props.inputDisabled);
101
+ useTextClearOnDisabled(onChangeText, props.inputDisabled);
99
102
  useAutoFocusOnEditMode(textInputRef, messageToEdit);
100
103
 
101
104
  const onPressToMention = (user: SendbirdMember, searchStringRange: Range) => {
@@ -138,8 +141,8 @@ const ChannelInput = (props: ChannelInputProps) => {
138
141
  onChangeText={onChangeText}
139
142
  autoFocus={AUTO_FOCUS}
140
143
  onSelectionChange={onSelectionChange}
141
- messageToEdit={messageToEdit}
142
144
  mentionedUsers={mentionedUsers}
145
+ messageToEdit={messageToEdit}
143
146
  setMessageToEdit={setMessageToEdit}
144
147
  />
145
148
  )}
@@ -171,16 +174,9 @@ const useTypingTrigger = (text: string, channel: SendbirdBaseChannel) => {
171
174
  }
172
175
  };
173
176
 
174
- const useTextPersistenceOnDisabled = (text: string, setText: (val: string) => void, chatDisabled: boolean) => {
175
- const textTmpRef = useRef('');
176
-
177
+ const useTextClearOnDisabled = (setText: (val: string) => void, chatDisabled: boolean) => {
177
178
  useEffect(() => {
178
- if (chatDisabled) {
179
- textTmpRef.current = text;
180
- setText('');
181
- } else {
182
- setText(textTmpRef.current);
183
- }
179
+ if (chatDisabled) setText('');
184
180
  }, [chatDisabled]);
185
181
  };
186
182
 
@@ -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