@sendbird/uikit-react-native 1.1.1 → 1.1.3
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/FileViewer.js +8 -3
- package/lib/commonjs/components/FileViewer.js.map +1 -1
- package/lib/commonjs/components/MessageRenderer/FileMessage/ImageFileMessage.js +47 -15
- package/lib/commonjs/components/MessageRenderer/FileMessage/ImageFileMessage.js.map +1 -1
- package/lib/commonjs/components/MessageRenderer/FileMessage/VideoFileMessage.js +51 -34
- package/lib/commonjs/components/MessageRenderer/FileMessage/VideoFileMessage.js.map +1 -1
- package/lib/commonjs/components/MessageRenderer/MessageIncomingSenderName.js +2 -1
- package/lib/commonjs/components/MessageRenderer/MessageIncomingSenderName.js.map +1 -1
- package/lib/commonjs/components/MessageRenderer/index.js +7 -2
- package/lib/commonjs/components/MessageRenderer/index.js.map +1 -1
- package/lib/commonjs/contexts/SendbirdChat.js +1 -1
- package/lib/commonjs/contexts/SendbirdChat.js.map +1 -1
- package/lib/commonjs/domain/groupChannel/component/GroupChannelInput/SendInput.js +39 -6
- package/lib/commonjs/domain/groupChannel/component/GroupChannelInput/SendInput.js.map +1 -1
- package/lib/commonjs/domain/groupChannelSettings/component/GroupChannelSettingsInfo.js +1 -1
- package/lib/commonjs/domain/groupChannelSettings/component/GroupChannelSettingsInfo.js.map +1 -1
- package/lib/commonjs/domain/groupChannelSettings/module/moduleContext.js +35 -2
- package/lib/commonjs/domain/groupChannelSettings/module/moduleContext.js.map +1 -1
- package/lib/commonjs/fragments/createGroupChannelListFragment.js +1 -1
- package/lib/commonjs/fragments/createGroupChannelListFragment.js.map +1 -1
- package/lib/commonjs/libs/SBUError.js +41 -0
- package/lib/commonjs/libs/SBUError.js.map +1 -0
- package/lib/commonjs/libs/SBUUtils.js +20 -0
- package/lib/commonjs/libs/SBUUtils.js.map +1 -0
- package/lib/commonjs/localization/StringSet.type.js +6 -0
- package/lib/commonjs/localization/StringSet.type.js.map +1 -1
- package/lib/commonjs/platform/createFileService.expo.js +6 -4
- package/lib/commonjs/platform/createFileService.expo.js.map +1 -1
- package/lib/commonjs/platform/createFileService.native.js +20 -8
- package/lib/commonjs/platform/createFileService.native.js.map +1 -1
- package/lib/commonjs/platform/types.js +4 -0
- package/lib/commonjs/platform/types.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/commonjs/version.js.map +1 -1
- package/lib/module/components/FileViewer.js +9 -4
- package/lib/module/components/FileViewer.js.map +1 -1
- package/lib/module/components/MessageRenderer/FileMessage/ImageFileMessage.js +48 -17
- package/lib/module/components/MessageRenderer/FileMessage/ImageFileMessage.js.map +1 -1
- package/lib/module/components/MessageRenderer/FileMessage/VideoFileMessage.js +52 -35
- package/lib/module/components/MessageRenderer/FileMessage/VideoFileMessage.js.map +1 -1
- package/lib/module/components/MessageRenderer/MessageIncomingSenderName.js +2 -1
- package/lib/module/components/MessageRenderer/MessageIncomingSenderName.js.map +1 -1
- package/lib/module/components/MessageRenderer/index.js +7 -2
- package/lib/module/components/MessageRenderer/index.js.map +1 -1
- package/lib/module/contexts/SendbirdChat.js +1 -1
- package/lib/module/contexts/SendbirdChat.js.map +1 -1
- package/lib/module/domain/groupChannel/component/GroupChannelInput/SendInput.js +38 -7
- package/lib/module/domain/groupChannel/component/GroupChannelInput/SendInput.js.map +1 -1
- package/lib/module/domain/groupChannelSettings/component/GroupChannelSettingsInfo.js +1 -1
- package/lib/module/domain/groupChannelSettings/component/GroupChannelSettingsInfo.js.map +1 -1
- package/lib/module/domain/groupChannelSettings/module/moduleContext.js +34 -3
- package/lib/module/domain/groupChannelSettings/module/moduleContext.js.map +1 -1
- package/lib/module/fragments/createGroupChannelListFragment.js +1 -1
- package/lib/module/fragments/createGroupChannelListFragment.js.map +1 -1
- package/lib/module/libs/SBUError.js +32 -0
- package/lib/module/libs/SBUError.js.map +1 -0
- package/lib/module/libs/SBUUtils.js +10 -0
- package/lib/module/libs/SBUUtils.js.map +1 -0
- package/lib/module/localization/StringSet.type.js +6 -0
- package/lib/module/localization/StringSet.type.js.map +1 -1
- package/lib/module/platform/createFileService.expo.js +5 -4
- package/lib/module/platform/createFileService.expo.js.map +1 -1
- package/lib/module/platform/createFileService.native.js +18 -8
- package/lib/module/platform/createFileService.native.js.map +1 -1
- package/lib/module/platform/types.js +1 -1
- package/lib/module/platform/types.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/module/version.js.map +1 -1
- package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +1 -1
- package/lib/typescript/src/libs/SBUError.d.ts +14 -0
- package/lib/typescript/src/libs/SBUUtils.d.ts +3 -0
- package/lib/typescript/src/localization/StringSet.type.d.ts +3 -0
- package/lib/typescript/src/platform/types.d.ts +2 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +5 -5
- package/src/components/FileViewer.tsx +12 -4
- package/src/components/MessageRenderer/FileMessage/ImageFileMessage.tsx +55 -12
- package/src/components/MessageRenderer/FileMessage/VideoFileMessage.tsx +38 -30
- package/src/components/MessageRenderer/MessageIncomingSenderName.tsx +1 -1
- package/src/components/MessageRenderer/index.tsx +5 -2
- package/src/contexts/SendbirdChat.tsx +1 -1
- package/src/domain/groupChannel/component/GroupChannelInput/SendInput.tsx +28 -4
- package/src/domain/groupChannelSettings/component/GroupChannelSettingsInfo.tsx +1 -1
- package/src/domain/groupChannelSettings/module/moduleContext.tsx +26 -3
- package/src/fragments/createGroupChannelListFragment.tsx +1 -1
- package/src/libs/SBUError.ts +26 -0
- package/src/libs/SBUUtils.ts +9 -0
- package/src/localization/StringSet.type.ts +10 -0
- package/src/platform/createFileService.expo.ts +5 -4
- package/src/platform/createFileService.native.ts +17 -8
- package/src/platform/types.ts +3 -1
- package/src/version.ts +1 -1
|
@@ -1,10 +1,39 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Platform, StyleSheet, View } from 'react-native';
|
|
2
3
|
|
|
3
4
|
import { Icon, Image, createStyleSheet, useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
|
|
4
|
-
import { getAvailableUriFromFileMessage } from '@sendbird/uikit-utils';
|
|
5
|
+
import { getAvailableUriFromFileMessage, useForceUpdate } from '@sendbird/uikit-utils';
|
|
5
6
|
|
|
6
7
|
import type { FileMessageProps } from './index';
|
|
7
8
|
|
|
9
|
+
const useRetry = (hasError: boolean, retryCount = 5) => {
|
|
10
|
+
if (Platform.OS === 'android') return '';
|
|
11
|
+
|
|
12
|
+
const forceUpdate = useForceUpdate();
|
|
13
|
+
const retryCountRef = useRef(1);
|
|
14
|
+
const retryTimeoutRef = useRef<NodeJS.Timeout>();
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (hasError) {
|
|
18
|
+
const reloadReservation = () => {
|
|
19
|
+
if (retryCountRef.current < retryCount) {
|
|
20
|
+
retryTimeoutRef.current = setTimeout(() => {
|
|
21
|
+
retryCountRef.current++;
|
|
22
|
+
reloadReservation();
|
|
23
|
+
forceUpdate();
|
|
24
|
+
}, retryCountRef.current * 5000);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return reloadReservation();
|
|
29
|
+
} else {
|
|
30
|
+
return clearTimeout(retryTimeoutRef.current);
|
|
31
|
+
}
|
|
32
|
+
}, [hasError]);
|
|
33
|
+
|
|
34
|
+
return retryCountRef.current;
|
|
35
|
+
};
|
|
36
|
+
|
|
8
37
|
const ImageFileMessage = ({ message }: FileMessageProps) => {
|
|
9
38
|
const { colors } = useUIKitTheme();
|
|
10
39
|
const [imageNotFound, setImageNotFound] = useState(false);
|
|
@@ -12,18 +41,28 @@ const ImageFileMessage = ({ message }: FileMessageProps) => {
|
|
|
12
41
|
const fileUrl = getAvailableUriFromFileMessage(message);
|
|
13
42
|
const style = [styles.image, { backgroundColor: colors.onBackground04 }];
|
|
14
43
|
|
|
15
|
-
|
|
16
|
-
return <Icon containerStyle={style} icon={'thumbnail-none'} size={48} color={colors.onBackground02} />;
|
|
17
|
-
}
|
|
44
|
+
const key = useRetry(imageNotFound);
|
|
18
45
|
|
|
19
46
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
47
|
+
<View style={style}>
|
|
48
|
+
<Image
|
|
49
|
+
key={key}
|
|
50
|
+
source={{ uri: fileUrl }}
|
|
51
|
+
style={[StyleSheet.absoluteFill, imageNotFound && styles.hide]}
|
|
52
|
+
resizeMode={'cover'}
|
|
53
|
+
resizeMethod={'resize'}
|
|
54
|
+
onError={() => setImageNotFound(true)}
|
|
55
|
+
onLoad={() => setImageNotFound(false)}
|
|
56
|
+
/>
|
|
57
|
+
{imageNotFound && (
|
|
58
|
+
<Icon
|
|
59
|
+
containerStyle={StyleSheet.absoluteFill}
|
|
60
|
+
icon={'thumbnail-none'}
|
|
61
|
+
size={48}
|
|
62
|
+
color={colors.onBackground02}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
</View>
|
|
27
66
|
);
|
|
28
67
|
};
|
|
29
68
|
|
|
@@ -33,6 +72,10 @@ const styles = createStyleSheet({
|
|
|
33
72
|
maxWidth: 240,
|
|
34
73
|
height: 160,
|
|
35
74
|
borderRadius: 16,
|
|
75
|
+
overflow: 'hidden',
|
|
76
|
+
},
|
|
77
|
+
hide: {
|
|
78
|
+
display: 'none',
|
|
36
79
|
},
|
|
37
80
|
});
|
|
38
81
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import { Icon, Image, createStyleSheet, useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
|
|
@@ -7,35 +7,49 @@ import { getAvailableUriFromFileMessage } from '@sendbird/uikit-utils';
|
|
|
7
7
|
import { usePlatformService } from '../../../hooks/useContext';
|
|
8
8
|
import type { FileMessageProps } from './index';
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const {
|
|
10
|
+
const useRetry = (videoFileUrl: string, retryCount = 5) => {
|
|
11
|
+
const [state, setState] = useState({ thumbnail: null as null | string, loading: true });
|
|
12
|
+
const retryCountRef = useRef(0);
|
|
13
|
+
const retryTimeoutRef = useRef<NodeJS.Timeout>();
|
|
12
14
|
|
|
13
15
|
const { mediaService } = usePlatformService();
|
|
14
|
-
const fileUrl = getAvailableUriFromFileMessage(message);
|
|
15
|
-
const style = [styles.image, { backgroundColor: colors.onBackground04 }];
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
17
|
+
const fetchThumbnail = () => {
|
|
18
|
+
return mediaService?.getVideoThumbnail({ url: videoFileUrl, timeMills: 1000 }).then((result) => {
|
|
19
|
+
setState({ loading: false, thumbnail: result?.path ?? null });
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
useEffect(() => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
if (!state.thumbnail) {
|
|
25
|
+
const reloadReservation = () => {
|
|
26
|
+
if (retryCountRef.current < retryCount) {
|
|
27
|
+
retryTimeoutRef.current = setTimeout(() => {
|
|
28
|
+
retryCountRef.current++;
|
|
29
|
+
reloadReservation();
|
|
30
|
+
fetchThumbnail();
|
|
31
|
+
}, retryCountRef.current * 5000);
|
|
31
32
|
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return reloadReservation();
|
|
36
|
+
} else {
|
|
37
|
+
return clearTimeout(retryTimeoutRef.current);
|
|
38
|
+
}
|
|
39
|
+
}, [state.thumbnail]);
|
|
40
|
+
|
|
41
|
+
return state;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const VideoFileMessage = ({ message }: FileMessageProps) => {
|
|
45
|
+
const { colors } = useUIKitTheme();
|
|
46
|
+
|
|
47
|
+
const fileUrl = getAvailableUriFromFileMessage(message);
|
|
48
|
+
const style = [styles.image, { backgroundColor: colors.onBackground04 }];
|
|
49
|
+
|
|
50
|
+
const { loading, thumbnail } = useRetry(fileUrl);
|
|
37
51
|
|
|
38
|
-
if (
|
|
52
|
+
if (loading) {
|
|
39
53
|
return (
|
|
40
54
|
<View style={[style, styles.container]}>
|
|
41
55
|
<PlayIcon />
|
|
@@ -45,13 +59,7 @@ const VideoFileMessage = ({ message }: FileMessageProps) => {
|
|
|
45
59
|
|
|
46
60
|
return (
|
|
47
61
|
<View style={styles.container}>
|
|
48
|
-
<Image
|
|
49
|
-
source={{ uri: state.thumbnail || fileUrl }}
|
|
50
|
-
style={style}
|
|
51
|
-
resizeMode={'cover'}
|
|
52
|
-
resizeMethod={'resize'}
|
|
53
|
-
onError={() => setState((prev) => ({ ...prev, imageNotFound: true }))}
|
|
54
|
-
/>
|
|
62
|
+
<Image source={{ uri: thumbnail || fileUrl }} style={style} resizeMode={'cover'} resizeMethod={'resize'} />
|
|
55
63
|
<PlayIcon />
|
|
56
64
|
</View>
|
|
57
65
|
);
|
|
@@ -18,7 +18,7 @@ const MessageIncomingSenderName = ({ message, grouping }: Props) => {
|
|
|
18
18
|
return (
|
|
19
19
|
<View style={styles.sender}>
|
|
20
20
|
{(message.isFileMessage() || message.isUserMessage()) && (
|
|
21
|
-
<Text caption1 color={colors.ui.message.incoming.enabled.textSenderName}>
|
|
21
|
+
<Text caption1 color={colors.ui.message.incoming.enabled.textSenderName} numberOfLines={1}>
|
|
22
22
|
{message.sender?.nickname || STRINGS.LABELS.USER_NO_NAME}
|
|
23
23
|
</Text>
|
|
24
24
|
)}
|
|
@@ -107,9 +107,9 @@ const MessageRenderer: GroupChannelProps['Fragment']['renderMessage'] = ({
|
|
|
107
107
|
</View>
|
|
108
108
|
)}
|
|
109
109
|
{isIncoming && <MessageIncomingAvatar message={message} grouping={groupWithNext} />}
|
|
110
|
-
<View>
|
|
110
|
+
<View style={styles.bubbleContainer}>
|
|
111
111
|
{isIncoming && <MessageIncomingSenderName message={message} grouping={groupWithPrev} />}
|
|
112
|
-
<View style={styles.
|
|
112
|
+
<View style={styles.bubbleWrapper}>
|
|
113
113
|
{messageComponent}
|
|
114
114
|
{isIncoming && <MessageTime message={message} grouping={groupWithNext} style={styles.timeIncoming} />}
|
|
115
115
|
</View>
|
|
@@ -150,6 +150,9 @@ const styles = createStyleSheet({
|
|
|
150
150
|
maxWidth: 240,
|
|
151
151
|
},
|
|
152
152
|
bubbleContainer: {
|
|
153
|
+
flexShrink: 1,
|
|
154
|
+
},
|
|
155
|
+
bubbleWrapper: {
|
|
153
156
|
flexDirection: 'row',
|
|
154
157
|
alignItems: 'flex-end',
|
|
155
158
|
},
|
|
@@ -91,7 +91,7 @@ export const SendbirdChatProvider = ({
|
|
|
91
91
|
const listener = (status: AppStateStatus) => {
|
|
92
92
|
// 'active' | 'background' | 'inactive' | 'unknown' | 'extension';
|
|
93
93
|
if (status === 'active') sdkInstance.getConnectionState() === 'CLOSED' && sdkInstance.setForegroundState();
|
|
94
|
-
else sdkInstance.getConnectionState() === 'OPEN' && sdkInstance.setBackgroundState();
|
|
94
|
+
else if (status === 'background') sdkInstance.getConnectionState() === 'OPEN' && sdkInstance.setBackgroundState();
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
const subscriber = AppState.addEventListener('change', listener);
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
Icon,
|
|
6
6
|
TextInput,
|
|
7
7
|
createStyleSheet,
|
|
8
|
+
useAlert,
|
|
8
9
|
useBottomSheet,
|
|
9
10
|
useToast,
|
|
10
11
|
useUIKitTheme,
|
|
@@ -12,6 +13,8 @@ import {
|
|
|
12
13
|
import { conditionChaining } from '@sendbird/uikit-utils';
|
|
13
14
|
|
|
14
15
|
import { useLocalization, usePlatformService } from '../../../../hooks/useContext';
|
|
16
|
+
import SBUError from '../../../../libs/SBUError';
|
|
17
|
+
import SBUUtils from '../../../../libs/SBUUtils';
|
|
15
18
|
import type { GroupChannelProps } from '../../types';
|
|
16
19
|
|
|
17
20
|
type SendInputProps = GroupChannelProps['Input'] & {
|
|
@@ -21,9 +24,10 @@ type SendInputProps = GroupChannelProps['Input'] & {
|
|
|
21
24
|
};
|
|
22
25
|
const SendInput = ({ onSendUserMessage, onSendFileMessage, text, setText, disabled }: SendInputProps) => {
|
|
23
26
|
const { STRINGS } = useLocalization();
|
|
24
|
-
const { openSheet } = useBottomSheet();
|
|
25
27
|
const { fileService } = usePlatformService();
|
|
26
28
|
const { colors } = useUIKitTheme();
|
|
29
|
+
const { openSheet } = useBottomSheet();
|
|
30
|
+
const { alert } = useAlert();
|
|
27
31
|
const toast = useToast();
|
|
28
32
|
|
|
29
33
|
const onPressSend = () => {
|
|
@@ -39,7 +43,17 @@ const SendInput = ({ onSendUserMessage, onSendFileMessage, text, setText, disabl
|
|
|
39
43
|
onPress: async () => {
|
|
40
44
|
const photo = await fileService.openCamera({
|
|
41
45
|
mediaType: 'all',
|
|
42
|
-
|
|
46
|
+
onOpenFailure: (error) => {
|
|
47
|
+
if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
|
|
48
|
+
alert({
|
|
49
|
+
title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
|
|
50
|
+
message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('camera', 'UIKitSample'),
|
|
51
|
+
buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
toast.show(STRINGS.TOAST.OPEN_CAMERA_ERROR, 'error');
|
|
55
|
+
}
|
|
56
|
+
},
|
|
43
57
|
});
|
|
44
58
|
|
|
45
59
|
if (photo) {
|
|
@@ -54,7 +68,17 @@ const SendInput = ({ onSendUserMessage, onSendFileMessage, text, setText, disabl
|
|
|
54
68
|
const photo = await fileService.openMediaLibrary({
|
|
55
69
|
selectionLimit: 1,
|
|
56
70
|
mediaType: 'all',
|
|
57
|
-
|
|
71
|
+
onOpenFailure: (error) => {
|
|
72
|
+
if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
|
|
73
|
+
alert({
|
|
74
|
+
title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
|
|
75
|
+
message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('device storage', 'UIKitSample'),
|
|
76
|
+
buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
toast.show(STRINGS.TOAST.OPEN_PHOTO_LIBRARY_ERROR, 'error');
|
|
80
|
+
}
|
|
81
|
+
},
|
|
58
82
|
});
|
|
59
83
|
|
|
60
84
|
if (photo && photo[0]) {
|
|
@@ -67,7 +91,7 @@ const SendInput = ({ onSendUserMessage, onSendFileMessage, text, setText, disabl
|
|
|
67
91
|
icon: 'document',
|
|
68
92
|
onPress: async () => {
|
|
69
93
|
const file = await fileService.openDocument({
|
|
70
|
-
|
|
94
|
+
onOpenFailure: () => toast.show(STRINGS.TOAST.OPEN_FILES_ERROR, 'error'),
|
|
71
95
|
});
|
|
72
96
|
|
|
73
97
|
if (file) {
|
|
@@ -15,7 +15,7 @@ const GroupChannelSettingsInfo = (_: GroupChannelSettingsProps['Info']) => {
|
|
|
15
15
|
const { STRINGS } = useLocalization();
|
|
16
16
|
|
|
17
17
|
if (!currentUser) {
|
|
18
|
-
Logger.warn('Cannot render GroupChannelSettingsInfo,
|
|
18
|
+
Logger.warn('Cannot render GroupChannelSettingsInfo, please connect using `useConnection()` hook first');
|
|
19
19
|
return null;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { createContext, useCallback } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useActiveGroupChannel, useChannelHandler } from '@sendbird/uikit-chat-hooks';
|
|
4
|
-
import { useActionMenu, useBottomSheet, usePrompt, useToast } from '@sendbird/uikit-react-native-foundation';
|
|
4
|
+
import { useActionMenu, useAlert, useBottomSheet, usePrompt, useToast } from '@sendbird/uikit-react-native-foundation';
|
|
5
5
|
import {
|
|
6
6
|
NOOP,
|
|
7
7
|
SendbirdGroupChannel,
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
|
|
15
15
|
import ProviderLayout from '../../../components/ProviderLayout';
|
|
16
16
|
import { useLocalization, usePlatformService, useSendbirdChat } from '../../../hooks/useContext';
|
|
17
|
+
import SBUError from '../../../libs/SBUError';
|
|
18
|
+
import SBUUtils from '../../../libs/SBUUtils';
|
|
17
19
|
import type { GroupChannelSettingsContextsType, GroupChannelSettingsModule } from '../types';
|
|
18
20
|
|
|
19
21
|
export const GroupChannelSettingsContexts: GroupChannelSettingsContextsType = {
|
|
@@ -32,6 +34,7 @@ export const GroupChannelSettingsContextsProvider: GroupChannelSettingsModule['P
|
|
|
32
34
|
const { STRINGS } = useLocalization();
|
|
33
35
|
const { sdk } = useSendbirdChat();
|
|
34
36
|
const { fileService } = usePlatformService();
|
|
37
|
+
const { alert } = useAlert();
|
|
35
38
|
|
|
36
39
|
const { activeChannel, setActiveChannel } = useActiveGroupChannel(sdk, channel);
|
|
37
40
|
|
|
@@ -84,7 +87,17 @@ export const GroupChannelSettingsContextsProvider: GroupChannelSettingsModule['P
|
|
|
84
87
|
onPress: async () => {
|
|
85
88
|
const file = await fileService.openCamera({
|
|
86
89
|
mediaType: 'photo',
|
|
87
|
-
|
|
90
|
+
onOpenFailure: (error) => {
|
|
91
|
+
if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
|
|
92
|
+
alert({
|
|
93
|
+
title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
|
|
94
|
+
message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('camera', 'UIKitSample'),
|
|
95
|
+
buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
toast.show(STRINGS.TOAST.OPEN_CAMERA_ERROR, 'error');
|
|
99
|
+
}
|
|
100
|
+
},
|
|
88
101
|
});
|
|
89
102
|
if (!file) return;
|
|
90
103
|
|
|
@@ -99,7 +112,17 @@ export const GroupChannelSettingsContextsProvider: GroupChannelSettingsModule['P
|
|
|
99
112
|
const files = await fileService.openMediaLibrary({
|
|
100
113
|
selectionLimit: 1,
|
|
101
114
|
mediaType: 'photo',
|
|
102
|
-
|
|
115
|
+
onOpenFailure: (error) => {
|
|
116
|
+
if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
|
|
117
|
+
alert({
|
|
118
|
+
title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
|
|
119
|
+
message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('device storage', 'UIKitSample'),
|
|
120
|
+
buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
|
|
121
|
+
});
|
|
122
|
+
} else {
|
|
123
|
+
toast.show(STRINGS.TOAST.OPEN_PHOTO_LIBRARY_ERROR, 'error');
|
|
124
|
+
}
|
|
125
|
+
},
|
|
103
126
|
});
|
|
104
127
|
if (!files || !files[0]) return;
|
|
105
128
|
|
|
@@ -57,7 +57,7 @@ const createGroupChannelListFragment = (initModule?: Partial<GroupChannelListMod
|
|
|
57
57
|
);
|
|
58
58
|
|
|
59
59
|
if (!currentUser) {
|
|
60
|
-
Logger.warn('Cannot render GroupChannelListFragment,
|
|
60
|
+
Logger.warn('Cannot render GroupChannelListFragment, please connect using `useConnection()` hook first');
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
enum SBUErrorCode {
|
|
2
|
+
ERR_UNKNOWN = 90000000,
|
|
3
|
+
|
|
4
|
+
// Platform service - 91001000 ~
|
|
5
|
+
ERR_PERMISSIONS_DENIED = 91001000,
|
|
6
|
+
ERR_DEVICE_UNAVAILABLE,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default class SBUError extends Error {
|
|
10
|
+
static CODE = SBUErrorCode;
|
|
11
|
+
|
|
12
|
+
static get UNKNOWN() {
|
|
13
|
+
return new SBUError(SBUErrorCode.ERR_UNKNOWN);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static get PERMISSIONS_DENIED() {
|
|
17
|
+
return new SBUError(SBUErrorCode.ERR_PERMISSIONS_DENIED);
|
|
18
|
+
}
|
|
19
|
+
static get DEVICE_UNAVAILABLE() {
|
|
20
|
+
return new SBUError(SBUErrorCode.ERR_DEVICE_UNAVAILABLE);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
constructor(public code: SBUErrorCode, message?: string) {
|
|
24
|
+
super(message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -149,6 +149,11 @@ export interface StringSet {
|
|
|
149
149
|
};
|
|
150
150
|
DIALOG: {
|
|
151
151
|
ALERT_DEFAULT_OK: string;
|
|
152
|
+
|
|
153
|
+
ALERT_PERMISSIONS_TITLE: string;
|
|
154
|
+
ALERT_PERMISSIONS_MESSAGE: (permission: string, appName: string) => string;
|
|
155
|
+
ALERT_PERMISSIONS_OK: string;
|
|
156
|
+
|
|
152
157
|
PROMPT_DEFAULT_OK: string;
|
|
153
158
|
PROMPT_DEFAULT_CANCEL: string;
|
|
154
159
|
PROMPT_DEFAULT_PLACEHOLDER: string;
|
|
@@ -316,6 +321,11 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
|
|
|
316
321
|
},
|
|
317
322
|
DIALOG: {
|
|
318
323
|
ALERT_DEFAULT_OK: 'OK',
|
|
324
|
+
ALERT_PERMISSIONS_TITLE: 'Allow permission',
|
|
325
|
+
ALERT_PERMISSIONS_MESSAGE: (permission, appName = 'Application') => {
|
|
326
|
+
return `${appName} need permission to access your ${permission}. Go to Settings to allow access`;
|
|
327
|
+
},
|
|
328
|
+
ALERT_PERMISSIONS_OK: 'SETTINGS',
|
|
319
329
|
PROMPT_DEFAULT_OK: 'Submit',
|
|
320
330
|
PROMPT_DEFAULT_CANCEL: 'Cancel',
|
|
321
331
|
PROMPT_DEFAULT_PLACEHOLDER: 'Enter',
|
|
@@ -5,6 +5,7 @@ import type * as ExpoMediaLibrary from 'expo-media-library';
|
|
|
5
5
|
|
|
6
6
|
import { getFileExtension, getFileType } from '@sendbird/uikit-utils';
|
|
7
7
|
|
|
8
|
+
import SBUError from '../libs/SBUError';
|
|
8
9
|
import type { ExpoMediaLibraryPermissionResponse, ExpoPermissionResponse } from '../utils/expoPermissionGranted';
|
|
9
10
|
import expoPermissionGranted from '../utils/expoPermissionGranted';
|
|
10
11
|
import fileTypeGuard from '../utils/fileTypeGuard';
|
|
@@ -55,7 +56,7 @@ const createExpoFileService = ({
|
|
|
55
56
|
if (!hasPermission) {
|
|
56
57
|
const granted = await this.requestCameraPermission();
|
|
57
58
|
if (!granted) {
|
|
58
|
-
options?.
|
|
59
|
+
options?.onOpenFailure?.(SBUError.PERMISSIONS_DENIED);
|
|
59
60
|
return null;
|
|
60
61
|
}
|
|
61
62
|
}
|
|
@@ -89,7 +90,7 @@ const createExpoFileService = ({
|
|
|
89
90
|
if (!hasPermission) {
|
|
90
91
|
const granted = await this.requestMediaLibraryPermission('read');
|
|
91
92
|
if (!granted) {
|
|
92
|
-
options?.
|
|
93
|
+
options?.onOpenFailure?.(SBUError.PERMISSIONS_DENIED);
|
|
93
94
|
return null;
|
|
94
95
|
}
|
|
95
96
|
}
|
|
@@ -123,8 +124,8 @@ const createExpoFileService = ({
|
|
|
123
124
|
if (response.type === 'cancel') return null;
|
|
124
125
|
const { mimeType, uri, size, name } = response;
|
|
125
126
|
return fileTypeGuard({ uri, size, name, type: mimeType });
|
|
126
|
-
} catch {
|
|
127
|
-
options?.
|
|
127
|
+
} catch (e) {
|
|
128
|
+
options?.onOpenFailure?.(SBUError.UNKNOWN, e);
|
|
128
129
|
return null;
|
|
129
130
|
}
|
|
130
131
|
}
|
|
@@ -7,7 +7,9 @@ import type * as Permissions from 'react-native-permissions';
|
|
|
7
7
|
import type { Permission } from 'react-native-permissions';
|
|
8
8
|
|
|
9
9
|
import { getFileExtension, getFileType } from '@sendbird/uikit-utils';
|
|
10
|
+
import { normalizeFileName } from '@sendbird/uikit-utils/src/shared/regex';
|
|
10
11
|
|
|
12
|
+
import SBUError from '../libs/SBUError';
|
|
11
13
|
import fileTypeGuard from '../utils/fileTypeGuard';
|
|
12
14
|
import nativePermissionGranted from '../utils/nativePermissionGranted';
|
|
13
15
|
import type {
|
|
@@ -87,12 +89,13 @@ const createNativeFileService = ({
|
|
|
87
89
|
if (!hasPermission) {
|
|
88
90
|
const granted = await this.requestCameraPermission();
|
|
89
91
|
if (!granted) {
|
|
90
|
-
options?.
|
|
92
|
+
options?.onOpenFailure?.(SBUError.PERMISSIONS_DENIED);
|
|
91
93
|
return null;
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
const response = await imagePickerModule.launchCamera({
|
|
98
|
+
presentationStyle: 'fullScreen',
|
|
96
99
|
cameraType: options?.cameraType ?? 'back',
|
|
97
100
|
mediaType: (() => {
|
|
98
101
|
switch (options?.mediaType) {
|
|
@@ -109,7 +112,7 @@ const createNativeFileService = ({
|
|
|
109
112
|
});
|
|
110
113
|
if (response.didCancel) return null;
|
|
111
114
|
if (response.errorCode === 'camera_unavailable') {
|
|
112
|
-
options?.
|
|
115
|
+
options?.onOpenFailure?.(SBUError.DEVICE_UNAVAILABLE, new Error(response.errorMessage));
|
|
113
116
|
return null;
|
|
114
117
|
}
|
|
115
118
|
|
|
@@ -126,12 +129,13 @@ const createNativeFileService = ({
|
|
|
126
129
|
if (!hasPermission) {
|
|
127
130
|
const granted = await this.requestMediaLibraryPermission();
|
|
128
131
|
if (!granted) {
|
|
129
|
-
options?.
|
|
132
|
+
options?.onOpenFailure?.(SBUError.PERMISSIONS_DENIED);
|
|
130
133
|
return null;
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
const response = await imagePickerModule.launchImageLibrary({
|
|
138
|
+
presentationStyle: 'fullScreen',
|
|
135
139
|
selectionLimit,
|
|
136
140
|
mediaType: (() => {
|
|
137
141
|
switch (options?.mediaType) {
|
|
@@ -148,7 +152,7 @@ const createNativeFileService = ({
|
|
|
148
152
|
});
|
|
149
153
|
if (response.didCancel) return null;
|
|
150
154
|
if (response.errorCode === 'camera_unavailable') {
|
|
151
|
-
options?.
|
|
155
|
+
options?.onOpenFailure?.(SBUError.DEVICE_UNAVAILABLE, new Error(response.errorMessage));
|
|
152
156
|
return null;
|
|
153
157
|
}
|
|
154
158
|
|
|
@@ -162,7 +166,7 @@ const createNativeFileService = ({
|
|
|
162
166
|
return fileTypeGuard({ uri, size, name, type });
|
|
163
167
|
} catch (e) {
|
|
164
168
|
if (!documentPickerModule.isCancel(e) && documentPickerModule.isInProgress(e)) {
|
|
165
|
-
options?.
|
|
169
|
+
options?.onOpenFailure?.(SBUError.UNKNOWN, e);
|
|
166
170
|
}
|
|
167
171
|
return null;
|
|
168
172
|
}
|
|
@@ -186,13 +190,18 @@ const createNativeFileService = ({
|
|
|
186
190
|
await fsModule.FileSystem.fetch(options.fileUrl, { path: downloadPath });
|
|
187
191
|
const fileType = getFileType(getFileExtension(options.fileUrl));
|
|
188
192
|
|
|
189
|
-
if (Platform.OS === 'ios' && fileType
|
|
190
|
-
|
|
193
|
+
if (Platform.OS === 'ios' && (fileType === 'image' || fileType === 'video')) {
|
|
194
|
+
const type = ({ 'image': 'photo', 'video': 'video' } as const)[fileType];
|
|
195
|
+
await mediaLibraryModule.save(downloadPath, { type });
|
|
191
196
|
}
|
|
192
197
|
|
|
193
198
|
if (Platform.OS === 'android') {
|
|
194
199
|
const dirType = { 'file': 'downloads', 'audio': 'audio', 'image': 'images', 'video': 'video' } as const;
|
|
195
|
-
await fsModule.FileSystem.cpExternal(
|
|
200
|
+
await fsModule.FileSystem.cpExternal(
|
|
201
|
+
downloadPath,
|
|
202
|
+
normalizeFileName(options.fileName, getFileExtension(options.fileUrl)),
|
|
203
|
+
dirType[fileType],
|
|
204
|
+
);
|
|
196
205
|
}
|
|
197
206
|
return downloadPath;
|
|
198
207
|
}
|
package/src/platform/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type SBUError from '../libs/SBUError';
|
|
2
|
+
|
|
1
3
|
export type Unsubscribe = () => void | undefined;
|
|
2
4
|
export type DownloadedPath = string;
|
|
3
5
|
export type FilePickerResponse = FileType | null;
|
|
@@ -23,7 +25,7 @@ export interface ClipboardServiceInterface {
|
|
|
23
25
|
export interface FileServiceInterface extends FilePickerServiceInterface, FileSystemServiceInterface {}
|
|
24
26
|
|
|
25
27
|
export interface OpenResultListener {
|
|
26
|
-
|
|
28
|
+
onOpenFailure?: (error: SBUError, originError?: unknown) => void;
|
|
27
29
|
}
|
|
28
30
|
export interface OpenMediaLibraryOptions extends OpenResultListener {
|
|
29
31
|
selectionLimit?: number;
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const VERSION = '1.1.
|
|
1
|
+
const VERSION = '1.1.3';
|
|
2
2
|
export default VERSION;
|