From d64a9971a4c24b46b509015ddcc2401a853dfd7e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 4 Mar 2024 16:41:25 +0700 Subject: [PATCH 01/26] Migrate 'AttachmentPicker' component to TypeScript --- .../{index.native.js => index.native.tsx} | 198 ++++++++---------- .../AttachmentPicker/{index.js => index.tsx} | 34 +-- .../launchCamera.android.ts} | 7 +- .../launchCamera.ios.ts} | 0 .../launchCamera.ts} | 2 +- .../AttachmentPicker/launchCamera/types.ts | 47 +++++ ...{attachmentPickerPropTypes.js => types.ts} | 17 +- 7 files changed, 159 insertions(+), 146 deletions(-) rename src/components/AttachmentPicker/{index.native.js => index.native.tsx} (68%) rename src/components/AttachmentPicker/{index.js => index.tsx} (78%) rename src/components/AttachmentPicker/{launchCamera.android.js => launchCamera/launchCamera.android.ts} (88%) rename src/components/AttachmentPicker/{launchCamera.ios.js => launchCamera/launchCamera.ios.ts} (100%) rename src/components/AttachmentPicker/{launchCamera.js => launchCamera/launchCamera.ts} (66%) create mode 100644 src/components/AttachmentPicker/launchCamera/types.ts rename src/components/AttachmentPicker/{attachmentPickerPropTypes.js => types.ts} (58%) diff --git a/src/components/AttachmentPicker/index.native.js b/src/components/AttachmentPicker/index.native.tsx similarity index 68% rename from src/components/AttachmentPicker/index.native.js rename to src/components/AttachmentPicker/index.native.tsx index 0387ee087127..dcc4296b71ac 100644 --- a/src/components/AttachmentPicker/index.native.js +++ b/src/components/AttachmentPicker/index.native.tsx @@ -1,12 +1,9 @@ -import Str from 'expensify-common/lib/str'; import lodashCompact from 'lodash/compact'; -import PropTypes from 'prop-types'; import React, {useCallback, useMemo, useRef, useState} from 'react'; -import {Alert, Image as RNImage, View} from 'react-native'; +import {Alert, View} from 'react-native'; import RNFetchBlob from 'react-native-blob-util'; import RNDocumentPicker from 'react-native-document-picker'; import {launchImageLibrary} from 'react-native-image-picker'; -import _ from 'underscore'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Popover from '@components/Popover'; @@ -17,19 +14,32 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as FileUtils from '@libs/fileDownload/FileUtils'; import CONST from '@src/CONST'; -import {defaultProps as baseDefaultProps, propTypes as basePropTypes} from './attachmentPickerPropTypes'; -import launchCamera from './launchCamera'; - -const propTypes = { - ...basePropTypes, - +import type {TranslationPaths} from '@src/languages/types'; +import type IconAsset from '@src/types/utils/IconAsset'; +import type {DocumentPickerOptions, DocumentPickerResponse} from 'react-native-document-picker' +import type {SupportedPlatforms} from 'react-native-document-picker/lib/typescript/fileTypes'; +import type {Asset, Callback, CameraOptions, ImagePickerResponse} from 'react-native-image-picker'; +import launchCamera from './launchCamera/launchCamera'; +import type BaseAttachmentPickerProps from './types'; + +type AttachmentPickerProps = BaseAttachmentPickerProps & { /** If this value is true, then we exclude Camera option. */ - shouldHideCameraOption: PropTypes.bool, + shouldHideCameraOption?: boolean; }; -const defaultProps = { - ...baseDefaultProps, - shouldHideCameraOption: false, +type Item = { + icon: IconAsset; + textTranslationKey: string; + pickAttachment: () => Promise; +}; + +type FileResult = { + name: string; + type: string; + width: number | undefined; + height: number | undefined; + uri: string; + size: number | null; }; /** @@ -45,10 +55,8 @@ const imagePickerOptions = { /** * Return imagePickerOptions based on the type - * @param {String} type - * @returns {Object} */ -const getImagePickerOptions = (type) => { +const getImagePickerOptions = (type: string): CameraOptions => { // mediaType property is one of the ImagePicker configuration to restrict types' const mediaType = type === CONST.ATTACHMENT_PICKER_TYPE.IMAGE ? 'photo' : 'mixed'; return { @@ -58,40 +66,26 @@ const getImagePickerOptions = (type) => { }; /** - * Return documentPickerOptions based on the type - * @param {String} type - * @returns {Object} + * See https://github.com/rnmods/react-native-document-picker#options for DocumentPicker configuration options */ - -const getDocumentPickerOptions = (type) => { - if (type === CONST.ATTACHMENT_PICKER_TYPE.IMAGE) { - return { - type: [RNDocumentPicker.types.images], - copyTo: 'cachesDirectory', - }; - } - return { - type: [RNDocumentPicker.types.allFiles], - copyTo: 'cachesDirectory', - }; -}; +const documentPickerOptions = { + type: [RNDocumentPicker.types.allFiles], + copyTo: 'cachesDirectory', +} satisfies DocumentPickerOptions; /** * The data returned from `show` is different on web and mobile, so use this function to ensure the data we * send to the xhr will be handled properly. - * - * @param {Object} fileData - * @return {Promise} */ -const getDataForUpload = (fileData) => { - const fileName = fileData.fileName || fileData.name || 'chat_attachment'; - const fileResult = { +const getDataForUpload = (fileData: Asset & DocumentPickerResponse): Promise => { + const fileName = fileData.fileName ?? fileData.name ?? 'chat_attachment'; + const fileResult: FileResult = { name: FileUtils.cleanFileName(fileName), type: fileData.type, width: fileData.width, height: fileData.height, - uri: fileData.fileCopyUri || fileData.uri, - size: fileData.fileSize || fileData.size, + uri: fileData.fileCopyUri ?? fileData.uri, + size: fileData.fileSize ?? fileData.size, }; if (fileResult.size) { @@ -109,16 +103,15 @@ const getDataForUpload = (fileData) => { * returns a "show attachment picker" method that takes * a callback. This is the ios/android implementation * opening a modal with attachment options - * @param {propTypes} props - * @returns {JSX.Element} */ -function AttachmentPicker({type, children, shouldHideCameraOption}) { +function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, shouldHideCameraOption = false}: AttachmentPickerProps) { const styles = useThemeStyles(); const [isVisible, setIsVisible] = useState(false); - const completeAttachmentSelection = useRef(); - const onModalHide = useRef(); - const onCanceled = useRef(); + const completeAttachmentSelection = useRef<(data: FileResult) => void>(() => {}); + const onModalHide = useRef<() => void>(() => {}); + const onCanceled = useRef<() => void>(() => {}); + const popoverRef = useRef(null); const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -126,20 +119,19 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { /** * A generic handling when we don't know the exact reason for an error */ - const showGeneralAlert = useCallback(() => { - Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingAttachment')); + const showGeneralAlert = useCallback((message = '') => { + Alert.alert(translate('attachmentPicker.attachmentError'), `${message !== '' ? message : translate('attachmentPicker.errorWhileSelectingAttachment')}`); }, [translate]); /** * Common image picker handling * * @param {function} imagePickerFunc - RNImagePicker.launchCamera or RNImagePicker.launchImageLibrary - * @returns {Promise} */ const showImagePicker = useCallback( - (imagePickerFunc) => + (imagePickerFunc: (options: CameraOptions, callback: Callback) => Promise): Promise => new Promise((resolve, reject) => { - imagePickerFunc(getImagePickerOptions(type), (response) => { + imagePickerFunc(getImagePickerOptions(type), (response: ImagePickerResponse) => { if (response.didCancel) { // When the user cancelled resolve with no attachment return resolve(); @@ -166,11 +158,11 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { /** * Launch the DocumentPicker. Results are in the same format as ImagePicker * - * @returns {Promise} + * @returns {Promise} */ const showDocumentPicker = useCallback( - () => - RNDocumentPicker.pick(getDocumentPickerOptions(type)).catch((error) => { + (): Promise => + RNDocumentPicker.pick(documentPickerOptions).catch((error) => { if (RNDocumentPicker.isCancel(error)) { return; } @@ -178,10 +170,10 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { showGeneralAlert(error.message); throw error; }), - [showGeneralAlert, type], + [showGeneralAlert], ); - const menuItemData = useMemo(() => { + const menuItemData: Item[] = useMemo(() => { const data = lodashCompact([ !shouldHideCameraOption && { icon: Expensicons.Camera, @@ -193,7 +185,7 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { textTranslationKey: 'attachmentPicker.chooseFromGallery', pickAttachment: () => showImagePicker(launchImageLibrary), }, - { + type !== CONST.ATTACHMENT_PICKER_TYPE.IMAGE && { icon: Expensicons.Paperclip, textTranslationKey: 'attachmentPicker.chooseDocument', pickAttachment: showDocumentPicker, @@ -201,7 +193,7 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { ]); return data; - }, [showDocumentPicker, showImagePicker, shouldHideCameraOption]); + }, [showDocumentPicker, showImagePicker, type, shouldHideCameraOption]); const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({initialFocusedIndex: -1, maxIndex: menuItemData.length - 1, isActive: isVisible}); @@ -215,10 +207,10 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { /** * Opens the attachment modal * - * @param {function} onPickedHandler A callback that will be called with the selected attachment - * @param {function} onCanceledHandler A callback that will be called without a selected attachment + * @param onPickedHandler A callback that will be called with the selected attachment + * @param onCanceledHandler A callback that will be called without a selected attachment */ - const open = (onPickedHandler, onCanceledHandler = () => {}) => { + const open = (onPickedHandler: () => void, onCanceledHandler: () => void = () => {}) => { completeAttachmentSelection.current = onPickedHandler; onCanceled.current = onCanceledHandler; setIsVisible(true); @@ -232,15 +224,23 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { }; /** - * @param {Object} fileData - * @returns {Promise} + * Handles the image/document picker result and + * sends the selected attachment to the caller (parent component) */ - const validateAndCompleteAttachmentSelection = useCallback( - (fileData) => { + const pickAttachment = useCallback( + (attachments: Array = []): Promise => { + if (attachments.length === 0) { + onCanceled.current(); + return Promise.resolve(); + } + + const fileData = attachments[0]; + if (fileData.width === -1 || fileData.height === -1) { showImageCorruptionAlert(); return Promise.resolve(); } + return getDataForUpload(fileData) .then((result) => { completeAttachmentSelection.current(result); @@ -253,33 +253,6 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { [showGeneralAlert, showImageCorruptionAlert], ); - /** - * Handles the image/document picker result and - * sends the selected attachment to the caller (parent component) - * - * @param {Array} attachments - * @returns {Promise} - */ - const pickAttachment = useCallback( - (attachments = []) => { - if (attachments.length === 0) { - onCanceled.current(); - return Promise.resolve(); - } - const fileData = _.first(attachments); - if (Str.isImage(fileData.fileName || fileData.name)) { - RNImage.getSize(fileData.fileCopyUri || fileData.uri, (width, height) => { - fileData.width = width; - fileData.height = height; - return validateAndCompleteAttachmentSelection(fileData); - }); - } else { - return validateAndCompleteAttachmentSelection(fileData); - } - }, - [validateAndCompleteAttachmentSelection], - ); - /** * Setup native attachment selection to start after this popover closes * @@ -287,24 +260,24 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { * @param {Function} item.pickAttachment */ const selectItem = useCallback( - (item) => { + (item: Item) => { /* setTimeout delays execution to the frame after the modal closes * without this on iOS closing the modal closes the gallery/camera as well */ - onModalHide.current = () => - setTimeout( - () => - item - .pickAttachment() - .then(pickAttachment) - .catch(console.error) - .finally(() => delete onModalHide.current), - 200, - ); - + onModalHide.current = () => { + setTimeout(() => { + item + .pickAttachment() + .then(pickAttachment) + .catch(console.error) + .finally(() => delete onModalHide.current !== undefined); + }, 200); + }; + close(); }, [pickAttachment], ); + useKeyboardShortcut( CONST.KEYBOARD_SHORTCUTS.ENTER, @@ -322,10 +295,8 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { /** * Call the `children` renderProp with the interface defined in propTypes - * - * @returns {React.ReactNode} */ - const renderChildren = () => + const renderChildren = (): React.ReactNode => children({ openPicker: ({onPicked, onCanceled: newOnCanceled}) => open(onPicked, newOnCanceled), }); @@ -338,15 +309,16 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { onCanceled.current(); }} isVisible={isVisible} - anchorPosition={styles.createMenuPosition} + anchorRef={popoverRef} + // anchorPosition={styles.createMenuPosition} onModalHide={onModalHide.current} > - {_.map(menuItemData, (item, menuIndex) => ( + {menuItemData.map((item, menuIndex) => ( selectItem(item)} focused={focusedIndex === menuIndex} /> @@ -358,8 +330,6 @@ function AttachmentPicker({type, children, shouldHideCameraOption}) { ); } -AttachmentPicker.propTypes = propTypes; -AttachmentPicker.defaultProps = defaultProps; AttachmentPicker.displayName = 'AttachmentPicker'; -export default AttachmentPicker; +export default AttachmentPicker; \ No newline at end of file diff --git a/src/components/AttachmentPicker/index.js b/src/components/AttachmentPicker/index.tsx similarity index 78% rename from src/components/AttachmentPicker/index.js rename to src/components/AttachmentPicker/index.tsx index 24024eae6515..9a372a9d4a48 100644 --- a/src/components/AttachmentPicker/index.js +++ b/src/components/AttachmentPicker/index.tsx @@ -1,14 +1,12 @@ import React, {useRef} from 'react'; import Visibility from '@libs/Visibility'; import CONST from '@src/CONST'; -import {defaultProps, propTypes} from './attachmentPickerPropTypes'; +import type AttachmentPickerProps from './types'; /** * Returns acceptable FileTypes based on ATTACHMENT_PICKER_TYPE - * @param {String} type - * @returns {String|undefined} Picker will accept all file types when its undefined */ -function getAcceptableFileTypes(type) { +function getAcceptableFileTypes(type: string): string | undefined { if (type !== CONST.ATTACHMENT_PICKER_TYPE.IMAGE) { return; } @@ -22,13 +20,11 @@ function getAcceptableFileTypes(type) { * a callback. This is the web/mWeb/desktop version since * on a Browser we must append a hidden input to the DOM * and listen to onChange event. - * @param {propTypes} props - * @returns {JSX.Element} */ -function AttachmentPicker(props) { - const fileInput = useRef(); - const onPicked = useRef(); - const onCanceled = useRef(() => {}); +function AttachmentPicker({children, type = CONST.ATTACHMENT_PICKER_TYPE.FILE}: AttachmentPickerProps): React.JSX.Element { + const fileInput = useRef(null); + const onPicked = useRef<(file: File) => void>(() => {}); + const onCanceled = useRef<() => void>(() => {}); return ( <> @@ -37,6 +33,10 @@ function AttachmentPicker(props) { type="file" ref={fileInput} onChange={(e) => { + if (!e.target.files) { + return; + } + const file = e.target.files[0]; if (file) { @@ -45,7 +45,9 @@ function AttachmentPicker(props) { } // Cleanup after selecting a file to start from a fresh state - fileInput.current.value = null; + if (fileInput.current) { + fileInput.current.value = ''; + } }} // We are stopping the event propagation because triggering the `click()` on the hidden input // causes the event to unexpectedly bubble up to anything wrapping this component e.g. Pressable @@ -72,12 +74,12 @@ function AttachmentPicker(props) { {once: true}, ); }} - accept={getAcceptableFileTypes(props.type)} + accept={getAcceptableFileTypes(type)} /> - {props.children({ + {children({ openPicker: ({onPicked: newOnPicked, onCanceled: newOnCanceled = () => {}}) => { onPicked.current = newOnPicked; - fileInput.current.click(); + fileInput.current?.click(); onCanceled.current = newOnCanceled; }, })} @@ -85,6 +87,4 @@ function AttachmentPicker(props) { ); } -AttachmentPicker.propTypes = propTypes; -AttachmentPicker.defaultProps = defaultProps; -export default AttachmentPicker; +export default AttachmentPicker; \ No newline at end of file diff --git a/src/components/AttachmentPicker/launchCamera.android.js b/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts similarity index 88% rename from src/components/AttachmentPicker/launchCamera.android.js rename to src/components/AttachmentPicker/launchCamera/launchCamera.android.ts index b431c55e756d..cac42a874495 100644 --- a/src/components/AttachmentPicker/launchCamera.android.js +++ b/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts @@ -1,15 +1,14 @@ import {PermissionsAndroid} from 'react-native'; import {launchCamera} from 'react-native-image-picker'; +import type {Callback, CameraOptions} from './types'; /** * Launching the camera for Android involves checking for permissions * And only then starting the camera * If the user deny permission the callback will be called with an error response * in the same format as the error returned by react-native-image-picker - * @param {CameraOptions} options - * @param {function} callback - callback called with the result */ -export default function launchCameraAndroid(options, callback) { +export default function launchCameraAndroid(options: CameraOptions, callback: Callback) { // Checks current camera permissions and prompts the user in case they aren't granted PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA) .then((permission) => { @@ -29,4 +28,4 @@ export default function launchCameraAndroid(options, callback) { errorCode: error.errorCode || 'others', }); }); -} +} \ No newline at end of file diff --git a/src/components/AttachmentPicker/launchCamera.ios.js b/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts similarity index 100% rename from src/components/AttachmentPicker/launchCamera.ios.js rename to src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts diff --git a/src/components/AttachmentPicker/launchCamera.js b/src/components/AttachmentPicker/launchCamera/launchCamera.ts similarity index 66% rename from src/components/AttachmentPicker/launchCamera.js rename to src/components/AttachmentPicker/launchCamera/launchCamera.ts index dc1f921086de..d272e629f98f 100644 --- a/src/components/AttachmentPicker/launchCamera.js +++ b/src/components/AttachmentPicker/launchCamera/launchCamera.ts @@ -1,3 +1,3 @@ import {launchCamera} from 'react-native-image-picker'; -export default launchCamera; +export default launchCamera; \ No newline at end of file diff --git a/src/components/AttachmentPicker/launchCamera/types.ts b/src/components/AttachmentPicker/launchCamera/types.ts new file mode 100644 index 000000000000..c7f0481c7773 --- /dev/null +++ b/src/components/AttachmentPicker/launchCamera/types.ts @@ -0,0 +1,47 @@ +type Callback = (response: ImagePickerResponse) => void; +type OptionsCommon = { + mediaType: MediaType; + maxWidth?: number; + maxHeight?: number; + quality?: PhotoQuality; + videoQuality?: AndroidVideoOptions | IOSVideoOptions; + includeBase64?: boolean; + includeExtra?: boolean; + presentationStyle?: 'currentContext' | 'fullScreen' | 'pageSheet' | 'formSheet' | 'popover' | 'overFullScreen' | 'overCurrentContext'; +}; +type ImageLibraryOptions = OptionsCommon & { + selectionLimit?: number; +}; +type CameraOptions = OptionsCommon & { + durationLimit?: number; + saveToPhotos?: boolean; + cameraType?: CameraType; +}; +type Asset = { + base64?: string; + uri?: string; + width?: number; + height?: number; + fileSize?: number; + type?: string; + fileName?: string; + duration?: number; + bitrate?: number; + timestamp?: string; + id?: string; +}; +type ImagePickerResponse = { + didCancel?: boolean; + errorCode?: ErrorCode; + errorMessage?: string; + assets?: Asset[]; +}; +type PhotoQuality = 0 | 0.1 | 0.2 | 0.3 | 0.4 | 0.5 | 0.6 | 0.7 | 0.8 | 0.9 | 1; +type CameraType = 'back' | 'front'; +type MediaType = 'photo' | 'video' | 'mixed'; +type AndroidVideoOptions = 'low' | 'high'; +type IOSVideoOptions = 'low' | 'medium' | 'high'; +type ErrorCode = 'camera_unavailable' | 'permission' | 'others'; +type ErrorLaunchCamera = Error & ErrorCode + +export type {CameraOptions, Callback, ErrorCode, ImagePickerResponse, Asset, ImageLibraryOptions}; \ No newline at end of file diff --git a/src/components/AttachmentPicker/attachmentPickerPropTypes.js b/src/components/AttachmentPicker/types.ts similarity index 58% rename from src/components/AttachmentPicker/attachmentPickerPropTypes.js rename to src/components/AttachmentPicker/types.ts index a3a346f5ea27..19b98d85f691 100644 --- a/src/components/AttachmentPicker/attachmentPickerPropTypes.js +++ b/src/components/AttachmentPicker/types.ts @@ -1,7 +1,8 @@ -import PropTypes from 'prop-types'; -import CONST from '@src/CONST'; +import type {ReactNode} from 'react'; +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; -const propTypes = { +type AttachmentPickerProps = { /** * A renderProp with the following interface * @@ -20,14 +21,10 @@ const propTypes = { * )} * * */ - children: PropTypes.func.isRequired, + children: (openPicker: ({onPicked, onCanceled}: {onPicked: (file: File) => void; onCanceled?: () => void}) => void) => ReactNode; /** The types of files that can be selected with this picker. */ - type: PropTypes.oneOf([CONST.ATTACHMENT_PICKER_TYPE.FILE, CONST.ATTACHMENT_PICKER_TYPE.IMAGE]), + type?: ValueOf; }; -const defaultProps = { - type: CONST.ATTACHMENT_PICKER_TYPE.FILE, -}; - -export {propTypes, defaultProps}; +export default AttachmentPickerProps; \ No newline at end of file From 049f95069ee50330c678076d1b4deadcc9aa389c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 4 Mar 2024 17:08:58 +0700 Subject: [PATCH 02/26] fix: type in launch camera --- .../AttachmentPicker/index.native.tsx | 23 ++++++++++--------- src/components/AttachmentPicker/index.tsx | 2 +- .../launchCamera/launchCamera.android.ts | 7 +++--- .../launchCamera/launchCamera.ios.ts | 11 ++++----- .../launchCamera/launchCamera.ts | 2 +- .../AttachmentPicker/launchCamera/types.ts | 11 +++++++-- src/components/AttachmentPicker/types.ts | 2 +- 7 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index dcc4296b71ac..3aaaac1083a0 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -3,7 +3,10 @@ import React, {useCallback, useMemo, useRef, useState} from 'react'; import {Alert, View} from 'react-native'; import RNFetchBlob from 'react-native-blob-util'; import RNDocumentPicker from 'react-native-document-picker'; +import type {DocumentPickerOptions, DocumentPickerResponse} from 'react-native-document-picker'; +import type {SupportedPlatforms} from 'react-native-document-picker/lib/typescript/fileTypes'; import {launchImageLibrary} from 'react-native-image-picker'; +import type {Asset, Callback, CameraOptions, ImagePickerResponse} from 'react-native-image-picker'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Popover from '@components/Popover'; @@ -16,9 +19,6 @@ import * as FileUtils from '@libs/fileDownload/FileUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type IconAsset from '@src/types/utils/IconAsset'; -import type {DocumentPickerOptions, DocumentPickerResponse} from 'react-native-document-picker' -import type {SupportedPlatforms} from 'react-native-document-picker/lib/typescript/fileTypes'; -import type {Asset, Callback, CameraOptions, ImagePickerResponse} from 'react-native-image-picker'; import launchCamera from './launchCamera/launchCamera'; import type BaseAttachmentPickerProps from './types'; @@ -119,9 +119,12 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s /** * A generic handling when we don't know the exact reason for an error */ - const showGeneralAlert = useCallback((message = '') => { - Alert.alert(translate('attachmentPicker.attachmentError'), `${message !== '' ? message : translate('attachmentPicker.errorWhileSelectingAttachment')}`); - }, [translate]); + const showGeneralAlert = useCallback( + (message = '') => { + Alert.alert(translate('attachmentPicker.attachmentError'), `${message !== '' ? message : translate('attachmentPicker.errorWhileSelectingAttachment')}`); + }, + [translate], + ); /** * Common image picker handling @@ -265,19 +268,17 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s * without this on iOS closing the modal closes the gallery/camera as well */ onModalHide.current = () => { setTimeout(() => { - item - .pickAttachment() + item.pickAttachment() .then(pickAttachment) .catch(console.error) .finally(() => delete onModalHide.current !== undefined); }, 200); }; - + close(); }, [pickAttachment], ); - useKeyboardShortcut( CONST.KEYBOARD_SHORTCUTS.ENTER, @@ -332,4 +333,4 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s AttachmentPicker.displayName = 'AttachmentPicker'; -export default AttachmentPicker; \ No newline at end of file +export default AttachmentPicker; diff --git a/src/components/AttachmentPicker/index.tsx b/src/components/AttachmentPicker/index.tsx index 9a372a9d4a48..e8a23e29f114 100644 --- a/src/components/AttachmentPicker/index.tsx +++ b/src/components/AttachmentPicker/index.tsx @@ -87,4 +87,4 @@ function AttachmentPicker({children, type = CONST.ATTACHMENT_PICKER_TYPE.FILE}: ); } -export default AttachmentPicker; \ No newline at end of file +export default AttachmentPicker; diff --git a/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts b/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts index cac42a874495..135b5dfd80e6 100644 --- a/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts +++ b/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts @@ -1,6 +1,7 @@ import {PermissionsAndroid} from 'react-native'; import {launchCamera} from 'react-native-image-picker'; import type {Callback, CameraOptions} from './types'; +import {ErrorLaunchCamera} from './types'; /** * Launching the camera for Android involves checking for permissions @@ -13,9 +14,7 @@ export default function launchCameraAndroid(options: CameraOptions, callback: Ca PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA) .then((permission) => { if (permission !== PermissionsAndroid.RESULTS.GRANTED) { - const error = new Error('User did not grant permissions'); - error.errorCode = 'permission'; - throw error; + throw new ErrorLaunchCamera('User did not grant permissions', 'permission'); } launchCamera(options, callback); @@ -28,4 +27,4 @@ export default function launchCameraAndroid(options: CameraOptions, callback: Ca errorCode: error.errorCode || 'others', }); }); -} \ No newline at end of file +} diff --git a/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts b/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts index d6e3518d7188..cffb00f39e4a 100644 --- a/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts +++ b/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts @@ -1,24 +1,21 @@ import {launchCamera} from 'react-native-image-picker'; import {PERMISSIONS, request, RESULTS} from 'react-native-permissions'; +import type {Callback, CameraOptions} from './types'; +import {ErrorLaunchCamera} from './types'; /** * Launching the camera for iOS involves checking for permissions * And only then starting the camera * If the user deny permission the callback will be called with an error response * in the same format as the error returned by react-native-image-picker - * @param {CameraOptions} options - * @param {function} callback - callback called with the result */ -export default function launchCameraIOS(options, callback) { +export default function launchCameraIOS(options: CameraOptions, callback: Callback) { // Checks current camera permissions and prompts the user in case they aren't granted request(PERMISSIONS.IOS.CAMERA) .then((permission) => { if (permission !== RESULTS.GRANTED) { - const error = new Error('User did not grant permissions'); - error.errorCode = 'permission'; - throw error; + throw new ErrorLaunchCamera('User did not grant permissions', 'permission'); } - launchCamera(options, callback); }) .catch((error) => { diff --git a/src/components/AttachmentPicker/launchCamera/launchCamera.ts b/src/components/AttachmentPicker/launchCamera/launchCamera.ts index d272e629f98f..dc1f921086de 100644 --- a/src/components/AttachmentPicker/launchCamera/launchCamera.ts +++ b/src/components/AttachmentPicker/launchCamera/launchCamera.ts @@ -1,3 +1,3 @@ import {launchCamera} from 'react-native-image-picker'; -export default launchCamera; \ No newline at end of file +export default launchCamera; diff --git a/src/components/AttachmentPicker/launchCamera/types.ts b/src/components/AttachmentPicker/launchCamera/types.ts index c7f0481c7773..1a3aae3f0ad7 100644 --- a/src/components/AttachmentPicker/launchCamera/types.ts +++ b/src/components/AttachmentPicker/launchCamera/types.ts @@ -42,6 +42,13 @@ type MediaType = 'photo' | 'video' | 'mixed'; type AndroidVideoOptions = 'low' | 'high'; type IOSVideoOptions = 'low' | 'medium' | 'high'; type ErrorCode = 'camera_unavailable' | 'permission' | 'others'; -type ErrorLaunchCamera = Error & ErrorCode +class ErrorLaunchCamera extends Error { + errorCode: ErrorCode; -export type {CameraOptions, Callback, ErrorCode, ImagePickerResponse, Asset, ImageLibraryOptions}; \ No newline at end of file + constructor(message: string, errorCode: ErrorCode) { + super(message); + this.errorCode = errorCode; + } +} +export {ErrorLaunchCamera}; +export type {CameraOptions, Callback, ErrorCode, ImagePickerResponse, Asset, ImageLibraryOptions}; diff --git a/src/components/AttachmentPicker/types.ts b/src/components/AttachmentPicker/types.ts index 19b98d85f691..b9974f4082b2 100644 --- a/src/components/AttachmentPicker/types.ts +++ b/src/components/AttachmentPicker/types.ts @@ -27,4 +27,4 @@ type AttachmentPickerProps = { type?: ValueOf; }; -export default AttachmentPickerProps; \ No newline at end of file +export default AttachmentPickerProps; From d34c62bd1aa9ab68b95f58bf37d8268111d0b72a Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 5 Mar 2024 10:21:51 +0700 Subject: [PATCH 03/26] fix type select item function --- src/components/AttachmentPicker/index.native.tsx | 9 ++++----- src/components/AttachmentPicker/index.tsx | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 3aaaac1083a0..e144a210fa7c 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -109,7 +109,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s const [isVisible, setIsVisible] = useState(false); const completeAttachmentSelection = useRef<(data: FileResult) => void>(() => {}); - const onModalHide = useRef<() => void>(() => {}); + const onModalHide = useRef<() => void>(); const onCanceled = useRef<() => void>(() => {}); const popoverRef = useRef(null); @@ -266,15 +266,14 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s (item: Item) => { /* setTimeout delays execution to the frame after the modal closes * without this on iOS closing the modal closes the gallery/camera as well */ - onModalHide.current = () => { + onModalHide.current = () => { setTimeout(() => { item.pickAttachment() - .then(pickAttachment) + .then((result) => pickAttachment(result as Array)) .catch(console.error) - .finally(() => delete onModalHide.current !== undefined); + .finally(() => delete onModalHide.current); }, 200); }; - close(); }, [pickAttachment], diff --git a/src/components/AttachmentPicker/index.tsx b/src/components/AttachmentPicker/index.tsx index e8a23e29f114..9a372a9d4a48 100644 --- a/src/components/AttachmentPicker/index.tsx +++ b/src/components/AttachmentPicker/index.tsx @@ -87,4 +87,4 @@ function AttachmentPicker({children, type = CONST.ATTACHMENT_PICKER_TYPE.FILE}: ); } -export default AttachmentPicker; +export default AttachmentPicker; \ No newline at end of file From cd2681e34826070baa28bafde8a7fd0eda10007e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 6 Mar 2024 10:13:31 +0700 Subject: [PATCH 04/26] fix type --- src/components/AttachmentPicker/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/AttachmentPicker/index.tsx b/src/components/AttachmentPicker/index.tsx index 9a372a9d4a48..dc5ce45153a3 100644 --- a/src/components/AttachmentPicker/index.tsx +++ b/src/components/AttachmentPicker/index.tsx @@ -76,13 +76,13 @@ function AttachmentPicker({children, type = CONST.ATTACHMENT_PICKER_TYPE.FILE}: }} accept={getAcceptableFileTypes(type)} /> - {children({ - openPicker: ({onPicked: newOnPicked, onCanceled: newOnCanceled = () => {}}) => { + {children( + ({onPicked: newOnPicked, onCanceled: newOnCanceled = () => {}}) => { onPicked.current = newOnPicked; fileInput.current?.click(); onCanceled.current = newOnCanceled; }, - })} + )} ); } From 2dda80fcfc839d3823f07882bde897b88cf8c3c2 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 6 Mar 2024 14:32:22 +0700 Subject: [PATCH 05/26] fix: type attachment --- src/components/AttachmentModal.tsx | 10 +++++----- .../AttachmentPicker/index.native.tsx | 20 ++++++------------- src/components/AttachmentPicker/index.tsx | 8 ++++---- src/components/AttachmentPicker/types.ts | 4 ++-- src/components/AvatarWithImagePicker.tsx | 18 +++++++---------- .../AttachmentPickerWithMenuItems.tsx | 1 - 6 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index eed40d75387e..2a6ebd12f143 100755 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -71,12 +71,12 @@ type Attachment = { }; type ImagePickerResponse = { - height: number; + height?: number; name: string; - size: number; + size?: number | null; type: string; uri: string; - width: number; + width?: number; }; type FileObject = File | ImagePickerResponse; @@ -292,14 +292,14 @@ function AttachmentModal({ }, [transaction, report]); const isValidFile = useCallback((fileObject: FileObject) => { - if (fileObject.size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { + if (fileObject.size && fileObject.size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { setIsAttachmentInvalid(true); setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooLarge'); setAttachmentInvalidReason('attachmentPicker.sizeExceeded'); return false; } - if (fileObject.size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { + if (fileObject.size && fileObject.size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { setIsAttachmentInvalid(true); setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooSmall'); setAttachmentInvalidReason('attachmentPicker.sizeNotMet'); diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index e144a210fa7c..7412c7382513 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -7,6 +7,7 @@ import type {DocumentPickerOptions, DocumentPickerResponse} from 'react-native-d import type {SupportedPlatforms} from 'react-native-document-picker/lib/typescript/fileTypes'; import {launchImageLibrary} from 'react-native-image-picker'; import type {Asset, Callback, CameraOptions, ImagePickerResponse} from 'react-native-image-picker'; +import type {FileObject} from '@components/AttachmentModal'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Popover from '@components/Popover'; @@ -33,15 +34,6 @@ type Item = { pickAttachment: () => Promise; }; -type FileResult = { - name: string; - type: string; - width: number | undefined; - height: number | undefined; - uri: string; - size: number | null; -}; - /** * See https://github.com/react-native-image-picker/react-native-image-picker/#options * for ImagePicker configuration options @@ -77,9 +69,9 @@ const documentPickerOptions = { * The data returned from `show` is different on web and mobile, so use this function to ensure the data we * send to the xhr will be handled properly. */ -const getDataForUpload = (fileData: Asset & DocumentPickerResponse): Promise => { +const getDataForUpload = (fileData: Asset & DocumentPickerResponse): Promise => { const fileName = fileData.fileName ?? fileData.name ?? 'chat_attachment'; - const fileResult: FileResult = { + const fileResult: FileObject = { name: FileUtils.cleanFileName(fileName), type: fileData.type, width: fileData.width, @@ -108,7 +100,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s const styles = useThemeStyles(); const [isVisible, setIsVisible] = useState(false); - const completeAttachmentSelection = useRef<(data: FileResult) => void>(() => {}); + const completeAttachmentSelection = useRef<(data: FileObject) => void>(() => {}); const onModalHide = useRef<() => void>(); const onCanceled = useRef<() => void>(() => {}); const popoverRef = useRef(null); @@ -213,7 +205,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s * @param onPickedHandler A callback that will be called with the selected attachment * @param onCanceledHandler A callback that will be called without a selected attachment */ - const open = (onPickedHandler: () => void, onCanceledHandler: () => void = () => {}) => { + const open = (onPickedHandler: (file: FileObject) => void, onCanceledHandler: () => void = () => {}) => { completeAttachmentSelection.current = onPickedHandler; onCanceled.current = onCanceledHandler; setIsVisible(true); @@ -266,7 +258,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s (item: Item) => { /* setTimeout delays execution to the frame after the modal closes * without this on iOS closing the modal closes the gallery/camera as well */ - onModalHide.current = () => { + onModalHide.current = () => { setTimeout(() => { item.pickAttachment() .then((result) => pickAttachment(result as Array)) diff --git a/src/components/AttachmentPicker/index.tsx b/src/components/AttachmentPicker/index.tsx index dc5ce45153a3..e8a23e29f114 100644 --- a/src/components/AttachmentPicker/index.tsx +++ b/src/components/AttachmentPicker/index.tsx @@ -76,15 +76,15 @@ function AttachmentPicker({children, type = CONST.ATTACHMENT_PICKER_TYPE.FILE}: }} accept={getAcceptableFileTypes(type)} /> - {children( - ({onPicked: newOnPicked, onCanceled: newOnCanceled = () => {}}) => { + {children({ + openPicker: ({onPicked: newOnPicked, onCanceled: newOnCanceled = () => {}}) => { onPicked.current = newOnPicked; fileInput.current?.click(); onCanceled.current = newOnCanceled; }, - )} + })} ); } -export default AttachmentPicker; \ No newline at end of file +export default AttachmentPicker; diff --git a/src/components/AttachmentPicker/types.ts b/src/components/AttachmentPicker/types.ts index b9974f4082b2..66ee91b33c24 100644 --- a/src/components/AttachmentPicker/types.ts +++ b/src/components/AttachmentPicker/types.ts @@ -1,5 +1,6 @@ import type {ReactNode} from 'react'; import type {ValueOf} from 'type-fest'; +import type {FileObject} from '@components/AttachmentModal'; import type CONST from '@src/CONST'; type AttachmentPickerProps = { @@ -21,8 +22,7 @@ type AttachmentPickerProps = { * )} * * */ - children: (openPicker: ({onPicked, onCanceled}: {onPicked: (file: File) => void; onCanceled?: () => void}) => void) => ReactNode; - + children: (props: {openPicker: (options: {onPicked: (file: FileObject) => void; onCanceled?: () => void}) => void}) => ReactNode; /** The types of files that can be selected with this picker. */ type?: ValueOf; }; diff --git a/src/components/AvatarWithImagePicker.tsx b/src/components/AvatarWithImagePicker.tsx index 5755c69641c8..6c8b4e65ae93 100644 --- a/src/components/AvatarWithImagePicker.tsx +++ b/src/components/AvatarWithImagePicker.tsx @@ -15,7 +15,7 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type IconAsset from '@src/types/utils/IconAsset'; -import AttachmentModal from './AttachmentModal'; +import AttachmentModal, {FileObject} from './AttachmentModal'; import AttachmentPicker from './AttachmentPicker'; import Avatar from './Avatar'; import AvatarCropModal from './AvatarCropModal/AvatarCropModal'; @@ -34,7 +34,7 @@ type ErrorData = { }; type OpenPickerParams = { - onPicked: (image: File) => void; + onPicked: (image: FileObject) => void; }; type OpenPicker = (args: OpenPickerParams) => void; @@ -174,7 +174,7 @@ function AvatarWithImagePicker({ /** * Check if the attachment extension is allowed. */ - const isValidExtension = (image: File): boolean => { + const isValidExtension = (image: FileObject): boolean => { const {fileExtension} = FileUtils.splitExtensionFromFileName(image?.name ?? ''); return CONST.AVATAR_ALLOWED_EXTENSIONS.some((extension) => extension === fileExtension.toLowerCase()); }; @@ -182,12 +182,12 @@ function AvatarWithImagePicker({ /** * Check if the attachment size is less than allowed size. */ - const isValidSize = (image: File): boolean => (image?.size ?? 0) < CONST.AVATAR_MAX_ATTACHMENT_SIZE; + const isValidSize = (image: FileObject): boolean => (image?.size ?? 0) < CONST.AVATAR_MAX_ATTACHMENT_SIZE; /** * Check if the attachment resolution matches constraints. */ - const isValidResolution = (image: File): Promise => + const isValidResolution = (image: FileObject): Promise => getImageResolution(image).then( ({height, width}) => height >= CONST.AVATAR_MIN_HEIGHT_PX && width >= CONST.AVATAR_MIN_WIDTH_PX && height <= CONST.AVATAR_MAX_HEIGHT_PX && width <= CONST.AVATAR_MAX_WIDTH_PX, ); @@ -195,7 +195,7 @@ function AvatarWithImagePicker({ /** * Validates if an image has a valid resolution and opens an avatar crop modal */ - const showAvatarCropModal = (image: File) => { + const showAvatarCropModal = (image: FileObject) => { if (!isValidExtension(image)) { setError('avatarWithImagePicker.notAllowedExtension', {allowedExtensions: CONST.AVATAR_ALLOWED_EXTENSIONS}); return; @@ -343,11 +343,7 @@ function AvatarWithImagePicker({ maybeIcon={isUsingDefaultAvatar} > {({show}) => ( - - {/* @ts-expect-error TODO: Remove this once AttachmentPicker (https://github.com/Expensify/App/issues/25134) is migrated to TypeScript. */} + {({openPicker}) => { const menuItems = createMenuItems(openPicker); diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index 68c7f0883683..b8b4693ac66e 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -185,7 +185,6 @@ function AttachmentPickerWithMenuItems({ return ( - {/* @ts-expect-error TODO: Remove this once AttachmentPicker (https://github.com/Expensify/App/issues/25134) is migrated to TypeScript. */} {({openPicker}) => { const triggerAttachmentPicker = () => { onTriggerAttachmentPicker(); From 9ac6e89085815d5c3f3064262fc3c37dde8206de Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 6 Mar 2024 14:38:28 +0700 Subject: [PATCH 06/26] fix lint --- src/components/AvatarWithImagePicker.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/AvatarWithImagePicker.tsx b/src/components/AvatarWithImagePicker.tsx index 6c8b4e65ae93..0e1a8d0e17a6 100644 --- a/src/components/AvatarWithImagePicker.tsx +++ b/src/components/AvatarWithImagePicker.tsx @@ -15,7 +15,8 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import type IconAsset from '@src/types/utils/IconAsset'; -import AttachmentModal, {FileObject} from './AttachmentModal'; +import AttachmentModal from './AttachmentModal'; +import type {FileObject} from './AttachmentModal'; import AttachmentPicker from './AttachmentPicker'; import Avatar from './Avatar'; import AvatarCropModal from './AvatarCropModal/AvatarCropModal'; From 1e3abba497e864983486ebb25cad4d35e74367d4 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 11 Mar 2024 16:31:59 +0700 Subject: [PATCH 07/26] fix type launch camera --- src/components/AttachmentPicker/index.native.tsx | 10 +++++----- .../launchCamera/launchCamera.android.ts | 8 +++++--- .../AttachmentPicker/launchCamera/launchCamera.ios.ts | 7 ++++--- src/components/AttachmentPicker/launchCamera/types.ts | 7 ++++++- src/components/AttachmentPicker/types.ts | 4 +++- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 7412c7382513..507eef40e1d8 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -30,7 +30,7 @@ type AttachmentPickerProps = BaseAttachmentPickerProps & { type Item = { icon: IconAsset; - textTranslationKey: string; + textTranslationKey: TranslationPaths; pickAttachment: () => Promise; }; @@ -172,17 +172,17 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s const data = lodashCompact([ !shouldHideCameraOption && { icon: Expensicons.Camera, - textTranslationKey: 'attachmentPicker.takePhoto', + textTranslationKey: 'attachmentPicker.takePhoto' as TranslationPaths, pickAttachment: () => showImagePicker(launchCamera), }, { icon: Expensicons.Gallery, - textTranslationKey: 'attachmentPicker.chooseFromGallery', + textTranslationKey: 'attachmentPicker.chooseFromGallery' as TranslationPaths, pickAttachment: () => showImagePicker(launchImageLibrary), }, type !== CONST.ATTACHMENT_PICKER_TYPE.IMAGE && { icon: Expensicons.Paperclip, - textTranslationKey: 'attachmentPicker.chooseDocument', + textTranslationKey: 'attachmentPicker.chooseDocument' as TranslationPaths, pickAttachment: showDocumentPicker, }, ]); @@ -310,7 +310,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s selectItem(item)} focused={focusedIndex === menuIndex} /> diff --git a/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts b/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts index 135b5dfd80e6..748639f0f903 100644 --- a/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts +++ b/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts @@ -1,6 +1,6 @@ import {PermissionsAndroid} from 'react-native'; import {launchCamera} from 'react-native-image-picker'; -import type {Callback, CameraOptions} from './types'; +import type {LaunchCamera} from './types'; import {ErrorLaunchCamera} from './types'; /** @@ -9,7 +9,7 @@ import {ErrorLaunchCamera} from './types'; * If the user deny permission the callback will be called with an error response * in the same format as the error returned by react-native-image-picker */ -export default function launchCameraAndroid(options: CameraOptions, callback: Callback) { +const launchCameraAndroid: LaunchCamera = (options, callback) => { // Checks current camera permissions and prompts the user in case they aren't granted PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA) .then((permission) => { @@ -27,4 +27,6 @@ export default function launchCameraAndroid(options: CameraOptions, callback: Ca errorCode: error.errorCode || 'others', }); }); -} +}; + +export default launchCameraAndroid; diff --git a/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts b/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts index cffb00f39e4a..ae421b583f6b 100644 --- a/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts +++ b/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts @@ -1,6 +1,6 @@ import {launchCamera} from 'react-native-image-picker'; import {PERMISSIONS, request, RESULTS} from 'react-native-permissions'; -import type {Callback, CameraOptions} from './types'; +import type {LaunchCamera} from './types'; import {ErrorLaunchCamera} from './types'; /** @@ -9,7 +9,7 @@ import {ErrorLaunchCamera} from './types'; * If the user deny permission the callback will be called with an error response * in the same format as the error returned by react-native-image-picker */ -export default function launchCameraIOS(options: CameraOptions, callback: Callback) { +const launchCameraIOS: LaunchCamera = (options, callback) => { // Checks current camera permissions and prompts the user in case they aren't granted request(PERMISSIONS.IOS.CAMERA) .then((permission) => { @@ -26,4 +26,5 @@ export default function launchCameraIOS(options: CameraOptions, callback: Callba errorCode: error.errorCode || 'others', }); }); -} +}; +export default launchCameraIOS; diff --git a/src/components/AttachmentPicker/launchCamera/types.ts b/src/components/AttachmentPicker/launchCamera/types.ts index 1a3aae3f0ad7..48813d331e3f 100644 --- a/src/components/AttachmentPicker/launchCamera/types.ts +++ b/src/components/AttachmentPicker/launchCamera/types.ts @@ -48,7 +48,12 @@ class ErrorLaunchCamera extends Error { constructor(message: string, errorCode: ErrorCode) { super(message); this.errorCode = errorCode; + + } } + +type LaunchCamera = (options: CameraOptions, callback: Callback) => void; + export {ErrorLaunchCamera}; -export type {CameraOptions, Callback, ErrorCode, ImagePickerResponse, Asset, ImageLibraryOptions}; +export type {CameraOptions, Callback, ErrorCode, ImagePickerResponse, Asset, ImageLibraryOptions, LaunchCamera}; diff --git a/src/components/AttachmentPicker/types.ts b/src/components/AttachmentPicker/types.ts index 66ee91b33c24..2d8ab79af3ed 100644 --- a/src/components/AttachmentPicker/types.ts +++ b/src/components/AttachmentPicker/types.ts @@ -3,6 +3,8 @@ import type {ValueOf} from 'type-fest'; import type {FileObject} from '@components/AttachmentModal'; import type CONST from '@src/CONST'; +type OpenPickerFunction = (options: {onPicked: (file: FileObject) => void; onCanceled?: () => void}) => void; + type AttachmentPickerProps = { /** * A renderProp with the following interface @@ -22,7 +24,7 @@ type AttachmentPickerProps = { * )} * * */ - children: (props: {openPicker: (options: {onPicked: (file: FileObject) => void; onCanceled?: () => void}) => void}) => ReactNode; + children: (props: {openPicker: OpenPickerFunction}) => ReactNode; /** The types of files that can be selected with this picker. */ type?: ValueOf; }; From fc29a73a1ca2d8cc21d785c80fd0de8f159f5712 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 11 Mar 2024 17:07:45 +0700 Subject: [PATCH 08/26] fix: lint --- src/components/AttachmentPicker/launchCamera/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/AttachmentPicker/launchCamera/types.ts b/src/components/AttachmentPicker/launchCamera/types.ts index 48813d331e3f..23040d94bb67 100644 --- a/src/components/AttachmentPicker/launchCamera/types.ts +++ b/src/components/AttachmentPicker/launchCamera/types.ts @@ -48,8 +48,6 @@ class ErrorLaunchCamera extends Error { constructor(message: string, errorCode: ErrorCode) { super(message); this.errorCode = errorCode; - - } } From 8e5a242ff2b76d6fd44b29ce6030e5ee65accc41 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 14 Mar 2024 00:20:10 +0700 Subject: [PATCH 09/26] fix type attachment picker --- .../AttachmentPicker/index.native.tsx | 65 ++++++++++++------- src/components/AttachmentPicker/types.ts | 7 +- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 507eef40e1d8..6f89ca2543df 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -1,10 +1,8 @@ -import lodashCompact from 'lodash/compact'; import React, {useCallback, useMemo, useRef, useState} from 'react'; import {Alert, View} from 'react-native'; import RNFetchBlob from 'react-native-blob-util'; import RNDocumentPicker from 'react-native-document-picker'; -import type {DocumentPickerOptions, DocumentPickerResponse} from 'react-native-document-picker'; -import type {SupportedPlatforms} from 'react-native-document-picker/lib/typescript/fileTypes'; +import type {DocumentPickerResponse} from 'react-native-document-picker'; import {launchImageLibrary} from 'react-native-image-picker'; import type {Asset, Callback, CameraOptions, ImagePickerResponse} from 'react-native-image-picker'; import type {FileObject} from '@components/AttachmentModal'; @@ -58,12 +56,28 @@ const getImagePickerOptions = (type: string): CameraOptions => { }; /** - * See https://github.com/rnmods/react-native-document-picker#options for DocumentPicker configuration options + * Return documentPickerOptions based on the type + * @param {String} type + * @returns {Object} */ -const documentPickerOptions = { - type: [RNDocumentPicker.types.allFiles], - copyTo: 'cachesDirectory', -} satisfies DocumentPickerOptions; + +const getDocumentPickerOptions = ( + type: string, +): { + type: string[]; + copyTo: 'cachesDirectory' | 'documentDirectory'; +} => { + if (type === CONST.ATTACHMENT_PICKER_TYPE.IMAGE) { + return { + type: [RNDocumentPicker.types.images], + copyTo: 'cachesDirectory', + }; + } + return { + type: [RNDocumentPicker.types.allFiles], + copyTo: 'cachesDirectory', + }; +}; /** * The data returned from `show` is different on web and mobile, so use this function to ensure the data we @@ -157,7 +171,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s */ const showDocumentPicker = useCallback( (): Promise => - RNDocumentPicker.pick(documentPickerOptions).catch((error) => { + RNDocumentPicker.pick(getDocumentPickerOptions(type)).catch((error) => { if (RNDocumentPicker.isCancel(error)) { return; } @@ -165,30 +179,32 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s showGeneralAlert(error.message); throw error; }), - [showGeneralAlert], + [showGeneralAlert, type], ); const menuItemData: Item[] = useMemo(() => { - const data = lodashCompact([ - !shouldHideCameraOption && { - icon: Expensicons.Camera, - textTranslationKey: 'attachmentPicker.takePhoto' as TranslationPaths, - pickAttachment: () => showImagePicker(launchCamera), - }, + const data: Item[] = [ { icon: Expensicons.Gallery, - textTranslationKey: 'attachmentPicker.chooseFromGallery' as TranslationPaths, + textTranslationKey: 'attachmentPicker.chooseFromGallery', pickAttachment: () => showImagePicker(launchImageLibrary), }, - type !== CONST.ATTACHMENT_PICKER_TYPE.IMAGE && { + { icon: Expensicons.Paperclip, - textTranslationKey: 'attachmentPicker.chooseDocument' as TranslationPaths, + textTranslationKey: 'attachmentPicker.chooseDocument', pickAttachment: showDocumentPicker, }, - ]); + ]; + if (!shouldHideCameraOption) { + data.push({ + icon: Expensicons.Camera, + textTranslationKey: 'attachmentPicker.takePhoto', + pickAttachment: () => showImagePicker(launchCamera), + }); + } return data; - }, [showDocumentPicker, showImagePicker, type, shouldHideCameraOption]); + }, [showDocumentPicker, showImagePicker, shouldHideCameraOption]); const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({initialFocusedIndex: -1, maxIndex: menuItemData.length - 1, isActive: isVisible}); @@ -223,7 +239,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s * sends the selected attachment to the caller (parent component) */ const pickAttachment = useCallback( - (attachments: Array = []): Promise => { + (attachments: Array<(Asset & DocumentPickerResponse) | void> = []): Promise => { if (attachments.length === 0) { onCanceled.current(); return Promise.resolve(); @@ -231,6 +247,11 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s const fileData = attachments[0]; + if (!fileData) { + onCanceled.current(); + return Promise.resolve(); + } + if (fileData.width === -1 || fileData.height === -1) { showImageCorruptionAlert(); return Promise.resolve(); diff --git a/src/components/AttachmentPicker/types.ts b/src/components/AttachmentPicker/types.ts index 2d8ab79af3ed..574d324fb2c4 100644 --- a/src/components/AttachmentPicker/types.ts +++ b/src/components/AttachmentPicker/types.ts @@ -3,7 +3,12 @@ import type {ValueOf} from 'type-fest'; import type {FileObject} from '@components/AttachmentModal'; import type CONST from '@src/CONST'; -type OpenPickerFunction = (options: {onPicked: (file: FileObject) => void; onCanceled?: () => void}) => void; +type PickerOptions = { + onPicked: (file: FileObject) => void; + onCanceled?: () => void; +}; + +type OpenPickerFunction = (options: PickerOptions) => void; type AttachmentPickerProps = { /** From 564b7d57666c455eed3958990ee7ede7692d2eca Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 14 Mar 2024 00:23:56 +0700 Subject: [PATCH 10/26] fix lint --- src/components/AttachmentPicker/index.native.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 6f89ca2543df..651ebb3010de 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -32,6 +32,11 @@ type Item = { pickAttachment: () => Promise; }; +type DocumentPickerOptionsParams = { + type: string[]; + copyTo: 'cachesDirectory' | 'documentDirectory'; +}; + /** * See https://github.com/react-native-image-picker/react-native-image-picker/#options * for ImagePicker configuration options @@ -61,12 +66,7 @@ const getImagePickerOptions = (type: string): CameraOptions => { * @returns {Object} */ -const getDocumentPickerOptions = ( - type: string, -): { - type: string[]; - copyTo: 'cachesDirectory' | 'documentDirectory'; -} => { +const getDocumentPickerOptions = (type: string): DocumentPickerOptionsParams => { if (type === CONST.ATTACHMENT_PICKER_TYPE.IMAGE) { return { type: [RNDocumentPicker.types.images], From 1ab9c3c2a054de48eb4d4d2a6267e00f6be9a33e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 14 Mar 2024 00:52:57 +0700 Subject: [PATCH 11/26] fix type attachment --- .../AttachmentPicker/index.native.tsx | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 651ebb3010de..754342a424dc 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -1,5 +1,6 @@ +import Str from 'expensify-common/lib/str'; import React, {useCallback, useMemo, useRef, useState} from 'react'; -import {Alert, View} from 'react-native'; +import {Alert, Image as RNImage, View} from 'react-native'; import RNFetchBlob from 'react-native-blob-util'; import RNDocumentPicker from 'react-native-document-picker'; import type {DocumentPickerResponse} from 'react-native-document-picker'; @@ -234,12 +235,30 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s setIsVisible(false); }; + const validateAndCompleteAttachmentSelection = useCallback( + (fileData: Asset & DocumentPickerResponse) => { + if (fileData.width === -1 || fileData.height === -1) { + showImageCorruptionAlert(); + return Promise.resolve(); + } + return getDataForUpload(fileData) + .then((result) => { + completeAttachmentSelection.current(result); + }) + .catch((error) => { + showGeneralAlert(error.message); + throw error; + }); + }, + [showGeneralAlert, showImageCorruptionAlert], + ); + /** * Handles the image/document picker result and * sends the selected attachment to the caller (parent component) */ const pickAttachment = useCallback( - (attachments: Array<(Asset & DocumentPickerResponse) | void> = []): Promise => { + (attachments: Array<(Asset & DocumentPickerResponse) | void> = []): Promise | undefined => { if (attachments.length === 0) { onCanceled.current(); return Promise.resolve(); @@ -251,22 +270,17 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s onCanceled.current(); return Promise.resolve(); } - - if (fileData.width === -1 || fileData.height === -1) { - showImageCorruptionAlert(); - return Promise.resolve(); - } - - return getDataForUpload(fileData) - .then((result) => { - completeAttachmentSelection.current(result); - }) - .catch((error) => { - showGeneralAlert(error.message); - throw error; + if (fileData.fileName && Str.isImage(fileData.fileName ?? fileData.name)) { + RNImage.getSize(fileData.fileCopyUri ?? fileData.uri, (width, height) => { + fileData.width = width; + fileData.height = height; + validateAndCompleteAttachmentSelection(fileData); }); + } else { + return validateAndCompleteAttachmentSelection(fileData); + } }, - [showGeneralAlert, showImageCorruptionAlert], + [validateAndCompleteAttachmentSelection], ); /** From 1450ea95997482d5e04daa7a202e69bd86bfe033 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 15 Mar 2024 17:28:47 +0700 Subject: [PATCH 12/26] fix type attchment picker --- .../AttachmentPicker/index.native.tsx | 21 ++++++++----------- src/components/AttachmentPicker/index.tsx | 1 + 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 754342a424dc..8d0b00d35352 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -3,7 +3,7 @@ import React, {useCallback, useMemo, useRef, useState} from 'react'; import {Alert, Image as RNImage, View} from 'react-native'; import RNFetchBlob from 'react-native-blob-util'; import RNDocumentPicker from 'react-native-document-picker'; -import type {DocumentPickerResponse} from 'react-native-document-picker'; +import type {DocumentPickerOptions, DocumentPickerResponse} from 'react-native-document-picker'; import {launchImageLibrary} from 'react-native-image-picker'; import type {Asset, Callback, CameraOptions, ImagePickerResponse} from 'react-native-image-picker'; import type {FileObject} from '@components/AttachmentModal'; @@ -33,11 +33,6 @@ type Item = { pickAttachment: () => Promise; }; -type DocumentPickerOptionsParams = { - type: string[]; - copyTo: 'cachesDirectory' | 'documentDirectory'; -}; - /** * See https://github.com/react-native-image-picker/react-native-image-picker/#options * for ImagePicker configuration options @@ -67,7 +62,7 @@ const getImagePickerOptions = (type: string): CameraOptions => { * @returns {Object} */ -const getDocumentPickerOptions = (type: string): DocumentPickerOptionsParams => { +const getDocumentPickerOptions = (type: string): DocumentPickerOptions<'ios' | 'android'> => { if (type === CONST.ATTACHMENT_PICKER_TYPE.IMAGE) { return { type: [RNDocumentPicker.types.images], @@ -91,8 +86,10 @@ const getDataForUpload = (fileData: Asset & DocumentPickerResponse): Promise showImagePicker(launchCamera), @@ -271,7 +268,8 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s return Promise.resolve(); } if (fileData.fileName && Str.isImage(fileData.fileName ?? fileData.name)) { - RNImage.getSize(fileData.fileCopyUri ?? fileData.uri, (width, height) => { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + RNImage.getSize(fileData.fileCopyUri || fileData.uri, (width, height) => { fileData.width = width; fileData.height = height; validateAndCompleteAttachmentSelection(fileData); @@ -337,7 +335,6 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s }} isVisible={isVisible} anchorRef={popoverRef} - // anchorPosition={styles.createMenuPosition} onModalHide={onModalHide.current} > diff --git a/src/components/AttachmentPicker/index.tsx b/src/components/AttachmentPicker/index.tsx index e8a23e29f114..a6ff9cb8d27a 100644 --- a/src/components/AttachmentPicker/index.tsx +++ b/src/components/AttachmentPicker/index.tsx @@ -86,5 +86,6 @@ function AttachmentPicker({children, type = CONST.ATTACHMENT_PICKER_TYPE.FILE}: ); } +AttachmentPicker.displayName = 'AttachmentPicker'; export default AttachmentPicker; From b729ae632b9103abc28ed069f678b0d9cea4a403 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 18 Mar 2024 15:58:42 +0700 Subject: [PATCH 13/26] fix type attachment picker --- src/components/AttachmentPicker/index.native.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 8d0b00d35352..e5f5e8333925 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -125,7 +125,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s */ const showGeneralAlert = useCallback( (message = '') => { - Alert.alert(translate('attachmentPicker.attachmentError'), `${message !== '' ? message : translate('attachmentPicker.errorWhileSelectingAttachment')}`); + Alert.alert(translate('attachmentPicker.attachmentError'), message || translate('attachmentPicker.errorWhileSelectingAttachment')); }, [translate], ); @@ -255,7 +255,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s * sends the selected attachment to the caller (parent component) */ const pickAttachment = useCallback( - (attachments: Array<(Asset & DocumentPickerResponse) | void> = []): Promise | undefined => { + (attachments: Array = []): Promise | undefined => { if (attachments.length === 0) { onCanceled.current(); return Promise.resolve(); @@ -267,7 +267,8 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s onCanceled.current(); return Promise.resolve(); } - if (fileData.fileName && Str.isImage(fileData.fileName ?? fileData.name)) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if ((fileData.fileName || fileData.name) && Str.isImage(fileData.fileName || fileData.name || '')) { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing RNImage.getSize(fileData.fileCopyUri || fileData.uri, (width, height) => { fileData.width = width; From d73fe5da777a6c86fef53678bcf980af11972d7c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 19 Mar 2024 22:59:32 +0700 Subject: [PATCH 14/26] fix: fallback in show general alert --- src/components/AttachmentPicker/index.native.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index e5f5e8333925..98a3ccacc80b 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -124,8 +124,8 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s * A generic handling when we don't know the exact reason for an error */ const showGeneralAlert = useCallback( - (message = '') => { - Alert.alert(translate('attachmentPicker.attachmentError'), message || translate('attachmentPicker.errorWhileSelectingAttachment')); + (message = translate('attachmentPicker.errorWhileSelectingAttachment')) => { + Alert.alert(translate('attachmentPicker.attachmentError'), message); }, [translate], ); From 8d405ff20fa82dddbb1c3b579d4cdd6f26ad3349 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 16:09:19 +0700 Subject: [PATCH 15/26] fix type attachment picker --- .../AttachmentPicker/index.native.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 98a3ccacc80b..1b8b867578a2 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -233,7 +233,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s }; const validateAndCompleteAttachmentSelection = useCallback( - (fileData: Asset & DocumentPickerResponse) => { + (fileData: Asset | DocumentPickerResponse) => { if (fileData.width === -1 || fileData.height === -1) { showImageCorruptionAlert(); return Promise.resolve(); @@ -255,12 +255,15 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s * sends the selected attachment to the caller (parent component) */ const pickAttachment = useCallback( - (attachments: Array = []): Promise | undefined => { + (attachments: Asset[] | DocumentPickerResponse[] | void = []): Promise | undefined => { + if(!attachments){ + onCanceled.current(); + return Promise.resolve(); + } if (attachments.length === 0) { onCanceled.current(); return Promise.resolve(); } - const fileData = attachments[0]; if (!fileData) { @@ -268,9 +271,11 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s return Promise.resolve(); } // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if ((fileData.fileName || fileData.name) && Str.isImage(fileData.fileName || fileData.name || '')) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - RNImage.getSize(fileData.fileCopyUri || fileData.uri, (width, height) => { + const fileDataName = ('fileName' in fileData && fileData.fileName) || ('name' in fileData && fileData.name) + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const fileDataUri = ('fileCopyUri' in fileData && fileData.fileCopyUri) || ('uri' in fileData && fileData.uri) || '' + if (fileDataName && Str.isImage(fileDataName)) { + RNImage.getSize(fileDataUri, (width, height) => { fileData.width = width; fileData.height = height; validateAndCompleteAttachmentSelection(fileData); @@ -295,7 +300,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s onModalHide.current = () => { setTimeout(() => { item.pickAttachment() - .then((result) => pickAttachment(result as Array)) + .then((result) => pickAttachment(result)) .catch(console.error) .finally(() => delete onModalHide.current); }, 200); From 166a61674f6da75b4388b980eb435116b59030c8 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 16:59:01 +0700 Subject: [PATCH 16/26] fix typecheck --- src/components/AttachmentModal.tsx | 2 +- .../AttachmentPicker/index.native.tsx | 46 +++++++++++++------ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index db4f54e7ed48..dc84c0e89a5f 100755 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -637,4 +637,4 @@ export default withOnyx({ }, })(memo(AttachmentModal)); -export type {Attachment, FileObject}; +export type {Attachment, FileObject, ImagePickerResponse}; diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 1b8b867578a2..89f35127bed0 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -6,7 +6,7 @@ import RNDocumentPicker from 'react-native-document-picker'; import type {DocumentPickerOptions, DocumentPickerResponse} from 'react-native-document-picker'; import {launchImageLibrary} from 'react-native-image-picker'; import type {Asset, Callback, CameraOptions, ImagePickerResponse} from 'react-native-image-picker'; -import type {FileObject} from '@components/AttachmentModal'; +import type {FileObject, ImagePickerResponse as FileResponse} from '@components/AttachmentModal'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Popover from '@components/Popover'; @@ -33,6 +33,17 @@ type Item = { pickAttachment: () => Promise; }; +type FileData = { + fileName?: string; + name?: string | undefined; + fileCopyUri?: string; + uri?: string; + width?: number; + height?: number; + size?: number; + type?: string; +}; + /** * See https://github.com/react-native-image-picker/react-native-image-picker/#options * for ImagePicker configuration options @@ -79,17 +90,17 @@ const getDocumentPickerOptions = (type: string): DocumentPickerOptions<'ios' | ' * The data returned from `show` is different on web and mobile, so use this function to ensure the data we * send to the xhr will be handled properly. */ -const getDataForUpload = (fileData: Asset & DocumentPickerResponse): Promise => { - const fileName = fileData.fileName ?? fileData.name ?? 'chat_attachment'; +const getDataForUpload = (fileData: FileResponse): Promise => { + const fileName = fileData.name ?? 'chat_attachment'; const fileResult: FileObject = { name: FileUtils.cleanFileName(fileName), type: fileData.type, width: fileData.width, height: fileData.height, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - uri: fileData.fileCopyUri || fileData.uri, + uri: fileData.uri, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - size: fileData.fileSize || fileData.size, + size: fileData.size, }; if (fileResult.size) { @@ -233,7 +244,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s }; const validateAndCompleteAttachmentSelection = useCallback( - (fileData: Asset | DocumentPickerResponse) => { + (fileData: FileResponse) => { if (fileData.width === -1 || fileData.height === -1) { showImageCorruptionAlert(); return Promise.resolve(); @@ -256,7 +267,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s */ const pickAttachment = useCallback( (attachments: Asset[] | DocumentPickerResponse[] | void = []): Promise | undefined => { - if(!attachments){ + if (!attachments) { onCanceled.current(); return Promise.resolve(); } @@ -271,17 +282,26 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s return Promise.resolve(); } // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const fileDataName = ('fileName' in fileData && fileData.fileName) || ('name' in fileData && fileData.name) + const fileDataName = ('fileName' in fileData && fileData.fileName) || ('name' in fileData && fileData.name) || ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const fileDataUri = ('fileCopyUri' in fileData && fileData.fileCopyUri) || ('uri' in fileData && fileData.uri) || '' + const fileDataUri = ('fileCopyUri' in fileData && fileData.fileCopyUri) || ('uri' in fileData && fileData.uri) || ''; + + const fileDataObject: FileResponse = { + name: fileDataName ?? '', + uri: fileDataUri, + size: ('size' in fileData && fileData.size) || ('fileSize' in fileData && fileData.fileSize) || null, + type: fileData.type ?? '', + width: ('width' in fileData && fileData.width) || undefined, + height: ('height' in fileData && fileData.height) || undefined, + }; if (fileDataName && Str.isImage(fileDataName)) { RNImage.getSize(fileDataUri, (width, height) => { - fileData.width = width; - fileData.height = height; - validateAndCompleteAttachmentSelection(fileData); + fileDataObject.width = width; + fileDataObject.height = height; + validateAndCompleteAttachmentSelection(fileDataObject); }); } else { - return validateAndCompleteAttachmentSelection(fileData); + return validateAndCompleteAttachmentSelection(fileDataObject); } }, [validateAndCompleteAttachmentSelection], From 1ae4e2b4b05a3d043db9fff7cf3d5785b08b7cbc Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 17:10:59 +0700 Subject: [PATCH 17/26] fix lint --- src/components/AttachmentPicker/index.native.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 89f35127bed0..54d7c87981c1 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -33,17 +33,6 @@ type Item = { pickAttachment: () => Promise; }; -type FileData = { - fileName?: string; - name?: string | undefined; - fileCopyUri?: string; - uri?: string; - width?: number; - height?: number; - size?: number; - type?: string; -}; - /** * See https://github.com/react-native-image-picker/react-native-image-picker/#options * for ImagePicker configuration options @@ -289,9 +278,12 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s const fileDataObject: FileResponse = { name: fileDataName ?? '', uri: fileDataUri, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing size: ('size' in fileData && fileData.size) || ('fileSize' in fileData && fileData.fileSize) || null, type: fileData.type ?? '', + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing width: ('width' in fileData && fileData.width) || undefined, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing height: ('height' in fileData && fileData.height) || undefined, }; if (fileDataName && Str.isImage(fileDataName)) { From 78a345ecdf6ced2cd679318ea5fa0346ca69346e Mon Sep 17 00:00:00 2001 From: dukenv0307 <129500732+dukenv0307@users.noreply.github.com> Date: Mon, 25 Mar 2024 15:18:57 +0700 Subject: [PATCH 18/26] Update src/components/AttachmentPicker/index.native.tsx Co-authored-by: Situ Chandra Shil <108292595+situchan@users.noreply.github.com> --- src/components/AttachmentPicker/index.native.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 54d7c87981c1..fb5a4b8300c9 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -80,7 +80,7 @@ const getDocumentPickerOptions = (type: string): DocumentPickerOptions<'ios' | ' * send to the xhr will be handled properly. */ const getDataForUpload = (fileData: FileResponse): Promise => { - const fileName = fileData.name ?? 'chat_attachment'; + const fileName = fileData.name || 'chat_attachment'; const fileResult: FileObject = { name: FileUtils.cleanFileName(fileName), type: fileData.type, From 9e390898852de33b60c8c83224891cb25069d4d2 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 25 Mar 2024 15:41:16 +0700 Subject: [PATCH 19/26] fix: eslint --- src/components/AttachmentPicker/index.native.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index fb5a4b8300c9..fc8f07cdf198 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -86,9 +86,7 @@ const getDataForUpload = (fileData: FileResponse): Promise => { type: fileData.type, width: fileData.width, height: fileData.height, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing uri: fileData.uri, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing size: fileData.size, }; @@ -256,11 +254,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s */ const pickAttachment = useCallback( (attachments: Asset[] | DocumentPickerResponse[] | void = []): Promise | undefined => { - if (!attachments) { - onCanceled.current(); - return Promise.resolve(); - } - if (attachments.length === 0) { + if (!attachments || attachments.length === 0) { onCanceled.current(); return Promise.resolve(); } @@ -270,22 +264,19 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s onCanceled.current(); return Promise.resolve(); } - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ const fileDataName = ('fileName' in fileData && fileData.fileName) || ('name' in fileData && fileData.name) || ''; - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const fileDataUri = ('fileCopyUri' in fileData && fileData.fileCopyUri) || ('uri' in fileData && fileData.uri) || ''; const fileDataObject: FileResponse = { name: fileDataName ?? '', uri: fileDataUri, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing size: ('size' in fileData && fileData.size) || ('fileSize' in fileData && fileData.fileSize) || null, type: fileData.type ?? '', - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing width: ('width' in fileData && fileData.width) || undefined, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing height: ('height' in fileData && fileData.height) || undefined, }; + /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */ if (fileDataName && Str.isImage(fileDataName)) { RNImage.getSize(fileDataUri, (width, height) => { fileDataObject.width = width; From d3b891e2859c0ea76010d158ba94478bdfa0ec29 Mon Sep 17 00:00:00 2001 From: dukenv0307 <129500732+dukenv0307@users.noreply.github.com> Date: Tue, 26 Mar 2024 10:11:56 +0700 Subject: [PATCH 20/26] Update src/components/AttachmentPicker/types.ts Co-authored-by: Tim Golen --- src/components/AttachmentPicker/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/AttachmentPicker/types.ts b/src/components/AttachmentPicker/types.ts index 574d324fb2c4..9b48ac1b9e19 100644 --- a/src/components/AttachmentPicker/types.ts +++ b/src/components/AttachmentPicker/types.ts @@ -30,6 +30,7 @@ type AttachmentPickerProps = { * * */ children: (props: {openPicker: OpenPickerFunction}) => ReactNode; + /** The types of files that can be selected with this picker. */ type?: ValueOf; }; From b0fd19ef2693f3e8079af9f729afc8b8a24a83c6 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 26 Mar 2024 10:44:58 +0700 Subject: [PATCH 21/26] Add description for each property --- .../AttachmentPicker/index.native.tsx | 3 + .../AttachmentPicker/launchCamera/types.ts | 57 +++++++++++++++++++ src/components/AttachmentPicker/types.ts | 7 +++ 3 files changed, 67 insertions(+) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index fc8f07cdf198..19244913174d 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -28,8 +28,11 @@ type AttachmentPickerProps = BaseAttachmentPickerProps & { }; type Item = { + /** The icon associated with the item. */ icon: IconAsset; + /** The key in the translations file to use for the title */ textTranslationKey: TranslationPaths; + /** Function to call when the user clicks the item */ pickAttachment: () => Promise; }; diff --git a/src/components/AttachmentPicker/launchCamera/types.ts b/src/components/AttachmentPicker/launchCamera/types.ts index 23040d94bb67..89617a8f8022 100644 --- a/src/components/AttachmentPicker/launchCamera/types.ts +++ b/src/components/AttachmentPicker/launchCamera/types.ts @@ -1,48 +1,99 @@ +/** + * A callback function used to handle the response from the image picker. + * + * @param response - The response object containing information about the picked images or any errors encountered. + */ type Callback = (response: ImagePickerResponse) => void; + type OptionsCommon = { + /** Specifies the type of media to be captured. */ mediaType: MediaType; + /** Specifies the maximum width of the media to be captured. */ maxWidth?: number; + /** Specifies the maximum height of the media to be captured. */ maxHeight?: number; + /** Specifies the quality of the photo to be captured. */ quality?: PhotoQuality; + /** Specifies the video quality for video capture. */ videoQuality?: AndroidVideoOptions | IOSVideoOptions; + /** Specifies whether to include the media in base64 format. */ includeBase64?: boolean; + /** Specifies whether to include extra information about the captured media. */ includeExtra?: boolean; + /** Specifies the presentation style for the media picker. */ presentationStyle?: 'currentContext' | 'fullScreen' | 'pageSheet' | 'formSheet' | 'popover' | 'overFullScreen' | 'overCurrentContext'; }; + type ImageLibraryOptions = OptionsCommon & { + /** Specifies the maximum number of images that can be selected from the library. */ selectionLimit?: number; }; + type CameraOptions = OptionsCommon & { + /** Specifies the maximum duration limit. */ durationLimit?: number; + /** Specifies whether to save captured media. */ saveToPhotos?: boolean; + /** Specifies the type of camera to be used. */ cameraType?: CameraType; }; + type Asset = { + /** Base64 representation of the asset. */ base64?: string; + /** URI pointing to the asset. */ uri?: string; + /** Width of the asset. */ width?: number; + /** Height of the asset. */ height?: number; + /** Size of the asset file in bytes. */ fileSize?: number; + /** Type of the asset. */ type?: string; + /** Name of the asset file. */ fileName?: string; + /** Duration of the asset. */ duration?: number; + /** Bitrate of the asset. */ bitrate?: number; + /** Timestamp of when the asset was created or modified. */ timestamp?: string; + /** ID of the asset. */ id?: string; }; + type ImagePickerResponse = { + /** Indicates whether the image picker operation was canceled. */ didCancel?: boolean; + /** The error code, if an error occurred during the image picking process. */ errorCode?: ErrorCode; + /** A descriptive error message, if an error occurred during the image picking process. */ errorMessage?: string; + /** An array of assets representing the picked images. */ assets?: Asset[]; }; + +/** Represents the quality options. */ type PhotoQuality = 0 | 0.1 | 0.2 | 0.3 | 0.4 | 0.5 | 0.6 | 0.7 | 0.8 | 0.9 | 1; + +/** Represents the type of camera to be used. */ type CameraType = 'back' | 'front'; + +/** Represents the type of media to be captured. */ type MediaType = 'photo' | 'video' | 'mixed'; + +/** Represents the quality options for video capture on Android devices. */ type AndroidVideoOptions = 'low' | 'high'; + +/** Represents the quality options for video capture on iOS devices. */ type IOSVideoOptions = 'low' | 'medium' | 'high'; + +/** Represents various error codes that may occur during camera operations. */ type ErrorCode = 'camera_unavailable' | 'permission' | 'others'; + class ErrorLaunchCamera extends Error { + /** The error code associated with the error. */ errorCode: ErrorCode; constructor(message: string, errorCode: ErrorCode) { @@ -51,6 +102,12 @@ class ErrorLaunchCamera extends Error { } } +/** + * A function used to launch the camera with specified options and handle the callback. + * + * @param options - The options for the camera, specifying various settings. + * @param callback - The callback function to handle the response from the camera operation. + */ type LaunchCamera = (options: CameraOptions, callback: Callback) => void; export {ErrorLaunchCamera}; diff --git a/src/components/AttachmentPicker/types.ts b/src/components/AttachmentPicker/types.ts index 9b48ac1b9e19..445d79bce07a 100644 --- a/src/components/AttachmentPicker/types.ts +++ b/src/components/AttachmentPicker/types.ts @@ -4,10 +4,17 @@ import type {FileObject} from '@components/AttachmentModal'; import type CONST from '@src/CONST'; type PickerOptions = { + /** A callback that will be called with the selected attachment. */ onPicked: (file: FileObject) => void; + /** A callback that will be called without a selected attachment. */ onCanceled?: () => void; }; +/** + * A function used to open a picker with specified options. + * + * @param options - The options for the picker, including callbacks for handling picked file and cancellation. + */ type OpenPickerFunction = (options: PickerOptions) => void; type AttachmentPickerProps = { From 594fafa634460c14b12af651f2444ea7c58c1f5e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 26 Mar 2024 10:57:29 +0700 Subject: [PATCH 22/26] fix type check --- src/components/AttachmentModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index da8570e0bb21..c915b2f227aa 100755 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -284,14 +284,14 @@ function AttachmentModal({ }, [transaction, report]); const isValidFile = useCallback((fileObject: FileObject) => { - if (fileObject.size !== undefined && fileObject.size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { + if (fileObject.size && fileObject.size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { setIsAttachmentInvalid(true); setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooLarge'); setAttachmentInvalidReason('attachmentPicker.sizeExceeded'); return false; } - if (fileObject.size !== undefined && fileObject.size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { + if (fileObject.size && fileObject.size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { setIsAttachmentInvalid(true); setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooSmall'); setAttachmentInvalidReason('attachmentPicker.sizeNotMet'); From be0c526de520ce0af151fcc2e152a55654d851a0 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 26 Mar 2024 11:01:22 +0700 Subject: [PATCH 23/26] fix type check --- src/components/AvatarWithImagePicker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AvatarWithImagePicker.tsx b/src/components/AvatarWithImagePicker.tsx index f94c2256eae4..d803aa97fd0f 100644 --- a/src/components/AvatarWithImagePicker.tsx +++ b/src/components/AvatarWithImagePicker.tsx @@ -226,8 +226,8 @@ function AvatarWithImagePicker({ setIsMenuVisible(false); setImageData({ uri: image.uri ?? '', - name: image.name, - type: image.type, + name: image.name ?? '', + type: image.type ?? '', }); }); }; From 21cb31b574589bb393c610e6c69d20e2a76fda31 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 26 Mar 2024 22:08:18 +0700 Subject: [PATCH 24/26] import lanchCamera with alias name --- .../launchCamera/launchCamera.android.ts | 8 ++++---- .../AttachmentPicker/launchCamera/launchCamera.ios.ts | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts b/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts index 748639f0f903..c4359b06d619 100644 --- a/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts +++ b/src/components/AttachmentPicker/launchCamera/launchCamera.android.ts @@ -1,5 +1,5 @@ import {PermissionsAndroid} from 'react-native'; -import {launchCamera} from 'react-native-image-picker'; +import {launchCamera as launchCameraImagePicker} from 'react-native-image-picker'; import type {LaunchCamera} from './types'; import {ErrorLaunchCamera} from './types'; @@ -9,7 +9,7 @@ import {ErrorLaunchCamera} from './types'; * If the user deny permission the callback will be called with an error response * in the same format as the error returned by react-native-image-picker */ -const launchCameraAndroid: LaunchCamera = (options, callback) => { +const launchCamera: LaunchCamera = (options, callback) => { // Checks current camera permissions and prompts the user in case they aren't granted PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA) .then((permission) => { @@ -17,7 +17,7 @@ const launchCameraAndroid: LaunchCamera = (options, callback) => { throw new ErrorLaunchCamera('User did not grant permissions', 'permission'); } - launchCamera(options, callback); + launchCameraImagePicker(options, callback); }) .catch((error) => { /* Intercept the permission error as well as any other errors and call the callback @@ -29,4 +29,4 @@ const launchCameraAndroid: LaunchCamera = (options, callback) => { }); }; -export default launchCameraAndroid; +export default launchCamera; diff --git a/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts b/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts index ae421b583f6b..568fc90421e3 100644 --- a/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts +++ b/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts @@ -1,4 +1,4 @@ -import {launchCamera} from 'react-native-image-picker'; +import {launchCamera as launchCameraImagePicker} from 'react-native-image-picker'; import {PERMISSIONS, request, RESULTS} from 'react-native-permissions'; import type {LaunchCamera} from './types'; import {ErrorLaunchCamera} from './types'; @@ -9,14 +9,14 @@ import {ErrorLaunchCamera} from './types'; * If the user deny permission the callback will be called with an error response * in the same format as the error returned by react-native-image-picker */ -const launchCameraIOS: LaunchCamera = (options, callback) => { +const launchCamera: LaunchCamera = (options, callback) => { // Checks current camera permissions and prompts the user in case they aren't granted request(PERMISSIONS.IOS.CAMERA) .then((permission) => { if (permission !== RESULTS.GRANTED) { throw new ErrorLaunchCamera('User did not grant permissions', 'permission'); } - launchCamera(options, callback); + launchCameraImagePicker(options, callback); }) .catch((error) => { /* Intercept the permission error as well as any other errors and call the callback @@ -27,4 +27,5 @@ const launchCameraIOS: LaunchCamera = (options, callback) => { }); }); }; -export default launchCameraIOS; + +export default launchCamera; From 9c26f070e576761779a29c8cb8d3807d0a05189b Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 26 Mar 2024 22:09:48 +0700 Subject: [PATCH 25/26] add new line --- src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts b/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts index 568fc90421e3..7ed8352cf970 100644 --- a/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts +++ b/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts @@ -16,6 +16,7 @@ const launchCamera: LaunchCamera = (options, callback) => { if (permission !== RESULTS.GRANTED) { throw new ErrorLaunchCamera('User did not grant permissions', 'permission'); } + launchCameraImagePicker(options, callback); }) .catch((error) => { From 640283e47010ac3521004698febdb5cbc9d73496 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 26 Mar 2024 22:10:18 +0700 Subject: [PATCH 26/26] fix lint --- .../AttachmentPicker/launchCamera/launchCamera.ios.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts b/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts index 7ed8352cf970..7015d4d9ed13 100644 --- a/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts +++ b/src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts @@ -16,7 +16,7 @@ const launchCamera: LaunchCamera = (options, callback) => { if (permission !== RESULTS.GRANTED) { throw new ErrorLaunchCamera('User did not grant permissions', 'permission'); } - + launchCameraImagePicker(options, callback); }) .catch((error) => {