@sendbird/uikit-react-native 2.2.0 → 2.3.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 (135) hide show
  1. package/README.md +42 -17
  2. package/lib/commonjs/components/FileViewer.js +2 -5
  3. package/lib/commonjs/components/FileViewer.js.map +1 -1
  4. package/lib/commonjs/components/MessageRenderer/FileMessage/ImageFileMessage.js +1 -1
  5. package/lib/commonjs/components/MessageRenderer/FileMessage/ImageFileMessage.js.map +1 -1
  6. package/lib/commonjs/components/MessageRenderer/FileMessage/VideoFileMessage.js +1 -1
  7. package/lib/commonjs/components/MessageRenderer/FileMessage/VideoFileMessage.js.map +1 -1
  8. package/lib/commonjs/components/MessageRenderer/FileMessage/index.js +1 -6
  9. package/lib/commonjs/components/MessageRenderer/FileMessage/index.js.map +1 -1
  10. package/lib/commonjs/components/MessageRenderer/UserMessage/BaseUserMessage.js +2 -2
  11. package/lib/commonjs/components/MessageRenderer/UserMessage/BaseUserMessage.js.map +1 -1
  12. package/lib/commonjs/components/MessageRenderer/UserMessage/OpenGraphUserMessage.js +5 -3
  13. package/lib/commonjs/components/MessageRenderer/UserMessage/OpenGraphUserMessage.js.map +1 -1
  14. package/lib/commonjs/containers/SendbirdUIKitContainer.js +14 -3
  15. package/lib/commonjs/containers/SendbirdUIKitContainer.js.map +1 -1
  16. package/lib/commonjs/contexts/PlatformServiceCtx.js.map +1 -1
  17. package/lib/commonjs/contexts/SendbirdChatCtx.js +10 -14
  18. package/lib/commonjs/contexts/SendbirdChatCtx.js.map +1 -1
  19. package/lib/commonjs/domain/groupChannel/component/GroupChannelInput/SendInput.js +68 -13
  20. package/lib/commonjs/domain/groupChannel/component/GroupChannelInput/SendInput.js.map +1 -1
  21. package/lib/commonjs/domain/groupChannel/component/GroupChannelInput/index.js +1 -0
  22. package/lib/commonjs/domain/groupChannel/component/GroupChannelInput/index.js.map +1 -1
  23. package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js +3 -1
  24. package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
  25. package/lib/commonjs/domain/groupChannelSettings/module/moduleContext.js +2 -2
  26. package/lib/commonjs/domain/groupChannelSettings/module/moduleContext.js.map +1 -1
  27. package/lib/commonjs/fragments/createGroupChannelListFragment.js +4 -14
  28. package/lib/commonjs/fragments/createGroupChannelListFragment.js.map +1 -1
  29. package/lib/commonjs/hooks/useKeyboardStatus.js +13 -9
  30. package/lib/commonjs/hooks/useKeyboardStatus.js.map +1 -1
  31. package/lib/commonjs/index.js.map +1 -1
  32. package/lib/commonjs/libs/ImageCompressionConfig.js +38 -0
  33. package/lib/commonjs/libs/ImageCompressionConfig.js.map +1 -0
  34. package/lib/commonjs/libs/SBUUtils.js +27 -0
  35. package/lib/commonjs/libs/SBUUtils.js.map +1 -1
  36. package/lib/commonjs/localization/StringSet.type.js +3 -0
  37. package/lib/commonjs/localization/StringSet.type.js.map +1 -1
  38. package/lib/commonjs/platform/createMediaService.expo.js +35 -1
  39. package/lib/commonjs/platform/createMediaService.expo.js.map +1 -1
  40. package/lib/commonjs/platform/createMediaService.native.js +41 -6
  41. package/lib/commonjs/platform/createMediaService.native.js.map +1 -1
  42. package/lib/commonjs/platform/dynamicModule.js +9 -57
  43. package/lib/commonjs/platform/dynamicModule.js.map +1 -1
  44. package/lib/commonjs/platform/types.js.map +1 -1
  45. package/lib/commonjs/version.js +1 -1
  46. package/lib/commonjs/version.js.map +1 -1
  47. package/lib/module/components/FileViewer.js +2 -5
  48. package/lib/module/components/FileViewer.js.map +1 -1
  49. package/lib/module/components/MessageRenderer/FileMessage/ImageFileMessage.js +1 -1
  50. package/lib/module/components/MessageRenderer/FileMessage/ImageFileMessage.js.map +1 -1
  51. package/lib/module/components/MessageRenderer/FileMessage/VideoFileMessage.js +1 -1
  52. package/lib/module/components/MessageRenderer/FileMessage/VideoFileMessage.js.map +1 -1
  53. package/lib/module/components/MessageRenderer/FileMessage/index.js +1 -5
  54. package/lib/module/components/MessageRenderer/FileMessage/index.js.map +1 -1
  55. package/lib/module/components/MessageRenderer/UserMessage/BaseUserMessage.js +2 -2
  56. package/lib/module/components/MessageRenderer/UserMessage/BaseUserMessage.js.map +1 -1
  57. package/lib/module/components/MessageRenderer/UserMessage/OpenGraphUserMessage.js +4 -4
  58. package/lib/module/components/MessageRenderer/UserMessage/OpenGraphUserMessage.js.map +1 -1
  59. package/lib/module/containers/SendbirdUIKitContainer.js +13 -3
  60. package/lib/module/containers/SendbirdUIKitContainer.js.map +1 -1
  61. package/lib/module/contexts/PlatformServiceCtx.js.map +1 -1
  62. package/lib/module/contexts/SendbirdChatCtx.js +12 -14
  63. package/lib/module/contexts/SendbirdChatCtx.js.map +1 -1
  64. package/lib/module/domain/groupChannel/component/GroupChannelInput/SendInput.js +69 -14
  65. package/lib/module/domain/groupChannel/component/GroupChannelInput/SendInput.js.map +1 -1
  66. package/lib/module/domain/groupChannel/component/GroupChannelInput/index.js +1 -0
  67. package/lib/module/domain/groupChannel/component/GroupChannelInput/index.js.map +1 -1
  68. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js +3 -2
  69. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
  70. package/lib/module/domain/groupChannelSettings/module/moduleContext.js +2 -2
  71. package/lib/module/domain/groupChannelSettings/module/moduleContext.js.map +1 -1
  72. package/lib/module/fragments/createGroupChannelListFragment.js +5 -9
  73. package/lib/module/fragments/createGroupChannelListFragment.js.map +1 -1
  74. package/lib/module/hooks/useKeyboardStatus.js +12 -9
  75. package/lib/module/hooks/useKeyboardStatus.js.map +1 -1
  76. package/lib/module/index.js.map +1 -1
  77. package/lib/module/libs/ImageCompressionConfig.js +30 -0
  78. package/lib/module/libs/ImageCompressionConfig.js.map +1 -0
  79. package/lib/module/libs/SBUUtils.js +26 -1
  80. package/lib/module/libs/SBUUtils.js.map +1 -1
  81. package/lib/module/localization/StringSet.type.js +3 -0
  82. package/lib/module/localization/StringSet.type.js.map +1 -1
  83. package/lib/module/platform/createMediaService.expo.js +33 -1
  84. package/lib/module/platform/createMediaService.expo.js.map +1 -1
  85. package/lib/module/platform/createMediaService.native.js +38 -5
  86. package/lib/module/platform/createMediaService.native.js.map +1 -1
  87. package/lib/module/platform/dynamicModule.js +10 -56
  88. package/lib/module/platform/dynamicModule.js.map +1 -1
  89. package/lib/module/platform/types.js.map +1 -1
  90. package/lib/module/version.js +1 -1
  91. package/lib/module/version.js.map +1 -1
  92. package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +10 -7
  93. package/lib/typescript/src/contexts/PlatformServiceCtx.d.ts +8 -2
  94. package/lib/typescript/src/contexts/SendbirdChatCtx.d.ts +8 -3
  95. package/lib/typescript/src/hooks/useContext.d.ts +2 -29
  96. package/lib/typescript/src/index.d.ts +1 -1
  97. package/lib/typescript/src/libs/ImageCompressionConfig.d.ts +16 -0
  98. package/lib/typescript/src/libs/SBUUtils.d.ts +6 -0
  99. package/lib/typescript/src/localization/StringSet.type.d.ts +3 -0
  100. package/lib/typescript/src/platform/createMediaService.expo.d.ts +5 -1
  101. package/lib/typescript/src/platform/createMediaService.native.d.ts +7 -5
  102. package/lib/typescript/src/platform/dynamicModule.d.ts +7 -12
  103. package/lib/typescript/src/platform/types.d.ts +28 -3
  104. package/lib/typescript/src/version.d.ts +1 -1
  105. package/package.json +15 -5
  106. package/src/components/FileViewer.tsx +2 -5
  107. package/src/components/MessageRenderer/FileMessage/ImageFileMessage.tsx +6 -1
  108. package/src/components/MessageRenderer/FileMessage/VideoFileMessage.tsx +1 -1
  109. package/src/components/MessageRenderer/FileMessage/index.tsx +1 -4
  110. package/src/components/MessageRenderer/UserMessage/BaseUserMessage.tsx +2 -2
  111. package/src/components/MessageRenderer/UserMessage/OpenGraphUserMessage.tsx +4 -4
  112. package/src/containers/SendbirdUIKitContainer.tsx +25 -6
  113. package/src/contexts/PlatformServiceCtx.tsx +9 -2
  114. package/src/contexts/SendbirdChatCtx.tsx +22 -19
  115. package/src/domain/groupChannel/component/GroupChannelInput/SendInput.tsx +82 -14
  116. package/src/domain/groupChannel/component/GroupChannelInput/index.tsx +1 -0
  117. package/src/domain/groupChannel/component/GroupChannelMessageList.tsx +3 -2
  118. package/src/domain/groupChannelSettings/module/moduleContext.tsx +8 -2
  119. package/src/fragments/createGroupChannelListFragment.tsx +5 -9
  120. package/src/hooks/useKeyboardStatus.ts +10 -5
  121. package/src/index.ts +1 -0
  122. package/src/libs/ImageCompressionConfig.ts +31 -0
  123. package/src/libs/SBUUtils.ts +28 -1
  124. package/src/localization/StringSet.type.ts +7 -0
  125. package/src/platform/createMediaService.expo.tsx +24 -1
  126. package/src/platform/createMediaService.native.tsx +31 -9
  127. package/src/platform/dynamicModule.ts +17 -59
  128. package/src/platform/types.ts +29 -7
  129. package/src/version.ts +1 -1
  130. package/lib/commonjs/utils/common.js +0 -19
  131. package/lib/commonjs/utils/common.js.map +0 -1
  132. package/lib/module/utils/common.js +0 -7
  133. package/lib/module/utils/common.js.map +0 -1
  134. package/lib/typescript/src/utils/common.d.ts +0 -1
  135. package/src/utils/common.ts +0 -8
@@ -30,6 +30,8 @@ import type { UIKitFeaturesInSendbirdChatContext } from '../contexts/SendbirdCha
30
30
  import { SendbirdChatProvider } from '../contexts/SendbirdChatCtx';
31
31
  import { UserProfileProvider } from '../contexts/UserProfileCtx';
32
32
  import EmojiManager from '../libs/EmojiManager';
33
+ import type { ImageCompressionConfigInterface } from '../libs/ImageCompressionConfig';
34
+ import ImageCompressionConfig from '../libs/ImageCompressionConfig';
33
35
  import InternalLocalCacheStorage from '../libs/InternalLocalCacheStorage';
34
36
  import MentionConfig, { MentionConfigInterface } from '../libs/MentionConfig';
35
37
  import MentionManager from '../libs/MentionManager';
@@ -57,6 +59,7 @@ export const SendbirdUIKit = Object.freeze({
57
59
  CHANNEL_LIST_MESSAGE_RECEIPT_STATUS: false,
58
60
  USE_USER_ID_FOR_NICKNAME: false,
59
61
  USER_MENTION: false,
62
+ IMAGE_COMPRESSION: true,
60
63
  },
61
64
  });
62
65
 
@@ -66,7 +69,7 @@ export type SendbirdUIKitContainerProps = React.PropsWithChildren<{
66
69
  file: FileServiceInterface;
67
70
  notification: NotificationServiceInterface;
68
71
  clipboard: ClipboardServiceInterface;
69
- media?: MediaServiceInterface;
72
+ media: MediaServiceInterface;
70
73
  };
71
74
  chatOptions?: {
72
75
  localCacheStorage?: LocalCacheStorage;
@@ -82,6 +85,10 @@ export type SendbirdUIKitContainerProps = React.PropsWithChildren<{
82
85
  defaultHeaderHeight?: number;
83
86
  HeaderComponent?: HeaderStyleContextType['HeaderComponent'];
84
87
  };
88
+ errorBoundary?: {
89
+ onError?: (props: ErrorBoundaryProps) => void;
90
+ ErrorInfoComponent?: (props: ErrorBoundaryProps) => JSX.Element;
91
+ };
85
92
  toast?: {
86
93
  dismissTimeout?: number;
87
94
  };
@@ -93,10 +100,7 @@ export type SendbirdUIKitContainerProps = React.PropsWithChildren<{
93
100
  ) => SendbirdGroupChannelCreateParams | Promise<SendbirdGroupChannelCreateParams>;
94
101
  };
95
102
  userMention?: Pick<Partial<MentionConfigInterface>, 'mentionLimit' | 'suggestionLimit' | 'debounceMills'>;
96
- errorBoundary?: {
97
- onError?: (props: ErrorBoundaryProps) => void;
98
- ErrorInfoComponent?: (props: ErrorBoundaryProps) => JSX.Element;
99
- };
103
+ imageCompression?: Partial<ImageCompressionConfigInterface>;
100
104
  }>;
101
105
 
102
106
  const SendbirdUIKitContainer = ({
@@ -106,10 +110,11 @@ const SendbirdUIKitContainer = ({
106
110
  platformServices,
107
111
  localization,
108
112
  styles,
113
+ errorBoundary,
109
114
  toast,
110
115
  userProfile,
111
116
  userMention,
112
- errorBoundary,
117
+ imageCompression,
113
118
  }: SendbirdUIKitContainerProps) => {
114
119
  const defaultStringSet = localization?.stringSet ?? StringSetEn;
115
120
 
@@ -125,7 +130,9 @@ const SendbirdUIKitContainer = ({
125
130
  unsubscribes.current = sendbird.unsubscribes;
126
131
  return sendbird.chatSDK;
127
132
  });
133
+
128
134
  const emojiManager = useMemo(() => new EmojiManager(internalStorage), [internalStorage]);
135
+
129
136
  const mentionManager = useMemo(() => {
130
137
  const config = new MentionConfig({
131
138
  mentionLimit: userMention?.mentionLimit || MentionConfig.DEFAULT.MENTION_LIMIT,
@@ -137,6 +144,16 @@ const SendbirdUIKitContainer = ({
137
144
  return new MentionManager(config, chatOptions?.enableUserMention ?? SendbirdUIKit.DEFAULT.USER_MENTION);
138
145
  }, [userMention?.mentionLimit, userMention?.suggestionLimit, userMention?.debounceMills]);
139
146
 
147
+ const imageCompressionConfig = useMemo(
148
+ () =>
149
+ new ImageCompressionConfig({
150
+ compressionRate: imageCompression?.compressionRate || ImageCompressionConfig.DEFAULT.COMPRESSION_RATE,
151
+ width: imageCompression?.width,
152
+ height: imageCompression?.height,
153
+ }),
154
+ [imageCompression?.compressionRate, imageCompression?.width, imageCompression?.height],
155
+ );
156
+
140
157
  useLayoutEffect(() => {
141
158
  if (!isFirstMount) {
142
159
  const sendbird = initializeSendbird(appId, internalStorage, chatOptions?.onInitialized);
@@ -161,6 +178,7 @@ const SendbirdUIKitContainer = ({
161
178
  sdkInstance={sdkInstance}
162
179
  emojiManager={emojiManager}
163
180
  mentionManager={mentionManager}
181
+ imageCompressionConfig={imageCompressionConfig}
164
182
  enableAutoPushTokenRegistration={
165
183
  chatOptions?.enableAutoPushTokenRegistration ?? SendbirdUIKit.DEFAULT.AUTO_PUSH_TOKEN_REGISTRATION
166
184
  }
@@ -175,6 +193,7 @@ const SendbirdUIKitContainer = ({
175
193
  chatOptions?.enableUseUserIdForNickname ?? SendbirdUIKit.DEFAULT.USE_USER_ID_FOR_NICKNAME
176
194
  }
177
195
  enableUserMention={chatOptions?.enableUserMention ?? SendbirdUIKit.DEFAULT.USER_MENTION}
196
+ enableImageCompression={chatOptions?.enableImageCompression ?? SendbirdUIKit.DEFAULT.IMAGE_COMPRESSION}
178
197
  >
179
198
  <LocalizationProvider stringSet={defaultStringSet}>
180
199
  <PlatformServiceProvider
@@ -11,10 +11,17 @@ type Props = React.PropsWithChildren<{
11
11
  fileService: FileServiceInterface;
12
12
  clipboardService: ClipboardServiceInterface;
13
13
  notificationService: NotificationServiceInterface;
14
- mediaService?: MediaServiceInterface;
14
+ mediaService: MediaServiceInterface;
15
15
  }>;
16
16
 
17
- export const PlatformServiceContext = React.createContext<Props | null>(null);
17
+ export type PlatformServiceContextType = {
18
+ fileService: FileServiceInterface;
19
+ clipboardService: ClipboardServiceInterface;
20
+ notificationService: NotificationServiceInterface;
21
+ mediaService: MediaServiceInterface;
22
+ };
23
+
24
+ export const PlatformServiceContext = React.createContext<PlatformServiceContextType | null>(null);
18
25
  export const PlatformServiceProvider = ({
19
26
  children,
20
27
  fileService,
@@ -1,5 +1,4 @@
1
- import React, { useCallback, useEffect, useState } from 'react';
2
- import { AppState, AppStateStatus } from 'react-native';
1
+ import React, { useCallback, useState } from 'react';
3
2
 
4
3
  import { useAppFeatures } from '@sendbird/uikit-chat-hooks';
5
4
  import type {
@@ -8,9 +7,10 @@ import type {
8
7
  SendbirdUser,
9
8
  SendbirdUserUpdateParams,
10
9
  } from '@sendbird/uikit-utils';
11
- import { confirmAndMarkAsDelivered, useForceUpdate } from '@sendbird/uikit-utils';
10
+ import { confirmAndMarkAsDelivered, useAppState, useForceUpdate } from '@sendbird/uikit-utils';
12
11
 
13
12
  import type EmojiManager from '../libs/EmojiManager';
13
+ import type ImageCompressionConfig from '../libs/ImageCompressionConfig';
14
14
  import type MentionManager from '../libs/MentionManager';
15
15
  import type { FileType } from '../platform/types';
16
16
 
@@ -20,18 +20,21 @@ export interface UIKitFeaturesInSendbirdChatContext {
20
20
  enableChannelListMessageReceiptStatus: boolean;
21
21
  enableUseUserIdForNickname: boolean;
22
22
  enableUserMention: boolean;
23
+ enableImageCompression: boolean;
23
24
  }
24
25
 
25
26
  interface Props extends UIKitFeaturesInSendbirdChatContext, React.PropsWithChildren {
26
27
  sdkInstance: SendbirdChatSDK;
27
28
  emojiManager: EmojiManager;
28
29
  mentionManager: MentionManager;
30
+ imageCompressionConfig: ImageCompressionConfig;
29
31
  }
30
32
 
31
- type Context = {
33
+ export type SendbirdChatContextType = {
32
34
  sdk: SendbirdChatSDK;
33
35
  emojiManager: EmojiManager;
34
36
  mentionManager: MentionManager;
37
+ imageCompressionConfig: ImageCompressionConfig;
35
38
  currentUser?: SendbirdUser;
36
39
  setCurrentUser: React.Dispatch<React.SetStateAction<SendbirdUser | undefined>>;
37
40
 
@@ -46,6 +49,7 @@ type Context = {
46
49
  channelListMessageReceiptStatusEnabled: boolean;
47
50
  useUserIdForNicknameEnabled: boolean;
48
51
  userMentionEnabled: boolean;
52
+ imageCompressionEnabled: boolean;
49
53
 
50
54
  // Sendbird application features
51
55
  deliveryReceiptEnabled: boolean;
@@ -55,29 +59,31 @@ type Context = {
55
59
  };
56
60
  };
57
61
 
58
- export const SendbirdChatContext = React.createContext<Context | null>(null);
62
+ export const SendbirdChatContext = React.createContext<SendbirdChatContextType | null>(null);
59
63
  export const SendbirdChatProvider = ({
60
64
  children,
61
65
  sdkInstance,
62
66
  emojiManager,
63
67
  mentionManager,
68
+ imageCompressionConfig,
64
69
  enableAutoPushTokenRegistration,
65
70
  enableChannelListMessageReceiptStatus,
66
71
  enableChannelListTypingIndicator,
67
72
  enableUseUserIdForNickname,
68
73
  enableUserMention,
74
+ enableImageCompression,
69
75
  }: Props) => {
70
76
  const [currentUser, _setCurrentUser] = useState<SendbirdUser>();
71
77
  const forceUpdate = useForceUpdate();
72
78
  const appFeatures = useAppFeatures(sdkInstance);
73
79
 
74
- const setCurrentUser: Context['setCurrentUser'] = useCallback((user) => {
80
+ const setCurrentUser: SendbirdChatContextType['setCurrentUser'] = useCallback((user) => {
75
81
  // NOTE: Sendbird SDK handle User object is always same object, so force update after setCurrentUser
76
82
  _setCurrentUser(user);
77
83
  forceUpdate();
78
84
  }, []);
79
85
 
80
- const updateCurrentUserInfo: Context['updateCurrentUserInfo'] = useCallback(
86
+ const updateCurrentUserInfo: SendbirdChatContextType['updateCurrentUserInfo'] = useCallback(
81
87
  async (nickname, profile) => {
82
88
  let user = currentUser;
83
89
 
@@ -109,28 +115,24 @@ export const SendbirdChatProvider = ({
109
115
  [sdkInstance, currentUser, setCurrentUser],
110
116
  );
111
117
 
112
- const markAsDeliveredWithChannel: Context['markAsDeliveredWithChannel'] = useCallback(
118
+ const markAsDeliveredWithChannel: SendbirdChatContextType['markAsDeliveredWithChannel'] = useCallback(
113
119
  (channel: SendbirdGroupChannel) => {
114
120
  if (appFeatures.deliveryReceiptEnabled) confirmAndMarkAsDelivered([channel]);
115
121
  },
116
122
  [sdkInstance, appFeatures.deliveryReceiptEnabled],
117
123
  );
118
124
 
119
- useEffect(() => {
120
- const listener = (status: AppStateStatus) => {
121
- // 'active' | 'background' | 'inactive' | 'unknown' | 'extension';
122
- if (status === 'active') sdkInstance.connectionState === 'CLOSED' && sdkInstance.setForegroundState();
123
- else if (status === 'background') sdkInstance.connectionState === 'OPEN' && sdkInstance.setBackgroundState();
124
- };
125
+ useAppState('change', (status) => {
126
+ // 'active' | 'background' | 'inactive' | 'unknown' | 'extension';
127
+ if (status === 'active') sdkInstance.connectionState === 'CLOSED' && sdkInstance.setForegroundState();
128
+ else if (status === 'background') sdkInstance.connectionState === 'OPEN' && sdkInstance.setBackgroundState();
129
+ });
125
130
 
126
- const subscriber = AppState.addEventListener('change', listener);
127
- return () => subscriber.remove();
128
- }, [sdkInstance]);
129
-
130
- const value: Context = {
131
+ const value: SendbirdChatContextType = {
131
132
  sdk: sdkInstance,
132
133
  emojiManager,
133
134
  mentionManager,
135
+ imageCompressionConfig,
134
136
  currentUser,
135
137
  setCurrentUser,
136
138
 
@@ -144,6 +146,7 @@ export const SendbirdChatProvider = ({
144
146
  channelListMessageReceiptStatusEnabled: enableChannelListMessageReceiptStatus,
145
147
  useUserIdForNicknameEnabled: enableUseUserIdForNickname,
146
148
  userMentionEnabled: enableUserMention,
149
+ imageCompressionEnabled: enableImageCompression,
147
150
  },
148
151
  };
149
152
 
@@ -18,7 +18,7 @@ import {
18
18
  useToast,
19
19
  useUIKitTheme,
20
20
  } from '@sendbird/uikit-react-native-foundation';
21
- import { conditionChaining } from '@sendbird/uikit-utils';
21
+ import { conditionChaining, isImage, shouldCompressImage } from '@sendbird/uikit-utils';
22
22
 
23
23
  import { useLocalization, usePlatformService, useSendbirdChat } from '../../../../hooks/useContext';
24
24
  import SBUError from '../../../../libs/SBUError';
@@ -49,9 +49,9 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
49
49
  },
50
50
  ref,
51
51
  ) {
52
- const { mentionManager } = useSendbirdChat();
52
+ const { mentionManager, imageCompressionConfig, features } = useSendbirdChat();
53
53
  const { STRINGS } = useLocalization();
54
- const { fileService } = usePlatformService();
54
+ const { fileService, mediaService } = usePlatformService();
55
55
  const { colors } = useUIKitTheme();
56
56
  const { openSheet } = useBottomSheet();
57
57
  const { alert } = useAlert();
@@ -75,13 +75,16 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
75
75
  title: STRINGS.GROUP_CHANNEL.DIALOG_ATTACHMENT_CAMERA,
76
76
  icon: 'camera',
77
77
  onPress: async () => {
78
- const photo = await fileService.openCamera({
78
+ const mediaFile = await fileService.openCamera({
79
79
  mediaType: 'all',
80
80
  onOpenFailure: (error) => {
81
81
  if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
82
82
  alert({
83
83
  title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
84
- message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('camera', 'UIKitSample'),
84
+ message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE(
85
+ STRINGS.LABELS.PERMISSION_CAMERA,
86
+ STRINGS.LABELS.PERMISSION_APP_NAME,
87
+ ),
85
88
  buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
86
89
  });
87
90
  } else {
@@ -90,8 +93,28 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
90
93
  },
91
94
  });
92
95
 
93
- if (photo) {
94
- onSendFileMessage(photo).catch(() => toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error'));
96
+ if (mediaFile) {
97
+ // Image compression
98
+ if (
99
+ isImage(mediaFile.uri, mediaFile.type) &&
100
+ shouldCompressImage(mediaFile.uri, features.imageCompressionEnabled)
101
+ ) {
102
+ await SBUUtils.safeRun(async () => {
103
+ const compressed = await mediaService.compressImage({
104
+ uri: mediaFile.uri,
105
+ maxWidth: imageCompressionConfig.width,
106
+ maxHeight: imageCompressionConfig.height,
107
+ compressionRate: imageCompressionConfig.compressionRate,
108
+ });
109
+
110
+ if (compressed) {
111
+ mediaFile.uri = compressed.uri;
112
+ mediaFile.size = compressed.size;
113
+ }
114
+ });
115
+ }
116
+
117
+ onSendFileMessage(mediaFile).catch(() => toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error'));
95
118
  }
96
119
  },
97
120
  },
@@ -99,14 +122,17 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
99
122
  title: STRINGS.GROUP_CHANNEL.DIALOG_ATTACHMENT_PHOTO_LIBRARY,
100
123
  icon: 'photo',
101
124
  onPress: async () => {
102
- const photo = await fileService.openMediaLibrary({
125
+ const mediaFiles = await fileService.openMediaLibrary({
103
126
  selectionLimit: 1,
104
127
  mediaType: 'all',
105
128
  onOpenFailure: (error) => {
106
129
  if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
107
130
  alert({
108
131
  title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
109
- message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('device storage', 'UIKitSample'),
132
+ message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE(
133
+ STRINGS.LABELS.PERMISSION_DEVICE_STORAGE,
134
+ STRINGS.LABELS.PERMISSION_APP_NAME,
135
+ ),
110
136
  buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
111
137
  });
112
138
  } else {
@@ -115,8 +141,30 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
115
141
  },
116
142
  });
117
143
 
118
- if (photo && photo[0]) {
119
- onSendFileMessage(photo[0]).catch(() => toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error'));
144
+ if (mediaFiles && mediaFiles[0]) {
145
+ const mediaFile = mediaFiles[0];
146
+
147
+ // Image compression
148
+ if (
149
+ isImage(mediaFile.uri, mediaFile.type) &&
150
+ shouldCompressImage(mediaFile.uri, features.imageCompressionEnabled)
151
+ ) {
152
+ await SBUUtils.safeRun(async () => {
153
+ const compressed = await mediaService.compressImage({
154
+ uri: mediaFile.uri,
155
+ maxWidth: imageCompressionConfig.width,
156
+ maxHeight: imageCompressionConfig.height,
157
+ compressionRate: imageCompressionConfig.compressionRate,
158
+ });
159
+
160
+ if (compressed) {
161
+ mediaFile.uri = compressed.uri;
162
+ mediaFile.size = compressed.size;
163
+ }
164
+ });
165
+ }
166
+
167
+ onSendFileMessage(mediaFiles[0]).catch(() => toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error'));
120
168
  }
121
169
  },
122
170
  },
@@ -124,12 +172,32 @@ const SendInput = forwardRef<RNTextInput, SendInputProps>(function SendInput(
124
172
  title: STRINGS.GROUP_CHANNEL.DIALOG_ATTACHMENT_FILES,
125
173
  icon: 'document',
126
174
  onPress: async () => {
127
- const file = await fileService.openDocument({
175
+ const documentFile = await fileService.openDocument({
128
176
  onOpenFailure: () => toast.show(STRINGS.TOAST.OPEN_FILES_ERROR, 'error'),
129
177
  });
130
178
 
131
- if (file) {
132
- onSendFileMessage(file).catch(() => toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error'));
179
+ if (documentFile) {
180
+ // Image compression
181
+ if (
182
+ isImage(documentFile.uri, documentFile.type) &&
183
+ shouldCompressImage(documentFile.uri, features.imageCompressionEnabled)
184
+ ) {
185
+ await SBUUtils.safeRun(async () => {
186
+ const compressed = await mediaService.compressImage({
187
+ uri: documentFile.uri,
188
+ maxWidth: imageCompressionConfig.width,
189
+ maxHeight: imageCompressionConfig.height,
190
+ compressionRate: imageCompressionConfig.compressionRate,
191
+ });
192
+
193
+ if (compressed) {
194
+ documentFile.uri = compressed.uri;
195
+ documentFile.size = compressed.size;
196
+ }
197
+ });
198
+ }
199
+
200
+ onSendFileMessage(documentFile).catch(() => toast.show(STRINGS.TOAST.SEND_MSG_ERROR, 'error'));
133
201
  }
134
202
  },
135
203
  },
@@ -24,6 +24,7 @@ const KEYBOARD_AVOID_VIEW_BEHAVIOR = Platform.select({ ios: 'padding' as const,
24
24
 
25
25
  // FIXME(iOS): Dynamic style does not work properly when typing the CJK. (https://github.com/facebook/react-native/issues/26107)
26
26
  // To workaround temporarily, change the key for re-mount the component.
27
+ // -> This will affect to keyboard blur when add/remove first mentioned user.
27
28
  const GET_INPUT_KEY = (shouldReset: boolean) => (shouldReset ? 'uikit-input-clear' : 'uikit-input');
28
29
 
29
30
  // TODO: Refactor 'Edit' mode to clearly
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
2
- import { Linking, ListRenderItem, Platform, View } from 'react-native';
2
+ import { ListRenderItem, Platform, View } from 'react-native';
3
3
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
4
4
 
5
5
  import type { BottomSheetItem } from '@sendbird/uikit-react-native-foundation';
@@ -29,6 +29,7 @@ import ChatFlatList from '../../../components/ChatFlatList';
29
29
  import { ReactionAddons } from '../../../components/ReactionAddons';
30
30
  import { DEPRECATION_WARNING } from '../../../constants';
31
31
  import { useLocalization, usePlatformService, useSendbirdChat } from '../../../hooks/useContext';
32
+ import SBUUtils from '../../../libs/SBUUtils';
32
33
  import { GroupChannelContexts } from '../module/moduleContext';
33
34
  import type { GroupChannelProps } from '../types';
34
35
 
@@ -281,7 +282,7 @@ const useGetMessagePressActions = ({
281
282
  break;
282
283
  }
283
284
  default: {
284
- response.onPress = () => Linking.openURL(msg.url).catch();
285
+ response.onPress = () => SBUUtils.openURL(msg.url);
285
286
  break;
286
287
  }
287
288
  }
@@ -84,7 +84,10 @@ export const GroupChannelSettingsContextsProvider: GroupChannelSettingsModule['P
84
84
  if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
85
85
  alert({
86
86
  title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
87
- message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('camera', 'UIKitSample'),
87
+ message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE(
88
+ STRINGS.LABELS.PERMISSION_CAMERA,
89
+ STRINGS.LABELS.PERMISSION_APP_NAME,
90
+ ),
88
91
  buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
89
92
  });
90
93
  } else {
@@ -107,7 +110,10 @@ export const GroupChannelSettingsContextsProvider: GroupChannelSettingsModule['P
107
110
  if (error.code === SBUError.CODE.ERR_PERMISSIONS_DENIED) {
108
111
  alert({
109
112
  title: STRINGS.DIALOG.ALERT_PERMISSIONS_TITLE,
110
- message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE('device storage', 'UIKitSample'),
113
+ message: STRINGS.DIALOG.ALERT_PERMISSIONS_MESSAGE(
114
+ STRINGS.LABELS.PERMISSION_DEVICE_STORAGE,
115
+ STRINGS.LABELS.PERMISSION_APP_NAME,
116
+ ),
111
117
  buttons: [{ text: STRINGS.DIALOG.ALERT_PERMISSIONS_OK, onPress: () => SBUUtils.openSettings() }],
112
118
  });
113
119
  } else {
@@ -1,8 +1,7 @@
1
- import React, { useEffect } from 'react';
2
- import { AppState } from 'react-native';
1
+ import React from 'react';
3
2
 
4
3
  import { useGroupChannelList } from '@sendbird/uikit-chat-hooks';
5
- import { PASS, useFreshCallback } from '@sendbird/uikit-utils';
4
+ import { PASS, useAppState, useFreshCallback } from '@sendbird/uikit-utils';
6
5
 
7
6
  import StatusComposition from '../components/StatusComposition';
8
7
  import GroupChannelPreviewContainer from '../containers/GroupChannelPreviewContainer';
@@ -34,12 +33,9 @@ const createGroupChannelListFragment = (initModule?: Partial<GroupChannelListMod
34
33
  });
35
34
 
36
35
  if (features.deliveryReceiptEnabled) {
37
- useEffect(() => {
38
- const listener = AppState.addEventListener('change', (status) => {
39
- if (status === 'active') groupChannels.forEach(markAsDeliveredWithChannel);
40
- });
41
- return () => listener.remove();
42
- }, []);
36
+ useAppState('change', (status) => {
37
+ if (status === 'active') groupChannels.forEach(markAsDeliveredWithChannel);
38
+ });
43
39
  }
44
40
 
45
41
  const _renderGroupChannelPreview: GroupChannelListProps['List']['renderGroupChannelPreview'] = useFreshCallback(
@@ -9,9 +9,14 @@ type KeyboardEvents = {
9
9
  hideEvent: KeyboardEventName;
10
10
  };
11
11
 
12
+ let isLayoutAnimationConfigured = false;
13
+
12
14
  const configureNextLayoutAnimation = (event: KeyboardEvent) => {
13
- const config = LayoutAnimation.create(event.duration, event.easing);
14
- LayoutAnimation.configureNext(config);
15
+ if (isLayoutAnimationConfigured) return;
16
+ const config = LayoutAnimation.create(event.duration, event.easing, LayoutAnimation.Properties.scaleY);
17
+ isLayoutAnimationConfigured = true;
18
+ const onEnd = () => (isLayoutAnimationConfigured = false);
19
+ LayoutAnimation.configureNext(config, onEnd, onEnd);
15
20
  };
16
21
 
17
22
  const { showEvent, hideEvent } = Platform.select<KeyboardEvents>({
@@ -34,12 +39,12 @@ const useKeyboardStatus = () => {
34
39
  setKeyboardStatus({ visible: true, height, bottomSpace });
35
40
  }),
36
41
 
37
- Keyboard.addListener(hideEvent, (event) => {
42
+ Keyboard.addListener(hideEvent, () => {
38
43
  const height = 0;
39
44
  const bottomSpace = Platform.select({ default: height });
40
- const nextLayoutAnimation = Platform.select({ ios: configureNextLayoutAnimation, default: NOOP });
45
+ // const nextLayoutAnimation = Platform.select({ ios: configureNextLayoutAnimation, default: NOOP });
41
46
 
42
- nextLayoutAnimation(event);
47
+ // nextLayoutAnimation(event);
43
48
  setKeyboardStatus({ visible: false, height, bottomSpace });
44
49
  }),
45
50
  ];
package/src/index.ts CHANGED
@@ -72,6 +72,7 @@ export type {
72
72
  FilePickerResponse,
73
73
  FileType,
74
74
  NotificationServiceInterface,
75
+ MediaServiceInterface,
75
76
  } from './platform/types';
76
77
 
77
78
  /** Domain **/
@@ -0,0 +1,31 @@
1
+ import { Logger } from '@sendbird/uikit-utils';
2
+
3
+ export interface ImageCompressionConfigInterface {
4
+ compressionRate: number;
5
+ width?: number;
6
+ height?: number;
7
+ }
8
+
9
+ class ImageCompressionConfig {
10
+ static DEFAULT = {
11
+ COMPRESSION_RATE: 0.7,
12
+ };
13
+
14
+ constructor(private _config: ImageCompressionConfigInterface) {
15
+ if (_config.compressionRate > 1) Logger.warn('Compression rate must be in the range of 0.0 - 1.0');
16
+ }
17
+
18
+ get compressionRate() {
19
+ return Math.min(Math.max(0, this._config.compressionRate), 1);
20
+ }
21
+
22
+ get width() {
23
+ return this._config.width;
24
+ }
25
+
26
+ get height() {
27
+ return this._config.height;
28
+ }
29
+ }
30
+
31
+ export default ImageCompressionConfig;
@@ -1,4 +1,6 @@
1
- import { Linking, Platform } from 'react-native';
1
+ import { Image, Linking, Platform } from 'react-native';
2
+
3
+ import { Logger } from '@sendbird/uikit-utils';
2
4
 
3
5
  export default class SBUUtils {
4
6
  static openSettings() {
@@ -6,4 +8,29 @@ export default class SBUUtils {
6
8
  if (Platform.OS === 'ios') Linking.openURL('App-Prefs:root');
7
9
  });
8
10
  }
11
+
12
+ static openURL(url: string) {
13
+ const targetUrl = url.startsWith('http') ? url : 'https://' + url;
14
+ Linking.openURL(targetUrl).catch((err) => Logger.warn('Cannot open url', err));
15
+ }
16
+
17
+ static getImageSize(uri: string): Promise<{ width: number; height: number }> {
18
+ return new Promise((resolve, reject) => {
19
+ Image.getSize(
20
+ uri,
21
+ (width, height) => {
22
+ resolve({ width, height });
23
+ },
24
+ (error) => {
25
+ reject(error);
26
+ },
27
+ );
28
+ });
29
+ }
30
+
31
+ static async safeRun(callback: () => Promise<void>) {
32
+ try {
33
+ await callback();
34
+ } catch (e) {}
35
+ }
9
36
  }
@@ -169,6 +169,10 @@ export interface StringSet {
169
169
  };
170
170
  // UI
171
171
  LABELS: {
172
+ PERMISSION_APP_NAME: string;
173
+ PERMISSION_CAMERA: string;
174
+ PERMISSION_DEVICE_STORAGE: string;
175
+
172
176
  USER_NO_NAME: string;
173
177
  CHANNEL_NO_MEMBERS: string;
174
178
  TYPING_INDICATOR_TYPINGS: (users: SendbirdUser[]) => string | undefined;
@@ -391,6 +395,9 @@ export const createBaseStringSet = ({ dateLocale, overrides }: StringSetCreateOp
391
395
  ...overrides?.GROUP_CHANNEL_INVITE,
392
396
  },
393
397
  LABELS: {
398
+ PERMISSION_APP_NAME: 'Application',
399
+ PERMISSION_CAMERA: 'camera',
400
+ PERMISSION_DEVICE_STORAGE: 'device storage',
394
401
  USER_NO_NAME,
395
402
  CHANNEL_NO_MEMBERS,
396
403
  TYPING_INDICATOR_TYPINGS: (users, NO_NAME = USER_NO_NAME) => {