diff --git a/package/expo-package/src/optionalDependencies/takePhoto.ts b/package/expo-package/src/optionalDependencies/takePhoto.ts index cc64f8df6..157a738bc 100644 --- a/package/expo-package/src/optionalDependencies/takePhoto.ts +++ b/package/expo-package/src/optionalDependencies/takePhoto.ts @@ -19,8 +19,15 @@ type Size = { width?: number; }; +// Media type mapping for iOS and Android +const mediaTypeMap = { + mixed: ['images', 'videos'], + image: 'images', + video: 'videos', +}; + export const takePhoto = ImagePicker - ? async ({ compressImageQuality = 1 }) => { + ? async ({ compressImageQuality = 1, mediaType = Platform.OS === 'ios' ? 'mixed' : 'image' }) => { try { const permissionCheck = await ImagePicker.getCameraPermissionsAsync(); const canRequest = permissionCheck.canAskAgain; @@ -36,61 +43,64 @@ export const takePhoto = ImagePicker if (permissionGranted) { const imagePickerSuccessResult = await ImagePicker.launchCameraAsync({ - mediaTypes: Platform.OS === 'ios' ? ['images', 'videos'] : 'images', + mediaTypes: Platform.OS === 'ios' ? mediaTypeMap[mediaType] : mediaType[mediaType], quality: Math.min(Math.max(0, compressImageQuality), 1), }); const canceled = imagePickerSuccessResult.canceled; const assets = imagePickerSuccessResult.assets; // since we only support single photo upload for now we will only be focusing on 0'th element. const photo = assets && assets[0]; - if (Platform.OS === 'ios') { - if (photo.mimeType.includes('video')) { - const clearFilter = new RegExp('[.:]', 'g'); - const date = new Date().toISOString().replace(clearFilter, '_'); - return { - ...photo, - cancelled: false, - duration: photo.duration, - source: 'camera', - name: 'video_recording_' + date + photo.uri.split('.').pop(), - size: photo.fileSize, - type: photo.mimeType, - uri: photo.uri, - }; - } + console.log('photo', photo); + if (canceled) { + return { cancelled: true }; } - if (canceled === false && photo && photo.height && photo.width && photo.uri) { - let size: Size = {}; - if (Platform.OS === 'android') { - const getSize = (): Promise => - new Promise((resolve) => { - Image.getSize(photo.uri, (width, height) => { - resolve({ height, width }); - }); - }); - - try { - const { height, width } = await getSize(); - size.height = height; - size.width = width; - } catch (e) { - console.warn('Error get image size of picture caputred from camera ', e); - } - } else { - size = { - height: photo.height, - width: photo.width, - }; - } - + if (photo.mimeType.includes('video')) { + const clearFilter = new RegExp('[.:]', 'g'); + const date = new Date().toISOString().replace(clearFilter, '_'); return { + ...photo, cancelled: false, + duration: photo.duration, // in milliseconds + source: 'camera', + name: 'video_recording_' + date + photo.uri.split('.').pop(), size: photo.fileSize, type: photo.mimeType, - source: 'camera', uri: photo.uri, - ...size, }; + } else { + if (photo && photo.height && photo.width && photo.uri) { + let size: Size = {}; + if (Platform.OS === 'android') { + const getSize = (): Promise => + new Promise((resolve) => { + Image.getSize(photo.uri, (width, height) => { + resolve({ height, width }); + }); + }); + + try { + const { height, width } = await getSize(); + size.height = height; + size.width = width; + } catch (e) { + console.warn('Error get image size of picture caputred from camera ', e); + } + } else { + size = { + height: photo.height, + width: photo.width, + }; + } + + return { + cancelled: false, + size: photo.fileSize, + type: photo.mimeType, + source: 'camera', + uri: photo.uri, + ...size, + }; + } } } } catch (error) { diff --git a/package/native-package/src/optionalDependencies/takePhoto.ts b/package/native-package/src/optionalDependencies/takePhoto.ts index 5dcf3f840..673cc368c 100644 --- a/package/native-package/src/optionalDependencies/takePhoto.ts +++ b/package/native-package/src/optionalDependencies/takePhoto.ts @@ -11,7 +11,10 @@ try { } export const takePhoto = ImagePicker - ? async ({ compressImageQuality = Platform.OS === 'ios' ? 0.8 : 1 }) => { + ? async ({ + compressImageQuality = Platform.OS === 'ios' ? 0.8 : 1, + mediaType = Platform.OS === 'ios' ? 'mixed' : 'image', + }) => { if (Platform.OS === 'android') { const cameraPermissions = await PermissionsAndroid.check( PermissionsAndroid.PERMISSIONS.CAMERA, @@ -29,7 +32,7 @@ export const takePhoto = ImagePicker } try { const result = await ImagePicker.launchCamera({ - mediaType: Platform.OS === 'ios' ? 'mixed' : 'images', + mediaType: mediaType, quality: Math.min(Math.max(0, compressImageQuality), 1), }); if (!result.assets.length) { @@ -38,56 +41,55 @@ export const takePhoto = ImagePicker }; } const asset = result.assets[0]; - if (Platform.OS === 'ios') { - if (asset.type.includes('video')) { - const clearFilter = new RegExp('[.:]', 'g'); - const date = new Date().toISOString().replace(clearFilter, '_'); + if (asset.type.includes('video')) { + const clearFilter = new RegExp('[.:]', 'g'); + const date = new Date().toISOString().replace(clearFilter, '_'); + return { + ...asset, + cancelled: false, + duration: asset.duration * 1000, + source: 'camera', + name: 'video_recording_' + date + asset.fileName.split('.').pop(), + size: asset.fileSize, + type: asset.type, + uri: asset.uri, + }; + } else { + if (asset.height && asset.width && asset.uri) { + let size: { height?: number; width?: number } = {}; + if (Platform.OS === 'android') { + // Height and width returned by ImagePicker are incorrect on Android. + const getSize = (): Promise<{ height: number; width: number }> => + new Promise((resolve) => { + Image.getSize(asset.uri, (width, height) => { + resolve({ height, width }); + }); + }); + + try { + const { height, width } = await getSize(); + size.height = height; + size.width = width; + } catch (e) { + // do nothing + console.warn('Error get image size of picture caputred from camera ', e); + } + } else { + size = { + height: asset.height, + width: asset.width, + }; + } return { - ...asset, cancelled: false, - duration: asset.duration ? asset.duration * 1000 : undefined, // in milliseconds - source: 'camera', - name: 'video_recording_' + date + asset.fileName.split('.').pop(), - size: asset.fileSize, type: asset.type, + size: asset.size, + source: 'camera', uri: asset.uri, + ...size, }; } } - if (asset.height && asset.width && asset.uri) { - let size: { height?: number; width?: number } = {}; - if (Platform.OS === 'android') { - // Height and width returned by ImagePicker are incorrect on Android. - const getSize = (): Promise<{ height: number; width: number }> => - new Promise((resolve) => { - Image.getSize(asset.uri, (width, height) => { - resolve({ height, width }); - }); - }); - - try { - const { height, width } = await getSize(); - size.height = height; - size.width = width; - } catch (e) { - // do nothing - console.warn('Error get image size of picture caputred from camera ', e); - } - } else { - size = { - height: asset.height, - width: asset.width, - }; - } - return { - cancelled: false, - type: asset.type, - size: asset.size, - source: 'camera', - uri: asset.uri, - ...size, - }; - } } catch (e: unknown) { if (e instanceof Error) { // on iOS: if it was in inactive state, then the user had just denied the permissions diff --git a/package/src/components/AttachmentPicker/components/AttachmentPickerSelectionBar.tsx b/package/src/components/AttachmentPicker/components/AttachmentPickerSelectionBar.tsx index a86cea012..74c9751fc 100644 --- a/package/src/components/AttachmentPicker/components/AttachmentPickerSelectionBar.tsx +++ b/package/src/components/AttachmentPicker/components/AttachmentPickerSelectionBar.tsx @@ -7,6 +7,7 @@ import { useMessageInputContext } from '../../../contexts/messageInputContext/Me import { useMessagesContext } from '../../../contexts/messagesContext/MessagesContext'; import { useOwnCapabilitiesContext } from '../../../contexts/ownCapabilitiesContext/OwnCapabilitiesContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; +import { Recorder } from '../../../icons'; const styles = StyleSheet.create({ container: { @@ -48,6 +49,7 @@ export const AttachmentPickerSelectionBar = () => { const { theme: { attachmentSelectionBar: { container, icon }, + colors: { grey }, }, } = useTheme(); @@ -105,7 +107,9 @@ export const AttachmentPickerSelectionBar = () => { {hasCameraPicker ? ( { + takeAndUploadImage(); + }} testID='take-photo-touchable' > @@ -116,6 +120,19 @@ export const AttachmentPickerSelectionBar = () => { ) : null} + {hasCameraPicker ? ( + { + takeAndUploadImage('video'); + }} + testID='take-photo-touchable' + > + + + + + ) : null} {!threadList && hasCreatePoll && ownCapabilities.sendPoll ? ( // do not allow poll creation in threads void; @@ -28,10 +29,10 @@ export const NativeAttachmentPicker = ({ }: NativeAttachmentPickerProps) => { const size = attachButtonLayoutRectangle?.width ?? 0; const attachButtonItemSize = 40; - const NUMBER_OF_BUTTONS = 3; + const NUMBER_OF_BUTTONS = 5; const { theme: { - colors: { grey_whisper }, + colors: { grey, grey_whisper }, messageInput: { nativeAttachmentPicker: { buttonContainer, @@ -149,6 +150,13 @@ export const NativeAttachmentPicker = ({ id: 'Camera', onPressHandler: takeAndUploadImage, }); + buttons.push({ + icon: , + id: 'Video', + onPressHandler: () => { + takeAndUploadImage('video'); + }, + }); } return ( diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index 5983f3334..fca286143 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -57,6 +57,7 @@ import type { Emoji } from '../../emoji-data'; import { isDocumentPickerAvailable, isImageMediaLibraryAvailable, + MediaTypes, pickDocument, pickImage, takePhoto, @@ -232,7 +233,7 @@ export type LocalMessageInputContext< /** * Function for taking a photo and uploading it */ - takeAndUploadImage: () => Promise; + takeAndUploadImage: (mediaType?: MediaTypes) => Promise; text: string; toggleAttachmentPicker: () => void; /** @@ -686,10 +687,10 @@ export const MessageInputProvider = < /** * Function for capturing a photo and uploading it */ - const takeAndUploadImage = async () => { + const takeAndUploadImage = async (mediaType?: MediaTypes) => { setSelectedPicker(undefined); closePicker(); - const photo = await takePhoto({ compressImageQuality: value.compressImageQuality }); + const photo = await takePhoto({ compressImageQuality: value.compressImageQuality, mediaType }); if (photo.askToOpenSettings) { Alert.alert( t('Allow camera access in device settings'), diff --git a/package/src/native.ts b/package/src/native.ts index 59a053a09..6bc60cb7a 100644 --- a/package/src/native.ts +++ b/package/src/native.ts @@ -85,7 +85,11 @@ type Photo = Omit & { askToOpenSettings?: boolean; cancelled?: boolean; }; -type TakePhoto = (options: { compressImageQuality?: number }) => Promise | never; +export type MediaTypes = 'image' | 'video' | 'mixed'; +type TakePhoto = (options: { + compressImageQuality?: number; + mediaType?: MediaTypes; +}) => Promise | never; export let takePhoto: TakePhoto = fail; type HapticFeedbackMethod =