Skip to content

Commit 6bd6ecd

Browse files
authored
fix: image/video picker upload issue from expo apps and getLocalAssetURI improvements (#2220)
* fix: image/video picker upload issue from expo apps and getLocalAssetURI improvements * fix: function name and max file condition
1 parent 64ef4e3 commit 6bd6ecd

File tree

5 files changed

+92
-51
lines changed

5 files changed

+92
-51
lines changed

examples/TypeScriptMessaging/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ const ThreadScreen: React.FC<ThreadScreenProps> = ({ navigation }) => {
158158
});
159159
}, [overlay]);
160160

161-
if (!channel) return;
161+
if (!channel) return null;
162162

163163
return (
164164
<SafeAreaView>

package/src/components/AttachmentPicker/AttachmentPicker.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,14 @@ export const AttachmentPicker = React.forwardRef(
209209
maxNumberOfFiles,
210210
numberOfAttachmentPickerImageColumns,
211211
numberOfUploads: selectedFiles.length + selectedImages.length,
212+
// `id` is available for Expo MediaLibrary while Cameraroll doesn't share id therefore we use `uri`
212213
selected:
213-
selectedImages.some((image) => image.uri === asset.uri) ||
214-
selectedFiles.some((file) => file.uri === asset.uri),
214+
selectedImages.some((image) =>
215+
image.id ? image.id === asset.id : image.uri === asset.uri,
216+
) ||
217+
selectedFiles.some((file) => (file.id ? file.id === asset.id : file.uri === asset.uri)),
218+
selectedFiles,
219+
selectedImages,
215220
setSelectedFiles,
216221
setSelectedImages,
217222
}));

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

Lines changed: 77 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
11
import React from 'react';
22

3-
import { Alert, ImageBackground, StyleSheet, Text, View } from 'react-native';
3+
import { Alert, ImageBackground, Platform, StyleSheet, Text, View } from 'react-native';
44

55
import { TouchableOpacity } from '@gorhom/bottom-sheet';
66
import dayjs from 'dayjs';
77
import { lookup } from 'mime-types';
88

9+
import type { AttachmentPickerContextValue } from '../../../contexts/attachmentPickerContext/AttachmentPickerContext';
910
import { useTheme } from '../../../contexts/themeContext/ThemeContext';
1011
import { Recorder } from '../../../icons';
12+
import { getLocalAssetUri } from '../../../native';
1113
import type { Asset, File } from '../../../types/types';
1214
import { vw } from '../../../utils/utils';
1315

14-
type AttachmentPickerItemType = {
16+
type AttachmentPickerItemType = Pick<
17+
AttachmentPickerContextValue,
18+
'selectedFiles' | 'setSelectedFiles' | 'setSelectedImages' | 'selectedImages' | 'maxNumberOfFiles'
19+
> & {
1520
asset: Asset;
1621
ImageOverlaySelectedComponent: React.ComponentType;
17-
maxNumberOfFiles: number;
1822
numberOfUploads: number;
1923
selected: boolean;
20-
setSelectedFiles: React.Dispatch<React.SetStateAction<File[]>>;
21-
setSelectedImages: React.Dispatch<React.SetStateAction<Asset[]>>;
2224
numberOfAttachmentPickerImageColumns?: number;
2325
};
2426

25-
type AttachmentImageProps = Omit<AttachmentPickerItemType, 'setSelectedFiles'>;
27+
type AttachmentImageProps = Omit<AttachmentPickerItemType, 'setSelectedFiles' | 'selectedFiles'>;
2628

27-
type AttachmentVideoProps = Omit<AttachmentPickerItemType, 'setSelectedImages'>;
29+
type AttachmentVideoProps = Omit<AttachmentPickerItemType, 'setSelectedImages' | 'selectedImages'>;
2830

2931
const AttachmentVideo: React.FC<AttachmentVideoProps> = (props) => {
3032
const {
@@ -34,6 +36,7 @@ const AttachmentVideo: React.FC<AttachmentVideoProps> = (props) => {
3436
numberOfAttachmentPickerImageColumns,
3537
numberOfUploads,
3638
selected,
39+
selectedFiles,
3740
setSelectedFiles,
3841
} = props;
3942

@@ -61,29 +64,43 @@ const AttachmentVideo: React.FC<AttachmentVideoProps> = (props) => {
6164

6265
const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2;
6366

67+
/* Patches video files with uri and mimetype */
68+
const patchVideoFile = async (files: File[]) => {
69+
// For the case of Expo CLI where you need to fetch the file uri from file id. Here it is only done for iOS since for android the file.uri is fine.
70+
const localAssetURI = Platform.OS === 'ios' && asset.id && (await getLocalAssetUri(asset.id));
71+
const uri = localAssetURI || asset.uri || '';
72+
// We need a mime-type to upload a video file.
73+
const mimeType = lookup(asset.filename) || 'multipart/form-data';
74+
return [
75+
...files,
76+
{
77+
duration: durationLabel,
78+
id: asset.id,
79+
mimeType,
80+
name: asset.filename,
81+
size: asset.fileSize,
82+
uri,
83+
},
84+
];
85+
};
86+
87+
const updateSelectedFiles = async () => {
88+
if (numberOfUploads >= maxNumberOfFiles) {
89+
Alert.alert('Maximum number of files reached');
90+
return;
91+
}
92+
const files = await patchVideoFile(selectedFiles);
93+
setSelectedFiles(files);
94+
};
95+
6496
const onPressVideo = () => {
6597
if (selected) {
66-
setSelectedFiles((files) => files.filter((file) => file.uri !== asset.uri));
98+
setSelectedFiles((files) =>
99+
// `id` is available for Expo MediaLibrary while Cameraroll doesn't share id therefore we use `uri`
100+
files.filter((file) => (file.id ? file.id !== asset.id : file.uri !== asset.uri)),
101+
);
67102
} else {
68-
setSelectedFiles((files) => {
69-
if (numberOfUploads >= maxNumberOfFiles) {
70-
Alert.alert('Maximum number of files reached');
71-
return files;
72-
}
73-
// We need a mime-type to upload a video file.
74-
const mimeType = lookup(asset.filename) || 'multipart/form-data';
75-
return [
76-
...files,
77-
{
78-
duration: durationLabel,
79-
id: asset.id,
80-
mimeType,
81-
name: asset.filename,
82-
size: asset.fileSize,
83-
uri: asset.uri,
84-
},
85-
];
86-
});
103+
updateSelectedFiles();
87104
}
88105
};
89106

@@ -126,6 +143,7 @@ const AttachmentImage: React.FC<AttachmentImageProps> = (props) => {
126143
numberOfAttachmentPickerImageColumns,
127144
numberOfUploads,
128145
selected,
146+
selectedImages,
129147
setSelectedImages,
130148
} = props;
131149
const {
@@ -139,17 +157,37 @@ const AttachmentImage: React.FC<AttachmentImageProps> = (props) => {
139157

140158
const { uri } = asset;
141159

160+
/* Patches image files with uri */
161+
const patchImageFile = async (images: Asset[]) => {
162+
// For the case of Expo CLI where you need to fetch the file uri from file id. Here it is only done for iOS since for android the file.uri is fine.
163+
const localAssetURI = Platform.OS === 'ios' && asset.id && (await getLocalAssetUri(asset.id));
164+
const uri = localAssetURI || asset.uri || '';
165+
return [
166+
...images,
167+
{
168+
...asset,
169+
uri,
170+
},
171+
];
172+
};
173+
174+
const updateSelectedImages = async () => {
175+
if (numberOfUploads >= maxNumberOfFiles) {
176+
Alert.alert('Maximum number of files reached');
177+
return;
178+
}
179+
const images = await patchImageFile(selectedImages);
180+
setSelectedImages(images);
181+
};
182+
142183
const onPressImage = () => {
143184
if (selected) {
144-
setSelectedImages((images) => images.filter((image) => image.uri !== asset.uri));
185+
// `id` is available for Expo MediaLibrary while Cameraroll doesn't share id therefore we use `uri`
186+
setSelectedImages((images) =>
187+
images.filter((image) => (image.id ? image.id !== asset.id : image.uri !== asset.uri)),
188+
);
145189
} else {
146-
setSelectedImages((images) => {
147-
if (numberOfUploads >= maxNumberOfFiles) {
148-
Alert.alert('Maximum number of files reached');
149-
return images;
150-
}
151-
return [...images, asset];
152-
});
190+
updateSelectedImages();
153191
}
154192
};
155193

@@ -184,6 +222,8 @@ export const renderAttachmentPickerItem = ({ item }: { item: AttachmentPickerIte
184222
numberOfAttachmentPickerImageColumns,
185223
numberOfUploads,
186224
selected,
225+
selectedFiles,
226+
selectedImages,
187227
setSelectedFiles,
188228
setSelectedImages,
189229
} = item;
@@ -205,6 +245,7 @@ export const renderAttachmentPickerItem = ({ item }: { item: AttachmentPickerIte
205245
numberOfAttachmentPickerImageColumns={numberOfAttachmentPickerImageColumns}
206246
numberOfUploads={numberOfUploads}
207247
selected={selected}
248+
selectedFiles={selectedFiles}
208249
setSelectedFiles={setSelectedFiles}
209250
/>
210251
);
@@ -218,6 +259,7 @@ export const renderAttachmentPickerItem = ({ item }: { item: AttachmentPickerIte
218259
numberOfAttachmentPickerImageColumns={numberOfAttachmentPickerImageColumns}
219260
numberOfUploads={numberOfUploads}
220261
selected={selected}
262+
selectedImages={selectedImages}
221263
setSelectedImages={setSelectedImages}
222264
/>
223265
);

package/src/components/Channel/Channel.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';
2-
import { KeyboardAvoidingViewProps, Platform, StyleSheet, Text, View } from 'react-native';
2+
import { KeyboardAvoidingViewProps, StyleSheet, Text, View } from 'react-native';
33

44
import debounce from 'lodash/debounce';
55
import throttle from 'lodash/throttle';
@@ -74,7 +74,7 @@ import {
7474
ThumbsUpReaction,
7575
WutReaction,
7676
} from '../../icons';
77-
import { FlatList as FlatListDefault, getLocalAssetUri, pickDocument } from '../../native';
77+
import { FlatList as FlatListDefault, pickDocument } from '../../native';
7878
import * as dbApi from '../../store/apis';
7979
import type { DefaultStreamChatGenerics } from '../../types/types';
8080
import { addReactionToLocalState } from '../../utils/addReactionToLocalState';
@@ -1295,16 +1295,12 @@ const ChannelWithContext = <
12951295
attachment.image_url &&
12961296
isLocalUrl(attachment.image_url)
12971297
) {
1298-
// For the case of Expo CLI where you need to fetch the file uri from file id. Here it is only done for iOS since for android the file.uri is fine.
1299-
const localAssetURI =
1300-
Platform.OS === 'ios' && file.id && (await getLocalAssetUri(file.id));
1301-
const uri = localAssetURI || file.uri || '';
1302-
const filename = file.name ?? uri.replace(/^(file:\/\/|content:\/\/)/, '');
1298+
const filename = file.name ?? file.uri.replace(/^(file:\/\/|content:\/\/)/, '');
13031299
const contentType = lookup(filename) || 'multipart/form-data';
13041300

13051301
const uploadResponse = doImageUploadRequest
13061302
? await doImageUploadRequest(file, channel)
1307-
: await channel.sendImage(uri, filename, contentType);
1303+
: await channel.sendImage(file.uri, filename, contentType);
13081304

13091305
attachment.image_url = uploadResponse.file;
13101306
delete attachment.originalFile;

package/src/contexts/messageInputContext/MessageInputContext.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { LegacyRef } from 'react';
22
import React, { PropsWithChildren, useContext, useEffect, useRef, useState } from 'react';
33
import type { TextInput, TextInputProps } from 'react-native';
4-
import { Alert, Keyboard, Platform } from 'react-native';
4+
import { Alert, Keyboard } from 'react-native';
55

66
import uniq from 'lodash/uniq';
77
import { lookup } from 'mime-types';
@@ -35,7 +35,7 @@ import type { MoreOptionsButtonProps } from '../../components/MessageInput/MoreO
3535
import type { SendButtonProps } from '../../components/MessageInput/SendButton';
3636
import type { UploadProgressIndicatorProps } from '../../components/MessageInput/UploadProgressIndicator';
3737
import type { MessageType } from '../../components/MessageList/hooks/useMessageList';
38-
import { compressImage, getLocalAssetUri, pickDocument } from '../../native';
38+
import { compressImage, pickDocument } from '../../native';
3939
import type { Asset, DefaultStreamChatGenerics, File, UnknownType } from '../../types/types';
4040
import { removeReservedFields } from '../../utils/removeReservedFields';
4141
import {
@@ -984,9 +984,7 @@ export const MessageInputProvider = <
984984
let response = {} as SendFileAPIResponse;
985985

986986
try {
987-
// For the case of Expo CLI where you need to fetch the file uri from file id. Here it is only done for iOS since for android the file.uri is fine.
988-
const localAssetURI = Platform.OS === 'ios' && file.id && (await getLocalAssetUri(file.id));
989-
const uri = localAssetURI || file.uri || '';
987+
const uri = file.uri || '';
990988
/**
991989
* We skip compression if:
992990
* - the file is from the camera as that should already be compressed

0 commit comments

Comments
 (0)