@sendbird/uikit-react-native 3.2.0 → 3.4.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.
- package/lib/commonjs/components/ChannelInput/EditInput.js +2 -11
- package/lib/commonjs/components/ChannelInput/EditInput.js.map +1 -1
- package/lib/commonjs/components/ChannelInput/SendInput.js +2 -11
- package/lib/commonjs/components/ChannelInput/SendInput.js.map +1 -1
- package/lib/commonjs/components/ChannelInput/index.js +30 -3
- package/lib/commonjs/components/ChannelInput/index.js.map +1 -1
- package/lib/commonjs/components/ChannelMessageList/index.js +148 -116
- package/lib/commonjs/components/ChannelMessageList/index.js.map +1 -1
- package/lib/commonjs/components/FileViewer/FileViewerContent.js +140 -0
- package/lib/commonjs/components/FileViewer/FileViewerContent.js.map +1 -0
- package/lib/commonjs/components/FileViewer/FileViewerFooter.js +82 -0
- package/lib/commonjs/components/FileViewer/FileViewerFooter.js.map +1 -0
- package/lib/commonjs/components/FileViewer/FileViewerHeader.js +93 -0
- package/lib/commonjs/components/FileViewer/FileViewerHeader.js.map +1 -0
- package/lib/commonjs/components/FileViewer/index.js +133 -0
- package/lib/commonjs/components/FileViewer/index.js.map +1 -0
- package/lib/commonjs/components/GroupChannelMessageRenderer/index.js +34 -1
- package/lib/commonjs/components/GroupChannelMessageRenderer/index.js.map +1 -1
- package/lib/commonjs/components/ReactionAddons/BottomSheetReactionAddon.js.map +1 -1
- package/lib/commonjs/domain/groupChannel/component/GroupChannelHeader.js +14 -4
- package/lib/commonjs/domain/groupChannel/component/GroupChannelHeader.js.map +1 -1
- package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js +11 -9
- package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
- package/lib/commonjs/domain/groupChannel/types.js.map +1 -1
- package/lib/commonjs/domain/messageSearch/component/MessageSearchHeader.js +4 -1
- package/lib/commonjs/domain/messageSearch/component/MessageSearchHeader.js.map +1 -1
- package/lib/commonjs/domain/openChannelCreate/component/OpenChannelCreateProfileInput.js +4 -2
- package/lib/commonjs/domain/openChannelCreate/component/OpenChannelCreateProfileInput.js.map +1 -1
- package/lib/commonjs/fragments/createGroupChannelFragment.js +18 -16
- package/lib/commonjs/fragments/createGroupChannelFragment.js.map +1 -1
- package/lib/commonjs/index.js +4 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/types.js +7 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/utils/promise.js +138 -0
- package/lib/commonjs/utils/promise.js.map +1 -0
- package/lib/commonjs/version.js +1 -1
- package/lib/commonjs/version.js.map +1 -1
- package/lib/module/components/ChannelInput/EditInput.js +3 -12
- package/lib/module/components/ChannelInput/EditInput.js.map +1 -1
- package/lib/module/components/ChannelInput/SendInput.js +3 -12
- package/lib/module/components/ChannelInput/SendInput.js.map +1 -1
- package/lib/module/components/ChannelInput/index.js +32 -5
- package/lib/module/components/ChannelInput/index.js.map +1 -1
- package/lib/module/components/ChannelMessageList/index.js +148 -116
- package/lib/module/components/ChannelMessageList/index.js.map +1 -1
- package/lib/module/components/FileViewer/FileViewerContent.js +130 -0
- package/lib/module/components/FileViewer/FileViewerContent.js.map +1 -0
- package/lib/module/components/FileViewer/FileViewerFooter.js +74 -0
- package/lib/module/components/FileViewer/FileViewerFooter.js.map +1 -0
- package/lib/module/components/FileViewer/FileViewerHeader.js +85 -0
- package/lib/module/components/FileViewer/FileViewerHeader.js.map +1 -0
- package/lib/module/components/FileViewer/index.js +123 -0
- package/lib/module/components/FileViewer/index.js.map +1 -0
- package/lib/module/components/GroupChannelMessageRenderer/index.js +34 -2
- package/lib/module/components/GroupChannelMessageRenderer/index.js.map +1 -1
- package/lib/module/components/ReactionAddons/BottomSheetReactionAddon.js.map +1 -1
- package/lib/module/domain/groupChannel/component/GroupChannelHeader.js +15 -5
- package/lib/module/domain/groupChannel/component/GroupChannelHeader.js.map +1 -1
- package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js +11 -9
- package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
- package/lib/module/domain/groupChannel/types.js.map +1 -1
- package/lib/module/domain/messageSearch/component/MessageSearchHeader.js +4 -1
- package/lib/module/domain/messageSearch/component/MessageSearchHeader.js.map +1 -1
- package/lib/module/domain/openChannelCreate/component/OpenChannelCreateProfileInput.js +4 -2
- package/lib/module/domain/openChannelCreate/component/OpenChannelCreateProfileInput.js.map +1 -1
- package/lib/module/fragments/createGroupChannelFragment.js +19 -17
- package/lib/module/fragments/createGroupChannelFragment.js.map +1 -1
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +5 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/promise.js +132 -0
- package/lib/module/utils/promise.js.map +1 -0
- package/lib/module/version.js +1 -1
- package/lib/module/version.js.map +1 -1
- package/lib/typescript/src/components/ChannelInput/index.d.ts +2 -0
- package/lib/typescript/src/components/ChannelMessageList/index.d.ts +4 -1
- package/lib/typescript/src/components/FileViewer/FileViewerContent.d.ts +13 -0
- package/lib/typescript/src/components/FileViewer/FileViewerFooter.d.ts +9 -0
- package/lib/typescript/src/components/FileViewer/FileViewerHeader.d.ts +10 -0
- package/lib/typescript/src/components/{FileViewer.d.ts → FileViewer/index.d.ts} +5 -1
- package/lib/typescript/src/components/GroupChannelMessageRenderer/index.d.ts +3 -0
- package/lib/typescript/src/components/OpenChannelMessageRenderer/index.d.ts +2 -0
- package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +1 -1
- package/lib/typescript/src/domain/groupChannel/component/GroupChannelMessageList.d.ts +2 -2
- package/lib/typescript/src/domain/groupChannel/types.d.ts +5 -2
- package/lib/typescript/src/types.d.ts +4 -0
- package/lib/typescript/src/utils/promise.d.ts +7 -0
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +8 -7
- package/src/components/ChannelInput/EditInput.tsx +3 -15
- package/src/components/ChannelInput/SendInput.tsx +2 -9
- package/src/components/ChannelInput/index.tsx +27 -4
- package/src/components/ChannelMessageList/index.tsx +145 -115
- package/src/components/FileViewer/FileViewerContent.tsx +141 -0
- package/src/components/FileViewer/FileViewerFooter.tsx +73 -0
- package/src/components/FileViewer/FileViewerHeader.tsx +86 -0
- package/src/components/FileViewer/index.tsx +139 -0
- package/src/components/GroupChannelMessageRenderer/index.tsx +34 -2
- package/src/components/ReactionAddons/BottomSheetReactionAddon.tsx +3 -2
- package/src/domain/groupChannel/component/GroupChannelHeader.tsx +14 -3
- package/src/domain/groupChannel/component/GroupChannelMessageList.tsx +8 -6
- package/src/domain/groupChannel/types.ts +6 -2
- package/src/domain/messageSearch/component/MessageSearchHeader.tsx +4 -1
- package/src/domain/openChannelCreate/component/OpenChannelCreateProfileInput.tsx +4 -2
- package/src/fragments/createGroupChannelFragment.tsx +25 -15
- package/src/index.ts +5 -1
- package/src/types.ts +5 -0
- package/src/utils/promise.ts +139 -0
- package/src/version.ts +1 -1
- package/lib/commonjs/components/FileViewer.js +0 -300
- package/lib/commonjs/components/FileViewer.js.map +0 -1
- package/lib/module/components/FileViewer.js +0 -291
- package/lib/module/components/FileViewer.js.map +0 -1
- package/src/components/FileViewer.tsx +0 -288
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
NativeSyntheticEvent,
|
|
4
|
-
Platform,
|
|
5
|
-
TextInput as RNTextInput,
|
|
6
|
-
TextInputSelectionChangeEventData,
|
|
7
|
-
View,
|
|
8
|
-
} from 'react-native';
|
|
2
|
+
import { NativeSyntheticEvent, TextInput as RNTextInput, TextInputSelectionChangeEventData, View } from 'react-native';
|
|
9
3
|
|
|
10
4
|
import { MentionType } from '@sendbird/chat/message';
|
|
11
5
|
import { Button, TextInput, createStyleSheet, useToast } from '@sendbird/uikit-react-native-foundation';
|
|
@@ -27,6 +21,7 @@ interface EditInputProps extends ChannelInputProps {
|
|
|
27
21
|
|
|
28
22
|
const EditInput = forwardRef<RNTextInput, EditInputProps>(function EditInput(
|
|
29
23
|
{
|
|
24
|
+
style,
|
|
30
25
|
text,
|
|
31
26
|
onChangeText,
|
|
32
27
|
messageToEdit,
|
|
@@ -81,7 +76,7 @@ const EditInput = forwardRef<RNTextInput, EditInputProps>(function EditInput(
|
|
|
81
76
|
editable={!inputDisabled}
|
|
82
77
|
autoFocus={autoFocus}
|
|
83
78
|
onChangeText={onChangeText}
|
|
84
|
-
style={
|
|
79
|
+
style={style}
|
|
85
80
|
placeholder={STRINGS.LABELS.CHANNEL_INPUT_PLACEHOLDER_ACTIVE}
|
|
86
81
|
onSelectionChange={onSelectionChange}
|
|
87
82
|
>
|
|
@@ -112,13 +107,6 @@ const styles = createStyleSheet({
|
|
|
112
107
|
flexDirection: 'column',
|
|
113
108
|
alignItems: 'center',
|
|
114
109
|
},
|
|
115
|
-
input: {
|
|
116
|
-
flex: 1,
|
|
117
|
-
marginRight: 4,
|
|
118
|
-
minHeight: 36,
|
|
119
|
-
maxHeight: 36 * Platform.select({ ios: 2.5, default: 2 }),
|
|
120
|
-
borderRadius: 20,
|
|
121
|
-
},
|
|
122
110
|
inputWrapper: {
|
|
123
111
|
flexDirection: 'row',
|
|
124
112
|
},
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
NativeSyntheticEvent,
|
|
4
|
-
Platform,
|
|
5
4
|
TextInput as RNTextInput,
|
|
6
5
|
TextInputSelectionChangeEventData,
|
|
7
6
|
TouchableOpacity,
|
|
@@ -38,6 +37,7 @@ interface SendInputProps extends ChannelInputProps {
|
|
|
38
37
|
|
|
39
38
|
const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
|
|
40
39
|
{
|
|
40
|
+
style,
|
|
41
41
|
VoiceMessageInput,
|
|
42
42
|
MessageToReplyPreview,
|
|
43
43
|
AttachmentsButton,
|
|
@@ -173,7 +173,7 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
|
|
|
173
173
|
onSelectionChange={onSelectionChange}
|
|
174
174
|
editable={!inputDisabled}
|
|
175
175
|
onChangeText={onChangeText}
|
|
176
|
-
style={
|
|
176
|
+
style={style}
|
|
177
177
|
placeholder={getPlaceholder()}
|
|
178
178
|
>
|
|
179
179
|
{mentionManager.textToMentionedComponents(
|
|
@@ -284,13 +284,6 @@ const styles = createStyleSheet({
|
|
|
284
284
|
alignItems: 'center',
|
|
285
285
|
flexDirection: 'row',
|
|
286
286
|
},
|
|
287
|
-
input: {
|
|
288
|
-
flex: 1,
|
|
289
|
-
marginRight: 4,
|
|
290
|
-
minHeight: 36,
|
|
291
|
-
maxHeight: 36 * Platform.select({ ios: 2.5, default: 2 }),
|
|
292
|
-
borderRadius: 20,
|
|
293
|
-
},
|
|
294
287
|
sendIcon: {
|
|
295
288
|
marginLeft: 4,
|
|
296
289
|
padding: 4,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import { KeyboardAvoidingView, Platform, TextInput, View } from 'react-native';
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { KeyboardAvoidingView, Platform, StyleProp, StyleSheet, TextInput, TextStyle, View } from 'react-native';
|
|
3
3
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
4
|
|
|
5
5
|
import { createStyleSheet, useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
|
|
@@ -39,6 +39,9 @@ export type SuggestedMentionListProps = {
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
export type ChannelInputProps = {
|
|
42
|
+
// style
|
|
43
|
+
style?: StyleProp<TextStyle>;
|
|
44
|
+
|
|
42
45
|
// default
|
|
43
46
|
channel: SendbirdBaseChannel;
|
|
44
47
|
shouldRenderInput: boolean;
|
|
@@ -85,7 +88,7 @@ const ChannelInput = (props: ChannelInputProps) => {
|
|
|
85
88
|
const { channel, keyboardAvoidOffset, messageToEdit, setMessageToEdit } = props;
|
|
86
89
|
|
|
87
90
|
const { top, left, right, bottom } = useSafeAreaInsets();
|
|
88
|
-
const { colors } = useUIKitTheme();
|
|
91
|
+
const { colors, typography } = useUIKitTheme();
|
|
89
92
|
const { sbOptions, mentionManager } = useSendbirdChat();
|
|
90
93
|
|
|
91
94
|
const { selection, onSelectionChange, textInputRef, text, onChangeText, mentionedUsers } = useMentionTextInput({
|
|
@@ -98,11 +101,18 @@ const ChannelInput = (props: ChannelInputProps) => {
|
|
|
98
101
|
|
|
99
102
|
const mentionAvailable =
|
|
100
103
|
sbOptions.uikit.groupChannel.channel.enableMention && channel.isGroupChannel() && !channel.isBroadcast;
|
|
101
|
-
|
|
102
104
|
const inputKeyToRemount = GET_INPUT_KEY(mentionAvailable ? mentionedUsers.length === 0 : false);
|
|
103
105
|
|
|
104
106
|
const [inputHeight, setInputHeight] = useState(styles.inputDefault.height);
|
|
105
107
|
|
|
108
|
+
const fontStyle = useMemo(() => {
|
|
109
|
+
if (!typography.body3.fontSize) return typography.body3;
|
|
110
|
+
// NOTE: iOS does not support textAlignVertical, so we should adjust lineHeight to center the text in multiline TextInput.
|
|
111
|
+
return { ...typography.body3, lineHeight: typography.body3.fontSize * 1.275, textAlignVertical: 'center' };
|
|
112
|
+
}, [typography.body3.fontSize]);
|
|
113
|
+
|
|
114
|
+
const textInputStyle = StyleSheet.flatten([styles.input, fontStyle, props.style]);
|
|
115
|
+
|
|
106
116
|
useTypingTrigger(text, channel);
|
|
107
117
|
useTextClearOnDisabled(onChangeText, props.inputDisabled);
|
|
108
118
|
useAutoFocusOnEditMode(textInputRef, messageToEdit);
|
|
@@ -138,6 +148,7 @@ const ChannelInput = (props: ChannelInputProps) => {
|
|
|
138
148
|
VoiceMessageInput={props.VoiceMessageInput ?? VoiceMessageInput}
|
|
139
149
|
AttachmentsButton={props.AttachmentsButton ?? AttachmentsButton}
|
|
140
150
|
MessageToReplyPreview={props.MessageToReplyPreview ?? MessageToReplyPreview}
|
|
151
|
+
style={textInputStyle}
|
|
141
152
|
/>
|
|
142
153
|
)}
|
|
143
154
|
{inputMode === 'edit' && messageToEdit && (
|
|
@@ -152,6 +163,7 @@ const ChannelInput = (props: ChannelInputProps) => {
|
|
|
152
163
|
mentionedUsers={mentionedUsers}
|
|
153
164
|
messageToEdit={messageToEdit}
|
|
154
165
|
setMessageToEdit={setMessageToEdit}
|
|
166
|
+
style={textInputStyle}
|
|
155
167
|
/>
|
|
156
168
|
)}
|
|
157
169
|
</View>
|
|
@@ -211,6 +223,17 @@ const styles = createStyleSheet({
|
|
|
211
223
|
inputDefault: {
|
|
212
224
|
height: 56,
|
|
213
225
|
},
|
|
226
|
+
input: {
|
|
227
|
+
flex: 1,
|
|
228
|
+
marginRight: 4,
|
|
229
|
+
borderRadius: 20,
|
|
230
|
+
paddingTop: 8,
|
|
231
|
+
paddingBottom: 8,
|
|
232
|
+
minHeight: 36,
|
|
233
|
+
// Android - padding area is hidden
|
|
234
|
+
// iOS - padding area is visible
|
|
235
|
+
maxHeight: Platform.select({ ios: 36 * 2 + 16, android: 36 * 2 }),
|
|
236
|
+
},
|
|
214
237
|
});
|
|
215
238
|
|
|
216
239
|
export default React.memo(ChannelInput);
|
|
@@ -36,8 +36,9 @@ import type { CommonComponent } from '../../types';
|
|
|
36
36
|
import ChatFlatList from '../ChatFlatList';
|
|
37
37
|
import { ReactionAddons } from '../ReactionAddons';
|
|
38
38
|
|
|
39
|
-
type PressActions = { onPress?: () => void; onLongPress?: () => void };
|
|
39
|
+
type PressActions = { onPress?: () => void; onLongPress?: () => void; bottomSheetItem?: BottomSheetItem };
|
|
40
40
|
type HandleableMessage = SendbirdUserMessage | SendbirdFileMessage;
|
|
41
|
+
type CreateMessagePressActions = (params: { message: SendbirdMessage }) => PressActions;
|
|
41
42
|
export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpenChannel> = {
|
|
42
43
|
enableMessageGrouping: boolean;
|
|
43
44
|
currentUserId?: string;
|
|
@@ -58,7 +59,7 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
|
|
|
58
59
|
onEditMessage: (message: HandleableMessage) => void;
|
|
59
60
|
onReplyMessage?: (message: HandleableMessage) => void; // only available on group channel
|
|
60
61
|
onDeleteMessage: (message: HandleableMessage) => Promise<void>;
|
|
61
|
-
onResendFailedMessage: (failedMessage: HandleableMessage) => Promise<void>;
|
|
62
|
+
onResendFailedMessage: (failedMessage: HandleableMessage) => Promise<HandleableMessage | void>;
|
|
62
63
|
onPressParentMessage?: (parentMessage: SendbirdMessage) => void;
|
|
63
64
|
onPressMediaMessage?: (message: SendbirdFileMessage, deleteMessage: () => Promise<void>, uri: string) => void;
|
|
64
65
|
|
|
@@ -74,6 +75,8 @@ export type ChannelMessageListProps<T extends SendbirdGroupChannel | SendbirdOpe
|
|
|
74
75
|
channel: T;
|
|
75
76
|
currentUserId?: ChannelMessageListProps<T>['currentUserId'];
|
|
76
77
|
enableMessageGrouping: ChannelMessageListProps<T>['enableMessageGrouping'];
|
|
78
|
+
bottomSheetItem?: BottomSheetItem;
|
|
79
|
+
isFirstItem: boolean;
|
|
77
80
|
}) => React.ReactElement | null;
|
|
78
81
|
renderNewMessagesButton: null | CommonComponent<{
|
|
79
82
|
visible: boolean;
|
|
@@ -121,7 +124,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
|
|
|
121
124
|
const { colors } = useUIKitTheme();
|
|
122
125
|
const { show } = useUserProfile();
|
|
123
126
|
const { left, right } = useSafeAreaInsets();
|
|
124
|
-
const
|
|
127
|
+
const createMessagePressActions = useCreateMessagePressActions({
|
|
125
128
|
channel,
|
|
126
129
|
currentUserId,
|
|
127
130
|
onEditMessage,
|
|
@@ -134,7 +137,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
|
|
|
134
137
|
const safeAreaLayout = { paddingLeft: left, paddingRight: right };
|
|
135
138
|
|
|
136
139
|
const renderItem: ListRenderItem<SendbirdMessage> = useFreshCallback(({ item, index }) => {
|
|
137
|
-
const { onPress, onLongPress } =
|
|
140
|
+
const { onPress, onLongPress, bottomSheetItem } = createMessagePressActions({ message: item });
|
|
138
141
|
return renderMessage({
|
|
139
142
|
message: item,
|
|
140
143
|
prevMessage: messages[index + 1],
|
|
@@ -147,6 +150,8 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
|
|
|
147
150
|
channel,
|
|
148
151
|
currentUserId,
|
|
149
152
|
focused: (searchItem?.startingPoint ?? -1) === item.createdAt,
|
|
153
|
+
bottomSheetItem,
|
|
154
|
+
isFirstItem: index === 0,
|
|
150
155
|
});
|
|
151
156
|
});
|
|
152
157
|
|
|
@@ -191,7 +196,7 @@ const ChannelMessageList = <T extends SendbirdGroupChannel | SendbirdOpenChannel
|
|
|
191
196
|
);
|
|
192
197
|
};
|
|
193
198
|
|
|
194
|
-
const
|
|
199
|
+
const useCreateMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpenChannel>({
|
|
195
200
|
channel,
|
|
196
201
|
currentUserId,
|
|
197
202
|
onResendFailedMessage,
|
|
@@ -208,7 +213,7 @@ const useGetMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpen
|
|
|
208
213
|
| 'onDeleteMessage'
|
|
209
214
|
| 'onResendFailedMessage'
|
|
210
215
|
| 'onPressMediaMessage'
|
|
211
|
-
>) => {
|
|
216
|
+
>): CreateMessagePressActions => {
|
|
212
217
|
const { colors } = useUIKitTheme();
|
|
213
218
|
const { STRINGS } = useLocalization();
|
|
214
219
|
const toast = useToast();
|
|
@@ -217,161 +222,186 @@ const useGetMessagePressActions = <T extends SendbirdGroupChannel | SendbirdOpen
|
|
|
217
222
|
const { clipboardService, fileService } = usePlatformService();
|
|
218
223
|
const { sbOptions } = useSendbirdChat();
|
|
219
224
|
|
|
220
|
-
const
|
|
225
|
+
const onResendFailure = (error: Error) => {
|
|
221
226
|
toast.show(STRINGS.TOAST.RESEND_MSG_ERROR, 'error');
|
|
222
227
|
Logger.error(STRINGS.TOAST.RESEND_MSG_ERROR, error);
|
|
223
228
|
};
|
|
224
229
|
|
|
225
|
-
const
|
|
230
|
+
const onDeleteFailure = (error: Error) => {
|
|
231
|
+
toast.show(STRINGS.TOAST.DELETE_MSG_ERROR, 'error');
|
|
232
|
+
Logger.error(STRINGS.TOAST.DELETE_MSG_ERROR, error);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const onCopyText = (message: HandleableMessage) => {
|
|
236
|
+
if (message.isUserMessage()) {
|
|
237
|
+
clipboardService.setString(message.message || '');
|
|
238
|
+
toast.show(STRINGS.TOAST.COPY_OK, 'success');
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const onDownloadFile = (message: HandleableMessage) => {
|
|
243
|
+
if (message.isFileMessage()) {
|
|
244
|
+
if (toMegabyte(message.size) > 4) {
|
|
245
|
+
toast.show(STRINGS.TOAST.DOWNLOAD_START, 'success');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
fileService
|
|
249
|
+
.save({ fileUrl: message.url, fileName: message.name, fileType: message.type })
|
|
250
|
+
.then((response) => {
|
|
251
|
+
toast.show(STRINGS.TOAST.DOWNLOAD_OK, 'success');
|
|
252
|
+
Logger.log('File saved to', response);
|
|
253
|
+
})
|
|
254
|
+
.catch((err) => {
|
|
255
|
+
toast.show(STRINGS.TOAST.DOWNLOAD_ERROR, 'error');
|
|
256
|
+
Logger.log('File save failure', err);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const onOpenFile = (message: HandleableMessage) => {
|
|
262
|
+
if (message.isFileMessage()) {
|
|
263
|
+
const fileType = getFileType(message.type || getFileExtension(message.name));
|
|
264
|
+
if (['image', 'video', 'audio'].includes(fileType)) {
|
|
265
|
+
onPressMediaMessage?.(message, () => onDeleteMessage(message), getAvailableUriFromFileMessage(message));
|
|
266
|
+
} else {
|
|
267
|
+
SBUUtils.openURL(message.url);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const openSheetForFailedMessage = (message: HandleableMessage) => {
|
|
226
273
|
openSheet({
|
|
227
274
|
sheetItems: [
|
|
228
275
|
{
|
|
229
276
|
title: STRINGS.LABELS.CHANNEL_MESSAGE_FAILED_RETRY,
|
|
230
|
-
onPress: () =>
|
|
231
|
-
onResendFailedMessage(message).catch(onFailureToReSend);
|
|
232
|
-
},
|
|
277
|
+
onPress: () => onResendFailedMessage(message).catch(onResendFailure),
|
|
233
278
|
},
|
|
234
279
|
{
|
|
235
280
|
title: STRINGS.LABELS.CHANNEL_MESSAGE_FAILED_REMOVE,
|
|
236
281
|
titleColor: colors.ui.dialog.default.none.destructive,
|
|
237
|
-
onPress: () =>
|
|
282
|
+
onPress: () => alertForMessageDelete(message),
|
|
238
283
|
},
|
|
239
284
|
],
|
|
240
285
|
});
|
|
241
286
|
};
|
|
242
|
-
|
|
287
|
+
|
|
288
|
+
const alertForMessageDelete = (message: HandleableMessage) => {
|
|
243
289
|
alert({
|
|
244
290
|
title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_TITLE,
|
|
245
291
|
buttons: [
|
|
246
|
-
{
|
|
247
|
-
text: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_CANCEL,
|
|
248
|
-
},
|
|
292
|
+
{ text: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_CANCEL },
|
|
249
293
|
{
|
|
250
294
|
text: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_OK,
|
|
251
295
|
style: 'destructive',
|
|
252
296
|
onPress: () => {
|
|
253
|
-
onDeleteMessage(message).catch(
|
|
297
|
+
onDeleteMessage(message).catch(onDeleteFailure);
|
|
254
298
|
},
|
|
255
299
|
},
|
|
256
300
|
],
|
|
257
301
|
});
|
|
258
302
|
};
|
|
259
303
|
|
|
260
|
-
return (
|
|
261
|
-
if (!
|
|
262
|
-
return { onPress: undefined, onLongPress: undefined };
|
|
263
|
-
}
|
|
304
|
+
return ({ message }) => {
|
|
305
|
+
if (!message.isUserMessage() && !message.isFileMessage()) return {};
|
|
264
306
|
|
|
265
307
|
const sheetItems: BottomSheetItem['sheetItems'] = [];
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
if (msg.isUserMessage()) {
|
|
272
|
-
sheetItems.push({
|
|
273
|
-
icon: 'copy',
|
|
308
|
+
const menu = {
|
|
309
|
+
copy: (message: HandleableMessage) => ({
|
|
310
|
+
icon: 'copy' as const,
|
|
274
311
|
title: STRINGS.LABELS.CHANNEL_MESSAGE_COPY,
|
|
275
|
-
onPress: () =>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
312
|
+
onPress: () => onCopyText(message),
|
|
313
|
+
}),
|
|
314
|
+
edit: (message: HandleableMessage) => ({
|
|
315
|
+
icon: 'edit' as const,
|
|
316
|
+
title: STRINGS.LABELS.CHANNEL_MESSAGE_EDIT,
|
|
317
|
+
onPress: () => onEditMessage(message),
|
|
318
|
+
}),
|
|
319
|
+
delete: (message: HandleableMessage) => ({
|
|
320
|
+
disabled: message.threadInfo ? message.threadInfo.replyCount > 0 : undefined,
|
|
321
|
+
icon: 'delete' as const,
|
|
322
|
+
title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE,
|
|
323
|
+
onPress: () => alertForMessageDelete(message),
|
|
324
|
+
}),
|
|
325
|
+
reply: (message: HandleableMessage) => ({
|
|
326
|
+
disabled: Boolean(message.parentMessageId),
|
|
327
|
+
icon: 'reply' as const,
|
|
328
|
+
title: STRINGS.LABELS.CHANNEL_MESSAGE_REPLY,
|
|
329
|
+
onPress: () => onReplyMessage?.(message),
|
|
330
|
+
}),
|
|
331
|
+
download: (message: HandleableMessage) => ({
|
|
332
|
+
icon: 'download' as const,
|
|
284
333
|
title: STRINGS.LABELS.CHANNEL_MESSAGE_SAVE,
|
|
285
|
-
onPress:
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
fileService
|
|
291
|
-
.save({ fileUrl: msg.url, fileName: msg.name, fileType: msg.type })
|
|
292
|
-
.then((response) => {
|
|
293
|
-
toast.show(STRINGS.TOAST.DOWNLOAD_OK, 'success');
|
|
294
|
-
Logger.log('File saved to', response);
|
|
295
|
-
})
|
|
296
|
-
.catch((err) => {
|
|
297
|
-
toast.show(STRINGS.TOAST.DOWNLOAD_ERROR, 'error');
|
|
298
|
-
Logger.log('File save failure', err);
|
|
299
|
-
});
|
|
300
|
-
},
|
|
301
|
-
});
|
|
302
|
-
}
|
|
334
|
+
onPress: () => onDownloadFile(message),
|
|
335
|
+
}),
|
|
336
|
+
};
|
|
303
337
|
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
338
|
+
if (message.isUserMessage()) {
|
|
339
|
+
sheetItems.push(menu.copy(message));
|
|
340
|
+
if (!channel.isEphemeral) {
|
|
341
|
+
if (isMyMessage(message, currentUserId) && message.sendingStatus === 'succeeded') {
|
|
342
|
+
sheetItems.push(menu.edit(message));
|
|
343
|
+
sheetItems.push(menu.delete(message));
|
|
344
|
+
}
|
|
345
|
+
if (channel.isGroupChannel() && sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
|
|
346
|
+
sheetItems.push(menu.reply(message));
|
|
312
347
|
}
|
|
313
|
-
sheetItems.push({
|
|
314
|
-
disabled: msg.threadInfo ? msg.threadInfo.replyCount > 0 : undefined,
|
|
315
|
-
icon: 'delete',
|
|
316
|
-
title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE,
|
|
317
|
-
onPress: () => confirmDelete(msg),
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
if (channel.isGroupChannel() && sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
|
|
321
|
-
sheetItems.push({
|
|
322
|
-
disabled: Boolean(msg.parentMessageId),
|
|
323
|
-
icon: 'reply',
|
|
324
|
-
title: STRINGS.LABELS.CHANNEL_MESSAGE_REPLY,
|
|
325
|
-
onPress: () => onReplyMessage?.(msg),
|
|
326
|
-
});
|
|
327
348
|
}
|
|
328
349
|
}
|
|
329
350
|
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
onPressMediaMessage?.(msg, () => onDeleteMessage(msg), getAvailableUriFromFileMessage(msg));
|
|
338
|
-
};
|
|
339
|
-
break;
|
|
351
|
+
if (message.isFileMessage()) {
|
|
352
|
+
if (!isVoiceMessage(message)) {
|
|
353
|
+
sheetItems.push(menu.download(message));
|
|
354
|
+
}
|
|
355
|
+
if (!channel.isEphemeral) {
|
|
356
|
+
if (isMyMessage(message, currentUserId) && message.sendingStatus === 'succeeded') {
|
|
357
|
+
sheetItems.push(menu.delete(message));
|
|
340
358
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
break;
|
|
359
|
+
if (channel.isGroupChannel() && sbOptions.uikit.groupChannel.channel.replyType === 'quote_reply') {
|
|
360
|
+
sheetItems.push(menu.reply(message));
|
|
344
361
|
}
|
|
345
362
|
}
|
|
346
363
|
}
|
|
347
364
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
sbOptions.uikitWithAppInfo.groupChannel.channel.enableReactions,
|
|
355
|
-
)
|
|
356
|
-
? ({ onClose }) => <ReactionAddons.BottomSheet message={msg} channel={channel} onClose={onClose} />
|
|
357
|
-
: undefined,
|
|
358
|
-
});
|
|
359
|
-
};
|
|
360
|
-
}
|
|
365
|
+
const bottomSheetItem: BottomSheetItem = {
|
|
366
|
+
sheetItems,
|
|
367
|
+
HeaderComponent: shouldRenderReaction(channel, sbOptions.uikitWithAppInfo.groupChannel.channel.enableReactions)
|
|
368
|
+
? ({ onClose }) => <ReactionAddons.BottomSheet message={message} channel={channel} onClose={onClose} />
|
|
369
|
+
: undefined,
|
|
370
|
+
};
|
|
361
371
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
372
|
+
switch (true) {
|
|
373
|
+
case message.sendingStatus === 'pending': {
|
|
374
|
+
return {
|
|
375
|
+
onPress: undefined,
|
|
376
|
+
onLongPress: undefined,
|
|
377
|
+
bottomSheetItem: undefined,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
368
380
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
381
|
+
case message.sendingStatus === 'failed': {
|
|
382
|
+
return {
|
|
383
|
+
onPress: () => onResendFailedMessage(message).catch(onResendFailure),
|
|
384
|
+
onLongPress: () => openSheetForFailedMessage(message),
|
|
385
|
+
bottomSheetItem,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
case message.isFileMessage(): {
|
|
390
|
+
return {
|
|
391
|
+
onPress: () => onOpenFile(message),
|
|
392
|
+
onLongPress: () => openSheet(bottomSheetItem),
|
|
393
|
+
bottomSheetItem,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
373
396
|
|
|
374
|
-
|
|
397
|
+
default: {
|
|
398
|
+
return {
|
|
399
|
+
onPress: undefined,
|
|
400
|
+
onLongPress: () => openSheet(bottomSheetItem),
|
|
401
|
+
bottomSheetItem,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
375
405
|
};
|
|
376
406
|
};
|
|
377
407
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { ReactNativeZoomableView, ReactNativeZoomableViewProps } from '@openspacelabs/react-native-zoomable-view';
|
|
2
|
+
import React, { useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { ImageProps, ImageStyle, ImageURISource, StyleProp, StyleSheet, useWindowDimensions } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Box,
|
|
7
|
+
Image,
|
|
8
|
+
LoadingSpinner,
|
|
9
|
+
createStyleSheet,
|
|
10
|
+
useHeaderStyle,
|
|
11
|
+
useUIKitTheme,
|
|
12
|
+
} from '@sendbird/uikit-react-native-foundation';
|
|
13
|
+
import { FileType, useIIFE } from '@sendbird/uikit-utils';
|
|
14
|
+
|
|
15
|
+
import { usePlatformService } from '../../hooks/useContext';
|
|
16
|
+
import SBUUtils from '../../libs/SBUUtils';
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
type: FileType;
|
|
20
|
+
src: string;
|
|
21
|
+
topInset?: number;
|
|
22
|
+
bottomInset?: number;
|
|
23
|
+
maxZoom?: number;
|
|
24
|
+
minZoom?: number;
|
|
25
|
+
onPress?: () => void;
|
|
26
|
+
};
|
|
27
|
+
const FileViewerContent = ({ type, src, topInset = 0, bottomInset = 0, maxZoom = 4, minZoom = 1, onPress }: Props) => {
|
|
28
|
+
const [loading, setLoading] = useState(true);
|
|
29
|
+
|
|
30
|
+
const { defaultHeight } = useHeaderStyle();
|
|
31
|
+
const { mediaService } = usePlatformService();
|
|
32
|
+
const { palette } = useUIKitTheme();
|
|
33
|
+
|
|
34
|
+
const source = { uri: src };
|
|
35
|
+
const onLoadEnd = () => setLoading(false);
|
|
36
|
+
const mediaViewer = useIIFE(() => {
|
|
37
|
+
switch (type) {
|
|
38
|
+
case 'image': {
|
|
39
|
+
return (
|
|
40
|
+
<ZoomableImageView
|
|
41
|
+
source={source}
|
|
42
|
+
style={StyleSheet.absoluteFill}
|
|
43
|
+
resizeMode={'contain'}
|
|
44
|
+
onLoadEnd={onLoadEnd}
|
|
45
|
+
zoomProps={{
|
|
46
|
+
minZoom,
|
|
47
|
+
maxZoom,
|
|
48
|
+
onTouchEnd: onPress,
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
case 'video':
|
|
55
|
+
case 'audio': {
|
|
56
|
+
return (
|
|
57
|
+
<mediaService.VideoComponent
|
|
58
|
+
source={source}
|
|
59
|
+
style={[StyleSheet.absoluteFill, { top: topInset, bottom: defaultHeight + bottomInset }]}
|
|
60
|
+
resizeMode={'contain'}
|
|
61
|
+
onLoad={onLoadEnd}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
default: {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Box style={styles.container}>
|
|
74
|
+
{mediaViewer}
|
|
75
|
+
{loading && <LoadingSpinner style={{ position: 'absolute' }} size={40} color={palette.primary300} />}
|
|
76
|
+
</Box>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const ZoomableImageView = ({
|
|
81
|
+
zoomProps,
|
|
82
|
+
...props
|
|
83
|
+
}: {
|
|
84
|
+
source: ImageURISource;
|
|
85
|
+
style: StyleProp<ImageStyle>;
|
|
86
|
+
resizeMode: ImageProps['resizeMode'];
|
|
87
|
+
onLoadEnd: () => void;
|
|
88
|
+
zoomProps?: ReactNativeZoomableViewProps;
|
|
89
|
+
}) => {
|
|
90
|
+
const { width, height } = useWindowDimensions();
|
|
91
|
+
|
|
92
|
+
const imageSize = useRef<{ width: number; height: number }>();
|
|
93
|
+
const [contentSizeProps, setContentSizeProps] = useState<ReactNativeZoomableViewProps>({
|
|
94
|
+
contentWidth: width,
|
|
95
|
+
contentHeight: height,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
useLayoutEffect(() => {
|
|
99
|
+
SBUUtils.safeRun(async () => {
|
|
100
|
+
if (props.source.uri) {
|
|
101
|
+
const image = imageSize.current ?? (await SBUUtils.getImageSize(props.source.uri));
|
|
102
|
+
imageSize.current = image;
|
|
103
|
+
|
|
104
|
+
const viewRatio = width / height;
|
|
105
|
+
const imageRatio = image.width / image.height;
|
|
106
|
+
|
|
107
|
+
const fitDirection = viewRatio > imageRatio ? 'height' : 'width';
|
|
108
|
+
const ratio = fitDirection === 'height' ? height / image.height : width / image.width;
|
|
109
|
+
const actualSize = { width: image.width * ratio, height: image.height * ratio };
|
|
110
|
+
|
|
111
|
+
setContentSizeProps({
|
|
112
|
+
contentWidth: actualSize.width,
|
|
113
|
+
contentHeight: actualSize.height,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}, [props.source.uri, width, height]);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<ReactNativeZoomableView
|
|
121
|
+
visualTouchFeedbackEnabled={false}
|
|
122
|
+
style={{ width, height }}
|
|
123
|
+
initialZoom={1}
|
|
124
|
+
{...contentSizeProps}
|
|
125
|
+
{...zoomProps}
|
|
126
|
+
>
|
|
127
|
+
<Image {...props} />
|
|
128
|
+
</ReactNativeZoomableView>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const styles = createStyleSheet({
|
|
133
|
+
container: {
|
|
134
|
+
zIndex: -1,
|
|
135
|
+
flex: 1,
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
justifyContent: 'center',
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export default FileViewerContent;
|