@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Box,
|
|
6
|
+
Icon,
|
|
7
|
+
PressBox,
|
|
8
|
+
createStyleSheet,
|
|
9
|
+
useHeaderStyle,
|
|
10
|
+
useUIKitTheme,
|
|
11
|
+
} from '@sendbird/uikit-react-native-foundation';
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
bottomInset: number;
|
|
15
|
+
deleteShown: boolean;
|
|
16
|
+
onPressDelete: () => void;
|
|
17
|
+
onPressDownload: () => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const FileViewerFooter = ({ bottomInset, deleteShown, onPressDelete, onPressDownload }: Props) => {
|
|
21
|
+
const { palette } = useUIKitTheme();
|
|
22
|
+
const { defaultHeight } = useHeaderStyle();
|
|
23
|
+
const { left, right } = useSafeAreaInsets();
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Box
|
|
27
|
+
style={[
|
|
28
|
+
styles.container,
|
|
29
|
+
{
|
|
30
|
+
paddingLeft: styles.container.paddingHorizontal + left,
|
|
31
|
+
paddingRight: styles.container.paddingHorizontal + right,
|
|
32
|
+
paddingBottom: bottomInset,
|
|
33
|
+
height: defaultHeight + bottomInset,
|
|
34
|
+
backgroundColor: palette.overlay01,
|
|
35
|
+
},
|
|
36
|
+
]}
|
|
37
|
+
>
|
|
38
|
+
<PressBox activeOpacity={0.75} onPress={onPressDownload} style={styles.buttonContainer}>
|
|
39
|
+
<Icon icon={'download'} size={24} color={palette.onBackgroundDark01} />
|
|
40
|
+
</PressBox>
|
|
41
|
+
<Box style={styles.titleContainer} />
|
|
42
|
+
<PressBox activeOpacity={0.75} onPress={onPressDelete} style={styles.buttonContainer} disabled={!deleteShown}>
|
|
43
|
+
{deleteShown && <Icon icon={'delete'} size={24} color={palette.onBackgroundDark01} />}
|
|
44
|
+
</PressBox>
|
|
45
|
+
</Box>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const styles = createStyleSheet({
|
|
50
|
+
container: {
|
|
51
|
+
position: 'absolute',
|
|
52
|
+
left: 0,
|
|
53
|
+
right: 0,
|
|
54
|
+
bottom: 0,
|
|
55
|
+
flexDirection: 'row',
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
justifyContent: 'center',
|
|
58
|
+
paddingHorizontal: 12,
|
|
59
|
+
},
|
|
60
|
+
buttonContainer: {
|
|
61
|
+
width: 32,
|
|
62
|
+
height: 32,
|
|
63
|
+
alignItems: 'center',
|
|
64
|
+
justifyContent: 'center',
|
|
65
|
+
},
|
|
66
|
+
titleContainer: {
|
|
67
|
+
flex: 1,
|
|
68
|
+
alignItems: 'center',
|
|
69
|
+
justifyContent: 'center',
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export default FileViewerFooter;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Box,
|
|
6
|
+
Icon,
|
|
7
|
+
PressBox,
|
|
8
|
+
Text,
|
|
9
|
+
createStyleSheet,
|
|
10
|
+
useHeaderStyle,
|
|
11
|
+
useUIKitTheme,
|
|
12
|
+
} from '@sendbird/uikit-react-native-foundation';
|
|
13
|
+
import { truncate } from '@sendbird/uikit-utils';
|
|
14
|
+
|
|
15
|
+
type Props = {
|
|
16
|
+
headerShown?: boolean;
|
|
17
|
+
topInset: number;
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
title: string;
|
|
20
|
+
subtitle: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const FileViewerHeader = ({ headerShown = true, topInset, onClose, subtitle, title }: Props) => {
|
|
24
|
+
const { palette } = useUIKitTheme();
|
|
25
|
+
const { defaultHeight } = useHeaderStyle();
|
|
26
|
+
const { left, right } = useSafeAreaInsets();
|
|
27
|
+
|
|
28
|
+
if (!headerShown) return null;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Box
|
|
32
|
+
style={[
|
|
33
|
+
styles.container,
|
|
34
|
+
{
|
|
35
|
+
paddingLeft: styles.container.paddingHorizontal + left,
|
|
36
|
+
paddingRight: styles.container.paddingHorizontal + right,
|
|
37
|
+
paddingTop: topInset,
|
|
38
|
+
height: defaultHeight + topInset,
|
|
39
|
+
backgroundColor: palette.overlay01,
|
|
40
|
+
},
|
|
41
|
+
]}
|
|
42
|
+
>
|
|
43
|
+
<PressBox activeOpacity={0.75} onPress={onClose} style={styles.buttonContainer}>
|
|
44
|
+
<Icon icon={'close'} size={24} color={palette.onBackgroundDark01} />
|
|
45
|
+
</PressBox>
|
|
46
|
+
<Box style={styles.titleContainer}>
|
|
47
|
+
<Text h2 color={palette.onBackgroundDark01} style={styles.title} numberOfLines={1}>
|
|
48
|
+
{truncate(title, { mode: 'mid', maxLen: 18 })}
|
|
49
|
+
</Text>
|
|
50
|
+
<Text caption2 color={palette.onBackgroundDark01} numberOfLines={1}>
|
|
51
|
+
{subtitle}
|
|
52
|
+
</Text>
|
|
53
|
+
</Box>
|
|
54
|
+
<Box style={styles.buttonContainer} />
|
|
55
|
+
</Box>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const styles = createStyleSheet({
|
|
60
|
+
container: {
|
|
61
|
+
top: 0,
|
|
62
|
+
left: 0,
|
|
63
|
+
right: 0,
|
|
64
|
+
position: 'absolute',
|
|
65
|
+
flexDirection: 'row',
|
|
66
|
+
alignItems: 'center',
|
|
67
|
+
justifyContent: 'center',
|
|
68
|
+
paddingHorizontal: 12,
|
|
69
|
+
},
|
|
70
|
+
buttonContainer: {
|
|
71
|
+
width: 32,
|
|
72
|
+
height: 32,
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
justifyContent: 'center',
|
|
75
|
+
},
|
|
76
|
+
titleContainer: {
|
|
77
|
+
flex: 1,
|
|
78
|
+
alignItems: 'center',
|
|
79
|
+
justifyContent: 'center',
|
|
80
|
+
},
|
|
81
|
+
title: {
|
|
82
|
+
marginBottom: 2,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export default FileViewerHeader;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { StatusBar } from 'react-native';
|
|
3
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
|
+
|
|
5
|
+
import { Box, useAlert, useHeaderStyle, useToast, useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
|
|
6
|
+
import type { SendbirdFileMessage } from '@sendbird/uikit-utils';
|
|
7
|
+
import { Logger, getFileExtension, getFileType, isMyMessage, toMegabyte } from '@sendbird/uikit-utils';
|
|
8
|
+
|
|
9
|
+
import { useLocalization, usePlatformService, useSendbirdChat } from '../../hooks/useContext';
|
|
10
|
+
import FileViewerContent from './FileViewerContent';
|
|
11
|
+
import FileViewerFooter from './FileViewerFooter';
|
|
12
|
+
import FileViewerHeader from './FileViewerHeader';
|
|
13
|
+
|
|
14
|
+
type Props = {
|
|
15
|
+
fileMessage: SendbirdFileMessage;
|
|
16
|
+
deleteMessage: () => Promise<void>;
|
|
17
|
+
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
onPressDownload?: (message: SendbirdFileMessage) => void;
|
|
20
|
+
onPressDelete?: (message: SendbirdFileMessage) => void;
|
|
21
|
+
|
|
22
|
+
headerShown?: boolean;
|
|
23
|
+
headerTopInset?: number;
|
|
24
|
+
|
|
25
|
+
/** This prop is only available on the Image viewer */
|
|
26
|
+
minZoom?: number;
|
|
27
|
+
/** This prop is only available on the Image viewer */
|
|
28
|
+
maxZoom?: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const FileViewer = ({
|
|
32
|
+
headerShown = true,
|
|
33
|
+
maxZoom = 3,
|
|
34
|
+
minZoom = 1,
|
|
35
|
+
headerTopInset,
|
|
36
|
+
fileMessage,
|
|
37
|
+
onClose,
|
|
38
|
+
onPressDownload,
|
|
39
|
+
onPressDelete,
|
|
40
|
+
deleteMessage,
|
|
41
|
+
}: Props) => {
|
|
42
|
+
const { topInset, statusBarTranslucent } = useHeaderStyle();
|
|
43
|
+
const { bottom } = useSafeAreaInsets();
|
|
44
|
+
const { palette } = useUIKitTheme();
|
|
45
|
+
const { alert } = useAlert();
|
|
46
|
+
const { show } = useToast();
|
|
47
|
+
|
|
48
|
+
const { fileService } = usePlatformService();
|
|
49
|
+
const { currentUser } = useSendbirdChat();
|
|
50
|
+
const { STRINGS } = useLocalization();
|
|
51
|
+
|
|
52
|
+
const fileType = getFileType(fileMessage.type || getFileExtension(fileMessage.url));
|
|
53
|
+
const canDelete = isMyMessage(fileMessage, currentUser?.userId);
|
|
54
|
+
const basicTopInset = statusBarTranslucent ? topInset : 0;
|
|
55
|
+
|
|
56
|
+
const onPressDeleteButton = () => {
|
|
57
|
+
if (!canDelete) return;
|
|
58
|
+
|
|
59
|
+
if (onPressDelete) {
|
|
60
|
+
onPressDelete(fileMessage);
|
|
61
|
+
} else {
|
|
62
|
+
alert({
|
|
63
|
+
title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_TITLE,
|
|
64
|
+
buttons: [
|
|
65
|
+
{
|
|
66
|
+
text: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_CANCEL,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
text: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_OK,
|
|
70
|
+
style: 'destructive',
|
|
71
|
+
onPress: () => {
|
|
72
|
+
deleteMessage()
|
|
73
|
+
.then(() => {
|
|
74
|
+
onClose();
|
|
75
|
+
})
|
|
76
|
+
.catch(() => {
|
|
77
|
+
show(STRINGS.TOAST.DELETE_MSG_ERROR, 'error');
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const onPressDownloadButton = () => {
|
|
87
|
+
if (onPressDownload) {
|
|
88
|
+
onPressDownload(fileMessage);
|
|
89
|
+
} else {
|
|
90
|
+
if (toMegabyte(fileMessage.size) > 4) {
|
|
91
|
+
show(STRINGS.TOAST.DOWNLOAD_START, 'success');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fileService
|
|
95
|
+
.save({ fileUrl: fileMessage.url, fileName: fileMessage.name, fileType: fileMessage.type })
|
|
96
|
+
.then((response) => {
|
|
97
|
+
show(STRINGS.TOAST.DOWNLOAD_OK, 'success');
|
|
98
|
+
Logger.log('File saved to', response);
|
|
99
|
+
})
|
|
100
|
+
.catch((err) => {
|
|
101
|
+
show(STRINGS.TOAST.DOWNLOAD_ERROR, 'error');
|
|
102
|
+
Logger.log('File save failure', err);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (fileType === 'file') onClose();
|
|
109
|
+
}, []);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Box flex={1} backgroundColor={palette.background700}>
|
|
113
|
+
<StatusBar barStyle={'light-content'} animated />
|
|
114
|
+
<FileViewerHeader
|
|
115
|
+
topInset={headerTopInset ?? basicTopInset}
|
|
116
|
+
headerShown={headerShown}
|
|
117
|
+
title={STRINGS.FILE_VIEWER.TITLE(fileMessage)}
|
|
118
|
+
subtitle={STRINGS.FILE_VIEWER.SUBTITLE(fileMessage)}
|
|
119
|
+
onClose={onClose}
|
|
120
|
+
/>
|
|
121
|
+
<FileViewerContent
|
|
122
|
+
topInset={headerTopInset ?? basicTopInset}
|
|
123
|
+
bottomInset={bottom}
|
|
124
|
+
type={fileType}
|
|
125
|
+
src={fileMessage.url}
|
|
126
|
+
maxZoom={maxZoom}
|
|
127
|
+
minZoom={minZoom}
|
|
128
|
+
/>
|
|
129
|
+
<FileViewerFooter
|
|
130
|
+
bottomInset={bottom}
|
|
131
|
+
deleteShown={canDelete}
|
|
132
|
+
onPressDelete={onPressDeleteButton}
|
|
133
|
+
onPressDownload={onPressDownloadButton}
|
|
134
|
+
/>
|
|
135
|
+
</Box>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export default FileViewer;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import React, { useRef } from 'react';
|
|
1
|
+
import React, { useContext, useEffect, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import type { GroupChannelMessageProps, RegexTextPattern } from '@sendbird/uikit-react-native-foundation';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Box,
|
|
6
|
+
GroupChannelMessage,
|
|
7
|
+
Text,
|
|
8
|
+
TypingIndicatorBubble,
|
|
9
|
+
useUIKitTheme,
|
|
10
|
+
} from '@sendbird/uikit-react-native-foundation';
|
|
5
11
|
import {
|
|
6
12
|
SendbirdAdminMessage,
|
|
7
13
|
SendbirdFileMessage,
|
|
@@ -17,9 +23,11 @@ import {
|
|
|
17
23
|
} from '@sendbird/uikit-utils';
|
|
18
24
|
|
|
19
25
|
import { VOICE_MESSAGE_META_ARRAY_DURATION_KEY } from '../../constants';
|
|
26
|
+
import { GroupChannelContexts } from '../../domain/groupChannel/module/moduleContext';
|
|
20
27
|
import type { GroupChannelProps } from '../../domain/groupChannel/types';
|
|
21
28
|
import { useLocalization, usePlatformService, useSendbirdChat } from '../../hooks/useContext';
|
|
22
29
|
import SBUUtils from '../../libs/SBUUtils';
|
|
30
|
+
import { TypingIndicatorType } from '../../types';
|
|
23
31
|
import { ReactionAddons } from '../ReactionAddons';
|
|
24
32
|
import GroupChannelMessageDateSeparator from './GroupChannelMessageDateSeparator';
|
|
25
33
|
import GroupChannelMessageFocusAnimation from './GroupChannelMessageFocusAnimation';
|
|
@@ -292,4 +300,28 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
|
|
|
292
300
|
);
|
|
293
301
|
};
|
|
294
302
|
|
|
303
|
+
export const GroupChannelTypingIndicatorBubble = () => {
|
|
304
|
+
const { sbOptions } = useSendbirdChat();
|
|
305
|
+
const { publish } = useContext(GroupChannelContexts.PubSub);
|
|
306
|
+
const { typingUsers } = useContext(GroupChannelContexts.TypingIndicator);
|
|
307
|
+
|
|
308
|
+
const shouldRenderBubble = useIIFE(() => {
|
|
309
|
+
if (typingUsers.length === 0) return false;
|
|
310
|
+
if (!sbOptions.uikit.groupChannel.channel.enableTypingIndicator) return false;
|
|
311
|
+
if (!sbOptions.uikit.groupChannel.channel.typingIndicatorTypes.has(TypingIndicatorType.Bubble)) return false;
|
|
312
|
+
return true;
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
if (shouldRenderBubble) publish({ type: 'TYPING_BUBBLE_RENDERED' });
|
|
317
|
+
}, [shouldRenderBubble]);
|
|
318
|
+
|
|
319
|
+
if (!shouldRenderBubble) return null;
|
|
320
|
+
return (
|
|
321
|
+
<Box paddingHorizontal={16} marginTop={4} marginBottom={16}>
|
|
322
|
+
<TypingIndicatorBubble typingUsers={typingUsers} />
|
|
323
|
+
</Box>
|
|
324
|
+
);
|
|
325
|
+
};
|
|
326
|
+
|
|
295
327
|
export default React.memo(GroupChannelMessageRenderer);
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { Pressable, View } from 'react-native';
|
|
3
3
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
4
|
|
|
5
|
+
import type { BaseMessage } from '@sendbird/chat/message';
|
|
5
6
|
import { useChannelHandler } from '@sendbird/uikit-chat-hooks';
|
|
6
7
|
import { Icon, Image, createStyleSheet, useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
|
|
7
8
|
import { SendbirdBaseChannel, SendbirdBaseMessage, useUniqHandlerId } from '@sendbird/uikit-utils';
|
|
@@ -24,12 +25,12 @@ const BottomSheetReactionAddon = ({ onClose, message, channel }: Props) => {
|
|
|
24
25
|
useChannelHandler(sdk, handlerId, {
|
|
25
26
|
async onReactionUpdated(eventChannel, event) {
|
|
26
27
|
if (channel?.url === eventChannel.url && event.messageId === message?.messageId) {
|
|
27
|
-
const msg = await sdk.message.getMessage({
|
|
28
|
+
const msg = (await sdk.message.getMessage({
|
|
28
29
|
includeReactions: true,
|
|
29
30
|
messageId: message.messageId,
|
|
30
31
|
channelUrl: message.channelUrl,
|
|
31
32
|
channelType: message.channelType,
|
|
32
|
-
});
|
|
33
|
+
})) as null | BaseMessage;
|
|
33
34
|
if (msg) updateReactionFocusedItem({ message: msg });
|
|
34
35
|
}
|
|
35
36
|
},
|
|
@@ -4,7 +4,8 @@ import { View } from 'react-native';
|
|
|
4
4
|
import { Header, Icon, createStyleSheet, useHeaderStyle } from '@sendbird/uikit-react-native-foundation';
|
|
5
5
|
|
|
6
6
|
import ChannelCover from '../../../components/ChannelCover';
|
|
7
|
-
import { useLocalization } from '../../../hooks/useContext';
|
|
7
|
+
import { useLocalization, useSendbirdChat } from '../../../hooks/useContext';
|
|
8
|
+
import { TypingIndicatorType } from '../../../types';
|
|
8
9
|
import { GroupChannelContexts } from '../module/moduleContext';
|
|
9
10
|
import type { GroupChannelProps } from '../types';
|
|
10
11
|
|
|
@@ -13,11 +14,21 @@ const GroupChannelHeader = ({
|
|
|
13
14
|
onPressHeaderLeft,
|
|
14
15
|
onPressHeaderRight,
|
|
15
16
|
}: GroupChannelProps['Header']) => {
|
|
17
|
+
const { sbOptions } = useSendbirdChat();
|
|
16
18
|
const { headerTitle, channel } = useContext(GroupChannelContexts.Fragment);
|
|
17
19
|
const { typingUsers } = useContext(GroupChannelContexts.TypingIndicator);
|
|
18
20
|
const { STRINGS } = useLocalization();
|
|
19
21
|
const { HeaderComponent } = useHeaderStyle();
|
|
20
|
-
|
|
22
|
+
|
|
23
|
+
const renderSubtitle = () => {
|
|
24
|
+
const subtitle = STRINGS.LABELS.TYPING_INDICATOR_TYPINGS(typingUsers);
|
|
25
|
+
|
|
26
|
+
if (!subtitle) return null;
|
|
27
|
+
if (!sbOptions.uikit.groupChannel.channel.enableTypingIndicator) return null;
|
|
28
|
+
if (!sbOptions.uikit.groupChannel.channel.typingIndicatorTypes.has(TypingIndicatorType.Text)) return null;
|
|
29
|
+
|
|
30
|
+
return <Header.Subtitle style={styles.subtitle}>{subtitle}</Header.Subtitle>;
|
|
31
|
+
};
|
|
21
32
|
|
|
22
33
|
const isHidden = shouldHideRight();
|
|
23
34
|
|
|
@@ -29,7 +40,7 @@ const GroupChannelHeader = ({
|
|
|
29
40
|
<ChannelCover channel={channel} size={34} containerStyle={styles.avatarGroup} />
|
|
30
41
|
<View style={{ flexShrink: 1 }}>
|
|
31
42
|
<Header.Title h2>{headerTitle}</Header.Title>
|
|
32
|
-
{
|
|
43
|
+
{renderSubtitle()}
|
|
33
44
|
</View>
|
|
34
45
|
</View>
|
|
35
46
|
}
|
|
@@ -34,7 +34,9 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
|
|
|
34
34
|
lazyScrollToIndex({ index: foundMessageIndex, animated: true, timeout });
|
|
35
35
|
} else {
|
|
36
36
|
if (props.channel.messageOffsetTimestamp <= createdAt) {
|
|
37
|
-
if (focusAnimated)
|
|
37
|
+
if (focusAnimated) {
|
|
38
|
+
props.onUpdateSearchItem({ startingPoint: createdAt });
|
|
39
|
+
}
|
|
38
40
|
props.onResetMessageListWithStartingPoint(createdAt);
|
|
39
41
|
} else {
|
|
40
42
|
return false;
|
|
@@ -44,15 +46,14 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
|
|
|
44
46
|
},
|
|
45
47
|
);
|
|
46
48
|
|
|
47
|
-
const scrollToBottom = useFreshCallback((animated = false) => {
|
|
49
|
+
const scrollToBottom = useFreshCallback(async (animated = false) => {
|
|
48
50
|
if (props.hasNext()) {
|
|
49
51
|
props.onUpdateSearchItem(undefined);
|
|
50
52
|
props.onScrolledAwayFromBottom(false);
|
|
51
53
|
|
|
52
|
-
props.onResetMessageList(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
54
|
+
await props.onResetMessageList();
|
|
55
|
+
props.onScrolledAwayFromBottom(false);
|
|
56
|
+
lazyScrollToBottom({ animated });
|
|
56
57
|
} else {
|
|
57
58
|
lazyScrollToBottom({ animated });
|
|
58
59
|
}
|
|
@@ -73,6 +74,7 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
|
|
|
73
74
|
useEffect(() => {
|
|
74
75
|
return subscribe(({ type }) => {
|
|
75
76
|
switch (type) {
|
|
77
|
+
case 'TYPING_BUBBLE_RENDERED':
|
|
76
78
|
case 'MESSAGES_RECEIVED': {
|
|
77
79
|
if (!props.scrolledAwayFromBottom) {
|
|
78
80
|
scrollToBottom(true);
|
|
@@ -73,8 +73,8 @@ export interface GroupChannelProps {
|
|
|
73
73
|
| 'hasNext'
|
|
74
74
|
| 'searchItem'
|
|
75
75
|
> & {
|
|
76
|
-
onResetMessageList: (
|
|
77
|
-
onResetMessageListWithStartingPoint: (startingPoint: number
|
|
76
|
+
onResetMessageList: () => Promise<void>;
|
|
77
|
+
onResetMessageListWithStartingPoint: (startingPoint: number) => Promise<void>;
|
|
78
78
|
|
|
79
79
|
// Changing the search item will trigger the focus animation on messages.
|
|
80
80
|
onUpdateSearchItem: (searchItem?: GroupChannelProps['MessageList']['searchItem']) => void;
|
|
@@ -183,4 +183,8 @@ export type GroupChannelPubSubContextPayload =
|
|
|
183
183
|
data: {
|
|
184
184
|
messages: SendbirdMessage[];
|
|
185
185
|
};
|
|
186
|
+
}
|
|
187
|
+
| {
|
|
188
|
+
type: 'TYPING_BUBBLE_RENDERED';
|
|
189
|
+
data?: undefined;
|
|
186
190
|
};
|
|
@@ -142,8 +142,10 @@ const styles = createStyleSheet({
|
|
|
142
142
|
},
|
|
143
143
|
input: {
|
|
144
144
|
flex: 1,
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
paddingLeft: 0,
|
|
146
|
+
paddingRight: 0,
|
|
147
|
+
paddingTop: 0,
|
|
148
|
+
paddingBottom: 0,
|
|
147
149
|
},
|
|
148
150
|
removeButtonContainer: {
|
|
149
151
|
alignItems: 'flex-end',
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { ReplyType } from '@sendbird/chat/message';
|
|
4
|
-
import {
|
|
4
|
+
import { Box } from '@sendbird/uikit-react-native-foundation';
|
|
5
|
+
import { useGroupChannelMessages } from '@sendbird/uikit-tools';
|
|
5
6
|
import {
|
|
6
7
|
NOOP,
|
|
7
8
|
PASS,
|
|
8
9
|
SendbirdFileMessage,
|
|
9
10
|
SendbirdGroupChannel,
|
|
10
11
|
SendbirdUserMessage,
|
|
12
|
+
confirmAndMarkAsRead,
|
|
11
13
|
messageComparator,
|
|
12
14
|
useFreshCallback,
|
|
13
15
|
useIIFE,
|
|
14
16
|
useRefTracker,
|
|
15
17
|
} from '@sendbird/uikit-utils';
|
|
16
18
|
|
|
17
|
-
import GroupChannelMessageRenderer
|
|
19
|
+
import GroupChannelMessageRenderer, {
|
|
20
|
+
GroupChannelTypingIndicatorBubble,
|
|
21
|
+
} from '../components/GroupChannelMessageRenderer';
|
|
18
22
|
import NewMessagesButton from '../components/NewMessagesButton';
|
|
19
23
|
import ScrollToBottomButton from '../components/ScrollToBottomButton';
|
|
20
24
|
import StatusComposition from '../components/StatusComposition';
|
|
@@ -72,8 +76,8 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
72
76
|
messages,
|
|
73
77
|
newMessages,
|
|
74
78
|
resetNewMessages,
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
loadNext,
|
|
80
|
+
loadPrevious,
|
|
77
81
|
hasNext,
|
|
78
82
|
sendFileMessage,
|
|
79
83
|
sendUserMessage,
|
|
@@ -82,7 +86,7 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
82
86
|
resendMessage,
|
|
83
87
|
deleteMessage,
|
|
84
88
|
resetWithStartingPoint,
|
|
85
|
-
} = useGroupChannelMessages(sdk, channel,
|
|
89
|
+
} = useGroupChannelMessages(sdk, channel, {
|
|
86
90
|
shouldCountNewMessages: () => scrolledAwayFromBottomRef.current,
|
|
87
91
|
onMessagesReceived(messages) {
|
|
88
92
|
groupChannelPubSub.publish({ type: 'MESSAGES_RECEIVED', data: { messages } });
|
|
@@ -90,12 +94,13 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
90
94
|
onMessagesUpdated(messages) {
|
|
91
95
|
groupChannelPubSub.publish({ type: 'MESSAGES_UPDATED', data: { messages } });
|
|
92
96
|
},
|
|
97
|
+
onChannelDeleted,
|
|
98
|
+
onCurrentUserBanned: onChannelDeleted,
|
|
93
99
|
collectionCreator,
|
|
94
100
|
sortComparator,
|
|
95
|
-
|
|
101
|
+
markAsRead: confirmAndMarkAsRead,
|
|
96
102
|
replyType,
|
|
97
103
|
startingPoint: internalSearchItem?.startingPoint,
|
|
98
|
-
enableCollectionWithoutLocalCache: true,
|
|
99
104
|
});
|
|
100
105
|
|
|
101
106
|
const onBlurFragment = () => {
|
|
@@ -123,8 +128,13 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
123
128
|
}, []);
|
|
124
129
|
|
|
125
130
|
const renderItem: GroupChannelProps['MessageList']['renderMessage'] = useFreshCallback((props) => {
|
|
126
|
-
|
|
127
|
-
return
|
|
131
|
+
const content = renderMessage ? renderMessage(props) : <GroupChannelMessageRenderer {...props} />;
|
|
132
|
+
return (
|
|
133
|
+
<Box>
|
|
134
|
+
{content}
|
|
135
|
+
{props.isFirstItem && !hasNext() && <GroupChannelTypingIndicatorBubble />}
|
|
136
|
+
</Box>
|
|
137
|
+
);
|
|
128
138
|
});
|
|
129
139
|
|
|
130
140
|
const memoizedFlatListProps = useMemo(
|
|
@@ -136,12 +146,12 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
136
146
|
[flatListProps],
|
|
137
147
|
);
|
|
138
148
|
|
|
139
|
-
const onResetMessageList = useCallback(
|
|
140
|
-
resetWithStartingPoint(Number.MAX_SAFE_INTEGER
|
|
149
|
+
const onResetMessageList = useCallback(async () => {
|
|
150
|
+
return await resetWithStartingPoint(Number.MAX_SAFE_INTEGER);
|
|
141
151
|
}, []);
|
|
142
152
|
|
|
143
|
-
const onResetMessageListWithStartingPoint = useCallback((startingPoint: number
|
|
144
|
-
resetWithStartingPoint(startingPoint
|
|
153
|
+
const onResetMessageListWithStartingPoint = useCallback(async (startingPoint: number) => {
|
|
154
|
+
return await resetWithStartingPoint(startingPoint);
|
|
145
155
|
}, []);
|
|
146
156
|
|
|
147
157
|
// Changing the search item will trigger the focus animation on messages.
|
|
@@ -216,8 +226,8 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
|
|
|
216
226
|
renderMessage={renderItem}
|
|
217
227
|
messages={messages}
|
|
218
228
|
newMessages={newMessages}
|
|
219
|
-
onTopReached={
|
|
220
|
-
onBottomReached={
|
|
229
|
+
onTopReached={loadPrevious}
|
|
230
|
+
onBottomReached={loadNext}
|
|
221
231
|
hasNext={hasNext}
|
|
222
232
|
scrolledAwayFromBottom={scrolledAwayFromBottom}
|
|
223
233
|
onScrolledAwayFromBottom={onScrolledAwayFromBottom}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { Platform } from 'react-native';
|
|
|
2
2
|
|
|
3
3
|
import { Logger } from '@sendbird/uikit-utils';
|
|
4
4
|
|
|
5
|
+
import { PromisePolyfill } from './utils/promise';
|
|
6
|
+
|
|
5
7
|
/** Components **/
|
|
6
8
|
export { default as ChannelInput } from './components/ChannelInput';
|
|
7
9
|
export { default as ChannelMessageList } from './components/ChannelMessageList';
|
|
@@ -132,8 +134,10 @@ export { default as SendbirdUIKitContainer, SendbirdUIKit } from './containers/S
|
|
|
132
134
|
export type { SendbirdUIKitContainerProps } from './containers/SendbirdUIKitContainer';
|
|
133
135
|
export { default as SBUError } from './libs/SBUError';
|
|
134
136
|
export { default as SBUUtils } from './libs/SBUUtils';
|
|
135
|
-
|
|
136
137
|
export * from './types';
|
|
137
138
|
|
|
138
139
|
Logger.setLogLevel(__DEV__ ? 'warn' : 'none');
|
|
139
140
|
Logger.setTitle(`[UIKIT_${Platform.OS}]`);
|
|
141
|
+
|
|
142
|
+
// NOTE: In Hermes, not all implementations of Promise are included
|
|
143
|
+
PromisePolyfill.apply();
|