@sendbird/uikit-react-native 3.11.0 → 3.11.2

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 (48) hide show
  1. package/README.md +7 -5
  2. package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js +0 -3
  3. package/lib/commonjs/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
  4. package/lib/commonjs/platform/createMediaService.expo.js +83 -12
  5. package/lib/commonjs/platform/createMediaService.expo.js.map +1 -1
  6. package/lib/commonjs/platform/createNotificationService.native.js +31 -6
  7. package/lib/commonjs/platform/createNotificationService.native.js.map +1 -1
  8. package/lib/commonjs/platform/createPlayerService.expo.js +214 -113
  9. package/lib/commonjs/platform/createPlayerService.expo.js.map +1 -1
  10. package/lib/commonjs/platform/createRecorderService.expo.js +248 -130
  11. package/lib/commonjs/platform/createRecorderService.expo.js.map +1 -1
  12. package/lib/commonjs/utils/expoBackwardUtils.js +23 -0
  13. package/lib/commonjs/utils/expoBackwardUtils.js.map +1 -1
  14. package/lib/commonjs/utils/expoPermissionGranted.js.map +1 -1
  15. package/lib/commonjs/version.js +1 -1
  16. package/lib/commonjs/version.js.map +1 -1
  17. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js +0 -3
  18. package/lib/module/domain/groupChannel/component/GroupChannelMessageList.js.map +1 -1
  19. package/lib/module/platform/createMediaService.expo.js +82 -13
  20. package/lib/module/platform/createMediaService.expo.js.map +1 -1
  21. package/lib/module/platform/createNotificationService.native.js +32 -6
  22. package/lib/module/platform/createNotificationService.native.js.map +1 -1
  23. package/lib/module/platform/createPlayerService.expo.js +214 -113
  24. package/lib/module/platform/createPlayerService.expo.js.map +1 -1
  25. package/lib/module/platform/createRecorderService.expo.js +249 -131
  26. package/lib/module/platform/createRecorderService.expo.js.map +1 -1
  27. package/lib/module/utils/expoBackwardUtils.js +23 -0
  28. package/lib/module/utils/expoBackwardUtils.js.map +1 -1
  29. package/lib/module/utils/expoPermissionGranted.js.map +1 -1
  30. package/lib/module/version.js +1 -1
  31. package/lib/module/version.js.map +1 -1
  32. package/lib/typescript/src/containers/SendbirdUIKitContainer.d.ts +1 -1
  33. package/lib/typescript/src/platform/createMediaService.expo.d.ts +2 -2
  34. package/lib/typescript/src/platform/createNotificationService.native.d.ts +13 -1
  35. package/lib/typescript/src/platform/createPlayerService.expo.d.ts +2 -2
  36. package/lib/typescript/src/platform/createRecorderService.expo.d.ts +2 -2
  37. package/lib/typescript/src/utils/expoBackwardUtils.d.ts +10 -0
  38. package/lib/typescript/src/utils/expoPermissionGranted.d.ts +1 -1
  39. package/lib/typescript/src/version.d.ts +1 -1
  40. package/package.json +16 -5
  41. package/src/domain/groupChannel/component/GroupChannelMessageList.tsx +0 -3
  42. package/src/platform/createMediaService.expo.tsx +87 -9
  43. package/src/platform/createNotificationService.native.ts +53 -7
  44. package/src/platform/createPlayerService.expo.tsx +242 -109
  45. package/src/platform/createRecorderService.expo.tsx +267 -110
  46. package/src/utils/expoBackwardUtils.ts +29 -0
  47. package/src/utils/expoPermissionGranted.ts +3 -1
  48. package/src/version.ts +1 -1
@@ -1,7 +1,7 @@
1
- import * as ExpoAV from 'expo-av';
1
+ import type { ExpoAudioModule } from '../utils/expoBackwardUtils';
2
2
  import type { RecorderServiceInterface } from './types';
3
3
  type Modules = {
4
- avModule: typeof ExpoAV;
4
+ avModule: ExpoAudioModule;
5
5
  };
6
6
  declare const createExpoRecorderService: ({ avModule }: Modules) => RecorderServiceInterface;
7
7
  export default createExpoRecorderService;
@@ -1,6 +1,9 @@
1
+ import type * as ExpoAudio from 'expo-audio';
2
+ import type * as ExpoAV from 'expo-av';
1
3
  import type * as ExpoDocumentPicker from 'expo-document-picker';
2
4
  import type * as ExpoFs from 'expo-file-system';
3
5
  import type * as ExpoImagePicker from 'expo-image-picker';
6
+ import type * as ExpoVideo from 'expo-video';
4
7
  import type { FilePickerResponse } from '../platform/types';
5
8
  declare const expoBackwardUtils: {
6
9
  imagePicker: {
@@ -11,6 +14,13 @@ declare const expoBackwardUtils: {
11
14
  isCanceled(result: ExpoDocumentPicker.DocumentPickerResult): boolean;
12
15
  toFilePickerResponses(result: ExpoDocumentPicker.DocumentPickerResult): Promise<FilePickerResponse[]>;
13
16
  };
17
+ expoAV: {
18
+ isLegacyAVModule(module: ExpoAudioModule | ExpoVideoModule): module is typeof ExpoAV;
19
+ isAudioModule(module: ExpoAudioModule): module is typeof ExpoAudio;
20
+ isVideoModule(module: ExpoVideoModule): module is typeof ExpoVideo;
21
+ };
14
22
  toFileSize(info: ExpoFs.FileInfo): number;
15
23
  };
24
+ export type ExpoAudioModule = typeof ExpoAV | typeof ExpoAudio;
25
+ export type ExpoVideoModule = typeof ExpoAV | typeof ExpoVideo;
16
26
  export default expoBackwardUtils;
@@ -7,7 +7,7 @@ export interface ExpoPermissionResponse {
7
7
  export interface ExpoMediaLibraryPermissionResponse extends ExpoPermissionResponse {
8
8
  accessPrivileges?: 'all' | 'limited' | 'none';
9
9
  }
10
- export interface ExpoPushPermissionResponse extends ExpoPermissionResponse, NotificationPermissionsStatus {
10
+ export interface ExpoPushPermissionResponse extends Omit<ExpoPermissionResponse, 'status'>, NotificationPermissionsStatus {
11
11
  }
12
12
  declare const expoPermissionGranted: (stats: Array<ExpoMediaLibraryPermissionResponse | ExpoPushPermissionResponse | ExpoPermissionResponse>, limitedCallback?: () => void) => boolean;
13
13
  export default expoPermissionGranted;
@@ -1,2 +1,2 @@
1
- declare const VERSION = "3.11.0";
1
+ declare const VERSION = "3.11.2";
2
2
  export default VERSION;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendbird/uikit-react-native",
3
- "version": "3.11.0",
3
+ "version": "3.11.2",
4
4
  "description": "Sendbird UIKit for React Native: A feature-rich and customizable chat UI kit with messaging, channel management, and user authentication.",
5
5
  "keywords": [
6
6
  "sendbird",
@@ -60,10 +60,10 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "@openspacelabs/react-native-zoomable-view": "^2.1.5",
63
- "@sendbird/uikit-chat-hooks": "3.11.0",
64
- "@sendbird/uikit-react-native-foundation": "3.11.0",
63
+ "@sendbird/uikit-chat-hooks": "3.11.2",
64
+ "@sendbird/uikit-react-native-foundation": "3.11.2",
65
65
  "@sendbird/uikit-tools": "0.0.15",
66
- "@sendbird/uikit-utils": "3.11.0"
66
+ "@sendbird/uikit-utils": "3.11.2"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@bam.tech/react-native-image-resizer": "^3.0.4",
@@ -77,6 +77,8 @@
77
77
  "@types/react": "*",
78
78
  "@types/react-native": "*",
79
79
  "date-fns": ">=2.28.0",
80
+ "expo": "^54.0.12",
81
+ "expo-audio": "^1.0.13",
80
82
  "expo-av": "^13.2.1",
81
83
  "expo-clipboard": "^4.1.2",
82
84
  "expo-document-picker": "^11.5.3",
@@ -85,6 +87,7 @@
85
87
  "expo-image-picker": "^14.1.1",
86
88
  "expo-media-library": "^16.0.0",
87
89
  "expo-notifications": "^0.18.1",
90
+ "expo-video": "^3.0.11",
88
91
  "expo-video-thumbnails": "^7.2.1",
89
92
  "glob": "^7.2.0",
90
93
  "inquirer": "^8.2.0",
@@ -117,6 +120,7 @@
117
120
  "@sendbird/react-native-scrollview-enhancer": "*",
118
121
  "@sendbird/uikit-tools": ">=0.0.10",
119
122
  "date-fns": ">=2.28.0",
123
+ "expo-audio": ">=1.0.0",
120
124
  "expo-av": ">=12.0.4",
121
125
  "expo-clipboard": ">=2.1.1",
122
126
  "expo-document-picker": ">=10.1.3",
@@ -125,6 +129,7 @@
125
129
  "expo-image-picker": ">=12.0.2",
126
130
  "expo-media-library": ">=16.0.0",
127
131
  "expo-notifications": ">=0.14.1",
132
+ "expo-video": ">=3.0.0",
128
133
  "expo-video-thumbnails": ">=6.4.0",
129
134
  "react": ">=17.0.2",
130
135
  "react-native": ">=0.65.0",
@@ -165,6 +170,12 @@
165
170
  "expo-av": {
166
171
  "optional": true
167
172
  },
173
+ "expo-audio": {
174
+ "optional": true
175
+ },
176
+ "expo-video": {
177
+ "optional": true
178
+ },
168
179
  "expo-clipboard": {
169
180
  "optional": true
170
181
  },
@@ -231,5 +242,5 @@
231
242
  ]
232
243
  ]
233
244
  },
234
- "gitHead": "f0ffc478a073fe16f571a3f298fe101cf8359d4a"
245
+ "gitHead": "2df681bc6eefb1296d78f33982f3beecd3769c66"
235
246
  }
@@ -256,9 +256,6 @@ const GroupChannelMessageList = (props: GroupChannelProps['MessageList']) => {
256
256
  lazyScrollToBottom({ animated: true, timeout: 250 });
257
257
  }
258
258
  },
259
- onChannelChanged(channel) {
260
- if (isDifferentChannel(channel, props.channel)) return;
261
- },
262
259
  });
263
260
 
264
261
  useEffect(() => {
@@ -1,34 +1,112 @@
1
1
  import type * as ExpoAV from 'expo-av';
2
2
  import type * as ExpoFS from 'expo-file-system';
3
3
  import type * as ExpoImageManipulator from 'expo-image-manipulator';
4
+ import type { EventSubscription } from 'expo-modules-core';
5
+ import type * as ExpoVideo from 'expo-video';
6
+ import type { StatusChangeEventPayload } from 'expo-video';
4
7
  import type * as ExpoVideoThumbnail from 'expo-video-thumbnails';
5
- import React from 'react';
8
+ import React, { useEffect } from 'react';
6
9
 
7
- import { getDownscaleSize } from '@sendbird/uikit-utils';
10
+ import { Logger, getDownscaleSize } from '@sendbird/uikit-utils';
8
11
 
9
12
  import SBUUtils from '../libs/SBUUtils';
10
13
  import expoBackwardUtils from '../utils/expoBackwardUtils';
11
- import type { MediaServiceInterface } from './types';
14
+ import type { ExpoVideoModule } from '../utils/expoBackwardUtils';
15
+ import type { MediaServiceInterface, VideoProps } from './types';
12
16
 
13
17
  type Modules = {
14
- avModule: typeof ExpoAV;
18
+ avModule: ExpoVideoModule;
15
19
  thumbnailModule: typeof ExpoVideoThumbnail;
16
20
  imageManipulator: typeof ExpoImageManipulator;
17
21
  fsModule: typeof ExpoFS;
18
22
  };
19
23
 
24
+ interface VideoModuleAdapter {
25
+ VideoComponent: React.ComponentType<VideoProps>;
26
+ }
27
+
28
+ class LegacyExpoAVVideoAdapter implements VideoModuleAdapter {
29
+ private readonly avModule: typeof ExpoAV;
30
+ constructor(avModule: typeof ExpoAV) {
31
+ this.avModule = avModule;
32
+ }
33
+
34
+ VideoComponent = ({ source, resizeMode, onLoad, ...props }: VideoProps) => {
35
+ // FIXME: type error https://github.com/expo/expo/issues/17101
36
+ // @ts-ignore
37
+ return <this.avModule.Video {...props} source={source} resizeMode={resizeMode} onLoad={onLoad} useNativeControls />;
38
+ };
39
+ }
40
+
41
+ class ExpoVideoAdapter implements VideoModuleAdapter {
42
+ constructor(private readonly _videoModule: typeof ExpoVideo) {}
43
+
44
+ VideoComponent = ({ source, resizeMode, onLoad, ...props }: VideoProps) => {
45
+ const player = this._videoModule.useVideoPlayer(source);
46
+
47
+ useEffect(() => {
48
+ if (onLoad && player) {
49
+ let subscription: EventSubscription | null = null;
50
+ try {
51
+ subscription = player.addListener('statusChange', (eventData: StatusChangeEventPayload) => {
52
+ const { status, error } = eventData;
53
+ if (status === 'readyToPlay' && !error) {
54
+ onLoad();
55
+ }
56
+ });
57
+ } catch (error) {
58
+ const timeout = setTimeout(() => onLoad(), 300);
59
+ return () => clearTimeout(timeout);
60
+ }
61
+
62
+ return () => {
63
+ if (subscription) {
64
+ subscription.remove();
65
+ }
66
+ };
67
+ }
68
+ return undefined;
69
+ }, [onLoad, player]);
70
+
71
+ const getContentFit = (mode: typeof resizeMode): 'cover' | 'contain' | 'fill' => {
72
+ switch (mode) {
73
+ case 'cover':
74
+ return 'cover';
75
+ case 'contain':
76
+ return 'contain';
77
+ case 'stretch':
78
+ return 'fill';
79
+ default:
80
+ return 'contain';
81
+ }
82
+ };
83
+
84
+ return React.createElement(this._videoModule.VideoView, {
85
+ ...props,
86
+ player,
87
+ contentFit: getContentFit(resizeMode),
88
+ });
89
+ };
90
+ }
91
+
20
92
  const createExpoMediaService = ({
21
93
  avModule,
22
94
  thumbnailModule,
23
95
  imageManipulator,
24
96
  fsModule,
25
97
  }: Modules): MediaServiceInterface => {
98
+ if (expoBackwardUtils.expoAV.isLegacyAVModule(avModule)) {
99
+ Logger.warn(
100
+ '[MediaService.Expo] expo-av is deprecated and will be removed in Expo 54. Please migrate to expo-video.',
101
+ );
102
+ }
103
+
104
+ const videoAdapter = expoBackwardUtils.expoAV.isVideoModule(avModule)
105
+ ? new ExpoVideoAdapter(avModule)
106
+ : new LegacyExpoAVVideoAdapter(avModule);
107
+
26
108
  return {
27
- VideoComponent({ source, resizeMode, onLoad, ...props }) {
28
- // FIXME: type error https://github.com/expo/expo/issues/17101
29
- // @ts-ignore
30
- return <avModule.Video {...props} source={source} resizeMode={resizeMode} onLoad={onLoad} useNativeControls />;
31
- },
109
+ VideoComponent: videoAdapter.VideoComponent,
32
110
  async getVideoThumbnail({ url, quality, timeMills }) {
33
111
  try {
34
112
  const { uri } = await thumbnailModule.getThumbnailAsync(url, { quality, time: timeMills });
@@ -1,27 +1,64 @@
1
1
  import type RNFBMessaging from '@react-native-firebase/messaging';
2
+ import type { FirebaseMessagingTypes } from '@react-native-firebase/messaging';
2
3
  import { Platform } from 'react-native';
3
4
  import type * as Permissions from 'react-native-permissions';
4
5
 
5
6
  import type { NotificationServiceInterface } from './types';
6
7
 
8
+ // Type definitions for Firebase Messaging Module instance
9
+ // This represents the instance returned by messaging() or getMessaging()
10
+ // We use a type alias to handle both v14 and v22+ types
11
+ type MessagingInstance = ReturnType<typeof RNFBMessaging>;
12
+
13
+ // Type definitions for modular API support (Firebase v22+)
14
+ // The modular API provides standalone functions that accept a messaging instance
15
+ type ModularMessagingType = {
16
+ getMessaging: () => MessagingInstance;
17
+ getAPNSToken: (messaging: MessagingInstance) => Promise<string | null>;
18
+ getToken: (messaging: MessagingInstance) => Promise<string>;
19
+ hasPermission: (messaging: MessagingInstance) => Promise<FirebaseMessagingTypes.AuthorizationStatus>;
20
+ requestPermission: (
21
+ messaging: MessagingInstance,
22
+ iosPermissions?: FirebaseMessagingTypes.IOSPermissions,
23
+ ) => Promise<FirebaseMessagingTypes.AuthorizationStatus>;
24
+ onTokenRefresh: (messaging: MessagingInstance, listener: (token: string) => void) => () => void;
25
+ AuthorizationStatus: typeof RNFBMessaging.AuthorizationStatus;
26
+ };
27
+
28
+ // Create a flexible type that can handle both v14 and v22+ Firebase modules
29
+ type FirebaseMessagingModule = typeof RNFBMessaging | ModularMessagingType;
30
+
31
+ const isModularAPI = (module: FirebaseMessagingModule): module is ModularMessagingType => {
32
+ return typeof (module as ModularMessagingType).getMessaging === 'function';
33
+ };
34
+
7
35
  const createNativeNotificationService = ({
8
36
  messagingModule,
9
37
  permissionModule,
10
38
  }: {
11
- messagingModule: typeof RNFBMessaging;
39
+ messagingModule: FirebaseMessagingModule;
12
40
  permissionModule: typeof Permissions;
13
41
  }): NotificationServiceInterface => {
14
- const module = messagingModule();
42
+ const isModular = isModularAPI(messagingModule);
43
+ const modularMessaging = isModular ? (messagingModule as ModularMessagingType) : null;
44
+ const messaging = isModular ? modularMessaging!.getMessaging() : (messagingModule as typeof RNFBMessaging)();
45
+
15
46
  const authorizedStatus = [
16
47
  messagingModule.AuthorizationStatus.AUTHORIZED,
17
48
  messagingModule.AuthorizationStatus.PROVISIONAL,
18
49
  ];
19
50
  return {
20
51
  getAPNSToken(): Promise<string | null> {
21
- return module.getAPNSToken();
52
+ if (modularMessaging) {
53
+ return modularMessaging.getAPNSToken(messaging);
54
+ }
55
+ return messaging.getAPNSToken();
22
56
  },
23
57
  getFCMToken(): Promise<string | null> {
24
- return module.getToken();
58
+ if (modularMessaging) {
59
+ return modularMessaging.getToken(messaging);
60
+ }
61
+ return messaging.getToken();
25
62
  },
26
63
  async hasPushPermission(): Promise<boolean> {
27
64
  if (Platform.OS === 'android') {
@@ -30,7 +67,9 @@ const createNativeNotificationService = ({
30
67
  }
31
68
 
32
69
  if (Platform.OS === 'ios') {
33
- const status = await module.hasPermission();
70
+ const status = modularMessaging
71
+ ? await modularMessaging.hasPermission(messaging)
72
+ : await messaging.hasPermission();
34
73
  return authorizedStatus.includes(status);
35
74
  }
36
75
 
@@ -43,14 +82,21 @@ const createNativeNotificationService = ({
43
82
  }
44
83
 
45
84
  if (Platform.OS === 'ios') {
46
- const status = await module.requestPermission();
85
+ const status = modularMessaging
86
+ ? await modularMessaging.requestPermission(messaging)
87
+ : await messaging.requestPermission();
47
88
  return authorizedStatus.includes(status);
48
89
  }
49
90
 
50
91
  return false;
51
92
  },
52
93
  onTokenRefresh(handler: (token: string) => void): () => void | undefined {
53
- return module.onTokenRefresh((token) => {
94
+ if (modularMessaging) {
95
+ return modularMessaging.onTokenRefresh(messaging, (token: string) => {
96
+ if (Platform.OS === 'android') handler(token);
97
+ });
98
+ }
99
+ return messaging.onTokenRefresh((token: string) => {
54
100
  if (Platform.OS === 'android') handler(token);
55
101
  });
56
102
  },