Skip to content

Commit 1113ce1

Browse files
authored
Merge pull request #2898 from GetStream/develop
Next Release
2 parents 261f8a6 + 020cecc commit 1113ce1

File tree

27 files changed

+561
-355
lines changed

27 files changed

+561
-355
lines changed

examples/ExpoMessaging/app.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"expo-image-picker",
4040
{
4141
"photosPermission": "The app accesses your photos to let them share with others.",
42-
"cameraPermission": "The app accesses your camera to let you take photos and share with others."
42+
"cameraPermission": "The app accesses your camera to let you take photos and share with others.",
43+
"microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone for video recording."
4344
}
4445
],
4546
[

examples/ExpoMessaging/components/ChatWrapper.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { AuthProgressLoader } from './AuthProgressLoader';
1010
import { StreamChatGenerics } from '../types';
1111
import { STREAM_API_KEY, user, userToken } from '../constants';
1212
import { useSafeAreaInsets } from 'react-native-safe-area-context';
13+
import { useStreamChatTheme } from '../useStreamChatTheme';
1314

1415
const streami18n = new Streami18n({
1516
language: 'en',
@@ -26,13 +27,18 @@ export const ChatWrapper = ({ children }: PropsWithChildren<{}>) => {
2627
userData: user,
2728
tokenOrProvider: userToken,
2829
});
30+
const theme = useStreamChatTheme();
2931

3032
if (!chatClient) {
3133
return <AuthProgressLoader />;
3234
}
3335

3436
return (
35-
<OverlayProvider<StreamChatGenerics> bottomInset={bottom} i18nInstance={streami18n}>
37+
<OverlayProvider<StreamChatGenerics>
38+
bottomInset={bottom}
39+
i18nInstance={streami18n}
40+
value={{ style: theme }}
41+
>
3642
<Chat client={chatClient} i18nInstance={streami18n}>
3743
{children}
3844
</Chat>

examples/ExpoMessaging/package.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,28 @@
1313
"@op-engineering/op-sqlite": "^9.3.0",
1414
"@react-native-community/netinfo": "11.4.1",
1515
"@react-navigation/elements": "^1.3.30",
16-
"expo": "^52.0.0",
16+
"expo": "~52.0.20",
1717
"expo-av": "~15.0.1",
1818
"expo-clipboard": "~7.0.0",
1919
"expo-constants": "~17.0.3",
2020
"expo-document-picker": "~13.0.1",
21-
"expo-file-system": "~18.0.4",
21+
"expo-file-system": "~18.0.6",
2222
"expo-haptics": "~14.0.0",
2323
"expo-image-manipulator": "~13.0.5",
2424
"expo-image-picker": "^16.0.3",
2525
"expo-linking": "~7.0.3",
26-
"expo-router": "~4.0.11",
26+
"expo-media-library": "~17.0.4",
27+
"expo-router": "~4.0.15",
2728
"expo-sharing": "~13.0.0",
28-
"expo-splash-screen": "~0.29.13",
29+
"expo-splash-screen": "~0.29.18",
2930
"expo-status-bar": "~2.0.0",
3031
"react": "18.3.1",
3132
"react-dom": "18.3.1",
32-
"react-native": "0.76.3",
33+
"react-native": "0.76.5",
3334
"react-native-gesture-handler": "~2.20.2",
3435
"react-native-reanimated": "~3.16.1",
3536
"react-native-safe-area-context": "4.12.0",
36-
"react-native-screens": "~4.1.0",
37+
"react-native-screens": "~4.4.0",
3738
"react-native-svg": "15.8.0",
3839
"react-native-web": "~0.19.13",
3940
"stream-chat-expo": "link:../../package/expo-package",

examples/ExpoMessaging/yarn.lock

Lines changed: 219 additions & 171 deletions
Large diffs are not rendered by default.

package/expo-package/src/optionalDependencies/takePhoto.ts

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ try {
1010

1111
if (!ImagePicker) {
1212
console.log(
13-
'expo-image-picker is not installed. Installing this package will enable campturing photos through the app, and thereby send it.',
13+
'expo-image-picker is not installed. Installing this package will enable capturing photos and videos(for iOS) through the app, and thereby send it.',
1414
);
1515
}
1616

@@ -19,8 +19,15 @@ type Size = {
1919
width?: number;
2020
};
2121

22+
// Media type mapping for iOS and Android
23+
const mediaTypeMap = {
24+
image: 'images',
25+
mixed: ['images', 'videos'],
26+
video: 'videos',
27+
};
28+
2229
export const takePhoto = ImagePicker
23-
? async ({ compressImageQuality = 1 }) => {
30+
? async ({ compressImageQuality = 1, mediaType = Platform.OS === 'ios' ? 'mixed' : 'image' }) => {
2431
try {
2532
const permissionCheck = await ImagePicker.getCameraPermissionsAsync();
2633
const canRequest = permissionCheck.canAskAgain;
@@ -35,45 +42,65 @@ export const takePhoto = ImagePicker
3542
}
3643

3744
if (permissionGranted) {
38-
const imagePickerSuccessResult = await ImagePicker.launchCameraAsync({
45+
const result = await ImagePicker.launchCameraAsync({
46+
mediaTypes: mediaTypeMap[mediaType],
3947
quality: Math.min(Math.max(0, compressImageQuality), 1),
4048
});
41-
const canceled = imagePickerSuccessResult.canceled;
42-
const assets = imagePickerSuccessResult.assets;
49+
if (!result || !result.assets || !result.assets.length || result.canceled) {
50+
return { cancelled: true };
51+
}
4352
// since we only support single photo upload for now we will only be focusing on 0'th element.
44-
const photo = assets && assets[0];
45-
46-
if (canceled === false && photo && photo.height && photo.width && photo.uri) {
47-
let size: Size = {};
48-
if (Platform.OS === 'android') {
49-
const getSize = (): Promise<Size> =>
50-
new Promise((resolve) => {
51-
Image.getSize(photo.uri, (width, height) => {
52-
resolve({ height, width });
53-
});
54-
});
55-
56-
try {
57-
const { height, width } = await getSize();
58-
size.height = height;
59-
size.width = width;
60-
} catch (e) {
61-
console.warn('Error get image size of picture caputred from camera ', e);
62-
}
63-
} else {
64-
size = {
65-
height: photo.height,
66-
width: photo.width,
67-
};
68-
}
69-
53+
const photo = result.assets[0];
54+
if (!photo) {
55+
return { cancelled: true };
56+
}
57+
if (photo.mimeType.includes('video')) {
58+
const clearFilter = new RegExp('[.:]', 'g');
59+
const date = new Date().toISOString().replace(clearFilter, '_');
7060
return {
61+
...photo,
7162
cancelled: false,
63+
duration: photo.duration, // in milliseconds
64+
name: 'video_recording_' + date + photo.uri.split('.').pop(),
7265
size: photo.fileSize,
7366
source: 'camera',
67+
type: photo.mimeType,
7468
uri: photo.uri,
75-
...size,
7669
};
70+
} else {
71+
if (photo && photo.height && photo.width && photo.uri) {
72+
let size: Size = {};
73+
if (Platform.OS === 'android') {
74+
const getSize = (): Promise<Size> =>
75+
new Promise((resolve) => {
76+
Image.getSize(photo.uri, (width, height) => {
77+
resolve({ height, width });
78+
});
79+
});
80+
81+
try {
82+
const { height, width } = await getSize();
83+
size.height = height;
84+
size.width = width;
85+
} catch (e) {
86+
console.warn('Error get image size of picture caputred from camera ', e);
87+
}
88+
} else {
89+
size = {
90+
height: photo.height,
91+
width: photo.width,
92+
};
93+
}
94+
95+
return {
96+
cancelled: false,
97+
size: photo.fileSize,
98+
source: 'camera',
99+
type: photo.mimeType,
100+
uri: photo.uri,
101+
...size,
102+
};
103+
}
77104
}
78105
}
79106
} catch (error) {

package/native-package/src/optionalDependencies/takePhoto.ts

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ try {
66
ImagePicker = require('react-native-image-picker');
77
} catch (e) {
88
console.log(
9-
'The package react-native-image-picker is not installed. Please install the same so as to take photo through camera and upload it.',
9+
'The package react-native-image-picker is not installed. Installing this package will enable capturing photos and videos(for iOS) through the app, and thereby send it.',
1010
);
1111
}
1212

1313
export const takePhoto = ImagePicker
14-
? async ({ compressImageQuality = Platform.OS === 'ios' ? 0.8 : 1 }) => {
14+
? async ({
15+
compressImageQuality = Platform.OS === 'ios' ? 0.8 : 1,
16+
mediaType = Platform.OS === 'ios' ? 'mixed' : 'image',
17+
}) => {
1518
if (Platform.OS === 'android') {
1619
const cameraPermissions = await PermissionsAndroid.check(
1720
PermissionsAndroid.PERMISSIONS.CAMERA,
@@ -29,46 +32,68 @@ export const takePhoto = ImagePicker
2932
}
3033
try {
3134
const result = await ImagePicker.launchCamera({
35+
mediaType,
3236
quality: Math.min(Math.max(0, compressImageQuality), 1),
3337
});
34-
if (!result.assets.length) {
38+
if (!result || !result.assets || !result.assets.length || result.didCancel) {
3539
return {
3640
cancelled: true,
3741
};
3842
}
39-
const photo = result.assets[0];
40-
if (photo.height && photo.width && photo.uri) {
41-
let size: { height?: number; width?: number } = {};
42-
if (Platform.OS === 'android') {
43-
// Height and width returned by ImagePicker are incorrect on Android.
44-
const getSize = (): Promise<{ height: number; width: number }> =>
45-
new Promise((resolve) => {
46-
Image.getSize(photo.uri, (width, height) => {
47-
resolve({ height, width });
43+
const asset = result.assets[0];
44+
if (!asset) {
45+
return {
46+
cancelled: true,
47+
};
48+
}
49+
if (asset.type.includes('video')) {
50+
const clearFilter = new RegExp('[.:]', 'g');
51+
const date = new Date().toISOString().replace(clearFilter, '_');
52+
return {
53+
...asset,
54+
cancelled: false,
55+
duration: asset.duration * 1000,
56+
name: 'video_recording_' + date + asset.fileName.split('.').pop(),
57+
size: asset.fileSize,
58+
source: 'camera',
59+
type: asset.type,
60+
uri: asset.uri,
61+
};
62+
} else {
63+
if (asset.height && asset.width && asset.uri) {
64+
let size: { height?: number; width?: number } = {};
65+
if (Platform.OS === 'android') {
66+
// Height and width returned by ImagePicker are incorrect on Android.
67+
const getSize = (): Promise<{ height: number; width: number }> =>
68+
new Promise((resolve) => {
69+
Image.getSize(asset.uri, (width, height) => {
70+
resolve({ height, width });
71+
});
4872
});
49-
});
5073

51-
try {
52-
const { height, width } = await getSize();
53-
size.height = height;
54-
size.width = width;
55-
} catch (e) {
56-
// do nothing
57-
console.warn('Error get image size of picture caputred from camera ', e);
74+
try {
75+
const { height, width } = await getSize();
76+
size.height = height;
77+
size.width = width;
78+
} catch (e) {
79+
// do nothing
80+
console.warn('Error get image size of picture caputred from camera ', e);
81+
}
82+
} else {
83+
size = {
84+
height: asset.height,
85+
width: asset.width,
86+
};
5887
}
59-
} else {
60-
size = {
61-
height: photo.height,
62-
width: photo.width,
88+
return {
89+
cancelled: false,
90+
size: asset.size,
91+
source: 'camera',
92+
type: asset.type,
93+
uri: asset.uri,
94+
...size,
6395
};
6496
}
65-
return {
66-
cancelled: false,
67-
size: photo.size,
68-
source: 'camera',
69-
uri: photo.uri,
70-
...size,
71-
};
7297
}
7398
} catch (e: unknown) {
7499
if (e instanceof Error) {

package/src/components/AITypingIndicatorView/hooks/useAIState.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { AIState, Channel, Event } from 'stream-chat';
44

55
import { useChatContext } from '../../../contexts';
66
import type { DefaultStreamChatGenerics } from '../../../types/types';
7-
import { useIsOnline } from '../../Chat/hooks/useIsOnline';
87

98
export const AIStates = {
109
Error: 'AI_STATE_ERROR',
@@ -24,8 +23,7 @@ export const useAIState = <
2423
>(
2524
channel?: Channel<StreamChatGenerics>,
2625
): { aiState: AIState } => {
27-
const { client } = useChatContext<StreamChatGenerics>();
28-
const { isOnline } = useIsOnline<StreamChatGenerics>(client);
26+
const { isOnline } = useChatContext<StreamChatGenerics>();
2927

3028
const [aiState, setAiState] = useState<AIState>(AIStates.Idle);
3129

package/src/components/AttachmentPicker/components/AttachmentPickerItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ const AttachmentVideo = (props: AttachmentVideoProps) => {
113113
</View>
114114
)}
115115
<View style={styles.videoView}>
116-
<Recorder height={20} pathFill={white} width={25} />
116+
<Recorder height={20} pathFill={white} width={20} />
117117
{videoDuration ? (
118118
<Text style={[{ color: white }, styles.durationText, durationText]}>
119119
{durationLabel}

package/src/components/AttachmentPicker/components/AttachmentPickerSelectionBar.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { StyleSheet, TouchableOpacity, View } from 'react-native';
2+
import { Platform, StyleSheet, TouchableOpacity, View } from 'react-native';
33

44
import { useAttachmentPickerContext } from '../../../contexts/attachmentPickerContext/AttachmentPickerContext';
55
import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
@@ -29,6 +29,7 @@ export const AttachmentPickerSelectionBar = () => {
2929
ImageSelectorIcon,
3030
selectedPicker,
3131
setSelectedPicker,
32+
VideoRecorderSelectorIcon,
3233
} = useAttachmentPickerContext();
3334

3435
const {
@@ -105,7 +106,9 @@ export const AttachmentPickerSelectionBar = () => {
105106
{hasCameraPicker ? (
106107
<TouchableOpacity
107108
hitSlop={{ bottom: 15, top: 15 }}
108-
onPress={takeAndUploadImage}
109+
onPress={() => {
110+
takeAndUploadImage(Platform.OS === 'android' ? 'image' : 'mixed');
111+
}}
109112
testID='take-photo-touchable'
110113
>
111114
<View style={[styles.icon, icon]}>
@@ -116,6 +119,22 @@ export const AttachmentPickerSelectionBar = () => {
116119
</View>
117120
</TouchableOpacity>
118121
) : null}
122+
{hasCameraPicker && Platform.OS === 'android' ? (
123+
<TouchableOpacity
124+
hitSlop={{ bottom: 15, top: 15 }}
125+
onPress={() => {
126+
takeAndUploadImage('video');
127+
}}
128+
testID='take-photo-touchable'
129+
>
130+
<View style={[styles.icon, { marginTop: 4 }, icon]}>
131+
<VideoRecorderSelectorIcon
132+
numberOfImageUploads={imageUploads.length}
133+
selectedPicker={selectedPicker}
134+
/>
135+
</View>
136+
</TouchableOpacity>
137+
) : null}
119138
{!threadList && hasCreatePoll && ownCapabilities.sendPoll ? ( // do not allow poll creation in threads
120139
<TouchableOpacity
121140
hitSlop={{ bottom: 15, top: 15 }}

0 commit comments

Comments
 (0)