@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.
Files changed (116) hide show
  1. package/lib/commonjs/components/ChannelInput/EditInput.js +2 -11
  2. package/lib/commonjs/components/ChannelInput/EditInput.js.map +1 -1
  3. package/lib/commonjs/components/ChannelInput/SendInput.js +2 -11
  4. package/lib/commonjs/components/ChannelInput/SendInput.js.map +1 -1
  5. package/lib/commonjs/components/ChannelInput/index.js +30 -3
  6. package/lib/commonjs/components/ChannelInput/index.js.map +1 -1
  7. package/lib/commonjs/components/ChannelMessageList/index.js +148 -116
  8. package/lib/commonjs/components/ChannelMessageList/index.js.map +1 -1
  9. package/lib/commonjs/components/FileViewer/FileViewerContent.js +140 -0
  10. package/lib/commonjs/components/FileViewer/FileViewerContent.js.map +1 -0
  11. package/lib/commonjs/components/FileViewer/FileViewerFooter.js +82 -0
  12. package/lib/commonjs/components/FileViewer/FileViewerFooter.js.map +1 -0
  13. package/lib/commonjs/components/FileViewer/FileViewerHeader.js +93 -0
  14. package/lib/commonjs/components/FileViewer/FileViewerHeader.js.map +1 -0
  15. package/lib/commonjs/components/FileViewer/index.js +133 -0
  16. package/lib/commonjs/components/FileViewer/index.js.map +1 -0
  17. package/lib/commonjs/components/GroupChannelMessageRenderer/index.js +34 -1
  18. package/lib/commonjs/components/GroupChannelMessageRenderer/index.js.map +1 -1
  19. package/lib/commonjs/components/ReactionAddons/BottomSheetReactionAddon.js.map +1 -1
  20. package/lib/commonjs/domain/groupChannel/component/GroupChannelHeader.js +14 -4
  21. package/lib/commonjs/domain/groupChannel/component/GroupChannelHeader.js.map +1 -1
  22. package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js +11 -9
  23. package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
  24. package/lib/commonjs/domain/groupChannel/types.js.map +1 -1
  25. package/lib/commonjs/domain/messageSearch/component/MessageSearchHeader.js +4 -1
  26. package/lib/commonjs/domain/messageSearch/component/MessageSearchHeader.js.map +1 -1
  27. package/lib/commonjs/domain/openChannelCreate/component/OpenChannelCreateProfileInput.js +4 -2
  28. package/lib/commonjs/domain/openChannelCreate/component/OpenChannelCreateProfileInput.js.map +1 -1
  29. package/lib/commonjs/fragments/createGroupChannelFragment.js +18 -16
  30. package/lib/commonjs/fragments/createGroupChannelFragment.js.map +1 -1
  31. package/lib/commonjs/index.js +4 -0
  32. package/lib/commonjs/index.js.map +1 -1
  33. package/lib/commonjs/types.js +7 -0
  34. package/lib/commonjs/types.js.map +1 -1
  35. package/lib/commonjs/utils/promise.js +138 -0
  36. package/lib/commonjs/utils/promise.js.map +1 -0
  37. package/lib/commonjs/version.js +1 -1
  38. package/lib/commonjs/version.js.map +1 -1
  39. package/lib/module/components/ChannelInput/EditInput.js +3 -12
  40. package/lib/module/components/ChannelInput/EditInput.js.map +1 -1
  41. package/lib/module/components/ChannelInput/SendInput.js +3 -12
  42. package/lib/module/components/ChannelInput/SendInput.js.map +1 -1
  43. package/lib/module/components/ChannelInput/index.js +32 -5
  44. package/lib/module/components/ChannelInput/index.js.map +1 -1
  45. package/lib/module/components/ChannelMessageList/index.js +148 -116
  46. package/lib/module/components/ChannelMessageList/index.js.map +1 -1
  47. package/lib/module/components/FileViewer/FileViewerContent.js +130 -0
  48. package/lib/module/components/FileViewer/FileViewerContent.js.map +1 -0
  49. package/lib/module/components/FileViewer/FileViewerFooter.js +74 -0
  50. package/lib/module/components/FileViewer/FileViewerFooter.js.map +1 -0
  51. package/lib/module/components/FileViewer/FileViewerHeader.js +85 -0
  52. package/lib/module/components/FileViewer/FileViewerHeader.js.map +1 -0
  53. package/lib/module/components/FileViewer/index.js +123 -0
  54. package/lib/module/components/FileViewer/index.js.map +1 -0
  55. package/lib/module/components/GroupChannelMessageRenderer/index.js +34 -2
  56. package/lib/module/components/GroupChannelMessageRenderer/index.js.map +1 -1
  57. package/lib/module/components/ReactionAddons/BottomSheetReactionAddon.js.map +1 -1
  58. package/lib/module/domain/groupChannel/component/GroupChannelHeader.js +15 -5
  59. package/lib/module/domain/groupChannel/component/GroupChannelHeader.js.map +1 -1
  60. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js +11 -9
  61. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
  62. package/lib/module/domain/groupChannel/types.js.map +1 -1
  63. package/lib/module/domain/messageSearch/component/MessageSearchHeader.js +4 -1
  64. package/lib/module/domain/messageSearch/component/MessageSearchHeader.js.map +1 -1
  65. package/lib/module/domain/openChannelCreate/component/OpenChannelCreateProfileInput.js +4 -2
  66. package/lib/module/domain/openChannelCreate/component/OpenChannelCreateProfileInput.js.map +1 -1
  67. package/lib/module/fragments/createGroupChannelFragment.js +19 -17
  68. package/lib/module/fragments/createGroupChannelFragment.js.map +1 -1
  69. package/lib/module/index.js +4 -0
  70. package/lib/module/index.js.map +1 -1
  71. package/lib/module/types.js +5 -1
  72. package/lib/module/types.js.map +1 -1
  73. package/lib/module/utils/promise.js +132 -0
  74. package/lib/module/utils/promise.js.map +1 -0
  75. package/lib/module/version.js +1 -1
  76. package/lib/module/version.js.map +1 -1
  77. package/lib/typescript/src/components/ChannelInput/index.d.ts +2 -0
  78. package/lib/typescript/src/components/ChannelMessageList/index.d.ts +4 -1
  79. package/lib/typescript/src/components/FileViewer/FileViewerContent.d.ts +13 -0
  80. package/lib/typescript/src/components/FileViewer/FileViewerFooter.d.ts +9 -0
  81. package/lib/typescript/src/components/FileViewer/FileViewerHeader.d.ts +10 -0
  82. package/lib/typescript/src/components/{FileViewer.d.ts → FileViewer/index.d.ts} +5 -1
  83. package/lib/typescript/src/components/GroupChannelMessageRenderer/index.d.ts +3 -0
  84. package/lib/typescript/src/components/OpenChannelMessageRenderer/index.d.ts +2 -0
  85. package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +1 -1
  86. package/lib/typescript/src/domain/groupChannel/component/GroupChannelMessageList.d.ts +2 -2
  87. package/lib/typescript/src/domain/groupChannel/types.d.ts +5 -2
  88. package/lib/typescript/src/types.d.ts +4 -0
  89. package/lib/typescript/src/utils/promise.d.ts +7 -0
  90. package/lib/typescript/src/version.d.ts +1 -1
  91. package/package.json +8 -7
  92. package/src/components/ChannelInput/EditInput.tsx +3 -15
  93. package/src/components/ChannelInput/SendInput.tsx +2 -9
  94. package/src/components/ChannelInput/index.tsx +27 -4
  95. package/src/components/ChannelMessageList/index.tsx +145 -115
  96. package/src/components/FileViewer/FileViewerContent.tsx +141 -0
  97. package/src/components/FileViewer/FileViewerFooter.tsx +73 -0
  98. package/src/components/FileViewer/FileViewerHeader.tsx +86 -0
  99. package/src/components/FileViewer/index.tsx +139 -0
  100. package/src/components/GroupChannelMessageRenderer/index.tsx +34 -2
  101. package/src/components/ReactionAddons/BottomSheetReactionAddon.tsx +3 -2
  102. package/src/domain/groupChannel/component/GroupChannelHeader.tsx +14 -3
  103. package/src/domain/groupChannel/component/GroupChannelMessageList.tsx +8 -6
  104. package/src/domain/groupChannel/types.ts +6 -2
  105. package/src/domain/messageSearch/component/MessageSearchHeader.tsx +4 -1
  106. package/src/domain/openChannelCreate/component/OpenChannelCreateProfileInput.tsx +4 -2
  107. package/src/fragments/createGroupChannelFragment.tsx +25 -15
  108. package/src/index.ts +5 -1
  109. package/src/types.ts +5 -0
  110. package/src/utils/promise.ts +139 -0
  111. package/src/version.ts +1 -1
  112. package/lib/commonjs/components/FileViewer.js +0 -300
  113. package/lib/commonjs/components/FileViewer.js.map +0 -1
  114. package/lib/module/components/FileViewer.js +0 -291
  115. package/lib/module/components/FileViewer.js.map +0 -1
  116. 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 { Box, GroupChannelMessage, Text, useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
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
- const subtitle = STRINGS.LABELS.TYPING_INDICATOR_TYPINGS(typingUsers);
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
- {Boolean(subtitle) && subtitle && <Header.Subtitle style={styles.subtitle}>{subtitle}</Header.Subtitle>}
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) props.onUpdateSearchItem({ startingPoint: createdAt });
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
- props.onScrolledAwayFromBottom(false);
54
- lazyScrollToBottom({ animated });
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: (callback?: () => void) => void;
77
- onResetMessageListWithStartingPoint: (startingPoint: number, callback?: () => void) => void;
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
  };
@@ -91,7 +91,10 @@ const styles = createStyleSheet({
91
91
  flex: 1,
92
92
  height: '100%',
93
93
  fontSize: 14,
94
- padding: 0,
94
+ paddingLeft: 0,
95
+ paddingTop: 0,
96
+ paddingBottom: 0,
97
+ paddingRight: 0,
95
98
  },
96
99
  });
97
100
 
@@ -142,8 +142,10 @@ const styles = createStyleSheet({
142
142
  },
143
143
  input: {
144
144
  flex: 1,
145
- paddingVertical: 0,
146
- paddingHorizontal: 0,
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 { useGroupChannelMessages } from '@sendbird/uikit-chat-hooks';
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 from '../components/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
- next,
76
- prev,
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, currentUser?.userId, {
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
- onChannelDeleted,
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
- if (renderMessage) return renderMessage(props);
127
- return <GroupChannelMessageRenderer {...props} />;
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((callback?: () => void) => {
140
- resetWithStartingPoint(Number.MAX_SAFE_INTEGER, callback);
149
+ const onResetMessageList = useCallback(async () => {
150
+ return await resetWithStartingPoint(Number.MAX_SAFE_INTEGER);
141
151
  }, []);
142
152
 
143
- const onResetMessageListWithStartingPoint = useCallback((startingPoint: number, callback?: () => void) => {
144
- resetWithStartingPoint(startingPoint, callback);
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={prev}
220
- onBottomReached={next}
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();
package/src/types.ts CHANGED
@@ -28,3 +28,8 @@ export type Range = {
28
28
  start: number;
29
29
  end: number;
30
30
  };
31
+
32
+ export enum TypingIndicatorType {
33
+ Text = 'text',
34
+ Bubble = 'bubble',
35
+ }